Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VerifyProofs #1873

Closed
wants to merge 58 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
a03905e
VerifyProofs
May 21, 2024
79d3f63
lint
May 21, 2024
ae27bd9
aesthetic change leafRange
May 21, 2024
796684d
add failing test - wip
May 21, 2024
186b30a
update VerifyProofs signatrure
May 21, 2024
b95746f
VerifyProofs len check
May 21, 2024
22a97c0
failing tests..
May 21, 2024
7f79200
outline VerifyRangeProof using geth impl
May 22, 2024
8cad6c2
add some checks
May 22, 2024
d67b58e
wip
May 22, 2024
7988c02
wip
May 22, 2024
6efc223
wip
May 22, 2024
4955745
wip
May 22, 2024
44dfff0
wip
May 22, 2024
ac3a919
get proofNodeValue
May 22, 2024
437193d
wip
May 22, 2024
3cbb6a1
follow ashraf impl
May 24, 2024
1f061b7
proofToPath - wip
May 27, 2024
8a59f1e
TestChildMethods wip
May 27, 2024
4195504
only make proofs for leaves
May 27, 2024
1ab91b5
ProofToPath testing...
May 27, 2024
d7b9e13
VerifyRangeProof comment
May 28, 2024
8c603ae
update GetBoundaryProofs
May 29, 2024
ebae482
ProofToPath update
May 29, 2024
03b2449
first TestProoftoPath test passes!
May 29, 2024
79f3810
ProofToPath two tests pass
May 29, 2024
c54e148
remove unneeded methods
May 29, 2024
2719a7c
TestBuildTrie wip
May 29, 2024
081d3f7
wip TestProofToPath three key test
May 29, 2024
a850b52
three key trie test passes
May 29, 2024
64d439f
TestProofToPath passes
May 29, 2024
d3b19fe
buildTrieTest - root not updating for proof paths..
May 29, 2024
26a2888
reonstructing trie wip
May 30, 2024
3bc3f3e
reconstructed trie seems correct,fails on Root()
May 30, 2024
eb5669e
wip - twriting leaves overwrites boundarys..
May 31, 2024
f2a9740
insertNode not playing well with pre set inner nodes..
May 31, 2024
fd43ee8
reconstructed trie has correct structure (but incorrect root)
Jun 2, 2024
c4747b9
make test more explicit on structure
Jun 2, 2024
afa8758
leaf values are correct
Jun 2, 2024
e0a92cc
overriding built root.value gives correct commitment
Jun 3, 2024
69e37df
build trie passes!
Jun 3, 2024
4e9737e
lint
Jun 3, 2024
a537d68
test fix
Jun 3, 2024
56cf043
addd VerifyRangeProof test
Jun 3, 2024
71f732e
clean code up a bit
Jun 3, 2024
aca7481
add all-keys no proof case
Jun 4, 2024
70dc9bc
add left proof, all right keys case
Jun 4, 2024
ba71675
add right proof, all left key case
Jun 4, 2024
8de028b
address some comments
Jun 7, 2024
57b2d92
address comment
Jun 7, 2024
4b9d78b
address comment to refactor
Jun 7, 2024
ba2234a
comments
Jun 7, 2024
a71abfd
merge main
Jun 7, 2024
16c6b13
lint
Jun 7, 2024
a26c904
Merge branch 'main' into rianhughes/proof-range
rianhughes Jun 7, 2024
77af502
fix test
Jun 7, 2024
b1ef62d
asd
Jun 7, 2024
d90633e
lint
Jun 7, 2024
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
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) 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 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 @@

// 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 @@
}

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}) {
rianhughes marked this conversation as resolved.
Show resolved Hide resolved
edgePath := path(sNode.node.Right, sNode.key)
rEdge := ProofNode{Edge: &Edge{
Path: &edgePath,
Expand All @@ -96,7 +119,7 @@
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 @@
// 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 @@
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
Loading