From 055599db3d1dc205fd61fd2f82c067a09ee8ca26 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Wed, 27 Sep 2023 10:58:21 -0400 Subject: [PATCH 01/10] nibbles impl --- crypto/statetrie/nibbles/nibbles.go | 170 +++++++++++++++++++ crypto/statetrie/nibbles/nibbles_test.go | 200 +++++++++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 crypto/statetrie/nibbles/nibbles.go create mode 100644 crypto/statetrie/nibbles/nibbles_test.go diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go new file mode 100644 index 0000000000..90a385fd30 --- /dev/null +++ b/crypto/statetrie/nibbles/nibbles.go @@ -0,0 +1,170 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package nibbles + +import ( + "bytes" + "errors" +) + +// Nibbles are 4-bit values stored in an 8-bit byte arrays +type Nibbles []byte + +const ( + // evenIndicator for serialization when the last nibble in a byte array + // is part of the nibble array. + evenIndicator = 0x01 + // oddIndicator for when it is not. + oddIndicator = 0x03 +) + +// MakeNibbles returns a nibble array from the byte array. If oddLength is true, +// the last 4 bits of the last byte of the array are ignored. +func MakeNibbles(data []byte, oddLength bool) Nibbles { + return Unpack(data, oddLength) +} + +// Unpack the byte array into a nibble array. If oddLength is true, the last 4 +// bits of the last byte of the array are ignored. Allocates a new byte +// slice. +// +// [0x12, 0x30], true -> [0x1, 0x2, 0x3] +// [0x12, 0x34], false -> [0x1, 0x2, 0x3, 0x4] +// [0x12, 0x34], true -> [0x1, 0x2, 0x3] <-- last byte last 4 bits ignored +// [], false -> [] +// never to be called with [], true +func Unpack(data []byte, oddLength bool) Nibbles { + length := len(data) * 2 + if oddLength { + length = length - 1 + } + ns := make([]byte, length) + + j := 0 + for i := 0; i < length; i++ { + if i%2 == 0 { + ns[i] = data[j] >> 4 + } else { + ns[i] = data[j] & 0x0f + j++ + } + } + return ns +} + +// Pack the nibble array into a byte array. +// Return the byte array and a bool indicating if the last byte is a full byte or +// only the high 4 bits are part of the encoding +// the last four bits of a oddLength byte encoding will always be zero. +// Allocates a new byte slice. +// +// [0x1, 0x2, 0x3] -> [0x12, 0x30], true +// [0x1, 0x2, 0x3, 0x4] -> [0x12, 0x34], false +// [0x1] -> [0x10], true +// [] -> [], false +func Pack(nyb Nibbles) ([]byte, bool) { + length := len(nyb) + data := make([]byte, length/2+length%2) + for i := 0; i < length; i++ { + if i%2 == 0 { + data[i/2] = nyb[i] << 4 + } else { + data[i/2] = data[i/2] | nyb[i] + } + } + + return data, length%2 != 0 +} + +// Equal returns true if the two nibble arrays are equal +// [0x1, 0x2, 0x3], [0x1, 0x2, 0x3] -> true +// [0x1, 0x2, 0x3], [0x1, 0x2, 0x4] -> false +// [0x1, 0x2, 0x3], [0x1] -> false +// [0x1, 0x2, 0x3], [0x1, 0x2, 0x3, 0x4] -> false +// [], [] -> true +// [], [0x1] -> false +func Equal(nyb1 Nibbles, nyb2 Nibbles) bool { + return bytes.Equal(nyb1, nyb2) +} + +// ShiftLeft returns a slice of nyb1 that contains the Nibbles after the first +// numNibbles +func ShiftLeft(nyb1 Nibbles, numNibbles int) Nibbles { + if numNibbles <= 0 { + return nyb1 + } + if numNibbles > len(nyb1) { + return nyb1[:0] + } + + return nyb1[numNibbles:] +} + +// SharedPrefix returns a slice from nyb1 that contains the shared prefix +// between nyb1 and nyb2 +func SharedPrefix(nyb1 Nibbles, nyb2 Nibbles) Nibbles { + minLength := len(nyb1) + if len(nyb2) < minLength { + minLength = len(nyb2) + } + for i := 0; i < minLength; i++ { + if nyb1[i] != nyb2[i] { + return nyb1[:i] + } + } + return nyb1[:minLength] +} + +// Serialize returns a byte array that represents the Nibbles +// an empty nibble array is serialized as a single byte with value 0x3 +// as the empty nibble is considered to be full width +// +// [0x1, 0x2, 0x3] -> [0x12, 0x30, 0x01] +// [0x1, 0x2, 0x3, 0x4] -> [0x12, 0x34, 0x03] +// [] -> [0x03] +func Serialize(nyb Nibbles) (data []byte) { + p, h := Pack(nyb) + length := len(p) + output := make([]byte, length+1) + copy(output, p) + if h { + // 0x1 is the arbitrary odd length indicator + output[length] = evenIndicator + } else { + // 0x3 is the arbitrary even length indicator + output[length] = oddIndicator + } + + return output +} + +// Deserialize returns a nibble array from the byte array. +func Deserialize(encoding []byte) (Nibbles, error) { + var ns Nibbles + length := len(encoding) + if length == 0 { + return nil, errors.New("invalid encoding") + } + if encoding[length-1] == evenIndicator { + ns = Unpack(encoding[:length-1], true) + } else if encoding[length-1] == oddIndicator { + ns = Unpack(encoding[:length-1], false) + } else { + return nil, errors.New("invalid encoding") + } + return ns, nil +} diff --git a/crypto/statetrie/nibbles/nibbles_test.go b/crypto/statetrie/nibbles/nibbles_test.go new file mode 100644 index 0000000000..c3833d1da1 --- /dev/null +++ b/crypto/statetrie/nibbles/nibbles_test.go @@ -0,0 +1,200 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package nibbles + +import ( + "bytes" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" + "math/rand" + "testing" + "time" +) + +func TestNibblesRandom(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + seed := time.Now().UnixNano() + localRand := rand.New(rand.NewSource(seed)) + defer func() { + if t.Failed() { + t.Logf("The seed was %d", seed) + } + }() + + for i := 0; i < 1_000; i++ { + length := localRand.Intn(8192) + 1 + data := make([]byte, length) + localRand.Read(data) + half := localRand.Intn(2) == 0 // half of the time, we have an odd number of nibbles + if half && localRand.Intn(2) == 0 { + data[len(data)-1] &= 0xf0 // sometimes clear the last nibble, sometimes do not + } + nibbles := MakeNibbles(data, half) + + data2 := Serialize(nibbles) + nibbles2, err := Deserialize(data2) + require.NoError(t, err) + require.Equal(t, nibbles, nibbles2) + + if half { + data[len(data)-1] &= 0xf0 // clear last nibble + } + packed, odd := Pack(nibbles) + require.Equal(t, odd, half) + require.Equal(t, packed, data) + unpacked := Unpack(packed, odd) + require.Equal(t, nibbles, unpacked) + + packed, odd = Pack(nibbles2) + require.Equal(t, odd, half) + require.Equal(t, packed, data) + unpacked = Unpack(packed, odd) + require.Equal(t, nibbles2, unpacked) + } +} + +func TestNibbles(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + sampleNibbles := []Nibbles{ + {0x0, 0x1, 0x2, 0x3, 0x4}, + {0x4, 0x1, 0x2, 0x3, 0x4}, + {0x0, 0x0, 0x2, 0x3, 0x5}, + {0x0, 0x1, 0x2, 0x3, 0x4, 0x5}, + {}, + {0x1}, + } + + sampleNibblesPacked := [][]byte{ + {0x01, 0x23, 0x40}, + {0x41, 0x23, 0x40}, + {0x00, 0x23, 0x50}, + {0x01, 0x23, 0x45}, + {}, + {0x10}, + } + + sampleNibblesShifted1 := []Nibbles{ + {0x1, 0x2, 0x3, 0x4}, + {0x1, 0x2, 0x3, 0x4}, + {0x0, 0x2, 0x3, 0x5}, + {0x1, 0x2, 0x3, 0x4, 0x5}, + {}, + {}, + } + + sampleNibblesShifted2 := []Nibbles{ + {0x2, 0x3, 0x4}, + {0x2, 0x3, 0x4}, + {0x2, 0x3, 0x5}, + {0x2, 0x3, 0x4, 0x5}, + {}, + {}, + } + + for i, n := range sampleNibbles { + b, oddLength := Pack(n) + if oddLength { + // require that oddLength packs returns a byte slice with the last nibble set to 0x0 + require.Equal(t, b[len(b)-1]&0x0f == 0x00, true) + } + + require.Equal(t, oddLength == (len(n)%2 == 1), true) + require.Equal(t, bytes.Equal(b, sampleNibblesPacked[i]), true) + + unp := Unpack(b, oddLength) + require.Equal(t, bytes.Equal(unp, n), true) + + } + for i, n := range sampleNibbles { + require.Equal(t, bytes.Equal(ShiftLeft(n, -2), sampleNibbles[i]), true) + require.Equal(t, bytes.Equal(ShiftLeft(n, -1), sampleNibbles[i]), true) + require.Equal(t, bytes.Equal(ShiftLeft(n, 0), sampleNibbles[i]), true) + require.Equal(t, bytes.Equal(ShiftLeft(n, 1), sampleNibblesShifted1[i]), true) + require.Equal(t, bytes.Equal(ShiftLeft(n, 2), sampleNibblesShifted2[i]), true) + } + + sampleSharedNibbles := [][]Nibbles{ + {{0x0, 0x1, 0x2, 0x9, 0x2}, {0x0, 0x1, 0x2}}, + {{0x4, 0x1}, {0x4, 0x1}}, + {{0x9, 0x2, 0x3}, {}}, + {{0x0}, {0x0}}, + {{}, {}}, + } + for i, n := range sampleSharedNibbles { + shared := SharedPrefix(n[0], sampleNibbles[i]) + require.Equal(t, bytes.Equal(shared, n[1]), true) + shared = SharedPrefix(sampleNibbles[i], n[0]) + require.Equal(t, bytes.Equal(shared, n[1]), true) + } + + sampleSerialization := []Nibbles{ + {0x0, 0x1, 0x2, 0x9, 0x2}, + {0x4, 0x1}, + {0x4, 0x1, 0x4, 0xf}, + {0x4, 0x1, 0x4, 0xf, 0x0}, + {0x9, 0x2, 0x3}, + {}, + {0x05}, + {}, + } + + for _, n := range sampleSerialization { + nbytes := Serialize(n) + n2, err := Deserialize(nbytes) + require.NoError(t, err) + require.Equal(t, bytes.Equal(n, n2), true) + } + + makeNibblesTestExpected := Nibbles{0x0, 0x1, 0x2, 0x9, 0x2} + makeNibblesTestData := []byte{0x01, 0x29, 0x20} + mntr := MakeNibbles(makeNibblesTestData, true) + require.Equal(t, bytes.Equal(mntr, makeNibblesTestExpected), true) + makeNibblesTestExpectedFW := Nibbles{0x0, 0x1, 0x2, 0x9, 0x2, 0x0} + mntr2 := MakeNibbles(makeNibblesTestData, false) + require.Equal(t, bytes.Equal(mntr2, makeNibblesTestExpectedFW), true) + + sampleEqualFalse := [][]Nibbles{ + {{0x0, 0x1, 0x2, 0x9, 0x2}, {0x0, 0x1, 0x2, 0x9}}, + {{0x0, 0x1, 0x2, 0x9}, {0x0, 0x1, 0x2, 0x9, 0x2}}, + {{0x0, 0x1, 0x2, 0x9, 0x2}, {}}, + {{}, {0x0, 0x1, 0x2, 0x9, 0x2}}, + {{0x0}, {}}, + {{}, {0x0}}, + {{}, {0x1}}, + } + for _, n := range sampleEqualFalse { + ds := Serialize(n[0]) + us, e := Deserialize(ds) + require.NoError(t, e) + require.Equal(t, Equal(n[0], us), true) + require.Equal(t, Equal(n[0], n[0]), true) + require.Equal(t, Equal(us, n[0]), true) + require.Equal(t, Equal(n[0], n[1]), false) + require.Equal(t, Equal(us, n[1]), false) + require.Equal(t, Equal(n[1], n[0]), false) + require.Equal(t, Equal(n[1], us), false) + } + + _, e := Deserialize([]byte{}) + require.Error(t, e) + _, e = Deserialize([]byte{0x02}) + require.Error(t, e) +} From d9f8546c831471680862e4bd08b9eae8bf542b5c Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Fri, 29 Sep 2023 13:03:13 +0100 Subject: [PATCH 02/10] cr --- crypto/statetrie/nibbles/nibbles_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crypto/statetrie/nibbles/nibbles_test.go b/crypto/statetrie/nibbles/nibbles_test.go index c3833d1da1..91cc59487f 100644 --- a/crypto/statetrie/nibbles/nibbles_test.go +++ b/crypto/statetrie/nibbles/nibbles_test.go @@ -17,12 +17,14 @@ package nibbles import ( + "time" "bytes" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" - "math/rand" "testing" - "time" + "math/rand" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestNibblesRandom(t *testing.T) { From 87d9c411da57ed0cafd57821d18d1190887cd16b Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Sun, 8 Oct 2023 00:53:02 +0100 Subject: [PATCH 03/10] cr --- crypto/statetrie/nibbles/nibbles.go | 41 +++++++++++------------- crypto/statetrie/nibbles/nibbles_test.go | 14 ++++++-- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 90a385fd30..3eafb6bd26 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -25,29 +25,23 @@ import ( type Nibbles []byte const ( - // evenIndicator for serialization when the last nibble in a byte array - // is part of the nibble array. - evenIndicator = 0x01 - // oddIndicator for when it is not. - oddIndicator = 0x03 + // oddIndicator for serialization when the last nibble in a byte array + // is not part of the nibble array. + oddIndicator = 0x01 + // evenIndicator for when it is. + evenIndicator = 0x03 ) // MakeNibbles returns a nibble array from the byte array. If oddLength is true, -// the last 4 bits of the last byte of the array are ignored. -func MakeNibbles(data []byte, oddLength bool) Nibbles { - return Unpack(data, oddLength) -} - -// Unpack the byte array into a nibble array. If oddLength is true, the last 4 -// bits of the last byte of the array are ignored. Allocates a new byte -// slice. +// the last 4 bits of the last byte of the array are ignored. // // [0x12, 0x30], true -> [0x1, 0x2, 0x3] // [0x12, 0x34], false -> [0x1, 0x2, 0x3, 0x4] // [0x12, 0x34], true -> [0x1, 0x2, 0x3] <-- last byte last 4 bits ignored // [], false -> [] // never to be called with [], true -func Unpack(data []byte, oddLength bool) Nibbles { +// Allocates a new byte slice. +func MakeNibbles(data []byte, oddLength bool) Nibbles { length := len(data) * 2 if oddLength { length = length - 1 @@ -142,11 +136,11 @@ func Serialize(nyb Nibbles) (data []byte) { output := make([]byte, length+1) copy(output, p) if h { - // 0x1 is the arbitrary odd length indicator - output[length] = evenIndicator - } else { - // 0x3 is the arbitrary even length indicator + // 0x01 is the odd length indicator output[length] = oddIndicator + } else { + // 0x03 is the even length indicator + output[length] = evenIndicator } return output @@ -159,10 +153,13 @@ func Deserialize(encoding []byte) (Nibbles, error) { if length == 0 { return nil, errors.New("invalid encoding") } - if encoding[length-1] == evenIndicator { - ns = Unpack(encoding[:length-1], true) - } else if encoding[length-1] == oddIndicator { - ns = Unpack(encoding[:length-1], false) + if encoding[length-1] == oddIndicator { + if length == 1 { + return nil, errors.New("invalid encoding") + } + ns = MakeNibbles(encoding[:length-1], true) + } else if encoding[length-1] == evenIndicator { + ns = MakeNibbles(encoding[:length-1], false) } else { return nil, errors.New("invalid encoding") } diff --git a/crypto/statetrie/nibbles/nibbles_test.go b/crypto/statetrie/nibbles/nibbles_test.go index 91cc59487f..ef8ff32c1b 100644 --- a/crypto/statetrie/nibbles/nibbles_test.go +++ b/crypto/statetrie/nibbles/nibbles_test.go @@ -60,17 +60,25 @@ func TestNibblesRandom(t *testing.T) { packed, odd := Pack(nibbles) require.Equal(t, odd, half) require.Equal(t, packed, data) - unpacked := Unpack(packed, odd) + unpacked := MakeNibbles(packed, odd) require.Equal(t, nibbles, unpacked) packed, odd = Pack(nibbles2) require.Equal(t, odd, half) require.Equal(t, packed, data) - unpacked = Unpack(packed, odd) + unpacked = MakeNibbles(packed, odd) require.Equal(t, nibbles2, unpacked) } } +func TestNibblesDeserialize(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + enc := []byte{0x01} + _, err := Deserialize(enc) + require.Error(t, err, "should return invalid encoding error") +} + func TestNibbles(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -121,7 +129,7 @@ func TestNibbles(t *testing.T) { require.Equal(t, oddLength == (len(n)%2 == 1), true) require.Equal(t, bytes.Equal(b, sampleNibblesPacked[i]), true) - unp := Unpack(b, oddLength) + unp := MakeNibbles(b, oddLength) require.Equal(t, bytes.Equal(unp, n), true) } From c89239b22e34faa8efdaacee2518009797077b26 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Wed, 8 Nov 2023 22:32:11 -0500 Subject: [PATCH 04/10] fmt --- crypto/statetrie/nibbles/nibbles.go | 8 ++++---- crypto/statetrie/nibbles/nibbles_test.go | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 3eafb6bd26..10e175832a 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -33,7 +33,7 @@ const ( ) // MakeNibbles returns a nibble array from the byte array. If oddLength is true, -// the last 4 bits of the last byte of the array are ignored. +// the last 4 bits of the last byte of the array are ignored. // // [0x12, 0x30], true -> [0x1, 0x2, 0x3] // [0x12, 0x34], false -> [0x1, 0x2, 0x3, 0x4] @@ -154,9 +154,9 @@ func Deserialize(encoding []byte) (Nibbles, error) { return nil, errors.New("invalid encoding") } if encoding[length-1] == oddIndicator { - if length == 1 { - return nil, errors.New("invalid encoding") - } + if length == 1 { + return nil, errors.New("invalid encoding") + } ns = MakeNibbles(encoding[:length-1], true) } else if encoding[length-1] == evenIndicator { ns = MakeNibbles(encoding[:length-1], false) diff --git a/crypto/statetrie/nibbles/nibbles_test.go b/crypto/statetrie/nibbles/nibbles_test.go index ef8ff32c1b..6ab8092fbe 100644 --- a/crypto/statetrie/nibbles/nibbles_test.go +++ b/crypto/statetrie/nibbles/nibbles_test.go @@ -17,10 +17,10 @@ package nibbles import ( - "time" "bytes" - "testing" "math/rand" + "testing" + "time" "github.com/stretchr/testify/require" @@ -74,9 +74,9 @@ func TestNibblesRandom(t *testing.T) { func TestNibblesDeserialize(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - enc := []byte{0x01} - _, err := Deserialize(enc) - require.Error(t, err, "should return invalid encoding error") + enc := []byte{0x01} + _, err := Deserialize(enc) + require.Error(t, err, "should return invalid encoding error") } func TestNibbles(t *testing.T) { From 1a0fd5295f834600814ae811de660a598936ea68 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Wed, 8 Nov 2023 22:42:15 -0500 Subject: [PATCH 05/10] serialize-deserialize capacity --- crypto/statetrie/nibbles/nibbles.go | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 10e175832a..6dc5e53476 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2018-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify @@ -72,7 +72,7 @@ func MakeNibbles(data []byte, oddLength bool) Nibbles { // [] -> [], false func Pack(nyb Nibbles) ([]byte, bool) { length := len(nyb) - data := make([]byte, length/2+length%2) + data := make([]byte, length/2+length%2, length/2+length%2+1) for i := 0; i < length; i++ { if i%2 == 0 { data[i/2] = nyb[i] << 4 @@ -131,19 +131,13 @@ func SharedPrefix(nyb1 Nibbles, nyb2 Nibbles) Nibbles { // [0x1, 0x2, 0x3, 0x4] -> [0x12, 0x34, 0x03] // [] -> [0x03] func Serialize(nyb Nibbles) (data []byte) { - p, h := Pack(nyb) - length := len(p) - output := make([]byte, length+1) - copy(output, p) - if h { + if p, h := Pack(nyb); h { // 0x01 is the odd length indicator - output[length] = oddIndicator + return append(p, oddIndicator) } else { // 0x03 is the even length indicator - output[length] = evenIndicator + return append(p, evenIndicator) } - - return output } // Deserialize returns a nibble array from the byte array. From d3f1effe65181082b7a1f59065630c0b82a3f76e Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Wed, 8 Nov 2023 23:10:02 -0500 Subject: [PATCH 06/10] addl test --- crypto/statetrie/nibbles/nibbles_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crypto/statetrie/nibbles/nibbles_test.go b/crypto/statetrie/nibbles/nibbles_test.go index 6ab8092fbe..527862fbeb 100644 --- a/crypto/statetrie/nibbles/nibbles_test.go +++ b/crypto/statetrie/nibbles/nibbles_test.go @@ -18,6 +18,7 @@ package nibbles import ( "bytes" + "fmt" "math/rand" "testing" "time" @@ -170,7 +171,14 @@ func TestNibbles(t *testing.T) { nbytes := Serialize(n) n2, err := Deserialize(nbytes) require.NoError(t, err) - require.Equal(t, bytes.Equal(n, n2), true) + require.True(t, bytes.Equal(n, n2)) + require.Equal(t, len(nbytes), len(n)/2+len(n)%2+1, fmt.Sprintf("nbytes: %v, n: %v", nbytes, n)) + if len(n)%2 == 0 { + require.Equal(t, nbytes[len(nbytes)-1], uint8(evenIndicator)) + } else { + require.Equal(t, nbytes[len(nbytes)-1], uint8(oddIndicator)) + require.Equal(t, nbytes[len(nbytes)-2]&0x0F, uint8(0)) + } } makeNibblesTestExpected := Nibbles{0x0, 0x1, 0x2, 0x9, 0x2} From 6e960edafbb1420846806684acbc7d28caa3cc51 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Thu, 9 Nov 2023 10:43:51 -0500 Subject: [PATCH 07/10] reviewdog --- crypto/statetrie/nibbles/nibbles.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 6dc5e53476..0419f31571 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -134,10 +134,9 @@ func Serialize(nyb Nibbles) (data []byte) { if p, h := Pack(nyb); h { // 0x01 is the odd length indicator return append(p, oddIndicator) - } else { - // 0x03 is the even length indicator - return append(p, evenIndicator) } + // 0x03 is the even length indicator + return append(p, evenIndicator) } // Deserialize returns a nibble array from the byte array. From a6118ba94d88ad8de637318f59771d1922eb65c1 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Thu, 9 Nov 2023 10:46:25 -0500 Subject: [PATCH 08/10] licence --- crypto/statetrie/nibbles/nibbles.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 0419f31571..47b62d4129 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2023 Algorand, Inc. +// Copyright (C) 2019-2023 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify From 6dc8758f5e3526f4d25e401f8c55e43890f06755 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Thu, 9 Nov 2023 10:54:26 -0500 Subject: [PATCH 09/10] reviewdog --- crypto/statetrie/nibbles/nibbles.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 47b62d4129..42c64b9269 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -131,7 +131,8 @@ func SharedPrefix(nyb1 Nibbles, nyb2 Nibbles) Nibbles { // [0x1, 0x2, 0x3, 0x4] -> [0x12, 0x34, 0x03] // [] -> [0x03] func Serialize(nyb Nibbles) (data []byte) { - if p, h := Pack(nyb); h { + p, h := Pack(nyb) + if h { // 0x01 is the odd length indicator return append(p, oddIndicator) } From 615552d68d33dbbbfbc14e9b3ccea367cf3cec94 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Thu, 9 Nov 2023 16:20:17 -0500 Subject: [PATCH 10/10] makeNibbles --- crypto/statetrie/nibbles/nibbles.go | 60 ++++++++++++------------ crypto/statetrie/nibbles/nibbles_test.go | 12 ++--- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 42c64b9269..8a8409b6b0 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -32,34 +32,6 @@ const ( evenIndicator = 0x03 ) -// MakeNibbles returns a nibble array from the byte array. If oddLength is true, -// the last 4 bits of the last byte of the array are ignored. -// -// [0x12, 0x30], true -> [0x1, 0x2, 0x3] -// [0x12, 0x34], false -> [0x1, 0x2, 0x3, 0x4] -// [0x12, 0x34], true -> [0x1, 0x2, 0x3] <-- last byte last 4 bits ignored -// [], false -> [] -// never to be called with [], true -// Allocates a new byte slice. -func MakeNibbles(data []byte, oddLength bool) Nibbles { - length := len(data) * 2 - if oddLength { - length = length - 1 - } - ns := make([]byte, length) - - j := 0 - for i := 0; i < length; i++ { - if i%2 == 0 { - ns[i] = data[j] >> 4 - } else { - ns[i] = data[j] & 0x0f - j++ - } - } - return ns -} - // Pack the nibble array into a byte array. // Return the byte array and a bool indicating if the last byte is a full byte or // only the high 4 bits are part of the encoding @@ -151,11 +123,39 @@ func Deserialize(encoding []byte) (Nibbles, error) { if length == 1 { return nil, errors.New("invalid encoding") } - ns = MakeNibbles(encoding[:length-1], true) + ns = makeNibbles(encoding[:length-1], true) } else if encoding[length-1] == evenIndicator { - ns = MakeNibbles(encoding[:length-1], false) + ns = makeNibbles(encoding[:length-1], false) } else { return nil, errors.New("invalid encoding") } return ns, nil } + +// makeNibbles returns a nibble array from the byte array. If oddLength is true, +// the last 4 bits of the last byte of the array are ignored. +// +// [0x12, 0x30], true -> [0x1, 0x2, 0x3] +// [0x12, 0x34], false -> [0x1, 0x2, 0x3, 0x4] +// [0x12, 0x34], true -> [0x1, 0x2, 0x3] <-- last byte last 4 bits ignored +// [], false -> [] +// never to be called with [], true +// Allocates a new byte slice. +func makeNibbles(data []byte, oddLength bool) Nibbles { + length := len(data) * 2 + if oddLength { + length = length - 1 + } + ns := make([]byte, length) + + j := 0 + for i := 0; i < length; i++ { + if i%2 == 0 { + ns[i] = data[j] >> 4 + } else { + ns[i] = data[j] & 0x0f + j++ + } + } + return ns +} diff --git a/crypto/statetrie/nibbles/nibbles_test.go b/crypto/statetrie/nibbles/nibbles_test.go index 527862fbeb..c088f1dd8a 100644 --- a/crypto/statetrie/nibbles/nibbles_test.go +++ b/crypto/statetrie/nibbles/nibbles_test.go @@ -48,7 +48,7 @@ func TestNibblesRandom(t *testing.T) { if half && localRand.Intn(2) == 0 { data[len(data)-1] &= 0xf0 // sometimes clear the last nibble, sometimes do not } - nibbles := MakeNibbles(data, half) + nibbles := makeNibbles(data, half) data2 := Serialize(nibbles) nibbles2, err := Deserialize(data2) @@ -61,13 +61,13 @@ func TestNibblesRandom(t *testing.T) { packed, odd := Pack(nibbles) require.Equal(t, odd, half) require.Equal(t, packed, data) - unpacked := MakeNibbles(packed, odd) + unpacked := makeNibbles(packed, odd) require.Equal(t, nibbles, unpacked) packed, odd = Pack(nibbles2) require.Equal(t, odd, half) require.Equal(t, packed, data) - unpacked = MakeNibbles(packed, odd) + unpacked = makeNibbles(packed, odd) require.Equal(t, nibbles2, unpacked) } } @@ -130,7 +130,7 @@ func TestNibbles(t *testing.T) { require.Equal(t, oddLength == (len(n)%2 == 1), true) require.Equal(t, bytes.Equal(b, sampleNibblesPacked[i]), true) - unp := MakeNibbles(b, oddLength) + unp := makeNibbles(b, oddLength) require.Equal(t, bytes.Equal(unp, n), true) } @@ -183,10 +183,10 @@ func TestNibbles(t *testing.T) { makeNibblesTestExpected := Nibbles{0x0, 0x1, 0x2, 0x9, 0x2} makeNibblesTestData := []byte{0x01, 0x29, 0x20} - mntr := MakeNibbles(makeNibblesTestData, true) + mntr := makeNibbles(makeNibblesTestData, true) require.Equal(t, bytes.Equal(mntr, makeNibblesTestExpected), true) makeNibblesTestExpectedFW := Nibbles{0x0, 0x1, 0x2, 0x9, 0x2, 0x0} - mntr2 := MakeNibbles(makeNibblesTestData, false) + mntr2 := makeNibbles(makeNibblesTestData, false) require.Equal(t, bytes.Equal(mntr2, makeNibblesTestExpectedFW), true) sampleEqualFalse := [][]Nibbles{