Skip to main content

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 parameters(&self) -> Vec<(DataType, String)> {
27        vec![(DataType::VoidPointer, "*struct".to_owned())]
28    }
29
30    fn return_values(&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        ) -> Result<(), RustShadowError> {
116            // If the type can be decoded then it must have valid size indicators
117            let pointer = stack.pop().ok_or(RustShadowError::StackUnderflow)?;
118            let obj = T::decode_from_memory(memory, pointer)
119                .map_err(|_| RustShadowError::DecodingError)?;
120            let encoding_len = obj.encode().len();
121            let encoding_len: u32 = encoding_len
122                .try_into()
123                .map_err(|_| RustShadowError::UsizeToU32Error)?;
124
125            // Verify contained in ND-region
126            let start_address: u32 = pointer
127                .value()
128                .try_into()
129                .map_err(|_| RustShadowError::U64ToU32Error)?;
130            let _end_address = start_address
131                .checked_add(encoding_len)
132                .ok_or(RustShadowError::ArithmeticOverflow)?;
133
134            stack.push(bfe!(u64::from(encoding_len)));
135
136            Ok(())
137        }
138
139        fn pseudorandom_initial_state(
140            &self,
141            seed: [u8; 32],
142            _bench_case: Option<BenchmarkCase>,
143        ) -> AccessorInitialState {
144            let mut rng = StdRng::from_seed(seed);
145
146            let t: T = {
147                let mut randomness = [0u8; 100000];
148                rng.fill(&mut randomness);
149                self.prepare_random_object(&randomness)
150            };
151
152            let address: u32 = rng.random_range(0..(1 << 30));
153            let address = bfe!(address);
154            self.initial_state(address, t)
155        }
156
157        fn corner_case_initial_states(&self) -> Vec<AccessorInitialState> {
158            // This *should* always return `None` if `T: Option<S>`, and empty
159            // vec if type is Vec<T>. So some notion of "empty" or default.
160            let empty_struct: T = {
161                let unstructured = Unstructured::new(&[]);
162                T::arbitrary_take_rest(unstructured).unwrap()
163            };
164
165            println!("empty_struct:\n{empty_struct:?}");
166            let empty_struct_at_zero = self.initial_state(BFieldElement::ZERO, empty_struct);
167
168            vec![empty_struct_at_zero]
169        }
170    }
171
172    macro_rules! test_case {
173        (fn $test_name:ident for $t:ty) => {
174            #[macro_rules_attr::apply(test)]
175            fn $test_name() {
176                ShadowedAccessor::new(VerifyNdSiIntegrity::<$t>::default()).test();
177            }
178        };
179        (fn $test_name:ident for new type $t:ty: $($type_declaration:tt)*) => {
180            #[macro_rules_attr::apply(test)]
181            fn $test_name() {
182                #[derive(Debug, Clone, TasmObject, BFieldCodec, Arbitrary)]
183                $($type_declaration)*
184                ShadowedAccessor::new(VerifyNdSiIntegrity::<$t>::default()).test();
185            }
186        };
187    }
188
189    mod simple_struct {
190        use super::*;
191        use crate::test_helpers::negative_test;
192        use crate::test_helpers::test_assertion_failure;
193
194        #[derive(Debug, Clone, TasmObject, BFieldCodec, Arbitrary)]
195        struct TestStruct {
196            a: Vec<u128>,
197            b: Digest,
198            c: Vec<Digest>,
199        }
200
201        test_case! { fn test_pbt_simple_struct for TestStruct }
202
203        #[macro_rules_attr::apply(test)]
204        fn struct_not_contained_in_nd_region() {
205            let snippet = VerifyNdSiIntegrity::<TestStruct>::default();
206
207            let t = snippet.prepare_random_object(&[]);
208            let begin_address = bfe!((1u64 << 32) - 4);
209            let init_state = snippet.initial_state(begin_address, t.clone());
210
211            let actual_size = t.encode().len();
212            let end_address = begin_address + bfe!(actual_size as u64);
213            let expected_err =
214                InstructionError::OpStackError(OpStackError::FailedU32Conversion(end_address));
215            negative_test(
216                &ShadowedAccessor::new(snippet),
217                init_state.into(),
218                &[expected_err],
219            )
220        }
221
222        #[macro_rules_attr::apply(test)]
223        fn struct_does_not_start_in_nd_region() {
224            let snippet = VerifyNdSiIntegrity::<TestStruct>::default();
225
226            let begin_address = bfe!(-4);
227            let init_state =
228                snippet.initial_state(begin_address, snippet.prepare_random_object(&[]));
229            let expected_err =
230                InstructionError::OpStackError(OpStackError::FailedU32Conversion(begin_address));
231            negative_test(
232                &ShadowedAccessor::new(snippet),
233                init_state.into(),
234                &[expected_err],
235            )
236        }
237
238        #[macro_rules_attr::apply(test)]
239        fn lie_about_digest_vec_size() {
240            let snippet = VerifyNdSiIntegrity::<TestStruct>::default();
241
242            let begin_address = bfe!(4);
243            let mut init_state =
244                snippet.initial_state(begin_address, snippet.prepare_random_object(&[]));
245            let true_value = init_state.memory[&begin_address];
246            init_state
247                .memory
248                .insert(begin_address, true_value + bfe!(1));
249
250            test_assertion_failure(&ShadowedAccessor::new(snippet), init_state.into(), &[181])
251        }
252
253        #[macro_rules_attr::apply(test)]
254        fn lie_about_digest_vec_len() {
255            let snippet = VerifyNdSiIntegrity::<TestStruct>::default();
256
257            let begin_address = bfe!(4);
258            let mut init_state =
259                snippet.initial_state(begin_address, snippet.prepare_random_object(&[42u8; 20000]));
260            let vec_digest_len_indicator = begin_address + bfe!(1);
261            let true_value = init_state.memory[&vec_digest_len_indicator];
262            init_state
263                .memory
264                .insert(vec_digest_len_indicator, true_value + bfe!(1));
265
266            test_assertion_failure(&ShadowedAccessor::new(snippet), init_state.into(), &[181])
267        }
268    }
269
270    mod option_types {
271        use super::*;
272        use crate::test_helpers::test_assertion_failure;
273
274        #[derive(Debug, Clone, TasmObject, BFieldCodec, Arbitrary)]
275        struct StatSizedPayload {
276            a: Option<Digest>,
277        }
278
279        #[derive(Debug, Clone, TasmObject, BFieldCodec, Arbitrary, Default)]
280        struct DynSizedPayload {
281            a: Option<Vec<u128>>,
282            b: Digest,
283            c: Vec<Vec<BFieldElement>>,
284            d: Option<Vec<Option<BFieldElement>>>,
285        }
286
287        test_case! {fn test_option_stat_sized_elem for StatSizedPayload }
288        test_case! {fn test_option_dyn_sized_elem for DynSizedPayload }
289
290        #[macro_rules_attr::apply(test)]
291        fn lie_about_option_payload_field_size() {
292            let snippet = VerifyNdSiIntegrity::<DynSizedPayload>::default();
293
294            let begin_address = bfe!(4);
295            let randomness = rand::random::<[u8; 100_000]>();
296            let obj = snippet.prepare_random_object(&randomness);
297            let true_init_state = snippet.initial_state(begin_address, obj.clone());
298
299            /*  Lie about size of field 'd'*/
300            let mut manipulated_si_outer = true_init_state.clone();
301            let outer_option_payload_si_ptr = begin_address; // field size-indicator of `d` field
302            let true_value = true_init_state.memory[&outer_option_payload_si_ptr];
303            manipulated_si_outer
304                .memory
305                .insert(outer_option_payload_si_ptr, true_value + bfe!(1));
306
307            test_assertion_failure(
308                &ShadowedAccessor::new(snippet),
309                manipulated_si_outer.into(),
310                &[181],
311            );
312        }
313
314        #[macro_rules_attr::apply(test)]
315        fn illegal_discriminant_value_for_option() {
316            let snippet = VerifyNdSiIntegrity::<DynSizedPayload>::default();
317
318            let obj = DynSizedPayload::default();
319            let begin_address = bfe!(4);
320            let mut manipulated_init_state = snippet.initial_state(begin_address, obj.clone());
321            let option_discriminant_ptr = begin_address + bfe!(1);
322            manipulated_init_state
323                .memory
324                .insert(option_discriminant_ptr, bfe!(2));
325
326            test_assertion_failure(
327                &ShadowedAccessor::new(snippet.clone()),
328                manipulated_init_state.into(),
329                &[200],
330            );
331        }
332
333        #[macro_rules_attr::apply(test)]
334        fn lie_about_option_payload_size() {
335            let snippet = VerifyNdSiIntegrity::<DynSizedPayload>::default();
336
337            let obj = DynSizedPayload {
338                d: Some(vec![Some(bfe!(14)), None, Some(bfe!(15))]),
339                ..Default::default()
340            };
341            let begin_address = bfe!(4);
342            let true_init_state = snippet.initial_state(begin_address, obj.clone());
343
344            /*  Lie about size of payload of outer Some(...)*/
345            let mut add_one = true_init_state.clone();
346            let len_of_dyn_sized_list_elem_0 = begin_address + bfe!(3);
347            let true_value = true_init_state.memory[&len_of_dyn_sized_list_elem_0];
348            add_one
349                .memory
350                .insert(len_of_dyn_sized_list_elem_0, true_value + bfe!(1));
351
352            test_assertion_failure(
353                &ShadowedAccessor::new(snippet.clone()),
354                add_one.into(),
355                &[211],
356            );
357
358            let mut sub_one = true_init_state.clone();
359            sub_one
360                .memory
361                .insert(len_of_dyn_sized_list_elem_0, true_value - bfe!(1));
362
363            test_assertion_failure(&ShadowedAccessor::new(snippet), sub_one.into(), &[211]);
364        }
365    }
366
367    test_case! { fn test_stat_sized_tuple for new type TestStruct:
368        struct TestStruct { field: (Digest, XFieldElement) }
369    }
370
371    test_case! { fn test_dyn_state_sized_tuple_right_dyn for new type RightIsDyn:
372        struct RightIsDyn { field: (Digest, Vec<XFieldElement>) }
373    }
374
375    test_case! { fn test_dyn_state_sized_tuple_left_dyn for new type LeftIsDyn:
376        struct LeftIsDyn { field: (Vec<XFieldElement>, Digest) }
377    }
378
379    test_case! { fn test_dyn_state_sized_tuple_both_dyn for new type BothDyn:
380        struct BothDyn { field: (Vec<XFieldElement>, Vec<XFieldElement>) }
381    }
382
383    test_case! { fn proof for Proof }
384    test_case! { fn coin for CoinLookalike }
385    test_case! { fn utxo for UtxoLookalike }
386    test_case! { fn salted_utxos for SaltedUtxosLookalike }
387    test_case! { fn collect_lock_scripts_witness for CollectLockScriptsWitnessLookalike }
388    test_case! { fn collect_type_scripts_witness for CollectTypeScriptsWitnessLookalike }
389    test_case! { fn claim for Claim }
390    test_case! { fn neptune_coins for NeptuneCoinsLookalike }
391    test_case! { fn option_neptune_coins for Option<NeptuneCoinsLookalike> }
392    test_case! { fn chunk for ChunkLookalike }
393    test_case! { fn chunk_dictionary for ChunkDictionaryLookalike }
394    test_case! { fn proof_collection for ProofCollectionLookalike }
395    test_case! { fn absolute_index_set for AbsoluteIndexSetLookalike }
396    test_case! { fn removal_record for RemovalRecordLookalike }
397    test_case! { fn addition_record for AdditionRecordLookalike }
398    test_case! { fn public_announcement for PublicAnnouncementLookalike }
399    test_case! { fn timestamp for TimestampLookalike }
400    test_case! { fn transaction_kernel for TransactionKernelLookalike }
401    test_case! { fn kernel_to_outputs_witness for KernelToOutputsWitnessLookalike }
402    test_case! { fn merge_witness for MergeWitnessLookalike }
403    test_case! { fn ms_membership_proof for MsMembershipProofLookalike }
404    test_case! { fn removal_records_witness for RemovalRecordsIntegrityWitnessLookalike }
405    test_case! { fn update_witness for UpdateWitnessLookalike }
406    test_case! { fn lock_script_and_witness for LockScriptAndWitnessLookalike }
407    test_case! { fn mutator_set_accumulator for MutatorSetAccumulatorLookalike }
408    test_case! { fn primitive_witness for PrimitiveWitnessLookalike }
409    test_case! { fn mmr_successor_proof for MmrSuccessorProof }
410}
411
412#[cfg(test)]
413mod benches {
414    use super::*;
415    use crate::neptune::neptune_like_types_for_tests::ProofCollectionLookalike;
416    use crate::neptune::neptune_like_types_for_tests::TransactionKernelLookalike;
417    use crate::test_prelude::*;
418
419    #[macro_rules_attr::apply(test)]
420    fn bench_proof_collection_lookalike() {
421        ShadowedAccessor::new(VerifyNdSiIntegrity::<ProofCollectionLookalike>::default()).bench();
422    }
423
424    #[macro_rules_attr::apply(test)]
425    fn bench_transaction_kernel_lookalike() {
426        ShadowedAccessor::new(VerifyNdSiIntegrity::<TransactionKernelLookalike>::default()).bench();
427    }
428}