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#[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 AuthenticationStructure::compute_size_and_assert_valid_size_indicator(library)
62 }
63 _ => unreachable!(),
64 }
65 }
66
67 fn increment_current_item_count(&self) -> Vec<LabelledInstruction> {
74 const CURRENT_ITEM_COUNT_OFFSET: BFieldElement = BFieldElement::new(4);
75
76 triton_asm!(
77 dup 0
80 addi {CURRENT_ITEM_COUNT_OFFSET}
81 read_mem 1
84 addi 1
85 pick 1
88 addi 1
89 pick 1
90 write_mem 1
93 pop 1
94 )
96 }
97
98 fn assert_consistent_size_indicators(&self, library: &mut Library) -> Vec<LabelledInstruction> {
103 if let Some(static_length) = self.proof_item.payload_static_length() {
109 let expected_item_size = static_length + 1;
110 return triton_asm! {
111 dup 0
113 read_mem 1
114 pop 1
115 push {expected_item_size}
118 eq
119 assert error_id {Self::BAD_PROOF_ITEM_SIZE_INDICATOR_STATIC_SIZE_PAYLOAD}
120 };
122 }
123
124 let calculate_own_size = self.proof_item_calculate_size(library);
125
126 triton_asm! {
127 dup 0
131 addi 3
132 {&calculate_own_size}
133 hint calculated_payload_size = stack[0]
134 dup 1
137 addi 2
138 read_mem 1
139 pop 1
140 hint indicated_payload_size = stack[0]
141 dup 1
144 eq
145 assert
146 hint payload_size = stack[0]
147 addi 2
152 hint calculated_item_size = stack[0]
153 dup 1
156 read_mem 1
157 pop 1
158 eq
161 assert
162 }
164 }
165
166 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 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 dup 0
189 addi 1 push {absorb_length}
191 call {absorb_multiple}
193 };
195 }
196
197 let pad_and_absorb_all = library.import(Box::new(PadAndAbsorbAll));
201 triton_asm! {
202 dup 0 call {pad_and_absorb_all}
204 }
206 }
207
208 fn update_proof_item_iter_to_next_proof_item(&self) -> Vec<LabelledInstruction> {
213 triton_asm! {
214 dup 0 {&self.advance_proof_item_pointer_to_next_item()}
216 hint next_proof_item_list_element_size_pointer = stack[0]
217 swap 1 swap 2 write_mem 1 pop 1 }
222 }
223
224 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 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 fn advance_proof_item_pointer_to_next_item_reading_length_from_memory(
255 &self,
256 ) -> Vec<LabelledInstruction> {
257 triton_asm! {
258 read_mem 1 hint proof_item_length = stack[1]
260
261 push {MAX_SIZE_FOR_DYNAMICALLY_SIZED_PROOF_ITEMS}
263 dup 2
264 lt
267 assert
270 push 2 add add }
274 }
275
276 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 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 dup 0
308 push 1
309 add
310 read_mem 1
311 pop 1
312 push {EXTENSION_DEGREE}
315 mul
316 push 1
317 add
318 dup 1
321 add
322 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 push 0
341 eq
342 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 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 read_mem 1
378 hint proof_item_list_element_size_pointer = stack[1]
379
380 addi 1
381 swap 1 addi 1
384 hint proof_item_discriminant_pointer = stack[0]
385
386 read_mem 1 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 {&self.assert_consistent_size_indicators(library)}
397 {&self.fiat_shamir_absorb_snippet(library)}
400 {&self.update_proof_item_iter_to_next_proof_item()}
403 {&self.advance_list_element_pointer_to_proof_item_payload()}
406 {final_hint} {&self.verify_last_xfe_is_non_zero_if_payload_is_polynomial()}
409 {final_hint} 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 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 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 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 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 ram.insert(address, bfe!(encoding_length as u64 + 2));
812 address.increment();
813
814 ram.insert(address, bfe!(1));
816 address.increment();
817
818 ram.insert(address, bfe!(encoding_length as u64));
820 address.increment();
821
822 for word in encoded_fri_poly_proof_item {
824 ram.insert(address, word);
825 address.increment();
826 }
827
828 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 #[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 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 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 assert_ne!(
914 sponge(root_a),
915 sponge(root_b),
916 "honest encoding must bind Merkle root"
917 );
918
919 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 #[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 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}