Skip to main content

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 parameters(&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 return_values(&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"), 0x79df3fecee2c597.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        ) -> Result<(), RustShadowError> {
178            let auth_path_pointer = stack.pop().ok_or(RustShadowError::StackUnderflow)?;
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().ok_or(RustShadowError::StackUnderflow)?;
183
184            let auth_path = *Vec::<Digest>::decode_from_memory(memory, auth_path_pointer)
185                .map_err(|_| RustShadowError::DecodingError)?;
186            let peaks = *Vec::<Digest>::decode_from_memory(memory, peaks_pointer)
187                .map_err(|_| RustShadowError::DecodingError)?;
188
189            let proof = MmrMembershipProof::new(auth_path);
190            let is_valid = proof.verify(leaf_index, leaf, &peaks, leaf_count);
191            push_encodable(stack, &is_valid);
192            Ok(())
193        }
194
195        fn pseudorandom_initial_state(
196            &self,
197            seed: [u8; 32],
198            bench_case: Option<BenchmarkCase>,
199        ) -> FunctionInitialState {
200            let mut rng = StdRng::from_seed(seed);
201            let (leaf_index, leaf_count) = match bench_case {
202                Some(BenchmarkCase::CommonCase) => ((1 << 31) - 1, 1 << 31),
203                Some(BenchmarkCase::WorstCase) => ((1 << 62) - 1, 1 << 62),
204                None => {
205                    let leaf_count = rng.random_range(1..=1 << 62);
206                    let leaf_index = rng.random_range(0..leaf_count);
207                    (leaf_index, leaf_count)
208                }
209            };
210
211            let leaf = rng.random();
212            let (mmra, mps) = mmra_with_mps(leaf_count, vec![(leaf_index, leaf)]);
213            let auth_path = mps[0].authentication_path.clone();
214
215            self.set_up_initial_state(&mmra, leaf, leaf_index, auth_path)
216        }
217    }
218
219    #[macro_rules_attr::apply(test)]
220    fn rust_shadow() {
221        ShadowedFunction::new(MmrVerifyFromMemory).test();
222    }
223
224    // This will crash the VM because leaf?index is not strictly less than leaf_count
225    #[macro_rules_attr::apply(test)]
226    #[should_panic]
227    fn mmra_ap_verify_test_empty() {
228        let digest0 = Tip5::hash(&BFieldElement::new(4545));
229        let mmr = MmrAccumulator::new_from_leafs(vec![]);
230        let leaf_index = 0;
231        prop_verify_from_memory(&mmr, digest0, leaf_index, vec![], false);
232    }
233
234    #[macro_rules_attr::apply(test)]
235    fn mmra_ap_verify_test_one() {
236        let digest0 = Tip5::hash(&BFieldElement::new(4545));
237        let mut mmr = MmrAccumulator::new_from_leafs(vec![]);
238        mmr.append(digest0);
239        let leaf_index = 0;
240        prop_verify_from_memory(&mmr, digest0, leaf_index, vec![], true);
241    }
242
243    #[macro_rules_attr::apply(test)]
244    fn mmra_ap_verify_test_two() {
245        let digest0 = Tip5::hash(&BFieldElement::new(123));
246        let digest1 = Tip5::hash(&BFieldElement::new(456));
247
248        let mut mmr = MmrAccumulator::new_from_leafs(vec![]);
249        mmr.append(digest0);
250        mmr.append(digest1);
251
252        let leaf_index_0 = 0;
253        prop_verify_from_memory(&mmr, digest0, leaf_index_0, vec![digest1], true);
254
255        let leaf_index_1 = 1;
256        prop_verify_from_memory(&mmr, digest1, leaf_index_1, vec![digest0], true);
257    }
258
259    #[macro_rules_attr::apply(test)]
260    fn mmra_ap_verify_test_pbt() {
261        let max_size = 19;
262
263        for leaf_count in 0..max_size {
264            let digests: Vec<Digest> = random_elements(leaf_count);
265
266            let (mmr, mps) = mmra_with_mps(
267                leaf_count as u64,
268                digests
269                    .iter()
270                    .cloned()
271                    .enumerate()
272                    .map(|(i, d)| (i as u64, d))
273                    .collect_vec(),
274            );
275
276            let bad_leaf: Digest = rand::rng().random();
277            for (leaf_index, leaf_digest) in digests.into_iter().enumerate() {
278                let auth_path = mps[leaf_index].clone();
279
280                // Positive test
281                prop_verify_from_memory(
282                    &mmr,
283                    leaf_digest,
284                    leaf_index as u64,
285                    auth_path.authentication_path.clone(),
286                    true,
287                );
288
289                // Negative tests
290                let bad_index = (leaf_index + 1) % leaf_count;
291                if bad_index != leaf_index {
292                    prop_verify_from_memory(
293                        &mmr,
294                        leaf_digest,
295                        bad_index as u64,
296                        auth_path.authentication_path.clone(),
297                        false,
298                    );
299                }
300                prop_verify_from_memory(
301                    &mmr,
302                    bad_leaf,
303                    leaf_index as u64,
304                    auth_path.authentication_path,
305                    false,
306                );
307            }
308        }
309    }
310
311    #[macro_rules_attr::apply(test)]
312    fn mmra_ap_verify_many_leafs() {
313        for init_leaf_count in [
314            (1u64 << 40) + (1 << 21) + 510,
315            (1 << 32) - 1,
316            (1 << 61) - 3,
317            (1 << 61) - 2,
318            (1 << 61) - 1,
319            1 << 61,
320        ] {
321            // let init_peak_count = 10; // 1 + 1 + 8
322            let init_peak_count = init_leaf_count.count_ones();
323            println!("init_peak_count = {init_peak_count}");
324
325            // We can't construct this large archival MMRs, so we have to handle it with an MMRA
326            // and handle the membership proofs ourselves
327            let fake_peaks: Vec<Digest> = random_elements(init_peak_count as usize);
328            let mut mmr = MmrAccumulator::init(fake_peaks, init_leaf_count);
329
330            // Insert the 1st leaf
331            let second_to_last_leaf: Digest = rand::rng().random();
332            let second_to_last_leaf_index = init_leaf_count;
333            let mut real_membership_proof_second_to_last = mmr.append(second_to_last_leaf);
334
335            // Insert one more leaf and update the existing membership proof
336            let last_leaf: Digest = rand::rng().random();
337            let last_leaf_index = second_to_last_leaf_index + 1;
338            MmrMembershipProof::update_from_append(
339                &mut real_membership_proof_second_to_last,
340                second_to_last_leaf_index,
341                mmr.num_leafs(),
342                last_leaf,
343                &mmr.peaks(),
344            );
345            let real_membership_proof_last = mmr.append(last_leaf);
346
347            // Positive tests
348            prop_verify_from_memory(
349                &mmr,
350                second_to_last_leaf,
351                second_to_last_leaf_index,
352                real_membership_proof_second_to_last
353                    .authentication_path
354                    .clone(),
355                true,
356            );
357            prop_verify_from_memory(
358                &mmr,
359                last_leaf,
360                last_leaf_index,
361                real_membership_proof_last.authentication_path.clone(),
362                true,
363            );
364
365            // Negative tests
366            let bad_leaf: Digest = rand::rng().random();
367            prop_verify_from_memory(
368                &mmr,
369                bad_leaf,
370                second_to_last_leaf_index,
371                real_membership_proof_second_to_last.authentication_path,
372                false,
373            );
374            prop_verify_from_memory(
375                &mmr,
376                bad_leaf,
377                last_leaf_index,
378                real_membership_proof_last.authentication_path,
379                false,
380            );
381        }
382    }
383
384    fn prop_verify_from_memory(
385        mmr: &MmrAccumulator,
386        leaf: Digest,
387        leaf_index: u64,
388        auth_path: Vec<Digest>,
389        expect_validation_success: bool,
390    ) {
391        let exec_state =
392            MmrVerifyFromMemory.set_up_initial_state(mmr, leaf, leaf_index, auth_path.clone());
393
394        // AFTER: _ validation_result
395        let mut expected_final_stack = empty_stack();
396        expected_final_stack.push(bfe!(expect_validation_success as u64));
397
398        test_rust_equivalence_given_complete_state(
399            &ShadowedFunction::new(MmrVerifyFromMemory),
400            &exec_state.stack,
401            &[],
402            &NonDeterminism::default().with_ram(exec_state.memory),
403            &None,
404            Some(&expected_final_stack),
405        );
406
407        // Verify that auth path expectation was correct
408        assert_eq!(
409            expect_validation_success,
410            MmrMembershipProof::new(auth_path).verify(
411                leaf_index,
412                leaf,
413                &mmr.peaks(),
414                mmr.num_leafs(),
415            )
416        );
417    }
418}
419
420#[cfg(test)]
421mod benches {
422    use super::*;
423    use crate::test_prelude::*;
424
425    #[macro_rules_attr::apply(test)]
426    fn benchmark() {
427        ShadowedFunction::new(MmrVerifyFromMemory).bench();
428    }
429}