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}