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

implement getProof and verifyProof on trie #1852

Merged
merged 33 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1c1f8ca
implement getProof and verifyProof on trie
Apr 29, 2024
05d87da
address two comments
May 2, 2024
7a4dbfe
change VerifyProof signature
May 2, 2024
f4a5dd9
remove todo
May 2, 2024
65887a9
edge nodes - wip
May 3, 2024
53753d1
outline algo fro edge and binary
May 3, 2024
b374e0c
update GetProof to acocunt for edge nodes
May 6, 2024
2d0f58c
test - wip
May 6, 2024
b40a388
store hashes not keys
May 6, 2024
997c9d2
GetProof tidy
May 6, 2024
6e6391f
reimpl VerifyProof
May 6, 2024
233afe7
impl VerifyProof + tests
May 6, 2024
45eecb1
add anoter test for verify proof - both pass
May 6, 2024
a8dc36a
TestGetProofs passes
May 6, 2024
ce5549a
GetProof..
May 6, 2024
be5a195
GetProof juno-trie to pathfinder-trie..
May 6, 2024
e9d28dd
GetProof update - both tests pass
May 7, 2024
c343913
tidy
May 7, 2024
05761ef
fix getProof test 1
May 7, 2024
096ee27
test error source todo
May 7, 2024
57c64b6
add motes tests for GetProof - fail
May 8, 2024
b4a76d1
wip - path seems correct, rightHash is incorrect. Todo: edge-dge
May 8, 2024
97f2407
test left-right edge passes
May 8, 2024
c1b6419
edge-edge seems to work, only rightHash error
May 8, 2024
7959476
progress! use path(node,parent)
May 8, 2024
a02b13c
tidy
May 8, 2024
425e504
Merge branch 'main' into rianhughes/trie-proof2
rianhughes May 8, 2024
a862473
remove uneded methods
May 8, 2024
338df4c
tidy few things
May 9, 2024
f405556
small tidy
May 9, 2024
5e9b75b
Merge branch 'main' into rianhughes/trie-proof2
rianhughes May 9, 2024
2e3d34b
VerifyProof expects keys.len=251, and takes hashFun as parameter
May 13, 2024
a07d3d5
turn off golint flag
May 13, 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
38 changes: 38 additions & 0 deletions core/trie/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@
return k
}

func (k *Key) SubKey(n uint8) *Key {
if n > k.len {
panic("n is greater than the length of the key")

Check warning on line 28 in core/trie/key.go

View check run for this annotation

Codecov / codecov/patch

core/trie/key.go#L28

Added line #L28 was not covered by tests
}

newKey := &Key{len: n}
copy(newKey.bitset[:], k.bitset[len(k.bitset)-int((k.len+7)/8):]) //nolint:gomnd

// Shift right by the number of bits that are not needed
shift := k.len - n
for i := len(newKey.bitset) - 1; i >= 0; i-- {
newKey.bitset[i] >>= shift
if i > 0 {
newKey.bitset[i] |= newKey.bitset[i-1] << (8 - shift)
}
}

return newKey
}

func (k *Key) bytesNeeded() uint {
const byteBits = 8
return (uint(k.len) + (byteBits - 1)) / byteBits
Expand Down Expand Up @@ -114,3 +134,21 @@
inUseBytes[0] = (inUseBytes[0] << unusedBitsCount) >> unusedBitsCount
}
}

func (k *Key) RemoveLastBit() {
if k.len == 0 {
return

Check warning on line 140 in core/trie/key.go

View check run for this annotation

Codecov / codecov/patch

core/trie/key.go#L140

Added line #L140 was not covered by tests
}

k.len--

unusedBytes := k.unusedBytes()
clear(unusedBytes)

// clear upper bits on the last used byte
inUseBytes := k.inUseBytes()
unusedBitsCount := 8 - (k.len % 8)
if unusedBitsCount != 8 && len(inUseBytes) > 0 {
inUseBytes[0] = (inUseBytes[0] << unusedBitsCount) >> unusedBitsCount
}
}
180 changes: 180 additions & 0 deletions core/trie/proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package trie

import (
"fmt"

"github.com/NethermindEth/juno/core/crypto"
"github.com/NethermindEth/juno/core/felt"
)

// https://github.com/starknet-io/starknet-p2p-specs/blob/main/p2p/proto/snapshot.proto#L6
type ProofNode struct {
Binary *Binary
Edge *Edge
}

// Note: does not work for leaves
func (pn *ProofNode) Hash() *felt.Felt {
switch {
case pn.Binary != nil:
return crypto.Pedersen(pn.Binary.LeftHash, pn.Binary.RightHash)
case pn.Edge != nil:
length := make([]byte, len(pn.Edge.Path.bitset))
length[len(pn.Edge.Path.bitset)-1] = pn.Edge.Path.len
pathFelt := pn.Edge.Path.Felt()
lengthFelt := new(felt.Felt).SetBytes(length)
return new(felt.Felt).Add(crypto.Pedersen(pn.Edge.Child, &pathFelt), lengthFelt)
rianhughes marked this conversation as resolved.
Show resolved Hide resolved
default:
return nil

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L27-L28

Added lines #L27 - L28 were not covered by tests
}
}

