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}