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
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 @@ -28,18 +28,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 ErrBadProof 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 ErrBadProof
}

// 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 @@ -19,9 +19,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 @@ -15,6 +15,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 @@ -37,6 +41,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 @@ -46,7 +60,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 @@ -57,6 +72,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 @@ -180,6 +199,7 @@ func CompactProof(proof SparseMerkleProof, hasher hash.Hash) (SparseCompactMerkl
NonMembershipLeafData: proof.NonMembershipLeafData,
BitMask: bitMask,
NumSideNodes: len(proof.SideNodes),
SiblingData: proof.SiblingData,
}, nil
}

Expand All @@ -205,5 +225,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 @@ -150,7 +150,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 @@ -318,67 +318,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 @@ -405,12 +442,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