func (pn *ProofNode) PrettyPrint() {
if pn.Binary != nil {
fmt.Printf(" Binary:\n")
fmt.Printf(" LeftHash: %v\n", pn.Binary.LeftHash)
fmt.Printf(" RightHash: %v\n", pn.Binary.RightHash)
}
if pn.Edge != nil {
fmt.Printf(" Edge:\n")
fmt.Printf(" Child: %v\n", pn.Edge.Child)
fmt.Printf(" Path: %v\n", pn.Edge.Path)
fmt.Printf(" Value: %v\n", pn.Edge.Value)
}
}

type Binary struct {
LeftHash *felt.Felt
RightHash *felt.Felt
rianhughes marked this conversation as resolved.
Show resolved Hide resolved
}

type Edge struct {
Child *felt.Felt
Path *Key
Value *felt.Felt
}

func isEdge(parentKey *Key, sNode storageNode) bool {
sNodeLen := sNode.key.len
if parentKey == nil { // Root
return sNodeLen != 0
}
return sNodeLen-parentKey.len > 1
}

// 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) {
isEdgeBool := isEdge(parentKey, sNode)

var edge *Edge
if isEdgeBool {
edgePath := path(sNode.key, parentKey)
edge = &Edge{
Path: &edgePath,
Child: sNode.node.Value,
}
}
if sNode.key.len == tri.height { // Leaf
return edge, nil, nil
}
lNode, err := tri.GetNodeFromKey(sNode.node.Left)
if err != nil {
return nil, nil, err

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L83

Added line #L83 was not covered by tests
}
rNode, err := tri.GetNodeFromKey(sNode.node.Right)
if err != nil {
return nil, nil, err

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L87

Added line #L87 was not covered by tests
}

rightHash := rNode.Value
if isEdge(sNode.key, storageNode{node: rNode, key: sNode.node.Right}) {
edgePath := path(sNode.node.Right, sNode.key)
rEdge := ProofNode{Edge: &Edge{
Path: &edgePath,
Child: rNode.Value,
rianhughes marked this conversation as resolved.
Show resolved Hide resolved
}}
rightHash = rEdge.Hash()
}
leftHash := lNode.Value
if isEdge(sNode.key, storageNode{node: lNode, key: sNode.node.Left}) {
edgePath := path(sNode.node.Left, sNode.key)
lEdge := ProofNode{Edge: &Edge{
Path: &edgePath,
Child: lNode.Value,
}}
leftHash = lEdge.Hash()
}
binary := &Binary{
LeftHash: leftHash,
RightHash: rightHash,
}

return edge, binary, nil
}

// https://github.com/eqlabs/pathfinder/blob/main/crates/merkle-tree/src/tree.rs#L514
func GetProof(leaf *felt.Felt, tri *Trie) ([]ProofNode, error) {
leafKey := tri.feltToKey(leaf)
nodesToLeaf, err := tri.nodesFromRoot(&leafKey)
if err != nil {
return nil, err

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L121

Added line #L121 was not covered by tests
}
proofNodes := []ProofNode{}

var parentKey *Key

for i := 0; i < len(nodesToLeaf); i++ {
sNode := nodesToLeaf[i]
sNodeEdge, sNodeBinary, err := transformNode(tri, parentKey, sNode)
if err != nil {
return nil, err

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L131

Added line #L131 was not covered by tests
}
isLeaf := sNode.key.len == tri.height

if sNodeEdge != nil && !isLeaf { // Internal Edge
proofNodes = append(proofNodes, []ProofNode{{Edge: sNodeEdge}, {Binary: sNodeBinary}}...)
} else if sNodeEdge == nil && !isLeaf { // Internal Binary
proofNodes = append(proofNodes, []ProofNode{{Binary: sNodeBinary}}...)
} else if sNodeEdge != nil && isLeaf { // Leaf Edge
proofNodes = append(proofNodes, []ProofNode{{Edge: sNodeEdge}}...)
} else if sNodeEdge == nil && sNodeBinary == nil { // sNode is a binary leaf
break
}
parentKey = nodesToLeaf[i].key
}
return proofNodes, nil
}

// verifyProof checks if `leafPath` leads from `root` to `leafHash` along the `proofNodes`
// 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) bool {
if key.Len() != key.len {
return false

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L153

Added line #L153 was not covered by tests
}

expectedHash := root
remainingPath := key

for _, proofNode := range proofs {
if !proofNode.Hash().Equal(expectedHash) {
return false

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L161

Added line #L161 was not covered by tests
}
switch {
case proofNode.Binary != nil:
if remainingPath.Test(remainingPath.Len() - 1) {
expectedHash = proofNode.Binary.RightHash

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L166

Added line #L166 was not covered by tests
} else {
expectedHash = proofNode.Binary.LeftHash
}
remainingPath.RemoveLastBit()
case proofNode.Edge != nil:
if !proofNode.Edge.Path.Equal(remainingPath.SubKey(proofNode.Edge.Path.Len())) {
return false

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

View check run for this annotation

Codecov / codecov/patch

core/trie/proof.go#L173

Added line #L173 was not covered by tests
}
expectedHash = proofNode.Edge.Child
remainingPath.Truncate(proofNode.Edge.Path.Len())
}
}
return expectedHash.Equal(value)
}
Loading
Loading