p3_fri/
two_adic_pcs.rs

1//! The FRI PCS protocol over two-adic fields.
2//!
3//! The following implements a slight variant of the usual FRI protocol. As usual we start
4//! with a polynomial `F(x)` of degree `n` given as evaluations over the coset `gH` with `|H| = 2^n`.
5//!
6//! Now consider the polynomial `G(x) = F(gx)`. Note that `G(x)` has the same degree as `F(x)` and
7//! the evaluations of `F(x)` over `gH` are identical to the evaluations of `G(x)` over `H`.
8//!
9//! Hence we can reinterpret our vector of evaluations as evaluations of `G(x)` over `H` and apply
10//! the standard FRI protocol to this evaluation vector. This makes it easier to apply FRI to a collection
11//! of polynomials defined over different cosets as we don't need to keep track of the coset shifts. We
12//! can just assume that every polynomial is defined over the subgroup of the relevant size.
13//!
14//! If we changed our domain construction (e.g., using multiple cosets), we would need to carefully reconsider these assumptions.
15
16use alloc::vec;
17use alloc::vec::Vec;
18use core::fmt::Debug;
19use core::marker::PhantomData;
20
21use itertools::{Itertools, izip};
22use p3_challenger::{CanObserve, FieldChallenger, GrindingChallenger};
23use p3_commit::{BatchOpening, Mmcs, OpenedValues, Pcs};
24use p3_dft::TwoAdicSubgroupDft;
25use p3_field::coset::TwoAdicMultiplicativeCoset;
26use p3_field::{
27    ExtensionField, PackedFieldExtension, TwoAdicField, batch_multiplicative_inverse, dot_product,
28};
29use p3_interpolation::interpolate_coset_with_precomputation;
30use p3_matrix::Matrix;
31use p3_matrix::bitrev::{BitReversedMatrixView, BitReversibleMatrix};
32use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixView};
33use p3_maybe_rayon::prelude::*;
34use p3_util::linear_map::LinearMap;
35use p3_util::{log2_strict_usize, reverse_bits_len, reverse_slice_index_bits};
36use tracing::{info_span, instrument};
37
38use crate::verifier::{self, FriError};
39use crate::{FriFoldingStrategy, FriParameters, FriProof, prover};
40
41/// A polynomial commitment scheme using FRI to generate opening proofs.
42///
43/// We commit to a polynomial `f` via its evaluation vectors over a coset
44/// `gH` where `|H| >= 2 * deg(f)`. A value `f(z)` is opened by using a FRI
45/// proof to show that the evaluations of `(f(x) - f(z))/(x - z)` over
46/// `gH` are low degree.
47#[derive(Clone, Debug)]
48pub struct TwoAdicFriPcs<Val, Dft, InputMmcs, FriMmcs> {
49    pub(crate) dft: Dft,
50    pub(crate) mmcs: InputMmcs,
51    pub(crate) fri: FriParameters<FriMmcs>,
52    _phantom: PhantomData<Val>,
53}
54
55impl<Val, Dft, InputMmcs, FriMmcs> TwoAdicFriPcs<Val, Dft, InputMmcs, FriMmcs> {
56    pub const fn new(dft: Dft, mmcs: InputMmcs, fri: FriParameters<FriMmcs>) -> Self {
57        Self {
58            dft,
59            mmcs,
60            fri,
61            _phantom: PhantomData,
62        }
63    }
64}
65
66/// The Prover Data associated to a commitment to a collection of matrices
67/// and a list of points to open each matrix at.
68pub type ProverDataWithOpeningPoints<'a, EF, ProverData> = (
69    // The matrices and auxiliary prover data
70    &'a ProverData,
71    // for each matrix,
72    Vec<
73        // points to open
74        Vec<EF>,
75    >,
76);
77
78/// A joint commitment to a collection of matrices and their opening at
79/// a collection of points.
80pub type CommitmentWithOpeningPoints<Challenge, Commitment, Domain> = (
81    Commitment,
82    // For each matrix in the commitment:
83    Vec<(
84        // The domain of the matrix
85        Domain,
86        // A vector of (point, claimed_evaluation) pairs
87        Vec<(Challenge, Vec<Challenge>)>,
88    )>,
89);
90
91pub struct TwoAdicFriFolding<InputProof, InputError>(pub PhantomData<(InputProof, InputError)>);
92
93pub type TwoAdicFriFoldingForMmcs<F, M> =
94    TwoAdicFriFolding<Vec<BatchOpening<F, M>>, <M as Mmcs<F>>::Error>;
95
96impl<F: TwoAdicField, InputProof, InputError: Debug, EF: ExtensionField<F>>
97    FriFoldingStrategy<F, EF> for TwoAdicFriFolding<InputProof, InputError>
98{
99    type InputProof = InputProof;
100    type InputError = InputError;
101
102    fn extra_query_index_bits(&self) -> usize {
103        0
104    }
105
106    fn fold_row(
107        &self,
108        index: usize,
109        log_height: usize,
110        beta: EF,
111        evals: impl Iterator<Item = EF>,
112    ) -> EF {
113        let arity = 2;
114        let log_arity = 1;
115        let (e0, e1) = evals
116            .collect_tuple()
117            .expect("TwoAdicFriFolder only supports arity=2");
118        // If performance critical, make this API stateful to avoid this
119        // This is a bit more math than is necessary, but leaving it here
120        // in case we want higher arity in the future.
121        let subgroup_start = F::two_adic_generator(log_height + log_arity)
122            .exp_u64(reverse_bits_len(index, log_height) as u64);
123        let mut xs = F::two_adic_generator(log_arity)
124            .shifted_powers(subgroup_start)
125            .collect_n(arity);
126        reverse_slice_index_bits(&mut xs);
127        assert_eq!(log_arity, 1, "can only interpolate two points for now");
128        // interpolate and evaluate at beta
129        e0 + (beta - xs[0]) * (e1 - e0) * (xs[1] - xs[0]).inverse()
130        // Currently Algebra<F> does not include division so we do it manually.
131        // Note we do not want to do an EF division as that is far more expensive.
132    }
133
134    fn fold_matrix<M: Matrix<EF>>(&self, beta: EF, m: M) -> Vec<EF> {
135        // We use the fact that
136        //     p_e(x^2) = (p(x) + p(-x)) / 2
137        //     p_o(x^2) = (p(x) - p(-x)) / (2 x)
138        // that is,
139        //     p_e(g^(2i)) = (p(g^i) + p(g^(n/2 + i))) / 2
140        //     p_o(g^(2i)) = (p(g^i) - p(g^(n/2 + i))) / (2 g^i)
141        // so
142        //     result(g^(2i)) = p_e(g^(2i)) + beta p_o(g^(2i))
143        //
144        // As p_e, p_o will be in the extension field we want to find ways to avoid extension multiplications.
145        // We should only need a single one (namely multiplication by beta).
146        let g_inv = F::two_adic_generator(log2_strict_usize(m.height()) + 1).inverse();
147
148        // TODO: vectorize this (after we have packed extension fields)
149
150        // As beta is in the extension field, we want to avoid multiplying by it
151        // for as long as possible. Here we precompute the powers  `g_inv^i / 2` in the base field.
152        let mut halve_inv_powers = g_inv.shifted_powers(F::ONE.halve()).collect_n(m.height());
153        reverse_slice_index_bits(&mut halve_inv_powers);
154
155        m.par_rows()
156            .zip(halve_inv_powers)
157            .map(|(mut row, halve_inv_power)| {
158                let (lo, hi) = row.next_tuple().unwrap();
159                (lo + hi).halve() + (lo - hi) * beta * halve_inv_power
160            })
161            .collect()
162    }
163}
164
165impl<Val, Dft, InputMmcs, FriMmcs, Challenge, Challenger> Pcs<Challenge, Challenger>
166    for TwoAdicFriPcs<Val, Dft, InputMmcs, FriMmcs>
167where
168    Val: TwoAdicField,
169    Dft: TwoAdicSubgroupDft<Val>,
170    InputMmcs: Mmcs<Val>,
171    FriMmcs: Mmcs<Challenge>,
172    Challenge: ExtensionField<Val>,
173    Challenger:
174        FieldChallenger<Val> + CanObserve<FriMmcs::Commitment> + GrindingChallenger<Witness = Val>,
175{
176    type Domain = TwoAdicMultiplicativeCoset<Val>;
177    type Commitment = InputMmcs::Commitment;
178    type ProverData = InputMmcs::ProverData<RowMajorMatrix<Val>>;
179    type EvaluationsOnDomain<'a> = BitReversedMatrixView<RowMajorMatrixView<'a, Val>>;
180    type Proof = FriProof<Challenge, FriMmcs, Val, Vec<BatchOpening<Val, InputMmcs>>>;
181    type Error = FriError<FriMmcs::Error, InputMmcs::Error>;
182    const ZK: bool = false;
183
184    /// Get the unique subgroup `H` of size `|H| = degree`.
185    ///
186    /// # Panics:
187    /// This function will panic if `degree` is not a power of 2 or `degree > (1 << Val::TWO_ADICITY)`.
188    fn natural_domain_for_degree(&self, degree: usize) -> Self::Domain {
189        TwoAdicMultiplicativeCoset::new(Val::ONE, log2_strict_usize(degree)).unwrap()
190    }
191
192    /// Commit to a collection of evaluation matrices.
193    ///
194    /// Each element of `evaluations` contains a coset `shift * H` and a matrix `mat` with `mat.height() = |H|`.
195    /// Interpreting each column of `mat` as the evaluations of a polynomial `p_i(x)` over `shift * H`,
196    /// this computes the evaluations of `p_i` over `gK` where `g` is the chosen generator of the multiplicative group
197    /// of `Val` and `K` is the unique subgroup of order `|H| << self.fri.log_blowup`.
198    ///
199    /// This then outputs a Merkle commitment to these evaluations.
200    fn commit(
201        &self,
202        evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val>)>,
203    ) -> (Self::Commitment, Self::ProverData) {
204        let ldes: Vec<_> = evaluations
205            .into_iter()
206            .map(|(domain, evals)| {
207                assert_eq!(domain.size(), evals.height());
208                // coset_lde_batch converts from evaluations over `xH` to evaluations over `shift * x * K`.
209                // Hence, letting `shift = g/x` the output will be evaluations over `gK` as desired.
210                // When `x = g`, we could just use the standard LDE but currently this doesn't seem
211                // to give a meaningful performance boost.
212                let shift = Val::GENERATOR / domain.shift();
213                // Compute the LDE with blowup factor fri.log_blowup.
214                // We bit reverse as this is required by our implementation of the FRI protocol.
215                self.dft
216                    .coset_lde_batch(evals, self.fri.log_blowup, shift)
217                    .bit_reverse_rows()
218                    .to_row_major_matrix()
219            })
220            .collect();
221
222        // Commit to the bit-reversed LDEs.
223        self.mmcs.commit(ldes)
224    }
225
226    fn get_quotient_ldes(
227        &self,
228        evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val>)>,
229        _num_chunks: usize,
230    ) -> Vec<RowMajorMatrix<Val>> {
231        evaluations
232            .into_iter()
233            .map(|(domain, evals)| {
234                assert_eq!(domain.size(), evals.height());
235                // coset_lde_batch converts from evaluations over `xH` to evaluations over `shift * x * K`.
236                // Hence, letting `shift = g/x` the output will be evaluations over `gK` as desired.
237                // When `x = g`, we could just use the standard LDE but currently this doesn't seem
238                // to give a meaningful performance boost.
239                let shift = Val::GENERATOR / domain.shift();
240                // Compute the LDE with blowup factor fri.log_blowup.
241                // We bit reverse as this is required by our implementation of the FRI protocol.
242                self.dft
243                    .coset_lde_batch(evals, self.fri.log_blowup, shift)
244                    .bit_reverse_rows()
245                    .to_row_major_matrix()
246            })
247            .collect()
248    }
249
250    fn commit_ldes(&self, ldes: Vec<RowMajorMatrix<Val>>) -> (Self::Commitment, Self::ProverData) {
251        self.mmcs.commit(ldes)
252    }
253
254    /// Given the evaluations on a domain `gH`, return the evaluations on a different domain `g'K`.
255    ///
256    /// Arguments:
257    /// - `prover_data`: The prover data containing all committed evaluation matrices.
258    /// - `idx`: The index of the matrix containing the evaluations we want. These evaluations
259    ///   are assumed to be over the coset `gH` where `g = Val::GENERATOR`.
260    /// - `domain`: The domain `g'K` on which to get evaluations on. Currently, this assumes that
261    ///   `g' = g` and `K` is a subgroup of `H` and panics if this is not the case.
262    fn get_evaluations_on_domain<'a>(
263        &self,
264        prover_data: &'a Self::ProverData,
265        idx: usize,
266        domain: Self::Domain,
267    ) -> Self::EvaluationsOnDomain<'a> {
268        // todo: handle extrapolation for LDEs we don't have
269        assert_eq!(domain.shift(), Val::GENERATOR);
270        let lde = self.mmcs.get_matrices(prover_data)[idx];
271        assert!(lde.height() >= domain.size());
272        lde.split_rows(domain.size()).0.bit_reverse_rows()
273    }
274
275    /// Open a batch of matrices at a collection of points.
276    ///
277    /// Returns the opened values along with a proof.
278    ///
279    /// This function assumes that all matrices correspond to evaluations over the
280    /// coset `gH` where `g = Val::GENERATOR` and `H` is a subgroup of appropriate size depending on the
281    /// matrix.
282    fn open(
283        &self,
284        // For each multi-matrix commitment,
285        commitment_data_with_opening_points: Vec<(
286            // The matrices and auxiliary prover data
287            &Self::ProverData,
288            // for each matrix,
289            Vec<
290                // points to open
291                Vec<Challenge>,
292            >,
293        )>,
294        challenger: &mut Challenger,
295    ) -> (OpenedValues<Challenge>, Self::Proof) {
296        /*
297
298        A quick rundown of the optimizations in this function:
299        We are trying to compute sum_i alpha^i * (p(X) - y)/(X - z),
300        for each z an opening point, y = p(z). Each p(X) is given as evaluations in bit-reversed order
301        in the columns of the matrices. y is computed by barycentric interpolation.
302        X and p(X) are in the base field; alpha, y and z are in the extension.
303        The primary goal is to minimize extension multiplications.
304
305        - Instead of computing all alpha^i, we just compute alpha^i for i up to the largest width
306        of a matrix, then multiply by an "alpha offset" when accumulating.
307              a^0 x0 + a^1 x1 + a^2 x2 + a^3 x3 + ...
308            = ( a^0 x0 + a^1 x1 ) + a^2 ( a^0 x2 + a^1 x3 ) + ...
309            (see `alpha_pows`, `alpha_pow_offset`, `num_reduced`)
310
311        - For each unique point z, we precompute 1/(X-z) for the largest subgroup opened at this point.
312        Since we compute it in bit-reversed order, smaller subgroups can simply truncate the vector.
313            (see `inv_denoms`)
314
315        - Then, for each matrix (with columns p_i) and opening point z, we want:
316            for each row (corresponding to subgroup element X):
317                reduced[X] += alpha_offset * sum_i [ alpha^i * inv_denom[X] * (p_i[X] - y[i]) ]
318
319            We can factor out inv_denom, and expand what's left:
320                reduced[X] += alpha_offset * inv_denom[X] * sum_i [ alpha^i * p_i[X] - alpha^i * y[i] ]
321
322            And separate the sum:
323                reduced[X] += alpha_offset * inv_denom[X] * [ sum_i [ alpha^i * p_i[X] ] - sum_i [ alpha^i * y[i] ] ]
324
325            And now the last sum doesn't depend on X, so we can precompute that for the matrix, too.
326            So the hot loop (that depends on both X and i) is just:
327                sum_i [ alpha^i * p_i[X] ]
328
329            with alpha^i an extension, p_i[X] a base
330
331        */
332
333        // Contained in each `Self::ProverData` is a list of matrices which have been committed to.
334        // We extract those matrices to be able to refer to them directly.
335        let commitment_data_with_opening_pts = commitment_data_with_opening_points
336            .iter()
337            .map(|(data, points)| (*data, points.clone()))
338            .collect::<Vec<_>>();
339        let mats_and_points = commitment_data_with_opening_pts
340            .iter()
341            .map(|(data, points)| {
342                let mats = self
343                    .mmcs
344                    .get_matrices(data)
345                    .into_iter()
346                    .map(|m| m.as_view())
347                    .collect_vec();
348                debug_assert_eq!(
349                    mats.len(),
350                    points.len(),
351                    "each matrix should have a corresponding set of evaluation points"
352                );
353                (mats, points)
354            })
355            .collect_vec();
356
357        // Find the maximum height and the maximum width of matrices in the batch.
358        // These do not need to correspond to the same matrix.
359        let (global_max_height, global_max_width) = mats_and_points
360            .iter()
361            .flat_map(|(mats, _)| mats.iter().map(|m| (m.height(), m.width())))
362            .reduce(|(hmax, wmax), (h, w)| (hmax.max(h), wmax.max(w)))
363            .expect("No Matrices Supplied?");
364        let log_global_max_height = log2_strict_usize(global_max_height);
365
366        // Get all values of the coset `gH` for the largest necessary subgroup `H`.
367        // We also bit reverse which means that coset has the nice property that
368        // `coset[..2^i]` contains the values of `gK` for `|K| = 2^i`.
369        let coset = {
370            let coset =
371                TwoAdicMultiplicativeCoset::new(Val::GENERATOR, log_global_max_height).unwrap();
372            let mut coset_points = coset.iter().collect();
373            reverse_slice_index_bits(&mut coset_points);
374            coset_points
375        };
376
377        // For each unique opening point z, we will find the largest degree bound
378        // for that point, and precompute 1/(z - X) for the largest subgroup (in bitrev order).
379        let inv_denoms = compute_inverse_denominators(&mats_and_points, &coset);
380
381        // Evaluate coset representations and write openings to the challenger
382        let all_opened_values = mats_and_points
383            .iter()
384            .map(|(mats, points)| {
385                // For each collection of matrices
386                izip!(mats.iter(), points.iter())
387                    .map(|(mat, points_for_mat)| {
388                        // TODO: This assumes that every input matrix has a blowup of at least self.fri.log_blowup.
389                        // If the blow_up factor is smaller than self.fri.log_blowup, this will lead to errors.
390                        // If it is bigger, we shouldn't get any errors but it will be slightly slower.
391                        // Ideally, polynomials could be passed in with their blow_up factors known.
392
393                        // The point of this correction is that each column of the matrix corresponds to a low degree polynomial.
394                        // Hence we can save time by restricting the height of the matrix to be the minimal height which
395                        // uniquely identifies the polynomial.
396                        let h = mat.height() >> self.fri.log_blowup;
397
398                        // `subgroup` and `mat` are both in bit-reversed order, so we can truncate.
399                        let (low_coset, _) = mat.split_rows(h);
400                        let coset_h = &coset[..h];
401
402                        points_for_mat
403                            .iter()
404                            .map(|&point| {
405                                let _guard =
406                                    info_span!("evaluate matrix", dims = %mat.dimensions())
407                                        .entered();
408
409                                // Use Barycentric interpolation to evaluate each column of the matrix at the given point.
410                                let ys =
411                                    info_span!("compute opened values with Lagrange interpolation")
412                                        .in_scope(|| {
413                                            // Get the relevant inverse denominators for this point and use these to
414                                            // interpolate to get the evaluation of each polynomial in the matrix
415                                            // at the desired point.
416                                            let inv_denoms = &inv_denoms.get(&point).unwrap()[..h];
417                                            interpolate_coset_with_precomputation(
418                                                &low_coset,
419                                                Val::GENERATOR,
420                                                point,
421                                                coset_h,
422                                                inv_denoms,
423                                            )
424                                        });
425
426                                challenger.observe_algebra_slice(&ys);
427                                ys
428                            })
429                            .collect_vec()
430                    })
431                    .collect_vec()
432            })
433            .collect_vec();
434
435        // Batch combination challenge
436
437        // Soundness Error:
438        // See the discussion in the doc comment of [`prove_fri`]. Essentially, the soundness error
439        // for this sample is tightly tied to the soundness error of the FRI protocol.
440        // Roughly speaking, at a minimum is it k/|EF| where `k` is the sum of, for each function, the number of
441        // points it needs to be opened at. This comes from the fact that we are taking a large linear combination
442        // of `(f(zeta) - f(x))/(zeta - x)` for each function `f` and all of `f`'s opening points.
443        // In our setup, k is two times the trace width plus the number of quotient polynomials.
444        let alpha: Challenge = challenger.sample_algebra_element();
445
446        // We precompute powers of alpha as we need the same powers for each matrix.
447        // We compute both a vector of unpacked powers and a vector of packed powers.
448        // TODO: It should be possible to refactor this to only use the packed powers but
449        // this is not a bottleneck so is not a priority.
450        let packed_alpha_powers =
451            Challenge::ExtensionPacking::packed_ext_powers_capped(alpha, global_max_width)
452                .collect_vec();
453        let alpha_powers =
454            Challenge::ExtensionPacking::to_ext_iter(packed_alpha_powers.iter().copied())
455                .collect_vec();
456
457        // Now that we have sent the openings to the verifier, it remains to prove
458        // that those openings are correct.
459
460        // Given a low degree polynomial `f(x)` with claimed evaluation `f(zeta)`, we can check
461        // that `f(zeta)` is correct by doing a low degree test on `(f(zeta) - f(x))/(zeta - x)`.
462        // We will use `alpha` to batch together both different claimed openings `zeta` and
463        // different polynomials `f` whose evaluation vectors have the same height.
464
465        // TODO: If we allow different polynomials to have different blow_up factors
466        // we may need to revisit this and to ensure it is safe to batch them together.
467
468        // num_reduced records the number of (function, opening point) pairs for each `log_height`.
469        // TODO: This should really be `[0; Val::TWO_ADICITY]` but that runs into issues with generics.
470        let mut num_reduced = [0; 32];
471
472        // For each `log_height` from 2^1 -> 2^32, reduced_openings will contain either `None`
473        // if there are no matrices of that height, or `Some(vec)` where `vec` is equal to
474        // a weighted sum of `(f(zeta) - f(x))/(zeta - x)` over all `f`'s of that height and
475        // for each `f`, all opening points `zeta`. The sum is weighted by powers of the challenge alpha.
476        let mut reduced_openings: [_; 32] = core::array::from_fn(|_| None);
477
478        for ((mats, points), openings_for_round) in
479            mats_and_points.iter().zip(all_opened_values.iter())
480        {
481            for (mat, points_for_mat, openings_for_mat) in
482                izip!(mats.iter(), points.iter(), openings_for_round.iter())
483            {
484                let _guard =
485                    info_span!("reduce matrix quotient", dims = %mat.dimensions()).entered();
486
487                let log_height = log2_strict_usize(mat.height());
488
489                // If this is our first matrix at this height, initialise reduced_openings to zero.
490                // Otherwise, get a mutable reference to it.
491                let reduced_opening_for_log_height = reduced_openings[log_height]
492                    .get_or_insert_with(|| vec![Challenge::ZERO; mat.height()]);
493                debug_assert_eq!(reduced_opening_for_log_height.len(), mat.height());
494
495                // Treating our matrix M as the evaluations of functions f_0, f_1, ...
496                // Compute the evaluations of `Mred(x) = f_0(x) + alpha*f_1(x) + ...`
497                let mat_compressed = info_span!("compress mat").in_scope(|| {
498                    // This will be reused for all points z which M is opened at so we collect into a vector.
499                    mat.rowwise_packed_dot_product::<Challenge>(&packed_alpha_powers)
500                        .collect::<Vec<_>>()
501                });
502
503                for (&point, openings) in points_for_mat.iter().zip(openings_for_mat) {
504                    // If we have multiple matrices at the same height, we need to scale alpha to combine them.
505                    // This means that reduced_openings will contain:
506                    // Mred_0(x) + alpha^{M_0.width()}Mred_1(x) + alpha^{M_0.width() + M_1.width()}Mred_2(x) + ...
507                    // Where M_0, M_1, ... are the matrices of the same height.
508                    let alpha_pow_offset = alpha.exp_u64(num_reduced[log_height] as u64);
509
510                    // As we have all the openings `f_i(z)`, we can combine them using `alpha`
511                    // in an identical way to before to compute `Mred(z)`.
512                    let reduced_openings: Challenge =
513                        dot_product(alpha_powers.iter().copied(), openings.iter().copied());
514
515                    mat_compressed
516                        .par_iter()
517                        .zip(reduced_opening_for_log_height.par_iter_mut())
518                        // inv_denoms contains `1/(z - x)` for `x` in a coset `gK`.
519                        // If `|K| =/= mat.height()` we actually want a subset of this
520                        // corresponding to the evaluations over `gH` for `|H| = mat.height()`.
521                        // As inv_denoms is bit reversed, the evaluations over `gH` are exactly
522                        // the evaluations over `gK` at the indices `0..mat.height()`.
523                        // So zip will truncate to the desired smaller length.
524                        .zip(inv_denoms.get(&point).unwrap().par_iter())
525                        // Map the function `Mred(x) -> (Mred(z) - Mred(x))/(z - x)`
526                        // across the evaluation vector of `Mred(x)`. Adjust by alpha_pow_offset
527                        // as needed.
528                        .for_each(|((&reduced_row, ro), &inv_denom)| {
529                            *ro += alpha_pow_offset * (reduced_openings - reduced_row) * inv_denom;
530                        });
531                    num_reduced[log_height] += mat.width();
532                }
533            }
534        }
535
536        // It remains to prove that all evaluation vectors in reduced_openings correspond to
537        // low degree functions.
538        let fri_input = reduced_openings.into_iter().rev().flatten().collect_vec();
539
540        let folding: TwoAdicFriFoldingForMmcs<Val, InputMmcs> = TwoAdicFriFolding(PhantomData);
541
542        // Produce the FRI proof.
543        let fri_proof = prover::prove_fri(
544            &folding,
545            &self.fri,
546            fri_input,
547            challenger,
548            log_global_max_height,
549            &commitment_data_with_opening_pts,
550            &self.mmcs,
551        );
552
553        (all_opened_values, fri_proof)
554    }
555
556    fn verify(
557        &self,
558        // For each commitment:
559        commitments_with_opening_points: Vec<
560            CommitmentWithOpeningPoints<Challenge, Self::Commitment, Self::Domain>,
561        >,
562        proof: &Self::Proof,
563        challenger: &mut Challenger,
564    ) -> Result<(), Self::Error> {
565        // Write all evaluations to challenger.
566        // Need to ensure to do this in the same order as the prover.
567        for (_, round) in &commitments_with_opening_points {
568            for (_, mat) in round {
569                for (_, point) in mat {
570                    challenger.observe_algebra_slice(point);
571                }
572            }
573        }
574
575        let folding: TwoAdicFriFoldingForMmcs<Val, InputMmcs> = TwoAdicFriFolding(PhantomData);
576
577        verifier::verify_fri(
578            &folding,
579            &self.fri,
580            proof,
581            challenger,
582            &commitments_with_opening_points,
583            &self.mmcs,
584        )?;
585
586        Ok(())
587    }
588}
589
590/// Compute vectors of inverse denominators for each unique opening point.
591///
592/// Arguments:
593/// - `mats_and_points` is a list of matrices and for each matrix a list of points. We assume that
594///    the total number of distinct points is very small as several methods contained herein are `O(n^2)`
595///    in the number of points.
596/// - `coset` is the set of points `gH` where `H` a two-adic subgroup such that `|H|` is greater
597///     than or equal to the largest height of any matrix in `mats_and_points`. The values
598///     in `coset` must be in bit-reversed order.
599///
600/// For each point `z`, let `M` be the matrix of largest height which opens at `z`.
601/// let `H_z` be the unique subgroup of order `M.height()`. Compute the vector of
602/// `1/(z - x)` for `x` in `gH_z`.
603///
604/// Return a LinearMap which allows us to recover the computed vectors for each `z`.
605#[instrument(skip_all)]
606fn compute_inverse_denominators<F: TwoAdicField, EF: ExtensionField<F>, M: Matrix<F>>(
607    mats_and_points: &[(Vec<M>, &Vec<Vec<EF>>)],
608    coset: &[F],
609) -> LinearMap<EF, Vec<EF>> {
610    // For each `z`, find the maximal height of any matrix which we need to
611    // open at `z`.
612    let mut max_log_height_for_point: LinearMap<EF, usize> = LinearMap::new();
613    for (mats, points) in mats_and_points {
614        for (mat, points_for_mat) in izip!(mats, *points) {
615            let log_height = log2_strict_usize(mat.height());
616            for &z in points_for_mat {
617                if let Some(lh) = max_log_height_for_point.get_mut(&z) {
618                    *lh = core::cmp::max(*lh, log_height);
619                } else {
620                    max_log_height_for_point.insert(z, log_height);
621                }
622            }
623        }
624    }
625
626    // Compute the inverse denominators for each point `z`.
627    max_log_height_for_point
628        .into_iter()
629        .map(|(z, log_height)| {
630            (
631                z,
632                batch_multiplicative_inverse(
633                    // As coset is stored in bit-reversed order,
634                    // we can just take the first `2^log_height` elements.
635                    &coset[..(1 << log_height)]
636                        .iter()
637                        .map(|&x| z - x)
638                        .collect_vec(),
639                ),
640            )
641        })
642        .collect()
643}