winter_fri/verifier/
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
6//! Contains an implementation of FRI verifier and associated components.
7
8use alloc::vec::Vec;
9use core::{marker::PhantomData, mem};
10
11use crypto::{ElementHasher, RandomCoin, VectorCommitment};
12use math::{polynom, FieldElement, StarkField};
13
14use crate::{folding::fold_positions, utils::map_positions_to_indexes, FriOptions, VerifierError};
15
16mod channel;
17pub use channel::{DefaultVerifierChannel, VerifierChannel};
18
19// FRI VERIFIER
20// ================================================================================================
21/// Implements the verifier component of the FRI protocol.
22///
23/// Given a small number of evaluations of some function *f* over domain *D* and a FRI proof, a
24/// FRI verifier determines whether *f* is a polynomial of some bounded degree *d*, such that *d*
25/// < |*D*| / 2.
26///
27/// The verifier is parametrized by the following types:
28///
29/// * `B` specifies the base field of the STARK protocol.
30/// * `E` specifies the field in which the FRI protocol is executed. This can be the same as the
31///   base field `B`, but it can also be an extension of the base field in cases when the base field
32///   is too small to provide desired security level for the FRI protocol.
33/// * `C` specifies the type used to simulate prover-verifier interaction. This type is used as an
34///   abstraction for a [FriProof](crate::FriProof). Meaning, the verifier does not consume a FRI
35///   proof directly, but reads it via [VerifierChannel] interface.
36/// * `H` specifies the Hash function used by the prover to commit to polynomial evaluations.
37///
38/// Proof verification is performed in two phases: commit phase and query phase.
39///
40/// # Commit phase
41/// During the commit phase, which is executed when the verifier is instantiated via
42/// [new()](FriVerifier::new()) function, the verifier receives a list of FRI layer commitments
43/// from the prover (via [VerifierChannel]). After each received commitment, the verifier
44/// draws a random value α from the entire field, and sends it to the prover. In the
45/// non-interactive version of the protocol, α values are derived pseudo-randomly from FRI
46/// layer commitments.
47///
48/// # Query phase
49/// During the query phase, which is executed via [verify()](FriVerifier::verify()) function,
50/// the verifier sends a set of positions in the domain *D* to the prover, and the prover responds
51/// with polynomial evaluations at these positions (together with corresponding opening proofs)
52/// across all FRI layers. The verifier then checks that:
53/// * The opening proofs are valid against the layer commitments the verifier received during the
54///   commit phase.
55/// * The evaluations are consistent across FRI layers (i.e., the degree-respecting projection was
56///   applied correctly).
57/// * The degree of the polynomial implied by evaluations at the last FRI layer (the remainder) is
58///   smaller than the degree resulting from reducing degree *d* by `folding_factor` at each FRI
59///   layer.
60pub struct FriVerifier<E, C, H, R, V>
61where
62    E: FieldElement,
63    C: VerifierChannel<E, Hasher = H>,
64    H: ElementHasher<BaseField = E::BaseField>,
65    R: RandomCoin<BaseField = E::BaseField, Hasher = H>,
66    V: VectorCommitment<H>,
67{
68    max_poly_degree: usize,
69    domain_size: usize,
70    domain_generator: E::BaseField,
71    layer_commitments: Vec<H::Digest>,
72    layer_alphas: Vec<E>,
73    options: FriOptions,
74    num_partitions: usize,
75    _channel: PhantomData<C>,
76    _public_coin: PhantomData<R>,
77    _vector_com: PhantomData<V>,
78}
79
80impl<E, C, H, R, V> FriVerifier<E, C, H, R, V>
81where
82    E: FieldElement,
83    C: VerifierChannel<E, Hasher = H, VectorCommitment = V>,
84    H: ElementHasher<BaseField = E::BaseField>,
85    R: RandomCoin<BaseField = E::BaseField, Hasher = H>,
86    V: VectorCommitment<H>,
87{
88    /// Returns a new instance of FRI verifier created from the specified parameters.
89    ///
90    /// The `max_poly_degree` parameter specifies the highest polynomial degree accepted by the
91    /// returned verifier. In combination with `blowup_factor` from the `options` parameter,
92    /// `max_poly_degree` also defines the domain over which the tested polynomial is evaluated.
93    ///
94    /// Creating a FRI verifier executes the commit phase of the FRI protocol from the verifier's
95    /// perspective. Specifically, the verifier reads FRI layer commitments from the `channel`,
96    /// and for each commitment, updates the `public_coin` with this commitment and then draws
97    /// a random value α from the coin.
98    ///
99    /// The verifier stores layer commitments and corresponding α values in its internal state,
100    /// and, thus, an instance of FRI verifier can be used to verify only a single proof.
101    ///
102    /// # Errors
103    /// Returns an error if:
104    /// * `max_poly_degree` is inconsistent with the number of FRI layers read from the channel and
105    ///   `folding_factor` specified in the `options` parameter.
106    /// * An error was encountered while drawing a random α value from the coin.
107    pub fn new(
108        channel: &mut C,
109        public_coin: &mut R,
110        options: FriOptions,
111        max_poly_degree: usize,
112    ) -> Result<Self, VerifierError> {
113        // infer evaluation domain info
114        let domain_size = max_poly_degree.next_power_of_two() * options.blowup_factor();
115        let domain_generator = E::BaseField::get_root_of_unity(domain_size.ilog2());
116
117        let num_partitions = channel.read_fri_num_partitions();
118
119        // read layer commitments from the channel and use them to build a list of alphas
120        let layer_commitments = channel.read_fri_layer_commitments();
121        let mut layer_alphas = Vec::with_capacity(layer_commitments.len());
122        let mut max_degree_plus_1 = max_poly_degree + 1;
123        for (depth, commitment) in layer_commitments.iter().enumerate() {
124            public_coin.reseed(*commitment);
125            let alpha = public_coin.draw().map_err(VerifierError::RandomCoinError)?;
126            layer_alphas.push(alpha);
127
128            // make sure the degree can be reduced by the folding factor at all layers
129            // but the remainder layer
130            if depth != layer_commitments.len() - 1
131                && !max_degree_plus_1.is_multiple_of(options.folding_factor())
132            {
133                return Err(VerifierError::DegreeTruncation(
134                    max_degree_plus_1 - 1,
135                    options.folding_factor(),
136                    depth,
137                ));
138            }
139            max_degree_plus_1 /= options.folding_factor();
140        }
141
142        Ok(FriVerifier {
143            max_poly_degree,
144            domain_size,
145            domain_generator,
146            layer_commitments,
147            layer_alphas,
148            options,
149            num_partitions,
150            _channel: PhantomData,
151            _public_coin: PhantomData,
152            _vector_com: PhantomData,
153        })
154    }
155
156    // PUBLIC ACCESSORS
157    // --------------------------------------------------------------------------------------------
158
159    /// Returns maximum degree of a polynomial accepted by this verifier.
160    pub fn max_poly_degree(&self) -> usize {
161        self.max_poly_degree
162    }
163
164    /// Returns size of the domain over which a polynomial commitment checked by this verifier
165    /// has been evaluated.
166    ///
167    /// The domain size can be computed by rounding `max_poly_degree` to the next power of two
168    /// and multiplying the result by the `blowup_factor` from the protocol options.
169    pub fn domain_size(&self) -> usize {
170        self.domain_size
171    }
172
173    /// Returns number of partitions used during FRI proof generation.
174    ///
175    /// For non-distributed proof generation, number of partitions is usually set to 1.
176    pub fn num_partitions(&self) -> usize {
177        self.num_partitions
178    }
179
180    /// Returns protocol configuration options for this verifier.
181    pub fn options(&self) -> &FriOptions {
182        &self.options
183    }
184
185    // VERIFICATION PROCEDURE
186    // --------------------------------------------------------------------------------------------
187    /// Executes the query phase of the FRI protocol.
188    ///
189    /// Returns `Ok(())` if values in the `evaluations` slice represent evaluations of a polynomial
190    /// with degree <= `max_poly_degree` at x coordinates specified by the `positions` slice.
191    ///
192    /// Thus, `positions` parameter represents the positions in the evaluation domain at which the
193    /// verifier queries the prover at the first FRI layer. Similarly, the `evaluations` parameter
194    /// specifies the evaluations of the polynomial at the first FRI layer returned by the prover
195    /// for these positions.
196    ///
197    /// Evaluations of layer polynomials for all subsequent FRI layers the verifier reads from the
198    /// specified `channel`.
199    ///
200    /// # Errors
201    /// Returns an error if:
202    /// * The length of `evaluations` is not equal to the length of `positions`.
203    /// * An unsupported folding factor was specified by the `options` for this verifier.
204    /// * Decommitments to polynomial evaluations don't match the commitment value at any of the FRI
205    ///   layers.
206    /// * The verifier detects an error in how the degree-respecting projection was applied at any
207    ///   of the FRI layers.
208    /// * The degree of the remainder at the last FRI layer is greater than the degree implied by
209    ///   `max_poly_degree` reduced by the folding factor at each FRI layer.
210    pub fn verify(
211        &self,
212        channel: &mut C,
213        evaluations: &[E],
214        positions: &[usize],
215    ) -> Result<(), VerifierError> {
216        if evaluations.len() != positions.len() {
217            return Err(VerifierError::NumPositionEvaluationMismatch(
218                positions.len(),
219                evaluations.len(),
220            ));
221        }
222
223        // static dispatch for folding factor parameter
224        let folding_factor = self.options.folding_factor();
225        match folding_factor {
226            2 => self.verify_generic::<2>(channel, evaluations, positions),
227            4 => self.verify_generic::<4>(channel, evaluations, positions),
228            8 => self.verify_generic::<8>(channel, evaluations, positions),
229            16 => self.verify_generic::<16>(channel, evaluations, positions),
230            _ => Err(VerifierError::UnsupportedFoldingFactor(folding_factor)),
231        }
232    }
233
234    /// This is the actual implementation of the verification procedure described above, but it
235    /// also takes folding factor as a generic parameter N.
236    fn verify_generic<const N: usize>(
237        &self,
238        channel: &mut C,
239        evaluations: &[E],
240        positions: &[usize],
241    ) -> Result<(), VerifierError> {
242        // pre-compute roots of unity used in computing x coordinates in the folded domain
243        let folding_roots = (0..N)
244            .map(|i| self.domain_generator.exp_vartime(((self.domain_size / N * i) as u64).into()))
245            .collect::<Vec<_>>();
246
247        // 1 ----- verify the recursive components of the FRI proof -------------------------------
248        let mut domain_generator = self.domain_generator;
249        let mut domain_size = self.domain_size;
250        let mut max_degree_plus_1 = self.max_poly_degree + 1;
251        let mut positions = positions.to_vec();
252        let mut evaluations = evaluations.to_vec();
253
254        for depth in 0..self.options.num_fri_layers(self.domain_size) {
255            // determine which evaluations were queried in the folded layer
256            let mut folded_positions =
257                fold_positions(&positions, domain_size, self.options.folding_factor());
258            // determine where these evaluations are in the vector commitment
259            let position_indexes = map_positions_to_indexes(
260                &folded_positions,
261                domain_size,
262                self.options.folding_factor(),
263                self.num_partitions,
264            );
265            // read query values from the specified indexes
266            let layer_commitment = self.layer_commitments[depth];
267            // TODO: add layer depth to the potential error message
268            let layer_values = channel.read_layer_queries(&position_indexes, &layer_commitment)?;
269            let query_values =
270                get_query_values::<E, N>(&layer_values, &positions, &folded_positions, domain_size);
271            if evaluations != query_values {
272                return Err(VerifierError::InvalidLayerFolding(depth));
273            }
274
275            // build a set of x coordinates for each row polynomial
276            #[rustfmt::skip]
277            let xs = folded_positions.iter().map(|&i| {
278                let xe = domain_generator.exp_vartime((i as u64).into()) * self.options.domain_offset();
279                folding_roots.iter()
280                    .map(|&r| E::from(xe * r))
281                    .collect::<Vec<_>>().try_into().unwrap()
282            })
283            .collect::<Vec<_>>();
284
285            // interpolate x and y values into row polynomials
286            let row_polys = polynom::interpolate_batch(&xs, &layer_values);
287
288            // calculate the pseudo-random value used for linear combination in layer folding
289            let alpha = self.layer_alphas[depth];
290
291            // check that when the polynomials are evaluated at alpha, the result is equal to
292            // the corresponding column value
293            evaluations = row_polys.iter().map(|p| polynom::eval(p, alpha)).collect();
294
295            // make sure next degree reduction does not result in degree truncation
296            if !max_degree_plus_1.is_multiple_of(N) {
297                return Err(VerifierError::DegreeTruncation(max_degree_plus_1 - 1, N, depth));
298            }
299
300            // update variables for the next iteration of the loop
301            domain_generator = domain_generator.exp_vartime((N as u32).into());
302            max_degree_plus_1 /= N;
303            domain_size /= N;
304            mem::swap(&mut positions, &mut folded_positions);
305        }
306
307        // 2 ----- verify the remainder polynomial of the FRI proof -------------------------------
308
309        // read the remainder polynomial from the channel and make sure it agrees with the
310        // evaluations from the previous layer.
311        // note that the coefficients of the remainder polynomial are sent in reverse order and
312        // this simplifies evaluation using Horner's method.
313        let remainder_poly = channel.read_remainder()?;
314        if remainder_poly.len() > max_degree_plus_1 {
315            return Err(VerifierError::RemainderDegreeMismatch(max_degree_plus_1 - 1));
316        }
317        let offset: E::BaseField = self.options().domain_offset();
318
319        for (&position, evaluation) in positions.iter().zip(evaluations) {
320            let comp_eval = eval_horner_rev::<E>(
321                &remainder_poly,
322                offset * domain_generator.exp_vartime((position as u64).into()),
323            );
324            if comp_eval != evaluation {
325                return Err(VerifierError::InvalidRemainderFolding);
326            }
327        }
328
329        Ok(())
330    }
331}
332
333// HELPER FUNCTIONS
334// ================================================================================================
335fn get_query_values<E: FieldElement, const N: usize>(
336    values: &[[E; N]],
337    positions: &[usize],
338    folded_positions: &[usize],
339    domain_size: usize,
340) -> Vec<E> {
341    let row_length = domain_size / N;
342
343    let mut result = Vec::new();
344    for position in positions {
345        let idx = folded_positions.iter().position(|&v| v == position % row_length).unwrap();
346        let value = values[idx][position / row_length];
347        result.push(value);
348    }
349
350    result
351}
352
353/// Evaluates a polynomial with coefficients in an extension field given in reverse order at a point
354/// in the base field.
355fn eval_horner_rev<E>(p: &[E], x: E::BaseField) -> E
356where
357    E: FieldElement,
358{
359    p.iter().fold(E::ZERO, |acc, &coeff| acc * E::from(x) + coeff)
360}