Skip to main content

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