Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

Make branches updatable with sibling node value #27

Merged
merged 15 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from 14 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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
go.mod
go.sum
- name: install
run: GOOS=linux GOARCH=${{ matrix.goarch }} make build
run: GOOS=linux GOARCH=${{ matrix.goarch }} go build
if: "env.GIT_DIFF != ''"

tests:
Expand Down
2 changes: 1 addition & 1 deletion bulk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func bulkCheckAll(t *testing.T, smt *SparseMerkleTree, kv *map[string]string) {
largestCommonPrefix = commonPrefix
}
}
sideNodes, _, _, err := smt.sideNodesForRoot(smt.th.path([]byte(k)), smt.Root())
sideNodes, _, _, _, err := smt.sideNodesForRoot(smt.th.path([]byte(k)), smt.Root(), false)
if err != nil {
t.Errorf("error: %v", err)
}
Expand Down
14 changes: 14 additions & 0 deletions deepsubtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,32 @@ func NewDeepSparseMerkleSubTree(ms MapStore, hasher hash.Hash, root []byte) *Dee
// AddBranch adds a branch to the tree.
// These branches are generated by smt.ProveForRoot.
// If the proof is invalid, a BadProofError is returned.
//
// If the leaf may be updated (e.g. during a state transition fraud proof),
// an updatable proof should be used. See SparseMerkleTree.ProveUpdatable.
func (dsmst *DeepSparseMerkleSubTree) AddBranch(proof SparseMerkleProof, key []byte, value []byte) error {
result, updates := verifyProofWithUpdates(proof, dsmst.Root(), key, value, dsmst.th.hasher)
if !result {
return &BadProofError{}
}

// Update nodes along branch
for _, update := range updates {
err := dsmst.ms.Set(update[0], update[1])
if err != nil {
return err
}
}

// Update sibling node
if proof.SiblingData != nil {
if proof.SideNodes != nil && len(proof.SideNodes) > 0 {
err := dsmst.ms.Set(proof.SideNodes[0], proof.SiblingData)
if err != nil {
return err
}
}
}

return nil
}
6 changes: 3 additions & 3 deletions deepsubtree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ func TestDeepSparseMerkleSubTreeBasic(t *testing.T) {
var originalRoot []byte
copy(originalRoot, smt.Root())

proof1, _ := smt.Prove([]byte("testKey1"))
proof2, _ := smt.Prove([]byte("testKey2"))
proof5, _ := smt.Prove([]byte("testKey5"))
proof1, _ := smt.ProveUpdatable([]byte("testKey1"))
proof2, _ := smt.ProveUpdatable([]byte("testKey2"))
proof5, _ := smt.ProveUpdatable([]byte("testKey5"))

dsmst := NewDeepSparseMerkleSubTree(NewSimpleMap(), sha256.New(), smt.Root())
err := dsmst.AddBranch(proof1, []byte("testKey1"), []byte("testValue1"))
Expand Down
23 changes: 22 additions & 1 deletion proofs.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ type SparseMerkleProof struct {
// of the key being proven, in the case of a non-membership proof. For
// membership proofs, is nil.
NonMembershipLeafData []byte

// SiblingData is the data of the sibling node to the leaf being proven,
// required for updatable proofs. For unupdatable proofs, is nil.
SiblingData []byte
}

func (proof *SparseMerkleProof) sanityCheck(th *treeHasher) bool {
Expand All @@ -38,6 +42,16 @@ func (proof *SparseMerkleProof) sanityCheck(th *treeHasher) bool {
}
}

// Check that the sibling data hashes to the first side node if not nil
if proof.SiblingData != nil {
siblingHash := th.digest(proof.SiblingData)
if proof.SideNodes != nil && len(proof.SideNodes) > 0 {
if !bytes.Equal(proof.SideNodes[0], siblingHash) {
return false
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

return true
}

Expand All @@ -47,7 +61,8 @@ type SparseCompactMerkleProof struct {
SideNodes [][]byte

// NonMembershipLeafData is the data of the unrelated leaf at the position
// of the key being proven, in the case of a non-membership proof.
// of the key being proven, in the case of a non-membership proof. For
// membership proofs, is nil.
NonMembershipLeafData []byte

// BitMask, in the case of a compact proof, is a bit mask of the sidenodes
Expand All @@ -58,6 +73,10 @@ type SparseCompactMerkleProof struct {
// NumSideNodes, in the case of a compact proof, indicates the number of
// sidenodes in the proof when decompacted. This is only set if the proof is compact.
NumSideNodes int

// SiblingData is the data of the sibling node to the leaf being proven,
// required for updatable proofs. For unupdatable proofs, is nil.
SiblingData []byte
}

func (proof *SparseCompactMerkleProof) sanityCheck(th *treeHasher) bool {
Expand Down Expand Up @@ -181,6 +200,7 @@ func CompactProof(proof SparseMerkleProof, hasher hash.Hash) (SparseCompactMerkl
NonMembershipLeafData: proof.NonMembershipLeafData,
BitMask: bitMask,
NumSideNodes: len(proof.SideNodes),
SiblingData: proof.SiblingData,
}, nil
}

Expand All @@ -206,5 +226,6 @@ func DecompactProof(proof SparseCompactMerkleProof, hasher hash.Hash) (SparseMer
return SparseMerkleProof{
SideNodes: decompactedSideNodes,
NonMembershipLeafData: proof.NonMembershipLeafData,
SiblingData: proof.SiblingData,
}, nil
}
19 changes: 17 additions & 2 deletions proofs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,21 @@ func TestProofsSanityCheck(t *testing.T) {
if err == nil {
t.Error("did not return error when compacting a malformed proof")
}

// Case: incorrect non-nil sibling data
proof, _ = smt.ProveUpdatable([]byte("testKey1"))
proof.SiblingData = smt.th.digest(proof.SiblingData)
if proof.sanityCheck(th) {
t.Error("sanity check incorrectly passed")
}
result = VerifyProof(proof, root, []byte("testKey1"), []byte("testValue1"), smt.th.hasher)
if result {
t.Error("invalid proof verification returned true")
}
_, err = CompactProof(proof, smt.th.hasher)
if err == nil {
t.Error("did not return error when compacting a malformed proof")
}
}

// Test sanity check cases for compact proofs.
Expand Down Expand Up @@ -247,11 +262,11 @@ func randomiseProof(proof SparseMerkleProof) SparseMerkleProof {
func checkCompactEquivalence(t *testing.T, proof SparseMerkleProof, hasher hash.Hash) {
compactedProof, err := CompactProof(proof, hasher)
if err != nil {
t.Error("failed to compact proof")
t.Errorf("failed to compact proof %v", err)
}
decompactedProof, err := DecompactProof(compactedProof, hasher)
if err != nil {
t.Error("failed to decompact proof")
t.Errorf("failed to decompact proof %v", err)
}

for i, sideNode := range proof.SideNodes {
Expand Down
70 changes: 54 additions & 16 deletions smt.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (smt *SparseMerkleTree) Delete(key []byte) ([]byte, error) {
// UpdateForRoot sets a new value for a key in the tree at a specific root, and returns the new root.
func (smt *SparseMerkleTree) UpdateForRoot(key []byte, value []byte, root []byte) ([]byte, error) {
path := smt.th.path(key)
sideNodes, oldLeafHash, oldLeafData, err := smt.sideNodesForRoot(path, root)
sideNodes, oldLeafHash, oldLeafData, _, err := smt.sideNodesForRoot(path, root, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -321,67 +321,104 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path []byte, value []byte, side
}

// Get all the sibling nodes (sidenodes) for a given path from a given root.
// Returns an array of sibling nodes, the leaf hash found at that path and the
// leaf data. If the leaf is a placeholder, the leaf data is nil.
func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte) ([][]byte, []byte, []byte, error) {
// Returns an array of sibling nodes, the leaf hash found at that path, the
// leaf data, and the sibling data.
//
// If the leaf is a placeholder, the leaf data is nil.
func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSiblingData bool) ([][]byte, []byte, []byte, []byte, error) {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
// Side nodes for the path. Nodes are inserted in reverse order, then the
// slice is reversed at the end.
sideNodes := make([][]byte, 0, smt.depth())

if bytes.Equal(root, smt.th.placeholder()) {
// If the root is a placeholder, there are no sidenodes to return.
// Let the "actual path" be the input path.
return sideNodes, smt.th.placeholder(), nil, nil
return sideNodes, smt.th.placeholder(), nil, nil, nil
}

currentData, err := smt.ms.Get(root)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, nil, err
} else if smt.th.isLeaf(currentData) {
// If the root is a leaf, there are also no sidenodes to return.
return sideNodes, root, currentData, nil
return sideNodes, root, currentData, nil, nil
}

var nodeHash []byte
var sideNode []byte
var siblingData []byte
for i := 0; i < smt.depth(); i++ {
leftNode, rightNode := smt.th.parseNode(currentData)

// Get sidenode depending on whether the path bit is on or off.
if getBitAtFromMSB(path, i) == right {
sideNodes = append(sideNodes, leftNode)
sideNode = leftNode
nodeHash = rightNode
} else {
sideNodes = append(sideNodes, rightNode)
sideNode = rightNode
nodeHash = leftNode
}
sideNodes = append(sideNodes, sideNode)

if bytes.Equal(nodeHash, smt.th.placeholder()) {
// If the node is a placeholder, we've reached the end.
return reverseSideNodes(sideNodes), nodeHash, nil, nil
currentData = nil
break
}

currentData, err = smt.ms.Get(nodeHash)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, nil, err
} else if smt.th.isLeaf(currentData) {
// If the node is a leaf, we've reached the end.
break
}
}

return reverseSideNodes(sideNodes), nodeHash, currentData, nil
if getSiblingData {
siblingData, err = smt.ms.Get(sideNode)
if err != nil {
return nil, nil, nil, nil, err
}
}
return reverseSideNodes(sideNodes), nodeHash, currentData, siblingData, nil
}

// Prove generates a Merkle proof for a key.
// Prove generates a Merkle proof for a key against the current root.
//
// This proof can be used for read-only applications, but should not be used if
// the leaf may be updated (e.g. in a state transition fraud proof). For
// updatable proofs, see ProveUpdatable.
func (smt *SparseMerkleTree) Prove(key []byte) (SparseMerkleProof, error) {
proof, err := smt.ProveForRoot(key, smt.Root())
return proof, err
}

// ProveForRoot generates a Merkle proof for a key, at a specific root.
// ProveForRoot generates a Merkle proof for a key, against a specific node.
// This is primarily useful for generating Merkle proofs for subtrees.
//
// This proof can be used for read-only applications, but should not be used if
// the leaf may be updated (e.g. in a state transition fraud proof). For
// updatable proofs, see ProveUpdatableForRoot.
func (smt *SparseMerkleTree) ProveForRoot(key []byte, root []byte) (SparseMerkleProof, error) {
return smt.doProveForRoot(key, root, false)
}

// ProveUpdatable generates an updatable Merkle proof for a key against the current root.
func (smt *SparseMerkleTree) ProveUpdatable(key []byte) (SparseMerkleProof, error) {
proof, err := smt.ProveUpdatableForRoot(key, smt.Root())
return proof, err
}

// ProveUpdatableForRoot generates an updatable Merkle proof for a key, against a specific node.
// This is primarily useful for generating Merkle proofs for subtrees.
func (smt *SparseMerkleTree) ProveUpdatableForRoot(key []byte, root []byte) (SparseMerkleProof, error) {
return smt.doProveForRoot(key, root, true)
}

func (smt *SparseMerkleTree) doProveForRoot(key []byte, root []byte, isUpdatable bool) (SparseMerkleProof, error) {
path := smt.th.path(key)
sideNodes, leafHash, leafData, err := smt.sideNodesForRoot(path, root)
sideNodes, leafHash, leafData, siblingData, err := smt.sideNodesForRoot(path, root, isUpdatable)
if err != nil {
return SparseMerkleProof{}, err
}
Expand All @@ -408,12 +445,13 @@ func (smt *SparseMerkleTree) ProveForRoot(key []byte, root []byte) (SparseMerkle
proof := SparseMerkleProof{
SideNodes: nonEmptySideNodes,
NonMembershipLeafData: nonMembershipLeafData,
SiblingData: siblingData,
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
}

return proof, err
}

// ProveCompact generates a compacted Merkle proof for a key.
// ProveCompact generates a compacted Merkle proof for a key against the current root.
func (smt *SparseMerkleTree) ProveCompact(key []byte) (SparseCompactMerkleProof, error) {
proof, err := smt.ProveCompactForRoot(key, smt.Root())
return proof, err
Expand Down