Skip to content

Commit

Permalink
feat: ensures VerifyLeafHashes rejects leafHashes with wrong namespac…
Browse files Browse the repository at this point in the history
…es (#207)

## Overview
Closes #183. The modifications in this PR are also in line with
empowering light clients to furnish BEFPs for proofs that encounter
unsuccessful verification outcomes (where the early termination of
verification upon encountering invalid leaf hashes, with mismatching
namespaces, gives a clear indication of the failure root cause, enabling
them to decide on whether to craft BEFPs or not accordingly).

## 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 Jun 23, 2023
1 parent 033ce9e commit cdc88e8
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 6 deletions.
20 changes: 16 additions & 4 deletions proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (proof Proof) VerifyNamespace(h hash.Hash, nID namespace.ID, leaves [][]byt
// If the verifyCompleteness parameter is set to true, the function also checks
// the completeness of the proof by verifying that there is no leaf in the
// tree represented by the root parameter that matches the namespace ID nID
// but is not present in the leafHashes list.
// outside the leafHashes list.
func (proof Proof) VerifyLeafHashes(nth *Hasher, verifyCompleteness bool, nID namespace.ID, leafHashes [][]byte, root []byte) (bool, error) {
// check that the proof range is valid
if proof.Start() < 0 || proof.Start() >= proof.End() {
Expand All @@ -248,13 +248,25 @@ func (proof Proof) VerifyLeafHashes(nth *Hasher, verifyCompleteness bool, nID na
return false, fmt.Errorf("proof nodes do not match the NMT hasher's hash format: %w", err)
}
}
// check that all the proof.nodes are valid w.r.t the NMT hasher
for _, leaf := range leafHashes {
if err := nth.ValidateNodeFormat(leaf); err != nil {
// check that all the leafHashes are valid w.r.t the NMT hasher
for _, leafHash := range leafHashes {
if err := nth.ValidateNodeFormat(leafHash); err != nil {
return false, fmt.Errorf("leaf hash does not match the NMT hasher's hash format: %w", err)
}
}

// check that the namespace of leafHashes is the same as the queried namespace, except for the case of absence proof
if !proof.IsOfAbsence() { // in case of absence proof, the leafHash is the hash of a leaf next to the queried namespace, hence its namespace ID is not the same as the queried namespace ID
// check the namespace of all the leaf hashes to be the same as the queried namespace
for _, leafHash := range leafHashes {
minNsID := MinNamespace(leafHash, nth.NamespaceSize())
maxNsID := MaxNamespace(leafHash, nth.NamespaceSize())
if !nID.Equal(minNsID) || !nID.Equal(maxNsID) {
return false, fmt.Errorf("leaf hash %x does not belong to namespace %x", leafHash, nID)
}
}
}

var leafIndex uint64
// leftSubtrees is to be populated by the subtree roots upto [0, r.Start)
leftSubtrees := make([][]byte, 0, len(proof.nodes))
Expand Down
20 changes: 18 additions & 2 deletions proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ func TestVerifyNamespace_False(t *testing.T) {
}

func TestVerifyLeafHashes_False(t *testing.T) {
nIDs := []byte{1, 2, 3, 4, 5, 6, 7, 8}
nIDs := []byte{1, 2, 3, 4, 6, 7, 8, 9}

// create a sample tree with namespace ID size of 1
nmt1 := exampleNMT(1, true, nIDs...)
Expand All @@ -532,6 +532,21 @@ func TestVerifyLeafHashes_False(t *testing.T) {
proof4_1, err := nmt1.ProveNamespace(nid4_1) // leaf at index 3 has namespace ID 4
require.NoError(t, err)

leafHash1 := nmt1.leafHashes[3]

// corrupt the namespace of the leafHash
leafHash1Corrupted := make([]byte, len(leafHash1))
copy(leafHash1Corrupted, leafHash1)
leafHash1Corrupted[0] = 0 // change the min namespace
leafHash1Corrupted[1] = 0 // change the max namespace

// create an absence proof with namespace ID size of 1
nid5_1 := namespace.ID{5}
absenceProof5_1, err := nmt1.ProveNamespace(nid5_1)
require.NoError(t, err)
leafHash6_1 := nmt1.leafHashes[4]
assert.Equal(t, leafHash6_1, absenceProof5_1.leafHash)

// create a sample tree with namespace ID size of 2
nmt2 := exampleNMT(2, true, nIDs...)
root2, err := nmt2.Root()
Expand All @@ -540,7 +555,6 @@ func TestVerifyLeafHashes_False(t *testing.T) {
proof4_2, err := nmt2.ProveNamespace(nid4_2) // leaf at index 3 has namespace ID 4
require.NoError(t, err)

leafHash1 := nmt1.leafHashes[3]
leafHash2 := nmt2.leafHashes[3]

type args struct {
Expand All @@ -563,6 +577,8 @@ func TestVerifyLeafHashes_False(t *testing.T) {
{"size of queried nID < nID size of VerifyLeafHashes' nmt hasher", proof4_2, args{2, nid4_1, [][]byte{leafHash2}, root2}, false},
{"nID size of leafHash < nID size of VerifyLeafHashes' nmt hasher", proof4_2, args{2, nid4_2, [][]byte{leafHash1}, root2}, false},
{"nID size of leafHash > nID size of VerifyLeafHashes' nmt hasher", proof4_1, args{1, nid4_1, [][]byte{leafHash2}, root1}, false},
{"nID of leafHashes do not match the queried nID", proof4_1, args{1, nid4_1, [][]byte{leafHash1Corrupted}, root1}, false},
{"absence proof: nID of leafHashes do not match the queried nID, which is a valid case", absenceProof5_1, args{1, nid5_1, [][]byte{leafHash6_1}, root1}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down

0 comments on commit cdc88e8

Please sign in to comment.