1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
//! Traits for polynomial commitment schemes.
use alloc::vec::Vec;
use core::fmt::Debug;
use p3_field::ExtensionField;
use p3_matrix::Matrix;
use p3_matrix::dense::RowMajorMatrix;
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::PolynomialSpace;
pub type Val<D> = <D as PolynomialSpace>::Val;
/// A polynomial commitment scheme, for committing to batches of polynomials defined by their evaluations
/// over some domain.
///
/// In general this does not have to be a hiding commitment scheme but it might be for some implementations.
// TODO: Should we have a super-trait for weakly-binding PCSs, like FRI outside unique decoding radius?
pub trait Pcs<Challenge, Challenger>
where
Challenge: ExtensionField<Val<Self::Domain>>,
{
/// The class of evaluation domains that this commitment scheme works over.
type Domain: PolynomialSpace;
/// The commitment that's sent to the verifier.
type Commitment: Clone + Serialize + DeserializeOwned;
/// Data that the prover stores for committed polynomials, to help the prover with opening.
type ProverData;
/// Type of the output of `get_evaluations_on_domain`.
type EvaluationsOnDomain<'a>: Matrix<Val<Self::Domain>> + 'a;
/// The opening argument.
type Proof: Clone + Serialize + DeserializeOwned;
/// The type of a proof verification error.
type Error: Debug;
/// Set to true to activate randomization and achieve zero-knowledge.
const ZK: bool;
/// Index of the trace commitment in the computed opened values.
const TRACE_IDX: usize = Self::ZK as usize;
/// Index of the quotient commitments in the computed opened values.
const QUOTIENT_IDX: usize = Self::TRACE_IDX + 1;
/// Index of the preprocessed trace commitment in the computed opened values.
const PREPROCESSED_TRACE_IDX: usize = Self::QUOTIENT_IDX + 1; // Note: not always present
/// This should return a domain such that `Domain::next_point` returns `Some`.
fn natural_domain_for_degree(&self, degree: usize) -> Self::Domain;
/// Given a collection of evaluation matrices, produce a binding commitment to
/// the polynomials defined by those evaluations. If `zk` is enabled, the evaluations are
/// first randomized as explained in Section 3 of https://eprint.iacr.org/2024/1037.pdf .
///
/// Returns both the commitment which should be sent to the verifier
/// and the prover data which can be used to produce opening proofs.
#[allow(clippy::type_complexity)]
fn commit(
&self,
evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
) -> (Self::Commitment, Self::ProverData);
/// Same as `commit` but without randomization. This is used for preprocessed columns
/// which do not have to be randomized even when ZK is enabled. Note that the preprocessed columns still
/// need to be padded to the extended domain height.
///
/// Returns both the commitment which should be sent to the verifier
/// and the prover data which can be used to produce opening proofs.
fn commit_preprocessing(
&self,
evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
) -> (Self::Commitment, Self::ProverData) {
self.commit(evaluations)
}
/// Commit to the quotient polynomial. We first decompose the quotient polynomial into
/// `num_chunks` many smaller polynomials each of degree `degree / num_chunks`.
/// This can have minor performance benefits, but is not strictly necessary in the non `zk` case.
/// When `zk` is enabled, this commitment will additionally include some randomization process
/// to hide the inputs.
///
/// ### Arguments
/// - `quotient_domain` the domain of the quotient polynomial.
/// - `quotient_evaluations` the evaluations of the quotient polynomial over the domain. This should be in
/// standard (not bit-reversed) order.
/// - `num_chunks` the number of smaller polynomials to decompose the quotient polynomial into.
#[allow(clippy::type_complexity)]
fn commit_quotient(
&self,
quotient_domain: Self::Domain,
quotient_evaluations: RowMajorMatrix<Val<Self::Domain>>,
num_chunks: usize,
) -> (Self::Commitment, Self::ProverData) {
// Given the evaluation vector of `Q_i(x)` over a domain, split it into evaluation vectors
// of `q_{i0}(x), ...` over subdomains and commit to these `q`'s.
// TODO: Currently, split_evals involves copying the data to a new matrix.
// We may be able to avoid this copy making use of bit-reversals.
let quotient_sub_evaluations =
quotient_domain.split_evals(num_chunks, quotient_evaluations);
let quotient_sub_domains = quotient_domain.split_domains(num_chunks);
let ldes = self.get_quotient_ldes(
quotient_sub_domains
.into_iter()
.zip(quotient_sub_evaluations),
num_chunks,
);
self.commit_ldes(ldes)
}
/// When committing to quotient polynomials in batch-STARK,
/// it is simpler to first compute the LDE evaluations before batch-committing to them.
///
/// This corresponds to the first step of `commit_quotient`. When `zk` is enabled,
/// this will additionally add randomization.
fn get_quotient_ldes(
&self,
evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val<Self::Domain>>)>,
num_chunks: usize,
) -> Vec<RowMajorMatrix<Val<Self::Domain>>>;
/// Commits to a collection of LDE evaluation matrices.
fn commit_ldes(
&self,
ldes: Vec<RowMajorMatrix<Val<Self::Domain>>>,
) -> (Self::Commitment, Self::ProverData);
/// Given prover data corresponding to a commitment to a collection of evaluation matrices,
/// return the evaluations of those matrices on the given domain.
///
/// This is essentially a no-op when called with a `domain` which is a subset of the evaluation domain
/// on which the evaluation matrices are defined.
fn get_evaluations_on_domain<'a>(
&self,
prover_data: &'a Self::ProverData,
idx: usize,
domain: Self::Domain,
) -> Self::EvaluationsOnDomain<'a>;
/// This is the same as `get_evaluations_on_domain` but without randomization.
/// This is used for preprocessed columns which do not have to be randomized even when ZK is enabled.
fn get_evaluations_on_domain_no_random<'a>(
&self,
prover_data: &'a Self::ProverData,
idx: usize,
domain: Self::Domain,
) -> Self::EvaluationsOnDomain<'a> {
self.get_evaluations_on_domain(prover_data, idx, domain)
}
/// Open a collection of polynomial commitments at a set of points. Produce the values at those points along with a proof
/// of correctness.
///
/// Arguments:
/// - `commitment_data_with_opening_points`: A vector whose elements are a pair:
/// - `data`: The prover data corresponding to a multi-matrix commitment.
/// - `opening_points`: A vector containing, for each matrix committed to, a vector of opening points.
/// - `fiat_shamir_challenger`: The challenger that will be used to generate the proof.
///
/// Unwrapping the arguments further, each `data` contains a vector of the committed matrices (`matrices = Vec<M>`).
/// If the length of `matrices` is not equal to the length of `opening_points` the function will error. Otherwise, for
/// each index `i`, the matrix `M = matrices[i]` will be opened at the points `opening_points[i]`.
///
/// This means that each column of `M` will be interpreted as the evaluation vector of some polynomial
/// and we will compute the value of all of those polynomials at `opening_points[i]`.
///
/// The domains on which the evaluation vectors are defined is not part of the arguments here
/// but should be public information known to both the prover and verifier.
fn open(
&self,
// For each multi-matrix commitment,
commitment_data_with_opening_points: Vec<(
// The matrices and auxiliary prover data
&Self::ProverData,
// for each matrix,
Vec<
// the points to open
Vec<Challenge>,
>,
)>,
fiat_shamir_challenger: &mut Challenger,
) -> (OpenedValues<Challenge>, Self::Proof);
/// Open a collection of polynomial commitments at a set of points, when there is preprocessing data.
/// It is the same as `open` when `ZK` is disabled.
/// Produce the values at those points along with a proof of correctness.
///
/// Arguments:
/// - `commitment_data_with_opening_points`: A vector whose elements are a pair:
/// - `data`: The prover data corresponding to a multi-matrix commitment.
/// - `opening_points`: A vector containing, for each matrix committed to, a vector of opening points.
/// - `fiat_shamir_challenger`: The challenger that will be used to generate the proof.
/// - `is_preprocessing`: If one of the committed matrices corresponds to preprocessed columns, this is the index of that matrix.
///
/// Unwrapping the arguments further, each `data` contains a vector of the committed matrices (`matrices = Vec<M>`).
/// If the length of `matrices` is not equal to the length of `opening_points` the function will error. Otherwise, for
/// each index `i`, the matrix `M = matrices[i]` will be opened at the points `opening_points[i]`.
///
/// This means that each column of `M` will be interpreted as the evaluation vector of some polynomial
/// and we will compute the value of all of those polynomials at `opening_points[i]`.
///
/// The domains on which the evaluation vectors are defined is not part of the arguments here
/// but should be public information known to both the prover and verifier.
fn open_with_preprocessing(
&self,
// For each multi-matrix commitment,
commitment_data_with_opening_points: Vec<(
// The matrices and auxiliary prover data
&Self::ProverData,
// for each matrix,
Vec<
// the points to open
Vec<Challenge>,
>,
)>,
fiat_shamir_challenger: &mut Challenger,
_is_preprocessing: bool,
) -> (OpenedValues<Challenge>, Self::Proof) {
debug_assert!(
!Self::ZK,
"open_with_preprocessing should have a different implementation when ZK is enabled"
);
self.open(commitment_data_with_opening_points, fiat_shamir_challenger)
}
/// Verify that a collection of opened values is correct.
///
/// Arguments:
/// - `commitments_with_opening_points`: A vector whose elements are a pair:
/// - `commitment`: A multi matrix commitment.
/// - `opening_points`: A vector containing, for each matrix committed to, a vector of opening points and claimed evaluations.
/// - `proof`: A claimed proof of correctness for the opened values.
/// - `fiat_shamir_challenger`: The challenger that will be used to generate the proof.
#[allow(clippy::type_complexity)]
fn verify(
&self,
// For each commitment:
commitments_with_opening_points: Vec<(
// The commitment
Self::Commitment,
// for each matrix in the commitment:
Vec<(
// its domain,
Self::Domain,
// A vector of (point, claimed_evaluation) pairs
Vec<(
// the point the matrix was opened at,
Challenge,
// the claimed evaluations at that point
Vec<Challenge>,
)>,
)>,
)>,
// The opening proof for all claimed evaluations.
proof: &Self::Proof,
fiat_shamir_challenger: &mut Challenger,
) -> Result<(), Self::Error>;
fn get_opt_randomization_poly_commitment(
&self,
_domain: impl IntoIterator<Item = Self::Domain>,
) -> Option<(Self::Commitment, Self::ProverData)> {
None
}
}
pub type OpenedValues<F> = Vec<OpenedValuesForRound<F>>;
pub type OpenedValuesForRound<F> = Vec<OpenedValuesForMatrix<F>>;
pub type OpenedValuesForMatrix<F> = Vec<OpenedValuesForPoint<F>>;
pub type OpenedValuesForPoint<F> = Vec<F>;