From 85bf1b7510e96ce82a6f330b8d46a65c916d2091 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Wed, 27 Sep 2023 10:58:21 -0400 Subject: [PATCH 1/5] 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 411a37b84c81831a6aa9b18baa41ee38b411a528 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Fri, 29 Sep 2023 13:03:13 +0100 Subject: [PATCH 2/5] 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 086f8cb6d1cb85e38458766efa90215652ab28a8 Mon Sep 17 00:00:00 2001 From: Bob Broderick Date: Sun, 8 Oct 2023 00:53:02 +0100 Subject: [PATCH 3/5] 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 684599d8056ccd1be4a7afa7cb1bce89ecaec7af Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Tue, 7 Nov 2023 15:56:42 -0500 Subject: [PATCH 4/5] go 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 e3f5bac11b04d6a18167ffe5e5eb9c66c2cf047b Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Tue, 7 Nov 2023 16:20:52 -0500 Subject: [PATCH 5/5] CR: optimize Pack + Serialize --- crypto/statetrie/nibbles/nibbles.go | 11 ++++------- crypto/statetrie/nibbles/nibbles_test.go | 10 +++++++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 10e175832a..a71291b137 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -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 @@ -132,18 +132,15 @@ func SharedPrefix(nyb1 Nibbles, nyb2 Nibbles) Nibbles { // [] -> [0x03] func Serialize(nyb Nibbles) (data []byte) { p, h := Pack(nyb) - length := len(p) - output := make([]byte, length+1) - copy(output, p) if h { // 0x01 is the odd length indicator - output[length] = oddIndicator + p = append(p, oddIndicator) } else { // 0x03 is the even length indicator - output[length] = evenIndicator + p = append(p, evenIndicator) } - return output + return p } // Deserialize returns a nibble array from the byte array. 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}