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