Skip to content

Commit

Permalink
updating verify proof range to handle empty proof keys
Browse files Browse the repository at this point in the history
  • Loading branch information
rian committed Jun 7, 2024
1 parent 52a8ea9 commit c374dec
Show file tree
Hide file tree
Showing 4 changed files with 829 additions and 48 deletions.
295 changes: 287 additions & 8 deletions core/trie/proof.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package trie

import (
"errors"
"fmt"

"github.com/NethermindEth/juno/core/felt"
Expand Down Expand Up @@ -28,6 +29,13 @@ func (pn *ProofNode) Hash(hash hashFunc) *felt.Felt {
}
}

func (pn *ProofNode) Len() uint8 {
if pn.Binary != nil {
return 1
}
return pn.Edge.Path.len

Check warning on line 36 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L36

Added line #L36 was not covered by tests
}

func (pn *ProofNode) PrettyPrint() {
if pn.Binary != nil {
fmt.Printf(" Binary:\n")
Expand All @@ -48,12 +56,27 @@ type Binary struct {
}

type Edge struct {
Child *felt.Felt
Path *Key
Value *felt.Felt
Child *felt.Felt // child hash
Path *Key // path from parent to child
Value *felt.Felt // this nodes hash
}

func GetBoundaryProofs(leftBoundary, rightBoundary *Key, tri *Trie) ([2][]ProofNode, error) {
proofs := [2][]ProofNode{}
leftProof, err := GetProof(leftBoundary, tri)
if err != nil {
return proofs, err

Check warning on line 68 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L68

Added line #L68 was not covered by tests
}
rightProof, err := GetProof(rightBoundary, tri)
if err != nil {
return proofs, err

Check warning on line 72 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L72

Added line #L72 was not covered by tests
}
proofs[0] = leftProof
proofs[1] = rightProof
return proofs, nil
}

func isEdge(parentKey *Key, sNode storageNode) bool {
func isEdge(parentKey *Key, sNode StorageNode) bool {
sNodeLen := sNode.key.len
if parentKey == nil { // Root
return sNodeLen != 0
Expand All @@ -63,7 +86,7 @@ func isEdge(parentKey *Key, sNode storageNode) bool {

// Note: we need to account for the fact that Junos Trie has nodes that are Binary AND Edge,
// whereas the protocol requires nodes that are Binary XOR Edge
func transformNode(tri *Trie, parentKey *Key, sNode storageNode) (*Edge, *Binary, error) {
func transformNode(tri *Trie, parentKey *Key, sNode StorageNode) (*Edge, *Binary, error) {
isEdgeBool := isEdge(parentKey, sNode)

var edge *Edge
Expand All @@ -87,7 +110,7 @@ func transformNode(tri *Trie, parentKey *Key, sNode storageNode) (*Edge, *Binary
}

rightHash := rNode.Value
if isEdge(sNode.key, storageNode{node: rNode, key: sNode.node.Right}) {
if isEdge(sNode.key, StorageNode{node: rNode, key: sNode.node.Right}) {
edgePath := path(sNode.node.Right, sNode.key)
rEdge := ProofNode{Edge: &Edge{
Path: &edgePath,
Expand All @@ -96,7 +119,7 @@ func transformNode(tri *Trie, parentKey *Key, sNode storageNode) (*Edge, *Binary
rightHash = rEdge.Hash(tri.hash)
}
leftHash := lNode.Value
if isEdge(sNode.key, storageNode{node: lNode, key: sNode.node.Left}) {
if isEdge(sNode.key, StorageNode{node: lNode, key: sNode.node.Left}) {
edgePath := path(sNode.node.Left, sNode.key)
lEdge := ProofNode{Edge: &Edge{
Path: &edgePath,
Expand Down Expand Up @@ -147,7 +170,7 @@ func GetProof(key *Key, tri *Trie) ([]ProofNode, error) {
// https://github.com/eqlabs/pathfinder/blob/main/crates/merkle-tree/src/tree.rs#L2006
func VerifyProof(root *felt.Felt, key *Key, value *felt.Felt, proofs []ProofNode, hash hashFunc) bool {
expectedHash := root
remainingPath := key
remainingPath := NewKey(key.len, key.bitset[:])
for _, proofNode := range proofs {
if !proofNode.Hash(hash).Equal(expectedHash) {
return false
Expand All @@ -172,5 +195,261 @@ func VerifyProof(root *felt.Felt, key *Key, value *felt.Felt, proofs []ProofNode
remainingPath.Truncate(251 - proofNode.Edge.Path.Len()) //nolint:gomnd
}
}

return expectedHash.Equal(value)
}

// VerifyRangeProof verifies the range proof for the given range of keys.
// This is achieved by constructing a trie from the boundary proofs, and the supplied key-values.
// If the root of the reconstructed trie matches the supplied root, then the verification passes.
// If the trie is constructed incorrectly then the root will have an incorrect key(len,path), and value,
// and therefore it's hash won't match the expected root
// ref: https://github.com/ethereum/go-ethereum/blob/v1.14.3/trie/proof.go#L484
func VerifyRangeProof(root *felt.Felt, keys, values []*felt.Felt, proofKeys [2]*Key, proofValues [2]*felt.Felt,
proofs [2][]ProofNode, hash hashFunc,
) (bool, error) {
// Step 0: checks
if len(keys) != len(values) {
return false, fmt.Errorf("inconsistent proof data, number of keys: %d, number of values: %d", len(keys), len(values))

Check warning on line 213 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L213

Added line #L213 was not covered by tests
}

// Ensure all keys are monotonic increasing
if err := ensureMonotonicIncreasing(proofKeys, keys); err != nil {
return false, err

Check warning on line 218 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L218

Added line #L218 was not covered by tests
}

// Ensure the inner values contain no deletions
for _, value := range values {
if value.Equal(&felt.Zero) {
return false, errors.New("range contains deletion")

Check warning on line 224 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L224

Added line #L224 was not covered by tests
}
}

// Step 1: Verify proofs, and get proof paths
var proofPaths [2][]StorageNode
var err error
for i := 0; i < 2; i++ {
if proofs[i] != nil {
if !VerifyProof(root, proofKeys[i], proofValues[i], proofs[i], hash) {
return false, fmt.Errorf("invalid proof for key %x", proofKeys[i].String())

Check warning on line 234 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L234

Added line #L234 was not covered by tests
}

proofPaths[i], err = ProofToPath(proofs[i], proofKeys[i], hash)
if err != nil {
return false, err

Check warning on line 239 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L239

Added line #L239 was not covered by tests
}
}
}

// Step 2: Build trie from proofPaths and keys
tmpTrie, err := BuildTrie(proofPaths[0], proofPaths[1], keys, values)
if err != nil {
return false, err

Check warning on line 247 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L247

Added line #L247 was not covered by tests
}

// Verify that the recomputed root hash matches the provided root hash
recomputedRoot, err := tmpTrie.Root()
if err != nil {
return false, err

Check warning on line 253 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L253

Added line #L253 was not covered by tests
}
if !recomputedRoot.Equal(root) {
return false, errors.New("root hash mismatch")

Check warning on line 256 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L256

Added line #L256 was not covered by tests
}

return true, nil
}

func ensureMonotonicIncreasing(proofKeys [2]*Key, keys []*felt.Felt) error {
if proofKeys[0] != nil {
leftProofFelt := proofKeys[0].Felt()
if leftProofFelt.Cmp(keys[0]) >= 0 {
return errors.New("range is not monotonically increasing")

Check warning on line 266 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L266

Added line #L266 was not covered by tests
}
}
if proofKeys[1] != nil {
rightProofFelt := proofKeys[1].Felt()
if keys[len(keys)-1].Cmp(&rightProofFelt) >= 0 {
return errors.New("range is not monotonically increasing")

Check warning on line 272 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L272

Added line #L272 was not covered by tests
}
}
if len(keys) >= 2 {
for i := 0; i < len(keys)-1; i++ {
if keys[i].Cmp(keys[i+1]) >= 0 {
return errors.New("range is not monotonically increasing")

Check warning on line 278 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L278

Added line #L278 was not covered by tests
}
}
}
return nil
}

// shouldSquish determines if the node needs compressed, and if so, the len needed to arrive at the next key
func shouldSquish(idx int, proofNodes []ProofNode) (int, uint8) {
parent := &proofNodes[idx]
var child *ProofNode
// The child is nil of the current node is a leaf
if idx != len(proofNodes)-1 {
child = &proofNodes[idx+1]
}

if child == nil {
return 0, 0
}

if parent.Edge != nil && child.Binary != nil {
return 1, parent.Edge.Path.len
}

if parent.Binary != nil && child.Edge != nil {
return 1, child.Edge.Path.len
}

return 0, 0

Check warning on line 306 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L306

Added line #L306 was not covered by tests
}

func assignChild(crntNode *Node, nilKey, childKey *Key, isRight bool) {
if isRight {
crntNode.Right = childKey
crntNode.Left = nilKey
} else {
crntNode.Right = nilKey
crntNode.Left = childKey
}
}

// ProofToPath returns the set of storage nodes along the proofNodes towards the leaf.
// Note that only the nodes and children along this path will be set correctly.
func ProofToPath(proofNodes []ProofNode, leafKey *Key, hashF hashFunc) ([]StorageNode, error) {
pathNodes := []StorageNode{}

// Hack: this allows us to store a right without an existing left node.
zeroFeltBytes := new(felt.Felt).Bytes()
nilKey := NewKey(0, zeroFeltBytes[:])

i, offset := 0, 0
for i <= len(proofNodes)-1 {
var crntKey *Key
crntNode := Node{}

height := getHeight(i, pathNodes, proofNodes)

// Set the key of the current node
var err error
squishedParent, squishParentOffset := shouldSquish(i, proofNodes)
if proofNodes[i].Binary != nil {
crntKey, err = leafKey.SubKey(height)
} else {
crntKey, err = leafKey.SubKey(height + squishParentOffset)
}
if err != nil {
return nil, err

Check warning on line 344 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L344

Added line #L344 was not covered by tests
}
offset += squishedParent

// Set the value of the current node
crntNode.Value = proofNodes[i].Hash(hashF)

// Set the children of the current node
childIdx := i + squishedParent + 1
childIsRight := leafKey.Test(leafKey.len - crntKey.len - 1)
if i+2+squishedParent < len(proofNodes)-1 { // The child will be compressed, so point to its compressed form
squishedChild, squishChildOffset := shouldSquish(childIdx, proofNodes)
childKey, err := leafKey.SubKey(height + squishParentOffset + squishChildOffset + uint8(squishedChild))
if err != nil {
return nil, err

Check warning on line 358 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L355-L358

Added lines #L355 - L358 were not covered by tests
}
assignChild(&crntNode, &nilKey, childKey, childIsRight)

Check warning on line 360 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L360

Added line #L360 was not covered by tests
} else if i+1+offset == len(proofNodes)-1 { // The child points to a leaf, keep it as is
if proofNodes[childIdx].Edge != nil {
assignChild(&crntNode, &nilKey, leafKey, childIsRight)
} else {
childKey, err := leafKey.SubKey(crntKey.len + proofNodes[childIdx].Len())
if err != nil {
return nil, err

Check warning on line 367 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L367

Added line #L367 was not covered by tests
}
assignChild(&crntNode, &nilKey, childKey, childIsRight)
}
} else { // Current node points directly to leaf
if proofNodes[i].Edge != nil && len(pathNodes) > 0 {
break
}
assignChild(&crntNode, &nilKey, leafKey, childIsRight)
}

pathNodes = append(pathNodes, StorageNode{key: crntKey, node: &crntNode})
i += 1 + offset
}
pathNodes = addLeafNode(proofNodes, pathNodes, leafKey)
return pathNodes, nil
}

// getHeight returns the height of the current node, which depends on the previous
// height and whether the current proofnode is edge or binary
func getHeight(idx int, pathNodes []StorageNode, proofNodes []ProofNode) uint8 {
if len(pathNodes) > 0 {
if proofNodes[idx].Edge != nil {
return pathNodes[len(pathNodes)-1].key.len + proofNodes[idx].Edge.Path.len
} else {
return pathNodes[len(pathNodes)-1].key.len + 1
}
} else {
return 0
}
}

// addLeafNode appends the leaf node, if the final node in pathNodes points to a leaf.
func addLeafNode(proofNodes []ProofNode, pathNodes []StorageNode, leafKey *Key) []StorageNode {
lastNode := pathNodes[len(pathNodes)-1].node
lastProof := proofNodes[len(proofNodes)-1]
if lastNode.Left.Equal(leafKey) || lastNode.Right.Equal(leafKey) {
leafNode := Node{}
if lastProof.Edge != nil {
leafNode.Value = lastProof.Edge.Child
} else if lastNode.Left.Equal(leafKey) {
leafNode.Value = lastProof.Binary.LeftHash
} else {
leafNode.Value = lastProof.Binary.RightHash

Check warning on line 410 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L410

Added line #L410 was not covered by tests
}
pathNodes = append(pathNodes, StorageNode{key: leafKey, node: &leafNode})
}
return pathNodes
}

// BuildTrie builds a trie using the proof paths (including inner nodes), and then sets all the keys-values (leaves)
func BuildTrie(leftProofPath, rightProofPath []StorageNode, keys, values []*felt.Felt) (*Trie, error) {
tempTrie, err := NewTriePedersen(newMemStorage(), 251) //nolint:gomnd
if err != nil {
return nil, err

Check warning on line 421 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L421

Added line #L421 was not covered by tests
}

// merge proof paths
for i := range min(len(leftProofPath), len(rightProofPath)) {
if leftProofPath[i].key.Equal(rightProofPath[i].key) {
leftProofPath[i].node.Right = rightProofPath[i].node.Right
rightProofPath[i].node.Left = leftProofPath[i].node.Left
} else {
break
}
}

for _, sNode := range leftProofPath {
_, err := tempTrie.PutInner(sNode.key, sNode.node)
if err != nil {
return nil, err

Check warning on line 437 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L437

Added line #L437 was not covered by tests
}
}

for _, sNode := range rightProofPath {
_, err := tempTrie.PutInner(sNode.key, sNode.node)
if err != nil {
return nil, err

Check warning on line 444 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L444

Added line #L444 was not covered by tests
}
}

for i := range len(keys) {
_, err := tempTrie.PutWithProof(keys[i], values[i], leftProofPath, rightProofPath)
if err != nil {
return nil, err

Check warning on line 451 in core/trie/proof.go

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L451

Added line #L451 was not covered by tests
}
}
return tempTrie, nil
}
Loading

0 comments on commit c374dec

Please sign in to comment.