Skip to main content

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>;