Skip to content

Commit

Permalink
feat: ensures consistent namespace hash format for all tree node para…
Browse files Browse the repository at this point in the history
…meters in VerifyInclusion function (#176)

## Overview
Closes #157 
Closes #161
## 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 18, 2023
1 parent be509c3 commit 6cb81d0
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 20 deletions.
13 changes: 13 additions & 0 deletions proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,22 @@ func (proof Proof) verifyLeafHashes(nth *Hasher, verifyCompleteness bool, nID na
// and the provided proof to regenerate and compare the root. Note that the leavesWithoutNamespace data should not contain the prefixed namespace, unlike the tree.Push method,
// which takes prefixed data. All leaves implicitly have the same namespace ID:
// `nid`.
// VerifyInclusion does not verify the completeness of the proof, so it's possible for leavesWithoutNamespace to be a subset of the leaves in the tree that have the namespace ID nid.
func (proof Proof) VerifyInclusion(h hash.Hash, nid namespace.ID, leavesWithoutNamespace [][]byte, root []byte) bool {
nth := NewNmtHasher(h, nid.Size(), proof.isMaxNamespaceIDIgnored)

// perform some consistency checks:
// check that the root is valid w.r.t the NMT hasher
if err := nth.ValidateNodeFormat(root); err != nil {
return false
}
// check that all the proof.nodes are valid w.r.t the NMT hasher
for _, node := range proof.nodes {
if err := nth.ValidateNodeFormat(node); err != nil {
return false
}
}

// add namespace to all the leaves
hashes := make([][]byte, len(leavesWithoutNamespace))
for i, d := range leavesWithoutNamespace {
Expand Down
50 changes: 30 additions & 20 deletions proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package nmt
import (
"bytes"
"crypto/sha256"
"hash"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -256,42 +257,51 @@ func TestVerifyLeafHashes_Err(t *testing.T) {
}

func TestVerifyInclusion_False(t *testing.T) {
// create a sample tree
nmt := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
hasher := nmt.treeHasher
root, err := nmt.Root()
hasher := sha256.New()

// create a sample tree with namespace ID size of 1
nmt1 := exampleNMT(1, 1, 2, 3, 4, 5, 6, 7, 8)
root1, err := nmt1.Root()
require.NoError(t, err)
nid4_1 := namespace.ID{4}
proof4_1, err := nmt1.ProveRange(3, 4) // leaf at index 3 has namespace ID 4
require.NoError(t, err)
leaf4_1 := nmt1.leaves[3][nmt1.NamespaceSize():]

// create nmt proof for namespace ID 4
nID4 := namespace.ID{4, 4}
proof4, err := nmt.ProveNamespace(nID4)
// create a sample tree with namespace ID size of 2
nmt2 := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
root2, err := nmt2.Root()
require.NoError(t, err)
// proof4 is the inclusion proof for the leaf at index 3
leaf4WithoutNamespace := nmt.leaves[3][nmt.NamespaceSize():] // the VerifyInclusion function expects the leaf without the namespace ID, that's why we cut the namespace ID from the leaf.
nid4_2 := namespace.ID{4, 4}
proof4_2, err := nmt2.ProveRange(3, 4) // leaf at index 3 has namespace ID 4
require.NoError(t, err)
leaf4_2 := nmt2.leaves[3][nmt2.NamespaceSize():]

// corrupt the last node in the proof4.nodes, it resides on the right side of the proof4.end index.
// this test scenario makes the VerifyInclusion fail when constructing the tree root from the
// computed subtree root and the proof.nodes on the right side of the proof.end index.
proof4.nodes[2] = proof4.nodes[2][:nmt.NamespaceSize()-1]
require.Equal(t, leaf4_2, leaf4_1)
leaf := leaf4_1

// create nmt
type args struct {
Hasher *Hasher
nID namespace.ID
leafHashes [][]byte
root []byte
hasher hash.Hash
nID namespace.ID
leavesWithoutNamespace [][]byte
root []byte
}
tests := []struct {
name string
proof Proof
args args
result bool
}{
{" wrong proof.nodes: the last node has an incorrect format", proof4, args{hasher, nID4, [][]byte{leaf4WithoutNamespace}, root}, false},
{"nID size of proof < nID size of VerifyInclusion's nmt hasher", proof4_1, args{hasher, nid4_2, [][]byte{leaf}, root2}, false},
{"nID size of proof > nID size of VerifyInclusion's nmt hasher", proof4_2, args{hasher, nid4_1, [][]byte{leaf}, root1}, false},
{"nID size of root < nID size of VerifyInclusion's nmt hasher", proof4_2, args{hasher, nid4_2, [][]byte{leaf}, root1}, false},
{"nID size of root > nID size of VerifyInclusion's nmt hasher", proof4_1, args{hasher, nid4_1, [][]byte{leaf}, root2}, false},
{"nID size of proof and root < nID size of VerifyInclusion's nmt hasher", proof4_1, args{hasher, nid4_2, [][]byte{leaf}, root1}, false},
{"nID size of proof and root > nID size of VerifyInclusion's nmt hasher", proof4_2, args{hasher, nid4_1, [][]byte{leaf}, root2}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.proof.VerifyInclusion(tt.args.Hasher, tt.args.nID, tt.args.leafHashes, tt.args.root)
got := tt.proof.VerifyInclusion(tt.args.hasher, tt.args.nID, tt.args.leavesWithoutNamespace, tt.args.root)
assert.Equal(t, tt.result, got)
})
}
Expand Down

0 comments on commit 6cb81d0

Please sign in to comment.