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.len().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 parameters(&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 return_values(&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 test_strategy::proptest;
1158    use triton_vm::proof_item::ProofItem;
1159    use twenty_first::math::ntt::ntt;
1160    use twenty_first::util_types::sponge::Sponge;
1161
1162    use super::*;
1163    use crate::empty_stack;
1164    use crate::memory::dyn_malloc::DYN_MALLOC_FIRST_ADDRESS;
1165    use crate::structure::tasm_object::decode_from_memory_with_size;
1166    use crate::test_helpers::rust_final_state;
1167    use crate::test_helpers::tasm_final_state;
1168    use crate::test_helpers::verify_sponge_equivalence;
1169    use crate::test_helpers::verify_stack_growth;
1170    use crate::test_prelude::*;
1171
1172    impl FriVerify {
1173        pub fn new(
1174            offset: BFieldElement,
1175            domain_length: u32,
1176            expansion_factor: u32,
1177            num_collinearity_checks: u32,
1178        ) -> Self {
1179            let domain = ArithmeticDomain::of_length(domain_length as usize)
1180                .unwrap()
1181                .with_offset(offset);
1182            Self {
1183                expansion_factor,
1184                num_collinearity_checks,
1185                domain_length,
1186                domain_offset: domain.offset(),
1187                domain_generator: domain.generator(),
1188            }
1189        }
1190
1191        pub fn call(
1192            &self,
1193            proof_stream: &mut ProofStream,
1194            nondeterminism: &NonDeterminism,
1195        ) -> Vec<(u32, XFieldElement)> {
1196            self.inner_verify(proof_stream, &mut nondeterminism.digests.clone())
1197                .unwrap()
1198        }
1199
1200        /// Generate a proof, embedded in a proof stream.
1201        pub fn pseudorandom_fri_proof_stream(&self, seed: [u8; 32]) -> ProofStream {
1202            let max_degree = self.first_round_max_degree();
1203            let mut rng = StdRng::from_seed(seed);
1204            let polynomial_coefficients = (0..=max_degree).map(|_| rng.random()).collect_vec();
1205
1206            let mut codeword = polynomial_coefficients;
1207            codeword.resize(self.domain_length as usize, XFieldElement::zero());
1208            ntt(&mut codeword);
1209
1210            let mut proof_stream = ProofStream::new();
1211            let fri = self.to_fri();
1212            fri.prove(&codeword, &mut proof_stream).unwrap();
1213
1214            ProofStream {
1215                items: proof_stream.items,
1216                items_index: 0,
1217                sponge: Tip5::init(),
1218            }
1219        }
1220    }
1221
1222    impl FriSnippet {
1223        /// Test helper – panics if verification fails.
1224        fn verify_from_proof_with_digests(&self, proof: Vec<BFieldElement>, digests: Vec<Digest>) {
1225            let items = *Vec::<ProofItem>::decode(&proof).unwrap();
1226            let proof_stream = ProofStream {
1227                items,
1228                items_index: 0,
1229                sponge: Tip5::init(),
1230            };
1231            let (stack, nondeterminism) =
1232                self.set_up_stack_and_non_determinism_using_digests(proof_stream, digests);
1233
1234            let shadowed_snippet = ShadowedProcedure::new(self.to_owned());
1235            let _tasm = tasm_final_state(
1236                &shadowed_snippet,
1237                &stack,
1238                &[],
1239                nondeterminism,
1240                &Some(Tip5::init()),
1241            );
1242        }
1243
1244        fn set_up_stack_and_non_determinism(
1245            &self,
1246            proof_stream: ProofStream,
1247        ) -> (Vec<BFieldElement>, NonDeterminism) {
1248            let digests = self
1249                .test_instance
1250                .extract_digests_required_for_proving(&proof_stream);
1251            self.set_up_stack_and_non_determinism_using_digests(proof_stream, digests)
1252        }
1253
1254        fn set_up_stack_and_non_determinism_using_digests(
1255            &self,
1256            proof_stream: ProofStream,
1257            digests: Vec<Digest>,
1258        ) -> (Vec<BFieldElement>, NonDeterminism) {
1259            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
1260            let vm_proof_iter_pointer = BFieldElement::zero();
1261            // uses highly specific knowledge about `BFieldCodec`
1262            let proof_iter_current_item_pointer = vm_proof_iter_pointer + BFieldElement::new(2);
1263
1264            let fri_verify_pointer =
1265                encode_to_memory(&mut memory, vm_proof_iter_pointer, &proof_stream);
1266            let proof_iter_pointer =
1267                encode_to_memory(&mut memory, fri_verify_pointer, &self.test_instance);
1268            encode_to_memory(
1269                &mut memory,
1270                proof_iter_pointer,
1271                &proof_iter_current_item_pointer,
1272            );
1273            let nondeterminism = NonDeterminism::default()
1274                .with_ram(memory)
1275                .with_digests(digests);
1276
1277            let mut stack = empty_stack();
1278            stack.push(proof_iter_pointer);
1279            stack.push(fri_verify_pointer);
1280
1281            (stack, nondeterminism)
1282        }
1283    }
1284
1285    impl Procedure for FriSnippet {
1286        fn rust_shadow(
1287            &self,
1288            stack: &mut Vec<BFieldElement>,
1289            memory: &mut HashMap<BFieldElement, BFieldElement>,
1290            nondeterminism: &NonDeterminism,
1291            _public_input: &[BFieldElement],
1292            sponge: &mut Option<Tip5>,
1293        ) -> Vec<BFieldElement> {
1294            let fri_pointer = stack.pop().unwrap();
1295            let fri_verify = *FriVerify::decode_from_memory(memory, fri_pointer).unwrap();
1296            assert_eq!(fri_verify, self.test_instance);
1297
1298            let proof_iter_pointer = stack.pop().unwrap();
1299
1300            // uses highly specific knowledge about `BFieldCodec` and the test setup
1301            let proof_stream_pointer =
1302                *memory.get(&proof_iter_pointer).unwrap() - BFieldElement::new(2);
1303            // todo: hack using local knowledge: `fri_verify` lives directly after `vm_proof_iter`.
1304            //  Replace this once we have a better way to decode.
1305            let proof_stream_size = (fri_pointer - proof_stream_pointer).value() as usize;
1306            let mut proof_stream = decode_from_memory_with_size::<ProofStream>(
1307                memory,
1308                proof_stream_pointer,
1309                proof_stream_size,
1310            )
1311            .unwrap();
1312
1313            let revealed_indices_and_elements = fri_verify.call(&mut proof_stream, nondeterminism);
1314
1315            let indices_and_leafs_pointer = DYN_MALLOC_FIRST_ADDRESS;
1316            encode_to_memory(
1317                memory,
1318                indices_and_leafs_pointer,
1319                &revealed_indices_and_elements,
1320            );
1321
1322            stack.push(indices_and_leafs_pointer);
1323
1324            *sponge = Some(proof_stream.sponge);
1325
1326            // no standard output
1327            vec![]
1328        }
1329
1330        fn pseudorandom_initial_state(
1331            &self,
1332            seed: [u8; 32],
1333            _bench_case: Option<BenchmarkCase>,
1334        ) -> ProcedureInitialState {
1335            let vm_proof_iter = self.test_instance.pseudorandom_fri_proof_stream(seed);
1336            let (stack, nondeterminism) = self.set_up_stack_and_non_determinism(vm_proof_iter);
1337
1338            ProcedureInitialState {
1339                stack,
1340                nondeterminism,
1341                public_input: vec![],
1342                sponge: Some(Tip5::init()),
1343            }
1344        }
1345    }
1346
1347    #[derive(Debug, Clone, test_strategy::Arbitrary)]
1348    struct ArbitraryFriVerify {
1349        #[strategy(arb())]
1350        #[filter(!#offset.is_zero())]
1351        offset: BFieldElement,
1352
1353        #[strategy(0_u32..12)]
1354        domain_length_exponent: u32,
1355
1356        #[strategy(0_u32..6)]
1357        expansion_factor_exponent: u32,
1358
1359        #[strategy(1_u32..320)]
1360        num_collinearity_checks: u32,
1361    }
1362
1363    impl ArbitraryFriVerify {
1364        fn fri_verify(&self) -> FriVerify {
1365            let expansion_factor = 2 << self.expansion_factor_exponent;
1366            let domain_length = expansion_factor * (1 << self.domain_length_exponent);
1367            FriVerify::new(
1368                self.offset,
1369                domain_length,
1370                expansion_factor,
1371                self.num_collinearity_checks,
1372            )
1373        }
1374    }
1375
1376    #[derive(Debug, Clone, test_strategy::Arbitrary)]
1377    pub(super) struct TestCase {
1378        #[strategy(any::<ArbitraryFriVerify>().prop_map(|x| x.fri_verify()))]
1379        pub(super) fri_verify: FriVerify,
1380
1381        #[strategy(vec(arb(), #fri_verify.first_round_max_degree()))]
1382        polynomial_coefficients: Vec<XFieldElement>,
1383    }
1384
1385    impl TestCase {
1386        fn thirty_two_coefficients() -> [u64; 32] {
1387            [
1388                92, 42, 91, 59, 86, 64, 5, 64, 74, 53, 54, 68, 54, 23, 24, 58, 15, 44, 33, 31, 38,
1389                97, 25, 69, 11, 67, 66, 33, 37, 58, 43, 14,
1390            ]
1391        }
1392
1393        /// A test case with no FRI rounds.
1394        fn tiny_case() -> Self {
1395            let fri_verify = FriVerify::new(bfe!(1), 2, 2, 1);
1396            assert_eq!(0, fri_verify.num_rounds());
1397            assert_eq!(0, fri_verify.last_round_max_degree());
1398
1399            Self {
1400                fri_verify,
1401                polynomial_coefficients: vec![xfe!([42; 3])],
1402            }
1403        }
1404
1405        /// A test case with 2 FRI rounds.
1406        fn small_case() -> Self {
1407            let fri_verify = FriVerify::new(bfe!(1), 64, 2, 7);
1408            assert_eq!(2, fri_verify.num_rounds(), "test case must be meaningful");
1409
1410            let coefficients = Self::thirty_two_coefficients();
1411            assert_eq!(fri_verify.first_round_max_degree() + 1, coefficients.len());
1412
1413            Self {
1414                fri_verify,
1415                polynomial_coefficients: coefficients.map(|n| xfe!([n; 3])).to_vec(),
1416            }
1417        }
1418
1419        /// A test case resembling that which will be run by the STARK verifier
1420        pub(super) fn recursive_case(
1421            inner_padded_height: usize,
1422            log2_expansion_factor: usize,
1423        ) -> Self {
1424            const SECURITY_LEVEL: usize = 160;
1425            let stark = Stark::new(SECURITY_LEVEL, log2_expansion_factor);
1426
1427            let fri_verify: FriVerify = stark.fri(inner_padded_height).unwrap().into();
1428
1429            let coefficients = Self::thirty_two_coefficients();
1430
1431            Self {
1432                fri_verify,
1433                polynomial_coefficients: coefficients.map(|n| xfe!([n; 3])).to_vec(),
1434            }
1435        }
1436
1437        fn fri(&self) -> Fri {
1438            self.fri_verify.to_fri()
1439        }
1440
1441        fn codeword(&self) -> Vec<XFieldElement> {
1442            let domain_length = self.fri_verify.domain_length;
1443            let mut codeword = self.polynomial_coefficients.clone();
1444            codeword.resize(domain_length as usize, XFieldElement::zero());
1445
1446            ntt(&mut codeword);
1447            codeword
1448        }
1449
1450        fn proof_items(&self) -> Vec<ProofItem> {
1451            let fri = self.fri();
1452            let codeword = self.codeword();
1453            let mut proof_stream = ProofStream::new();
1454            fri.prove(&codeword, &mut proof_stream).unwrap();
1455
1456            proof_stream.items
1457        }
1458
1459        fn proof_stream(&self) -> ProofStream {
1460            ProofStream {
1461                items: self.proof_items(),
1462                items_index: 0,
1463                sponge: Tip5::init(),
1464            }
1465        }
1466
1467        pub(super) fn initial_state(&self) -> ProcedureInitialState {
1468            let fri_verify = self.fri_verify;
1469            let proof_stream = self.proof_stream();
1470            let snippet = FriSnippet {
1471                test_instance: fri_verify,
1472            };
1473            let (stack, nondeterminism) = snippet.set_up_stack_and_non_determinism(proof_stream);
1474
1475            ProcedureInitialState {
1476                stack,
1477                nondeterminism,
1478                public_input: vec![],
1479                sponge: Some(Tip5::init()),
1480            }
1481        }
1482    }
1483
1484    #[test]
1485    fn assert_behavioral_equivalence_of_tiny_fri_instance() {
1486        assert_behavioral_equivalence_of_fris(TestCase::tiny_case());
1487    }
1488
1489    /// Unfortunately, we cannot call the built-in test, _i.e._,
1490    /// `ShadowedProcedure::new(procedure).test()`: that test checks that the rust and tasm stacks
1491    /// are left in identical states. While they both contain a pointer to the same object, this
1492    /// object lives in a different (and difficult to predict) location in memory. This means the
1493    /// pointers are different. In the end, we don't care about the pointers. Therefore, instead of
1494    /// complete stack equivalence, we check equivalence of the pointed-to objects.
1495    fn assert_behavioral_equivalence_of_fris(test_case: TestCase) {
1496        let ProcedureInitialState {
1497            stack: initial_stack,
1498            nondeterminism,
1499            public_input: stdin,
1500            sponge,
1501        } = test_case.initial_state();
1502
1503        let snippet = FriSnippet {
1504            test_instance: test_case.fri_verify,
1505        };
1506        let shadowed_procedure = ShadowedProcedure::new(snippet);
1507        let rust = rust_final_state(
1508            &shadowed_procedure,
1509            &initial_stack,
1510            &stdin,
1511            &nondeterminism,
1512            &sponge,
1513        );
1514
1515        let tasm = tasm_final_state(
1516            &shadowed_procedure,
1517            &initial_stack,
1518            &stdin,
1519            nondeterminism,
1520            &sponge,
1521        );
1522
1523        assert_eq!(rust.public_output, tasm.public_output);
1524        verify_stack_growth(&shadowed_procedure, &initial_stack, &tasm.op_stack.stack);
1525        verify_sponge_equivalence(&rust.sponge, &tasm.sponge);
1526
1527        type IndexedLeaves = Vec<(u32, XFieldElement)>;
1528        let &rust_address = rust.stack.last().unwrap();
1529        let &tasm_address = tasm.op_stack.stack.last().unwrap();
1530        let rust_object = IndexedLeaves::decode_from_memory(&rust.ram, rust_address).unwrap();
1531        let tasm_object = IndexedLeaves::decode_from_memory(&tasm.ram, tasm_address).unwrap();
1532        assert_eq!(rust_object, tasm_object);
1533    }
1534
1535    #[proptest]
1536    fn fri_derived_params_match(test_case: ArbitraryFriVerify) {
1537        let fri_verify = test_case.fri_verify();
1538        let fri = fri_verify.to_fri();
1539
1540        prop_assert_eq!(fri_verify.num_rounds(), fri.num_rounds());
1541        prop_assert_eq!(
1542            fri_verify.last_round_max_degree(),
1543            fri.last_round_max_degree()
1544        );
1545        prop_assert_eq!(
1546            fri_verify.first_round_max_degree(),
1547            fri.first_round_max_degree()
1548        );
1549    }
1550
1551    #[proptest(cases = 10)]
1552    fn test_inner_verify(test_case: TestCase) {
1553        let fri = test_case.fri();
1554        let mut vm_proof_iter = test_case.proof_stream();
1555        let verify_result = fri.verify(&mut vm_proof_iter);
1556        prop_assert!(verify_result.is_ok(), "FRI verify error: {verify_result:?}");
1557
1558        let fri_verify = test_case.fri_verify;
1559        let mut vm_vm_proof_iter = test_case.proof_stream();
1560        let verify_result = fri_verify.inner_verify(&mut vm_vm_proof_iter, &mut vec![]);
1561        prop_assert!(verify_result.is_ok(), "FRI verify error: {verify_result:?}");
1562    }
1563
1564    #[proptest(cases = 3)]
1565    fn test_shadow_prop(test_case: TestCase) {
1566        assert_behavioral_equivalence_of_fris(test_case);
1567    }
1568
1569    #[test]
1570    fn test_shadow_small() {
1571        assert_behavioral_equivalence_of_fris(TestCase::small_case());
1572    }
1573
1574    #[test]
1575    fn modifying_any_element_in_vm_proof_iter_of_small_test_case_causes_verification_failure() {
1576        let test_case = TestCase::small_case();
1577        let fri_verify = test_case.fri_verify;
1578        let vm_proof_iter = test_case.proof_stream();
1579        let digests = fri_verify.extract_digests_required_for_proving(&vm_proof_iter);
1580
1581        // The digests required for verification are extracted and given to the verifier via
1582        // non-determinism. The corresponding digests in the proof are subsequently ignored by the
1583        // verifier. As a result, modifying one such digest will not cause a verification failure.
1584        // Therefore, any word occuring in any digest is ignored when looking for verification
1585        // failures. While this is a heuristic, the probability of a false negative is negligible.
1586        let words_occurring_in_some_digest: HashSet<_> =
1587            digests.iter().flat_map(|digest| digest.values()).collect();
1588
1589        // sanity check
1590        let proof = test_case.proof_items().encode();
1591        let snippet = FriSnippet {
1592            test_instance: test_case.fri_verify,
1593        };
1594        snippet.verify_from_proof_with_digests(proof.clone(), digests.clone());
1595
1596        let proof_len = proof.len();
1597        (0..proof_len).into_par_iter().for_each(|i| {
1598            if words_occurring_in_some_digest.contains(&proof[i]) {
1599                return;
1600            }
1601            let mut proof = proof.clone();
1602            proof[i].increment();
1603            catch_unwind(|| snippet.verify_from_proof_with_digests(proof, digests.clone()))
1604                .expect_err(&format!(
1605                    "Verification must fail, but succeeded at element {i}/{proof_len}"
1606                ));
1607        });
1608    }
1609
1610    #[proptest(cases = 2)]
1611    fn assert_nondeterministic_digests_are_all_used(test_case: TestCase) {
1612        let ProcedureInitialState {
1613            stack: initial_stack,
1614            nondeterminism,
1615            public_input: stdin,
1616            sponge,
1617        } = test_case.initial_state();
1618
1619        let snippet = FriSnippet {
1620            test_instance: test_case.fri_verify,
1621        };
1622        let shadowed_procedure = ShadowedProcedure::new(snippet);
1623
1624        let tasm = tasm_final_state(
1625            &shadowed_procedure,
1626            &initial_stack,
1627            &stdin,
1628            nondeterminism,
1629            &sponge,
1630        );
1631
1632        assert!(tasm.secret_digests.is_empty());
1633    }
1634}
1635
1636#[cfg(test)]
1637mod bench {
1638    use std::fs::File;
1639    use std::fs::create_dir_all;
1640    use std::io::Write;
1641    use std::path::Path;
1642    use std::path::PathBuf;
1643
1644    use super::*;
1645    use crate::generate_full_profile;
1646    use crate::test_helpers::prepend_program_with_sponge_init;
1647    use crate::test_helpers::prepend_program_with_stack_setup;
1648    use crate::test_prelude::*;
1649    use crate::verifier::fri::verify::tests::TestCase;
1650
1651    #[test]
1652    fn bench() {
1653        let expansion_factor = 4;
1654        let domain_length = 1 << 10;
1655        let offset = BFieldElement::new(7);
1656        let num_collinearity_checks = 80;
1657        let fri_verify = FriVerify::new(
1658            offset,
1659            domain_length,
1660            expansion_factor,
1661            num_collinearity_checks,
1662        );
1663        let snippet = FriSnippet {
1664            test_instance: fri_verify,
1665        };
1666        ShadowedProcedure::new(snippet).bench();
1667    }
1668
1669    #[ignore = "Takes many minutes to run"]
1670    #[test]
1671    fn profile_fri() {
1672        const INNER_PADDED_HEIGHTS: [usize; 4] = [1 << 8, 1 << 9, 1 << 10, 1 << 11];
1673        const SECURITY_LEVEL: usize = 160;
1674
1675        for inner_padded_height in INNER_PADDED_HEIGHTS {
1676            for log2_fri_expansion_factor in 1..=5 {
1677                let stark = Stark::new(SECURITY_LEVEL, log2_fri_expansion_factor);
1678                profile_and_bench_fri_run(stark, inner_padded_height);
1679            }
1680        }
1681    }
1682
1683    fn profile_and_bench_fri_run(stark: Stark, inner_padded_height: usize) {
1684        let test_case = TestCase::recursive_case(
1685            inner_padded_height,
1686            stark.fri_expansion_factor.ilog2() as usize,
1687        );
1688        let fri_params = stark.fri(inner_padded_height).unwrap();
1689        let name = format!(
1690            "fri_verify_expansion_factor_inner_padded_height_{}_expansion_factor_{}",
1691            inner_padded_height, fri_params.expansion_factor
1692        );
1693
1694        let ProcedureInitialState {
1695            stack: initial_stack,
1696            nondeterminism,
1697            public_input: stdin,
1698            sponge: _,
1699        } = test_case.initial_state();
1700        let snippet = FriSnippet {
1701            test_instance: test_case.fri_verify,
1702        };
1703        let code = snippet.link_for_isolated_run();
1704        let program = Program::new(&code);
1705        let program = prepend_program_with_stack_setup(&initial_stack, &program);
1706        let program = prepend_program_with_sponge_init(&program);
1707
1708        let profile = generate_full_profile(&name, program, &stdin.into(), &nondeterminism);
1709
1710        // write profile to profile file
1711        let mut path = PathBuf::new();
1712        path.push("profiles");
1713        create_dir_all(&path).expect("profiles directory must be created successfully");
1714
1715        path.push(Path::new(&name).with_extension("profile"));
1716        let mut file = File::create(path).expect("open file for writing");
1717        write!(file, "{profile}").unwrap();
1718
1719        println!("{profile}");
1720
1721        // Don't do this since entrypoint label of FRI snippet is not parameterized over its
1722        // variables, meaning all benchmarks are overwritten.
1723        // // Also create benchmarks for quick reference
1724        // ShadowedProcedure::new(snippet).bench();
1725    }
1726}