generated from eigerco/beerus
-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Starknet storage proof verifier (#189)
Starknet storage proof verifier. ## Pull Request type <!-- Please try to limit your pull request to one type; submit multiple pull requests if needed. --> Please check the type of change your PR introduces: - [ ] Bugfix - [x] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no API changes) - [ ] Build-related changes - [ ] Documentation content changes - [ ] Other (please describe): ## What is the current behavior? <!-- Please describe the current behavior that you are modifying, or link to a relevant issue. --> Issue Number: N/A ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - - - ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this does introduce a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR, such as screenshots of how the component looks before and after the change. -->
- Loading branch information
Showing
16 changed files
with
886 additions
and
24 deletions.
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
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 |
---|---|---|
@@ -1,25 +1,17 @@ | ||
# Data structures | ||
## [Array extension](./src/array_ext.cairo) | ||
A collection of handy functions to help with array manipulation. | ||
|
||
## [Merkle Tree](./src/merkle_tree.cairo) | ||
|
||
The Merkle tree algorithm is a cryptographic hashing algorithm used to create a hash tree, which is a tree data structure where each leaf node represents a data block and each non-leaf node represents a hash of its child nodes. | ||
The purpose of the Merkle tree algorithm is to provide a way to verify the integrity and authenticity of large amounts of data without needing to store the entire data set. The algorithm has applications in various areas of computer science, including cryptocurrency, file sharing, and database management. | ||
The Merkle tree algorithm is also used in creating digital signatures and verifying the authenticity of transactions. | ||
By providing a secure and efficient way to verify data integrity, the Merkle tree algorithm is an important tool in cryptography and information security. | ||
A generic implementation is available to manage both pedersen (legacy) and poseidon hash methods. | ||
A collection of handy functions to help with array manipulation. | ||
|
||
## [Queue](./src/queue.cairo) | ||
|
||
The queue is used to store and manipulate a collection of elements where the elements are processed in a first-in, first-out (FIFO) order. | ||
The purpose of the queue algorithm is to provide a way to manage and process elements in a specific order, where the oldest element is processed first. | ||
The queue algorithm has applications in various areas of computer science, including operating systems, networking, and data processing. It is used to manage tasks that are processed in a specific order and ensure that the order is maintained. | ||
The queue is used to store and manipulate a collection of elements where the elements are processed in a first-in, first-out (FIFO) order. | ||
The purpose of the queue algorithm is to provide a way to manage and process elements in a specific order, where the oldest element is processed first. | ||
The queue algorithm has applications in various areas of computer science, including operating systems, networking, and data processing. It is used to manage tasks that are processed in a specific order and ensure that the order is maintained. | ||
By providing a simple and efficient way to manage elements in a specific order, the queue algorithm is an important tool in computer science and software development. | ||
|
||
## [Stack](./src/stack.cairo) | ||
|
||
The stack is used to store and manipulate a collection of elements where the elements are processed in a last-in, first-out (LIFO) order. | ||
The purpose of the stack algorithm is to provide a way to manage and process elements in a specific order, where the most recently added element is processed first. | ||
The stack algorithm has applications in various areas of computer science, including programming languages, operating systems, and network protocols. It is used to manage tasks that require a temporary storage of data or information, and also for processing recursive function calls. | ||
The stack is used to store and manipulate a collection of elements where the elements are processed in a last-in, first-out (LIFO) order. | ||
The purpose of the stack algorithm is to provide a way to manage and process elements in a specific order, where the most recently added element is processed first. | ||
The stack algorithm has applications in various areas of computer science, including programming languages, operating systems, and network protocols. It is used to manage tasks that require a temporary storage of data or information, and also for processing recursive function calls. | ||
By providing a simple and efficient way to manage elements in a specific order, the stack algorithm is an important tool in computer science and software development. |
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 |
---|---|---|
@@ -1,7 +1,6 @@ | ||
mod array_ext; | ||
mod byte_array_ext; | ||
mod byte_array_reader; | ||
mod merkle_tree; | ||
mod queue; | ||
mod stack; | ||
mod vec; | ||
|
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 |
---|---|---|
@@ -1,7 +1,6 @@ | ||
mod array_ext_test; | ||
mod byte_array_ext_test; | ||
mod byte_array_reader_test; | ||
mod merkle_tree_test; | ||
mod queue_test; | ||
mod stack_test; | ||
mod vec_test; |
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,12 @@ | ||
# Merkle Tree related stuff | ||
|
||
## [Merkle Tree](./src/merkle_tree.cairo) | ||
|
||
The Merkle tree algorithm is a cryptographic hashing algorithm used to create a hash tree, which is a tree data structure where each leaf node represents a data block and each non-leaf node represents a hash of its child nodes. | ||
The purpose of the Merkle tree algorithm is to provide a way to verify the integrity and authenticity of large amounts of data without needing to store the entire data set. The algorithm has applications in various areas of computer science, including cryptocurrency, file sharing, and database management. | ||
The Merkle tree algorithm is also used in creating digital signatures and verifying the authenticity of transactions. | ||
By providing a secure and efficient way to verify data integrity, the Merkle tree algorithm is an important tool in cryptography and information security. | ||
A generic implementation is available to manage both pedersen (legacy) and poseidon hash methods. | ||
|
||
## [Starknet Storage Proof Verifier](./src/storage_proof.cairo) | ||
Implementation of Starknet ([storage proofs](https://docs.starknet.io/documentation/architecture_and_concepts/State/starknet-state/)) returned by `pathfinder_getproof` API endpoint ([see](https://github.com/eqlabs/pathfinder/blob/main/doc/rpc/pathfinder_rpc_api.json)). |
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,8 @@ | ||
[package] | ||
name = "alexandria_merkle_tree" | ||
version = "0.1.0" | ||
description = "Merkle tree related stuff" | ||
homepage = "https://github.com/keep-starknet-strange/alexandria/tree/src/merkle_tree" | ||
|
||
[dependencies] | ||
starknet.workspace = true |
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,6 @@ | ||
mod merkle_tree; | ||
mod storage_proof; | ||
|
||
|
||
#[cfg(test)] | ||
mod tests; |
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
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,163 @@ | ||
use pedersen::PedersenTrait; | ||
use poseidon::PoseidonTrait; | ||
use hash::HashStateTrait; | ||
|
||
#[derive(Drop)] | ||
struct BinaryNode { | ||
left: felt252, | ||
right: felt252, | ||
} | ||
|
||
#[derive(Drop, Copy)] | ||
struct EdgeNode { | ||
child: felt252, | ||
path: felt252, | ||
length: u8, | ||
} | ||
|
||
#[derive(Drop)] | ||
enum TrieNode { | ||
Binary: BinaryNode, | ||
Edge: EdgeNode, | ||
} | ||
|
||
#[derive(Destruct)] | ||
struct ContractData { | ||
class_hash: felt252, | ||
nonce: felt252, | ||
contract_state_hash_version: felt252, | ||
storage_proof: Array<TrieNode> | ||
} | ||
|
||
#[derive(Destruct)] | ||
struct ContractStateProof { | ||
class_commitment: felt252, | ||
contract_proof: Array<TrieNode>, | ||
contract_data: ContractData | ||
} | ||
|
||
/// Verify Starknet storage proof. For reference see: | ||
/// - ([state](https://docs.starknet.io/documentation/architecture_and_concepts/State/starknet-state/)) | ||
/// - ([pathfinder_getproof API endpoint](https://github.com/eqlabs/pathfinder/blob/main/doc/rpc/pathfinder_rpc_api.json)) | ||
/// - ([pathfinder storage implementation](https://github.com/eqlabs/pathfinder/blob/main/crates/merkle-tree/src/tree.rs)) | ||
/// # Arguments | ||
/// * `expected_state_commitment` - state root `proof` is going to be verified against | ||
/// * `contract_address` - `contract_address` of the value to be verified | ||
/// * `storage_address` - `storage_address` of the value to be verified | ||
/// * `proof` - `ContractStateProof` representing storage proof | ||
/// # Returns | ||
/// * `felt252` - `value` at `storage_address` if verified, panic otherwise. | ||
fn verify( | ||
expected_state_commitment: felt252, | ||
contract_address: felt252, | ||
storage_address: felt252, | ||
proof: ContractStateProof | ||
) -> felt252 { | ||
let contract_data = proof.contract_data; | ||
|
||
let (contract_root_hash, storage_value) = traverse( | ||
storage_address, contract_data.storage_proof | ||
); | ||
|
||
let contract_state_hash = pedersen_hash_4( | ||
contract_data.class_hash, | ||
contract_root_hash, | ||
contract_data.nonce, | ||
contract_data.contract_state_hash_version | ||
); | ||
|
||
let (contracts_tree_root, expected_contract_state_hash) = traverse( | ||
contract_address, proof.contract_proof | ||
); | ||
|
||
assert(expected_contract_state_hash == contract_state_hash, 'wrong contract_state_hash'); | ||
|
||
let state_commitment = poseidon_hash( | ||
'STARKNET_STATE_V0', contracts_tree_root, proof.class_commitment | ||
); | ||
|
||
assert(expected_state_commitment == state_commitment, 'wrong state_commitment'); | ||
|
||
storage_value | ||
} | ||
|
||
fn traverse(expected_path: felt252, proof: Array<TrieNode>) -> (felt252, felt252) { | ||
let mut path: felt252 = 0; | ||
let mut remaining_depth: u8 = 251; | ||
|
||
let mut nodes = proof.span(); | ||
let expected_path_u256: u256 = expected_path.into(); | ||
|
||
let leaf = *match nodes.pop_back().unwrap() { | ||
TrieNode::Binary(_) => panic_with_felt252('invalid leaf type'), | ||
TrieNode::Edge(edge) => edge | ||
}; | ||
|
||
let mut expected_hash = node_hash(@TrieNode::Edge(leaf)); | ||
let value = leaf.child; | ||
let mut path = leaf.path; | ||
let mut path_length_pow2 = pow(2, leaf.length); | ||
|
||
loop { | ||
match nodes.pop_back() { | ||
Option::Some(node) => { | ||
match node { | ||
TrieNode::Binary(binary_node) => { | ||
if expected_path_u256 & path_length_pow2.into() > 0 { | ||
assert(expected_hash == *binary_node.right, 'invalid node hash'); | ||
path += path_length_pow2; | ||
} else { | ||
assert(expected_hash == *binary_node.left, 'invalid node hash'); | ||
}; | ||
path_length_pow2 *= 2; | ||
}, | ||
TrieNode::Edge(edge_node) => { | ||
assert(expected_hash == *edge_node.child, 'invalid node hash'); | ||
path += *edge_node.path * path_length_pow2; | ||
path_length_pow2 *= pow(2, *edge_node.length); | ||
} | ||
} | ||
expected_hash = node_hash(node); | ||
}, | ||
Option::None => { break; } | ||
}; | ||
}; | ||
assert(expected_path == path, 'invalid proof path'); | ||
(expected_hash, value) | ||
} | ||
|
||
#[inline] | ||
fn node_hash(node: @TrieNode) -> felt252 { | ||
match node { | ||
TrieNode::Binary(binary) => pedersen_hash(*binary.left, *binary.right), | ||
TrieNode::Edge(edge) => pedersen_hash(*edge.child, *edge.path) + (*edge.length).into() | ||
} | ||
} | ||
|
||
// TODO: replace with lookup table once array constants are available in Cairo | ||
fn pow(x: felt252, n: u8) -> felt252 { | ||
if n == 0 { | ||
1 | ||
} else if n == 1 { | ||
x | ||
} else if (n & 1) == 1 { | ||
x * pow(x * x, n / 2) | ||
} else { | ||
pow(x * x, n / 2) | ||
} | ||
} | ||
|
||
#[inline(always)] | ||
fn pedersen_hash(a: felt252, b: felt252) -> felt252 { | ||
PedersenTrait::new(a).update(b).finalize() | ||
} | ||
|
||
#[inline(always)] | ||
fn pedersen_hash_4(a: felt252, b: felt252, c: felt252, d: felt252) -> felt252 { | ||
PedersenTrait::new(a).update(b).update(c).update(d).finalize() | ||
} | ||
|
||
#[inline(always)] | ||
fn poseidon_hash(a: felt252, b: felt252, c: felt252) -> felt252 { | ||
PoseidonTrait::new().update(a).update(b).update(c).finalize() | ||
} |
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,3 @@ | ||
mod merkle_tree_test; | ||
mod storage_proof_test; | ||
mod storage_proof_test_data; |
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,18 @@ | ||
#! /usr/bin/env bash | ||
set -e; | ||
set -o pipefail; | ||
|
||
curl -s -X POST \ | ||
-H 'Content-Type: application/json' \ | ||
-d '{ | ||
"jsonrpc": "2.0", | ||
"method": "pathfinder_getProof", | ||
"params": { | ||
"block_id": { "block_hash": "'${1}'"}, | ||
"contract_address": "'${2}'", | ||
"keys": ["'${3}'"] | ||
}, | ||
"id": 0 | ||
}' \ | ||
$STARKNET_RPC \ | ||
| jq -r -f storage_proof_filter.jq |
4 changes: 2 additions & 2 deletions
4
...ructures/src/tests/merkle_tree_test.cairo → ...kle_tree/src/tests/merkle_tree_test.cairo
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
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,34 @@ | ||
def node: | ||
if has("binary") | ||
then | ||
"TrieNode::Binary( | ||
BinaryNode { | ||
left: \(.binary.left), | ||
right: \(.binary.right), | ||
} | ||
)" | ||
else | ||
"TrieNode::Edge( | ||
EdgeNode { | ||
path: \(.edge.path.value), | ||
length: \(.edge.path.len), | ||
child: \(.edge.child), | ||
} | ||
)" | ||
end | ||
; | ||
|
||
def proof: | ||
"array![\n\(.|map(node) | join(",\n"))\n]" | ||
; | ||
|
||
.result|"ContractStateProof { | ||
class_commitment: \(.class_commitment), | ||
contract_proof: \(.contract_proof | proof), | ||
contract_data: \(.contract_data|"ContractData { | ||
class_hash: \(.class_hash), | ||
nonce: \(.nonce), | ||
contract_state_hash_version: 0, | ||
storage_proof: \(.storage_proofs | .[0] | proof), | ||
}") | ||
}" |
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,58 @@ | ||
use alexandria_merkle_tree::storage_proof::{ | ||
ContractStateProof, ContractData, TrieNode, BinaryNode, EdgeNode, verify | ||
}; | ||
|
||
use alexandria_merkle_tree::tests::storage_proof_test_data::{balance_proof, total_balance_proof}; | ||
|
||
const DAI: felt252 = 0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3; | ||
const ETH: felt252 = 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7; | ||
|
||
#[test] | ||
#[available_gas(2000000)] | ||
fn balance_lsb_proof_test() { | ||
let state_commitment = 0x07dc88984a2d8f9c2a6d2d431b2d8f2c32957da514c16ceef0761b6933121708; | ||
let contract_address = DAI; | ||
let storage_address = 0x4ae51d08cd202d1472587dfe63dbf2d5ec767cbf4218b59b7ab71956780c6ee; | ||
let expected_value = 8700000000000000005; | ||
let proof = balance_proof(); | ||
let value = verify(state_commitment, contract_address, storage_address, proof); | ||
assert(expected_value == value, 'wrong value'); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected: ('invalid proof path',))] | ||
#[available_gas(2000000)] | ||
fn balance_msb_proof_test() { | ||
let state_commitment = 0x07dc88984a2d8f9c2a6d2d431b2d8f2c32957da514c16ceef0761b6933121708; | ||
let contract_address = DAI; | ||
let storage_address = 0x4ae51d08cd202d1472587dfe63dbf2d5ec767cbf4218b59b7ab71956780c6ef; | ||
let expected_value = 8700000000000000005; | ||
let proof = balance_proof(); | ||
let value = verify(state_commitment, contract_address, storage_address, proof); | ||
assert(expected_value == value, 'wrong value'); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected: ('invalid node hash',))] | ||
#[available_gas(2000000)] | ||
fn wrong_contract_address_proof_test() { | ||
let state_commitment = 0x07dc88984a2d8f9c2a6d2d431b2d8f2c32957da514c16ceef0761b6933121708; | ||
let contract_address = ETH; | ||
let storage_address = 0x4ae51d08cd202d1472587dfe63dbf2d5ec767cbf4218b59b7ab71956780c6ee; | ||
let expected_value = 8700000000000000005; | ||
let proof = balance_proof(); | ||
let value = verify(state_commitment, contract_address, storage_address, proof); | ||
assert(expected_value == value, 'wrong value'); | ||
} | ||
|
||
#[test] | ||
#[available_gas(50000000)] | ||
fn total_balance_lsb_proof_test() { | ||
let state_commitment = 0x07dc88984a2d8f9c2a6d2d431b2d8f2c32957da514c16ceef0761b6933121708; | ||
let contract_address = DAI; | ||
let storage_address = 0x37a9774624a0e3e0d8e6b72bd35514f62b3e8e70fbaff4ed27181de4ffd4604; | ||
let expected_value = 2970506847688829412026631; | ||
let proof = total_balance_proof(); | ||
let value = verify(state_commitment, contract_address, storage_address, proof); | ||
assert(expected_value == value, 'wrong value'); | ||
} |
Oops, something went wrong.