Skip to content

Commit

Permalink
feat!: checks the exact size of the node in HashNode (#175)
Browse files Browse the repository at this point in the history
## Overview
Closes #153 (part of #173)

## Checklist


- [x] New and updated code has appropriate documentation
- [x] New and updated code has new and/or updated testing
- [x] Required CI checks are passing
- [x] Visual proof for any user facing features like CLI or
documentation updates
- [x] Linked issues closed with keywords
  • Loading branch information
staheri14 authored Apr 13, 2023
1 parent 2c15503 commit be509c3
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 47 deletions.
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) {
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

0 comments on commit be509c3

Please sign in to comment.