p3_commit/pcs/univariate.rs
1//! Traits for univariate polynomial commitment schemes.
2
3use alloc::vec::Vec;
4use core::fmt::Debug;
5
6use p3_field::ExtensionField;
7use p3_matrix::Matrix;
8use p3_matrix::dense::RowMajorMatrix;
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11
12use crate::{PeriodicLdeTable, 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 fn natural_domain_for_degree(&self, degree: usize) -> Self::Domain;
57
58 /// The base-2 logarithm of the largest evaluation domain this PCS can construct.
59 fn log_max_lde_height(&self) -> usize;
60
61 /// Given a collection of evaluation matrices, produce a binding commitment to
62 /// the polynomials defined by those evaluations. If `zk` is enabled, the evaluations are
63 /// first randomized as explained in Section 3 of <https://eprint.iacr.org/2024/1037.pdf>.
64 ///
65 /// Returns both the commitment which should be sent to the verifier
66 /// and the prover data which can be used to produce opening proofs.
67 #[allow(clippy::type_complexity)]
68 fn commit(
69 &self,
70 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
71 ) -> (Self::Commitment, Self::ProverData);
72
73 /// Same as `commit` but without randomization. This is used for preprocessed columns
74 /// which do not have to be randomized even when ZK is enabled. Note that the preprocessed columns still
75 /// need to be padded to the extended domain height.
76 ///
77 /// Returns both the commitment which should be sent to the verifier
78 /// and the prover data which can be used to produce opening proofs.
79 fn commit_preprocessing(
80 &self,
81 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
82 ) -> (Self::Commitment, Self::ProverData) {
83 self.commit(evaluations)
84 }
85
86 /// Commit to the quotient polynomial. We first decompose the quotient polynomial into
87 /// `num_chunks` many smaller polynomials each of degree `degree / num_chunks`.
88 /// This can have minor performance benefits, but is not strictly necessary in the non `zk` case.
89 /// When `zk` is enabled, this commitment will additionally include some randomization process
90 /// to hide the inputs.
91 ///
92 /// ### Arguments
93 /// - `quotient_domain` the domain of the quotient polynomial.
94 /// - `quotient_evaluations` the evaluations of the quotient polynomial over the domain. This should be in
95 /// standard (not bit-reversed) order.
96 /// - `num_chunks` the number of smaller polynomials to decompose the quotient polynomial into.
97 #[allow(clippy::type_complexity)]
98 fn commit_quotient(
99 &self,
100 quotient_domain: Self::Domain,
101 quotient_evaluations: RowMajorMatrix<Val<Self::Domain>>,
102 num_chunks: usize,
103 ) -> (Self::Commitment, Self::ProverData) {
104 // Given the evaluation vector of `Q_i(x)` over a domain, split it into evaluation vectors
105 // of `q_{i0}(x), ...` over subdomains and commit to these `q`'s.
106 // TODO: Currently, split_evals involves copying the data to a new matrix.
107 // We may be able to avoid this copy making use of bit-reversals.
108 let quotient_sub_evaluations =
109 quotient_domain.split_evals(num_chunks, quotient_evaluations);
110 let quotient_sub_domains = quotient_domain.split_domains(num_chunks);
111
112 let ldes = self.get_quotient_ldes(
113 quotient_sub_domains
114 .into_iter()
115 .zip(quotient_sub_evaluations),
116 num_chunks,
117 );
118 self.commit_ldes(ldes)
119 }
120
121 /// When committing to quotient polynomials in batch-STARK,
122 /// it is simpler to first compute the LDE evaluations before batch-committing to them.
123 ///
124 /// This corresponds to the first step of `commit_quotient`. When `zk` is enabled,
125 /// this will additionally add randomization.
126 fn get_quotient_ldes(
127 &self,
128 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
129 num_chunks: usize,
130 ) -> Vec<RowMajorMatrix<Val<Self::Domain>>>;
131
132 /// Commits to a collection of LDE evaluation matrices.
133 fn commit_ldes(
134 &self,
135 ldes: Vec<RowMajorMatrix<Val<Self::Domain>>>,
136 ) -> (Self::Commitment, Self::ProverData);
137
138 /// Given prover data corresponding to a commitment to a collection of evaluation matrices,
139 /// return the evaluations of those matrices on the given domain.
140 ///
141 /// This is essentially a no-op when called with a `domain` which is a subset of the evaluation domain
142 /// on which the evaluation matrices are defined.
143 fn get_evaluations_on_domain<'a>(
144 &self,
145 prover_data: &'a Self::ProverData,
146 idx: usize,
147 domain: Self::Domain,
148 ) -> Self::EvaluationsOnDomain<'a>;
149
150 /// This is the same as `get_evaluations_on_domain` but without randomization.
151 /// This is used for preprocessed columns which do not have to be randomized even when ZK is enabled.
152 fn get_evaluations_on_domain_no_random<'a>(
153 &self,
154 prover_data: &'a Self::ProverData,
155 idx: usize,
156 domain: Self::Domain,
157 ) -> Self::EvaluationsOnDomain<'a> {
158 self.get_evaluations_on_domain(prover_data, idx, domain)
159 }
160
161 /// Open a collection of polynomial commitments at a set of points. Produce the values at those points along with a proof
162 /// of correctness.
163 ///
164 /// Arguments:
165 /// - `commitment_data_with_opening_points`: A vector whose elements are a pair:
166 /// - `data`: The prover data corresponding to a multi-matrix commitment.
167 /// - `opening_points`: A vector containing, for each matrix committed to, a vector of opening points.
168 /// - `fiat_shamir_challenger`: The challenger that will be used to generate the proof.
169 ///
170 /// Unwrapping the arguments further, each `data` contains a vector of the committed matrices (`matrices = Vec<M>`).
171 /// If the length of `matrices` is not equal to the length of `opening_points` the function will error. Otherwise, for
172 /// each index `i`, the matrix `M = matrices[i]` will be opened at the points `opening_points[i]`.
173 ///
174 /// This means that each column of `M` will be interpreted as the evaluation vector of some polynomial
175 /// and we will compute the value of all of those polynomials at `opening_points[i]`.
176 ///
177 /// The domains on which the evaluation vectors are defined is not part of the arguments here
178 /// but should be public information known to both the prover and verifier.
179 fn open(
180 &self,
181 // For each multi-matrix commitment,
182 commitment_data_with_opening_points: Vec<(
183 // The matrices and auxiliary prover data
184 &Self::ProverData,
185 // for each matrix,
186 Vec<
187 // the points to open
188 Vec<Challenge>,
189 >,
190 )>,
191 fiat_shamir_challenger: &mut Challenger,
192 ) -> (OpenedValues<Challenge>, Self::Proof);
193
194 /// Open a collection of polynomial commitments at a set of points, when there is preprocessing data.
195 /// It is the same as `open` when `ZK` is disabled.
196 /// Produce the values at those points along with a proof of correctness.
197 ///
198 /// Arguments:
199 /// - `commitment_data_with_opening_points`: A vector whose elements are a pair:
200 /// - `data`: The prover data corresponding to a multi-matrix commitment.
201 /// - `opening_points`: A vector containing, for each matrix committed to, a vector of opening points.
202 /// - `fiat_shamir_challenger`: The challenger that will be used to generate the proof.
203 /// - `is_preprocessing`: If one of the committed matrices corresponds to preprocessed columns, this is the index of that matrix.
204 ///
205 /// Unwrapping the arguments further, each `data` contains a vector of the committed matrices (`matrices = Vec<M>`).
206 /// If the length of `matrices` is not equal to the length of `opening_points` the function will error. Otherwise, for
207 /// each index `i`, the matrix `M = matrices[i]` will be opened at the points `opening_points[i]`.
208 ///
209 /// This means that each column of `M` will be interpreted as the evaluation vector of some polynomial
210 /// and we will compute the value of all of those polynomials at `opening_points[i]`.
211 ///
212 /// The domains on which the evaluation vectors are defined is not part of the arguments here
213 /// but should be public information known to both the prover and verifier.
214 fn open_with_preprocessing(
215 &self,
216 // For each multi-matrix commitment,
217 commitment_data_with_opening_points: Vec<(
218 // The matrices and auxiliary prover data
219 &Self::ProverData,
220 // for each matrix,
221 Vec<
222 // the points to open
223 Vec<Challenge>,
224 >,
225 )>,
226 fiat_shamir_challenger: &mut Challenger,
227 _is_preprocessing: bool,
228 ) -> (OpenedValues<Challenge>, Self::Proof) {
229 assert!(
230 !Self::ZK,
231 "open_with_preprocessing should have a different implementation when ZK is enabled"
232 );
233 self.open(commitment_data_with_opening_points, fiat_shamir_challenger)
234 }
235
236 /// Verify that a collection of opened values is correct.
237 ///
238 /// Arguments:
239 /// - `commitments_with_opening_points`: A vector whose elements are a pair:
240 /// - `commitment`: A multi matrix commitment.
241 /// - `opening_points`: A vector containing, for each matrix committed to, a vector of opening points and claimed evaluations.
242 /// - `proof`: A claimed proof of correctness for the opened values.
243 /// - `fiat_shamir_challenger`: The challenger that will be used to generate the proof.
244 #[allow(clippy::type_complexity)]
245 fn verify(
246 &self,
247 // For each commitment:
248 commitments_with_opening_points: Vec<(
249 // The commitment
250 Self::Commitment,
251 // for each matrix in the commitment:
252 Vec<(
253 // its domain,
254 Self::Domain,
255 // A vector of (point, claimed_evaluation) pairs
256 Vec<(
257 // the point the matrix was opened at,
258 Challenge,
259 // the claimed evaluations at that point
260 Vec<Challenge>,
261 )>,
262 )>,
263 )>,
264 // The opening proof for all claimed evaluations.
265 proof: &Self::Proof,
266 fiat_shamir_challenger: &mut Challenger,
267 ) -> Result<(), Self::Error>;
268
269 fn get_opt_randomization_poly_commitment(
270 &self,
271 _domain: impl IntoIterator<Item = Self::Domain>,
272 ) -> Option<(Self::Commitment, Self::ProverData)> {
273 None
274 }
275
276 /// Build the compact periodic LDE table (height = max_period × blowup, width = num periodic columns).
277 ///
278 /// Default: evaluate each column at the first `extended_height` quotient points. Backends that
279 /// can compute this faster (e.g. via coset LDE) should override this method.
280 fn build_periodic_lde_table(
281 &self,
282 periodic_cols: &[Vec<Val<Self::Domain>>],
283 trace_domain: Self::Domain,
284 quotient_domain: Self::Domain,
285 ) -> PeriodicLdeTable<Val<Self::Domain>>
286 where
287 Self::Domain: Clone,
288 Val<Self::Domain>: Clone,
289 {
290 if periodic_cols.is_empty() {
291 return PeriodicLdeTable::empty();
292 }
293 let trace_size = trace_domain.size();
294 let quotient_size = quotient_domain.size();
295 assert!(
296 quotient_size >= trace_size,
297 "quotient domain size ({quotient_size}) must be >= trace domain size ({trace_size})",
298 );
299 assert!(
300 quotient_size.is_multiple_of(trace_size),
301 "quotient domain size ({quotient_size}) must be divisible by trace domain size ({trace_size})",
302 );
303 let blowup = quotient_size / trace_size;
304
305 for col in periodic_cols {
306 let period = col.len();
307 assert!(
308 period > 0 && period.is_power_of_two(),
309 "periodic column length must be a non-zero power of 2, got {period}",
310 );
311 assert!(
312 trace_size.is_multiple_of(period),
313 "trace domain size ({trace_size}) must be divisible by periodic column length ({period})",
314 );
315 }
316 let max_period = periodic_cols.iter().map(|c| c.len()).max().unwrap();
317 let extended_height = max_period
318 .checked_mul(blowup)
319 .expect("extended height overflow when computing max_period * blowup");
320 // Implied by the column checks above.
321 // Each period divides the trace size, so max_period <= trace_size.
322 // Therefore extended_height = max_period * blowup <= trace_size * blowup = quotient_size.
323 debug_assert!(extended_height <= quotient_size);
324 let num_cols = periodic_cols.len();
325 let row_major_capacity = extended_height
326 .checked_mul(num_cols)
327 .expect("row-major periodic table capacity overflow");
328
329 let mut quotient_pts = Vec::with_capacity(extended_height);
330 let mut pt = quotient_domain.first_point();
331 for _ in 0..extended_height {
332 quotient_pts.push(pt);
333 pt = quotient_domain
334 .next_point(pt)
335 .expect("quotient domain must support next_point");
336 }
337
338 let padded_cols: Vec<Vec<Val<Self::Domain>>> = periodic_cols
339 .iter()
340 .map(|col| (0..max_period).map(|i| col[i % col.len()]).collect())
341 .collect();
342
343 let mut row_major = Vec::with_capacity(row_major_capacity);
344 for point in quotient_pts.iter().take(extended_height) {
345 for padded in &padded_cols {
346 row_major.push(trace_domain.evaluate_periodic_column_at(padded, *point));
347 }
348 }
349 PeriodicLdeTable::new(RowMajorMatrix::new(row_major, num_cols))
350 }
351}
352
353pub type OpenedValues<F> = Vec<OpenedValuesForRound<F>>;
354pub type OpenedValuesForRound<F> = Vec<OpenedValuesForMatrix<F>>;
355pub type OpenedValuesForMatrix<F> = Vec<OpenedValuesForPoint<F>>;
356pub type OpenedValuesForPoint<F> = Vec<F>;