1use std::collections::HashMap;
2
3use triton_vm::prelude::*;
4
5use crate::prelude::*;
6use crate::traits::basic_snippet::Reviewer;
7use crate::traits::basic_snippet::SignOffFingerprint;
8
9#[derive(Clone, Debug)]
32pub struct MerkleVerify;
33
34impl MerkleVerify {
35 pub const TREE_TOO_HIGH_ERROR_ID: i128 = 0;
36 pub const OUT_OF_BOUNDS_LEAF_ERROR_ID: i128 = 1;
37 pub const ROOT_MISMATCH_ERROR_ID: i128 = 2;
38}
39
40impl BasicSnippet for MerkleVerify {
41 fn parameters(&self) -> Vec<(DataType, String)> {
42 vec![
43 (DataType::Digest, "root".to_string()),
44 (DataType::U32, "tree_height".to_string()),
45 (DataType::U32, "leaf_index".to_string()),
46 (DataType::Digest, "leaf".to_string()),
47 ]
48 }
49
50 fn return_values(&self) -> Vec<(DataType, String)> {
51 vec![]
52 }
53
54 fn entrypoint(&self) -> String {
55 "tasmlib_hashing_merkle_verify".to_string()
56 }
57
58 fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
59 let entrypoint = self.entrypoint();
60 let traverse_tree = format!("{entrypoint}_traverse_tree");
61 let tree_height_is_not_zero = format!("{entrypoint}_tree_height_is_not_zero");
62 triton_asm!(
63 {entrypoint}:
66 push 32
78 dup 7
79 lt
80 assert error_id {Self::TREE_TOO_HIGH_ERROR_ID}
81
82 dup 6
84 push 2
85 pow
86 dup 0 dup 7 lt
89 assert error_id {Self::OUT_OF_BOUNDS_LEAF_ERROR_ID}
92 pick 6
95 add
96 place 5
99 pick 6
102 skiz
103 call {tree_height_is_not_zero}
104 pick 5
108 pop 1
109 assert_vector error_id {Self::ROOT_MISMATCH_ERROR_ID}
110 pop 5
111
112 return
113
114 {tree_height_is_not_zero}:
116 push 1
117 place 6
118 call {traverse_tree}
121 pick 6
124 pop 1
125
126 return
127
128 {traverse_tree}:
129 merkle_step
130 recurse_or_return
131 )
132 }
133
134 fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
135 let mut sign_offs = HashMap::new();
136 sign_offs.insert(Reviewer("ferdinand"), 0xc5311e6579152b58.into());
137 sign_offs
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use std::collections::VecDeque;
144
145 use proptest::collection::vec;
146
147 use super::*;
148 use crate::test_helpers::negative_test;
149 use crate::test_prelude::*;
150
151 impl ReadOnlyAlgorithm for MerkleVerify {
152 fn rust_shadow(
153 &self,
154 stack: &mut Vec<BFieldElement>,
155 _: &HashMap<BFieldElement, BFieldElement>,
156 _: VecDeque<BFieldElement>,
157 mut nd_digests: VecDeque<Digest>,
158 ) -> Result<(), RustShadowError> {
159 let leaf = pop_encodable(stack)?;
162 let leaf_index = pop_encodable::<u32>(stack)?;
163 let tree_height = pop_encodable::<u32>(stack)?;
164 let root = pop_encodable(stack)?;
165
166 let num_leaves = 1_u32
167 .checked_shl(tree_height)
168 .ok_or(RustShadowError::ArithmeticOverflow)?;
169 if leaf_index >= num_leaves {
170 return Err(RustShadowError::InvalidProof);
171 }
172
173 let mut node_digest = leaf;
174 let mut node_index = leaf_index + num_leaves;
175 while node_index != 1 {
176 let sibling = nd_digests
177 .pop_front()
178 .ok_or(RustShadowError::InvalidProof)?;
179 let node_is_left_sibling = node_index.is_multiple_of(2);
180 node_digest = if node_is_left_sibling {
181 Tip5::hash_pair(node_digest, sibling)
182 } else {
183 Tip5::hash_pair(sibling, node_digest)
184 };
185 node_index /= 2;
186 }
187 if node_digest != root {
188 return Err(RustShadowError::InvalidProof);
189 }
190
191 Ok(())
192 }
193
194 fn pseudorandom_initial_state(
195 &self,
196 seed: [u8; 32],
197 maybe_bench_case: Option<BenchmarkCase>,
198 ) -> ReadOnlyAlgorithmInitialState {
199 let mut rng = StdRng::from_seed(seed);
201 let tree_height = match maybe_bench_case {
202 Some(BenchmarkCase::CommonCase) => 6,
203 Some(BenchmarkCase::WorstCase) => 20,
204 None => rng.random_range(1..20),
205 };
206
207 let num_leaves = 1 << tree_height;
209 let leaf_index = rng.random_range(0..num_leaves);
210 let path = (0..tree_height).map(|_| rng.random()).collect_vec();
211 let leaf = rng.random();
212
213 let mut current_node = leaf;
215 let mut node_index = leaf_index + num_leaves;
216 for &sibling in &path {
217 let node_is_left_sibling = node_index % 2 == 0;
218 current_node = if node_is_left_sibling {
219 Tip5::hash_pair(current_node, sibling)
220 } else {
221 Tip5::hash_pair(sibling, current_node)
222 };
223 node_index /= 2;
224 }
225 let root = current_node;
226
227 let mut stack = Self.init_stack_for_isolated_run();
228 push_encodable(&mut stack, &root);
229 push_encodable(&mut stack, &tree_height);
230 push_encodable(&mut stack, &leaf_index);
231 push_encodable(&mut stack, &leaf);
232
233 let nondeterminism = NonDeterminism::default().with_digests(path);
234 ReadOnlyAlgorithmInitialState {
235 stack,
236 nondeterminism,
237 }
238 }
239 }
240
241 #[macro_rules_attr::apply(test)]
242 fn merkle_verify_test() {
243 ShadowedReadOnlyAlgorithm::new(MerkleVerify).test()
244 }
245
246 #[macro_rules_attr::apply(proptest)]
247 fn merkle_tree_verification_fails_if_leaf_is_disturbed_slightly(
248 seed: [u8; 32],
249 #[strategy(0_usize..5)] perturbation_index: usize,
250 #[filter(#perturbation != 0)] perturbation: i8,
251 ) {
252 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
253 let top_of_stack = initial_state.stack.len() - 1;
254 initial_state.stack[top_of_stack - perturbation_index] += bfe!(perturbation);
255
256 test_assertion_failure(
257 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
258 initial_state.into(),
259 &[2],
260 );
261 }
262
263 #[macro_rules_attr::apply(proptest)]
264 fn merkle_tree_verification_fails_if_leaf_index_is_disturbed_slightly(
265 seed: [u8; 32],
266 #[filter(#perturbation != 0)] perturbation: i8,
267 ) {
268 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
269 let top_of_stack = initial_state.stack.len() - 1;
270 let leaf_index_index = top_of_stack - 5;
271 initial_state.stack[leaf_index_index] += bfe!(perturbation);
272
273 let leaf_index = initial_state.stack[leaf_index_index];
275 prop_assume!(u32::try_from(leaf_index).is_ok());
276
277 test_assertion_failure(
278 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
279 initial_state.into(),
280 &[1, 2],
281 );
282 }
283
284 #[macro_rules_attr::apply(proptest)]
285 fn merkle_tree_verification_fails_if_leaf_index_is_out_of_range(
286 seed: [u8; 32],
287 #[strategy(u64::from(u32::MAX)..=BFieldElement::MAX)]
288 #[map(BFieldElement::new)]
289 leaf_index: BFieldElement,
290 ) {
291 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
292 let top_of_stack = initial_state.stack.len() - 1;
293 let leaf_index_index = top_of_stack - 5;
294 initial_state.stack[leaf_index_index] = leaf_index;
295
296 negative_test(
297 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
298 initial_state.into(),
299 &[OpStackError::FailedU32Conversion(leaf_index).into()],
300 );
301 }
302
303 #[macro_rules_attr::apply(proptest)]
304 fn merkle_tree_verification_fails_if_tree_height_is_disturbed_slightly(
305 seed: [u8; 32],
306 #[strategy(-32_i8..32)]
307 #[filter(#perturbation != 0)]
308 perturbation: i8,
309 #[strategy(vec(arb(), #perturbation.clamp(0, 32) as usize))]
310 additional_bogus_tree_nodes: Vec<Digest>,
311 ) {
312 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
313 let top_of_stack = initial_state.stack.len() - 1;
314 let tree_height_index = top_of_stack - 6;
315 initial_state.stack[tree_height_index] += bfe!(perturbation);
316
317 let perturbed_tree_height = initial_state.stack[tree_height_index];
319 prop_assume!(u32::try_from(perturbed_tree_height).is_ok());
320 prop_assume!(perturbed_tree_height.value() < 32);
321
322 initial_state
324 .nondeterminism
325 .digests
326 .extend(additional_bogus_tree_nodes);
327
328 let expected_errors = [
329 MerkleVerify::OUT_OF_BOUNDS_LEAF_ERROR_ID,
330 MerkleVerify::ROOT_MISMATCH_ERROR_ID,
331 ];
332 test_assertion_failure(
333 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
334 initial_state.into(),
335 &expected_errors,
336 );
337 }
338
339 #[macro_rules_attr::apply(proptest)]
340 fn merkle_tree_verification_fails_if_tree_height_is_too_large(
341 seed: [u8; 32],
342 #[strategy(32_u32..)] tree_height: u32,
343 ) {
344 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
345 let top_of_stack = initial_state.stack.len() - 1;
346 let tree_height_index = top_of_stack - 6;
347 initial_state.stack[tree_height_index] = bfe!(tree_height);
348
349 test_assertion_failure(
350 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
351 initial_state.into(),
352 &[MerkleVerify::TREE_TOO_HIGH_ERROR_ID],
353 );
354 }
355
356 #[macro_rules_attr::apply(proptest)]
357 fn merkle_tree_verification_fails_if_tree_height_is_way_too_large(
358 seed: [u8; 32],
359 #[strategy(u64::from(u32::MAX)..=BFieldElement::MAX)]
360 #[map(BFieldElement::new)]
361 tree_height: BFieldElement,
362 ) {
363 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
364 let top_of_stack = initial_state.stack.len() - 1;
365 let tree_height_index = top_of_stack - 6;
366 initial_state.stack[tree_height_index] = tree_height;
367
368 negative_test(
369 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
370 initial_state.into(),
371 &[OpStackError::FailedU32Conversion(tree_height).into()],
372 );
373 }
374
375 #[macro_rules_attr::apply(proptest)]
376 fn merkle_tree_verification_fails_if_root_is_disturbed_slightly(
377 seed: [u8; 32],
378 #[strategy(7_usize..12)] perturbation_index: usize,
379 #[filter(#perturbation != 0)] perturbation: i8,
380 ) {
381 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
382 let top_of_stack = initial_state.stack.len() - 1;
383 initial_state.stack[top_of_stack - perturbation_index] += bfe!(perturbation);
384
385 test_assertion_failure(
386 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
387 initial_state.into(),
388 &[MerkleVerify::ROOT_MISMATCH_ERROR_ID],
389 );
390 }
391}
392
393#[cfg(test)]
394mod bench {
395 use super::*;
396 use crate::test_prelude::*;
397
398 #[macro_rules_attr::apply(test)]
399 fn benchmark() {
400 ShadowedReadOnlyAlgorithm::new(MerkleVerify).bench()
401 }
402}