diff --git a/proof.go b/proof.go index de307db..eea0730 100644 --- a/proof.go +++ b/proof.go @@ -146,23 +146,22 @@ func (proof Proof) IsEmptyProof() bool { } // VerifyNamespace verifies a whole namespace, i.e. 1) it verifies inclusion of -// the provided `data` in the tree (or the proof.leafHash in case of absence -// proof) 2) it verifies that the namespace is complete i.e., the data items -// matching the namespace ID `nID` are within the range [`proof.start`, -// `proof.end`) and no data of that namespace was left out. VerifyNamespace -// deems an empty `proof` valid if the queried `nID` falls outside the namespace -// range of the supplied `root` or if the `root` is empty +// the provided `leaves` in the tree (or the proof.leafHash in case of +// full/short absence proof) 2) it verifies that the namespace is complete +// i.e., the data items matching the namespace `nID` are within the range +// [`proof.start`, `proof.end`) and no data of that namespace was left out. +// VerifyNamespace deems an empty `proof` valid if the queried `nID` falls +// outside the namespace range of the supplied `root` or if the `root` is empty // // `h` MUST be the same as the underlying hash function used to generate the // proof. Otherwise, the verification will fail. `nID` is the namespace ID for -// which the namespace `proof` is generated. `data` contains the namespaced data -// items (but not namespace hash) underlying the leaves of the tree in the -// range of [`proof.start`, `proof.end`). For an absence `proof`, the `data` is -// empty. `data` items MUST be ordered according to their index in the tree, -// with `data[0]` corresponding to the namespaced data at index `start`, -// -// and the last element in `data` corresponding to the data item at index -// `end-1` of the tree. +// which the namespace `proof` is generated. `leaves` contains the namespaced +// leaves of the tree in the range of [`proof.start`, `proof.end`). +// For an absence `proof`, the `leaves` is empty. +// `leaves` items MUST be ordered according to their index in the tree, +// with `leaves[0]` corresponding to the namespaced leaf at index `start`, +// and the last element in `leaves` corresponding to the leaf at index `end-1` +// of the tree. // // `root` is the root of the NMT against which the `proof` is verified. func (proof Proof) VerifyNamespace(h hash.Hash, nID namespace.ID, leaves [][]byte, root []byte) bool { diff --git a/proof_test.go b/proof_test.go index cb2cccc..9d308c3 100644 --- a/proof_test.go +++ b/proof_test.go @@ -707,3 +707,189 @@ func TestIsEmptyProofOverlapAbsenceProof(t *testing.T) { }) } } + +// TestVerifyNamespace_ShortAbsenceProof_Valid checks whether VerifyNamespace +// can correctly verify short namespace absence proofs +func TestVerifyNamespace_ShortAbsenceProof_Valid(t *testing.T) { + // create a Merkle tree with 8 leaves + tree := exampleNMT(1, true, 1, 2, 3, 4, 6, 7, 8, 9) + qNS := []byte{5} // does not belong to the tree + root, err := tree.Root() + assert.NoError(t, err) + // In the following illustration, nodes are suffixed with the range + // of leaves they cover, with the upper bound being non-inclusive. + // For example, Node3_4 denotes a node that covers the 3rd leaf (excluding the 4th leaf), + // while Node4_6 represents the node that covers the 4th and 5th leaves. + // + // Node0_8 Tree Root + // / \ + // / \ + // Node0_4 Node4_8 Non-Leaf Node + // / \ / \ + // / \ / \ + // Node0_2 Node2_4 Node4_6 Node6_8 Non-Leaf Node + // / \ / \ / \ / \ + // Node0_1 Node1_2 Node2_3 Node3_4 Node4_5 Node5_6 Node6_7 Node7_8 Leaf Hash + // 1 2 3 4 6 7 8 9 Leaf namespace + // 0 1 2 3 4 5 6 7 Leaf index + + // nodes needed for the full absence proof of qNS + Node4_5 := tree.leafHashes[4] + Node5_6 := tree.leafHashes[5] + Node6_8, err := tree.computeRoot(6, 8) + assert.NoError(t, err) + Node0_4, err := tree.computeRoot(0, 4) + assert.NoError(t, err) + + // nodes needed for the short absence proof of qNS; the proof of inclusion + // of the parent of Node4_5 + + Node4_6, err := tree.computeRoot(4, 6) + assert.NoError(t, err) + + // nodes needed for another short absence parent of qNS; the proof of + // inclusion of the grandparent of Node4_5 + Node4_8, err := tree.computeRoot(4, 8) + assert.NoError(t, err) + + tests := []struct { + name string + qNID []byte + leafHash []byte + nodes [][]byte + start int + end int + }{ + { + name: "valid full absence proof", + qNID: qNS, + leafHash: Node4_5, + nodes: [][]byte{Node0_4, Node5_6, Node6_8}, + start: 4, // the index position of leafHash at its respective level + end: 5, + }, + { + name: "valid short absence proof: one level higher", + qNID: qNS, + leafHash: Node4_6, + nodes: [][]byte{Node0_4, Node6_8}, + start: 2, // the index position of leafHash at its respective level + end: 3, + }, + { + name: "valid short absence proof: two levels higher", + qNID: qNS, + leafHash: Node4_8, + nodes: [][]byte{Node0_4}, + start: 1, // the index position of leafHash at its respective level + end: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + proof := Proof{ + leafHash: tt.leafHash, + nodes: tt.nodes, + start: tt.start, + end: tt.end, + } + + res := proof.VerifyNamespace(sha256.New(), qNS, nil, root) + assert.True(t, res) + }) + } +} + +// TestVerifyNamespace_ShortAbsenceProof_Invalid checks whether VerifyNamespace rejects invalid short absence proofs. +func TestVerifyNamespace_ShortAbsenceProof_Invalid(t *testing.T) { + // create a Merkle tree with 8 leaves + tree := exampleNMT(1, true, 1, 2, 3, 4, 6, 8, 8, 8) + qNS := []byte{7} // does not belong to the tree + root, err := tree.Root() + assert.NoError(t, err) + // In the following illustration, nodes are suffixed with the range + // of leaves they cover, with the upper bound being non-inclusive. + // For example, Node3_4 denotes a node that covers the 3rd leaf (excluding the 4th leaf), + // while Node4_6 represents the node that covers the 4th and 5th leaves. + // + // Node0_8 Tree Root + // / \ + // / \ + // Node0_4 Node4_8 Non-Leaf Node + // / \ / \ + // / \ / \ + // Node0_2 Node2_4 Node4_6 Node6_8 Non-Leaf Node + // / \ / \ / \ / \ + // Node0_1 Node1_2 Node2_3 Node3_4 Node4_5 Node5_6 Node6_7 Node7_8 Leaf Hash + // 1 2 3 4 6 8 8 8 Leaf namespace + // 0 1 2 3 4 5 6 7 Leaf index + + // nodes needed for the full absence proof of qNS + Node5_6 := tree.leafHashes[5] + Node4_5 := tree.leafHashes[4] + Node6_8, err := tree.computeRoot(6, 8) + assert.NoError(t, err) + Node0_4, err := tree.computeRoot(0, 4) + assert.NoError(t, err) + + // nodes needed for the short absence proof of qNS; the proof of inclusion of the parent of Node5_6; + // the verification should fail since the namespace range o Node4_6, the parent, has overlap with the qNS i.e., 7 + Node4_6, err := tree.computeRoot(4, 6) + assert.NoError(t, err) + + // nodes needed for another short absence parent of qNS; the proof of inclusion of the grandparent of Node5_6 + // the verification should fail since the namespace range of Node4_8, the grandparent, has overlap with the qNS i.e., 7 + Node4_8, err := tree.computeRoot(4, 8) + assert.NoError(t, err) + + tests := []struct { + name string + qNID []byte + leafHash []byte + nodes [][]byte + start int + end int + want bool + }{ + { + name: "valid full absence proof", + qNID: qNS, + leafHash: Node5_6, + nodes: [][]byte{Node0_4, Node4_5, Node6_8}, + start: 5, // the index position of leafHash at its respective level + end: 6, + want: true, + }, + { + name: "invalid short absence proof: one level higher", + qNID: qNS, + leafHash: Node4_6, + nodes: [][]byte{Node0_4, Node6_8}, + start: 2, // the index position of leafHash at its respective level + end: 3, + want: false, + }, + { + name: "invalid short absence proof: two levels higher", + qNID: qNS, + leafHash: Node4_8, + nodes: [][]byte{Node0_4}, + start: 1, // the index position of leafHash at its respective level + end: 2, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + proof := Proof{ + leafHash: tt.leafHash, + nodes: tt.nodes, + start: tt.start, + end: tt.end, + } + + res := proof.VerifyNamespace(sha256.New(), qNS, nil, root) + assert.Equal(t, tt.want, res) + }) + } +}