Skip to content

Commit

Permalink
Bug fix: reject forged proofs of absence. Fixes (#86) (#90)
Browse files Browse the repository at this point in the history
- Reject empty "proofs" of absence, except in the special case where the
root doesn't cover the namespace being proved
- Add test case to catch regression
- Add test case to ensure that the absence of the zero namespace can be
proved against the empty tree

<!--
Please read and fill out this form before submitting your PR.

Please make sure you have reviewed our contributors guide before
submitting your
first PR.
-->

closes #86

## Overview

<!-- 
Please provide an explanation of the PR, including the appropriate
context,
background, goal, and rationale. If there is an issue with this
information,
please provide a tl;dr and link the issue. 
-->

## Checklist

<!-- 
Please complete the checklist to ensure that the PR is ready to be
reviewed.

IMPORTANT:
PRs should be left in Draft until the below checklist is completed.
-->

- [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

Co-authored-by: Ismail Khoffi <Ismail.Khoffi@gmail.com>
  • Loading branch information
preston-evans98 and liamsi authored Dec 16, 2022
1 parent 574ce4e commit 7d31915
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 2 deletions.
52 changes: 52 additions & 0 deletions nmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,58 @@ func TestNamespacedMerkleTree_calculateAbsenceIndex_Panic(t *testing.T) {
}
}

// This test checks for a regression of https://github.com/celestiaorg/nmt/issues/86
func TestNMT_absenceProofOfZeroNamespace_InEmptyTree(t *testing.T) {
tree := New(sha256.New(), NamespaceIDSize(1))
root := tree.Root()
emptyleaves, proof, err := tree.GetWithProof(namespace.ID{0})
if err != nil {
t.Fatalf("GetWithProof() could not get namespace{0}. err: %v ", err)
}
if len(emptyleaves) != 0 {
t.Fatalf("Get(namespace.ID{0}) should have returned no leaves but returned %v", emptyleaves)
}
if !proof.VerifyNamespace(sha256.New(), namespace.ID{0}, emptyleaves, root) {
t.Fatalf("Could not verify proof of absence of namespace zero in empty tree")
}
}

// This test checks for a regression of https://github.com/celestiaorg/nmt/issues/86
func TestNMT_forgedNamespaceEmptinessProof(t *testing.T) {
data := [][]byte{
append(namespace.ID{1}, []byte("leaf_0")...),
append(namespace.ID{1}, []byte("leaf_1")...),
append(namespace.ID{2}, []byte("leaf_2")...),
append(namespace.ID{2}, []byte("leaf_3")...)}
// Init a tree with the namespace size as well as
// the underlying hash function:
tree := New(sha256.New(), NamespaceIDSize(1))
for _, d := range data {
if err := tree.Push(d); err != nil {
panic(fmt.Sprintf("unexpected error: %v", err))
}
}

root := tree.Root()
actualLeaves := tree.Get(namespace.ID{1})
if len(actualLeaves) == 0 {
t.Fatalf("Get(namespace.ID{1}) should have returned two leaves but returned none.")
}

forgedProof := Proof{
start: 0,
end: 0,
nodes: [][]byte{},
leafHash: []byte{},
isMaxNamespaceIDIgnored: true,
}

forgedProofSuccess := forgedProof.VerifyNamespace(sha256.New(), namespace.ID{1}, [][]byte{}, root)
if forgedProofSuccess {
t.Fatalf("Successfully verified proof that non-empty namespace was empty")
}
}

func TestInvalidOptions(t *testing.T) {
shouldPanic(t, func() {
_ = New(sha256.New(), InitialCapacity(-1))
Expand Down
9 changes: 7 additions & 2 deletions proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,14 @@ func (proof Proof) VerifyNamespace(h hash.Hash, nID namespace.ID, data [][]byte,
}

isEmptyRange := proof.start == proof.end
// empty range, proof, and data: always checks out
if len(data) == 0 && isEmptyRange && len(proof.nodes) == 0 {
return true
// empty proofs are always rejected unless nID is outside the range of namespaces covered by the root
// we special case the empty root, since it purports to cover the zero namespace but does not actually
// include any such nodes
if nID.Less(min) || max.Less(nID) || bytes.Equal(root, nth.EmptyRoot()) {
return true
}
return false
}
gotLeafHashes := make([][]byte, 0, len(data))
nIDLen := nID.Size()
Expand Down

0 comments on commit 7d31915

Please sign in to comment.