tasm_lib/structure/
verify_nd_si_integrity.rs

1use std::fmt::Debug;
2use std::marker::PhantomData;
3
4use triton_vm::prelude::*;
5
6use crate::prelude::*;
7
8/// Verify size-indicator integrity of preloaded data, return size.
9///
10/// Crashes the VM if the structure in question is not entirely contained within
11/// the non-deterministic section of memory as defined in the memory layout.
12#[derive(Clone, Debug)]
13pub struct VerifyNdSiIntegrity<PreloadedData: TasmObject + Clone + Debug> {
14    _phantom_data: PhantomData<PreloadedData>,
15}
16
17impl<T: TasmObject + Clone + Debug> Default for VerifyNdSiIntegrity<T> {
18    fn default() -> Self {
19        Self {
20            _phantom_data: PhantomData,
21        }
22    }
23}
24
25impl<T: TasmObject + Clone + Debug> BasicSnippet for VerifyNdSiIntegrity<T> {
26    fn inputs(&self) -> Vec<(DataType, String)> {
27        vec![(DataType::VoidPointer, "*struct".to_owned())]
28    }
29
30    fn outputs(&self) -> Vec<(DataType, String)> {
31        vec![(DataType::U32, "struct size".to_owned())]
32    }
33
34    fn entrypoint(&self) -> String {
35        let name = T::label_friendly_name();
36        format!("tasmlib_structure_verify_nd_si_integrity___{name}")
37    }
38
39    fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
40        let entrypoint = self.entrypoint();
41        let si_integrity_check_code = T::compute_size_and_assert_valid_size_indicator(library);
42
43        triton_asm!(
44            {entrypoint}:
45                // _ *struct
46
47                dup 0
48                {&si_integrity_check_code}
49                // _ *struct calculated_size
50
51                /* Verify that both pointer and end of struct is in ND-region */
52                dup 1
53                // _ *struct calculated_size *struct
54
55                pop_count // verifies that `*struct` is a valid u32
56                pop 1
57                // _ *struct calculated_size
58
59                swap 1
60                dup 1
61                add
62                // _ calculated_size *end_of_struct
63
64                pop_count // verifies that end of struct is located in ND-region
65                pop 1
66                // _ calculated_size
67
68                return
69        )
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use std::fmt::Debug;
76
77    use arbitrary::Arbitrary;
78    use arbitrary::Unstructured;
79    use num_traits::ConstZero;
80    use twenty_first::util_types::mmr::mmr_successor_proof::MmrSuccessorProof;
81
82    use super::*;
83    use crate::memory::encode_to_memory;
84    use crate::neptune::neptune_like_types_for_tests::*;
85    use crate::test_prelude::*;
86
87    impl<T> VerifyNdSiIntegrity<T>
88    where
89        T: TasmObject + BFieldCodec + for<'a> Arbitrary<'a> + Debug + Clone,
90    {
91        fn initial_state(&self, address: BFieldElement, t: T) -> AccessorInitialState {
92            let mut memory = HashMap::default();
93            encode_to_memory(&mut memory, address, &t);
94
95            AccessorInitialState {
96                stack: [self.init_stack_for_isolated_run(), vec![address]].concat(),
97                memory,
98            }
99        }
100
101        fn prepare_random_object(&self, randomness: &[u8]) -> T {
102            let unstructured = Unstructured::new(randomness);
103            T::arbitrary_take_rest(unstructured).unwrap()
104        }
105    }
106
107    impl<T> Accessor for VerifyNdSiIntegrity<T>
108    where
109        T: TasmObject + for<'a> Arbitrary<'a> + Debug + Clone,
110    {
111        fn rust_shadow(
112            &self,
113            stack: &mut Vec<BFieldElement>,
114            memory: &HashMap<BFieldElement, BFieldElement>,
115        ) {
116            // If the type can be decoded then it must have valid size indicators
117            let pointer = stack.pop().unwrap();
118            let obj = T::decode_from_memory(memory, pointer).unwrap();
119            let encoding_len = obj.encode().len();
120            let encoding_len: u32 = encoding_len.try_into().unwrap();
121
122            // Verify contained in ND-region
123            let start_address: u32 = pointer.value().try_into().unwrap();
124            let _end_address = start_address.checked_add(encoding_len).unwrap();
125
126            stack.push(bfe!(obj.encode().len() as u64));
127        }
128
129        fn pseudorandom_initial_state(
130            &self,
131            seed: [u8; 32],
132            _bench_case: Option<BenchmarkCase>,
133        ) -> AccessorInitialState {
134            let mut rng = StdRng::from_seed(seed);
135
136            let t: T = {
137                let mut randomness = [0u8; 100000];
138                rng.fill(&mut randomness);
139                self.prepare_random_object(&randomness)
140            };
141
142            let address: u32 = rng.random_range(0..(1 << 30));
143            let address = bfe!(address);
144            self.initial_state(address, t)
145        }
146
147        fn corner_case_initial_states(&self) -> Vec<AccessorInitialState> {
148            // This *should* always return `None` if `T: Option<S>`, and empty
149            // vec if type is Vec<T>. So some notion of "empty" or default.
150            let empty_struct: T = {
151                let unstructured = Unstructured::new(&[]);
152                T::arbitrary_take_rest(unstructured).unwrap()
153            };
154
155            println!("empty_struct:\n{empty_struct:?}");
156            let empty_struct_at_zero = self.initial_state(BFieldElement::ZERO, empty_struct);
157
158            vec![empty_struct_at_zero]
159        }
160    }
161
162    macro_rules! test_case {
163        (fn $test_name:ident for $t:ty) => {
164            #[test]
165            fn $test_name() {
166                ShadowedAccessor::new(VerifyNdSiIntegrity::<$t>::default()).test();
167            }
168        };
169        (fn $test_name:ident for new type $t:ty: $($type_declaration:tt)*) => {
170            #[test]
171            fn $test_name() {
172                #[derive(Debug, Clone, TasmObject, BFieldCodec, Arbitrary)]
173                $($type_declaration)*
174                ShadowedAccessor::new(VerifyNdSiIntegrity::<$t>::default()).test();
175            }
176        };
177    }
178
179    mod simple_struct {
180        use super::*;
181        use crate::test_helpers::negative_test;
182        use crate::test_helpers::test_assertion_failure;
183
184        #[derive(Debug, Clone, TasmObject, BFieldCodec, Arbitrary)]
185        struct TestStruct {
186            a: Vec<u128>,
187            b: Digest,
188            c: Vec<Digest>,
189        }
190
191        test_case! { fn test_pbt_simple_struct for TestStruct }
192
193        #[test]
194        fn struct_not_contained_in_nd_region() {
195            let snippet = VerifyNdSiIntegrity::<TestStruct>::default();
196
197            let t = snippet.prepare_random_object(&[]);
198            let begin_address = bfe!((1u64 << 32) - 4);
199            let init_state = snippet.initial_state(begin_address, t.clone());
200
201            let actual_size = t.encode().len();
202            let end_address = begin_address + bfe!(actual_size as u64);
203            let expected_err =
204                InstructionError::OpStackError(OpStackError::FailedU32Conversion(end_address));
205            negative_test(
206                &ShadowedAccessor::new(snippet),
207                init_state.into(),
208                &[expected_err],
209            )
210        }
211
212        #[test]
213        fn struct_does_not_start_in_nd_region() {
214            let snippet = VerifyNdSiIntegrity::<TestStruct>::default();
215
216            let begin_address = bfe!(-4);
217            let init_state =
218                snippet.initial_state(begin_address, snippet.prepare_random_object(&[]));
219            let expected_err =
220                InstructionError::OpStackError(OpStackError::FailedU32Conversion(begin_address));
221            negative_test(
222                &ShadowedAccessor::new(snippet),
223                init_state.into(),
224                &[expected_err],
225            )
226        }
227
228        #[test]
229        fn lie_about_digest_vec_size() {
230            let snippet = VerifyNdSiIntegrity::<TestStruct>::default();
231
232            let begin_address = bfe!(4);
233            let mut init_state =
234                snippet.initial_state(begin_address, snippet.prepare_random_object(&[]));
235            let true_value = init_state.memory[&begin_address];
236            init_state
237                .memory
238                .insert(begin_address, true_value + bfe!(1));
239
240            test_assertion_failure(&ShadowedAccessor::new(snippet), init_state.into(), &[181])
241        }
242
243        #[test]
244        fn lie_about_digest_vec_len() {
245            let snippet = VerifyNdSiIntegrity::<TestStruct>::default();
246
247            let begin_address = bfe!(4);
248            let mut init_state =
249                snippet.initial_state(begin_address, snippet.prepare_random_object(&[42u8; 20000]));
250            let vec_digest_len_indicator = begin_address + bfe!(1);
251            let true_value = init_state.memory[&vec_digest_len_indicator];
252            init_state
253                .memory
254                .insert(vec_digest_len_indicator, true_value + bfe!(1));
255
256            test_assertion_failure(&ShadowedAccessor::new(snippet), init_state.into(), &[181])
257        }
258    }
259
260    mod option_types {
261        use super::*;
262        use crate::test_helpers::test_assertion_failure;
263
264        #[derive(Debug, Clone, TasmObject, BFieldCodec, Arbitrary)]
265        struct StatSizedPayload {
266            a: Option<Digest>,
267        }
268
269        #[derive(Debug, Clone, TasmObject, BFieldCodec, Arbitrary, Default)]
270        struct DynSizedPayload {
271            a: Option<Vec<u128>>,
272            b: Digest,
273            c: Vec<Vec<BFieldElement>>,
274            d: Option<Vec<Option<BFieldElement>>>,
275        }
276
277        test_case! {fn test_option_stat_sized_elem for StatSizedPayload }
278        test_case! {fn test_option_dyn_sized_elem for DynSizedPayload }
279
280        #[test]
281        fn lie_about_option_payload_field_size() {
282            let snippet = VerifyNdSiIntegrity::<DynSizedPayload>::default();
283
284            let begin_address = bfe!(4);
285            let randomness = rand::random::<[u8; 100_000]>();
286            let obj = snippet.prepare_random_object(&randomness);
287            let true_init_state = snippet.initial_state(begin_address, obj.clone());
288
289            /*  Lie about size of field 'd'*/
290            let mut manipulated_si_outer = true_init_state.clone();
291            let outer_option_payload_si_ptr = begin_address; // field size-indicator of `d` field
292            let true_value = true_init_state.memory[&outer_option_payload_si_ptr];
293            manipulated_si_outer
294                .memory
295                .insert(outer_option_payload_si_ptr, true_value + bfe!(1));
296
297            test_assertion_failure(
298                &ShadowedAccessor::new(snippet),
299                manipulated_si_outer.into(),
300                &[181],
301            );
302        }
303
304        #[test]
305        fn illegal_discriminant_value_for_option() {
306            let snippet = VerifyNdSiIntegrity::<DynSizedPayload>::default();
307
308            let obj = DynSizedPayload::default();
309            let begin_address = bfe!(4);
310            let mut manipulated_init_state = snippet.initial_state(begin_address, obj.clone());
311            let option_discriminant_ptr = begin_address + bfe!(1);
312            manipulated_init_state
313                .memory
314                .insert(option_discriminant_ptr, bfe!(2));
315
316            test_assertion_failure(
317                &ShadowedAccessor::new(snippet.clone()),
318                manipulated_init_state.into(),
319                &[200],
320            );
321        }
322
323        #[test]
324        fn lie_about_option_payload_size() {
325            let snippet = VerifyNdSiIntegrity::<DynSizedPayload>::default();
326
327            let obj = DynSizedPayload {
328                d: Some(vec![Some(bfe!(14)), None, Some(bfe!(15))]),
329                ..Default::default()
330            };
331            let begin_address = bfe!(4);
332            let true_init_state = snippet.initial_state(begin_address, obj.clone());
333
334            /*  Lie about size of payload of outer Some(...)*/
335            let mut add_one = true_init_state.clone();
336            let len_of_dyn_sized_list_elem_0 = begin_address + bfe!(3);
337            let true_value = true_init_state.memory[&len_of_dyn_sized_list_elem_0];
338            add_one
339                .memory
340                .insert(len_of_dyn_sized_list_elem_0, true_value + bfe!(1));
341
342            test_assertion_failure(
343                &ShadowedAccessor::new(snippet.clone()),
344                add_one.into(),
345                &[211],
346            );
347
348            let mut sub_one = true_init_state.clone();
349            sub_one
350                .memory
351                .insert(len_of_dyn_sized_list_elem_0, true_value - bfe!(1));
352
353            test_assertion_failure(&ShadowedAccessor::new(snippet), sub_one.into(), &[211]);
354        }
355    }
356
357    test_case! { fn test_stat_sized_tuple for new type TestStruct:
358        struct TestStruct { field: (Digest, XFieldElement) }
359    }
360
361    test_case! { fn test_dyn_state_sized_tuple_right_dyn for new type RightIsDyn:
362        struct RightIsDyn { field: (Digest, Vec<XFieldElement>) }
363    }
364
365    test_case! { fn test_dyn_state_sized_tuple_left_dyn for new type LeftIsDyn:
366        struct LeftIsDyn { field: (Vec<XFieldElement>, Digest) }
367    }
368
369    test_case! { fn test_dyn_state_sized_tuple_both_dyn for new type BothDyn:
370        struct BothDyn { field: (Vec<XFieldElement>, Vec<XFieldElement>) }
371    }
372
373    test_case! { fn proof for Proof }
374    test_case! { fn coin for CoinLookalike }
375    test_case! { fn utxo for UtxoLookalike }
376    test_case! { fn salted_utxos for SaltedUtxosLookalike }
377    test_case! { fn collect_lock_scripts_witness for CollectLockScriptsWitnessLookalike }
378    test_case! { fn collect_type_scripts_witness for CollectTypeScriptsWitnessLookalike }
379    test_case! { fn claim for Claim }
380    test_case! { fn neptune_coins for NeptuneCoinsLookalike }
381    test_case! { fn option_neptune_coins for Option<NeptuneCoinsLookalike> }
382    test_case! { fn chunk for ChunkLookalike }
383    test_case! { fn chunk_dictionary for ChunkDictionaryLookalike }
384    test_case! { fn proof_collection for ProofCollectionLookalike }
385    test_case! { fn absolute_index_set for AbsoluteIndexSetLookalike }
386    test_case! { fn removal_record for RemovalRecordLookalike }
387    test_case! { fn addition_record for AdditionRecordLookalike }
388    test_case! { fn public_announcement for PublicAnnouncementLookalike }
389    test_case! { fn timestamp for TimestampLookalike }
390    test_case! { fn transaction_kernel for TransactionKernelLookalike }
391    test_case! { fn kernel_to_outputs_witness for KernelToOutputsWitnessLookalike }
392    test_case! { fn merge_witness for MergeWitnessLookalike }
393    test_case! { fn ms_membership_proof for MsMembershipProofLookalike }
394    test_case! { fn removal_records_witness for RemovalRecordsIntegrityWitnessLookalike }
395    test_case! { fn update_witness for UpdateWitnessLookalike }
396    test_case! { fn lock_script_and_witness for LockScriptAndWitnessLookalike }
397    test_case! { fn mutator_set_accumulator for MutatorSetAccumulatorLookalike }
398    test_case! { fn primitive_witness for PrimitiveWitnessLookalike }
399    test_case! { fn mmr_successor_proof for MmrSuccessorProof }
400}
401
402#[cfg(test)]
403mod benches {
404    use super::*;
405    use crate::neptune::neptune_like_types_for_tests::ProofCollectionLookalike;
406    use crate::neptune::neptune_like_types_for_tests::TransactionKernelLookalike;
407    use crate::test_prelude::*;
408
409    #[test]
410    fn bench_proof_collection_lookalike() {
411        ShadowedAccessor::new(VerifyNdSiIntegrity::<ProofCollectionLookalike>::default()).bench();
412    }
413
414    #[test]
415    fn bench_transaction_kernel_lookalike() {
416        ShadowedAccessor::new(VerifyNdSiIntegrity::<TransactionKernelLookalike>::default()).bench();
417    }
418}