tasm_lib/verifier/fri/
verify.rs

1use anyhow::bail;
2use itertools::Itertools;
3use num::Zero;
4use triton_vm::arithmetic_domain::ArithmeticDomain;
5use triton_vm::error::FriValidationError;
6use triton_vm::fri::Fri;
7use triton_vm::prelude::*;
8use triton_vm::proof_item::FriResponse;
9use triton_vm::proof_item::ProofItemVariant;
10use triton_vm::proof_stream::ProofStream;
11use twenty_first::math::polynomial::Polynomial;
12use twenty_first::math::polynomial::barycentric_evaluate;
13use twenty_first::math::traits::ModPowU32;
14use twenty_first::math::x_field_element::EXTENSION_DEGREE;
15use twenty_first::util_types::merkle_tree::MerkleTree;
16use twenty_first::util_types::merkle_tree::MerkleTreeInclusionProof;
17
18use crate::U32_TO_USIZE_ERR;
19use crate::data_type::StructType;
20use crate::field;
21use crate::hashing::algebraic_hasher::sample_indices::SampleIndices;
22use crate::hashing::algebraic_hasher::sample_scalars_static_length_dyn_malloc::SampleScalarsStaticLengthDynMalloc;
23use crate::hashing::merkle_root_from_xfes::MerkleRootFromXfes;
24use crate::list::get::Get;
25use crate::list::higher_order::inner_function::InnerFunction;
26use crate::list::higher_order::inner_function::RawCode;
27use crate::list::higher_order::map::Map;
28use crate::list::higher_order::zip::Zip;
29use crate::list::horner_evaluation_dynamic_length::HornerEvaluationDynamicLength;
30use crate::list::length::Length;
31use crate::list::new::New;
32use crate::list::push::Push;
33use crate::prelude::*;
34use crate::verifier::fri::barycentric_evaluation::BarycentricEvaluation;
35use crate::verifier::fri::number_of_rounds::NumberOfRounds;
36use crate::verifier::fri::verify_fri_authentication_paths::VerifyFriAuthenticationPaths;
37use crate::verifier::vm_proof_iter::dequeue_next_as::DequeueNextAs;
38use crate::verifier::vm_proof_iter::shared::vm_proof_iter_type;
39
40/// FRI verification of a Reed-Solomon codeword.
41///
42/// `FriVerify` checks that a Reed-Solomon codeword, provided as an oracle, has a low
43/// degree interpolant. Specifically, the algorithm takes a `ProofStream` object, runs the
44/// verifier of the FRI protocol, and (if successful) returns the lists of indices at
45/// which the codeword is probed along with the values of the codeword at those indices.
46/// The test succeeds with probability 1 if the codeword is of low degree; and with
47/// probability *soundness error* if the codeword is far from low-degree. If the test is
48/// not successful, the VM crashes.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, BFieldCodec, TasmObject)]
50pub struct FriVerify {
51    // expansion factor = 1 / rate
52    pub expansion_factor: u32,
53    pub num_collinearity_checks: u32,
54    pub domain_length: u32,
55    pub domain_offset: BFieldElement,
56    pub domain_generator: BFieldElement,
57}
58
59impl From<Fri> for FriVerify {
60    fn from(fri: Fri) -> Self {
61        Self {
62            domain_generator: fri.domain.generator,
63
64            // This runtime type-conversion prevents a FRI domain of length 2^32 from being created.
65            domain_length: fri.domain.length.try_into().unwrap(),
66            domain_offset: fri.domain.offset,
67            expansion_factor: fri.expansion_factor.try_into().unwrap(),
68            num_collinearity_checks: fri.num_collinearity_checks.try_into().unwrap(),
69        }
70    }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct FriSnippet {
75    #[cfg(test)]
76    pub(crate) test_instance: FriVerify,
77}
78
79pub(super) fn fri_verify_type() -> StructType {
80    let name = "FriVerify".to_string();
81    let fields = vec![
82        ("expansion_factor".to_string(), DataType::U32),
83        ("num_collinearity_checks".to_string(), DataType::U32),
84        ("domain_length".to_string(), DataType::U32),
85        ("domain_offset".to_string(), DataType::Bfe),
86        ("domain_generator".to_string(), DataType::Bfe),
87    ];
88
89    StructType { name, fields }
90}
91
92impl FriSnippet {
93    pub(crate) fn indexed_leafs_list_type() -> DataType {
94        let indexed_leaf_type = DataType::Tuple(vec![DataType::U32, DataType::Xfe]);
95        DataType::List(Box::new(indexed_leaf_type))
96    }
97}
98
99impl BasicSnippet for FriSnippet {
100    fn inputs(&self) -> Vec<(DataType, String)> {
101        let proof_iter_ref = DataType::StructRef(vm_proof_iter_type());
102        let argument_0 = (proof_iter_ref, "*vm_proof_iter".to_string());
103
104        let fri_verify_ref = DataType::StructRef(fri_verify_type());
105        let argument_1 = (fri_verify_ref, "*fri_verify".to_string());
106
107        vec![argument_0, argument_1]
108    }
109
110    fn outputs(&self) -> Vec<(DataType, String)> {
111        vec![(
112            Self::indexed_leafs_list_type(),
113            "indices_and_elements".to_string(),
114        )]
115    }
116
117    fn entrypoint(&self) -> String {
118        "tasmlib_verifier_fri_verify".to_string()
119    }
120
121    fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
122        let entrypoint = self.entrypoint();
123        let fri_num_rounds = library.import(Box::new(NumberOfRounds {}));
124        let domain_length = field!(FriVerify::domain_length);
125        let domain_generator = field!(FriVerify::domain_generator);
126        let domain_offset = field!(FriVerify::domain_offset);
127        let expansion_factor = field!(FriVerify::expansion_factor);
128        let num_collinearity_checks = field!(FriVerify::num_collinearity_checks);
129        let new_list = library.import(Box::new(New));
130        let push_digest_to_list = library.import(Box::new(Push::new(DataType::Digest)));
131        let read_digest = triton_asm!(
132                                // _ *digest
133            push 4 add          // _ *digest+4
134            read_mem 5 pop 1    // _ [digest; 5]
135        );
136        let read_xfe = triton_asm!(
137            addi {EXTENSION_DEGREE - 1} read_mem {EXTENSION_DEGREE} pop 1
138        );
139        let push_scalar = library.import(Box::new(Push::new(DataType::Xfe)));
140
141        let vm_proof_iter_dequeue_next_as_merkle_root =
142            library.import(Box::new(DequeueNextAs::new(ProofItemVariant::MerkleRoot)));
143        let vm_proof_iter_dequeue_next_as_fri_codeword =
144            library.import(Box::new(DequeueNextAs::new(ProofItemVariant::FriCodeword)));
145        let vm_proof_iter_dequeue_next_as_fri_polynomial = library.import(Box::new(
146            DequeueNextAs::new(ProofItemVariant::FriPolynomial),
147        ));
148        let polynomial_evaluation = library.import(Box::new(HornerEvaluationDynamicLength));
149        let barycentric_evaluation = library.import(Box::new(BarycentricEvaluation));
150        let vm_proof_iter_dequeue_next_as_fri_response =
151            library.import(Box::new(DequeueNextAs::new(ProofItemVariant::FriResponse)));
152
153        let vm_proof_iter_sample_one_scalar =
154            library.import(Box::new(SampleScalarsStaticLengthDynMalloc {
155                num_elements: 1,
156            }));
157        let dequeue_commit_phase = format!("{entrypoint}_dequeue_commit_phase_remainder");
158        let length_of_list = library.import(Box::new(Length));
159        let merkle_root_from_xfes = library.import(Box::new(MerkleRootFromXfes));
160        let get_xfe_from_list = library.import(Box::new(Get::new(DataType::Xfe)));
161        let get_digest_from_list = library.import(Box::new(Get::new(DataType::Digest)));
162        let sample_indices = library.import(Box::new(SampleIndices));
163        let verify_authentication_paths_for_leaf_and_index_list =
164            library.import(Box::new(VerifyFriAuthenticationPaths));
165        let zip_index_xfe = library.import(Box::new(Zip::new(DataType::U32, DataType::Xfe)));
166        let query_phase_main_loop = format!("{entrypoint}_query_phase_main_loop");
167        let reduce_indices_label = format!("{entrypoint}_reduce_indices");
168        let map_buffer_len = Map::NUM_INTERNAL_REGISTERS;
169        let map_reduce_indices =
170            library.import(Box::new(Map::new(InnerFunction::RawCode(RawCode {
171                function: triton_asm! {
172                    {reduce_indices_label}:
173                                        // _ half_domain_length [bu ff er] index
174                    dup {map_buffer_len + 1}
175                    place 1             // _ half_domain_length [bu ff er] half_domain_length index
176                    div_mod             // _ half_domain_length [bu ff er] q r
177                    pick 1 pop 1        // _ half_domain_length [bu ff er] index%half_domain_length
178                    return
179                },
180                input_type: DataType::U32,
181                output_type: DataType::U32,
182            }))));
183        let compute_c_values_loop = format!("{entrypoint}_compute_c_values_loop");
184        let assert_membership_label = format!("{entrypoint}_assert_codeword_membership");
185        let map_assert_membership =
186            library.import(Box::new(Map::new(InnerFunction::RawCode(RawCode {
187                input_type: DataType::Tuple(vec![DataType::U32, DataType::Xfe]),
188                output_type: DataType::Tuple(vec![DataType::U32, DataType::Xfe]),
189                function: triton_asm! {
190                    // BEFORE: _ *codeword [bu ff er] index xfe2 xfe1 xfe0
191                    // AFTER:  _ *codeword [bu ff er] index xfe2 xfe1 xfe0
192                    {assert_membership_label}:
193                        hint element_to_check: Xfe = stack[0..3]
194                        hint codeword_index = stack[3]
195                        hint codeword: Pointer = stack[8]
196                        push 0                  // _ *codeword [bu ff er] index xfe2 xfe1 xfe0 0
197                        dup 4 dup {map_buffer_len + 6}
198                        dup 1                   // _ *codeword [bu ff er] index xfe2 xfe1 xfe0 0 index *codeword index
199                        call {get_xfe_from_list}// _ *codeword [bu ff er] index xfe2 xfe1 xfe0 0 index xfe2' xfe1' xfe0'
200                        push 0                  // _ *codeword [bu ff er] index xfe2 xfe1 xfe0 0 index xfe2' xfe1' xfe0' 0
201                        assert_vector           // _ *codeword [bu ff er] index xfe2 xfe1 xfe0 0
202                        pop 1                   // _ *codeword [bu ff er] index xfe2 xfe1 xfe0
203                        return
204                },
205            }))));
206
207        let verify_a_values_authentication_paths_against_input_codeword = triton_asm!(
208            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements num_leafs
209
210            dup 3
211            push -1
212            add
213            // _ ... (dom_len - 1)
214
215            dup 4
216            // _ ... (dom_len - 1) dom_len
217
218            dup 3
219            // _ ... (dom_len - 1) dom_len *a_elements
220
221            dup 3
222            push {EXTENSION_DEGREE}
223            mul
224            add
225            // _ ... (dom_len - 1) dom_len *a_elements_last_word
226
227            dup 5
228            // _ ... (dom_len - 1) dom_len *a_elements_last_word *idx
229
230            dup 0
231            dup 5
232            add
233            // _ ... (dom_len - 1) dom_len *a_elements_last_word *idx *idx_last
234
235            dup 10
236            push 0
237            call {get_digest_from_list}
238            // _ ... (dom_len - 1) dom_len *a_elements_last_word *idx *idx_last [root]
239
240            call {verify_authentication_paths_for_leaf_and_index_list}
241            // _ ...
242
243            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements num_leafs
244        );
245
246        let verify_b_values_authentication_paths_in_main_loop = triton_asm!(
247            /*Verify the authentication paths for the b-elements read from the proof stream */
248
249            // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx_last *a_last *b_last
250
251            dup 11
252            push -1
253            add
254            // _ ... (dom_len - 1)
255
256            dup 12
257            dup 12
258            add
259            // _ ... (dom_len - 1) (dom_len + half_dom_len)
260
261            dup 2
262            // _ ... (dom_len - 1) (dom_len + half_dom_len) *b_last
263
264            dup 6
265            dup 6
266            // _ ... (dom_len - 1) (dom_len + half_dom_len) *b_last *idx *idx_last
267
268            dup 14
269            dup 13
270            // _ ... (dom_len - 1) (dom_len + half_dom_len) *b_last *idx *idx_last *roots r
271
272            call {get_digest_from_list}
273            // _ ... (dom_len - 1) (dom_len + half_dom_len) *b_last *idx *idx_last [root]
274
275            call {verify_authentication_paths_for_leaf_and_index_list}
276            // _ ...
277
278            // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx_last *a_last *b_last
279        );
280
281        triton_asm! {
282            // BEFORE: _ *proof_iter *fri_verify
283            // AFTER:  _ *indices_and_leafs
284            {entrypoint}:
285                hint fri_verify_pointer = stack[0]
286                hint proof_iter_pointer = stack[1]
287
288                // calculate number of rounds
289                dup 0                       // _ *vm_proof_iter *fri_verify *fri_verify
290                call {fri_num_rounds}       // _ *vm_proof_iter *fri_verify num_rounds
291                hint num_rounds = stack[0]
292
293                // calculate max degree of last round
294                dup 1 {&domain_length}      // _ *vm_proof_iter *fri_verify num_rounds *domain_length
295                read_mem 1 pop 1            // _ *vm_proof_iter *fri_verify num_rounds domain_length
296                hint domain_length = stack[0]
297
298                dup 2 {&expansion_factor}   // _ *vm_proof_iter *fri_verify num_rounds domain_length *expansion_factor
299                read_mem 1 pop 1            // _ *vm_proof_iter *fri_verify num_rounds domain_length expansion_factor
300                hint expansion_factor = stack[0]
301
302                pick 1 div_mod pop 1        // _ *vm_proof_iter *fri_verify num_rounds first_round_code_dimension
303
304                push 2 dup 2 pick 1 pow     // _ *vm_proof_iter *fri_verify num_rounds first_round_code_dimension (1<<num_rounds)
305
306                pick 1 div_mod pop 1        // _ *vm_proof_iter *fri_verify num_rounds first_round_code_dimension>>num_rounds
307                push -1 add                 // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree
308                hint last_round_max_degree = stack[0]
309
310                // COMMIT PHASE
311
312                // create lists for roots and alphas
313                call {new_list}             // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots
314                    hint roots: ListPointer = stack[0]
315                call {new_list}             // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas
316                    hint folding_challenges: ListPointer = stack[0]
317
318                // dequeue first Merkle root
319                dup 5                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas *proof_iter
320                call {vm_proof_iter_dequeue_next_as_merkle_root}
321                                            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas *root
322                dup 2 place 1               // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas *roots *root
323
324                {&read_digest}              // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas *roots [root]
325                call {push_digest_to_list}  // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas
326
327                // dequeue remaining roots and collect Fiat-Shamir challenges
328                dup 3                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas num_rounds
329                place 2                     // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree num_rounds *roots *alphas
330                call {dequeue_commit_phase} // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree 0 *roots *alphas
331                pick 2 pop 1                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas
332
333                // dequeue last codeword and check length
334                dup 5                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas *proof_iter
335                call {vm_proof_iter_dequeue_next_as_fri_codeword}
336                    hint last_fri_codeword: ListPointer = stack[0]
337                                            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *roots *alphas *last_codeword
338                place 2                     // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas
339
340                // compute Merkle root
341                dup 2
342                dup 0
343                call {length_of_list}       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword codeword_len
344
345                dup 6 dup 8                 // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword codeword_len num_rounds *fri_verify
346                {&domain_length}            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword codeword_len num_rounds *domain_length
347                read_mem 1
348                    hint domain_length = stack[1]
349                pop 1                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword codeword_len num_rounds domain_length
350                pick 1 push 2               // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword codeword_len domain_length num_rounds 2
351                pow                         // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword codeword_len domain_length (1<<num_rounds)
352                pick 1 div_mod pop 1        // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword codeword_len (domain_length>>num_rounds)
353                eq                          // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword (codeword_len == (domain_length>>num_rounds))
354                assert                      // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_codeword
355                call {merkle_root_from_xfes}// _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas [last_root]
356                hint merkle_root: Digest = stack[0..5]
357
358                // check against last root dequeued
359                dup 6                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas [last_root] *roots
360                dup 0                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas [last_root] *roots *roots
361                call {length_of_list}       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas [last_root] *roots num_roots
362                addi -1                     // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas [last_root] *roots num_roots-1
363                call {get_digest_from_list} // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas [last_root] [roots[-1]]
364                assert_vector               // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas [last_root]
365
366                // clean up top of stack
367                pop 5                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas
368
369                // dequeue polynomial
370                dup 6                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *vm_proof_iter
371                call {vm_proof_iter_dequeue_next_as_fri_polynomial}
372                                            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_polynomial
373                hint last_fri_polynomial: ListPointer = stack[0]
374
375
376                // QUERY PHASE
377
378                // get "A" indices and verify membership
379
380                // get index count
381                dup 6                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_polynomial *fri_verify
382                {&num_collinearity_checks}  // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_polynomial *num_indices
383                read_mem 1 pop 1            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_polynomial num_indices
384
385                // get domain length
386                dup 7                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_polynomial num_indices *fri_verify
387                {&domain_length}            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_polynomial num_indices *domain_length
388                read_mem 1 pop 1            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_polynomial num_indices domain_length
389
390                // sample "A" indices
391                call {sample_indices}       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *last_polynomial *indices
392
393                // Verify low degree of last polynomial
394                pick 1                      // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_polynomial
395                addi 1                      // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs
396
397                dup 0                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs *last_poly_coeffs
398                call {length_of_list}       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs num_coefficients_received
399
400                dup 6 addi 1                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs num_coefficients_received num_coefficients_allowed
401                lt                          // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs (num_coefficients_received>num_coefficients_allowed)
402                push 0 eq                   // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs (num_coefficients_received<=num_coefficients_allowed)
403                assert                      // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs
404
405                // check that last polynomial agrees with codeword
406                call {vm_proof_iter_sample_one_scalar}
407                                            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs *indeterminates
408                push 0 push 0 dup 3 dup 3   // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs *indeterminates 0 0 *last_poly_coeffs *indeterminates
409                {&read_xfe}                 // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs *indeterminates 0 0 *last_poly_coeffs [x]
410                call {polynomial_evaluation}// _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs *indeterminates 0 0 [poly(x)]
411                push 0 push 0 dup 12 dup 8  // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs *indeterminates 0 0 [poly(x)] 0 0 *last_codeword *indeterminates
412                {&read_xfe}                 // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs *indeterminates 0 0 [poly(x)] 0 0 *last_codeword [x]
413                call {barycentric_evaluation}
414                                            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *last_poly_coeffs *indeterminates 0 0 [poly(x)] 0 0 [codeword(x)]
415                assert_vector
416                pop 5 pop 2                 // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices
417
418                // get domain length
419                dup 6                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *fri_verify
420                {&domain_length}            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices *domain_length
421                    hint domain_length_pointer: Pointer = stack[0]
422                read_mem 1 pop 1            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas *indices dom_len
423                    hint domain_length = stack[0]
424                place 1                     // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices
425
426                // dequeue proof item as fri response
427                dup 8                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *proof_iter
428                call {vm_proof_iter_dequeue_next_as_fri_response}
429                                            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *fri_response
430
431                // assert correct length of number of leafs
432                {&field!(FriResponse::revealed_leaves)}
433                                            // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements
434                dup 1 dup 1                 // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *indices *a_elements
435                call {length_of_list}       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *indices num_leafs
436                pick 1                      // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements num_leafs *indices
437                call {length_of_list}       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements num_leafs num_indices
438                dup 1                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements num_leafs num_indices num_leafs
439                eq assert                   // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements num_leafs
440
441                /* Verify round-0 authentication paths for a-values */
442                {&verify_a_values_authentication_paths_against_input_codeword}
443                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements num_leafs
444
445                pop 1
446                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements
447
448
449                // prepare the return value:
450                // the list of opened indices and elements
451                dup 1 dup 1                 // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *indices *a_elements
452                call {zip_index_xfe}        // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs
453                hint indices_and_leafs = stack[0]
454
455                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs
456
457                /* Prepare stack for main-loop */
458                dup 4                       // .. *alphas
459                dup 10                      // .. *alphas *fri_verify
460                {&num_collinearity_checks}  // .. *alphas *num_checks
461                read_mem 1 pop 1            // .. *alphas num_checks
462                dup 12                      // .. *alphas num_checks *vm_proof_iter
463                dup 12                      // .. *alphas num_checks *vm_proof_iter *fri_verify
464                {&domain_length}            // .. *alphas num_checks *vm_proof_iter *domain_len
465                read_mem 1 pop 1            // .. *alphas num_checks *vm_proof_iter dom_len
466                push 2 dup 1 div_mod pop 1  // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len
467                dup 10                      // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots
468                dup 15                      // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots *fri_verify
469                dup 15                      // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots *fri_verify num_rounds
470                place 1                     // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds *fri_verify
471                push 0 place 1              // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r *fri_verify
472                dup 0                       // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r *fri_verify *fri_verify
473                {&domain_generator}         // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r *fri_verify *g_0
474                read_mem 1 pop 1            // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r *fri_verify g_0
475                pick 1                      // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 *fri_verify
476                {&domain_offset}            // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 *domain_offset
477                read_mem 1 pop 1            // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 offset_0
478                dup 8                       // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 offset_0 num_checks
479                call {new_list}             // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 offset_0 num_checks *c
480                write_mem 1                 // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 offset_0 (*c + 1)
481                push -1 add                 // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 offset_0 *c
482                dup 13                      // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 offset_0 *c *idx
483                dup 0                       // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 offset_0 *c *idx *idx
484                dup 14                      // .. *alphas num_checks *vm_proof_iter dom_len half_dom_len *roots num_rounds r g_0 offset_0 *c *idx *idx *a
485
486                // _ ... *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a
487                call {query_phase_main_loop}
488                // _ ... *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a
489
490                // _ ... *a_elements *revealed_indices_and_leafs *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a
491                place 15 pick 14
492
493                // _ ... *a *revealed_indices_and_leafs *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a_elements
494                pop 5
495                pop 5
496                pop 4
497                // _ ... *a *revealed_indices_and_leafs
498
499                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs
500
501                // verify membership of C elements (here called A elements) in last codeword
502                dup 6 dup 3                 // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs *last_codeword *indices
503
504                dup 11
505                {&domain_length}
506                read_mem 1 pop 1
507                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs *last_codeword *indices dom_len_round_0
508
509                dup 11
510                push 2
511                pow
512                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs *last_codeword *indices dom_len_round_0 (1 << num_rounds)
513
514                pick 1
515                div_mod
516                pop 1
517                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs *last_codeword *indices (dom_len_round_0 / (1 << num_rounds))
518                // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs *last_codeword *indices dom_len_round_last
519
520                pick 1
521                call {map_reduce_indices}
522                place 1
523                pop 1
524
525                // TODO: Get rid of the next two higher-order function calls
526                dup 3                        // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs *last_codeword *c_indices *c_elements
527                call {zip_index_xfe}         // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs *last_codeword *c_indices_and_elements
528                call {map_assert_membership} // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs *last_codeword *c_indices_and_elements
529
530                // clean up stack
531                pop 2                       // _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree *last_codeword *roots *alphas dom_len *indices *a_elements *revealed_indices_and_leafs
532                place 10 pop 5 pop 5        // _ *revealed_indices_and_leafs
533
534                return
535
536            // START    : _ g_r offset_r *c_last_elem_first_word *idx_end_condition *idx_last_word *a_last_word    *b_last_word    [alphas[r]]
537            // INVARIANT: _ g_r offset_r *c[n]_first_word        *idx_end_condition *idx[n]        *a[n]_last_word *b[n]_last_word [alphas[r]]
538            {compute_c_values_loop}:
539                // Strategy for one iteration:
540                // 1. Read `a_y`
541                // 2. Calculate `a_x`
542                // 3. Read `b_y`
543                // 4. Calculate `[a_y- b_y]`, preserve [a_y]
544                // 5. Calculate `-b_x = a_x`
545                // 6. Calculate `1 / (a_x - b_x)` while preserving `a_x`
546                // 7. Calculate `(a_y - b_y) / (a_x - b_x)`
547                // 8: Read `[c_x]`
548                // 9. Calculate `c_x - a_x`
549                // 10. Calculate final `c_y`
550                // 11. Write c_y to *c_elem
551                hint alpha_r: Xfe          = stack[0..3]
552                hint b_values: Pointer     = stack[3]
553                hint a_values: Pointer     = stack[4]
554                hint idx: Pointer          = stack[5]
555                hint idx_end_cond: Pointer = stack[6]
556                hint c_values: Pointer     = stack[7]
557                hint offset_r: Bfe         = stack[8]
558                hint g_r: Bfe              = stack[9]
559
560                // _ g_r offset_r *c *idx_end_condition *idx *a *b [alphas[r]]
561
562                // 1: Read `a_y`
563                pick 4
564                read_mem {EXTENSION_DEGREE}
565                place 7
566                // _ g_r offset_r *c *idx_end_condition *idx *a' *b [alphas[r]] [a_y]
567
568                // 2: Calculate `a_x`: $g_r^{a_index} * offset_r$
569                pick 8
570                read_mem 1
571                place 9
572                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b [alphas[r]] [a_y] a_index
573
574                dup 13
575                pow
576                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b [alphas[r]] [a_y] (g_r**a_index)
577
578                dup 12
579                mul
580                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b [alphas[r]] [a_y] (g_r**a_index * offset_r)
581                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b [alphas[r]] [a_y] a_x
582
583                // 3: Read `b_y`
584                pick 7
585                read_mem {EXTENSION_DEGREE}
586                place 10
587                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] a_x [b_y]
588
589                // 4: Calculate [a_y - b_y], preserve [a_y]
590                push -1
591                xb_mul
592                dup 6
593                dup 6
594                dup 6
595                xx_add
596                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] a_x [a_y - b_y]
597
598                // 5: Calculate `-b_x = a_x`
599                dup 3
600                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] a_x [a_y - b_y] (-b_x)
601
602                // 6: Calculate `1 / (a_x - b_x)`, preserve `a_x`
603                dup 4
604                add
605                invert
606                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] a_x [a_y - b_y] (1/(a_x-b_x))
607
608                // 7: Calculate `(a_y - b_y) / (a_x - b_x)`
609                xb_mul
610                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] a_x [(a_y - b_y)/(a_x - b_x)]
611
612                // 8: Read `[c_x] = alphas[r]`
613                dup 9
614                dup 9
615                dup 9
616                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] a_x [(a_y - b_y)/(a_x - b_x)] [alphas[r]]
617                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] a_x [(a_y - b_y)/(a_x - b_x)] [c_x]
618
619                // 9: Calculate `c_x - a_x`
620                pick 6
621                push -1
622                mul
623                add
624                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] [(a_y - b_y)/(a_x - b_x)] [c_x - a_x]
625
626                // 10. Calculate final `c_y`
627                xx_mul
628                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y] [(a_y - b_y)/(a_x - b_x) * (c_x - a_x)]
629
630                xx_add
631                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [a_y + (a_y - b_y)/(a_x - b_x) * (c_x - a_x)]
632                // _ g_r offset_r *c *idx_end_condition *idx' *a' *b' [alphas[r]] [c_y]
633
634                // 11. Write `c_y` to `*c` list
635                pick 10
636                write_mem {EXTENSION_DEGREE}
637                addi  {-2 * EXTENSION_DEGREE as i32}
638                place 7
639                // _ g_r offset_r *c' *idx_end_condition *idx' *a' *b' [alphas[r]]
640
641                recurse_or_return
642
643            // BEFORE: _ *vm_proof_iter *fri_verify num_rounds last_round_max_degree | num_rounds *roots *alphas
644            // AFTER:  _ ... | 0 *roots *alphas
645            {dequeue_commit_phase}:
646
647                // return if done
648                dup 2       // _ num_rounds *roots *alphas num_rounds
649                push 0 eq   // _ num_rounds *roots *alphas num_rounds==0
650                skiz return
651
652                // decrement round number
653                pick 2      // _ *alphas *roots num_rounds
654                addi -1     // _ *alphas *roots num_rounds-1
655                place 2     // _ num_rounds-1 *roots *alphas
656
657                // sample scalar
658                call {vm_proof_iter_sample_one_scalar}
659                            // _ num_rounds-1 *roots *alphas *scalars
660                dup 1       // _ num_rounds-1 *roots *alphas *scalars *alphas
661                pick 1      // _ num_rounds-1 *roots *alphas *alphas *scalars
662                {&read_xfe}
663                            // _ num_rounds-1 *roots *alphas *alphas [scalars[0]]
664
665                call {push_scalar}
666                            // _ num_rounds-1 *roots *alphas
667
668                // dequeue Merkle root
669                pick 1      // _ num_rounds-1 *alphas *roots
670                dup 6       // _ num_rounds-1 *alphas *roots *vm_proof_iter
671
672                            // _ num_rounds-1 *alphas *roots *proof_iter
673                call {vm_proof_iter_dequeue_next_as_merkle_root}
674                                            // _ num_rounds-1 *alphas *roots *root
675                dup 1 pick 1                // _ num_rounds-1 *alphas *roots *roots *root
676                {&read_digest}              // _ num_rounds-1 *alphas *roots *roots [root]
677                call {push_digest_to_list}  // _ num_rounds-1 *alphas *roots
678                place 1                     // _ num_rounds-1 *roots *alphas
679                recurse
680
681            // INVARIANT:  _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a
682            {query_phase_main_loop}:
683                hint a_pointer          = stack[0]
684                hint idx_pointer        = stack[1]
685                hint idx_pointer        = stack[2]
686                hint c_pointer          = stack[3]
687                hint offset_r           = stack[4]
688                hint g_r                = stack[5]
689                hint r                  = stack[6]
690                hint num_rounds         = stack[7]
691                hint roots_pointer      = stack[8]
692                hint half_dom_len       = stack[9]
693                hint dom_len            = stack[10]
694                hint proof_iter_pointer = stack[11]
695                hint num_checks         = stack[12]
696                hint alphas_pointer     = stack[13]
697
698                /* Check end condition, r == num_rounds */
699                dup 7
700                dup 7
701                eq
702                skiz
703                    return
704                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a
705
706                /* Dequeue FRI response and get "B" elements */
707                dup 11
708                call {vm_proof_iter_dequeue_next_as_fri_response}
709                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a *fri_response
710
711                {&field!(FriResponse::revealed_leaves)}
712                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a *b_elements
713                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a *b
714
715                /* Verify that *b_elements has expected length */
716                read_mem 1
717                addi 1
718                place 1
719                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a *b b_len
720
721                dup 14
722                eq
723                assert // b_len == num_checks
724                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a *b
725
726                // TODO: Verify *b_elements against relevant Merkle-root
727
728                // There are three vectors to read from in the C-values calculating loop:
729                // `*indices`, `*a_elements`, `*b_elements`.
730                // There is one list to write to:
731                // `*c_elements`
732                // This indicates it is beneficial to traverse the lists from highest to lowest
733                // address, giving three of the four pointer updates for free. This requires
734                // updating the corresponding pointers to point to their respecive list's last
735                // element now.
736
737                /* Update `*idx` to point to last element */
738                pick 2
739                dup 13
740                add
741                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *a *b *idx_last
742
743                pick 2
744                dup 13
745                push {EXTENSION_DEGREE}
746                mul
747                add
748                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *b *idx_last *a_last
749
750                /* Update `*b_elements` to point to last element */
751                pick 2
752                dup 13
753                push {EXTENSION_DEGREE}
754                mul
755                add
756                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx_last *a_last *b_last
757
758                {&verify_b_values_authentication_paths_in_main_loop}
759                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx_last *a_last *b_last
760
761                /* Update `*c` to point to last element */
762                pick 4
763                dup 13
764                push {EXTENSION_DEGREE}
765                mul
766                add
767                addi {-((EXTENSION_DEGREE - 1) as isize)}
768                place 4
769                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c_last *idx *idx_last *a_last *b_last
770
771                /* Put round-challenge on top of stack for the c-values loop */
772                dup 14
773                dup 8
774                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c_last *idx *idx_last *a_last *b_last *alphas r
775
776                call {get_xfe_from_list}
777                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c_last *idx *idx_last *a_last *b_last [alphas[r]]
778
779                call {compute_c_values_loop}
780                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r (*c - 2) *idx *idx *a *b [alphas[r]]
781
782                pop {EXTENSION_DEGREE + 1}
783                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r (*c - 2) *idx *idx *a
784
785                pick 3
786                addi {EXTENSION_DEGREE - 1}
787                place 3
788                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r g_r offset_r *c *idx *idx *a
789
790                /* Update round parameters */
791                /* Update round index r' = r + 1 */
792                pick 6
793                addi 1
794                place 6
795                // _ *alphas num_checks *proof_iter dom_len half_dom_len *roots num_rounds r' g_r offset_r *c *idx *idx *a
796
797                /* dom_len' = half_dom_len */
798                pick 10 pop 1
799                    hint dom_len = stack[9]
800                // _ *alphas num_checks *proof_iter dom_len' *roots num_rounds r' g_r offset_r *c *idx *idx *a
801
802                /* half_dom_len' = half_dom_len / 2 */
803                dup 9
804                log_2_floor
805                addi -1
806                push 2 pow
807                    hint half_dom_len = stack[0]
808                place 9
809                // _ *alphas num_checks *proof_iter dom_len' half_dom_len' *roots num_rounds r' g_r offset_r *c *idx *idx *a
810
811                /* g_r' = g_r ** 2 */
812                pick 5
813                dup 0
814                mul
815                place 5
816                // _ *alphas num_checks *proof_iter dom_len' half_dom_len' *roots num_rounds r' g_r' offset_r *c *idx *idx *a
817
818                /* offset_r' = offset_r ** 2 */
819                pick 4
820                dup 0
821                mul
822                place 4
823                // _ *alphas num_checks *proof_iter dom_len' half_dom_len' *roots num_rounds r' g_r' offset_r' *c *idx *idx *a
824
825                /* a' = c, overwriting already happened in the c-values loop, but for r zero, this is necessary */
826                pop 1
827                dup 2
828                // _ *alphas num_checks *proof_iter dom_len' half_dom_len' *roots num_rounds r' g_r' offset_r' *c *idx *idx *a'
829
830                recurse
831        }
832    }
833}
834
835impl FriVerify {
836    /// Return a dummy FRI verify structure that can be used when an instance is not needed but the
837    /// compiler thinks it is. Is probably only needed when the FRI snippet is used in an external
838    /// test.
839    pub fn dummy() -> Self {
840        Self {
841            expansion_factor: 0,
842            num_collinearity_checks: 0,
843            domain_length: 0,
844            domain_offset: BFieldElement::zero(),
845            domain_generator: BFieldElement::zero(),
846        }
847    }
848
849    pub fn extract_digests_required_for_proving(&self, proof_stream: &ProofStream) -> Vec<Digest> {
850        let mut digests = vec![];
851        self.inner_verify(&mut proof_stream.clone(), &mut digests)
852            .unwrap();
853        digests
854    }
855
856    pub fn to_fri(self) -> Fri {
857        let fri_domain = ArithmeticDomain::of_length(self.domain_length as usize)
858            .unwrap()
859            .with_offset(self.domain_offset);
860        let maybe_fri = Fri::new(
861            fri_domain,
862            self.expansion_factor as usize,
863            self.num_collinearity_checks as usize,
864        );
865
866        maybe_fri.unwrap()
867    }
868
869    /// Verify the FRI proof embedded in the proof stream. This function expands the list
870    /// `nondeterministic_digests` with the digests of the individual authentication paths
871    /// obtained from reduplicating the authentication structures that live in the proof
872    /// stream.
873    fn inner_verify(
874        &self,
875        proof_stream: &mut ProofStream,
876        nondeterministic_digests: &mut Vec<Digest>,
877    ) -> anyhow::Result<Vec<(u32, XFieldElement)>> {
878        let mut num_nondeterministic_digests_read = 0;
879
880        // calculate number of rounds
881        let num_rounds = self.num_rounds();
882
883        // Extract all roots and calculate alpha based on Fiat-Shamir challenge
884        let mut roots = Vec::with_capacity(num_rounds);
885        let mut alphas = Vec::with_capacity(num_rounds);
886
887        let first_root = proof_stream
888            .dequeue()
889            .unwrap()
890            .try_into_merkle_root()
891            .unwrap();
892        roots.push(first_root);
893
894        for _round in 0..num_rounds {
895            // get a challenge from the verifier
896            let alpha = proof_stream.sample_scalars(1)[0];
897            alphas.push(alpha);
898
899            // get a commitment from the prover
900            let root = proof_stream
901                .dequeue()
902                .unwrap()
903                .try_into_merkle_root()
904                .unwrap();
905            roots.push(root);
906        }
907
908        // Extract last codeword and last polynomial
909        let last_codeword = proof_stream
910            .dequeue()
911            .unwrap()
912            .try_into_fri_codeword()
913            .unwrap();
914        assert_eq!(
915            last_codeword.len(),
916            self.domain_length as usize >> self.num_rounds()
917        );
918
919        let last_polynomial = proof_stream
920            .dequeue()
921            .unwrap()
922            .try_into_fri_polynomial()
923            .unwrap();
924
925        // QUERY PHASE
926
927        // query step 0: get "A" indices and verify set membership of corresponding values.
928        let domain_length = self.domain_length as usize;
929        let num_collinearity_check = self.num_collinearity_checks as usize;
930        let mut a_indices = proof_stream.sample_indices(domain_length, num_collinearity_check);
931
932        let tree_height = self.domain_length.ilog2();
933        let fri_response = proof_stream
934            .dequeue()
935            .unwrap()
936            .try_into_fri_response()
937            .unwrap();
938        assert_eq!(a_indices.len(), fri_response.revealed_leaves.len());
939        let mut a_values = fri_response.revealed_leaves;
940
941        let leaf_digests = Self::map_convert_xfe_to_digest(&a_values);
942        let indexed_a_leaves = a_indices.iter().copied().zip_eq(leaf_digests).collect_vec();
943
944        // Check if last codeword matches the given root
945        let codeword_digests = Self::map_convert_xfe_to_digest(&last_codeword);
946        let mt: MerkleTree = MerkleTree::par_new(&codeword_digests).unwrap();
947        let last_codeword_merkle_root = mt.root();
948
949        let last_root = roots.last().unwrap();
950        if *last_root != last_codeword_merkle_root {
951            bail!(FriValidationError::BadMerkleRootForLastCodeword);
952        }
953
954        // Verify that last codeword is of sufficiently low degree
955        let barycentric_indeterminate = proof_stream.sample_scalars(1)[0];
956        let barycentric_evaluation =
957            barycentric_evaluate(&last_codeword, barycentric_indeterminate);
958        let horner_evaluation = last_polynomial.evaluate(barycentric_indeterminate);
959
960        if barycentric_evaluation != horner_evaluation {
961            bail!(FriValidationError::LastRoundPolynomialEvaluationMismatch)
962        }
963
964        if last_polynomial.degree() > self.last_round_max_degree() as isize {
965            bail!(FriValidationError::LastRoundPolynomialHasTooHighDegree)
966        }
967
968        // reduplicate authentication structures if necessary
969        if num_nondeterministic_digests_read >= nondeterministic_digests.len() {
970            let inclusion_proof = MerkleTreeInclusionProof {
971                tree_height,
972                indexed_leafs: indexed_a_leaves.clone(),
973                authentication_structure: fri_response.auth_structure,
974            };
975
976            // sanity check: the authentication structure was valid, right?
977            assert!(inclusion_proof.clone().verify(roots[0]));
978            let reduplicated_authentication_paths = inclusion_proof.into_authentication_paths()?;
979            nondeterministic_digests.extend(
980                reduplicated_authentication_paths
981                    .into_iter()
982                    .rev()
983                    .flatten(),
984            );
985        }
986
987        // verify authentication paths for A leafs
988        for indexed_leaf in indexed_a_leaves.iter().rev() {
989            let auth_path_end = num_nondeterministic_digests_read
990                + usize::try_from(tree_height).expect(U32_TO_USIZE_ERR);
991            let authentication_path =
992                &nondeterministic_digests[num_nondeterministic_digests_read..auth_path_end];
993            num_nondeterministic_digests_read = auth_path_end;
994            let inclusion_proof = MerkleTreeInclusionProof {
995                tree_height,
996                indexed_leafs: vec![*indexed_leaf],
997                authentication_structure: authentication_path.to_vec(),
998            };
999            assert!(inclusion_proof.verify(roots[0]));
1000        }
1001
1002        // save indices and revealed leafs of first round's codeword for returning
1003        let revealed_indices_and_elements_round_0 = a_indices
1004            .iter()
1005            .map(|&idx| idx as u32)
1006            .zip_eq(a_values.iter().copied())
1007            .collect_vec();
1008
1009        // set up "B" for offsetting inside loop.  Note that "B" and "A" indices can be calculated
1010        // from each other.
1011        let mut b_indices = a_indices.clone();
1012        let mut current_domain_len = self.domain_length as usize;
1013        let mut current_tree_height = tree_height;
1014
1015        // query step 1:  loop over FRI rounds, verify "B"s, compute values for "C"s
1016        for r in 0..num_rounds {
1017            // get "B" indices and verify set membership of corresponding values
1018            b_indices = b_indices
1019                .iter()
1020                .map(|x| (x + current_domain_len / 2) % current_domain_len)
1021                .collect();
1022            let fri_response = proof_stream
1023                .dequeue()
1024                .unwrap()
1025                .try_into_fri_response()
1026                .unwrap();
1027            let b_values = fri_response.revealed_leaves;
1028
1029            let leaf_digests = Self::map_convert_xfe_to_digest(&b_values);
1030            let indexed_b_leaves = b_indices.iter().copied().zip_eq(leaf_digests).collect_vec();
1031
1032            // reduplicate authentication structures if necessary
1033            if num_nondeterministic_digests_read >= nondeterministic_digests.len() {
1034                let inclusion_proof = MerkleTreeInclusionProof {
1035                    tree_height: current_tree_height,
1036                    indexed_leafs: indexed_b_leaves.clone(),
1037                    authentication_structure: fri_response.auth_structure,
1038                };
1039
1040                // sanity check: the auth structure was valid, right?
1041                assert!(inclusion_proof.clone().verify(roots[r]));
1042                let reduplicated_authentication_paths =
1043                    inclusion_proof.into_authentication_paths()?;
1044                nondeterministic_digests.extend(
1045                    reduplicated_authentication_paths
1046                        .into_iter()
1047                        .rev()
1048                        .flatten(),
1049                );
1050            }
1051
1052            // verify authentication paths for B leafs
1053            for indexed_leaf in indexed_b_leaves.iter().rev() {
1054                let auth_path_end = num_nondeterministic_digests_read
1055                    + usize::try_from(current_tree_height).expect(U32_TO_USIZE_ERR);
1056                let authentication_path =
1057                    &nondeterministic_digests[num_nondeterministic_digests_read..auth_path_end];
1058                num_nondeterministic_digests_read = auth_path_end;
1059                let inclusion_proof = MerkleTreeInclusionProof {
1060                    tree_height: current_tree_height,
1061                    indexed_leafs: vec![*indexed_leaf],
1062                    authentication_structure: authentication_path.to_vec(),
1063                };
1064                if !inclusion_proof.verify(roots[r]) {
1065                    bail!(FriValidationError::BadMerkleAuthenticationPath);
1066                }
1067            }
1068
1069            debug_assert_eq!(self.num_collinearity_checks, a_indices.len() as u32);
1070            debug_assert_eq!(self.num_collinearity_checks, b_indices.len() as u32);
1071            debug_assert_eq!(self.num_collinearity_checks, a_values.len() as u32);
1072            debug_assert_eq!(self.num_collinearity_checks, b_values.len() as u32);
1073
1074            // compute "C" indices and values for next round from "A" and "B" of current round
1075            current_domain_len /= 2;
1076            current_tree_height -= 1;
1077            let c_indices = a_indices.iter().map(|x| x % current_domain_len).collect();
1078            let c_values = (0..self.num_collinearity_checks as usize)
1079                .map(|i| {
1080                    let a_x = self.get_collinearity_check_x(a_indices[i] as u32, r);
1081                    let b_x = self.get_collinearity_check_x(b_indices[i] as u32, r);
1082                    Polynomial::<XFieldElement>::get_colinear_y(
1083                        (a_x, a_values[i]),
1084                        (b_x, b_values[i]),
1085                        alphas[r],
1086                    )
1087                })
1088                .collect();
1089
1090            // next rounds "A"s correspond to current rounds "C"s
1091            a_indices = c_indices;
1092            a_values = c_values;
1093        }
1094
1095        // Finally compare "C" values (which are named "A" values in this enclosing scope) with
1096        // last codeword from the proofstream.
1097        a_indices = a_indices.iter().map(|x| x % current_domain_len).collect();
1098        if !(0..self.num_collinearity_checks as usize)
1099            .all(|i| last_codeword[a_indices[i]] == a_values[i])
1100        {
1101            bail!(FriValidationError::LastCodewordMismatch);
1102        }
1103
1104        Ok(revealed_indices_and_elements_round_0)
1105    }
1106
1107    /// Computes the number of rounds
1108    pub fn num_rounds(&self) -> usize {
1109        let first_round_code_dimension = self.first_round_max_degree() + 1;
1110        let max_num_rounds = first_round_code_dimension.next_power_of_two().ilog2();
1111
1112        // Skip rounds for which Merkle tree verification cost exceeds arithmetic cost,
1113        // because more than half the codeword's locations are queried.
1114        let num_rounds_checking_all_locations = self.num_collinearity_checks.ilog2() as u64;
1115        let num_rounds_checking_most_locations = num_rounds_checking_all_locations + 1;
1116
1117        (max_num_rounds as u64).saturating_sub(num_rounds_checking_most_locations) as usize
1118    }
1119
1120    /// Computes the max degree of the codeword interpolant after the last round
1121    pub fn last_round_max_degree(&self) -> usize {
1122        self.first_round_max_degree() >> self.num_rounds()
1123    }
1124
1125    /// Computes the max degree of the very first codeword interpolant
1126    pub fn first_round_max_degree(&self) -> usize {
1127        assert!(self.domain_length >= self.expansion_factor);
1128        (self.domain_length / self.expansion_factor) as usize - 1
1129    }
1130
1131    /// Compute a new list containing the `XFieldElement`s of the given list, but lifted
1132    /// to the type `Digest` via padding with 2 zeros.
1133    fn map_convert_xfe_to_digest(xfes: &[XFieldElement]) -> Vec<Digest> {
1134        xfes.iter().map(|x| (*x).into()).collect()
1135    }
1136
1137    /// Get the x-coordinate of an A or B point in a collinearity check, given the point's
1138    /// index and the round number in which the check takes place. In Triton VM, this
1139    /// method is called `get_evaluation_argument`.
1140    pub fn get_collinearity_check_x(&self, idx: u32, round: usize) -> XFieldElement {
1141        let domain_value = self.domain_offset * self.domain_generator.mod_pow_u32(idx);
1142        let round_exponent = 2u32.pow(round as u32);
1143        let evaluation_argument = domain_value.mod_pow_u32(round_exponent);
1144
1145        evaluation_argument.lift()
1146    }
1147}
1148
1149#[cfg(test)]
1150mod tests {
1151    use std::collections::HashSet;
1152    use std::panic::catch_unwind;
1153
1154    use num_traits::Zero;
1155    use proptest::collection::vec;
1156    use rayon::prelude::*;
1157    use triton_vm::proof_item::ProofItem;
1158    use twenty_first::math::ntt::ntt;
1159    use twenty_first::util_types::sponge::Sponge;
1160
1161    use super::*;
1162    use crate::empty_stack;
1163    use crate::memory::dyn_malloc::DYN_MALLOC_FIRST_ADDRESS;
1164    use crate::structure::tasm_object::decode_from_memory_with_size;
1165    use crate::test_helpers::rust_final_state;
1166    use crate::test_helpers::tasm_final_state;
1167    use crate::test_helpers::verify_sponge_equivalence;
1168    use crate::test_helpers::verify_stack_growth;
1169    use crate::test_prelude::*;
1170
1171    impl FriVerify {
1172        pub fn new(
1173            offset: BFieldElement,
1174            domain_length: u32,
1175            expansion_factor: u32,
1176            num_collinearity_checks: u32,
1177        ) -> Self {
1178            let domain = ArithmeticDomain::of_length(domain_length as usize)
1179                .unwrap()
1180                .with_offset(offset);
1181            Self {
1182                expansion_factor,
1183                num_collinearity_checks,
1184                domain_length,
1185                domain_offset: domain.offset,
1186                domain_generator: domain.generator,
1187            }
1188        }
1189
1190        pub fn call(
1191            &self,
1192            proof_stream: &mut ProofStream,
1193            nondeterminism: &NonDeterminism,
1194        ) -> Vec<(u32, XFieldElement)> {
1195            self.inner_verify(proof_stream, &mut nondeterminism.digests.clone())
1196                .unwrap()
1197        }
1198
1199        /// Generate a proof, embedded in a proof stream.
1200        pub fn pseudorandom_fri_proof_stream(&self, seed: [u8; 32]) -> ProofStream {
1201            let max_degree = self.first_round_max_degree();
1202            let mut rng = StdRng::from_seed(seed);
1203            let polynomial_coefficients = (0..=max_degree).map(|_| rng.random()).collect_vec();
1204
1205            let mut codeword = polynomial_coefficients;
1206            codeword.resize(self.domain_length as usize, XFieldElement::zero());
1207            ntt(&mut codeword);
1208
1209            let mut proof_stream = ProofStream::new();
1210            let fri = self.to_fri();
1211            fri.prove(&codeword, &mut proof_stream).unwrap();
1212
1213            ProofStream {
1214                items: proof_stream.items,
1215                items_index: 0,
1216                sponge: Tip5::init(),
1217            }
1218        }
1219    }
1220
1221    impl FriSnippet {
1222        /// Test helper – panics if verification fails.
1223        fn verify_from_proof_with_digests(&self, proof: Vec<BFieldElement>, digests: Vec<Digest>) {
1224            let items = *Vec::<ProofItem>::decode(&proof).unwrap();
1225            let proof_stream = ProofStream {
1226                items,
1227                items_index: 0,
1228                sponge: Tip5::init(),
1229            };
1230            let (stack, nondeterminism) =
1231                self.set_up_stack_and_non_determinism_using_digests(proof_stream, digests);
1232
1233            let shadowed_snippet = ShadowedProcedure::new(self.to_owned());
1234            let _tasm = tasm_final_state(
1235                &shadowed_snippet,
1236                &stack,
1237                &[],
1238                nondeterminism,
1239                &Some(Tip5::init()),
1240            );
1241        }
1242
1243        fn set_up_stack_and_non_determinism(
1244            &self,
1245            proof_stream: ProofStream,
1246        ) -> (Vec<BFieldElement>, NonDeterminism) {
1247            let digests = self
1248                .test_instance
1249                .extract_digests_required_for_proving(&proof_stream);
1250            self.set_up_stack_and_non_determinism_using_digests(proof_stream, digests)
1251        }
1252
1253        fn set_up_stack_and_non_determinism_using_digests(
1254            &self,
1255            proof_stream: ProofStream,
1256            digests: Vec<Digest>,
1257        ) -> (Vec<BFieldElement>, NonDeterminism) {
1258            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
1259            let vm_proof_iter_pointer = BFieldElement::zero();
1260            // uses highly specific knowledge about `BFieldCodec`
1261            let proof_iter_current_item_pointer = vm_proof_iter_pointer + BFieldElement::new(2);
1262
1263            let fri_verify_pointer =
1264                encode_to_memory(&mut memory, vm_proof_iter_pointer, &proof_stream);
1265            let proof_iter_pointer =
1266                encode_to_memory(&mut memory, fri_verify_pointer, &self.test_instance);
1267            encode_to_memory(
1268                &mut memory,
1269                proof_iter_pointer,
1270                &proof_iter_current_item_pointer,
1271            );
1272            let nondeterminism = NonDeterminism::default()
1273                .with_ram(memory)
1274                .with_digests(digests);
1275
1276            let mut stack = empty_stack();
1277            stack.push(proof_iter_pointer);
1278            stack.push(fri_verify_pointer);
1279
1280            (stack, nondeterminism)
1281        }
1282    }
1283
1284    impl Procedure for FriSnippet {
1285        fn rust_shadow(
1286            &self,
1287            stack: &mut Vec<BFieldElement>,
1288            memory: &mut HashMap<BFieldElement, BFieldElement>,
1289            nondeterminism: &NonDeterminism,
1290            _public_input: &[BFieldElement],
1291            sponge: &mut Option<Tip5>,
1292        ) -> Vec<BFieldElement> {
1293            let fri_pointer = stack.pop().unwrap();
1294            let fri_verify = *FriVerify::decode_from_memory(memory, fri_pointer).unwrap();
1295            assert_eq!(fri_verify, self.test_instance);
1296
1297            let proof_iter_pointer = stack.pop().unwrap();
1298
1299            // uses highly specific knowledge about `BFieldCodec` and the test setup
1300            let proof_stream_pointer =
1301                *memory.get(&proof_iter_pointer).unwrap() - BFieldElement::new(2);
1302            // todo: hack using local knowledge: `fri_verify` lives directly after `vm_proof_iter`.
1303            //  Replace this once we have a better way to decode.
1304            let proof_stream_size = (fri_pointer - proof_stream_pointer).value() as usize;
1305            let mut proof_stream = decode_from_memory_with_size::<ProofStream>(
1306                memory,
1307                proof_stream_pointer,
1308                proof_stream_size,
1309            )
1310            .unwrap();
1311
1312            let revealed_indices_and_elements = fri_verify.call(&mut proof_stream, nondeterminism);
1313
1314            let indices_and_leafs_pointer = DYN_MALLOC_FIRST_ADDRESS;
1315            encode_to_memory(
1316                memory,
1317                indices_and_leafs_pointer,
1318                &revealed_indices_and_elements,
1319            );
1320
1321            stack.push(indices_and_leafs_pointer);
1322
1323            *sponge = Some(proof_stream.sponge);
1324
1325            // no standard output
1326            vec![]
1327        }
1328
1329        fn pseudorandom_initial_state(
1330            &self,
1331            seed: [u8; 32],
1332            _bench_case: Option<BenchmarkCase>,
1333        ) -> ProcedureInitialState {
1334            let vm_proof_iter = self.test_instance.pseudorandom_fri_proof_stream(seed);
1335            let (stack, nondeterminism) = self.set_up_stack_and_non_determinism(vm_proof_iter);
1336
1337            ProcedureInitialState {
1338                stack,
1339                nondeterminism,
1340                public_input: vec![],
1341                sponge: Some(Tip5::init()),
1342            }
1343        }
1344    }
1345
1346    #[derive(Debug, Clone, test_strategy::Arbitrary)]
1347    struct ArbitraryFriVerify {
1348        #[strategy(arb())]
1349        #[filter(!#offset.is_zero())]
1350        offset: BFieldElement,
1351
1352        #[strategy(0_u32..12)]
1353        domain_length_exponent: u32,
1354
1355        #[strategy(0_u32..6)]
1356        expansion_factor_exponent: u32,
1357
1358        #[strategy(1_u32..320)]
1359        num_collinearity_checks: u32,
1360    }
1361
1362    impl ArbitraryFriVerify {
1363        fn fri_verify(&self) -> FriVerify {
1364            let expansion_factor = 2 << self.expansion_factor_exponent;
1365            let domain_length = expansion_factor * (1 << self.domain_length_exponent);
1366            FriVerify::new(
1367                self.offset,
1368                domain_length,
1369                expansion_factor,
1370                self.num_collinearity_checks,
1371            )
1372        }
1373    }
1374
1375    #[derive(Debug, Clone, test_strategy::Arbitrary)]
1376    pub(super) struct TestCase {
1377        #[strategy(any::<ArbitraryFriVerify>().prop_map(|x| x.fri_verify()))]
1378        pub(super) fri_verify: FriVerify,
1379
1380        #[strategy(vec(arb(), #fri_verify.first_round_max_degree()))]
1381        polynomial_coefficients: Vec<XFieldElement>,
1382    }
1383
1384    impl TestCase {
1385        fn thirty_two_coefficients() -> [u64; 32] {
1386            [
1387                92, 42, 91, 59, 86, 64, 5, 64, 74, 53, 54, 68, 54, 23, 24, 58, 15, 44, 33, 31, 38,
1388                97, 25, 69, 11, 67, 66, 33, 37, 58, 43, 14,
1389            ]
1390        }
1391
1392        /// A test case with no FRI rounds.
1393        fn tiny_case() -> Self {
1394            let fri_verify = FriVerify::new(bfe!(1), 2, 2, 1);
1395            assert_eq!(0, fri_verify.num_rounds());
1396            assert_eq!(0, fri_verify.last_round_max_degree());
1397
1398            Self {
1399                fri_verify,
1400                polynomial_coefficients: vec![xfe!([42; 3])],
1401            }
1402        }
1403
1404        /// A test case with 2 FRI rounds.
1405        fn small_case() -> Self {
1406            let fri_verify = FriVerify::new(bfe!(1), 64, 2, 7);
1407            assert_eq!(2, fri_verify.num_rounds(), "test case must be meaningful");
1408
1409            let coefficients = Self::thirty_two_coefficients();
1410            assert_eq!(fri_verify.first_round_max_degree() + 1, coefficients.len());
1411
1412            Self {
1413                fri_verify,
1414                polynomial_coefficients: coefficients.map(|n| xfe!([n; 3])).to_vec(),
1415            }
1416        }
1417
1418        /// A test case resembling that which will be run by the STARK verifier
1419        pub(super) fn recursive_case(
1420            inner_padded_height: usize,
1421            log2_expansion_factor: usize,
1422        ) -> Self {
1423            const SECURITY_LEVEL: usize = 160;
1424            let stark = Stark::new(SECURITY_LEVEL, log2_expansion_factor);
1425
1426            let fri_verify: FriVerify = stark.fri(inner_padded_height).unwrap().into();
1427
1428            let coefficients = Self::thirty_two_coefficients();
1429
1430            Self {
1431                fri_verify,
1432                polynomial_coefficients: coefficients.map(|n| xfe!([n; 3])).to_vec(),
1433            }
1434        }
1435
1436        fn fri(&self) -> Fri {
1437            self.fri_verify.to_fri()
1438        }
1439
1440        fn codeword(&self) -> Vec<XFieldElement> {
1441            let domain_length = self.fri_verify.domain_length;
1442            let mut codeword = self.polynomial_coefficients.clone();
1443            codeword.resize(domain_length as usize, XFieldElement::zero());
1444
1445            ntt(&mut codeword);
1446            codeword
1447        }
1448
1449        fn proof_items(&self) -> Vec<ProofItem> {
1450            let fri = self.fri();
1451            let codeword = self.codeword();
1452            let mut proof_stream = ProofStream::new();
1453            fri.prove(&codeword, &mut proof_stream).unwrap();
1454
1455            proof_stream.items
1456        }
1457
1458        fn proof_stream(&self) -> ProofStream {
1459            ProofStream {
1460                items: self.proof_items(),
1461                items_index: 0,
1462                sponge: Tip5::init(),
1463            }
1464        }
1465
1466        pub(super) fn initial_state(&self) -> ProcedureInitialState {
1467            let fri_verify = self.fri_verify;
1468            let proof_stream = self.proof_stream();
1469            let snippet = FriSnippet {
1470                test_instance: fri_verify,
1471            };
1472            let (stack, nondeterminism) = snippet.set_up_stack_and_non_determinism(proof_stream);
1473
1474            ProcedureInitialState {
1475                stack,
1476                nondeterminism,
1477                public_input: vec![],
1478                sponge: Some(Tip5::init()),
1479            }
1480        }
1481    }
1482
1483    #[test]
1484    fn assert_behavioral_equivalence_of_tiny_fri_instance() {
1485        assert_behavioral_equivalence_of_fris(TestCase::tiny_case());
1486    }
1487
1488    /// Unfortunately, we cannot call the built-in test, _i.e._,
1489    /// `ShadowedProcedure::new(procedure).test()`: that test checks that the rust and tasm stacks
1490    /// are left in identical states. While they both contain a pointer to the same object, this
1491    /// object lives in a different (and difficult to predict) location in memory. This means the
1492    /// pointers are different. In the end, we don't care about the pointers. Therefore, instead of
1493    /// complete stack equivalence, we check equivalence of the pointed-to objects.
1494    fn assert_behavioral_equivalence_of_fris(test_case: TestCase) {
1495        let ProcedureInitialState {
1496            stack: initial_stack,
1497            nondeterminism,
1498            public_input: stdin,
1499            sponge,
1500        } = test_case.initial_state();
1501
1502        let snippet = FriSnippet {
1503            test_instance: test_case.fri_verify,
1504        };
1505        let shadowed_procedure = ShadowedProcedure::new(snippet);
1506        let rust = rust_final_state(
1507            &shadowed_procedure,
1508            &initial_stack,
1509            &stdin,
1510            &nondeterminism,
1511            &sponge,
1512        );
1513
1514        let tasm = tasm_final_state(
1515            &shadowed_procedure,
1516            &initial_stack,
1517            &stdin,
1518            nondeterminism,
1519            &sponge,
1520        );
1521
1522        assert_eq!(rust.public_output, tasm.public_output);
1523        verify_stack_growth(&shadowed_procedure, &initial_stack, &tasm.op_stack.stack);
1524        verify_sponge_equivalence(&rust.sponge, &tasm.sponge);
1525
1526        type IndexedLeaves = Vec<(u32, XFieldElement)>;
1527        let &rust_address = rust.stack.last().unwrap();
1528        let &tasm_address = tasm.op_stack.stack.last().unwrap();
1529        let rust_object = IndexedLeaves::decode_from_memory(&rust.ram, rust_address).unwrap();
1530        let tasm_object = IndexedLeaves::decode_from_memory(&tasm.ram, tasm_address).unwrap();
1531        assert_eq!(rust_object, tasm_object);
1532    }
1533
1534    #[proptest]
1535    fn fri_derived_params_match(test_case: ArbitraryFriVerify) {
1536        let fri_verify = test_case.fri_verify();
1537        let fri = fri_verify.to_fri();
1538
1539        prop_assert_eq!(fri_verify.num_rounds(), fri.num_rounds());
1540        prop_assert_eq!(
1541            fri_verify.last_round_max_degree(),
1542            fri.last_round_max_degree()
1543        );
1544        prop_assert_eq!(
1545            fri_verify.first_round_max_degree(),
1546            fri.first_round_max_degree()
1547        );
1548    }
1549
1550    #[proptest(cases = 10)]
1551    fn test_inner_verify(test_case: TestCase) {
1552        let fri = test_case.fri();
1553        let mut vm_proof_iter = test_case.proof_stream();
1554        let verify_result = fri.verify(&mut vm_proof_iter);
1555        prop_assert!(verify_result.is_ok(), "FRI verify error: {verify_result:?}");
1556
1557        let fri_verify = test_case.fri_verify;
1558        let mut vm_vm_proof_iter = test_case.proof_stream();
1559        let verify_result = fri_verify.inner_verify(&mut vm_vm_proof_iter, &mut vec![]);
1560        prop_assert!(verify_result.is_ok(), "FRI verify error: {verify_result:?}");
1561    }
1562
1563    #[proptest(cases = 3)]
1564    fn test_shadow_prop(test_case: TestCase) {
1565        assert_behavioral_equivalence_of_fris(test_case);
1566    }
1567
1568    #[test]
1569    fn test_shadow_small() {
1570        assert_behavioral_equivalence_of_fris(TestCase::small_case());
1571    }
1572
1573    #[test]
1574    fn modifying_any_element_in_vm_proof_iter_of_small_test_case_causes_verification_failure() {
1575        let test_case = TestCase::small_case();
1576        let fri_verify = test_case.fri_verify;
1577        let vm_proof_iter = test_case.proof_stream();
1578        let digests = fri_verify.extract_digests_required_for_proving(&vm_proof_iter);
1579
1580        // The digests required for verification are extracted and given to the verifier via
1581        // non-determinism. The corresponding digests in the proof are subsequently ignored by the
1582        // verifier. As a result, modifying one such digest will not cause a verification failure.
1583        // Therefore, any word occuring in any digest is ignored when looking for verification
1584        // failures. While this is a heuristic, the probability of a false negative is negligible.
1585        let words_occurring_in_some_digest: HashSet<_> =
1586            digests.iter().flat_map(|digest| digest.values()).collect();
1587
1588        // sanity check
1589        let proof = test_case.proof_items().encode();
1590        let snippet = FriSnippet {
1591            test_instance: test_case.fri_verify,
1592        };
1593        snippet.verify_from_proof_with_digests(proof.clone(), digests.clone());
1594
1595        let proof_len = proof.len();
1596        (0..proof_len).into_par_iter().for_each(|i| {
1597            if words_occurring_in_some_digest.contains(&proof[i]) {
1598                return;
1599            }
1600            let mut proof = proof.clone();
1601            proof[i].increment();
1602            catch_unwind(|| snippet.verify_from_proof_with_digests(proof, digests.clone()))
1603                .expect_err(&format!(
1604                    "Verification must fail, but succeeded at element {i}/{proof_len}"
1605                ));
1606        });
1607    }
1608
1609    #[proptest(cases = 2)]
1610    fn assert_nondeterministic_digests_are_all_used(test_case: TestCase) {
1611        let ProcedureInitialState {
1612            stack: initial_stack,
1613            nondeterminism,
1614            public_input: stdin,
1615            sponge,
1616        } = test_case.initial_state();
1617
1618        let snippet = FriSnippet {
1619            test_instance: test_case.fri_verify,
1620        };
1621        let shadowed_procedure = ShadowedProcedure::new(snippet);
1622
1623        let tasm = tasm_final_state(
1624            &shadowed_procedure,
1625            &initial_stack,
1626            &stdin,
1627            nondeterminism,
1628            &sponge,
1629        );
1630
1631        assert!(tasm.secret_digests.is_empty());
1632    }
1633}
1634
1635#[cfg(test)]
1636mod bench {
1637    use std::fs::File;
1638    use std::fs::create_dir_all;
1639    use std::io::Write;
1640    use std::path::Path;
1641    use std::path::PathBuf;
1642
1643    use super::*;
1644    use crate::generate_full_profile;
1645    use crate::test_helpers::prepend_program_with_sponge_init;
1646    use crate::test_helpers::prepend_program_with_stack_setup;
1647    use crate::test_prelude::*;
1648    use crate::verifier::fri::verify::tests::TestCase;
1649
1650    #[test]
1651    fn bench() {
1652        let expansion_factor = 4;
1653        let domain_length = 1 << 10;
1654        let offset = BFieldElement::new(7);
1655        let num_collinearity_checks = 80;
1656        let fri_verify = FriVerify::new(
1657            offset,
1658            domain_length,
1659            expansion_factor,
1660            num_collinearity_checks,
1661        );
1662        let snippet = FriSnippet {
1663            test_instance: fri_verify,
1664        };
1665        ShadowedProcedure::new(snippet).bench();
1666    }
1667
1668    #[ignore = "Takes many minutes to run"]
1669    #[test]
1670    fn profile_fri() {
1671        const INNER_PADDED_HEIGHTS: [usize; 4] = [1 << 8, 1 << 9, 1 << 10, 1 << 11];
1672        const SECURITY_LEVEL: usize = 160;
1673
1674        for inner_padded_height in INNER_PADDED_HEIGHTS {
1675            for log2_fri_expansion_factor in 1..=5 {
1676                let stark = Stark::new(SECURITY_LEVEL, log2_fri_expansion_factor);
1677                profile_and_bench_fri_run(stark, inner_padded_height);
1678            }
1679        }
1680    }
1681
1682    fn profile_and_bench_fri_run(stark: Stark, inner_padded_height: usize) {
1683        let test_case = TestCase::recursive_case(
1684            inner_padded_height,
1685            stark.fri_expansion_factor.ilog2() as usize,
1686        );
1687        let fri_params = stark.fri(inner_padded_height).unwrap();
1688        let name = format!(
1689            "fri_verify_expansion_factor_inner_padded_height_{}_expansion_factor_{}",
1690            inner_padded_height, fri_params.expansion_factor
1691        );
1692
1693        let ProcedureInitialState {
1694            stack: initial_stack,
1695            nondeterminism,
1696            public_input: stdin,
1697            sponge: _,
1698        } = test_case.initial_state();
1699        let snippet = FriSnippet {
1700            test_instance: test_case.fri_verify,
1701        };
1702        let code = snippet.link_for_isolated_run();
1703        let program = Program::new(&code);
1704        let program = prepend_program_with_stack_setup(&initial_stack, &program);
1705        let program = prepend_program_with_sponge_init(&program);
1706
1707        let profile = generate_full_profile(&name, program, &stdin.into(), &nondeterminism);
1708
1709        // write profile to profile file
1710        let mut path = PathBuf::new();
1711        path.push("profiles");
1712        create_dir_all(&path).expect("profiles directory must be created successfully");
1713
1714        path.push(Path::new(&name).with_extension("profile"));
1715        let mut file = File::create(path).expect("open file for writing");
1716        write!(file, "{profile}").unwrap();
1717
1718        println!("{profile}");
1719
1720        // Don't do this since entrypoint label of FRI snippet is not parameterized over its
1721        // variables, meaning all benchmarks are overwritten.
1722        // // Also create benchmarks for quick reference
1723        // ShadowedProcedure::new(snippet).bench();
1724    }
1725}