tasm_lib/verifier/vm_proof_iter/
dequeue_next_as.rs

1use triton_vm::fri::AuthenticationStructure;
2use triton_vm::prelude::*;
3use triton_vm::proof_item::FriResponse;
4use triton_vm::proof_item::ProofItemVariant;
5use triton_vm::table::AuxiliaryRow;
6use triton_vm::table::MainRow;
7use triton_vm::table::QuotientSegments;
8use twenty_first::math::x_field_element::EXTENSION_DEGREE;
9use twenty_first::prelude::Polynomial;
10
11use crate::hashing::sponge_hasher::pad_and_absorb_all::PadAndAbsorbAll;
12use crate::prelude::*;
13
14const MAX_SIZE_FOR_DYNAMICALLY_SIZED_PROOF_ITEMS: u32 = 1u32 << 22;
15
16/// Reads a proof item of the supplied type from the [`ProofStream`][proof_stream].
17/// Crashes Triton VM if the proof item is not of the expected type.
18/// Updates an internal pointer to the next proof item.
19///
20/// [proof_stream]: triton_vm::proof_stream::ProofStream
21#[derive(Debug, Copy, Clone, Eq, PartialEq)]
22pub struct DequeueNextAs {
23    pub proof_item: ProofItemVariant,
24}
25
26impl DequeueNextAs {
27    pub fn new(proof_item: ProofItemVariant) -> Self {
28        Self { proof_item }
29    }
30
31    fn item_name(&self) -> String {
32        self.proof_item.to_string().to_lowercase()
33    }
34
35    fn proof_item_calculate_size(&self, library: &mut Library) -> Vec<LabelledInstruction> {
36        match self.proof_item {
37            ProofItemVariant::MasterMainTableRows => {
38                Vec::<MainRow<BFieldElement>>::compute_size_and_assert_valid_size_indicator(library)
39            }
40            ProofItemVariant::MasterAuxTableRows => {
41                Vec::<AuxiliaryRow>::compute_size_and_assert_valid_size_indicator(library)
42            }
43            ProofItemVariant::QuotientSegmentsElements => {
44                Vec::<QuotientSegments>::compute_size_and_assert_valid_size_indicator(library)
45            }
46            ProofItemVariant::FriCodeword => {
47                Vec::<XFieldElement>::compute_size_and_assert_valid_size_indicator(library)
48            }
49            ProofItemVariant::FriPolynomial => {
50                Polynomial::<XFieldElement>::compute_size_and_assert_valid_size_indicator(library)
51            }
52            ProofItemVariant::FriResponse => {
53                FriResponse::compute_size_and_assert_valid_size_indicator(library)
54            }
55            ProofItemVariant::AuthenticationStructure => {
56                // All authentication structures should be stripped from loaded proofs, but for
57                // more complete tests, we support it here anyway.
58                AuthenticationStructure::compute_size_and_assert_valid_size_indicator(library)
59            }
60            _ => unreachable!(),
61        }
62    }
63
64    /// Increment the counter of proof items already dequeued.
65    ///
66    /// ```text
67    /// BEFORE: _ *vm_proof_iter
68    /// AFTER:  _ *vm_proof_iter
69    /// ```
70    fn increment_current_item_count(&self) -> Vec<LabelledInstruction> {
71        const CURRENT_ITEM_COUNT_OFFSET: BFieldElement = BFieldElement::new(4);
72
73        triton_asm!(
74            // _ *proof_item_iter
75
76            dup 0
77            addi {CURRENT_ITEM_COUNT_OFFSET}
78            // _ *proof_item_iter *current_item_count
79
80            read_mem 1
81            addi 1
82            // _ *proof_item_iter current_item_count *current_item_count
83
84            pick 1
85            addi 1
86            pick 1
87            // _ *proof_item_iter (current_item_count + 1) *current_item_count
88
89            write_mem 1
90            pop 1
91            // _ *proof_item_iter
92        )
93    }
94
95    /// ```text
96    /// BEFORE: _ *proof_item_size
97    /// AFTER:  _ *proof_item_size
98    /// ```
99    fn assert_consistent_size_indicators(&self, library: &mut Library) -> Vec<LabelledInstruction> {
100        if self.proof_item.payload_static_length().is_some() {
101            return vec![];
102        }
103
104        let calculate_own_size = self.proof_item_calculate_size(library);
105
106        triton_asm! {
107            // _ *proof_item_size
108
109            dup 0
110            addi 3
111            {&calculate_own_size}
112            hint calculated_payload_size = stack[0]
113            // _ *proof_item_size calculated_payload_size
114
115            dup 1
116            addi 2
117            read_mem 1
118            pop 1
119            hint indicated_payload_size = stack[0]
120            // _ *proof_item_size calculated_payload_size reported_field_size
121
122            dup 1
123            eq
124            assert
125            hint payload_size = stack[0]
126            // _ *proof_item_size payload_size
127
128
129            /* Account for discriminant and size-indicator */
130            addi 2
131            hint calculated_item_size = stack[0]
132            // _ *proof_item_size calculated_item_size
133
134            dup 1
135            read_mem 1
136            pop 1
137            // _ *proof_item_size calculated_own_size indicated_item_size
138
139            eq
140            assert
141            // _ *proof_item_size
142        }
143    }
144
145    /// ```text
146    /// BEFORE: _ *proof_item_size
147    /// AFTER:  _ *proof_item_size
148    /// ```
149    fn fiat_shamir_absorb_snippet(&self, library: &mut Library) -> Vec<LabelledInstruction> {
150        if !self.proof_item.include_in_fiat_shamir_heuristic() {
151            return vec![];
152        }
153
154        let pad_and_absorb_all = library.import(Box::new(PadAndAbsorbAll));
155        triton_asm! {
156            dup 0           // _ *proof_item_size *proof_item_size
157            call {pad_and_absorb_all}
158                            // _ *proof_item_size
159        }
160    }
161
162    /// ```text
163    /// BEFORE: _ *proof_item_iter *proof_item_size
164    /// AFTER:  _ *proof_item_size
165    /// ```
166    fn update_proof_item_iter_to_next_proof_item(&self) -> Vec<LabelledInstruction> {
167        triton_asm! {
168            dup 0               // _ *proof_item_iter *proof_item_size *proof_item_iter
169            {&self.advance_proof_item_pointer_to_next_item()}
170            hint next_proof_item_list_element_size_pointer = stack[0]
171                                // _ *proof_item_iter *proof_item_size *next_proof_item_size
172
173            swap 1 swap 2       // _ *pi_size *next_pi_size *pi_iter
174            write_mem 1 pop 1   // _ *pi_size
175        }
176    }
177
178    /// ```text
179    /// BEFORE: _ *proof_item_size
180    /// AFTER:  _ *next_proof_item_size
181    /// ```
182    fn advance_proof_item_pointer_to_next_item(&self) -> Vec<LabelledInstruction> {
183        let Some(static_length) = self.proof_item.payload_static_length() else {
184            return self.advance_proof_item_pointer_to_next_item_reading_length_from_memory();
185        };
186        Self::advance_proof_item_pointer_to_next_item_using_static_size(static_length)
187    }
188
189    /// ```text
190    /// BEFORE: _ *proof_item_size
191    /// AFTER:  _ *next_proof_item_size
192    /// ```
193    fn advance_proof_item_pointer_to_next_item_using_static_size(
194        length: usize,
195    ) -> Vec<LabelledInstruction> {
196        let length_plus_bookkeeping_offset = length + 2;
197        triton_asm! {
198            push {length_plus_bookkeeping_offset}
199            hint proof_item_length_plus_bookkeeping_offset = stack[0]
200            add
201        }
202    }
203
204    /// ```text
205    /// BEFORE: _ *proof_item_size
206    /// AFTER:  _ *next_proof_item_size
207    /// ```
208    fn advance_proof_item_pointer_to_next_item_reading_length_from_memory(
209        &self,
210    ) -> Vec<LabelledInstruction> {
211        triton_asm! {
212            read_mem 1          // _ proof_item_size (*proof_item_size - 1)
213            hint proof_item_length = stack[1]
214
215            // Verify that size does not exceed max allowed size
216            push {MAX_SIZE_FOR_DYNAMICALLY_SIZED_PROOF_ITEMS}
217            dup 2
218            // _ proof_item_size (*proof_item_size - 1) max_size proof_item_size
219
220            lt
221            // _ proof_item_size (*proof_item_size - 1) (max_size > proof_item_size)
222
223            assert
224            // _ proof_item_size (*proof_item_size - 1)
225
226            push 2 add add      // _ *next_proof_item_size
227        }
228    }
229
230    /// ```text
231    /// BEFORE: _ *proof_item_size
232    /// AFTER:  _ *proof_item_payload
233    /// ```
234    fn advance_list_element_pointer_to_proof_item_payload(&self) -> Vec<LabelledInstruction> {
235        let payload_length_indicator_size = match self.proof_item.payload_static_length() {
236            Some(_) => 0,
237            None => 1,
238        };
239        let list_item_length_indicator_size = 1;
240        let discriminant_size = 1;
241        let bookkeeping_offset =
242            payload_length_indicator_size + list_item_length_indicator_size + discriminant_size;
243
244        triton_asm! { push {bookkeeping_offset} hint bookkeeping_offset = stack[0] add }
245    }
246
247    /// A [`BFieldCodec`] encoding of a [`Polynomial`] may not contain trailing
248    /// zeros. This is because it must be easy to read the degree of the polynomial.
249    /// In order to be consistent with that rule, we perform this check here, before
250    /// returning a pointer to the polynomial to the caller.
251    ///
252    /// ```text
253    /// BEFORE: _ *proof_item_payload
254    /// AFTER:  _ *proof_item_payload
255    /// ```
256    fn verify_last_xfe_is_non_zero_if_payload_is_polynomial(&self) -> Vec<LabelledInstruction> {
257        match self.proof_item {
258            ProofItemVariant::FriPolynomial => triton_asm!(
259                // _ *fri_polynomial
260
261                dup 0
262                push 1
263                add
264                read_mem 1
265                pop 1
266                // _ *fri_polynomial num_coefficients
267
268                push {EXTENSION_DEGREE}
269                mul
270                push 1
271                add
272                // _ *fri_polynomial offset_last_word
273
274                dup 1
275                add
276                // _ *proof_item_payload *coefficients_last_word
277
278                read_mem {EXTENSION_DEGREE}
279                pop 1
280                push 0
281                eq
282                swap 2
283                push 0
284                eq
285                swap 1
286                push 0
287                eq
288                add
289                add
290                push {EXTENSION_DEGREE}
291                eq
292                // _ *proof_item_payload (last_coefficient == 0)
293
294                push 0
295                eq
296                // _ *proof_item_payload (last_coefficient != 0)
297
298                assert
299            ),
300            _ => triton_asm!(),
301        }
302    }
303}
304
305impl BasicSnippet for DequeueNextAs {
306    fn inputs(&self) -> Vec<(DataType, String)> {
307        vec![(DataType::VoidPointer, "*proof_item_iter".to_string())]
308    }
309
310    fn outputs(&self) -> Vec<(DataType, String)> {
311        let payload_pointer_str = format!("*{}_payload", self.item_name());
312        vec![(DataType::VoidPointer, payload_pointer_str)]
313    }
314
315    fn entrypoint(&self) -> String {
316        let proof_item_name = self.item_name();
317        format!("tasmlib_verifier_vm_proof_iter_dequeue_next_as_{proof_item_name}")
318    }
319
320    /// ```text
321    /// BEFORE: _ *vm_proof_iter
322    /// AFTER:  _ *proof_item_payload
323    /// ```
324    fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
325        let final_hint = format!("hint {}_pointer: Pointer = stack[0]", self.item_name());
326        triton_asm! {
327            {self.entrypoint()}:
328                {&self.increment_current_item_count()}
329                                    // _ *vm_proof_iter
330
331                read_mem 1
332                hint proof_item_list_element_size_pointer = stack[1]
333
334                addi 1
335                swap 1              // _ *proof_item_iter *proof_item_size
336
337                addi 1
338                hint proof_item_discriminant_pointer = stack[0]
339
340                read_mem 1          // _ *proof_item_iter discriminant *proof_item_size
341                hint proof_item_list_element_size_pointer = stack[0]
342                hint proof_item_discriminant = stack[1]
343
344                swap 1
345                push {self.proof_item.bfield_codec_discriminant()}
346                hint expected_proof_item_discriminant = stack[0]
347
348                eq assert           // _ *proof_item_iter *proof_item_size
349
350                {&self.assert_consistent_size_indicators(library)}
351                                    // _ *proof_item_iter *proof_item_size
352
353                {&self.fiat_shamir_absorb_snippet(library)}
354                                    // _ *proof_item_iter *proof_item_size
355
356                {&self.update_proof_item_iter_to_next_proof_item()}
357                                    // _ *proof_item_size
358
359                {&self.advance_list_element_pointer_to_proof_item_payload()}
360                {final_hint}        // _ *proof_item_payload
361
362                {&self.verify_last_xfe_is_non_zero_if_payload_is_polynomial()}
363                {final_hint}        // _ *proof_item_payload
364
365                return
366        }
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use arbitrary::Arbitrary;
373    use arbitrary::Unstructured;
374    use num_traits::One;
375    use num_traits::Zero;
376    use strum::IntoEnumIterator;
377    use triton_vm::proof_item::ProofItem;
378    use triton_vm::proof_stream::ProofStream;
379    use triton_vm::table::master_table::MasterMainTable;
380    use twenty_first::prelude::*;
381
382    use super::*;
383    use crate::empty_stack;
384    use crate::execute_with_terminal_state;
385    use crate::rust_shadowing_helper_functions::dyn_malloc::dynamic_allocator;
386    use crate::structure::tasm_object::decode_from_memory_with_size;
387    use crate::test_prelude::*;
388    use crate::verifier::vm_proof_iter::shared::vm_proof_iter_struct::VmProofIter;
389
390    #[derive(Debug)]
391    struct RustShadowForDequeueNextAs<'a> {
392        dequeue_next_as: DequeueNextAs,
393
394        proof_iter_pointer: BFieldElement,
395
396        stack: &'a mut Vec<BFieldElement>,
397        memory: &'a mut HashMap<BFieldElement, BFieldElement>,
398        sponge: &'a mut Tip5,
399    }
400
401    impl RustShadowForDequeueNextAs<'_> {
402        fn execute(&mut self) -> Vec<BFieldElement> {
403            self.assert_correct_discriminant();
404            self.maybe_alter_fiat_shamir_heuristic_with_proof_item();
405            self.update_stack_using_current_proof_item();
406            self.update_proof_item_iter();
407            self.update_current_item_count();
408            self.public_output()
409        }
410
411        fn current_proof_item_list_element_size_pointer(&self) -> BFieldElement {
412            let &maybe_pointer = self.memory.get(&self.proof_iter_pointer).unwrap();
413            maybe_pointer
414        }
415
416        fn current_proof_item_list_element_size(&self) -> BFieldElement {
417            let Some(item_length) = self.dequeue_next_as.proof_item.payload_static_length() else {
418                return self.fetch_current_proof_item_list_element_size_from_memory();
419            };
420            let discriminant_length = 1;
421            BFieldElement::new((item_length + discriminant_length) as u64)
422        }
423
424        /// Accounts for the item's discriminant.
425        fn fetch_current_proof_item_list_element_size_from_memory(&self) -> BFieldElement {
426            let size_pointer = self.current_proof_item_list_element_size_pointer();
427            let &element_size = self.memory.get(&size_pointer).unwrap();
428            assert!(
429                element_size.value() < MAX_SIZE_FOR_DYNAMICALLY_SIZED_PROOF_ITEMS as u64,
430                "proof item size may not exceed max allowed size"
431            );
432            element_size
433        }
434
435        fn next_proof_item_list_element_size_pointer(&self) -> BFieldElement {
436            self.current_proof_item_list_element_size_pointer()
437                + self.current_proof_item_list_element_size()
438                + BFieldElement::one()
439        }
440
441        fn discriminant_pointer(&self) -> BFieldElement {
442            self.current_proof_item_list_element_size_pointer() + BFieldElement::one()
443        }
444
445        fn discriminant(&self) -> BFieldElement {
446            let &discriminant = self.memory.get(&self.discriminant_pointer()).unwrap();
447            discriminant
448        }
449
450        fn proof_item_payload_pointer(&self) -> BFieldElement {
451            self.discriminant_pointer()
452                + BFieldElement::one()
453                + self.proof_item_payload_size_indicator_length()
454        }
455
456        fn proof_item_payload_size_indicator_length(&self) -> BFieldElement {
457            match self.dequeue_next_as.proof_item.payload_static_length() {
458                Some(_) => BFieldElement::zero(),
459                None => BFieldElement::one(),
460            }
461        }
462
463        fn assert_correct_discriminant(&self) {
464            let expected_discriminant = self.dequeue_next_as.proof_item.bfield_codec_discriminant();
465            assert_eq!(expected_discriminant, self.discriminant().value() as usize);
466        }
467
468        fn proof_item(&self) -> ProofItem {
469            let maybe_item = decode_from_memory_with_size::<ProofItem>(
470                self.memory,
471                self.discriminant_pointer(),
472                self.current_proof_item_list_element_size().value() as usize,
473            );
474            *maybe_item.unwrap()
475        }
476
477        fn maybe_alter_fiat_shamir_heuristic_with_proof_item(&mut self) {
478            let proof_item = self.proof_item();
479            if proof_item.include_in_fiat_shamir_heuristic() {
480                Tip5::pad_and_absorb_all(self.sponge, &proof_item.encode());
481            }
482        }
483
484        fn update_stack_using_current_proof_item(&mut self) {
485            self.stack.push(self.proof_item_payload_pointer());
486        }
487
488        fn update_proof_item_iter(&mut self) {
489            let next_item_pointer = self.next_proof_item_list_element_size_pointer();
490            self.memory
491                .insert(self.proof_iter_pointer, next_item_pointer);
492        }
493
494        fn update_current_item_count(&mut self) {
495            let count = self
496                .memory
497                .get_mut(&(self.proof_iter_pointer + bfe!(4)))
498                .unwrap();
499            count.increment();
500        }
501
502        fn public_output(&self) -> Vec<BFieldElement> {
503            vec![]
504        }
505    }
506
507    impl Procedure for DequeueNextAs {
508        fn rust_shadow(
509            &self,
510            stack: &mut Vec<BFieldElement>,
511            memory: &mut HashMap<BFieldElement, BFieldElement>,
512            _: &NonDeterminism,
513            _: &[BFieldElement],
514            sponge: &mut Option<Tip5>,
515        ) -> Vec<BFieldElement> {
516            RustShadowForDequeueNextAs {
517                dequeue_next_as: *self,
518                proof_iter_pointer: stack.pop().unwrap(),
519                stack,
520                memory,
521                sponge: sponge.as_mut().unwrap(),
522            }
523            .execute()
524        }
525
526        fn pseudorandom_initial_state(
527            &self,
528            seed: [u8; 32],
529            _: Option<BenchmarkCase>,
530        ) -> ProcedureInitialState {
531            let mut rng = StdRng::from_seed(seed);
532            let mut proof_stream = ProofStream::new();
533            proof_stream.enqueue(Self::pseudorandom_proof_item(self.proof_item, rng.random()));
534
535            let other_item_type = ProofItemVariant::iter().choose(&mut rng).unwrap();
536            proof_stream.enqueue(Self::pseudorandom_proof_item(other_item_type, rng.random()));
537
538            self.initial_state_from_proof_and_address(proof_stream.into(), rng.random())
539        }
540    }
541
542    impl DequeueNextAs {
543        fn initial_state_from_proof_and_address(
544            &self,
545            proof: Proof,
546            proof_address: BFieldElement,
547        ) -> ProcedureInitialState {
548            let mut ram = HashMap::new();
549            encode_to_memory(&mut ram, proof_address, &proof);
550
551            let proof_iter_address = dynamic_allocator(&mut ram);
552            let vm_proof_iter_init_state = VmProofIter::new(proof_address, &proof);
553            encode_to_memory(&mut ram, proof_iter_address, &vm_proof_iter_init_state);
554
555            ProcedureInitialState {
556                stack: [self.init_stack_for_isolated_run(), vec![proof_iter_address]].concat(),
557                nondeterminism: NonDeterminism::default().with_ram(ram),
558                public_input: vec![],
559                sponge: Some(Tip5::init()),
560            }
561        }
562
563        fn pseudorandom_new(seed: [u8; 32]) -> Self {
564            let mut unstructured = Unstructured::new(&seed);
565            let proof_item: ProofItem = Arbitrary::arbitrary(&mut unstructured).unwrap();
566            let proof_item = proof_item.into();
567            Self { proof_item }
568        }
569
570        pub(crate) fn pseudorandom_proof_stream(
571            proof_items_variants: Vec<ProofItemVariant>,
572            seed: [u8; 32],
573        ) -> ProofStream {
574            let mut rng = StdRng::from_seed(seed);
575
576            let mut proof_stream = ProofStream::new();
577            for &proof_item in &proof_items_variants {
578                let item = DequeueNextAs::pseudorandom_proof_item(proof_item, rng.random());
579                proof_stream.enqueue(item);
580            }
581
582            proof_stream
583        }
584
585        fn pseudorandom_proof_item(
586            proof_item_variant: ProofItemVariant,
587            seed: [u8; 32],
588        ) -> ProofItem {
589            let mut rng = StdRng::from_seed(seed);
590            let proof_stream_seed: [u8; 10000] = rng.random();
591            let mut unstructured = Unstructured::new(&proof_stream_seed);
592
593            use ProofItemVariant::*;
594            match proof_item_variant {
595                MerkleRoot => {
596                    ProofItem::MerkleRoot(Arbitrary::arbitrary(&mut unstructured).unwrap())
597                }
598                OutOfDomainMainRow => {
599                    ProofItem::OutOfDomainMainRow(Arbitrary::arbitrary(&mut unstructured).unwrap())
600                }
601                OutOfDomainAuxRow => {
602                    ProofItem::OutOfDomainAuxRow(Arbitrary::arbitrary(&mut unstructured).unwrap())
603                }
604                OutOfDomainQuotientSegments => ProofItem::OutOfDomainQuotientSegments(
605                    Arbitrary::arbitrary(&mut unstructured).unwrap(),
606                ),
607                AuthenticationStructure => ProofItem::AuthenticationStructure(
608                    Arbitrary::arbitrary(&mut unstructured).unwrap(),
609                ),
610                MasterMainTableRows => {
611                    ProofItem::MasterMainTableRows(Arbitrary::arbitrary(&mut unstructured).unwrap())
612                }
613                MasterAuxTableRows => {
614                    ProofItem::MasterAuxTableRows(Arbitrary::arbitrary(&mut unstructured).unwrap())
615                }
616                Log2PaddedHeight => {
617                    ProofItem::Log2PaddedHeight(Arbitrary::arbitrary(&mut unstructured).unwrap())
618                }
619                QuotientSegmentsElements => ProofItem::QuotientSegmentsElements(
620                    Arbitrary::arbitrary(&mut unstructured).unwrap(),
621                ),
622                FriCodeword => {
623                    ProofItem::FriCodeword(Arbitrary::arbitrary(&mut unstructured).unwrap())
624                }
625                FriPolynomial => {
626                    ProofItem::FriPolynomial(Arbitrary::arbitrary(&mut unstructured).unwrap())
627                }
628                FriResponse => {
629                    // This code requires the authentication paths field to be empty
630                    let fri_response = triton_vm::proof_item::FriResponse {
631                        auth_structure: vec![],
632                        revealed_leaves: Arbitrary::arbitrary(&mut unstructured).unwrap(),
633                    };
634                    ProofItem::FriResponse(fri_response)
635                }
636            }
637        }
638
639        fn test_rust_equivalence(self, initial_state: ProcedureInitialState) {
640            test_rust_equivalence_given_complete_state(
641                &ShadowedProcedure::new(self),
642                &initial_state.stack,
643                &initial_state.public_input,
644                &initial_state.nondeterminism,
645                &initial_state.sponge,
646                None,
647            );
648        }
649    }
650
651    #[test]
652    fn disallow_too_big_dynamically_sized_proof_item() {
653        let dequeue_next_as = DequeueNextAs::new(ProofItemVariant::MasterMainTableRows);
654        let initial_state = initial_state_with_too_big_master_table_rows();
655        let tvm_result = execute_with_terminal_state(
656            Program::new(&dequeue_next_as.link_for_isolated_run()),
657            &[],
658            &initial_state.stack,
659            &initial_state.nondeterminism,
660            None,
661        );
662
663        let rust_result = std::panic::catch_unwind(|| {
664            let mut stack = initial_state.stack.clone();
665            let mut memory = initial_state.nondeterminism.ram.clone();
666            dequeue_next_as.rust_shadow(
667                &mut stack,
668                &mut memory,
669                &NonDeterminism::default(),
670                &[],
671                &mut None,
672            );
673        });
674
675        assert!(
676            rust_result.is_err() && tvm_result.is_err(),
677            "Test case: Too big dynamically-sized proof item must fail on both platforms."
678        );
679        let err = tvm_result.unwrap_err();
680        assert!(matches!(err, InstructionError::AssertionFailed(_)));
681    }
682
683    fn initial_state_with_too_big_master_table_rows() -> ProcedureInitialState {
684        let dummy_master_table_rows = vec![[bfe!(101); MasterMainTable::NUM_COLUMNS]; 15000];
685        let proof_item = ProofItem::MasterMainTableRows(dummy_master_table_rows);
686        let mut proof_stream = ProofStream::new();
687        proof_stream.enqueue(proof_item);
688        let proof_ptr = BFieldElement::zero();
689        DequeueNextAs::new(ProofItemVariant::MasterMainTableRows)
690            .initial_state_from_proof_and_address(proof_stream.into(), proof_ptr)
691    }
692
693    #[test]
694    fn disallow_trailing_zeros_in_xfe_poly_encoding() {
695        let dequeue_next_as = DequeueNextAs::new(ProofItemVariant::FriPolynomial);
696        let initial_state = initial_state_with_trailing_zeros_in_xfe_poly_encoding();
697        let code = dequeue_next_as.link_for_isolated_run();
698        let tvm_result = execute_with_terminal_state(
699            Program::new(&code),
700            &[],
701            &initial_state.stack,
702            &initial_state.nondeterminism,
703            None,
704        );
705
706        let rust_result = std::panic::catch_unwind(|| {
707            let mut stack = initial_state.stack.clone();
708            let mut memory = initial_state.nondeterminism.ram.clone();
709            dequeue_next_as.rust_shadow(
710                &mut stack,
711                &mut memory,
712                &NonDeterminism::default(),
713                &[],
714                &mut None,
715            );
716        });
717
718        assert!(
719            rust_result.is_err() && tvm_result.is_err(),
720            "Test case: Too big dynamically-sized proof item must fail on both platforms."
721        );
722        let err = tvm_result.unwrap_err();
723        assert!(matches!(err, InstructionError::AssertionFailed(_)));
724    }
725
726    fn initial_state_with_trailing_zeros_in_xfe_poly_encoding() -> ProcedureInitialState {
727        let mut encoded_fri_poly_proof_item =
728            ProofItem::FriPolynomial(Polynomial::new(vec![xfe!(1), xfe!(2), xfe!(3), xfe!(100)]))
729                .encode();
730
731        // Manually set the leading coefficient to zero
732        let encoding_length = encoded_fri_poly_proof_item.len();
733        for i in 0..EXTENSION_DEGREE {
734            encoded_fri_poly_proof_item[encoding_length - 1 - i] = bfe!(0);
735        }
736
737        let proof_start_address = BFieldElement::zero();
738
739        let mut ram = HashMap::new();
740
741        let mut address = proof_start_address;
742
743        // Inidicate a field-size of `ProofStream`
744        ram.insert(address, bfe!(encoding_length as u64 + 2));
745        address.increment();
746
747        // Indicate that there is one proof item
748        ram.insert(address, bfe!(1));
749        address.increment();
750
751        // Indicate size of encoded proof item
752        ram.insert(address, bfe!(encoding_length as u64));
753        address.increment();
754
755        // Add proof item to ND memory
756        for word in encoded_fri_poly_proof_item {
757            ram.insert(address, word);
758            address.increment();
759        }
760
761        // uses highly specific knowledge of `BFieldCodec`
762        let address_of_first_element = proof_start_address + BFieldElement::new(2);
763        let proof_iter_address = proof_start_address - BFieldElement::one();
764        ram.insert(proof_iter_address, address_of_first_element);
765        ProcedureInitialState {
766            stack: [empty_stack(), vec![proof_iter_address]].concat(),
767            nondeterminism: NonDeterminism::default().with_ram(ram),
768            public_input: vec![],
769            sponge: Some(Tip5::init()),
770        }
771    }
772
773    fn dequeueing_is_equivalent_in_rust_and_tasm_prop(proof_item_variant: ProofItemVariant) {
774        let mut proof_stream = ProofStream::new();
775        let proof_item = DequeueNextAs::pseudorandom_proof_item(proof_item_variant, rand::random());
776        proof_stream.enqueue(proof_item);
777
778        let dequeue_next_as = DequeueNextAs::new(proof_item_variant);
779        let proof_ptr = BFieldElement::new(14);
780        let init_state =
781            dequeue_next_as.initial_state_from_proof_and_address(proof_stream.into(), proof_ptr);
782        dequeue_next_as.test_rust_equivalence(init_state);
783    }
784
785    #[test]
786    fn dequeueing_all_proof_items_individually_is_equivalent_in_rust_and_tasm() {
787        for variant in ProofItemVariant::iter() {
788            dequeueing_is_equivalent_in_rust_and_tasm_prop(variant);
789        }
790    }
791
792    #[proptest]
793    fn dequeuing_as_suggested_element_is_equivalent_in_rust_and_tasm(seed: [u8; 32]) {
794        let dequeue_next_as = DequeueNextAs::pseudorandom_new(seed);
795        let initial_state = dequeue_next_as.pseudorandom_initial_state(seed, None);
796        dequeue_next_as.test_rust_equivalence(initial_state);
797    }
798
799    /// Helps testing dequeuing multiple items.
800    #[derive(Debug, Default, Clone, Eq, PartialEq)]
801    struct TestHelperDequeueMultipleAs {
802        proof_items: Vec<ProofItemVariant>,
803    }
804
805    impl BasicSnippet for TestHelperDequeueMultipleAs {
806        fn inputs(&self) -> Vec<(DataType, String)> {
807            vec![(DataType::VoidPointer, "*proof_item_iter".to_string())]
808        }
809
810        fn outputs(&self) -> Vec<(DataType, String)> {
811            vec![(DataType::VoidPointer, "*proof_item_iter".to_string())]
812        }
813
814        fn entrypoint(&self) -> String {
815            "test_helper_dequeue_multiple_as".to_string()
816        }
817
818        fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
819            let dequeue_next_as_entrypoints = self
820                .proof_items
821                .iter()
822                .map(|&item| library.import(Box::new(DequeueNextAs::new(item))))
823                .collect::<Vec<_>>();
824
825            let code_body = dequeue_next_as_entrypoints
826                .into_iter()
827                .flat_map(|snippet| triton_asm!(dup 0 call {snippet} pop 1))
828                .collect_vec();
829
830            triton_asm!({self.entrypoint()}: {&code_body} return)
831        }
832    }
833
834    impl Procedure for TestHelperDequeueMultipleAs {
835        fn rust_shadow(
836            &self,
837            stack: &mut Vec<BFieldElement>,
838            memory: &mut HashMap<BFieldElement, BFieldElement>,
839            non_determinism: &NonDeterminism,
840            std_in: &[BFieldElement],
841            sponge: &mut Option<Tip5>,
842        ) -> Vec<BFieldElement> {
843            let &proof_iter_pointer = stack.last().unwrap();
844            for &proof_item in &self.proof_items {
845                let dequeue_next_as = DequeueNextAs { proof_item };
846                stack.push(proof_iter_pointer);
847                dequeue_next_as.rust_shadow(stack, memory, non_determinism, std_in, sponge);
848                stack.pop().unwrap();
849            }
850            vec![]
851        }
852
853        fn pseudorandom_initial_state(
854            &self,
855            seed: [u8; 32],
856            _: Option<BenchmarkCase>,
857        ) -> ProcedureInitialState {
858            let mut rng = StdRng::from_seed(seed);
859            let proof_stream =
860                DequeueNextAs::pseudorandom_proof_stream(self.proof_items.clone(), rng.random());
861
862            // We just use the state-initialization method from the `[DequeueNextAs]` snippet.
863            // This is OK as long as we don't read the program hash which we don't do in this
864            // snippet.
865            let dummy_snippet_for_state_init = DequeueNextAs::new(ProofItemVariant::MerkleRoot);
866            dummy_snippet_for_state_init
867                .initial_state_from_proof_and_address(proof_stream.into(), rng.random())
868        }
869    }
870
871    impl TestHelperDequeueMultipleAs {
872        fn test_rust_equivalence(self, initial_state: ProcedureInitialState) {
873            test_rust_equivalence_given_complete_state(
874                &ShadowedProcedure::new(self),
875                &initial_state.stack,
876                &initial_state.public_input,
877                &initial_state.nondeterminism,
878                &initial_state.sponge,
879                None,
880            );
881        }
882    }
883
884    #[test]
885    fn dequeue_two() {
886        let proof_items = vec![ProofItemVariant::MerkleRoot, ProofItemVariant::MerkleRoot];
887        let dequeue_multiple = TestHelperDequeueMultipleAs { proof_items };
888        let initial_state = dequeue_multiple.pseudorandom_initial_state([0; 32], None);
889        dequeue_multiple.test_rust_equivalence(initial_state);
890    }
891
892    #[proptest]
893    fn dequeue_multiple(#[strategy(arb())] proof_items: Vec<ProofItem>, seed: [u8; 32]) {
894        let proof_items = proof_items.into_iter().map(|i| i.into()).collect_vec();
895        let dequeue_multiple = TestHelperDequeueMultipleAs { proof_items };
896        let initial_state = dequeue_multiple.pseudorandom_initial_state(seed, None);
897        dequeue_multiple.test_rust_equivalence(initial_state);
898    }
899}