1use core::fmt::Debug;
2use core::iter::zip;
3use core::ops::RangeInclusive;
4
5use hashbrown::HashMap;
6use itertools::{zip_eq, Itertools};
7use num_traits::Zero;
8use serde::{Deserialize, Serialize};
9use std_shims::{vec, Vec};
10use thiserror::Error;
11
12use super::channel::{Channel, MerkleChannel};
13use super::fields::qm31::{SecureField, QM31, SECURE_EXTENSION_DEGREE};
14use super::poly::circle::CircleDomain;
15use super::queries::{draw_queries, Queries};
16use crate::core::circle::Coset;
17use crate::core::fft::ibutterfly;
18use crate::core::fields::m31::BaseField;
19use crate::core::fields::FieldExpOps;
20use crate::core::poly::circle::CanonicCoset;
21use crate::core::poly::line::{LineDomain, LinePoly};
22use crate::core::utils::bit_reverse_index;
23use crate::core::vcs_lifted::merkle_hasher::MerkleHasherLifted;
24use crate::core::vcs_lifted::verifier::{
25 MerkleDecommitmentLifted, MerkleDecommitmentLiftedAux, MerkleVerificationError,
26 MerkleVerifierLifted,
27};
28
29#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
32pub struct FriConfig {
33 pub log_blowup_factor: u32,
34 pub log_last_layer_degree_bound: u32,
35 pub n_queries: usize,
36 }
38
39impl FriConfig {
40 const LOG_MIN_LAST_LAYER_DEGREE_BOUND: u32 = 0;
41 const LOG_MAX_LAST_LAYER_DEGREE_BOUND: u32 = 10;
42 const LOG_LAST_LAYER_DEGREE_BOUND_RANGE: RangeInclusive<u32> =
43 Self::LOG_MIN_LAST_LAYER_DEGREE_BOUND..=Self::LOG_MAX_LAST_LAYER_DEGREE_BOUND;
44
45 const LOG_MIN_BLOWUP_FACTOR: u32 = 1;
46 const LOG_MAX_BLOWUP_FACTOR: u32 = 16;
47 const LOG_BLOWUP_FACTOR_RANGE: RangeInclusive<u32> =
48 Self::LOG_MIN_BLOWUP_FACTOR..=Self::LOG_MAX_BLOWUP_FACTOR;
49
50 pub fn new(log_last_layer_degree_bound: u32, log_blowup_factor: u32, n_queries: usize) -> Self {
58 assert!(Self::LOG_LAST_LAYER_DEGREE_BOUND_RANGE.contains(&log_last_layer_degree_bound));
59 assert!(Self::LOG_BLOWUP_FACTOR_RANGE.contains(&log_blowup_factor));
60 Self {
61 log_blowup_factor,
62 log_last_layer_degree_bound,
63 n_queries,
64 }
65 }
66
67 pub const fn last_layer_domain_size(&self) -> usize {
68 1 << (self.log_last_layer_degree_bound + self.log_blowup_factor)
69 }
70
71 pub const fn security_bits(&self) -> u32 {
72 self.log_blowup_factor * self.n_queries as u32
73 }
74}
75
76pub struct FriVerifier<MC: MerkleChannel> {
77 config: FriConfig,
78 first_layer: FriFirstLayerVerifier<MC::H>,
83 inner_layers: Vec<FriInnerLayerVerifier<MC::H>>,
84 last_layer_domain: LineDomain,
85 last_layer_poly: LinePoly,
86 queries: Option<Queries>,
89}
90
91impl<MC: MerkleChannel> FriVerifier<MC> {
92 pub fn commit(
102 channel: &mut MC::C,
103 config: FriConfig,
104 proof: FriProof<MC::H>,
105 column_bound: CirclePolyDegreeBound,
106 ) -> Result<Self, FriVerificationError> {
107 MC::mix_root(channel, proof.first_layer.commitment);
108
109 let column_commitment_domain =
110 CanonicCoset::new(column_bound.log_degree_bound + config.log_blowup_factor)
111 .circle_domain();
112
113 let first_layer = FriFirstLayerVerifier {
114 column_commitment_domain,
115 proof: proof.first_layer,
116 folding_alpha: channel.draw_secure_felt(),
117 };
118
119 let mut inner_layers = Vec::new();
120 let mut layer_bound = column_bound.fold_to_line();
121 let mut layer_domain = LineDomain::new(Coset::half_odds(
122 layer_bound.log_degree_bound + config.log_blowup_factor,
123 ));
124
125 for (layer_index, proof) in proof.inner_layers.into_iter().enumerate() {
126 MC::mix_root(channel, proof.commitment);
127
128 inner_layers.push(FriInnerLayerVerifier {
129 domain: layer_domain,
130 folding_alpha: channel.draw_secure_felt(),
131 layer_index,
132 proof,
133 });
134
135 layer_bound = layer_bound
136 .fold(FOLD_STEP)
137 .ok_or(FriVerificationError::InvalidNumFriLayers)?;
138 layer_domain = layer_domain.double();
139 }
140
141 if layer_bound.log_degree_bound != config.log_last_layer_degree_bound {
142 return Err(FriVerificationError::InvalidNumFriLayers);
143 }
144
145 let last_layer_domain = layer_domain;
146 let last_layer_poly = proof.last_layer_poly;
147
148 if last_layer_poly.len() > (1 << config.log_last_layer_degree_bound) {
149 return Err(FriVerificationError::LastLayerDegreeInvalid);
150 }
151
152 channel.mix_felts(&last_layer_poly);
153
154 Ok(Self {
155 config,
156 first_layer,
157 inner_layers,
158 last_layer_domain,
159 last_layer_poly,
160 queries: None,
161 })
162 }
163
164 pub fn decommit(
176 mut self,
177 first_layer_query_evals: Vec<SecureField>,
178 ) -> Result<(), FriVerificationError> {
179 let queries = self.queries.take().expect("queries not sampled");
180 self.decommit_on_queries(&queries, first_layer_query_evals)
181 }
182
183 fn decommit_on_queries(
184 self,
185 queries: &Queries,
186 first_layer_query_evals: Vec<SecureField>,
187 ) -> Result<(), FriVerificationError> {
188 let first_layer_sparse_eval =
189 self.decommit_first_layer(queries, first_layer_query_evals)?;
190 let inner_layer_queries = queries.fold(CIRCLE_TO_LINE_FOLD_STEP);
191 let (last_layer_queries, last_layer_query_evals) =
192 self.decommit_inner_layers(&inner_layer_queries, first_layer_sparse_eval)?;
193 self.decommit_last_layer(last_layer_queries, last_layer_query_evals)
194 }
195
196 fn decommit_first_layer(
201 &self,
202 queries: &Queries,
203 first_layer_query_evals: Vec<SecureField>,
204 ) -> Result<SparseEvaluation, FriVerificationError> {
205 self.first_layer.verify(queries, first_layer_query_evals)
206 }
207
208 fn decommit_inner_layers(
212 &self,
213 queries: &Queries,
214 first_layer_sparse_evals: SparseEvaluation,
215 ) -> Result<(Queries, Vec<SecureField>), FriVerificationError> {
216 let mut layer_queries = queries.clone();
217 let mut layer_query_evals = vec![SecureField::zero(); layer_queries.len()];
218 let first_layer_column_domain = self.first_layer.column_commitment_domain;
219
220 let folded_column_evals = first_layer_sparse_evals
222 .fold_circle(self.first_layer.folding_alpha, first_layer_column_domain);
223
224 accumulate_line(
225 &mut layer_query_evals,
226 &folded_column_evals,
227 self.first_layer.folding_alpha,
228 );
229 for layer in self.inner_layers.iter() {
230 (layer_queries, layer_query_evals) =
232 layer.verify_and_fold(layer_queries, layer_query_evals)?;
233 }
234
235 Ok((layer_queries, layer_query_evals))
236 }
237
238 fn decommit_last_layer(
240 self,
241 queries: Queries,
242 query_evals: Vec<SecureField>,
243 ) -> Result<(), FriVerificationError> {
244 let Self {
245 last_layer_domain: domain,
246 last_layer_poly,
247 ..
248 } = self;
249
250 for (&query, query_eval) in zip(&*queries, query_evals) {
251 let x = domain.at(bit_reverse_index(query, domain.log_size()));
252
253 if query_eval != last_layer_poly.eval_at_point(x.into()) {
254 return Err(FriVerificationError::LastLayerEvaluationsInvalid);
255 }
256 }
257
258 Ok(())
259 }
260
261 pub fn sample_query_positions(&mut self, channel: &mut MC::C) -> Vec<usize> {
263 let first_layer_log_size = self.first_layer.column_commitment_domain.log_size();
264 let unsorted_query_locations =
265 draw_queries(channel, first_layer_log_size, self.config.n_queries);
266 let queries = Queries::new(&unsorted_query_locations, first_layer_log_size);
267 self.queries = Some(queries.clone());
268 queries.positions
269 }
270}
271
272fn accumulate_line(
273 layer_query_evals: &mut [SecureField],
274 column_query_evals: &[SecureField],
275 folding_alpha: SecureField,
276) {
277 let folding_alpha_squared = folding_alpha.square();
278 for (curr_layer_eval, folded_column_eval) in zip_eq(layer_query_evals, column_query_evals) {
279 *curr_layer_eval *= folding_alpha_squared;
280 *curr_layer_eval += *folded_column_eval;
281 }
282}
283
284#[derive(Clone, Copy, Debug, Error)]
285pub enum FriVerificationError {
286 #[error("proof contains an invalid number of FRI layers")]
287 InvalidNumFriLayers,
288 #[error("evaluations are invalid in the first layer")]
289 FirstLayerEvaluationsInvalid,
290 #[error("queries do not resolve to their commitment in the first layer")]
291 FirstLayerCommitmentInvalid { error: MerkleVerificationError },
292 #[error("queries do not resolve to their commitment in inner layer {inner_layer}")]
293 InnerLayerCommitmentInvalid {
294 inner_layer: usize,
295 error: MerkleVerificationError,
296 },
297 #[error("evaluations are invalid in inner layer {inner_layer}")]
298 InnerLayerEvaluationsInvalid { inner_layer: usize },
299 #[error("degree of last layer is invalid")]
300 LastLayerDegreeInvalid,
301 #[error("evaluations in the last layer are invalid")]
302 LastLayerEvaluationsInvalid,
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
306pub struct CirclePolyDegreeBound {
307 log_degree_bound: u32,
308}
309
310impl CirclePolyDegreeBound {
311 pub const fn new(log_degree_bound: u32) -> Self {
312 Self { log_degree_bound }
313 }
314
315 const fn fold_to_line(&self) -> LinePolyDegreeBound {
318 LinePolyDegreeBound {
319 log_degree_bound: self.log_degree_bound - CIRCLE_TO_LINE_FOLD_STEP,
320 }
321 }
322}
323
324impl PartialOrd<LinePolyDegreeBound> for CirclePolyDegreeBound {
325 fn partial_cmp(&self, other: &LinePolyDegreeBound) -> Option<core::cmp::Ordering> {
326 Some(self.log_degree_bound.cmp(&other.log_degree_bound))
327 }
328}
329
330impl PartialEq<LinePolyDegreeBound> for CirclePolyDegreeBound {
331 fn eq(&self, other: &LinePolyDegreeBound) -> bool {
332 self.log_degree_bound == other.log_degree_bound
333 }
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
337struct LinePolyDegreeBound {
338 log_degree_bound: u32,
339}
340
341impl LinePolyDegreeBound {
342 const fn fold(self, n_folds: u32) -> Option<Self> {
344 if self.log_degree_bound < n_folds {
345 return None;
346 }
347
348 let log_degree_bound = self.log_degree_bound - n_folds;
349 Some(Self { log_degree_bound })
350 }
351}
352
353#[derive(Clone, Debug, Serialize, Deserialize)]
355pub struct FriProof<H: MerkleHasherLifted> {
356 pub first_layer: FriLayerProof<H>,
357 pub inner_layers: Vec<FriLayerProof<H>>,
358 pub last_layer_poly: LinePoly,
359}
360
361#[derive(Clone, Debug, Serialize, Deserialize)]
363pub struct FriProofAux<H: MerkleHasherLifted> {
364 pub first_layer: FriLayerProofAux<H>,
365 pub inner_layers: Vec<FriLayerProofAux<H>>,
366}
367
368#[derive(Clone, Debug)]
369pub struct ExtendedFriProof<H: MerkleHasherLifted> {
370 pub proof: FriProof<H>,
371 pub aux: FriProofAux<H>,
372}
373
374pub const FOLD_STEP: u32 = 1;
377
378pub const CIRCLE_TO_LINE_FOLD_STEP: u32 = 1;
380
381#[derive(Clone, Debug, Serialize, Deserialize)]
383pub struct FriLayerProof<H: MerkleHasherLifted> {
384 pub fri_witness: Vec<SecureField>,
388 pub decommitment: MerkleDecommitmentLifted<H>,
389 pub commitment: H::Hash,
390}
391
392#[derive(Clone, Debug, Serialize, Deserialize)]
394pub struct FriLayerProofAux<H: MerkleHasherLifted> {
395 pub all_values: Vec<HashMap<usize, QM31>>,
399 pub decommitment: MerkleDecommitmentLiftedAux<H>,
401}
402
403#[derive(Clone, Debug)]
404pub struct ExtendedFriLayerProof<H: MerkleHasherLifted> {
405 pub proof: FriLayerProof<H>,
406 pub aux: FriLayerProofAux<H>,
407}
408
409struct FriFirstLayerVerifier<H: MerkleHasherLifted> {
410 column_commitment_domain: CircleDomain,
412 folding_alpha: SecureField,
413 proof: FriLayerProof<H>,
414}
415
416impl<H: MerkleHasherLifted> FriFirstLayerVerifier<H> {
417 fn verify(
432 &self,
433 queries: &Queries,
434 column_query_evals: Vec<SecureField>,
435 ) -> Result<SparseEvaluation, FriVerificationError> {
436 let column_log_size = self.column_commitment_domain.log_size();
437 assert_eq!(queries.log_domain_size, column_log_size);
438
439 let mut fri_witness = self.proof.fri_witness.iter().copied();
440
441 let (decommitment_positions, sparse_evaluation) =
442 compute_decommitment_positions_and_rebuild_evals(
443 queries,
444 &column_query_evals,
445 &mut fri_witness,
446 CIRCLE_TO_LINE_FOLD_STEP,
447 )
448 .map_err(|InsufficientWitnessError| {
449 FriVerificationError::FirstLayerEvaluationsInvalid
450 })?;
451
452 let mut decommitmented_values: [Vec<BaseField>; SECURE_EXTENSION_DEGREE] =
454 core::array::from_fn(|_| Vec::new());
455 sparse_evaluation
456 .subset_evals
457 .iter()
458 .flatten()
459 .for_each(|x| {
460 let arr = x.to_m31_array();
461 for (i, val) in arr.into_iter().enumerate() {
462 decommitmented_values[i].push(val);
463 }
464 });
465
466 if fri_witness.next().is_some() {
468 return Err(FriVerificationError::FirstLayerEvaluationsInvalid);
469 }
470
471 let merkle_verifier = MerkleVerifierLifted::new(
472 self.proof.commitment,
473 vec![self.column_commitment_domain.log_size(); SECURE_EXTENSION_DEGREE],
474 );
475
476 merkle_verifier
477 .verify(
478 &decommitment_positions,
479 decommitmented_values.to_vec(),
480 self.proof.decommitment.clone(),
481 )
482 .map_err(|error| FriVerificationError::FirstLayerCommitmentInvalid { error })?;
483
484 Ok(sparse_evaluation)
485 }
486}
487
488struct FriInnerLayerVerifier<H: MerkleHasherLifted> {
489 domain: LineDomain,
490 folding_alpha: SecureField,
491 layer_index: usize,
492 proof: FriLayerProof<H>,
493}
494
495impl<H: MerkleHasherLifted> FriInnerLayerVerifier<H> {
496 fn verify_and_fold(
510 &self,
511 queries: Queries,
512 evals_at_queries: Vec<SecureField>,
513 ) -> Result<(Queries, Vec<SecureField>), FriVerificationError> {
514 assert_eq!(queries.log_domain_size, self.domain.log_size());
515
516 let mut fri_witness = self.proof.fri_witness.iter().copied();
517
518 let (decommitment_positions, sparse_evaluation) =
519 compute_decommitment_positions_and_rebuild_evals(
520 &queries,
521 &evals_at_queries,
522 &mut fri_witness,
523 FOLD_STEP,
524 )
525 .map_err(|InsufficientWitnessError| {
526 FriVerificationError::InnerLayerEvaluationsInvalid {
527 inner_layer: self.layer_index,
528 }
529 })?;
530
531 if fri_witness.next().is_some() {
533 return Err(FriVerificationError::InnerLayerEvaluationsInvalid {
534 inner_layer: self.layer_index,
535 });
536 }
537
538 let mut decommitmented_values: [Vec<BaseField>; SECURE_EXTENSION_DEGREE] =
540 core::array::from_fn(|_| Vec::new());
541 sparse_evaluation
542 .subset_evals
543 .iter()
544 .flatten()
545 .for_each(|x| {
546 let arr = x.to_m31_array();
547 for (i, val) in arr.into_iter().enumerate() {
548 decommitmented_values[i].push(val);
549 }
550 });
551
552 let merkle_verifier = MerkleVerifierLifted::new(
553 self.proof.commitment,
554 vec![self.domain.log_size(); SECURE_EXTENSION_DEGREE],
555 );
556
557 merkle_verifier
558 .verify(
559 &decommitment_positions,
560 decommitmented_values.to_vec(),
561 self.proof.decommitment.clone(),
562 )
563 .map_err(|e| FriVerificationError::InnerLayerCommitmentInvalid {
564 inner_layer: self.layer_index,
565 error: e,
566 })?;
567
568 let folded_queries = queries.fold(FOLD_STEP);
569 let folded_evals = sparse_evaluation.fold_line(self.folding_alpha, self.domain);
570
571 Ok((folded_queries, folded_evals))
572 }
573}
574
575fn compute_decommitment_positions_and_rebuild_evals(
582 queries: &Queries,
583 query_evals: &[QM31],
584 mut witness_evals: impl Iterator<Item = QM31>,
585 fold_step: u32,
586) -> Result<(Vec<usize>, SparseEvaluation), InsufficientWitnessError> {
587 let mut query_evals = query_evals.iter().copied();
588
589 let mut decommitment_positions = Vec::new();
590 let mut subset_evals = Vec::new();
591 let mut subset_domain_index_initials = Vec::new();
592
593 for subset_queries in queries.chunk_by(|a, b| a >> fold_step == b >> fold_step) {
595 let subset_start = (subset_queries[0] >> fold_step) << fold_step;
596 let subset_decommitment_positions = subset_start..subset_start + (1 << fold_step);
597 decommitment_positions.extend(subset_decommitment_positions.clone());
598
599 let mut subset_queries_iter = subset_queries.iter().copied().peekable();
600
601 let subset_eval = subset_decommitment_positions
602 .map(|position| match subset_queries_iter.next_if_eq(&position) {
603 Some(_) => Ok(query_evals.next().unwrap()),
604 None => witness_evals.next().ok_or(InsufficientWitnessError),
605 })
606 .collect::<Result<_, _>>()?;
607
608 subset_evals.push(subset_eval);
609 subset_domain_index_initials.push(bit_reverse_index(subset_start, queries.log_domain_size));
610 }
611
612 let sparse_evaluation = SparseEvaluation::new(subset_evals, subset_domain_index_initials);
613
614 Ok((decommitment_positions, sparse_evaluation))
615}
616
617#[derive(Debug)]
618struct InsufficientWitnessError;
619
620struct SparseEvaluation {
624 subset_evals: Vec<Vec<SecureField>>,
626 subset_domain_initial_indexes: Vec<usize>,
627}
628
629impl SparseEvaluation {
630 fn new(subset_evals: Vec<Vec<SecureField>>, subset_domain_initial_indexes: Vec<usize>) -> Self {
635 let fold_factor = 1 << FOLD_STEP;
636 assert!(subset_evals.iter().all(|e| e.len() == fold_factor));
637 assert_eq!(subset_evals.len(), subset_domain_initial_indexes.len());
638 Self {
639 subset_evals,
640 subset_domain_initial_indexes,
641 }
642 }
643
644 fn fold_line(self, fold_alpha: SecureField, source_domain: LineDomain) -> Vec<SecureField> {
645 zip(self.subset_evals, self.subset_domain_initial_indexes)
646 .map(|(eval, domain_initial_index)| {
647 let fold_domain_initial = source_domain.coset().index_at(domain_initial_index);
648 let fold_domain = LineDomain::new(Coset::new(fold_domain_initial, FOLD_STEP));
649 let (_, folded_values) = fold_line(&eval, fold_domain, fold_alpha);
650 folded_values[0]
651 })
652 .collect()
653 }
654
655 fn fold_circle(self, fold_alpha: SecureField, source_domain: CircleDomain) -> Vec<SecureField> {
656 zip(self.subset_evals, self.subset_domain_initial_indexes)
657 .map(|(eval, domain_initial_index)| {
658 let fold_domain_initial = source_domain.index_at(domain_initial_index);
659 let fold_domain = CircleDomain::new(Coset::new(
660 fold_domain_initial,
661 CIRCLE_TO_LINE_FOLD_STEP - 1,
662 ));
663 let eval = eval.into_iter().collect_vec();
664 let mut buffer = vec![SecureField::zero(); fold_domain.half_coset.size()];
665 fold_circle_into_line(&mut buffer, &eval, fold_domain, fold_alpha);
666 buffer[0]
667 })
668 .collect()
669 }
670}
671
672pub fn fold_line(
675 eval: &[SecureField],
676 domain: LineDomain,
677 alpha: SecureField,
678) -> (LineDomain, Vec<SecureField>) {
679 let n = eval.len();
680 assert!(n >= 2, "Evaluation too small");
681
682 let folded_values = eval
683 .iter()
684 .tuples()
685 .enumerate()
686 .map(|(i, (&f_x, &f_neg_x))| {
687 let x = domain.at(bit_reverse_index(i << FOLD_STEP, domain.log_size()));
689
690 let (mut f0, mut f1) = (f_x, f_neg_x);
691 ibutterfly(&mut f0, &mut f1, x.inverse());
692 f0 + alpha * f1
693 })
694 .collect();
695
696 (domain.double(), folded_values)
697}
698
699pub fn fold_circle_into_line(
703 dst: &mut [SecureField],
704 src: &[SecureField],
705 src_domain: CircleDomain,
706 alpha: SecureField,
707) {
708 assert_eq!(src.len() >> CIRCLE_TO_LINE_FOLD_STEP, dst.len());
709
710 let alpha_sq = alpha * alpha;
711
712 src.iter()
713 .tuples()
714 .enumerate()
715 .for_each(|(i, (&f_p, &f_neg_p))| {
716 let p = src_domain.at(bit_reverse_index(
718 i << CIRCLE_TO_LINE_FOLD_STEP,
719 src_domain.log_size(),
720 ));
721
722 let (mut f0_px, mut f1_px) = (f_p, f_neg_p);
724 ibutterfly(&mut f0_px, &mut f1_px, p.y.inverse());
725 let f_prime = alpha * f1_px + f0_px;
726
727 dst[i] = dst[i] * alpha_sq + f_prime;
728 });
729}
730
731#[cfg(all(test, feature = "prover"))]
732mod tests {
733 use std::iter::zip;
734
735 use itertools::Itertools;
736 use num_traits::{One, Zero};
737
738 use super::FriVerificationError;
739 use crate::core::circle::Coset;
740 use crate::core::fields::m31::BaseField;
741 use crate::core::fields::qm31::SecureField;
742 use crate::core::fields::Field;
743 use crate::core::fri::{
744 fold_circle_into_line, fold_line, CirclePolyDegreeBound, FriConfig,
745 CIRCLE_TO_LINE_FOLD_STEP,
746 };
747 use crate::core::poly::circle::CircleDomain;
748 use crate::core::poly::line::{LineDomain, LinePoly};
749 use crate::core::queries::Queries;
750 use crate::core::test_utils::test_channel;
751 use crate::core::vcs_lifted::blake2_merkle::Blake2sMerkleChannel;
752 use crate::prover::backend::cpu::CpuCirclePoly;
753 use crate::prover::backend::{ColumnOps, CpuBackend};
754 use crate::prover::line::LineEvaluation;
755 use crate::prover::poly::circle::{PolyOps, SecureEvaluation};
756 use crate::prover::poly::BitReversedOrder;
757
758 const LOG_BLOWUP_FACTOR: u32 = 2;
760
761 type FriProver<'a> = crate::prover::fri::FriProver<'a, CpuBackend, Blake2sMerkleChannel>;
762 type FriVerifier = super::FriVerifier<Blake2sMerkleChannel>;
763
764 #[test]
765 fn fold_line_works() {
766 const DEGREE: usize = 8;
767 let even_coeffs: [SecureField; DEGREE / 2] = [1, 2, 1, 3].map(SecureField::from);
769 let odd_coeffs: [SecureField; DEGREE / 2] = [3, 5, 4, 1].map(SecureField::from);
770 let poly = LinePoly::new([even_coeffs, odd_coeffs].concat());
771 let even_poly = LinePoly::new(even_coeffs.to_vec());
772 let odd_poly = LinePoly::new(odd_coeffs.to_vec());
773 let alpha = BaseField::from_u32_unchecked(19283).into();
774 let domain = LineDomain::new(Coset::half_odds(DEGREE.ilog2()));
775 let mut values = domain
776 .iter()
777 .map(|p| poly.eval_at_point(p.into()))
778 .collect();
779 CpuBackend::bit_reverse_column(&mut values);
780
781 let (drp_domain, drp_evals) = fold_line(&values, domain, alpha);
782 let mut drp_evals = drp_evals.into_iter().collect_vec();
783 CpuBackend::bit_reverse_column(&mut drp_evals);
784
785 assert_eq!(drp_evals.len(), DEGREE / 2);
786 for (i, (&drp_eval, x)) in zip(&drp_evals, drp_domain).enumerate() {
787 let f_e: SecureField = even_poly.eval_at_point(x.into());
788 let f_o: SecureField = odd_poly.eval_at_point(x.into());
789 assert_eq!(drp_eval, (f_e + alpha * f_o).double(), "mismatch at {i}");
790 }
791 }
792
793 #[test]
794 fn fold_circle_to_line_works() {
795 const LOG_DEGREE: u32 = 4;
796 let circle_evaluation = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
797 let alpha = SecureField::one();
798 let folded_domain = LineDomain::new(circle_evaluation.domain.half_coset);
799
800 let mut folded_evaluation = vec![SecureField::zero(); folded_domain.size()];
801 fold_circle_into_line(
802 &mut folded_evaluation,
803 &circle_evaluation.values.into_iter().collect_vec(),
804 circle_evaluation.domain,
805 alpha,
806 );
807 let folded_evaluation =
808 LineEvaluation::new(folded_domain, folded_evaluation.into_iter().collect());
809
810 assert_eq!(
811 log_degree_bound(folded_evaluation),
812 LOG_DEGREE - CIRCLE_TO_LINE_FOLD_STEP
813 );
814 }
815
816 #[test]
817 fn valid_proof_passes_verification() -> Result<(), FriVerificationError> {
818 const LOG_DEGREE: u32 = 4;
819 let column = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
820 let twiddles = CpuBackend::precompute_twiddles(column.domain.half_coset);
821 let queries = Queries::from_positions(vec![5], column.domain.log_size());
822 let config = FriConfig::new(1, LOG_BLOWUP_FACTOR, queries.len());
823 let decommitment_value = query_polynomial(&column, &queries);
824 let prover = FriProver::commit(&mut test_channel(), config, &column, &twiddles);
825 let proof = prover.decommit_on_queries(&queries).proof;
826 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
827 let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap();
828
829 verifier.decommit_on_queries(&queries, decommitment_value)
830 }
831
832 #[test]
833 fn valid_proof_with_constant_last_layer_passes_verification() -> Result<(), FriVerificationError>
834 {
835 const LOG_DEGREE: u32 = 3;
836 const LAST_LAYER_LOG_BOUND: u32 = 0;
837 let column = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
838 let twiddles = CpuBackend::precompute_twiddles(column.domain.half_coset);
839 let queries = Queries::from_positions(vec![5], column.domain.log_size());
840 let config = FriConfig::new(LAST_LAYER_LOG_BOUND, LOG_BLOWUP_FACTOR, queries.len());
841 let decommitment_value = query_polynomial(&column, &queries);
842 let prover = FriProver::commit(&mut test_channel(), config, &column, &twiddles);
843 let proof = prover.decommit_on_queries(&queries).proof;
844 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
845 let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap();
846
847 verifier.decommit_on_queries(&queries, decommitment_value)
848 }
849
850 #[test]
851 fn proof_with_removed_layer_fails_verification() {
852 const LOG_DEGREE: u32 = 6;
853 let evaluation = polynomial_evaluation(6, LOG_BLOWUP_FACTOR);
854 let twiddles = CpuBackend::precompute_twiddles(evaluation.domain.half_coset);
855 let log_domain_size = evaluation.domain.log_size();
856 let queries = Queries::from_positions(vec![1], log_domain_size);
857 let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len());
858 let prover = FriProver::commit(&mut test_channel(), config, &evaluation, &twiddles);
859 let proof = prover.decommit_on_queries(&queries).proof;
860 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
861 let mut invalid_config = config;
863 invalid_config.log_last_layer_degree_bound -= 1;
864
865 let verifier = FriVerifier::commit(&mut test_channel(), invalid_config, proof, bound);
866
867 assert!(matches!(
868 verifier,
869 Err(FriVerificationError::InvalidNumFriLayers)
870 ));
871 }
872
873 #[test]
874 fn proof_with_added_layer_fails_verification() {
875 const LOG_DEGREE: u32 = 6;
876 let evaluation = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
877 let twiddles = CpuBackend::precompute_twiddles(evaluation.domain.half_coset);
878 let log_domain_size = evaluation.domain.log_size();
879 let queries = Queries::from_positions(vec![1], log_domain_size);
880 let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len());
881 let prover = FriProver::commit(&mut test_channel(), config, &evaluation, &twiddles);
882 let proof = prover.decommit_on_queries(&queries).proof;
883 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
884 let mut invalid_config = config;
886 invalid_config.log_last_layer_degree_bound += 1;
887
888 let verifier = FriVerifier::commit(&mut test_channel(), invalid_config, proof, bound);
889
890 assert!(matches!(
891 verifier,
892 Err(FriVerificationError::InvalidNumFriLayers)
893 ));
894 }
895
896 #[test]
897 fn proof_with_invalid_inner_layer_evaluation_fails_verification() {
898 const LOG_DEGREE: u32 = 6;
899 let evaluation = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
900 let twiddles = CpuBackend::precompute_twiddles(evaluation.domain.half_coset);
901 let log_domain_size = evaluation.domain.log_size();
902 let queries = Queries::from_positions(vec![5], log_domain_size);
903 let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len());
904 let decommitment_value = query_polynomial(&evaluation, &queries);
905 let prover = FriProver::commit(&mut test_channel(), config, &evaluation, &twiddles);
906 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
907 let mut proof = prover.decommit_on_queries(&queries).proof;
908 proof.inner_layers[1].fri_witness.pop();
910 let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap();
911
912 let verification_result = verifier.decommit_on_queries(&queries, decommitment_value);
913
914 assert!(matches!(
915 verification_result,
916 Err(FriVerificationError::InnerLayerEvaluationsInvalid { inner_layer: 1 })
917 ));
918 }
919
920 #[test]
921 fn proof_with_invalid_inner_layer_decommitment_fails_verification() {
922 const LOG_DEGREE: u32 = 6;
923 let evaluation = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
924 let twiddles = CpuBackend::precompute_twiddles(evaluation.domain.half_coset);
925 let log_domain_size = evaluation.domain.log_size();
926 let queries = Queries::from_positions(vec![5], log_domain_size);
927 let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len());
928 let decommitment_value = query_polynomial(&evaluation, &queries);
929 let prover = FriProver::commit(&mut test_channel(), config, &evaluation, &twiddles);
930 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
931 let mut proof = prover.decommit_on_queries(&queries).proof;
932 proof.inner_layers[1].fri_witness[0] += BaseField::one();
934 let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap();
935
936 let verification_result = verifier.decommit_on_queries(&queries, decommitment_value);
937
938 assert!(matches!(
939 verification_result,
940 Err(FriVerificationError::InnerLayerCommitmentInvalid { inner_layer: 1, .. })
941 ));
942 }
943
944 #[test]
945 fn proof_with_invalid_last_layer_degree_fails_verification() {
946 const LOG_DEGREE: u32 = 6;
947 const LOG_MAX_LAST_LAYER_DEGREE: u32 = 2;
948 let evaluation = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
949 let twiddles = CpuBackend::precompute_twiddles(evaluation.domain.half_coset);
950 let log_domain_size = evaluation.domain.log_size();
951 let queries = Queries::from_positions(vec![1, 7, 8], log_domain_size);
952 let config = FriConfig::new(LOG_MAX_LAST_LAYER_DEGREE, LOG_BLOWUP_FACTOR, queries.len());
953 let prover = FriProver::commit(&mut test_channel(), config, &evaluation, &twiddles);
954 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
955 let mut proof = prover.decommit_on_queries(&queries).proof;
956 let bad_last_layer_coeffs = vec![One::one(); 1 << (LOG_MAX_LAST_LAYER_DEGREE + 1)];
957 proof.last_layer_poly = LinePoly::new(bad_last_layer_coeffs);
958
959 let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound);
960
961 assert!(matches!(
962 verifier,
963 Err(FriVerificationError::LastLayerDegreeInvalid)
964 ));
965 }
966
967 #[test]
968 fn proof_with_invalid_last_layer_fails_verification() {
969 const LOG_DEGREE: u32 = 6;
970 let evaluation = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
971 let twiddles = CpuBackend::precompute_twiddles(evaluation.domain.half_coset);
972 let log_domain_size = evaluation.domain.log_size();
973 let queries = Queries::from_positions(vec![1, 7, 8], log_domain_size);
974 let config = FriConfig::new(2, LOG_BLOWUP_FACTOR, queries.len());
975 let decommitment_value = query_polynomial(&evaluation, &queries);
976 let prover = FriProver::commit(&mut test_channel(), config, &evaluation, &twiddles);
977 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
978 let mut proof = prover.decommit_on_queries(&queries).proof;
979 proof.last_layer_poly[0] += BaseField::one();
981 let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap();
982
983 let verification_result = verifier.decommit_on_queries(&queries, decommitment_value);
984
985 assert!(matches!(
986 verification_result,
987 Err(FriVerificationError::LastLayerEvaluationsInvalid)
988 ));
989 }
990
991 #[test]
992 #[should_panic]
993 fn decommit_queries_on_invalid_domain_fails_verification() {
994 const LOG_DEGREE: u32 = 3;
995 let evaluation = polynomial_evaluation(LOG_DEGREE, LOG_BLOWUP_FACTOR);
996 let twiddles = CpuBackend::precompute_twiddles(evaluation.domain.half_coset);
997 let log_domain_size = evaluation.domain.log_size();
998 let queries = Queries::from_positions(vec![5], log_domain_size);
999 let config = FriConfig::new(1, LOG_BLOWUP_FACTOR, queries.len());
1000 let decommitment_value = query_polynomial(&evaluation, &queries);
1001 let prover = FriProver::commit(&mut test_channel(), config, &evaluation, &twiddles);
1002 let proof = prover.decommit_on_queries(&queries).proof;
1003 let bound = CirclePolyDegreeBound::new(LOG_DEGREE);
1004 let verifier = FriVerifier::commit(&mut test_channel(), config, proof, bound).unwrap();
1005 let mut invalid_queries = queries.clone();
1007 invalid_queries.log_domain_size -= 1;
1008
1009 let _ = verifier.decommit_on_queries(&invalid_queries, decommitment_value);
1010 }
1011
1012 fn polynomial_evaluation(
1016 log_degree: u32,
1017 log_blowup_factor: u32,
1018 ) -> SecureEvaluation<CpuBackend, BitReversedOrder> {
1019 let poly = CpuCirclePoly::new(vec![BaseField::one(); 1 << log_degree]);
1020 let coset = Coset::half_odds(log_degree + log_blowup_factor - 1);
1021 let domain = CircleDomain::new(coset);
1022 let values = poly.evaluate(domain);
1023 SecureEvaluation::new(domain, values.into_iter().map(SecureField::from).collect())
1024 }
1025
1026 fn log_degree_bound(polynomial: LineEvaluation<CpuBackend>) -> u32 {
1028 let coeffs = polynomial.interpolate().into_ordered_coefficients();
1029 let degree = coeffs.into_iter().rposition(|c| !c.is_zero()).unwrap_or(0);
1030 (degree + 1).ilog2()
1031 }
1032
1033 fn query_polynomial(
1034 polynomial: &SecureEvaluation<CpuBackend, BitReversedOrder>,
1035 queries: &Queries,
1036 ) -> Vec<SecureField> {
1037 let queries = queries.fold(queries.log_domain_size - polynomial.domain.log_size());
1038 query_polynomial_at_positions(polynomial, &queries.positions)
1039 }
1040
1041 fn query_polynomial_at_positions(
1042 polynomial: &SecureEvaluation<CpuBackend, BitReversedOrder>,
1043 query_positions: &[usize],
1044 ) -> Vec<SecureField> {
1045 query_positions.iter().map(|p| polynomial.at(*p)).collect()
1046 }
1047}