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 inputs(&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 outputs(&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"), 0x54be0725136e609e.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 ) {
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 << tree_height;
167 assert!(leaf_index < num_leaves);
168
169 let mut node_digest = leaf;
170 let mut node_index = leaf_index + num_leaves;
171 while node_index != 1 {
172 let sibling = nd_digests.pop_front().unwrap();
173 let node_is_left_sibling = node_index % 2 == 0;
174 node_digest = if node_is_left_sibling {
175 Tip5::hash_pair(node_digest, sibling)
176 } else {
177 Tip5::hash_pair(sibling, node_digest)
178 };
179 node_index /= 2;
180 }
181 assert_eq!(node_digest, root);
182 }
183
184 fn pseudorandom_initial_state(
185 &self,
186 seed: [u8; 32],
187 maybe_bench_case: Option<BenchmarkCase>,
188 ) -> ReadOnlyAlgorithmInitialState {
189 let mut rng = StdRng::from_seed(seed);
191 let tree_height = match maybe_bench_case {
192 Some(BenchmarkCase::CommonCase) => 6,
193 Some(BenchmarkCase::WorstCase) => 20,
194 None => rng.random_range(1..20),
195 };
196
197 let num_leaves = 1 << tree_height;
199 let leaf_index = rng.random_range(0..num_leaves);
200 let path = (0..tree_height).map(|_| rng.random()).collect_vec();
201 let leaf = rng.random();
202
203 let mut current_node = leaf;
205 let mut node_index = leaf_index + num_leaves;
206 for &sibling in &path {
207 let node_is_left_sibling = node_index % 2 == 0;
208 current_node = if node_is_left_sibling {
209 Tip5::hash_pair(current_node, sibling)
210 } else {
211 Tip5::hash_pair(sibling, current_node)
212 };
213 node_index /= 2;
214 }
215 let root = current_node;
216
217 let mut stack = Self.init_stack_for_isolated_run();
218 push_encodable(&mut stack, &root);
219 push_encodable(&mut stack, &tree_height);
220 push_encodable(&mut stack, &leaf_index);
221 push_encodable(&mut stack, &leaf);
222
223 let nondeterminism = NonDeterminism::default().with_digests(path);
224 ReadOnlyAlgorithmInitialState {
225 stack,
226 nondeterminism,
227 }
228 }
229 }
230
231 #[test]
232 fn merkle_verify_test() {
233 ShadowedReadOnlyAlgorithm::new(MerkleVerify).test()
234 }
235
236 #[proptest]
237 fn merkle_tree_verification_fails_if_leaf_is_disturbed_slightly(
238 seed: [u8; 32],
239 #[strategy(0_usize..5)] perturbation_index: usize,
240 #[filter(#perturbation != 0)] perturbation: i8,
241 ) {
242 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
243 let top_of_stack = initial_state.stack.len() - 1;
244 initial_state.stack[top_of_stack - perturbation_index] += bfe!(perturbation);
245
246 test_assertion_failure(
247 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
248 initial_state.into(),
249 &[2],
250 );
251 }
252
253 #[proptest]
254 fn merkle_tree_verification_fails_if_leaf_index_is_disturbed_slightly(
255 seed: [u8; 32],
256 #[filter(#perturbation != 0)] perturbation: i8,
257 ) {
258 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
259 let top_of_stack = initial_state.stack.len() - 1;
260 let leaf_index_index = top_of_stack - 5;
261 initial_state.stack[leaf_index_index] += bfe!(perturbation);
262
263 let leaf_index = initial_state.stack[leaf_index_index];
265 prop_assume!(u32::try_from(leaf_index).is_ok());
266
267 test_assertion_failure(
268 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
269 initial_state.into(),
270 &[1, 2],
271 );
272 }
273
274 #[proptest]
275 fn merkle_tree_verification_fails_if_leaf_index_is_out_of_range(
276 seed: [u8; 32],
277 #[strategy(u64::from(u32::MAX)..=BFieldElement::MAX)]
278 #[map(BFieldElement::new)]
279 leaf_index: BFieldElement,
280 ) {
281 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
282 let top_of_stack = initial_state.stack.len() - 1;
283 let leaf_index_index = top_of_stack - 5;
284 initial_state.stack[leaf_index_index] = leaf_index;
285
286 negative_test(
287 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
288 initial_state.into(),
289 &[OpStackError::FailedU32Conversion(leaf_index).into()],
290 );
291 }
292
293 #[proptest]
294 fn merkle_tree_verification_fails_if_tree_height_is_disturbed_slightly(
295 seed: [u8; 32],
296 #[strategy(-32_i8..32)]
297 #[filter(#perturbation != 0)]
298 perturbation: i8,
299 #[strategy(vec(arb(), #perturbation.clamp(0, 32) as usize))]
300 additional_bogus_tree_nodes: Vec<Digest>,
301 ) {
302 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
303 let top_of_stack = initial_state.stack.len() - 1;
304 let tree_height_index = top_of_stack - 6;
305 initial_state.stack[tree_height_index] += bfe!(perturbation);
306
307 let perturbed_tree_height = initial_state.stack[tree_height_index];
309 prop_assume!(u32::try_from(perturbed_tree_height).is_ok());
310 prop_assume!(perturbed_tree_height.value() < 32);
311
312 initial_state
314 .nondeterminism
315 .digests
316 .extend(additional_bogus_tree_nodes);
317
318 let expected_errors = [
319 MerkleVerify::OUT_OF_BOUNDS_LEAF_ERROR_ID,
320 MerkleVerify::ROOT_MISMATCH_ERROR_ID,
321 ];
322 test_assertion_failure(
323 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
324 initial_state.into(),
325 &expected_errors,
326 );
327 }
328
329 #[proptest]
330 fn merkle_tree_verification_fails_if_tree_height_is_too_large(
331 seed: [u8; 32],
332 #[strategy(32_u32..)] tree_height: u32,
333 ) {
334 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
335 let top_of_stack = initial_state.stack.len() - 1;
336 let tree_height_index = top_of_stack - 6;
337 initial_state.stack[tree_height_index] = bfe!(tree_height);
338
339 test_assertion_failure(
340 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
341 initial_state.into(),
342 &[MerkleVerify::TREE_TOO_HIGH_ERROR_ID],
343 );
344 }
345
346 #[proptest]
347 fn merkle_tree_verification_fails_if_tree_height_is_way_too_large(
348 seed: [u8; 32],
349 #[strategy(u64::from(u32::MAX)..=BFieldElement::MAX)]
350 #[map(BFieldElement::new)]
351 tree_height: BFieldElement,
352 ) {
353 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
354 let top_of_stack = initial_state.stack.len() - 1;
355 let tree_height_index = top_of_stack - 6;
356 initial_state.stack[tree_height_index] = tree_height;
357
358 negative_test(
359 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
360 initial_state.into(),
361 &[OpStackError::FailedU32Conversion(tree_height).into()],
362 );
363 }
364
365 #[proptest]
366 fn merkle_tree_verification_fails_if_root_is_disturbed_slightly(
367 seed: [u8; 32],
368 #[strategy(7_usize..12)] perturbation_index: usize,
369 #[filter(#perturbation != 0)] perturbation: i8,
370 ) {
371 let mut initial_state = MerkleVerify.pseudorandom_initial_state(seed, None);
372 let top_of_stack = initial_state.stack.len() - 1;
373 initial_state.stack[top_of_stack - perturbation_index] += bfe!(perturbation);
374
375 test_assertion_failure(
376 &ShadowedReadOnlyAlgorithm::new(MerkleVerify),
377 initial_state.into(),
378 &[MerkleVerify::ROOT_MISMATCH_ERROR_ID],
379 );
380 }
381}
382
383#[cfg(test)]
384mod bench {
385 use super::*;
386 use crate::test_prelude::*;
387
388 #[test]
389 fn benchmark() {
390 ShadowedReadOnlyAlgorithm::new(MerkleVerify).bench()
391 }
392}