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 inputs(&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 outputs(&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    #[test]
120    fn prop() {
121        for _ in 0..10 {
122            ShadowedProcedure::new(MmrVerifyFromSecretInSecretLeafIndex).test();
123        }
124    }
125
126    #[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    #[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    #[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    #[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        ) -> Vec<BFieldElement> {
279            let mut leaf_digest = [BFieldElement::new(0); Digest::LEN];
280            for elem in leaf_digest.iter_mut() {
281                *elem = stack.pop().unwrap();
282            }
283
284            let leaf_digest = Digest::new(leaf_digest);
285            let leaf_count_lo: u32 = stack.pop().unwrap().try_into().unwrap();
286            let leaf_count_hi: u32 = stack.pop().unwrap().try_into().unwrap();
287            let leaf_count: u64 = ((leaf_count_hi as u64) << 32) + leaf_count_lo as u64;
288            let peaks_pointer = stack.pop().unwrap();
289            let peaks_count: u64 = memory[&peaks_pointer].value();
290            let mut peaks: Vec<Digest> = vec![];
291            for i in 0..peaks_count {
292                let digest_innards = rust_shadowing_helper_functions::list::list_get(
293                    peaks_pointer,
294                    i as usize,
295                    memory,
296                    Digest::LEN,
297                );
298                peaks.push(Digest::new(digest_innards.try_into().unwrap()));
299            }
300            let leaf_index_hi: u32 = nondeterminism.individual_tokens[0]
301                .value()
302                .try_into()
303                .unwrap();
304            let leaf_index_lo: u32 = nondeterminism.individual_tokens[1]
305                .value()
306                .try_into()
307                .unwrap();
308            let leaf_index: u64 = ((leaf_index_hi as u64) << 32) + leaf_index_lo as u64;
309            let (mut mt_index, _peak_index) =
310                leaf_index_to_mt_index_and_peak_index(leaf_index, leaf_count);
311
312            let mut auth_path: Vec<Digest> = vec![];
313            let mut i = 0;
314            while mt_index != 1 {
315                auth_path.push(nondeterminism.digests[i]);
316                mt_index /= 2;
317                i += 1;
318            }
319
320            let valid_mp = MmrMembershipProof::new(auth_path).verify(
321                leaf_index,
322                leaf_digest,
323                &peaks,
324                leaf_count,
325            );
326
327            assert!(valid_mp, "MMR leaf not authenticated");
328
329            vec![]
330        }
331
332        fn pseudorandom_initial_state(
333            &self,
334            seed: [u8; 32],
335            bench_case: Option<BenchmarkCase>,
336        ) -> ProcedureInitialState {
337            let mut rng = StdRng::from_seed(seed);
338            let leaf_count = rng.random_range(1..10000);
339            let leaf_index = rng.random_range(0..leaf_count);
340
341            match bench_case {
342                Some(BenchmarkCase::CommonCase) => {
343                    self.prepare_state_for_benchmark(32, (1 << 32) - 1)
344                }
345                Some(BenchmarkCase::WorstCase) => {
346                    self.prepare_state_for_benchmark(62, (1 << 62) - 1)
347                }
348                None => self.prepare_state_for_tests(leaf_count, leaf_index as u64, true),
349            }
350        }
351    }
352
353    impl MmrVerifyFromSecretInSecretLeafIndex {
354        fn prepare_state(
355            &self,
356            mmr: &MmrAccumulator,
357            claimed_leaf: Digest,
358            leaf_index: u64,
359            auth_path: Vec<Digest>,
360        ) -> ProcedureInitialState {
361            let ProcedureInitialState {
362                mut stack,
363                mut nondeterminism,
364                public_input: _,
365                sponge: _,
366            } = self.mmr_to_init_vm_state(mmr);
367
368            // push digests such that element 0 of digest is on top of stack
369            for value in claimed_leaf.values().iter().rev() {
370                stack.push(*value);
371            }
372
373            // Populate secret-in with leaf index and the provided authentication path
374            let leaf_index_hi = BFieldElement::new(leaf_index >> 32);
375            let leaf_index_lo = BFieldElement::new(leaf_index & u32::MAX as u64);
376            nondeterminism.individual_tokens = vec![leaf_index_hi, leaf_index_lo];
377            nondeterminism.digests = auth_path;
378
379            ProcedureInitialState {
380                stack,
381                nondeterminism,
382                ..Default::default()
383            }
384        }
385
386        fn prop_verify_from_secret_in_negative_test(
387            &self,
388            mmr: &MmrAccumulator,
389            claimed_leaf: Digest,
390            leaf_index: u64,
391            auth_path: Vec<Digest>,
392        ) {
393            let init_state = self.prepare_state(mmr, claimed_leaf, leaf_index, auth_path.clone());
394
395            test_assertion_failure(
396                &ShadowedProcedure::new(MmrVerifyFromSecretInSecretLeafIndex),
397                init_state.into(),
398                &[20],
399            );
400
401            // Sanity check
402            assert!(!MmrMembershipProof::new(auth_path).verify(
403                leaf_index,
404                claimed_leaf,
405                &mmr.peaks(),
406                mmr.num_leafs()
407            ));
408        }
409
410        // BEFORE: _ *peaks leaf_count_hi leaf_count_lo [digest (leaf_digest)]
411        // AFTER:  _
412        fn prop_verify_from_secret_in_positive_test(
413            &self,
414            mmr: &MmrAccumulator,
415            claimed_leaf: Digest,
416            leaf_index: u64,
417            auth_path: Vec<Digest>,
418        ) {
419            let init_state = self.prepare_state(mmr, claimed_leaf, leaf_index, auth_path.clone());
420            let expected_final_stack = self.init_stack_for_isolated_run();
421            test_rust_equivalence_given_complete_state(
422                &ShadowedProcedure::new(MmrVerifyFromSecretInSecretLeafIndex),
423                &init_state.stack,
424                &[],
425                &init_state.nondeterminism,
426                &None,
427                Some(&expected_final_stack),
428            );
429
430            // Sanity check
431            assert!(MmrMembershipProof::new(auth_path).verify(
432                leaf_index,
433                claimed_leaf,
434                &mmr.peaks(),
435                mmr.num_leafs()
436            ));
437        }
438
439        // BEFORE: _ *peaks [digest (leaf_digest)] leaf_count_hi leaf_count_lo
440        // AFTER:  _
441        fn prepare_state_for_tests(
442            &self,
443            size: usize,
444            leaf_index: u64,
445            generate_valid_proof: bool,
446        ) -> ProcedureInitialState {
447            let valid_leaf: Digest = rand::random();
448            let (mmr, mps) = mmra_with_mps(size as u64, vec![(leaf_index, valid_leaf)]);
449            let claimed_leaf = if generate_valid_proof {
450                valid_leaf
451            } else {
452                rand::random()
453            };
454
455            self.prepare_state(
456                &mmr,
457                claimed_leaf,
458                leaf_index,
459                mps[0].authentication_path.clone(),
460            )
461        }
462
463        fn prepare_state_for_benchmark(
464            &self,
465            log_2_leaf_count: u8,
466            leaf_index: u64,
467        ) -> ProcedureInitialState {
468            let leaf_count = 2u64.pow(log_2_leaf_count as u32);
469            let peaks: Vec<Digest> = random_elements(log_2_leaf_count as usize);
470            let mut mmra = MmrAccumulator::init(peaks, leaf_count - 1);
471            let new_leaf: Digest = rand::random();
472            let authentication_path = mmra.append(new_leaf).authentication_path;
473
474            let mut vm_init_state = self.mmr_to_init_vm_state(&mmra);
475
476            // Populate secret-in with the leaf index value, which is a u64
477            vm_init_state
478                .nondeterminism
479                .individual_tokens
480                .push(BFieldElement::new(leaf_index >> 32));
481            vm_init_state
482                .nondeterminism
483                .individual_tokens
484                .push(BFieldElement::new(leaf_index & u32::MAX as u64));
485
486            // Populate secret-in with the an authentication path
487            vm_init_state.nondeterminism.digests = authentication_path;
488
489            for value in new_leaf.values().iter().rev() {
490                vm_init_state.stack.push(*value);
491            }
492
493            vm_init_state
494        }
495
496        /// Prepare the part of the state that can be derived from the MMR without
497        /// knowing e.g. the leaf index of the leaf digest that you want to authenticate
498        /// so this function does not populate e.g. `secret_in`. The caller has to do that.
499        fn mmr_to_init_vm_state(&self, mmra: &MmrAccumulator) -> ProcedureInitialState {
500            let mut stack: Vec<BFieldElement> = self.init_stack_for_isolated_run();
501            let peaks_pointer = BFieldElement::one();
502            stack.push(peaks_pointer);
503
504            let leaf_count = mmra.num_leafs();
505            let leaf_count_hi = BFieldElement::new(leaf_count >> 32);
506            let leaf_count_lo = BFieldElement::new(leaf_count & u32::MAX as u64);
507            stack.push(leaf_count_hi);
508            stack.push(leaf_count_lo);
509
510            // Write peaks to memory
511            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::default();
512            rust_shadowing_helper_functions::list::list_new(peaks_pointer, &mut memory);
513
514            for peak in mmra.peaks() {
515                rust_shadowing_helper_functions::list::list_push(
516                    peaks_pointer,
517                    peak.values().to_vec(),
518                    &mut memory,
519                );
520            }
521
522            let nondeterminism = NonDeterminism::default().with_ram(memory);
523            ProcedureInitialState {
524                stack,
525                nondeterminism,
526                ..Default::default()
527            }
528        }
529    }
530}
531
532#[cfg(test)]
533mod benches {
534    use super::*;
535    use crate::test_prelude::*;
536
537    #[test]
538    fn benchmark() {
539        ShadowedProcedure::new(MmrVerifyFromSecretInSecretLeafIndex).bench();
540    }
541}