Skip to content

Commit c96290d

Browse files
committed
starknet_patricia: add storage proof verification
1 parent 7e41fc2 commit c96290d

4 files changed

Lines changed: 135 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/apollo_committer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ tracing.workspace = true
2424
[dev-dependencies]
2525
assert_matches.workspace = true
2626
indexmap.workspace = true
27+
starknet_patricia = { workspace = true, features = ["testing"] }
2728
tokio.workspace = true
2829

2930
[lints]

crates/starknet_patricia/src/patricia_merkle_tree.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ pub mod errors;
22
pub mod filled_tree;
33
pub mod node_data;
44
pub mod original_skeleton_tree;
5+
#[cfg(feature = "testing")]
6+
pub mod storage_proof_verification;
57
pub mod traversal;
68
pub mod types;
79
pub mod updated_skeleton_tree;
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use std::collections::{HashMap, VecDeque};
2+
3+
use starknet_api::hash::HashOutput;
4+
use thiserror::Error;
5+
6+
use crate::patricia_merkle_tree::node_data::inner_node::{
7+
BinaryData,
8+
EdgeData,
9+
NodeData,
10+
Preimage,
11+
PreimageMap,
12+
};
13+
use crate::patricia_merkle_tree::node_data::leaf::Leaf;
14+
use crate::patricia_merkle_tree::types::NodeIndex;
15+
use crate::patricia_merkle_tree::updated_skeleton_tree::hash_function::TreeHashFunction;
16+
17+
#[derive(Debug, Error, PartialEq, Eq)]
18+
pub enum ProofVerificationError {
19+
#[error("missing leaf at index {index:?}")]
20+
MissingLeaf { index: NodeIndex },
21+
#[error("hash mismatch at index {index:?}: proof {proof_value:?}, actual {actual:?}")]
22+
HashMismatch { index: NodeIndex, proof_value: HashOutput, actual: HashOutput },
23+
#[error("duplicate parent for index {index:?}")]
24+
DuplicateParent { index: NodeIndex },
25+
}
26+
27+
/// Verifies that `preimages` form valid Patricia paths from each accessed leaf to `root_hash`.
28+
pub fn verify_patricia_proof<L: Leaf, TH: TreeHashFunction<L>>(
29+
root_hash: HashOutput,
30+
preimages: &PreimageMap,
31+
leaf_indices: &[NodeIndex],
32+
leaf_hashes: &HashMap<NodeIndex, HashOutput>,
33+
) -> Result<(), ProofVerificationError> {
34+
if leaf_indices.is_empty() {
35+
return Ok(());
36+
}
37+
38+
let hash_by_index = build_proof_index_maps::<L, TH>(root_hash, preimages)?;
39+
40+
// Once an index-->hash map is built, it suffices to verify that the expected leaf hashes are
41+
// present in the map, since we already verified the validity of the path from the to the leaf
42+
// during the map construction.
43+
for leaf_index in leaf_indices {
44+
let actual_leaf_hash = leaf_hashes.get(leaf_index).ok_or_else(|| {
45+
panic!("leaf_hashes must contain every requested leaf index; missing {leaf_index:?}")
46+
})?;
47+
48+
match hash_by_index.get(leaf_index) {
49+
None => return Err(ProofVerificationError::MissingLeaf { index: *leaf_index }),
50+
Some(proof_value) if *proof_value != *actual_leaf_hash => {
51+
return Err(ProofVerificationError::HashMismatch {
52+
index: *leaf_index,
53+
proof_value: *proof_value,
54+
actual: *actual_leaf_hash,
55+
});
56+
}
57+
Some(_) => {}
58+
}
59+
}
60+
61+
Ok(())
62+
}
63+
64+
/// Builds an `index -> hash` map by expanding a Patricia proof from the root.
65+
///
66+
/// Verifies that the supplied hashes are consistent with the supplied preimages.
67+
pub fn build_proof_index_maps<L: Leaf, TH: TreeHashFunction<L>>(
68+
root_hash: HashOutput,
69+
preimages: &PreimageMap,
70+
) -> Result<HashMap<NodeIndex, HashOutput>, ProofVerificationError> {
71+
let mut hash_by_index = HashMap::from([(NodeIndex::ROOT, root_hash)]);
72+
let mut queue = VecDeque::from([NodeIndex::ROOT]);
73+
74+
while let Some(index) = queue.pop_front() {
75+
let hash = hash_by_index[&index];
76+
77+
let Some(preimage) = preimages.get(&hash) else {
78+
continue;
79+
};
80+
81+
let computed_hash = TH::compute_node_hash(&preimage_to_node_data::<L>(preimage));
82+
if hash != computed_hash {
83+
return Err(ProofVerificationError::HashMismatch {
84+
index,
85+
proof_value: hash,
86+
actual: computed_hash,
87+
});
88+
}
89+
90+
match preimage {
91+
Preimage::Binary(binary) => {
92+
let [left_index, right_index] = index.get_children_indices();
93+
register_child(&mut hash_by_index, &mut queue, left_index, binary.left_data)?;
94+
register_child(&mut hash_by_index, &mut queue, right_index, binary.right_data)?;
95+
}
96+
Preimage::Edge(edge) => {
97+
let bottom_index = edge.path_to_bottom.bottom_index(index);
98+
register_child(&mut hash_by_index, &mut queue, bottom_index, edge.bottom_data)?;
99+
}
100+
}
101+
}
102+
103+
Ok(hash_by_index)
104+
}
105+
106+
fn register_child(
107+
hash_by_index: &mut HashMap<NodeIndex, HashOutput>,
108+
queue: &mut VecDeque<NodeIndex>,
109+
child_index: NodeIndex,
110+
child_hash: HashOutput,
111+
) -> Result<(), ProofVerificationError> {
112+
if hash_by_index.contains_key(&child_index) {
113+
return Err(ProofVerificationError::DuplicateParent { index: child_index });
114+
}
115+
hash_by_index.insert(child_index, child_hash);
116+
queue.push_back(child_index);
117+
Ok(())
118+
}
119+
120+
fn preimage_to_node_data<L: Leaf>(preimage: &Preimage) -> NodeData<L, HashOutput> {
121+
match preimage {
122+
Preimage::Binary(binary) => NodeData::Binary(BinaryData {
123+
left_data: binary.left_data,
124+
right_data: binary.right_data,
125+
}),
126+
Preimage::Edge(edge) => NodeData::Edge(EdgeData {
127+
bottom_data: edge.bottom_data,
128+
path_to_bottom: edge.path_to_bottom,
129+
}),
130+
}
131+
}

0 commit comments

Comments
 (0)