tasm_lib/mmr/
verify_from_memory.rs

1use std::collections::HashMap;
2
3use triton_vm::prelude::*;
4
5use super::leaf_index_to_mt_index_and_peak_index::MmrLeafIndexToMtIndexAndPeakIndex;
6use crate::hashing::merkle_step_mem_u64_index::MerkleStepMemU64Index;
7use crate::list::get::Get;
8use crate::prelude::*;
9use crate::traits::basic_snippet::Reviewer;
10use crate::traits::basic_snippet::SignOffFingerprint;
11
12/// Check whether a given leaf is a member of a pointed-to [MMR], with the
13/// inclusion proof (the authentication path) residing in RAM.
14///
15/// ### Behavior
16///
17/// ```text
18/// BEFORE: _ *peaks [leaf_count: u64] [leaf_index: u64] [leaf: Digest] *auth_path
19/// AFTER:  _ [validation_result: bool]
20/// ```
21///
22/// ### Preconditions
23///
24/// - all input arguments are properly [`BFieldCodec`] encoded
25/// - input arguments `*peaks` points to a properly [`BFieldCodec`] encoded list
26///   of [`Digest`]s
27/// - input arguments `*auth_path` points to a properly [`BFieldCodec`] encoded
28///   list of [`Digest`]s
29/// - the `leaf_count` is consistent with the pointed-to MMR
30/// - the `leaf_index` is smaller than the `leaf_count`
31///
32/// ### Postconditions
33///
34/// - all output is properly [`BFieldCodec`] encoded
35///
36/// [MMR]: twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator
37#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
38pub struct MmrVerifyFromMemory;
39
40impl BasicSnippet for MmrVerifyFromMemory {
41    fn inputs(&self) -> Vec<(DataType, String)> {
42        let list_type = DataType::List(Box::new(DataType::Digest));
43
44        vec![
45            (list_type.clone(), "*peaks".to_string()),
46            (DataType::U64, "leaf_count".to_string()),
47            (DataType::U64, "leaf_index".to_string()),
48            (DataType::Digest, "leaf".to_string()),
49            (list_type, "*auth_path".to_string()),
50        ]
51    }
52
53    fn outputs(&self) -> Vec<(DataType, String)> {
54        vec![(DataType::Bool, "validation_result".to_string())]
55    }
56
57    fn entrypoint(&self) -> String {
58        "tasmlib_mmr_verify_from_memory".into()
59    }
60
61    fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
62        let leaf_index_to_mt_index = library.import(Box::new(MmrLeafIndexToMtIndexAndPeakIndex));
63        let get_list_element = library.import(Box::new(Get::new(DataType::Digest)));
64        let merkle_step_mem = library.import(Box::new(MerkleStepMemU64Index));
65
66        let entrypoint = self.entrypoint();
67        let loop_label = format!("{entrypoint}_loop");
68
69        triton_asm!(
70            // BEFORE: _ *peaks [leaf_count: u64] [leaf_index: u64] [leaf: Digest] *auth_path
71            // AFTER:  _ validation_result
72            {entrypoint}:
73                pick 9 pick 9
74                pick 9 pick 9
75                call {leaf_index_to_mt_index}
76                // _ *peaks [leaf: Digest] *auth_path [mt_index: u64] peak_index
77
78                place 8
79                // _ *peaks peak_index [leaf: Digest] *auth_path [mt_index: u64]
80
81                place 7 place 7
82                // _ *peaks peak_index [mt_index: u64] [leaf: Digest] *auth_path
83
84                addi 1
85                place 7
86                // _ *peaks peak_index *auth_path[0] [mt_index: u64] [leaf: Digest]
87
88                call {loop_label}
89                // _ *peaks peak_index *auth_path[n] [1: u64] [peak: Digest]
90
91                /* Compare computed `peak` to `peaks[peak_index]`,
92                 * which is the expected peak.
93                 */
94                pick 9 pick 9
95                // _ *auth_path[n] [1: u64] [acc: Digest] *peaks peak_index
96
97                call {get_list_element}
98                // _ *auth_path[n] [1: u64] [acc: Digest] [expected_peak: Digest]
99
100                {&DataType::Digest.compare()}
101                // _ *auth_path[n] [1: u64] (expected_peak == acc_hash)
102                // _ *auth_path[n] [1: u64] validation_result
103
104                place 3
105                pop 3
106                // _ validation_result
107
108                return
109
110            // INVARIANT: _ *auth_path[n] [mt_index: u64] [acc: Digest]
111            {loop_label}:
112                dup 6 push 0 eq
113                dup 6 push 1 eq
114                mul
115                // _ *auth_path[n] [mt_index: u64] [acc: Digest] (mt_index == 1)
116
117                skiz return
118                // _ *auth_path[n] [mt_index: u64] [acc: Digest]
119
120                call {merkle_step_mem}
121                recurse
122        )
123    }
124
125    fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
126        let mut sign_offs = HashMap::new();
127        sign_offs.insert(Reviewer("ferdinand"), 0xc35bd96dd2348c49.into());
128        sign_offs
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use itertools::Itertools;
135    use rand::prelude::*;
136    use twenty_first::math::other::random_elements;
137    use twenty_first::util_types::mmr::mmr_accumulator::util::mmra_with_mps;
138
139    use super::*;
140    use crate::empty_stack;
141    use crate::mmr::MAX_MMR_HEIGHT;
142    use crate::test_prelude::*;
143    use crate::twenty_first::prelude::*;
144    use crate::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator;
145
146    impl MmrVerifyFromMemory {
147        fn set_up_initial_state(
148            &self,
149            mmr: &MmrAccumulator,
150            leaf: Digest,
151            leaf_index: u64,
152            auth_path: Vec<Digest>,
153        ) -> FunctionInitialState {
154            let peaks_pointer = bfe!(1);
155            let auth_path_pointer = bfe!(MAX_MMR_HEIGHT * Digest::LEN + 1);
156
157            let mut stack = self.init_stack_for_isolated_run();
158            stack.push(peaks_pointer);
159            push_encodable(&mut stack, &mmr.num_leafs());
160            push_encodable(&mut stack, &leaf_index);
161            push_encodable(&mut stack, &leaf);
162            stack.push(auth_path_pointer);
163
164            let mut memory = HashMap::default();
165            encode_to_memory(&mut memory, peaks_pointer, &mmr.peaks());
166            encode_to_memory(&mut memory, auth_path_pointer, &auth_path);
167
168            FunctionInitialState { stack, memory }
169        }
170    }
171
172    impl Function for MmrVerifyFromMemory {
173        fn rust_shadow(
174            &self,
175            stack: &mut Vec<BFieldElement>,
176            memory: &mut HashMap<BFieldElement, BFieldElement>,
177        ) {
178            let auth_path_pointer = stack.pop().unwrap();
179            let leaf = pop_encodable(stack);
180            let leaf_index = pop_encodable(stack);
181            let leaf_count = pop_encodable(stack);
182            let peaks_pointer = stack.pop().unwrap();
183
184            let auth_path = *Vec::<Digest>::decode_from_memory(memory, auth_path_pointer).unwrap();
185            let peaks = *Vec::<Digest>::decode_from_memory(memory, peaks_pointer).unwrap();
186
187            let proof = MmrMembershipProof::new(auth_path);
188            let is_valid = proof.verify(leaf_index, leaf, &peaks, leaf_count);
189            push_encodable(stack, &is_valid);
190        }
191
192        fn pseudorandom_initial_state(
193            &self,
194            seed: [u8; 32],
195            bench_case: Option<BenchmarkCase>,
196        ) -> FunctionInitialState {
197            let mut rng = StdRng::from_seed(seed);
198            let (leaf_index, leaf_count) = match bench_case {
199                Some(BenchmarkCase::CommonCase) => ((1 << 31) - 1, 1 << 31),
200                Some(BenchmarkCase::WorstCase) => ((1 << 62) - 1, 1 << 62),
201                None => {
202                    let leaf_count = rng.random_range(1..=1 << 62);
203                    let leaf_index = rng.random_range(0..leaf_count);
204                    (leaf_index, leaf_count)
205                }
206            };
207
208            let leaf = rng.random();
209            let (mmra, mps) = mmra_with_mps(leaf_count, vec![(leaf_index, leaf)]);
210            let auth_path = mps[0].authentication_path.clone();
211
212            self.set_up_initial_state(&mmra, leaf, leaf_index, auth_path)
213        }
214    }
215
216    #[test]
217    fn rust_shadow() {
218        ShadowedFunction::new(MmrVerifyFromMemory).test();
219    }
220
221    // This will crash the VM because leaf?index is not strictly less than leaf_count
222    #[test]
223    #[should_panic]
224    fn mmra_ap_verify_test_empty() {
225        let digest0 = Tip5::hash(&BFieldElement::new(4545));
226        let mmr = MmrAccumulator::new_from_leafs(vec![]);
227        let leaf_index = 0;
228        prop_verify_from_memory(&mmr, digest0, leaf_index, vec![], false);
229    }
230
231    #[test]
232    fn mmra_ap_verify_test_one() {
233        let digest0 = Tip5::hash(&BFieldElement::new(4545));
234        let mut mmr = MmrAccumulator::new_from_leafs(vec![]);
235        mmr.append(digest0);
236        let leaf_index = 0;
237        prop_verify_from_memory(&mmr, digest0, leaf_index, vec![], true);
238    }
239
240    #[test]
241    fn mmra_ap_verify_test_two() {
242        let digest0 = Tip5::hash(&BFieldElement::new(123));
243        let digest1 = Tip5::hash(&BFieldElement::new(456));
244
245        let mut mmr = MmrAccumulator::new_from_leafs(vec![]);
246        mmr.append(digest0);
247        mmr.append(digest1);
248
249        let leaf_index_0 = 0;
250        prop_verify_from_memory(&mmr, digest0, leaf_index_0, vec![digest1], true);
251
252        let leaf_index_1 = 1;
253        prop_verify_from_memory(&mmr, digest1, leaf_index_1, vec![digest0], true);
254    }
255
256    #[test]
257    fn mmra_ap_verify_test_pbt() {
258        let max_size = 19;
259
260        for leaf_count in 0..max_size {
261            let digests: Vec<Digest> = random_elements(leaf_count);
262
263            let (mmr, mps) = mmra_with_mps(
264                leaf_count as u64,
265                digests
266                    .iter()
267                    .cloned()
268                    .enumerate()
269                    .map(|(i, d)| (i as u64, d))
270                    .collect_vec(),
271            );
272
273            let bad_leaf: Digest = rand::rng().random();
274            for (leaf_index, leaf_digest) in digests.into_iter().enumerate() {
275                let auth_path = mps[leaf_index].clone();
276
277                // Positive test
278                prop_verify_from_memory(
279                    &mmr,
280                    leaf_digest,
281                    leaf_index as u64,
282                    auth_path.authentication_path.clone(),
283                    true,
284                );
285
286                // Negative tests
287                let bad_index = (leaf_index + 1) % leaf_count;
288                if bad_index != leaf_index {
289                    prop_verify_from_memory(
290                        &mmr,
291                        leaf_digest,
292                        bad_index as u64,
293                        auth_path.authentication_path.clone(),
294                        false,
295                    );
296                }
297                prop_verify_from_memory(
298                    &mmr,
299                    bad_leaf,
300                    leaf_index as u64,
301                    auth_path.authentication_path,
302                    false,
303                );
304            }
305        }
306    }
307
308    #[test]
309    fn mmra_ap_verify_many_leafs() {
310        for init_leaf_count in [
311            (1u64 << 40) + (1 << 21) + 510,
312            (1 << 32) - 1,
313            (1 << 61) - 3,
314            (1 << 61) - 2,
315            (1 << 61) - 1,
316            1 << 61,
317        ] {
318            // let init_peak_count = 10; // 1 + 1 + 8
319            let init_peak_count = init_leaf_count.count_ones();
320            println!("init_peak_count = {init_peak_count}");
321
322            // We can't construct this large archival MMRs, so we have to handle it with an MMRA
323            // and handle the membership proofs ourselves
324            let fake_peaks: Vec<Digest> = random_elements(init_peak_count as usize);
325            let mut mmr = MmrAccumulator::init(fake_peaks, init_leaf_count);
326
327            // Insert the 1st leaf
328            let second_to_last_leaf: Digest = rand::rng().random();
329            let second_to_last_leaf_index = init_leaf_count;
330            let mut real_membership_proof_second_to_last = mmr.append(second_to_last_leaf);
331
332            // Insert one more leaf and update the existing membership proof
333            let last_leaf: Digest = rand::rng().random();
334            let last_leaf_index = second_to_last_leaf_index + 1;
335            MmrMembershipProof::update_from_append(
336                &mut real_membership_proof_second_to_last,
337                second_to_last_leaf_index,
338                mmr.num_leafs(),
339                last_leaf,
340                &mmr.peaks(),
341            );
342            let real_membership_proof_last = mmr.append(last_leaf);
343
344            // Positive tests
345            prop_verify_from_memory(
346                &mmr,
347                second_to_last_leaf,
348                second_to_last_leaf_index,
349                real_membership_proof_second_to_last
350                    .authentication_path
351                    .clone(),
352                true,
353            );
354            prop_verify_from_memory(
355                &mmr,
356                last_leaf,
357                last_leaf_index,
358                real_membership_proof_last.authentication_path.clone(),
359                true,
360            );
361
362            // Negative tests
363            let bad_leaf: Digest = rand::rng().random();
364            prop_verify_from_memory(
365                &mmr,
366                bad_leaf,
367                second_to_last_leaf_index,
368                real_membership_proof_second_to_last.authentication_path,
369                false,
370            );
371            prop_verify_from_memory(
372                &mmr,
373                bad_leaf,
374                last_leaf_index,
375                real_membership_proof_last.authentication_path,
376                false,
377            );
378        }
379    }
380
381    fn prop_verify_from_memory(
382        mmr: &MmrAccumulator,
383        leaf: Digest,
384        leaf_index: u64,
385        auth_path: Vec<Digest>,
386        expect_validation_success: bool,
387    ) {
388        let exec_state =
389            MmrVerifyFromMemory.set_up_initial_state(mmr, leaf, leaf_index, auth_path.clone());
390
391        // AFTER: _ validation_result
392        let mut expected_final_stack = empty_stack();
393        expected_final_stack.push(bfe!(expect_validation_success as u64));
394
395        test_rust_equivalence_given_complete_state(
396            &ShadowedFunction::new(MmrVerifyFromMemory),
397            &exec_state.stack,
398            &[],
399            &NonDeterminism::default().with_ram(exec_state.memory),
400            &None,
401            Some(&expected_final_stack),
402        );
403
404        // Verify that auth path expectation was correct
405        assert_eq!(
406            expect_validation_success,
407            MmrMembershipProof::new(auth_path).verify(
408                leaf_index,
409                leaf,
410                &mmr.peaks(),
411                mmr.num_leafs(),
412            )
413        );
414    }
415}
416
417#[cfg(test)]
418mod benches {
419    use super::*;
420    use crate::test_prelude::*;
421
422    #[test]
423    fn benchmark() {
424        ShadowedFunction::new(MmrVerifyFromMemory).bench();
425    }
426}