lib_q_stark_commit/pcs.rs
1//! Traits for polynomial commitment schemes.
2
3use alloc::vec::Vec;
4use core::fmt::Debug;
5
6use lib_q_stark_field::ExtensionField;
7use lib_q_stark_matrix::Matrix;
8use lib_q_stark_matrix::dense::RowMajorMatrix;
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11
12use crate::PolynomialSpace;
13
14pub type Val<D> = <D as PolynomialSpace>::Val;
15
16/// A polynomial commitment scheme, for committing to batches of polynomials defined by their evaluations
17/// over some domain.
18///
19/// In general this does not have to be a hiding commitment scheme but it might be for some implementations.
20// TODO: Should we have a super-trait for weakly-binding PCSs, like FRI outside unique decoding radius?
21pub trait Pcs<Challenge, Challenger>
22where
23 Challenge: ExtensionField<Val<Self::Domain>>,
24{
25 /// The class of evaluation domains that this commitment scheme works over.
26 type Domain: PolynomialSpace;
27
28 /// The commitment that's sent to the verifier.
29 type Commitment: Clone + Serialize + DeserializeOwned;
30
31 /// Data that the prover stores for committed polynomials, to help the prover with opening.
32 type ProverData;
33
34 /// Type of the output of `get_evaluations_on_domain`.
35 type EvaluationsOnDomain<'a>: Matrix<Val<Self::Domain>> + 'a;
36
37 /// The opening argument.
38 type Proof: Clone + Serialize + DeserializeOwned;
39
40 /// The type of a proof verification error.
41 type Error: Debug;
42
43 /// Set to true to activate randomization and achieve zero-knowledge.
44 const ZK: bool;
45
46 /// Index of the trace commitment in the computed opened values.
47 const TRACE_IDX: usize = Self::ZK as usize;
48
49 /// Index of the quotient commitments in the computed opened values.
50 const QUOTIENT_IDX: usize = Self::TRACE_IDX + 1;
51
52 /// Index of the preprocessed trace commitment in the computed opened values.
53 const PREPROCESSED_TRACE_IDX: usize = Self::QUOTIENT_IDX + 1; // Note: not always present
54
55 /// This should return a domain such that `Domain::next_point` returns `Some`.
56 ///
57 /// # Panics
58 /// Implementations backed by a two-adic domain (the only kind in this workspace today) will
59 /// panic if `degree` is not a power of two, or exceeds the field's two-adicity. Callers that
60 /// process untrusted/adversarial `degree` values (e.g. a proof verifier) MUST validate `degree`
61 /// themselves before calling this, or use [`try_natural_domain_for_degree`](Self::try_natural_domain_for_degree) instead.
62 fn natural_domain_for_degree(&self, degree: usize) -> Self::Domain;
63
64 /// Fallible sibling of [`natural_domain_for_degree`](Self::natural_domain_for_degree), for
65 /// callers (such as proof verifiers) that must reject an out-of-range `degree` instead of
66 /// panicking. Returns `None` exactly under the conditions documented on
67 /// `natural_domain_for_degree`'s `# Panics` section.
68 ///
69 /// The default implementation simply delegates to the infallible method, so it is only
70 /// non-panicking for implementations that override it; `TwoAdicFriPcs` and `HidingFriPcs`
71 /// (the only implementations in this workspace, in `lib-q-stark-fri`) both override it with a
72 /// genuinely non-panicking check.
73 fn try_natural_domain_for_degree(&self, degree: usize) -> Option<Self::Domain> {
74 Some(self.natural_domain_for_degree(degree))
75 }
76
77 /// Given a collection of evaluation matrices, produce a binding commitment to
78 /// the polynomials defined by those evaluations. If `zk` is enabled, the evaluations are
79 /// first randomized as explained in Section 3 of <https://eprint.iacr.org/2024/1037.pdf>.
80 ///
81 /// Returns both the commitment which should be sent to the verifier
82 /// and the prover data which can be used to produce opening proofs.
83 #[allow(clippy::type_complexity)]
84 fn commit(
85 &self,
86 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
87 ) -> (Self::Commitment, Self::ProverData);
88
89 /// Commit to the quotient polynomial. We first decompose the quotient polynomial into
90 /// `num_chunks` many smaller polynomials each of degree `degree / num_chunks`.
91 /// This can have minor performance benefits, but is not strictly necessary in the non `zk` case.
92 /// When `zk` is enabled, this commitment will additionally include some randomization process
93 /// to hide the inputs.
94 ///
95 /// ### Arguments
96 /// - `quotient_domain` the domain of the quotient polynomial.
97 /// - `quotient_evaluations` the evaluations of the quotient polynomial over the domain. This should be in
98 /// standard (not bit-reversed) order.
99 /// - `num_chunks` the number of smaller polynomials to decompose the quotient polynomial into.
100 #[allow(clippy::type_complexity)]
101 fn commit_quotient(
102 &self,
103 quotient_domain: Self::Domain,
104 quotient_evaluations: RowMajorMatrix<Val<Self::Domain>>,
105 num_chunks: usize,
106 ) -> (Self::Commitment, Self::ProverData) {
107 let quotient_sub_evaluations =
108 quotient_domain.split_evals(num_chunks, quotient_evaluations);
109 let quotient_sub_domains = quotient_domain.split_domains(num_chunks);
110 let ldes = self.get_quotient_ldes(
111 quotient_sub_domains
112 .into_iter()
113 .zip(quotient_sub_evaluations),
114 num_chunks,
115 );
116 self.commit_ldes(ldes)
117 }
118
119 /// When committing to quotient polynomials in batch-STARK, it is simpler to first compute
120 /// the LDE evaluations before batch-committing. When `zk` is enabled, this may add randomization.
121 fn get_quotient_ldes(
122 &self,
123 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
124 num_chunks: usize,
125 ) -> Vec<RowMajorMatrix<Val<Self::Domain>>>;
126
127 /// Commits to a collection of LDE evaluation matrices.
128 fn commit_ldes(
129 &self,
130 ldes: Vec<RowMajorMatrix<Val<Self::Domain>>>,
131 ) -> (Self::Commitment, Self::ProverData);
132
133 /// Same as `commit`; used when the committed data is preprocessing (e.g. fixed trace).
134 fn commit_preprocessing(
135 &self,
136 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
137 ) -> (Self::Commitment, Self::ProverData) {
138 self.commit(evaluations)
139 }
140
141 /// Given prover data corresponding to a commitment to a collection of evaluation matrices,
142 /// return the evaluations of those matrices on the given domain.
143 ///
144 /// This is essentially a no-op when called with a `domain` which is a subset of the evaluation domain
145 /// on which the evaluation matrices are defined.
146 fn get_evaluations_on_domain<'a>(
147 &self,
148 prover_data: &'a Self::ProverData,
149 idx: usize,
150 domain: Self::Domain,
151 ) -> Self::EvaluationsOnDomain<'a>;
152
153 /// Like `get_evaluations_on_domain` but without applying ZK randomization (e.g. for quotient domain).
154 fn get_evaluations_on_domain_no_random<'a>(
155 &self,
156 prover_data: &'a Self::ProverData,
157 idx: usize,
158 domain: Self::Domain,
159 ) -> Self::EvaluationsOnDomain<'a> {
160 self.get_evaluations_on_domain(prover_data, idx, domain)
161 }
162
163 /// Open a collection of polynomial commitments at a set of points. Produce the values at those points along with a proof
164 /// of correctness.
165 ///
166 /// Arguments:
167 /// - `commitment_data_with_opening_points`: A vector whose elements are a pair:
168 /// - `data`: The prover data corresponding to a multi-matrix commitment.
169 /// - `opening_points`: A vector containing, for each matrix committed to, a vector of opening points.
170 /// - `fiat_shamir_challenger`: The challenger that will be used to generate the proof.
171 ///
172 /// Unwrapping the arguments further, each `data` contains a vector of the committed matrices (`matrices = Vec<M>`).
173 /// If the length of `matrices` is not equal to the length of `opening_points` the function will error. Otherwise, for
174 /// each index `i`, the matrix `M = matrices[i]` will be opened at the points `opening_points[i]`.
175 ///
176 /// This means that each column of `M` will be interpreted as the evaluation vector of some polynomial
177 /// and we will compute the value of all of those polynomials at `opening_points[i]`.
178 ///
179 /// The domains on which the evaluation vectors are defined is not part of the arguments here
180 /// but should be public information known to both the prover and verifier.
181 fn open(
182 &self,
183 // For each multi-matrix commitment,
184 commitment_data_with_opening_points: Vec<(
185 // The matrices and auxiliary prover data
186 &Self::ProverData,
187 // for each matrix,
188 Vec<
189 // the points to open
190 Vec<Challenge>,
191 >,
192 )>,
193 fiat_shamir_challenger: &mut Challenger,
194 ) -> (OpenedValues<Challenge>, Self::Proof);
195
196 /// Like `open` but allows the implementation to treat some rounds as preprocessing (e.g. for ZK).
197 #[allow(clippy::type_complexity)]
198 fn open_with_preprocessing(
199 &self,
200 rounds: Vec<(&Self::ProverData, Vec<Vec<Challenge>>)>,
201 challenger: &mut Challenger,
202 _is_preprocessing: bool,
203 ) -> (OpenedValues<Challenge>, Self::Proof) {
204 self.open(rounds, challenger)
205 }
206
207 /// Verify that a collection of opened values is correct.
208 ///
209 /// Arguments:
210 /// - `commitments_with_opening_points`: A vector whose elements are a pair:
211 /// - `commitment`: A multi matrix commitment.
212 /// - `opening_points`: A vector containing, for each matrix committed to, a vector of opening points and claimed evaluations.
213 /// - `proof`: A claimed proof of correctness for the opened values.
214 /// - `fiat_shamir_challenger`: The challenger that will be used to generate the proof.
215 #[allow(clippy::type_complexity)]
216 fn verify(
217 &self,
218 // For each commitment:
219 commitments_with_opening_points: Vec<(
220 // The commitment
221 Self::Commitment,
222 // for each matrix in the commitment:
223 Vec<(
224 // its domain,
225 Self::Domain,
226 // A vector of (point, claimed_evaluation) pairs
227 Vec<(
228 // the point the matrix was opened at,
229 Challenge,
230 // the claimed evaluations at that point
231 Vec<Challenge>,
232 )>,
233 )>,
234 )>,
235 // The opening proof for all claimed evaluations.
236 proof: &Self::Proof,
237 fiat_shamir_challenger: &mut Challenger,
238 ) -> Result<(), Self::Error>;
239
240 fn get_opt_randomization_poly_commitment(
241 &self,
242 _domains: impl IntoIterator<Item = Self::Domain>,
243 ) -> Option<(Self::Commitment, Self::ProverData)> {
244 None
245 }
246}
247
248pub type OpenedValues<F> = Vec<OpenedValuesForRound<F>>;
249pub type OpenedValuesForRound<F> = Vec<OpenedValuesForMatrix<F>>;
250pub type OpenedValuesForMatrix<F> = Vec<OpenedValuesForPoint<F>>;
251pub type OpenedValuesForPoint<F> = Vec<F>;
252
253#[cfg(test)]
254mod tests {
255 use alloc::vec;
256 use core::marker::PhantomData;
257
258 use lib_q_stark_baby_bear::BabyBear;
259 use lib_q_stark_challenger::{
260 CanSample,
261 Shake128Challenger32,
262 };
263 use lib_q_stark_dft::{
264 NaiveDft,
265 TwoAdicSubgroupDft,
266 };
267 use lib_q_stark_field::coset::TwoAdicMultiplicativeCoset;
268 use lib_q_stark_field::{
269 PrimeCharacteristicRing,
270 TwoAdicField,
271 };
272 use lib_q_stark_shake128::Shake128Hash;
273
274 use super::*;
275 use crate::testing::{
276 TrivialPcs,
277 eval_coeffs_at_pt,
278 };
279
280 type F = BabyBear;
281 type Challenge = F;
282 type Challenger = Shake128Challenger32<F>;
283
284 fn pcs(log_n: usize) -> TrivialPcs<F, NaiveDft> {
285 TrivialPcs {
286 dft: NaiveDft,
287 log_n,
288 _phantom: PhantomData,
289 }
290 }
291
292 fn challenger() -> Challenger {
293 Challenger::from_hasher(Vec::new(), Shake128Hash)
294 }
295
296 /// Coefficients (one polynomial per column, low-degree-first down each column) for two
297 /// distinct small polynomials over an 8-element domain.
298 fn coeffs() -> RowMajorMatrix<F> {
299 RowMajorMatrix::new(
300 [1u32, 10, 2, 0, 3, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0]
301 .into_iter()
302 .map(F::new)
303 .collect(),
304 2,
305 )
306 }
307
308 /// `TrivialPcs::commit` is documented as "only commit on larger domain than natural": it takes
309 /// evaluations over a domain and recovers coefficients via an inverse DFT weighted by the
310 /// domain's shift. On the *natural* domain (shift = `ONE`, so the weighting is a no-op), this
311 /// must exactly invert the forward DFT used to build the evaluations in the first place.
312 #[test]
313 fn commit_recovers_the_original_coefficients_on_the_natural_domain() {
314 let p = pcs(3);
315 let domain =
316 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
317 &p, 8,
318 );
319 let original = coeffs();
320 let evals = NaiveDft.dft_batch(original.clone());
321
322 let (_commitment, prover_data) =
323 Pcs::<Challenge, Challenger>::commit(&p, [(domain, evals)]);
324 assert_eq!(prover_data[0], original);
325 }
326
327 /// End-to-end `commit` -> `open` -> `verify` round trip: the value produced by `open` for an
328 /// arbitrary (off-domain) evaluation point must be the true evaluation of the committed
329 /// polynomial there, and `verify` must accept that (correct) claim.
330 #[test]
331 fn open_and_verify_accept_an_honest_evaluation_claim() {
332 let p = pcs(3);
333 let domain =
334 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
335 &p, 8,
336 );
337 let original = coeffs();
338 let evals = NaiveDft.dft_batch(original.clone());
339 let (commitment, prover_data) = Pcs::<Challenge, Challenger>::commit(&p, [(domain, evals)]);
340
341 let z = F::new(12345);
342 let expected = eval_coeffs_at_pt(&original, z);
343
344 let (opened, proof) = Pcs::<Challenge, Challenger>::open(
345 &p,
346 vec![(&prover_data, vec![vec![z]])],
347 &mut challenger(),
348 );
349 assert_eq!(opened[0][0][0], expected);
350
351 Pcs::<Challenge, Challenger>::verify(
352 &p,
353 vec![(
354 commitment,
355 vec![(domain, vec![(z, opened[0][0][0].clone())])],
356 )],
357 &proof,
358 &mut challenger(),
359 )
360 .expect("verify must accept the value `open` itself produced for an honest commitment");
361 }
362
363 /// Negative control for the round trip above: assert a claimed evaluation known to be wrong
364 /// and confirm `verify` does NOT accept it. `TrivialPcs` is a testing-only PCS that signals a
365 /// bad claim by panicking (`assert_eq!` internally, documented on `Pcs::verify`'s impl), so the
366 /// failure mode here is a panic rather than an `Err`.
367 #[test]
368 #[should_panic]
369 fn verify_rejects_a_tampered_evaluation_claim() {
370 let p = pcs(3);
371 let domain =
372 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
373 &p, 8,
374 );
375 let original = coeffs();
376 let evals = NaiveDft.dft_batch(original.clone());
377 let (commitment, prover_data) = Pcs::<Challenge, Challenger>::commit(&p, [(domain, evals)]);
378
379 let z = F::new(12345);
380 let (_opened, proof) = Pcs::<Challenge, Challenger>::open(
381 &p,
382 vec![(&prover_data, vec![vec![z]])],
383 &mut challenger(),
384 );
385
386 let tampered_value = eval_coeffs_at_pt(&original, z)
387 .into_iter()
388 .map(|v| v + F::ONE)
389 .collect();
390
391 Pcs::<Challenge, Challenger>::verify(
392 &p,
393 vec![(commitment, vec![(domain, vec![(z, tampered_value)])])],
394 &proof,
395 &mut challenger(),
396 )
397 .ok();
398 }
399
400 // ---- Coverage of `Pcs`'s DEFAULT method bodies ----
401 //
402 // `TrivialPcs` (the only `Pcs` impl in this crate) overrides every required method plus
403 // `commit_quotient`, but deliberately leaves `try_natural_domain_for_degree`,
404 // `commit_preprocessing`, `open_with_preprocessing` and `get_opt_randomization_poly_commitment`
405 // on the trait's own default bodies (see `testing.rs`'s `impl Pcs for TrivialPcs`: those four
406 // methods are simply absent). So calling them through `TrivialPcs` exercises the DEFAULT body
407 // defined right here in `pcs.rs`, not an override elsewhere -- closing exactly the gap the
408 // 0/20-covered `pcs.rs` report pointed at.
409
410 /// `try_natural_domain_for_degree`'s default just wraps `natural_domain_for_degree` in `Some`.
411 #[test]
412 fn try_natural_domain_for_degree_default_wraps_the_infallible_version() {
413 let p = pcs(3);
414 let degree = 8;
415 let direct =
416 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
417 &p, degree,
418 );
419 let via_default = Pcs::<Challenge, Challenger>::try_natural_domain_for_degree(&p, degree);
420 // `TwoAdicMultiplicativeCoset` does not implement `PartialEq`; compare the two
421 // domain-identifying fields instead (shift and size uniquely determine a coset).
422 let via_default = via_default.expect("default must wrap in `Some`");
423 assert_eq!(via_default.shift(), direct.shift());
424 assert_eq!(via_default.log_size(), direct.log_size());
425 }
426
427 /// `commit_preprocessing`'s default is a pure passthrough to `commit`: same input must produce
428 /// the identical (commitment, prover_data) pair.
429 #[test]
430 fn commit_preprocessing_default_delegates_to_commit() {
431 let p = pcs(3);
432 let domain =
433 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
434 &p, 8,
435 );
436 let evals = NaiveDft.dft_batch(coeffs());
437
438 let (direct_commitment, direct_prover_data) =
439 Pcs::<Challenge, Challenger>::commit(&p, [(domain, evals.clone())]);
440 let (default_commitment, default_prover_data) =
441 Pcs::<Challenge, Challenger>::commit_preprocessing(&p, [(domain, evals)]);
442
443 assert_eq!(default_commitment, direct_commitment);
444 assert_eq!(default_prover_data, direct_prover_data);
445 }
446
447 /// Negative control for the delegation test above: prove the two calls are not being compared
448 /// via some vacuously-equal placeholder by feeding `commit` a genuinely different polynomial
449 /// and confirming the two commitments then differ.
450 #[test]
451 fn commit_preprocessing_negative_control_distinct_input_gives_distinct_commitment() {
452 let p = pcs(3);
453 let domain =
454 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
455 &p, 8,
456 );
457 let evals_a = NaiveDft.dft_batch(coeffs());
458 let mut other = coeffs();
459 // Perturb one coefficient so the two polynomials are genuinely different.
460 other.values[0] += F::ONE;
461 let evals_b = NaiveDft.dft_batch(other);
462
463 let (commitment_a, _) =
464 Pcs::<Challenge, Challenger>::commit_preprocessing(&p, [(domain, evals_a)]);
465 let (commitment_b, _) =
466 Pcs::<Challenge, Challenger>::commit_preprocessing(&p, [(domain, evals_b)]);
467 assert_ne!(commitment_a, commitment_b);
468 }
469
470 /// `open_with_preprocessing`'s default ignores the `is_preprocessing` flag entirely and
471 /// delegates straight to `open`; check both flag values produce `open`'s own result.
472 #[test]
473 fn open_with_preprocessing_default_delegates_to_open_regardless_of_flag() {
474 let p = pcs(3);
475 let domain =
476 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
477 &p, 8,
478 );
479 let evals = NaiveDft.dft_batch(coeffs());
480 let (_commitment, prover_data) =
481 Pcs::<Challenge, Challenger>::commit(&p, [(domain, evals)]);
482 let z = F::new(999);
483
484 let (direct_opened, _) = Pcs::<Challenge, Challenger>::open(
485 &p,
486 vec![(&prover_data, vec![vec![z]])],
487 &mut challenger(),
488 );
489 for flag in [false, true] {
490 let (via_default, _) = Pcs::<Challenge, Challenger>::open_with_preprocessing(
491 &p,
492 vec![(&prover_data, vec![vec![z]])],
493 &mut challenger(),
494 flag,
495 );
496 assert_eq!(via_default, direct_opened);
497 }
498 }
499
500 /// `get_evaluations_on_domain_no_random`'s default is a pure passthrough to
501 /// `get_evaluations_on_domain`: same inputs must produce identical evaluations.
502 #[test]
503 fn get_evaluations_on_domain_no_random_default_delegates() {
504 let p = pcs(3);
505 let domain =
506 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
507 &p, 8,
508 );
509 let evals = NaiveDft.dft_batch(coeffs());
510 let (_commitment, prover_data) =
511 Pcs::<Challenge, Challenger>::commit(&p, [(domain, evals)]);
512
513 let direct =
514 Pcs::<Challenge, Challenger>::get_evaluations_on_domain(&p, &prover_data, 0, domain);
515 let via_default = Pcs::<Challenge, Challenger>::get_evaluations_on_domain_no_random(
516 &p,
517 &prover_data,
518 0,
519 domain,
520 );
521 assert_eq!(via_default, direct);
522 }
523
524 /// `get_opt_randomization_poly_commitment`'s default always returns `None`; nothing about a
525 /// non-hiding PCS like `TrivialPcs` should make it produce a randomization commitment.
526 #[test]
527 fn get_opt_randomization_poly_commitment_default_is_none() {
528 let p = pcs(3);
529 let domain =
530 <TrivialPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
531 &p, 8,
532 );
533 assert!(
534 Pcs::<Challenge, Challenger>::get_opt_randomization_poly_commitment(&p, [domain])
535 .is_none()
536 );
537 }
538
539 /// Minimal wrapper around `TrivialPcs` that supplies real (if non-LDE) bodies for
540 /// `get_quotient_ldes`/`commit_ldes` -- `TrivialPcs`'s own versions are `unimplemented!()` --
541 /// and, crucially, does NOT re-override `commit_quotient`. That leaves `commit_quotient` on
542 /// the trait's own default body in `pcs.rs`, so calling it here runs that default end-to-end
543 /// instead of `TrivialPcs`'s override (see `testing.rs`, which DOES override `commit_quotient`).
544 struct QuotientDefaultPcs<Val: TwoAdicField, Dft: TwoAdicSubgroupDft<Val>>(
545 TrivialPcs<Val, Dft>,
546 );
547
548 impl<Val, Dft, Challenge, Challenger> Pcs<Challenge, Challenger> for QuotientDefaultPcs<Val, Dft>
549 where
550 Val: TwoAdicField,
551 Challenge: ExtensionField<Val>,
552 Challenger: CanSample<Challenge>,
553 Dft: TwoAdicSubgroupDft<Val>,
554 Vec<Vec<Val>>: Serialize + DeserializeOwned,
555 {
556 type Domain = TwoAdicMultiplicativeCoset<Val>;
557 type Commitment = Vec<Vec<Val>>;
558 type ProverData = Vec<RowMajorMatrix<Val>>;
559 type EvaluationsOnDomain<'a> = Dft::Evaluations;
560 type Proof = ();
561 type Error = ();
562 const ZK: bool = false;
563
564 fn natural_domain_for_degree(&self, degree: usize) -> Self::Domain {
565 Pcs::<Challenge, Challenger>::natural_domain_for_degree(&self.0, degree)
566 }
567
568 fn commit(
569 &self,
570 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val>)>,
571 ) -> (Self::Commitment, Self::ProverData) {
572 Pcs::<Challenge, Challenger>::commit(&self.0, evaluations)
573 }
574
575 // Deliberately NOT overriding `commit_quotient`: that is the point of this type.
576
577 fn get_quotient_ldes(
578 &self,
579 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val>)>,
580 _num_chunks: usize,
581 ) -> Vec<RowMajorMatrix<Val>> {
582 // Identity passthrough: this type exists only to observe `commit_quotient`'s default
583 // plumbing, not to compute an actual low-degree extension.
584 evaluations
585 .into_iter()
586 .map(|(_domain, evals)| evals)
587 .collect()
588 }
589
590 fn commit_ldes(
591 &self,
592 ldes: Vec<RowMajorMatrix<Val>>,
593 ) -> (Self::Commitment, Self::ProverData) {
594 (ldes.iter().map(|m| m.values.clone()).collect(), ldes)
595 }
596
597 fn get_evaluations_on_domain<'a>(
598 &self,
599 prover_data: &'a Self::ProverData,
600 idx: usize,
601 domain: Self::Domain,
602 ) -> Self::EvaluationsOnDomain<'a> {
603 Pcs::<Challenge, Challenger>::get_evaluations_on_domain(
604 &self.0,
605 prover_data,
606 idx,
607 domain,
608 )
609 }
610
611 fn open(
612 &self,
613 rounds: Vec<(&Self::ProverData, Vec<Vec<Challenge>>)>,
614 challenger: &mut Challenger,
615 ) -> (OpenedValues<Challenge>, Self::Proof) {
616 Pcs::<Challenge, Challenger>::open(&self.0, rounds, challenger)
617 }
618
619 #[allow(clippy::type_complexity)]
620 fn verify(
621 &self,
622 rounds: Vec<(
623 Self::Commitment,
624 Vec<(Self::Domain, Vec<(Challenge, Vec<Challenge>)>)>,
625 )>,
626 proof: &Self::Proof,
627 challenger: &mut Challenger,
628 ) -> Result<(), Self::Error> {
629 Pcs::<Challenge, Challenger>::verify(&self.0, rounds, proof, challenger)
630 }
631 }
632
633 /// `commit_quotient`'s default: split the quotient domain/evaluations into `num_chunks`
634 /// pieces, hand them to `get_quotient_ldes`, then commit the result via `commit_ldes`. Check
635 /// the whole default end-to-end against an independently-assembled expectation built from the
636 /// same `PolynomialSpace::split_evals` call the default itself must be using.
637 #[test]
638 fn commit_quotient_default_splits_then_delegates_to_get_quotient_ldes_and_commit_ldes() {
639 let p = QuotientDefaultPcs(pcs(2));
640 let quotient_domain =
641 <QuotientDefaultPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
642 &p, 8,
643 );
644 let evals = NaiveDft.dft_batch(coeffs());
645 let num_chunks = 2;
646
647 let (commitment, prover_data) = Pcs::<Challenge, Challenger>::commit_quotient(
648 &p,
649 quotient_domain,
650 evals.clone(),
651 num_chunks,
652 );
653
654 // Independently reconstruct what the default *should* produce: `get_quotient_ldes` here is
655 // an identity passthrough of the split evaluation chunks, and `commit_ldes` copies each
656 // chunk's raw values as the commitment -- so the expected prover data is exactly
657 // `split_evals`'s own output, in order.
658 let expected_ldes = PolynomialSpace::split_evals("ient_domain, num_chunks, evals);
659 let expected_commitment: Vec<Vec<F>> =
660 expected_ldes.iter().map(|m| m.values.clone()).collect();
661
662 assert_eq!(prover_data, expected_ldes);
663 assert_eq!(commitment, expected_commitment);
664 }
665
666 /// Negative control for the `commit_quotient` default test: corrupting `num_chunks` (splitting
667 /// into a different number of pieces than the default actually used) must produce a different
668 /// prover-data shape/content, proving the equality check above is not vacuous.
669 #[test]
670 fn commit_quotient_negative_control_wrong_num_chunks_disagrees() {
671 let p = QuotientDefaultPcs(pcs(2));
672 let quotient_domain =
673 <QuotientDefaultPcs<F, NaiveDft> as Pcs<Challenge, Challenger>>::natural_domain_for_degree(
674 &p, 8,
675 );
676 let evals = NaiveDft.dft_batch(coeffs());
677
678 let (_commitment, prover_data) =
679 Pcs::<Challenge, Challenger>::commit_quotient(&p, quotient_domain, evals.clone(), 2);
680 // Split into 4 chunks instead of the 2 actually used above: different shape entirely.
681 let wrong_split = PolynomialSpace::split_evals("ient_domain, 4, evals);
682 assert_ne!(prover_data.len(), wrong_split.len());
683 }
684}