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}