tasm_lib/mmr/
verify_from_secret_in_leaf_index_on_stack.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 from
9/// secret-in. Crashes the VM if the authentication fails.
10#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
11pub struct MmrVerifyFromSecretInLeafIndexOnStack;
12
13impl BasicSnippet for MmrVerifyFromSecretInLeafIndexOnStack {
14    fn inputs(&self) -> Vec<(DataType, String)> {
15        vec![(
16            DataType::Tuple(vec![
17                DataType::List(Box::new(DataType::Digest)), // *peaks
18                DataType::Digest,                           // leaf
19                DataType::U64,                              // leaf_count
20                DataType::U64,                              // leaf_index
21            ]),
22            "peaks_leaf_count_leaf_index_and_leaf".to_owned(),
23        )]
24    }
25
26    fn outputs(&self) -> Vec<(DataType, String)> {
27        vec![]
28    }
29
30    fn entrypoint(&self) -> String {
31        "tasmlib_mmr_verify_from_secret_in_leaf_index_on_stack".into()
32    }
33
34    fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
35        let entrypoint = self.entrypoint();
36        let auth_path_loop_label = format!("{entrypoint}_auth_path_loop");
37
38        let leaf_index_to_mt_index = library.import(Box::new(MmrLeafIndexToMtIndexAndPeakIndex));
39        let merkle_step_u64_index = library.import(Box::new(MerkleStepU64Index));
40        let list_get = library.import(Box::new(Get::new(DataType::Digest)));
41
42        let auth_path_loop_code = triton_asm!(
43            {auth_path_loop_label}:
44                dup 6 dup 6 push 0 push 1 {&DataType::U64.compare()}
45                // __ mt_index_hi mt_index_lo [acc_hash] (mt_index == 1)
46
47                skiz return
48                // __ mt_index_hi mt_index_lo [acc_hash]
49
50                // move up one layer in the Merkle tree
51                call {merkle_step_u64_index}
52
53                // _ (mt_index / 2)_hi (mt_index / 2)_lo [digest (acc_hash)]
54
55                recurse
56        );
57
58        triton_asm!(
59            {entrypoint}:
60                // _ *peaks [leaf] [leaf_count] [leaf_index]
61
62                call {leaf_index_to_mt_index}
63                // _ *peaks [leaf] mt_index_hi mt_index_lo peak_index
64
65                place 7 place 6 place 6
66                // _ *peaks peak_index mt_index_hi mt_index_lo [digest (leaf_digest)]
67
68                call {auth_path_loop_label}
69                // _ *peaks peak_index 0 1 [acc_hash_result]
70
71                dup 8 dup 8 call {list_get}
72                // _ *peaks peak_index 0 1 [acc_hash_result] [expected_root]
73
74                assert_vector error_id 10
75                // _ *peaks peak_index 0 1 [acc_hash_result]
76
77                pop 5
78                pop 4
79                // _
80
81
82                return
83
84            {&auth_path_loop_code}
85        )
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use twenty_first::math::other::random_elements;
92    use twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator;
93    use twenty_first::util_types::mmr::mmr_accumulator::util::mmra_with_mps;
94    use twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof;
95    use twenty_first::util_types::mmr::mmr_trait::Mmr;
96    use twenty_first::util_types::mmr::shared_basic::leaf_index_to_mt_index_and_peak_index;
97
98    use super::*;
99    use crate::rust_shadowing_helper_functions;
100    use crate::test_prelude::*;
101
102    impl MmrVerifyFromSecretInLeafIndexOnStack {
103        fn prepare_state(
104            &self,
105            mmr: &MmrAccumulator,
106            peaks_pointer: BFieldElement,
107            claimed_leaf_index: u64,
108            claimed_leaf: Digest,
109            auth_path: Vec<Digest>,
110        ) -> ProcedureInitialState {
111            let mut init_state = self.mmr_to_init_vm_state(mmr, peaks_pointer, claimed_leaf);
112            init_state.nondeterminism.digests = auth_path;
113            let leaf_index_encoded = [
114                bfe!(claimed_leaf_index >> 32),
115                bfe!(claimed_leaf_index & u32::MAX as u64),
116            ];
117            init_state.stack.extend(leaf_index_encoded);
118
119            init_state
120        }
121
122        /// Prepare the state with the known MMR and the known `claimed_leaf`, caller needs to set
123        /// leaf index and auth path.
124        fn mmr_to_init_vm_state(
125            &self,
126            mmra: &MmrAccumulator,
127            peaks_pointer: BFieldElement,
128            claimed_leaf: Digest,
129        ) -> ProcedureInitialState {
130            let mut stack: Vec<BFieldElement> = self.init_stack_for_isolated_run();
131            stack.push(peaks_pointer);
132
133            for word in claimed_leaf.0.into_iter().rev() {
134                stack.push(word);
135            }
136
137            let leaf_count = mmra.num_leafs();
138            let leaf_count_hi = BFieldElement::new(leaf_count >> 32);
139            let leaf_count_lo = BFieldElement::new(leaf_count & u32::MAX as u64);
140            stack.push(leaf_count_hi);
141            stack.push(leaf_count_lo);
142
143            // Write peaks to memory
144            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::default();
145            rust_shadowing_helper_functions::list::list_insert(
146                peaks_pointer,
147                mmra.peaks(),
148                &mut memory,
149            );
150            let nondeterminism = NonDeterminism::default().with_ram(memory);
151
152            ProcedureInitialState {
153                stack,
154                nondeterminism,
155                ..Default::default()
156            }
157        }
158    }
159
160    impl Procedure for MmrVerifyFromSecretInLeafIndexOnStack {
161        fn rust_shadow(
162            &self,
163            stack: &mut Vec<BFieldElement>,
164            memory: &mut HashMap<BFieldElement, BFieldElement>,
165            nondeterminism: &NonDeterminism,
166            _: &[BFieldElement],
167            _: &mut Option<Tip5>,
168        ) -> Vec<BFieldElement> {
169            let leaf_index = pop_encodable(stack);
170            let leaf_count = pop_encodable(stack);
171            let leaf_digest = pop_encodable(stack);
172            let peaks_pointer = pop_encodable(stack);
173
174            let peaks = Vec::<Digest>::decode_from_memory(memory, peaks_pointer).unwrap();
175
176            let (mut mt_index, _peak_index) =
177                leaf_index_to_mt_index_and_peak_index(leaf_index, leaf_count);
178
179            let mut auth_path: Vec<Digest> = vec![];
180            let mut i = 0;
181            while mt_index != 1 {
182                auth_path.push(nondeterminism.digests[i]);
183                mt_index /= 2;
184                i += 1;
185            }
186
187            let valid_mp = MmrMembershipProof::new(auth_path).verify(
188                leaf_index,
189                leaf_digest,
190                &peaks,
191                leaf_count,
192            );
193
194            assert!(valid_mp, "MMR leaf must authenticate against peak");
195
196            vec![]
197        }
198
199        fn pseudorandom_initial_state(
200            &self,
201            seed: [u8; 32],
202            bench_case: Option<BenchmarkCase>,
203        ) -> ProcedureInitialState {
204            let mut rng = StdRng::from_seed(seed);
205
206            let (leaf_count, leaf_index) = match bench_case {
207                Some(BenchmarkCase::CommonCase) => (1u64 << 32, 1 << 31),
208                Some(BenchmarkCase::WorstCase) => (1u64 << 62, 1 << 61),
209                None => {
210                    let leaf_count = rng.random_range(0..(1 << 62));
211                    let leaf_index = rng.random_range(0..leaf_count);
212
213                    (leaf_count, leaf_index)
214                }
215            };
216
217            let peaks_pointer: BFieldElement = rng.random();
218            let valid_leaf: Digest = rand::random();
219            let (mmr, mps) = mmra_with_mps(leaf_count, vec![(leaf_index, valid_leaf)]);
220            self.prepare_state(
221                &mmr,
222                peaks_pointer,
223                leaf_index,
224                valid_leaf,
225                mps[0].authentication_path.clone(),
226            )
227        }
228    }
229
230    #[test]
231    fn rust_shadow() {
232        ShadowedProcedure::new(MmrVerifyFromSecretInLeafIndexOnStack).test();
233    }
234
235    #[proptest(cases = 32)]
236    fn negative_test_bad_leaf_index(
237        #[strategy(0_u64..1 << 62)] leaf_count: u64,
238        #[strategy(0_u64..#leaf_count)] real_leaf_index: u64,
239        #[strategy(0..#leaf_count)]
240        #[filter(#real_leaf_index != #bad_leaf_index)]
241        bad_leaf_index: u64,
242        #[strategy(arb())] leaf: Digest,
243        #[strategy(arb())] peaks_pointer: BFieldElement,
244    ) {
245        let (mmr, mps) = mmra_with_mps(leaf_count, vec![(real_leaf_index, leaf)]);
246        let auth_path = mps[0].authentication_path.clone();
247
248        // Extend the auth path to ensure that execution does not run out of digests in non-
249        // determinism since this would result in another error code in TVM than the one we intend
250        // to get: vector assertion error.
251        let padded_auth_path = [auth_path.clone(), random_elements(64)].concat();
252        let init_state = MmrVerifyFromSecretInLeafIndexOnStack.prepare_state(
253            &mmr,
254            peaks_pointer,
255            bad_leaf_index,
256            leaf,
257            padded_auth_path,
258        );
259
260        test_assertion_failure(
261            &ShadowedProcedure::new(MmrVerifyFromSecretInLeafIndexOnStack),
262            init_state.into(),
263            &[10],
264        );
265
266        // Sanity check
267        assert!(!MmrMembershipProof::new(auth_path).verify(
268            bad_leaf_index,
269            leaf,
270            &mmr.peaks(),
271            mmr.num_leafs()
272        ));
273    }
274}
275
276#[cfg(test)]
277mod benches {
278    use super::*;
279    use crate::test_prelude::*;
280
281    #[test]
282    fn benchmark() {
283        ShadowedProcedure::new(MmrVerifyFromSecretInLeafIndexOnStack).bench();
284    }
285}