Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: checks the exact size of the node in HashNode #175

Merged
merged 13 commits into from
Apr 13, 2023
8 changes: 4 additions & 4 deletions hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ func (n *Hasher) MustHashLeaf(ndata []byte) []byte {
}

// ValidateNodeFormat checks whether the supplied node conforms to the
// namespaced hash format and returns an error if it does not. Specifically, it returns ErrInvalidNodeLen if the length of the node is less than the 2*namespace length which indicates it does not match the namespaced hash format.
// namespaced hash format and returns ErrInvalidNodeLen if not.
func (n *Hasher) ValidateNodeFormat(node []byte) (err error) {
totalNamespaceLen := 2 * n.NamespaceLen
expectedNodeLen := n.Size()
nodeLen := len(node)
if nodeLen < int(totalNamespaceLen) {
return fmt.Errorf("%w: got: %v, want >= %v", ErrInvalidNodeLen, nodeLen, totalNamespaceLen)
if nodeLen != expectedNodeLen {
return fmt.Errorf("%w: got: %v, want %v", ErrInvalidNodeLen, nodeLen, expectedNodeLen)
}
return nil
}
Expand Down
163 changes: 120 additions & 43 deletions hasher_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package nmt

import (
"bytes"
"crypto"
"crypto/sha256"
"errors"
Expand Down Expand Up @@ -64,7 +65,8 @@ func Test_namespacedTreeHasher_HashLeaf(t *testing.T) {
}

func Test_namespacedTreeHasher_HashNode(t *testing.T) {
sum(crypto.SHA256, []byte{NodePrefix}, []byte{0, 0, 0, 0}, []byte{1, 1, 1, 1})
// create a dummy hash to use as the digest of the left and right child
randHash := createByteSlice(crypto.SHA256.Size(), 0x01)
type children struct {
l []byte
r []byte
Expand All @@ -78,28 +80,37 @@ func Test_namespacedTreeHasher_HashNode(t *testing.T) {
}{
{
"leftmin<rightmin && leftmax<rightmax", 2,
children{[]byte{0, 0, 0, 0}, []byte{1, 1, 1, 1}},
append(
[]byte{0, 0, 1, 1},
sum(crypto.SHA256, []byte{NodePrefix}, []byte{0, 0, 0, 0}, []byte{1, 1, 1, 1})...,
),
children{
concat([]byte{0, 0, 0, 0}, randHash),
concat([]byte{1, 1, 1, 1}, randHash),
},
concat([]byte{0, 0, 1, 1}, // minNID||maxNID
sum(crypto.SHA256, []byte{NodePrefix}, // Hash(NodePrefix||left||right)
concat([]byte{0, 0, 0, 0}, randHash),
concat([]byte{1, 1, 1, 1}, randHash))),
},
{
"leftmin==rightmin && leftmax<rightmax", 2,
children{[]byte{0, 0, 0, 0}, []byte{0, 0, 1, 1}},
append(
[]byte{0, 0, 1, 1},
sum(crypto.SHA256, []byte{NodePrefix}, []byte{0, 0, 0, 0}, []byte{0, 0, 1, 1})...,
),
children{
concat([]byte{0, 0, 0, 0}, randHash),
concat([]byte{0, 0, 1, 1}, randHash),
},
concat([]byte{0, 0, 1, 1}, // minNID||maxNID
sum(crypto.SHA256, []byte{NodePrefix}, // Hash(NodePrefix||left||right)
concat([]byte{0, 0, 0, 0}, randHash),
concat([]byte{0, 0, 1, 1}, randHash))),
},
// XXX: can this happen in practice? or is this an invalid state?
{
"leftmin>rightmin && leftmax<rightmax", 2,
children{[]byte{1, 1, 0, 0}, []byte{0, 0, 0, 1}},
append(
[]byte{0, 0, 0, 1},
sum(crypto.SHA256, []byte{NodePrefix}, []byte{1, 1, 0, 0}, []byte{0, 0, 0, 1})...,
),
children{
concat([]byte{1, 1, 0, 0}, randHash),
concat([]byte{0, 0, 0, 1}, randHash),
},
concat([]byte{0, 0, 0, 1}, // minNID||maxNID
sum(crypto.SHA256, []byte{NodePrefix}, // Hash(NodePrefix||left||right)
concat([]byte{1, 1, 0, 0}, randHash),
concat([]byte{0, 0, 0, 1}, randHash))),
},
}
for _, tt := range tests {
Expand All @@ -124,6 +135,21 @@ func sum(hash crypto.Hash, data ...[]byte) []byte {
return h.Sum(nil)
}

// concat concatenates the given byte slices.
func concat(data ...[]byte) []byte {
var result []byte
for _, d := range data {
result = append(result, d...)
}

return result
}

// createByteSlice returns a byte slice of length n with all bytes set to b.
func createByteSlice(n int, b byte) []byte {
return bytes.Repeat([]byte{b}, n)
}

func TestNamespaceHasherWrite(t *testing.T) {
tt := []struct {
name string
Expand Down Expand Up @@ -196,6 +222,8 @@ func TestNamespaceHasherSum(t *testing.T) {
}

func TestHashNode_ChildrenNamespaceRange(t *testing.T) {
// create a dummy hash to use as the digest of the left and right child
randHash := createByteSlice(sha256.Size, 0x01)
type children struct {
l []byte // namespace hash of the left child with the format of MinNs||MaxNs||h
r []byte // namespace hash of the right child with the format of MinNs||MaxNs||h
Expand All @@ -210,19 +238,28 @@ func TestHashNode_ChildrenNamespaceRange(t *testing.T) {
}{
{
"left.maxNs>right.minNs", 2,
children{[]byte{0, 0, 1, 1}, []byte{0, 0, 1, 1}},
children{
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{0, 0, 1, 1}, randHash),
},
true, // this test case should emit an error since in an ordered NMT, left.maxNs cannot be greater than right.minNs
ErrUnorderedSiblings,
},
{
"left.maxNs=right.minNs", 2,
children{[]byte{0, 0, 1, 1}, []byte{1, 1, 2, 2}},
children{
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{1, 1, 2, 2}, randHash),
},
false,
nil,
},
{
"left.maxNs<right.minNs", 2,
children{[]byte{0, 0, 1, 1}, []byte{2, 2, 3, 3}},
children{
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{2, 2, 3, 3}, randHash),
},
false,
nil,
},
Expand Down Expand Up @@ -277,6 +314,9 @@ func TestValidateSiblingsNamespaceOrder(t *testing.T) {
}

func TestValidateNodeFormat(t *testing.T) {
staheri14 marked this conversation as resolved.
Show resolved Hide resolved
hashValue := createByteSlice(sha256.Size, 0x01)
minNID := createByteSlice(2, 0x00)
maxNID := createByteSlice(2, 0x01)
tests := []struct {
name string
nIDLen namespace.IDSize
Expand All @@ -286,21 +326,39 @@ func TestValidateNodeFormat(t *testing.T) {
wantErr bool
errType error
}{
{ // valid node
{
"valid node",
2,
[]byte{0, 0},
[]byte{1, 1},
[]byte{1, 2, 3, 4},
minNID,
maxNID,
hashValue,
false,
nil,
},
{ // mismatched namespace size
"invalid node: length",
{
"invalid node: length < 2 * namespace size",
2,
[]byte{0},
[]byte{1},
[]byte{0},
minNID,
[]byte{},
[]byte{},
true,
ErrInvalidNodeLen,
},
{
"invalid node: length < 2 * namespace Size + hash size",
2,
minNID,
maxNID,
[]byte{},
true,
ErrInvalidNodeLen,
},
{
"invalid node: length > 2 * namespace size + hash size",
2,
minNID,
maxNID,
concat(hashValue, []byte{1}),
true,
ErrInvalidNodeLen,
},
Expand Down Expand Up @@ -415,6 +473,8 @@ func TestHashLeafWithIsNamespacedData(t *testing.T) {

// TestHashNode_ErrorsCheck checks that the HashNode emits error only on invalid inputs. It also checks whether the returned error types are correct.
func TestHashNode_ErrorsCheck(t *testing.T) {
// create a dummy hash to use as the digest of the left and right child
randHash := createByteSlice(sha256.Size, 0x01)
type children struct {
l []byte // namespace hash of the left child with the format of MinNs||MaxNs||h
r []byte // namespace hash of the right child with the format of MinNs||MaxNs||h
Expand All @@ -429,31 +489,46 @@ func TestHashNode_ErrorsCheck(t *testing.T) {
}{
{
"left.maxNs<right.minNs", 2,
children{[]byte{0, 0, 1, 1}, []byte{2, 2, 3, 3}},
children{
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{2, 2, 3, 3}, randHash),
},
false,
nil,
},
{
"left.maxNs=right.minNs", 2,
children{[]byte{0, 0, 1, 1}, []byte{1, 1, 2, 2}},
children{
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{1, 1, 2, 2}, randHash),
},
false,
nil,
},
{
"left.maxNs>right.minNs", 2,
children{[]byte{0, 0, 1, 1}, []byte{0, 0, 1, 1}},
children{
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{0, 0, 1, 1}, randHash),
},
true,
ErrUnorderedSiblings,
},
{
"len(left)<NamespaceLen", 2,
children{[]byte{0, 0, 1}, []byte{2, 2, 3, 3}},
"len(left)<hasher.Size", 2,
children{
[]byte{0, 0, 1},
concat([]byte{2, 2, 3, 3}, randHash),
},
true,
ErrInvalidNodeLen,
},
{
"len(right)<NamespaceLen", 2,
children{[]byte{0, 0, 1, 1}, []byte{2, 2, 3}},
"len(right)<hasher.Size", 2,
children{
concat([]byte{0, 0, 1, 1}, randHash),
[]byte{2, 2, 3},
},
true,
ErrInvalidNodeLen,
},
Expand Down Expand Up @@ -560,6 +635,8 @@ func TestSum_Err(t *testing.T) {

// TestValidateNodes checks that the ValidateNodes method only emits error on invalid inputs. It also checks whether the returned error types are correct.
func TestValidateNodes(t *testing.T) {
// create a dummy hash to use as the digest of the left and right child
randHash := createByteSlice(sha256.Size, 0x01)
tests := []struct {
name string
nIDLen namespace.IDSize
Expand All @@ -571,38 +648,38 @@ func TestValidateNodes(t *testing.T) {
{
"left.maxNs<right.minNs",
2,
[]byte{0, 0, 1, 1},
[]byte{2, 2, 3, 3},
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{2, 2, 3, 3}, randHash),
false,
nil,
},
{
"left.maxNs=right.minNs",
2,
[]byte{0, 0, 1, 1},
[]byte{1, 1, 2, 2},
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{1, 1, 2, 2}, randHash),
false,
nil,
},
{
"left.maxNs>right.minNs",
2,
[]byte{0, 0, 1, 1},
[]byte{0, 0, 1, 1},
concat([]byte{0, 0, 1, 1}, randHash),
concat([]byte{0, 0, 1, 1}, randHash),
true,
ErrUnorderedSiblings,
},
{
"len(left)<NamespaceLen",
2,
[]byte{0, 0, 1},
[]byte{2, 2, 3, 3},
concat([]byte{2, 2, 3, 3}, randHash),
true,
ErrInvalidNodeLen,
},
{
"len(right)<NamespaceLen", 2,
[]byte{0, 0, 1, 1},
concat([]byte{0, 0, 1, 1}, randHash),
[]byte{2, 2, 3},
true,
ErrInvalidNodeLen,
Expand Down