Skip to content

Commit

Permalink
feat!: accounts for IgnoreMaxNamespace flag in the ProveNamespace met…
Browse files Browse the repository at this point in the history
…hod (#194)

## Overview
Closes #159
  • Loading branch information
staheri14 authored May 12, 2023
1 parent fd00c52 commit c41093d
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 30 deletions.
19 changes: 17 additions & 2 deletions nmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,24 @@ func (n *NamespacedMerkleTree) ProveRange(start, end int) (Proof, error) {
// Any error returned by this method is irrecoverable and indicates an illegal state of the tree (n).
func (n *NamespacedMerkleTree) ProveNamespace(nID namespace.ID) (Proof, error) {
isMaxNsIgnored := n.treeHasher.IsMaxNamespaceIDIgnored()
// case 1) In the cases (n.nID < minNID) or (n.maxNID < nID), return empty

// check if the tree is empty
if n.Size() == 0 {
return NewEmptyRangeProof(isMaxNsIgnored), nil
}

// compute the root of the tree
root, err := n.Root()
if err != nil {
return Proof{}, fmt.Errorf("failed to get root: %w", err)
}
// extract the min and max namespace of the tree from the root
treeMinNs := namespace.ID(MinNamespace(root, n.NamespaceSize()))
treeMaxNs := namespace.ID(MaxNamespace(root, n.NamespaceSize()))

// case 1) In the cases (n.nID < treeMinNs) or (treeMaxNs < nID), return empty
// range proof
if nID.Less(n.minNID) || n.maxNID.Less(nID) {
if nID.Less(treeMinNs) || treeMaxNs.Less(nID) {
return NewEmptyRangeProof(isMaxNsIgnored), nil
}

Expand Down
66 changes: 47 additions & 19 deletions nmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,10 @@ func TestIgnoreMaxNamespace(t *testing.T) {
if gotIgnored := proof.IsMaxNamespaceIDIgnored(); gotIgnored != tc.ignoreMaxNamespace {
t.Fatalf("Proof.IsMaxNamespaceIDIgnored() got: %v, want: %v", gotIgnored, tc.ignoreMaxNamespace)
}
leaves := tree.Get(d.NamespaceID())
var leaves [][]byte
if !proof.IsEmptyProof() {
leaves = tree.Get(d.NamespaceID())
}
r, err := tree.Root()
require.NoError(t, err)
if !proof.VerifyNamespace(hash, d.NamespaceID(), leaves, r) {
Expand Down Expand Up @@ -818,13 +821,13 @@ func TestMinMaxNamespace(t *testing.T) {
testCases := []testCase{
{
name: "example tree with four leaves",
tree: exampleNMT(1, 0, 0, 1, 3),
tree: exampleNMT(1, true, 0, 0, 1, 3),
wantMin: namespace.ID{0},
wantMax: namespace.ID{3},
},
{
name: "example tree with eight leaves",
tree: exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8),
tree: exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8),
wantMin: namespace.ID{1, 1},
wantMax: namespace.ID{8, 8},
},
Expand All @@ -843,8 +846,8 @@ func TestMinMaxNamespace(t *testing.T) {
}

// exampleNMT creates a new NamespacedMerkleTree with the given namespace ID size and leaf namespace IDs. Each byte in the leavesNIDs parameter corresponds to one leaf's namespace ID. If nidSize is greater than 1, the function repeats each NID in leavesNIDs nidSize times before prepending it to the leaf data.
func exampleNMT(nidSize int, leavesNIDs ...byte) *NamespacedMerkleTree {
tree := New(sha256.New(), NamespaceIDSize(nidSize))
func exampleNMT(nidSize int, ignoreMaxNamespace bool, leavesNIDs ...byte) *NamespacedMerkleTree {
tree := New(sha256.New(), NamespaceIDSize(nidSize), IgnoreMaxNamespace(ignoreMaxNamespace))
for i, nid := range leavesNIDs {
namespace := bytes.Repeat([]byte{nid}, nidSize)
d := append(namespace, []byte(fmt.Sprintf("leaf_%d", i))...)
Expand All @@ -867,17 +870,17 @@ func Test_buildRangeProof_Err(t *testing.T) {
nIDSize := 2

// create a nmt, 8 leaves namespaced sequentially from 1-8
treeWithCorruptLeafHash := exampleNMT(nIDSize, nIDList...)
treeWithCorruptLeafHash := exampleNMT(nIDSize, true, nIDList...)
// corrupt a leaf hash
treeWithCorruptLeafHash.leafHashes[4] = treeWithCorruptLeafHash.leafHashes[4][:treeWithCorruptLeafHash.NamespaceSize()]

// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithUnorderedLeafHashes := exampleNMT(nIDSize, nIDList...)
treeWithUnorderedLeafHashes := exampleNMT(nIDSize, true, nIDList...)
// swap the positions of the 4th and 5th leaves
swap(treeWithUnorderedLeafHashes.leaves, 4, 5)
swap(treeWithUnorderedLeafHashes.leafHashes, 4, 5)

validTree := exampleNMT(nIDSize, nIDList...)
validTree := exampleNMT(nIDSize, true, nIDList...)

tests := []struct {
name string
Expand Down Expand Up @@ -911,12 +914,12 @@ func Test_buildRangeProof_Err(t *testing.T) {
// Test_ProveRange_Err tests that ProveRange returns an error when the underlying tree has an invalid state e.g., leaves are not ordered by namespace ID or a leaf hash is corrupted.
func Test_ProveRange_Err(t *testing.T) {
// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithCorruptLeafHash := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
treeWithCorruptLeafHash := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
// corrupt a leaf hash
treeWithCorruptLeafHash.leafHashes[4] = treeWithCorruptLeafHash.leafHashes[4][:treeWithCorruptLeafHash.NamespaceSize()]

// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithUnorderedLeafHashes := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
treeWithUnorderedLeafHashes := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
// swap the positions of the 4th and 5th leaves
swap(treeWithUnorderedLeafHashes.leaves, 4, 5)
swap(treeWithUnorderedLeafHashes.leafHashes, 4, 5)
Expand Down Expand Up @@ -949,12 +952,12 @@ func Test_ProveRange_Err(t *testing.T) {
// The Test_ProveNamespace_Err function tests that ProveNamespace returns an error when the underlying tree is in an invalid state, such as when the leaves are not ordered by namespace ID or when a leaf hash is corrupt.
func Test_ProveNamespace_Err(t *testing.T) {
// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithCorruptLeafHash := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
treeWithCorruptLeafHash := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
// corrupt a leaf hash
treeWithCorruptLeafHash.leafHashes[4] = treeWithCorruptLeafHash.leafHashes[4][:treeWithCorruptLeafHash.NamespaceSize()]

// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithUnorderedLeafHashes := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
treeWithUnorderedLeafHashes := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
// swap the positions of the 4th and 5th leaves
swap(treeWithUnorderedLeafHashes.leaves, 4, 5)
swap(treeWithUnorderedLeafHashes.leafHashes, 4, 5)
Expand Down Expand Up @@ -987,12 +990,12 @@ func Test_ProveNamespace_Err(t *testing.T) {
// Test_Root_Error tests that the Root method returns an error when the underlying tree is in an invalid state, such as when the leaves are not ordered by namespace ID or when a leaf is corrupt.
func Test_Root_Error(t *testing.T) {
// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithCorruptLeafHash := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
treeWithCorruptLeafHash := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
// corrupt a leaf hash
treeWithCorruptLeafHash.leafHashes[4] = treeWithCorruptLeafHash.leafHashes[4][:treeWithCorruptLeafHash.NamespaceSize()-1]

// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithUnorderedLeaves := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
treeWithUnorderedLeaves := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
// swap the positions of the 4th and 5th leaves
swap(treeWithUnorderedLeaves.leaves, 4, 5)
swap(treeWithUnorderedLeaves.leafHashes, 4, 5)
Expand Down Expand Up @@ -1023,18 +1026,18 @@ func Test_computeRoot_Error(t *testing.T) {
nIDList := []byte{1, 2, 3, 4, 5, 6, 7, 8}

// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithCorruptLeafHash := exampleNMT(nIDSize, nIDList...)
treeWithCorruptLeafHash := exampleNMT(nIDSize, true, nIDList...)
// corrupt a leaf hash
treeWithCorruptLeafHash.leafHashes[4] = treeWithCorruptLeafHash.leafHashes[4][:treeWithCorruptLeafHash.NamespaceSize()-1]

// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithUnorderedLeaves := exampleNMT(nIDSize, nIDList...)
treeWithUnorderedLeaves := exampleNMT(nIDSize, true, nIDList...)
// swap the positions of the 4th and 5th leaves
swap(treeWithUnorderedLeaves.leaves, 4, 5)
swap(treeWithUnorderedLeaves.leafHashes, 4, 5)

// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
validTree := exampleNMT(nIDSize, nIDList...)
validTree := exampleNMT(nIDSize, true, nIDList...)

tests := []struct {
name string
Expand Down Expand Up @@ -1067,12 +1070,12 @@ func Test_computeRoot_Error(t *testing.T) {
// Test_MinMaxNamespace_Err tests that the MinNamespace and MaxNamespace methods return an error when the underlying tree is in an invalid state, such as when the leaves are not ordered by namespace ID or when a leaf is corrupt.
func Test_MinMaxNamespace_Err(t *testing.T) {
// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithCorruptLeafHash := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
treeWithCorruptLeafHash := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
// corrupt a leaf hash
treeWithCorruptLeafHash.leafHashes[4] = treeWithCorruptLeafHash.leafHashes[4][:treeWithCorruptLeafHash.NamespaceSize()-1]

// create an NMT with 8 sequentially namespaced leaves, numbered from 1 to 8.
treeWithUnorderedLeaves := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
treeWithUnorderedLeaves := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
// swap the positions of the 4th and 5th leaves
swap(treeWithUnorderedLeaves.leaves, 4, 5)
swap(treeWithUnorderedLeaves.leafHashes, 4, 5)
Expand Down Expand Up @@ -1102,3 +1105,28 @@ func Test_MinMaxNamespace_Err(t *testing.T) {
})
}
}

// TestProveNamespace_MaxNamespace checks the output of the ProveNamespace method when queried for the maximum namespace ID.
func TestProveNamespace_MaxNamespace(t *testing.T) {
nidSize := 1
MaxNS := byte(math.MaxUint8)
tests := []struct {
name string
nIDList []byte
isEmptyProof bool
}{
{"tree with no leaf", []byte{}, true},
{"tree with one leaf with MaxNS", []byte{MaxNS}, false},
{"tree with two leaves, the right leaf has MaxNS", []byte{1, MaxNS}, true},
{"tree with four leaves, the right half has MaxNS", []byte{1, 2, MaxNS, MaxNS}, true},
{"tree with 8 leaves, the right half has MaxNS", []byte{1, 2, 3, 4, MaxNS, MaxNS, MaxNS, MaxNS}, true},
}
for _, tt := range tests {
tree := exampleNMT(nidSize, true, tt.nIDList...)
t.Run(tt.name, func(t *testing.T) {
proof, err := tree.ProveNamespace(namespace.ID{MaxNS})
assert.NoError(t, err)
assert.Equal(t, tt.isEmptyProof, proof.IsEmptyProof())
})
}
}
18 changes: 9 additions & 9 deletions proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
func TestVerifyNamespace_EmptyProof(t *testing.T) {
// create a tree with 4 leaves
nIDSize := 1
tree := exampleNMT(nIDSize, 1, 2, 3, 4)
tree := exampleNMT(nIDSize, true, 1, 2, 3, 4)
root, err := tree.Root()
require.NoError(t, err)

Expand Down Expand Up @@ -275,7 +275,7 @@ func safeAppend(id, data []byte) []byte {
func TestVerifyLeafHashes_Err(t *testing.T) {
// create a sample tree
nameIDSize := 2
nmt := exampleNMT(nameIDSize, 1, 2, 3, 4, 5, 6, 7, 8)
nmt := exampleNMT(nameIDSize, true, 1, 2, 3, 4, 5, 6, 7, 8)
hasher := nmt.treeHasher
root, err := nmt.Root()
require.NoError(t, err)
Expand Down Expand Up @@ -359,7 +359,7 @@ func TestVerifyInclusion_False(t *testing.T) {
hasher := sha256.New()

// create a sample tree with namespace ID size of 1
nmt1 := exampleNMT(1, 1, 2, 3, 4, 5, 6, 7, 8)
nmt1 := exampleNMT(1, true, 1, 2, 3, 4, 5, 6, 7, 8)
root1, err := nmt1.Root()
require.NoError(t, err)
nid4_1 := namespace.ID{4}
Expand All @@ -368,7 +368,7 @@ func TestVerifyInclusion_False(t *testing.T) {
leaf4_1 := nmt1.leaves[3][nmt1.NamespaceSize():]

// create a sample tree with namespace ID size of 2
nmt2 := exampleNMT(2, 1, 2, 3, 4, 5, 6, 7, 8)
nmt2 := exampleNMT(2, true, 1, 2, 3, 4, 5, 6, 7, 8)
root2, err := nmt2.Root()
require.NoError(t, err)
nid4_2 := namespace.ID{4, 4}
Expand Down Expand Up @@ -412,7 +412,7 @@ func TestVerifyInclusion_EmptyProofs(t *testing.T) {

// create a tree
nIDSize := 1
tree := exampleNMT(nIDSize, 1, 2, 3, 4, 5, 6, 7, 8)
tree := exampleNMT(nIDSize, true, 1, 2, 3, 4, 5, 6, 7, 8)
root, err := tree.Root()
require.NoError(t, err)

Expand Down Expand Up @@ -458,15 +458,15 @@ func TestVerifyNamespace_False(t *testing.T) {
nIDs := []byte{1, 2, 3, 4, 5, 6, 7, 8, 11}

// create a sample tree with namespace ID size of 1
nmt1 := exampleNMT(1, nIDs...)
nmt1 := exampleNMT(1, true, nIDs...)
root1, err := nmt1.Root()
require.NoError(t, err)
nid4_1 := namespace.ID{4}
proof4_1, err := nmt1.ProveNamespace(nid4_1) // leaf at index 3 has namespace ID 4
require.NoError(t, err)

// create a sample tree with namespace ID size of 2
nmt2 := exampleNMT(2, nIDs...)
nmt2 := exampleNMT(2, true, nIDs...)
root2, err := nmt2.Root()
require.NoError(t, err)
nid4_2 := namespace.ID{4, 4}
Expand Down Expand Up @@ -525,15 +525,15 @@ func TestVerifyLeafHashes_False(t *testing.T) {
nIDs := []byte{1, 2, 3, 4, 5, 6, 7, 8}

// create a sample tree with namespace ID size of 1
nmt1 := exampleNMT(1, nIDs...)
nmt1 := exampleNMT(1, true, nIDs...)
root1, err := nmt1.Root()
require.NoError(t, err)
nid4_1 := namespace.ID{4}
proof4_1, err := nmt1.ProveNamespace(nid4_1) // leaf at index 3 has namespace ID 4
require.NoError(t, err)

// create a sample tree with namespace ID size of 2
nmt2 := exampleNMT(2, nIDs...)
nmt2 := exampleNMT(2, true, nIDs...)
root2, err := nmt2.Root()
require.NoError(t, err)
nid4_2 := namespace.ID{4, 4}
Expand Down

0 comments on commit c41093d

Please sign in to comment.