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}