Skip to content

Commit

Permalink
Remove interval digests in favor of []byte (#48)
Browse files Browse the repository at this point in the history
* stop using interval digests

* don't export min and max namespace parsers

* update docs to better inform the structure of the root
  • Loading branch information
evan-forbes authored Sep 10, 2021
1 parent d8fd217 commit 1d72cff
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 188 deletions.
53 changes: 0 additions & 53 deletions namespace/digest.go

This file was deleted.

84 changes: 0 additions & 84 deletions namespace/digest_test.go

This file was deleted.

20 changes: 16 additions & 4 deletions nmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,13 @@ func (n *NamespacedMerkleTree) Push(namespacedData namespace.PrefixedData) error
return nil
}

// Return the namespaced Merkle Tree's root together with the
// min. and max. namespace ID.
func (n *NamespacedMerkleTree) Root() namespace.IntervalDigest {
// Return the namespaced Merkle Tree's root with the minimum and maximum
// namespace. min || max || hashDigest
func (n *NamespacedMerkleTree) Root() []byte {
if n.rawRoot == nil {
n.rawRoot = n.computeRoot(0, len(n.leaves))
}
return mustIntervalDigestFromBytes(n.NamespaceSize(), n.rawRoot)
return n.rawRoot
}

func (n NamespacedMerkleTree) computeRoot(start, end int) []byte {
Expand Down Expand Up @@ -372,3 +372,15 @@ func (n *NamespacedMerkleTree) computeLeafHashesIfNecessary() {
type leafRange struct {
start, end uint64
}

// minNamespace parses the minimum namespace id from a given hash
func minNamespace(hash []byte, size namespace.IDSize) []byte {
min := make([]byte, 0, size)
return append(min, hash[:size]...)
}

// maxNamespace parses the maximum namespace id from a given hash
func maxNamespace(hash []byte, size namespace.IDSize) []byte {
max := make([]byte, 0, size)
return append(max, hash[size:size*2]...)
}
53 changes: 26 additions & 27 deletions nmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ func ExampleNamespacedMerkleTree() {
// compute the root
root := tree.Root()
// the root's min/max namespace is the min and max namespace of all leaves:
if root.Min.Equal(namespace.ID{0}) {
fmt.Printf("Min namespace: %x\n", root.Min)
minNS := minNamespace(root, tree.NamespaceSize())
maxNS := maxNamespace(root, tree.NamespaceSize())
if bytes.Equal(minNS, namespace.ID{0}) {
fmt.Printf("Min namespace: %x\n", minNS)
}
if root.Max.Equal(namespace.ID{1}) {
fmt.Printf("Max namespace: %x\n", root.Max)
if bytes.Equal(maxNS, namespace.ID{1}) {
fmt.Printf("Max namespace: %x\n", maxNS)
}

// compute proof for namespace 0:
Expand Down Expand Up @@ -135,16 +137,14 @@ func TestNamespacedMerkleTreeRoot(t *testing.T) {
name string
nidLen int
pushedData []namespaceDataPair
wantMinNs namespace.ID
wantMaxNs namespace.ID
wantRoot []byte
}{
// default empty root according to base case:
// https://github.com/celestiaorg/celestiaorg-specs/blob/master/specs/data_structures.md#namespace-merkle-tree
{"Empty", 3, nil, zeroNs, zeroNs, emptyRoot},
{"One leaf", 3, []namespaceDataPair{newNamespaceDataPair(zeroNs, leafData)}, zeroNs, zeroNs, sum(crypto.SHA256, []byte{LeafPrefix}, zeroNs, leafData)},
{"Two leaves", 3, []namespaceDataPair{newNamespaceDataPair(zeroNs, leafData), newNamespaceDataPair(zeroNs, leafData)}, zeroNs, zeroNs, twoZeroLeafsRoot},
{"Two leaves diff namespaces", 3, []namespaceDataPair{newNamespaceDataPair(zeroNs, leafData), newNamespaceDataPair(onesNS, leafData)}, zeroNs, onesNS, diffNSLeafsRoot},
{"Empty", 3, nil, appendAll(zeroNs, zeroNs, emptyRoot)},
{"One leaf", 3, []namespaceDataPair{newNamespaceDataPair(zeroNs, leafData)}, appendAll(zeroNs, zeroNs, sum(crypto.SHA256, []byte{LeafPrefix}, zeroNs, leafData))},
{"Two leaves", 3, []namespaceDataPair{newNamespaceDataPair(zeroNs, leafData), newNamespaceDataPair(zeroNs, leafData)}, appendAll(zeroNs, zeroNs, twoZeroLeafsRoot)},
{"Two leaves diff namespaces", 3, []namespaceDataPair{newNamespaceDataPair(zeroNs, leafData), newNamespaceDataPair(onesNS, leafData)}, appendAll(zeroNs, onesNS, diffNSLeafsRoot)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -154,21 +154,26 @@ func TestNamespacedMerkleTreeRoot(t *testing.T) {
t.Errorf("Push() error = %v, expected no error", err)
}
}
root := n.Root()
gotMinNs, gotMaxNs, gotRoot := root.Min, root.Max, root.Hash()
if !reflect.DeepEqual(gotMinNs, tt.wantMinNs) {
t.Errorf("Root() gotMinNs = %v, want %v", gotMinNs, tt.wantMinNs)
}
if !reflect.DeepEqual(gotMaxNs, tt.wantMaxNs) {
t.Errorf("Root() gotMaxNs = %v, want %v", gotMaxNs, tt.wantMaxNs)
}
gotRoot := n.Root()
if !reflect.DeepEqual(gotRoot, tt.wantRoot) {
t.Errorf("Root() gotRoot = %v, want %v", gotRoot, tt.wantRoot)
}
})
}
}

func appendAll(slices ...[]byte) []byte {
totalLen := 0
for _, slice := range slices {
totalLen += len(slice)
}
out := make([]byte, 0, totalLen)
for _, slice := range slices {
out = append(out, slice...)
}
return out
}

func TestNamespacedMerkleTree_ProveNamespace_Ranges_And_Verify(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -441,8 +446,8 @@ func TestIgnoreMaxNamespace(t *testing.T) {
panic("unexpected error")
}
}
gotRootMaxNID := tree.Root().Max
if !gotRootMaxNID.Equal(tc.wantRootMaxNID) {
gotRootMaxNID := tree.Root()[tree.NamespaceSize() : tree.NamespaceSize()*2]
if !bytes.Equal(tc.wantRootMaxNID, gotRootMaxNID) {
t.Fatalf("Case: %v, '%v', root.Max() got: %x, want: %x", i, tc.name, gotRootMaxNID, tc.wantRootMaxNID)
}
for idx, d := range tc.pushData {
Expand Down Expand Up @@ -493,15 +498,9 @@ func TestNodeVisitor(t *testing.T) {
}
root := n.Root()
last := nodeHashes[len(nodeHashes)-1]
if !bytes.Equal(root.Hash(), last[nidSize*2:]) {
if !bytes.Equal(root, last) {
t.Fatalf("last visited node's digest does not match the tree root's.")
}
if !bytes.Equal(root.Min, last[:nidSize]) {
t.Fatalf("last visited node's min namespace does not match the tree root's.")
}
if !bytes.Equal(root.Max, last[nidSize:nidSize*2]) {
t.Fatalf("last visited node's max namespace does not match the tree root's.")
}
t.Log("printing nodes in visiting order") // postorder DFS
for _, nodeHash := range nodeHashes {
t.Logf("|min: %x, max: %x, digest: %x...|\n", nodeHash[:nidSize], nodeHash[nidSize:nidSize*2], nodeHash[nidSize*2:nidSize*2+3])
Expand Down
30 changes: 11 additions & 19 deletions proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ func NewAbsenceProof(proofStart, proofEnd int, proofNodes [][]byte, leafHash []b
// VerifyNamespace verifies a whole namespace, i.e. it verifies inclusion of
// the provided data in the tree. Additionally, it verifies that the namespace
// is complete and no leaf of that namespace was left out in the proof.
func (proof Proof) VerifyNamespace(h hash.Hash, nID namespace.ID, data [][]byte, root namespace.IntervalDigest) bool {
func (proof Proof) VerifyNamespace(h hash.Hash, nID namespace.ID, data [][]byte, root []byte) bool {
nth := NewNmtHasher(h, nID.Size(), proof.isMaxNamespaceIDIgnored)
if nID.Size() != root.Min.Size() || nID.Size() != root.Max.Size() {
min := namespace.ID(minNamespace(root, nID.Size()))
max := namespace.ID(maxNamespace(root, nID.Size()))
if nID.Size() != min.Size() || nID.Size() != max.Size() {
// conflicting namespace sizes
return false
}
Expand Down Expand Up @@ -137,7 +139,7 @@ func (proof Proof) VerifyNamespace(h hash.Hash, nID namespace.ID, data [][]byte,
return proof.verifyLeafHashes(nth, true, nID, gotLeafHashes, root)
}

func (proof Proof) verifyLeafHashes(nth *Hasher, verifyCompleteness bool, nID namespace.ID, gotLeafHashes [][]byte, root namespace.IntervalDigest) bool {
func (proof Proof) verifyLeafHashes(nth *Hasher, verifyCompleteness bool, nID namespace.ID, gotLeafHashes [][]byte, root []byte) bool {
// The code below is almost identical to NebulousLabs'
// merkletree.VerifyMultiRangeProof.
//
Expand Down Expand Up @@ -183,16 +185,16 @@ func (proof Proof) verifyLeafHashes(nth *Hasher, verifyCompleteness bool, nID na
if verifyCompleteness {
// leftSubtrees contains the subtree roots upto [0, r.Start)
for _, subtree := range leftSubtrees {
leftSubTreeMax := mustIntervalDigestFromBytes(nth.NamespaceSize(), subtree).Max
if nID.LessOrEqual(leftSubTreeMax) {
leftSubTreeMax := maxNamespace(subtree, nth.NamespaceSize())
if nID.LessOrEqual(namespace.ID(leftSubTreeMax)) {
return false
}
}
// rightSubtrees only contains the subtrees after [0, r.Start)
rightSubtrees := proof.nodes
for _, subtree := range rightSubtrees {
rightSubTreeMin := mustIntervalDigestFromBytes(nth.NamespaceSize(), subtree).Min
if rightSubTreeMin.LessOrEqual(nID) {
rightSubTreeMin := minNamespace(subtree, nth.NamespaceSize())
if namespace.ID(rightSubTreeMin).LessOrEqual(nID) {
return false
}
}
Expand All @@ -201,10 +203,10 @@ func (proof Proof) verifyLeafHashes(nth *Hasher, verifyCompleteness bool, nID na
// add remaining proof hashes after the last range ends
consumeUntil(math.MaxUint64)

return bytes.Equal(tree.Root(), root.Bytes())
return bytes.Equal(tree.Root(), root)
}

func (proof Proof) VerifyInclusion(h hash.Hash, nid namespace.ID, data []byte, root namespace.IntervalDigest) bool {
func (proof Proof) VerifyInclusion(h hash.Hash, nid namespace.ID, data []byte, root []byte) bool {
nth := NewNmtHasher(h, nid.Size(), proof.isMaxNamespaceIDIgnored)
leafData := append(nid, data...)
return proof.verifyLeafHashes(nth, false, nid, [][]byte{nth.HashLeaf(leafData)}, root)
Expand All @@ -220,13 +222,3 @@ func nextSubtreeSize(start, end uint64) int {
}
return 1 << uint(ideal)
}

// mustIntervalDigestFromBytes optimistially converts bytes to IntervalDigest or panics
func mustIntervalDigestFromBytes(idlen namespace.IDSize, bytes []byte) namespace.IntervalDigest {
id, err := namespace.IntervalDigestFromBytes(idlen, bytes)
if err != nil {
panic(err)
}

return id
}
2 changes: 1 addition & 1 deletion proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestProof_VerifyNamespace_False(t *testing.T) {
type args struct {
nID namespace.ID
data [][]byte
root namespace.IntervalDigest
root []byte
}
pushedZeroNs := n.Get([]byte{0, 0, 0})
pushedLastNs := n.Get([]byte{0, 0, 8})
Expand Down

0 comments on commit 1d72cff

Please sign in to comment.