Skip to main content

stwo_gpu/core/
fri.rs

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/// FRI proof config
30// TODO(andrew): Support different step sizes.
31#[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    // TODO(andrew): fold_steps.
37}
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    /// Creates a new FRI configuration.
51    ///
52    /// # Panics
53    ///
54    /// Panics if:
55    /// * `log_last_layer_degree_bound` is greater than 10.
56    /// * `log_blowup_factor` is equal to zero or greater than 16.
57    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    // TODO(andrew): The first layer currently commits to all input polynomials. Consider allowing
79    // flexibility to only commit to input polynomials on a per-log-size basis. This allows
80    // flexibility for cases where committing to the first layer, for a specific log size, isn't
81    // necessary. FRI would simply return more query positions for the "uncommitted" log sizes.
82    first_layer: FriFirstLayerVerifier<MC::H>,
83    inner_layers: Vec<FriInnerLayerVerifier<MC::H>>,
84    last_layer_domain: LineDomain,
85    last_layer_poly: LinePoly,
86    /// The queries used for decommitment. Initialized when calling
87    /// [`FriVerifier::sample_query_positions()`].
88    queries: Option<Queries>,
89}
90
91impl<MC: MerkleChannel> FriVerifier<MC> {
92    /// Verifies the commitment stage of FRI.
93    ///
94    /// `column_bound` should be the committed circle polynomial degree bound.
95    ///
96    /// # Errors
97    ///
98    /// An `Err` will be returned if:
99    /// * The proof contains an invalid number of FRI layers.
100    /// * The degree of the last layer polynomial is too high.
101    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    /// Verifies the decommitment stage of FRI.
165    ///
166    /// The query evals need to be provided in the same order as their commitment.
167    ///
168    /// # Panics
169    ///
170    /// Panics if:
171    /// * The queries were not yet sampled.
172    /// * The queries were sampled on the wrong domain size.
173    /// * There aren't the same number of decommitted values as degree bounds.
174    // TODO(andrew): Finish docs.
175    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    /// Verifies the first layer decommitment.
197    ///
198    /// Returns the queries and first layer folded column evaluations needed for
199    /// verifying the remaining layers.
200    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    /// Verifies all inner layer decommitments.
209    ///
210    /// Returns the queries and query evaluations needed for verifying the last FRI layer.
211    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        // Fold the first layer.
221        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            // Verify the layer and fold it using the current layer's folding alpha.
231            (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    /// Verifies the last layer.
239    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    /// Samples and returns query positions mapped by column log size.
262    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    /// Maps a circle polynomial's degree bound to the degree bound of the univariate (line)
316    /// polynomial it gets folded into.
317    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    /// Returns [None] if the unfolded degree bound is smaller than the folding factor.
343    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/// A FRI proof.
354#[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/// Auxiliary data produced by the prover.
362#[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
374/// Number of folds for univariate polynomials.
375// TODO(andrew): Support different step sizes.
376pub const FOLD_STEP: u32 = 1;
377
378/// Number of folds when folding a circle polynomial to univariate polynomial.
379pub const CIRCLE_TO_LINE_FOLD_STEP: u32 = 1;
380
381/// Proof of an individual FRI layer.
382#[derive(Clone, Debug, Serialize, Deserialize)]
383pub struct FriLayerProof<H: MerkleHasherLifted> {
384    /// Values that the verifier needs but cannot deduce from previous computations, in the
385    /// order they are needed. This complements the values that were queried. These must be
386    /// supplied directly to the verifier.
387    pub fri_witness: Vec<SecureField>,
388    pub decommitment: MerkleDecommitmentLifted<H>,
389    pub commitment: H::Hash,
390}
391
392/// Auxiliary data for a single FRI layer.
393#[derive(Clone, Debug, Serialize, Deserialize)]
394pub struct FriLayerProofAux<H: MerkleHasherLifted> {
395    /// For each column (of different size), the values of all nodes that participate in the
396    /// decommitment.
397    // TODO(lior): Remove the `Vec<>` once mixed-degree Merkle is removed.
398    pub all_values: Vec<HashMap<usize, QM31>>,
399    /// The auxiliary data for the merkle decommitment.
400    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    /// The commitment domain all the circle polynomials in the first layer.
411    column_commitment_domain: CircleDomain,
412    folding_alpha: SecureField,
413    proof: FriLayerProof<H>,
414}
415
416impl<H: MerkleHasherLifted> FriFirstLayerVerifier<H> {
417    /// Verifies the first layer's merkle decommitment, and returns the evaluations needed to fold
418    /// the committed column.
419    ///
420    /// # Errors
421    ///
422    /// An `Err` will be returned if:
423    /// * The proof doesn't store enough evaluations.
424    /// * The merkle decommitment is invalid.
425    ///
426    /// # Panics
427    ///
428    /// Panics if:
429    /// * The queries are sampled on the wrong domain.
430    /// * There are an invalid number of provided column evals.
431    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        // A QM31 column is committed as 4 M31 columns.
453        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        // Check all proof evals have been consumed.
467        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    /// Verifies the layer's merkle decommitment and returns the the folded queries and query evals.
497    ///
498    /// # Errors
499    ///
500    /// An `Err` will be returned if:
501    /// * The proof doesn't store the correct number of evaluations.
502    /// * The merkle decommitment is invalid.
503    ///
504    /// # Panics
505    ///
506    /// Panics if:
507    /// * The number of queries doesn't match the number of evals.
508    /// * The queries are sampled on the wrong domain.
509    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        // Check all proof evals have been consumed.
532        if fri_witness.next().is_some() {
533            return Err(FriVerificationError::InnerLayerEvaluationsInvalid {
534                inner_layer: self.layer_index,
535            });
536        }
537
538        // A QM31 column is committed as 4 M31 columns.
539        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
575/// Returns a column's merkle tree decommitment positions and re-builds the evaluations needed by
576/// the verifier for folding and decommitment.
577///
578/// # Panics
579///
580/// Panics if the number of queries doesn't match the number of query evals.
581fn 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    // Group queries by the subset they reside in.
594    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
620/// Foldable subsets of evaluations on a [`CircleCoefficients`] or [`LinePoly`].
621///
622/// [`CircleCoefficients`]: crate::core::poly::circle::CircleCoefficients
623struct SparseEvaluation {
624    // TODO(andrew): Perhaps subset isn't the right word. Coset, Subgroup?
625    subset_evals: Vec<Vec<SecureField>>,
626    subset_domain_initial_indexes: Vec<usize>,
627}
628
629impl SparseEvaluation {
630    /// # Panics
631    ///
632    /// Panics if a subset size doesn't equal `2^FOLD_STEP` or there aren't the same number of
633    /// domain indexes as subsets.
634    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
672/// Folds a degree `d` polynomial into a degree `d/2` polynomial.
673/// See [`crate::prover::fri::FriOps::fold_line`].
674pub 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            // TODO(andrew): Inefficient. Update when domain twiddles get stored in a buffer.
688            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
699/// Folds and accumulates a degree `d` circle polynomial into a degree `d/2` univariate
700/// polynomial.
701/// See [`crate::prover::fri::FriOps::fold_circle_into_line`].
702pub 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            // TODO(andrew): Inefficient. Update when domain twiddles get stored in a buffer.
717            let p = src_domain.at(bit_reverse_index(
718                i << CIRCLE_TO_LINE_FOLD_STEP,
719                src_domain.log_size(),
720            ));
721
722            // Calculate `f0(px)` and `f1(px)` such that `2f(p) = f0(px) + py * f1(px)`.
723            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    /// Default blowup factor used for tests.
759    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        // Coefficients are bit-reversed.
768        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        // Set verifier's config to expect one extra layer than prover config.
862        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        // Set verifier's config to expect one less layer than prover config.
885        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        // Remove an evaluation from the second layer's proof.
909        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        // Modify the committed values in the second layer.
933        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        // Compromise the last layer polynomial's first coefficient.
980        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        // Simulate the verifier sampling queries on a smaller domain.
1006        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    /// Returns an evaluation of a random polynomial with degree `2^log_degree`.
1013    ///
1014    /// The evaluation domain size is `2^(log_degree + log_blowup_factor)`.
1015    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    /// Returns the log degree bound of a polynomial.
1027    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}