Skip to main content

tasm_lib/mmr/
verify_from_secret_in_secret_leaf_index.rs

1use triton_vm::prelude::*;
2
3use super::leaf_index_to_mt_index_and_peak_index::MmrLeafIndexToMtIndexAndPeakIndex;
4use crate::hashing::merkle_step_u64_index::MerkleStepU64Index;
5use crate::list::get::Get;
6use crate::prelude::*;
7
8/// Verify that a digest is a leaf in the MMR accumulator. Takes both authentication path and
9/// leaf index from secret-in. Crashes the VM if the authentication fails.
10#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
11pub struct MmrVerifyFromSecretInSecretLeafIndex;
12
13impl BasicSnippet for MmrVerifyFromSecretInSecretLeafIndex {
14    fn parameters(&self) -> Vec<(DataType, String)> {
15        vec![(
16            DataType::Tuple(vec![
17                DataType::List(Box::new(DataType::Digest)), // *peaks
18                DataType::U64,                              // leaf_count
19                DataType::Digest,                           // leaf
20            ]),
21            "*peaks_leaf_count_and_leaf".to_owned(),
22        )]
23    }
24
25    fn return_values(&self) -> Vec<(DataType, String)> {
26        vec![]
27    }
28
29    fn entrypoint(&self) -> String {
30        "tasmlib_mmr_verify_from_secret_in_secret_leaf_index".into()
31    }
32
33    // Already on stack (can be secret of public input): _ *peaks leaf_count_hi leaf_count_lo [digest (leaf)]
34    // Secret input: _ (authentication_path: Vec<Digest>), (leaf_digest: Digest), (leaf_index: u64)
35    fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
36        let entrypoint = self.entrypoint();
37        let while_loop_label = format!("{entrypoint}_while");
38
39        let leaf_index_to_mt_index = library.import(Box::new(MmrLeafIndexToMtIndexAndPeakIndex));
40        let merkle_step_u64_index = library.import(Box::new(MerkleStepU64Index));
41        let list_get = library.import(Box::new(Get::new(DataType::Digest)));
42
43        // BEFORE: _ *peaks leaf_count_hi leaf_count_lo [digest (leaf_digest)]
44        // AFTER:  _ leaf_index_hi leaf_index_lo validation_result
45        triton_asm!(
46            {entrypoint}:
47                // Read leaf index from secret in
48                divine 2
49                // _ *peaks leaf_count_hi leaf_count_lo [digest (leaf_digest)] leaf_index_hi leaf_index_lo
50                swap 8 swap 1 swap 9 swap 7
51                // _ leaf_index_hi leaf_index_lo *peaks [digest (leaf_digest)] leaf_count_hi leaf_count_lo
52
53                dup 9 dup 9
54                // _ leaf_index_hi leaf_index_lo *peaks [digest (leaf_digest)] leaf_count_hi leaf_count_lo leaf_index_hi leaf_index_lo
55
56                call {leaf_index_to_mt_index}
57                // _ leaf_index_hi leaf_index_lo *peaks [digest (leaf_digest)] mt_index_hi mt_index_lo peak_index
58
59                swap 7 swap 4 swap 1 swap 5 swap 2 swap 6 swap 3
60                // _ leaf_index_hi leaf_index_lo *peaks peak_index mt_index_hi mt_index_lo [digest (leaf_digest)]
61
62                // We're reading the authentication path from secret in, so we don't need a counter variable for that. We
63                // only need to stop the loop when `mt_index == 1`, since this indicates that we've hit a peak in the MMR.
64
65                // Rename: `leaf_digest` -> `acc_hash`
66                // _ leaf_index_hi leaf_index_lo *peaks peak_index mt_index_hi mt_index_lo [digest (acc_hash)]
67
68                call {while_loop_label}
69                // _ leaf_index_hi leaf_index_lo *peaks peak_index mt_index_hi mt_index_lo [digest (acc_hash)]
70
71                // Compare `acc_hash` with `peaks[peak_index]`
72                dup 8 dup 8 call {list_get}
73                // _ leaf_index_hi leaf_index_lo *peaks peak_index mt_index_hi mt_index_lo [digest (acc_hash)] [digest (peaks[peak_index])]
74
75                assert_vector error_id 20
76                // _ leaf_index_hi leaf_index_lo *peaks peak_index mt_index_hi mt_index_lo [digest (acc_hash)]
77
78                pop 5
79                pop 5
80                pop 1
81                // _
82
83                return
84
85            // start/end: _ mt_index_hi mt_index_lo [digest (acc_hash)]
86            {while_loop_label}:
87                dup 6 dup 6 push 0 push 1 {&DataType::U64.compare()}
88                // __ mt_index_hi mt_index_lo [digest (acc_hash)] (mt_index == 1)
89
90                skiz return
91                // __ mt_index_hi mt_index_lo [digest (acc_hash)]
92
93                // move up one layer in the Merkle tree
94                call {merkle_step_u64_index}
95
96                // _ mt_index_hi (mt_index_lo / 2) [digest (acc_hash)]
97
98                recurse
99        )
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use num::One;
106    use tasm_lib::test_helpers::test_assertion_failure;
107    use twenty_first::math::other::random_elements;
108    use twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator;
109    use twenty_first::util_types::mmr::mmr_accumulator::util::mmra_with_mps;
110    use twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof;
111    use twenty_first::util_types::mmr::mmr_trait::Mmr;
112    use twenty_first::util_types::mmr::shared_basic::leaf_index_to_mt_index_and_peak_index;
113
114    use super::*;
115    use crate::rust_shadowing_helper_functions;
116    use crate::test_helpers::test_rust_equivalence_given_complete_state;
117    use crate::test_prelude::*;
118
119    #[macro_rules_attr::apply(test)]
120    fn prop() {
121        for _ in 0..10 {
122            ShadowedProcedure::new(MmrVerifyFromSecretInSecretLeafIndex).test();
123        }
124    }
125
126    #[macro_rules_attr::apply(test)]
127    fn mmra_ap_verify_test_one() {
128        let digest0 = Tip5::hash(&BFieldElement::new(4545));
129        let (mmra, _mps) = mmra_with_mps(1u64, vec![(0, digest0)]);
130        MmrVerifyFromSecretInSecretLeafIndex.prop_verify_from_secret_in_positive_test(
131            &mmra,
132            digest0,
133            0u64,
134            vec![],
135        );
136    }
137
138    #[macro_rules_attr::apply(test)]
139    fn mmra_ap_verify_test_two() {
140        let digest0 = Tip5::hash(&BFieldElement::new(123));
141        let digest1 = Tip5::hash(&BFieldElement::new(456));
142
143        let leaf_count = 2u64;
144        let (mmr, _mps) = mmra_with_mps(leaf_count, vec![(0u64, digest0), (1u64, digest1)]);
145
146        let leaf_index_0 = 0;
147        MmrVerifyFromSecretInSecretLeafIndex.prop_verify_from_secret_in_positive_test(
148            &mmr,
149            digest0,
150            leaf_index_0,
151            vec![digest1],
152        );
153
154        let leaf_index_1 = 1;
155        MmrVerifyFromSecretInSecretLeafIndex.prop_verify_from_secret_in_positive_test(
156            &mmr,
157            digest1,
158            leaf_index_1,
159            vec![digest0],
160        );
161    }
162
163    #[macro_rules_attr::apply(test)]
164    fn mmra_ap_verify_test_pbt() {
165        let max_size = 19;
166        let snippet = MmrVerifyFromSecretInSecretLeafIndex;
167
168        for leaf_count in 0..max_size {
169            let digests: Vec<Digest> = random_elements(leaf_count);
170
171            let (mmr, mps) = mmra_with_mps(
172                leaf_count as u64,
173                digests
174                    .iter()
175                    .cloned()
176                    .enumerate()
177                    .map(|(i, d)| (i as u64, d))
178                    .collect_vec(),
179            );
180
181            let bad_leaf: Digest = rand::rng().random();
182            for (leaf_index, leaf_digest) in digests.into_iter().enumerate() {
183                let auth_path = mps[leaf_index].clone();
184
185                // Positive test
186                snippet.prop_verify_from_secret_in_positive_test(
187                    &mmr,
188                    leaf_digest,
189                    leaf_index as u64,
190                    auth_path.authentication_path.clone(),
191                );
192
193                // Negative test
194                snippet.prop_verify_from_secret_in_negative_test(
195                    &mmr,
196                    bad_leaf,
197                    leaf_index as u64,
198                    auth_path.authentication_path,
199                );
200            }
201        }
202    }
203
204    #[macro_rules_attr::apply(test)]
205    fn mmra_ap_verify_many_leafs() {
206        for init_leaf_count in [
207            (1u64 << 40) + (1 << 21) + 510,
208            (1 << 32) - 1,
209            (1 << 61) - 3,
210            (1 << 61) - 2,
211            (1 << 61) - 1,
212        ] {
213            let init_peak_count = init_leaf_count.count_ones();
214
215            // We can't construct this large archival MMRs, so we have to handle it with an MMRA
216            // and handle the membership proofs ourselves
217            let fake_peaks: Vec<Digest> = random_elements(init_peak_count as usize);
218            let mut mmr: MmrAccumulator = MmrAccumulator::init(fake_peaks, init_leaf_count);
219
220            // Insert the 1st leaf
221            let second_to_last_leaf: Digest = rand::rng().random();
222            let second_to_last_leaf_index = init_leaf_count;
223            let mut real_membership_proof_second_to_last = mmr.append(second_to_last_leaf);
224
225            // Insert one more leaf and update the existing membership proof
226            let last_leaf: Digest = rand::rng().random();
227            let last_leaf_index = second_to_last_leaf_index + 1;
228            MmrMembershipProof::update_from_append(
229                &mut real_membership_proof_second_to_last,
230                second_to_last_leaf_index,
231                mmr.num_leafs(),
232                last_leaf,
233                &mmr.peaks(),
234            );
235            let real_membership_proof_last = mmr.append(last_leaf);
236
237            // Positive tests
238            MmrVerifyFromSecretInSecretLeafIndex.prop_verify_from_secret_in_positive_test(
239                &mmr,
240                second_to_last_leaf,
241                second_to_last_leaf_index,
242                real_membership_proof_second_to_last
243                    .authentication_path
244                    .clone(),
245            );
246            MmrVerifyFromSecretInSecretLeafIndex.prop_verify_from_secret_in_positive_test(
247                &mmr,
248                last_leaf,
249                last_leaf_index,
250                real_membership_proof_last.authentication_path.clone(),
251            );
252
253            // Negative tests
254            let bad_leaf: Digest = rand::rng().random();
255            MmrVerifyFromSecretInSecretLeafIndex.prop_verify_from_secret_in_negative_test(
256                &mmr,
257                bad_leaf,
258                second_to_last_leaf_index,
259                real_membership_proof_second_to_last.authentication_path,
260            );
261            MmrVerifyFromSecretInSecretLeafIndex.prop_verify_from_secret_in_negative_test(
262                &mmr,
263                bad_leaf,
264                last_leaf_index,
265                real_membership_proof_last.authentication_path,
266            );
267        }
268    }
269
270    impl Procedure for MmrVerifyFromSecretInSecretLeafIndex {
271        fn rust_shadow(
272            &self,
273            stack: &mut Vec<BFieldElement>,
274            memory: &mut HashMap<BFieldElement, BFieldElement>,
275            nondeterminism: &NonDeterminism,
276            _public_input: &[BFieldElement],
277            _sponge: &mut Option<Tip5>,
278        ) -> Result<Vec<BFieldElement>, RustShadowError> {
279            let mut leaf_digest = [BFieldElement::new(0); Digest::LEN];
280            for elem in leaf_digest.iter_mut() {
281                *elem = stack.pop().ok_or(RustShadowError::StackUnderflow)?;
282            }
283
284            let leaf_digest = Digest::new(leaf_digest);
285            let leaf_count_lo: u32 = stack
286                .pop()
287                .ok_or(RustShadowError::StackUnderflow)?
288                .try_into()
289                .map_err(|_| RustShadowError::U64ToU32Error)?;
290            let leaf_count_hi: u32 = stack
291                .pop()
292                .ok_or(RustShadowError::StackUnderflow)?
293                .try_into()
294                .map_err(|_| RustShadowError::U64ToU32Error)?;
295            let leaf_count: u64 = ((leaf_count_hi as u64) << 32) + leaf_count_lo as u64;
296            let peaks_pointer = stack.pop().ok_or(RustShadowError::StackUnderflow)?;
297            let peaks_count: u64 = memory[&peaks_pointer].value();
298            let mut peaks: Vec<Digest> = vec![];
299            for i in 0..peaks_count {
300                let digest_innards = rust_shadowing_helper_functions::list::list_get(
301                    peaks_pointer,
302                    i as usize,
303                    memory,
304                    Digest::LEN,
305                )?;
306                peaks.push(Digest::new(digest_innards.try_into().unwrap()));
307            }
308            let leaf_index_hi: u32 = nondeterminism.individual_tokens[0]
309                .value()
310                .try_into()
311                .map_err(|_| RustShadowError::U64ToU32Error)?;
312            let leaf_index_lo: u32 = nondeterminism.individual_tokens[1]
313                .value()
314                .try_into()
315                .map_err(|_| RustShadowError::U64ToU32Error)?;
316            let leaf_index: u64 = ((leaf_index_hi as u64) << 32) + leaf_index_lo as u64;
317            let (mut mt_index, _peak_index) =
318                leaf_index_to_mt_index_and_peak_index(leaf_index, leaf_count);
319
320            let mut auth_path: Vec<Digest> = vec![];
321            let mut i = 0;
322            while mt_index != 1 {
323                auth_path.push(nondeterminism.digests[i]);
324                mt_index /= 2;
325                i += 1;
326            }
327
328            let valid_mp = MmrMembershipProof::new(auth_path).verify(
329                leaf_index,
330                leaf_digest,
331                &peaks,
332                leaf_count,
333            );
334
335            valid_mp.then(Vec::new).ok_or(RustShadowError::InvalidProof)
336        }
337
338        fn pseudorandom_initial_state(
339            &self,
340            seed: [u8; 32],
341            bench_case: Option<BenchmarkCase>,
342        ) -> ProcedureInitialState {
343            let mut rng = StdRng::from_seed(seed);
344            let leaf_count = rng.random_range(1..10000);
345            let leaf_index = rng.random_range(0..leaf_count);
346
347            match bench_case {
348                Some(BenchmarkCase::CommonCase) => {
349                    self.prepare_state_for_benchmark(32, (1 << 32) - 1)
350                }
351                Some(BenchmarkCase::WorstCase) => {
352                    self.prepare_state_for_benchmark(62, (1 << 62) - 1)
353                }
354                None => self.prepare_state_for_tests(leaf_count, leaf_index as u64, true),
355            }
356        }
357    }
358
359    impl MmrVerifyFromSecretInSecretLeafIndex {
360        fn prepare_state(
361            &self,
362            mmr: &MmrAccumulator,
363            claimed_leaf: Digest,
364            leaf_index: u64,
365            auth_path: Vec<Digest>,
366        ) -> ProcedureInitialState {
367            let ProcedureInitialState {
368                mut stack,
369                mut nondeterminism,
370                public_input: _,
371                sponge: _,
372            } = self.mmr_to_init_vm_state(mmr);
373
374            // push digests such that element 0 of digest is on top of stack
375            for value in claimed_leaf.values().iter().rev() {
376                stack.push(*value);
377            }
378
379            // Populate secret-in with leaf index and the provided authentication path
380            let leaf_index_hi = BFieldElement::new(leaf_index >> 32);
381            let leaf_index_lo = BFieldElement::new(leaf_index & u32::MAX as u64);
382            nondeterminism.individual_tokens = vec![leaf_index_hi, leaf_index_lo];
383            nondeterminism.digests = auth_path;
384
385            ProcedureInitialState {
386                stack,
387                nondeterminism,
388                ..Default::default()
389            }
390        }
391
392        fn prop_verify_from_secret_in_negative_test(
393            &self,
394            mmr: &MmrAccumulator,
395            claimed_leaf: Digest,
396            leaf_index: u64,
397            auth_path: Vec<Digest>,
398        ) {
399            let init_state = self.prepare_state(mmr, claimed_leaf, leaf_index, auth_path.clone());
400
401            test_assertion_failure(
402                &ShadowedProcedure::new(MmrVerifyFromSecretInSecretLeafIndex),
403                init_state.into(),
404                &[20],
405            );
406
407            // Sanity check
408            assert!(!MmrMembershipProof::new(auth_path).verify(
409                leaf_index,
410                claimed_leaf,
411                &mmr.peaks(),
412                mmr.num_leafs()
413            ));
414        }
415
416        // BEFORE: _ *peaks leaf_count_hi leaf_count_lo [digest (leaf_digest)]
417        // AFTER:  _
418        fn prop_verify_from_secret_in_positive_test(
419            &self,
420            mmr: &MmrAccumulator,
421            claimed_leaf: Digest,
422            leaf_index: u64,
423            auth_path: Vec<Digest>,
424        ) {
425            let init_state = self.prepare_state(mmr, claimed_leaf, leaf_index, auth_path.clone());
426            let expected_final_stack = self.init_stack_for_isolated_run();
427            test_rust_equivalence_given_complete_state(
428                &ShadowedProcedure::new(MmrVerifyFromSecretInSecretLeafIndex),
429                &init_state.stack,
430                &[],
431                &init_state.nondeterminism,
432                &None,
433                Some(&expected_final_stack),
434            );
435
436            // Sanity check
437            assert!(MmrMembershipProof::new(auth_path).verify(
438                leaf_index,
439                claimed_leaf,
440                &mmr.peaks(),
441                mmr.num_leafs()
442            ));
443        }
444
445        // BEFORE: _ *peaks [digest (leaf_digest)] leaf_count_hi leaf_count_lo
446        // AFTER:  _
447        fn prepare_state_for_tests(
448            &self,
449            size: usize,
450            leaf_index: u64,
451            generate_valid_proof: bool,
452        ) -> ProcedureInitialState {
453            let valid_leaf: Digest = rand::random();
454            let (mmr, mps) = mmra_with_mps(size as u64, vec![(leaf_index, valid_leaf)]);
455            let claimed_leaf = if generate_valid_proof {
456                valid_leaf
457            } else {
458                rand::random()
459            };
460
461            self.prepare_state(
462                &mmr,
463                claimed_leaf,
464                leaf_index,
465                mps[0].authentication_path.clone(),
466            )
467        }
468
469        fn prepare_state_for_benchmark(
470            &self,
471            log_2_leaf_count: u8,
472            leaf_index: u64,
473        ) -> ProcedureInitialState {
474            let leaf_count = 2u64.pow(log_2_leaf_count as u32);
475            let peaks: Vec<Digest> = random_elements(log_2_leaf_count as usize);
476            let mut mmra = MmrAccumulator::init(peaks, leaf_count - 1);
477            let new_leaf: Digest = rand::random();
478            let authentication_path = mmra.append(new_leaf).authentication_path;
479
480            let mut vm_init_state = self.mmr_to_init_vm_state(&mmra);
481
482            // Populate secret-in with the leaf index value, which is a u64
483            vm_init_state
484                .nondeterminism
485                .individual_tokens
486                .push(BFieldElement::new(leaf_index >> 32));
487            vm_init_state
488                .nondeterminism
489                .individual_tokens
490                .push(BFieldElement::new(leaf_index & u32::MAX as u64));
491
492            // Populate secret-in with the an authentication path
493            vm_init_state.nondeterminism.digests = authentication_path;
494
495            for value in new_leaf.values().iter().rev() {
496                vm_init_state.stack.push(*value);
497            }
498
499            vm_init_state
500        }
501
502        /// Prepare the part of the state that can be derived from the MMR without
503        /// knowing e.g. the leaf index of the leaf digest that you want to authenticate
504        /// so this function does not populate e.g. `secret_in`. The caller has to do that.
505        fn mmr_to_init_vm_state(&self, mmra: &MmrAccumulator) -> ProcedureInitialState {
506            let mut stack: Vec<BFieldElement> = self.init_stack_for_isolated_run();
507            let peaks_pointer = BFieldElement::one();
508            stack.push(peaks_pointer);
509
510            let leaf_count = mmra.num_leafs();
511            let leaf_count_hi = BFieldElement::new(leaf_count >> 32);
512            let leaf_count_lo = BFieldElement::new(leaf_count & u32::MAX as u64);
513            stack.push(leaf_count_hi);
514            stack.push(leaf_count_lo);
515
516            // Write peaks to memory
517            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::default();
518            rust_shadowing_helper_functions::list::list_new(peaks_pointer, &mut memory);
519
520            for peak in mmra.peaks() {
521                rust_shadowing_helper_functions::list::list_push(
522                    peaks_pointer,
523                    peak.values().to_vec(),
524                    &mut memory,
525                )
526                .unwrap();
527            }
528
529            let nondeterminism = NonDeterminism::default().with_ram(memory);
530            ProcedureInitialState {
531                stack,
532                nondeterminism,
533                ..Default::default()
534            }
535        }
536    }
537}
538
539#[cfg(test)]
540mod benches {
541    use super::*;
542    use crate::test_prelude::*;
543
544    #[macro_rules_attr::apply(test)]
545    fn benchmark() {
546        ShadowedProcedure::new(MmrVerifyFromSecretInSecretLeafIndex).bench();
547    }
548}