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!: accounts for IgnoreMaxNamespace flag in the ProveNamespace method #194

Merged
merged 7 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
rootulp marked this conversation as resolved.
Show resolved Hide resolved
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},
rootulp marked this conversation as resolved.
Show resolved Hide resolved
{"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