This repository has been archived by the owner on Aug 2, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 282
[golang] Merkle verifier #1161
Merged
Merged
[golang] Merkle verifier #1161
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
0d3bd5e
[golang] Merkle verifier
AlCutter 68b07a6
Swapped out the b0rked hasher business.
AlCutter 2e75618
Fixes
AlCutter 394f5b4
fixes
AlCutter e8a0051
mo_leaf_less_data
AlCutter b074a93
more leaf
AlCutter aead7ff
fix comment
AlCutter 2ca9bdc
bytes.Equal()
AlCutter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package merkletree | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
type RootMismatchError struct { | ||
ExpectedRoot []byte | ||
CalculatedRoot []byte | ||
} | ||
|
||
func (e RootMismatchError) Error() string { | ||
return fmt.Sprintf("calculated root:\n%v\n does not match expected root:\n%v", e.CalculatedRoot, e.ExpectedRoot) | ||
} | ||
|
||
// MerkleVerifier is a class which knows how to verify merkle inclusion and consistency proofs. | ||
type MerkleVerifier struct { | ||
treeHasher *TreeHasher | ||
} | ||
|
||
// NewMerkleVerifier returns a new MerkleVerifier for a tree based on the passed in hasher. | ||
func NewMerkleVerifier(h HasherFunc) MerkleVerifier { | ||
return MerkleVerifier{ | ||
treeHasher: NewTreeHasher(h), | ||
} | ||
} | ||
|
||
// VerifyInclusionProof verifies the correctness of the proof given the passed in information about the tree and leaf. | ||
func (m MerkleVerifier) VerifyInclusionProof(leafIndex, treeSize int64, proof [][]byte, root []byte, leaf []byte) error { | ||
calcRoot, err := m.RootFromInclusionProof(leafIndex, treeSize, proof, leaf) | ||
if err != nil { | ||
return err | ||
} | ||
if len(calcRoot) == 0 { | ||
return errors.New("calculated empty root") | ||
} | ||
if !bytes.Equal(calcRoot, root) { | ||
return RootMismatchError{ | ||
CalculatedRoot: calcRoot, | ||
ExpectedRoot: root, | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// RootFromInclusionProof calculates the expected tree root given the proof and leaf. | ||
func (m MerkleVerifier) RootFromInclusionProof(leafIndex, treeSize int64, proof [][]byte, leaf []byte) ([]byte, error) { | ||
if leafIndex > treeSize { | ||
return nil, fmt.Errorf("leafIndex %d > treeSize %d", leafIndex, treeSize) | ||
} | ||
if leafIndex == 0 { | ||
return nil, errors.New("leafIndex is zero") | ||
} | ||
|
||
node := leafIndex - 1 | ||
lastNode := treeSize - 1 | ||
nodeHash := m.treeHasher.HashLeaf(leaf) | ||
proofIndex := 0 | ||
|
||
for lastNode > 0 { | ||
if proofIndex == len(proof) { | ||
return nil, fmt.Errorf("insuficient number of proof components (%d) for treeSize %d", len(proof), treeSize) | ||
} | ||
if isRightChild(node) { | ||
nodeHash = m.treeHasher.HashChildren(proof[proofIndex], nodeHash) | ||
proofIndex++ | ||
} else if node < lastNode { | ||
nodeHash = m.treeHasher.HashChildren(nodeHash, proof[proofIndex]) | ||
proofIndex++ | ||
} else { | ||
// the sibling does not exist and the parent is a dummy copy; do nothing. | ||
} | ||
node = parent(node) | ||
lastNode = parent(lastNode) | ||
} | ||
if proofIndex != len(proof) { | ||
return nil, fmt.Errorf("invalid proof, expected %d components, but have %d", proofIndex, len(proof)) | ||
} | ||
return nodeHash, nil | ||
} | ||
|
||
// VerifyConsistencyProof checks that the passed in consistency proof is valid between the passed in tree snapshots. | ||
func (m MerkleVerifier) VerifyConsistencyProof(snapshot1, snapshot2 int64, root1, root2 []byte, proof [][]byte) error { | ||
if snapshot1 > snapshot2 { | ||
return fmt.Errorf("snapshot1 (%d) > snapshot2 (%d)", snapshot1, snapshot2) | ||
} | ||
if snapshot1 == snapshot2 { | ||
if !bytes.Equal(root1, root2) { | ||
return fmt.Errorf("root1:\n%v\ndoes not match root2:\n%v", root1, root2) | ||
} | ||
if len(proof) > 0 { | ||
return fmt.Errorf("root1 and root2 match, but proof is non-empty") | ||
} | ||
// proof ok | ||
return nil | ||
} | ||
|
||
if snapshot1 == 0 { | ||
// Any snapshot greater than 0 is consistent with snapshot 0. | ||
if len(proof) > 0 { | ||
return fmt.Errorf("expected empty proof, but provided proof has %d components", len(proof)) | ||
} | ||
return nil | ||
} | ||
|
||
if len(proof) == 0 { | ||
return errors.New("empty proof") | ||
} | ||
|
||
node := snapshot1 - 1 | ||
lastNode := snapshot2 - 1 | ||
proofIndex := 0 | ||
|
||
for isRightChild(node) { | ||
node = parent(node) | ||
lastNode = parent(lastNode) | ||
} | ||
|
||
var node1Hash []byte | ||
var node2Hash []byte | ||
|
||
if node > 0 { | ||
node1Hash = proof[proofIndex] | ||
node2Hash = proof[proofIndex] | ||
proofIndex++ | ||
} else { | ||
// The tree at snapshot1 was balanced, nothing to verify for root1. | ||
node1Hash = root1 | ||
node2Hash = root1 | ||
} | ||
|
||
for node > 0 { | ||
if proofIndex == len(proof) { | ||
return errors.New("insufficient number of proof components") | ||
} | ||
|
||
if isRightChild(node) { | ||
node1Hash = m.treeHasher.HashChildren(proof[proofIndex], node1Hash) | ||
node2Hash = m.treeHasher.HashChildren(proof[proofIndex], node2Hash) | ||
proofIndex++ | ||
} else if node < lastNode { | ||
// The sibling only exists in the later tree. The parent in the snapshot1 tree is a dummy copy. | ||
node2Hash = m.treeHasher.HashChildren(node2Hash, proof[proofIndex]) | ||
proofIndex++ | ||
} else { | ||
// Else the sibling does not exist in either tree. Do nothing. | ||
} | ||
|
||
node = parent(node) | ||
lastNode = parent(lastNode) | ||
} | ||
|
||
// Verify the first root. | ||
if !bytes.Equal(node1Hash, root1) { | ||
return fmt.Errorf("failed to verify root1:\n%v\ncalculated root of:\n%v\nfrom proof", root1, node1Hash) | ||
} | ||
|
||
for lastNode > 0 { | ||
if proofIndex == len(proof) { | ||
return errors.New("can't verify newer root; insufficient number of proof components") | ||
} | ||
|
||
node2Hash = m.treeHasher.HashChildren(node2Hash, proof[proofIndex]) | ||
proofIndex++ | ||
lastNode = parent(lastNode) | ||
} | ||
|
||
// Verify the second root. | ||
if !bytes.Equal(node2Hash, root2) { | ||
return fmt.Errorf("failed to verify root2:\n%v\ncalculated root of:\n%v\nfrom proof", root2, node2Hash) | ||
} | ||
if proofIndex != len(proof) { | ||
return errors.New("proof has too many components") | ||
} | ||
|
||
// proof ok | ||
return nil | ||
} | ||
|
||
func parent(leafIndex int64) int64 { | ||
return leafIndex >> 1 | ||
} | ||
|
||
func isRightChild(leafIndex int64) bool { | ||
return leafIndex&1 == 1 | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RFC refers to inclusion proofs as "audit proofs". Do we consistently use the term "inclusion proof" instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - "audit proof" is kinda vague, so we settled on using "inclusion-" and "consistency-proof" for clarity.