winter_fri/prover/mod.rs
1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// This source code is licensed under the MIT license found in the
4// LICENSE file in the root directory of this source tree.
5
6use alloc::vec::Vec;
7use core::marker::PhantomData;
8
9use crypto::{ElementHasher, Hasher, VectorCommitment};
10use math::{fft, FieldElement};
11#[cfg(feature = "concurrent")]
12use utils::iterators::*;
13use utils::{
14 flatten_vector_elements, group_slice_elements, iter_mut, transpose_slice, uninit_vector,
15};
16
17use crate::{
18 folding::{apply_drp, fold_positions},
19 proof::{FriProof, FriProofLayer},
20 FriOptions,
21};
22
23mod channel;
24pub use channel::{DefaultProverChannel, ProverChannel};
25
26#[cfg(test)]
27mod tests;
28
29// TYPES AND INTERFACES
30// ================================================================================================
31
32/// Implements the prover component of the FRI protocol.
33///
34/// Given evaluations of a function *f* over domain *D* (`evaluations`), a FRI prover generates
35/// a proof that *f* is a polynomial of some bounded degree *d*, such that
36/// *d* < |*D*| / *blowup_factor*.
37/// The proof is succinct: it exponentially smaller than `evaluations` and the verifier can verify
38/// it exponentially faster than it would have taken them to read all `evaluations`.
39///
40/// The prover is parametrized with the following types:
41///
42/// * `E` specifies the field in which the FRI protocol is executed.
43/// * `C` specifies the type used to simulate prover-verifier interaction.
44/// * `H` specifies the hash function used to build for each layer the vector of values committed to
45/// using the specified vector commitment scheme. The same hash function must be used in the
46/// prover channel to generate pseudo random values.
47/// * `V` specifies the vector commitment scheme used in order to commit to each layer.
48///
49/// Proof generation is performed in two phases: commit phase and query phase.
50///
51/// # Commit phase
52/// During the commit phase, which is executed via [build_layers()](FriProver::build_layers())
53/// function, the prover repeatedly applies a degree-respecting projection (DRP) to `evaluations`
54/// (see [folding](crate::folding)). With every application of the DRP, the degree of the function
55/// *f* (and size of the domain over which it is evaluated) is reduced by the `folding_factor`
56/// until the remaining evaluations correspond to a polynomial, called remainder polynomial, with
57/// a number of coefficients less than or equal to `remainder_max_degree_plus_1`.
58///
59/// At each layer of reduction, the prover commits to the current set of evaluations. This is done
60/// by building a vector commitment to hashed evaluations and sending the commitment string
61/// to the verifier (via [ProverChannel]). The vector commitment is build in such a way that all
62/// evaluations needed to compute a single value in the next FRI layer are grouped into the same
63/// leaf (the number of evaluations needed to compute a single element in the next FRI layer is
64/// equal to the `folding_factor`). This allows us to decommit all these values using a single
65/// individual opening proof.
66///
67/// After committing to the set of evaluations at the current layer, the prover draws a random
68/// field element α from the channel, and uses it to build the next FRI layer. In the interactive
69/// version of the protocol, the verifier draws α uniformly at random from the entire field and
70/// sends it to the prover. In the non-interactive version, α is pseudo-randomly generated based
71/// on the values the prover has written into the channel up to that point.
72///
73/// The prover keeps all FRI layers (consisting of evaluations and corresponding vector
74/// commitments) in its internal state.
75///
76/// # Query phase
77/// In the query phase, which is executed via [build_proof()](FriProver::build_proof()) function,
78/// the prover receives a set of positions in the domain *D* from the verifier. The prover then
79/// decommits evaluations corresponding to these positions across all FRI layers (except for the
80/// remainder layer) and builds a [FriProof] from these evaluations. The remainder polynomial
81/// is included in the proof in its entirety.
82///
83/// In the interactive version of the protocol, the verifier draws the position uniformly at
84/// random from domain *D*. In the non-interactive version, the positions are pseudo-randomly
85/// selected based on the values the prover has written into the channel up to that point.
86///
87/// Since the positions are drawn from domain *D*, they apply directly only to the first FRI
88/// layer. To map these positions to the positions in all subsequent layers, the prover uses
89/// [fold_positions] procedure.
90///
91/// After the proof is generated, the prover deletes all internally stored FRI layers.
92///
93/// Calling [build_layers()](FriProver::build_layers()) when the internal state is dirty, or
94/// calling [build_proof()](FriProver::build_proof()) on a clean state will result in a panic.
95pub struct FriProver<E, C, H, V>
96where
97 E: FieldElement,
98 C: ProverChannel<E, Hasher = H>,
99 H: ElementHasher<BaseField = E::BaseField>,
100 V: VectorCommitment<H>,
101{
102 options: FriOptions,
103 layers: Vec<FriLayer<E, H, V>>,
104 remainder_poly: FriRemainder<E>,
105 _channel: PhantomData<C>,
106}
107
108struct FriLayer<E: FieldElement, H: Hasher, V: VectorCommitment<H>> {
109 commitment: V,
110 evaluations: Vec<E>,
111 _h: PhantomData<H>,
112}
113
114struct FriRemainder<E: FieldElement>(Vec<E>);
115
116// PROVER IMPLEMENTATION
117// ================================================================================================
118
119impl<E, C, H, V> FriProver<E, C, H, V>
120where
121 E: FieldElement,
122 C: ProverChannel<E, Hasher = H>,
123 H: ElementHasher<BaseField = E::BaseField>,
124 V: VectorCommitment<H>,
125{
126 // CONSTRUCTOR
127 // --------------------------------------------------------------------------------------------
128 /// Returns a new FRI prover instantiated with the provided `options`.
129 pub fn new(options: FriOptions) -> Self {
130 FriProver {
131 options,
132 layers: Vec::new(),
133 remainder_poly: FriRemainder(vec![]),
134 _channel: PhantomData,
135 }
136 }
137
138 // ACCESSORS
139 // --------------------------------------------------------------------------------------------
140
141 /// Returns folding factor for this prover.
142 pub fn folding_factor(&self) -> usize {
143 self.options.folding_factor()
144 }
145
146 /// Returns offset of the domain over which FRI protocol is executed by this prover.
147 pub fn domain_offset(&self) -> E::BaseField {
148 self.options.domain_offset()
149 }
150
151 /// Returns number of FRI layers computed during the last execution of the
152 /// [build_layers()](FriProver::build_layers()) method.
153 pub fn num_layers(&self) -> usize {
154 self.layers.len()
155 }
156
157 /// Clears a vector of internally stored layers.
158 pub fn reset(&mut self) {
159 self.layers.clear();
160 self.remainder_poly.0.clear();
161 }
162
163 // COMMIT PHASE
164 // --------------------------------------------------------------------------------------------
165 /// Executes the commit phase of the FRI protocol.
166 ///
167 /// During this phase we repeatedly apply a degree-respecting projection (DRP) to
168 /// `evaluations` which contain evaluations of some function *f* over domain *D*. With every
169 /// application of the DRP the degree of the function (and size of the domain) is reduced by
170 /// `folding_factor` until the remaining evaluations can be represented by a remainder
171 /// polynomial with at most `remainder_max_degree_plus_1` number of coefficients.
172 /// At each layer of reduction the current evaluations are committed to using a vector
173 /// commitment scheme, and the commitment string of this vector commitment is written into
174 /// the channel. After this the prover draws a random field element α from the channel, and
175 /// uses it in the next application of the DRP.
176 ///
177 /// # Panics
178 /// Panics if the prover state is dirty (the vector of layers is not empty).
179 pub fn build_layers(&mut self, channel: &mut C, mut evaluations: Vec<E>) {
180 assert!(
181 self.layers.is_empty(),
182 "a prior proof generation request has not been completed yet"
183 );
184
185 // reduce the degree by folding_factor at each iteration until the remaining polynomial
186 // has small enough degree
187 for _ in 0..self.options.num_fri_layers(evaluations.len()) {
188 match self.folding_factor() {
189 2 => self.build_layer::<2>(channel, &mut evaluations),
190 4 => self.build_layer::<4>(channel, &mut evaluations),
191 8 => self.build_layer::<8>(channel, &mut evaluations),
192 16 => self.build_layer::<16>(channel, &mut evaluations),
193 _ => unimplemented!("folding factor {} is not supported", self.folding_factor()),
194 }
195 }
196
197 self.set_remainder(channel, &mut evaluations);
198 }
199
200 /// Builds a single FRI layer by first committing to the `evaluations`, then drawing a random
201 /// alpha from the channel and use it to perform degree-respecting projection.
202 fn build_layer<const N: usize>(&mut self, channel: &mut C, evaluations: &mut Vec<E>) {
203 // commit to the evaluations at the current layer; we do this by first transposing the
204 // evaluations into a matrix of N columns, then hashing each row into a digest, and finally
205 // commiting to vector of these digests; we do this so that we could de-commit to N values
206 // with a single opening proof.
207 let transposed_evaluations = transpose_slice(evaluations);
208 let evaluation_vector_commitment =
209 build_layer_commitment::<_, _, V, N>(&transposed_evaluations)
210 .expect("failed to construct FRI layer commitment");
211 channel.commit_fri_layer(evaluation_vector_commitment.commitment());
212
213 // draw a pseudo-random coefficient from the channel, and use it in degree-respecting
214 // projection to reduce the degree of evaluations by N
215 let alpha = channel.draw_fri_alpha();
216 *evaluations = apply_drp(&transposed_evaluations, self.domain_offset(), alpha);
217 self.layers.push(FriLayer {
218 commitment: evaluation_vector_commitment,
219 evaluations: flatten_vector_elements(transposed_evaluations),
220 _h: PhantomData,
221 });
222 }
223
224 /// Creates remainder polynomial in coefficient form from a vector of `evaluations` over a
225 /// domain.
226 ///
227 /// We commit to the coefficients of the remainder polynomial in reverse order so that
228 /// evaluating the remainder polynomial on the verifier's end, using Horner's evaluation
229 /// method, becomes easier.
230 fn set_remainder(&mut self, channel: &mut C, evaluations: &mut [E]) {
231 let inv_twiddles = fft::get_inv_twiddles(evaluations.len());
232 fft::interpolate_poly_with_offset(evaluations, &inv_twiddles, self.options.domain_offset());
233 let remainder_poly_size = evaluations.len() / self.options.blowup_factor();
234 let remainder_poly: Vec<_> =
235 evaluations[..remainder_poly_size].iter().copied().rev().collect();
236 let commitment = <H as ElementHasher>::hash_elements(&remainder_poly);
237 channel.commit_fri_layer(commitment);
238 self.remainder_poly = FriRemainder(remainder_poly);
239 }
240
241 // QUERY PHASE
242 // --------------------------------------------------------------------------------------------
243 /// Executes query phase of FRI protocol.
244 ///
245 /// For each of the provided `positions`, corresponding evaluations from each of the layers
246 /// (excluding the remainder layer) are recorded into the proof together with a batch opening
247 /// proof against the sent vector commitment. For the remainder, we send the whole remainder
248 /// polynomial resulting from interpolating the remainder layer evaluations. The coefficients
249 /// of the remainder polynomial are sent in reverse order so as to make evaluating it on
250 /// the verifier's end, using Horner's evaluation method, easier.
251 ///
252 /// # Panics
253 /// Panics is the prover state is clean (no FRI layers have been build yet).
254 pub fn build_proof(&mut self, positions: &[usize]) -> FriProof {
255 assert!(!self.remainder_poly.0.is_empty(), "FRI layers have not been built yet");
256
257 let mut layers = Vec::with_capacity(self.layers.len());
258
259 if !self.layers.is_empty() {
260 let mut positions = positions.to_vec();
261 let mut domain_size = self.layers[0].evaluations.len();
262 let folding_factor = self.options.folding_factor();
263
264 // for all FRI layers, except the last one, record tree root, determine a set of query
265 // positions, and query the layer at these positions.
266 for i in 0..self.layers.len() {
267 positions = fold_positions(&positions, domain_size, folding_factor);
268
269 // sort of a static dispatch for folding_factor parameter
270 let proof_layer = match folding_factor {
271 2 => query_layer::<E, H, V, 2>(&self.layers[i], &positions),
272 4 => query_layer::<E, H, V, 4>(&self.layers[i], &positions),
273 8 => query_layer::<E, H, V, 8>(&self.layers[i], &positions),
274 16 => query_layer::<E, H, V, 16>(&self.layers[i], &positions),
275 _ => unimplemented!("folding factor {} is not supported", folding_factor),
276 };
277
278 layers.push(proof_layer);
279 domain_size /= folding_factor;
280 }
281 }
282
283 // use the remaining polynomial values directly as proof
284 let remainder = self.remainder_poly.0.clone();
285
286 // clear layers so that another proof can be generated
287 self.reset();
288
289 FriProof::new(layers, remainder, 1)
290 }
291}
292
293// HELPER FUNCTIONS
294// ================================================================================================
295
296/// Builds a single proof layer by querying the evaluations of the passed in FRI layer at the
297/// specified positions.
298fn query_layer<E: FieldElement, H: Hasher, V: VectorCommitment<H>, const N: usize>(
299 layer: &FriLayer<E, H, V>,
300 positions: &[usize],
301) -> FriProofLayer {
302 // build a batch opening proof for all query positions
303 let proof = layer
304 .commitment
305 .open_many(positions)
306 .expect("failed to generate a batch opening proof for FRI layer queries");
307
308 // build a list of polynomial evaluations at each position; since evaluations in FRI layers
309 // are stored in transposed form, a position refers to N evaluations which are committed
310 // in a single leaf
311 let evaluations: &[[E; N]] = group_slice_elements(&layer.evaluations);
312 let mut queried_values: Vec<[E; N]> = Vec::with_capacity(positions.len());
313 for &position in positions.iter() {
314 queried_values.push(evaluations[position]);
315 }
316 FriProofLayer::new::<_, _, V, N>(queried_values, proof.1)
317}
318
319/// Hashes each of the arrays in the provided slice and returns a vector commitment to resulting
320/// hashes.
321pub fn build_layer_commitment<E, H, V, const N: usize>(
322 values: &[[E; N]],
323) -> Result<V, <V as VectorCommitment<H>>::Error>
324where
325 E: FieldElement,
326 H: ElementHasher<BaseField = E::BaseField>,
327 V: VectorCommitment<H>,
328{
329 let mut hashed_evaluations: Vec<H::Digest> = unsafe { uninit_vector(values.len()) };
330 iter_mut!(hashed_evaluations, 1024).zip(values).for_each(|(e, v)| {
331 let digest: H::Digest = H::hash_elements(v);
332 *e = digest
333 });
334
335 V::new(hashed_evaluations)
336}