Skip to main content

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