winter_verifier/lib.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//! This crate contains Winterfell STARK verifier.
7//!
8//! This verifier can be used to verify STARK proofs generated by the Winterfell STARK prover.
9//!
10//! # Usage
11//! To verify a proof that a computation was executed correctly, you'll need to do the following:
12//!
13//! 1. Define an *algebraic intermediate representation* (AIR) for you computation. This can be done
14//! by implementing [Air] trait.
15//! 2. Execute [verify()] function and supply the AIR of your computation together with the [Proof]
16//! and related public inputs as parameters.
17//!
18//! # Performance
19//! Proof verification is extremely fast and is nearly independent of the complexity of the
20//! computation being verified. In vast majority of cases proofs can be verified in 3 - 5 ms
21//! on a modern mid-range laptop CPU (using a single core).
22//!
23//! There is one exception, however: if a computation requires a lot of `sequence` assertions
24//! (see [Assertion] for more info), the verification time will grow linearly in the number of
25//! asserted values. But for the impact to be noticeable, the number of asserted values would
26//! need to be in tens of thousands. And even for hundreds of thousands of asserted values, the
27//! verification time should not exceed 50 ms.
28
29#![no_std]
30
31#[macro_use]
32extern crate alloc;
33
34use alloc::vec::Vec;
35use core::cmp;
36
37use air::proof::merge_ood_evaluations;
38pub use air::{
39 proof::Proof, Air, AirContext, Assertion, BoundaryConstraint, BoundaryConstraintGroup,
40 ConstraintCompositionCoefficients, ConstraintDivisor, DeepCompositionCoefficients,
41 EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree,
42};
43pub use crypto;
44use crypto::{ElementHasher, Hasher, RandomCoin, VectorCommitment};
45use fri::FriVerifier;
46pub use math;
47use math::{
48 fields::{CubeExtension, QuadExtension},
49 FieldElement, ToElements,
50};
51pub use utils::{
52 ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader,
53};
54
55mod channel;
56use channel::VerifierChannel;
57
58mod evaluator;
59use evaluator::evaluate_constraints;
60
61mod composer;
62use composer::DeepComposer;
63
64mod errors;
65pub use errors::VerifierError;
66
67// VERIFIER
68// ================================================================================================
69
70/// Verifies that the specified computation was executed correctly against the specified inputs.
71///
72/// Specifically, for a computation specified by `AIR` and `HashFn` type parameter, verifies that
73/// the provided `proof` attests to the correct execution of the computation against public inputs
74/// specified by `pub_inputs`. If the verification is successful, `Ok(())` is returned.
75///
76/// # Errors
77/// Returns an error if combination of the provided proof and public inputs does not attest to
78/// a correct execution of the computation. This could happen for many various reasons, including:
79/// - The specified proof was generated for a different computation.
80/// - The specified proof was generated for this computation but for different public inputs.
81/// - The specified proof was generated with parameters not providing an acceptable security level.
82pub fn verify<AIR, HashFn, RandCoin, VC>(
83 proof: Proof,
84 pub_inputs: AIR::PublicInputs,
85 acceptable_options: &AcceptableOptions,
86) -> Result<(), VerifierError>
87where
88 AIR: Air,
89 HashFn: ElementHasher<BaseField = AIR::BaseField>,
90 RandCoin: RandomCoin<BaseField = AIR::BaseField, Hasher = HashFn>,
91 VC: VectorCommitment<HashFn>,
92{
93 // check that `proof` was generated with an acceptable set of parameters from the point of view
94 // of the verifier
95 acceptable_options.validate::<HashFn>(&proof)?;
96
97 // build a seed for the public coin; the initial seed is a hash of the proof context and the
98 // public inputs, but as the protocol progresses, the coin will be reseeded with the info
99 // received from the prover
100 let mut public_coin_seed = proof.context.to_elements();
101 public_coin_seed.append(&mut pub_inputs.to_elements());
102
103 // create AIR instance for the computation specified in the proof
104 let air = AIR::new(proof.trace_info().clone(), pub_inputs, proof.options().clone());
105
106 // figure out which version of the generic proof verification procedure to run. this is a sort
107 // of static dispatch for selecting two generic parameter: extension field and hash function.
108 match air.options().field_extension() {
109 FieldExtension::None => {
110 let public_coin = RandCoin::new(&public_coin_seed);
111 let channel = VerifierChannel::new(&air, proof)?;
112 perform_verification::<AIR, AIR::BaseField, HashFn, RandCoin, VC>(
113 air,
114 channel,
115 public_coin,
116 )
117 },
118 FieldExtension::Quadratic => {
119 if !<QuadExtension<AIR::BaseField>>::is_supported() {
120 return Err(VerifierError::UnsupportedFieldExtension(2));
121 }
122 let public_coin = RandCoin::new(&public_coin_seed);
123 let channel = VerifierChannel::new(&air, proof)?;
124 perform_verification::<AIR, QuadExtension<AIR::BaseField>, HashFn, RandCoin, VC>(
125 air,
126 channel,
127 public_coin,
128 )
129 },
130 FieldExtension::Cubic => {
131 if !<CubeExtension<AIR::BaseField>>::is_supported() {
132 return Err(VerifierError::UnsupportedFieldExtension(3));
133 }
134 let public_coin = RandCoin::new(&public_coin_seed);
135 let channel = VerifierChannel::new(&air, proof)?;
136 perform_verification::<AIR, CubeExtension<AIR::BaseField>, HashFn, RandCoin, VC>(
137 air,
138 channel,
139 public_coin,
140 )
141 },
142 }
143}
144
145// VERIFICATION PROCEDURE
146// ================================================================================================
147/// Performs the actual verification by reading the data from the `channel` and making sure it
148/// attests to a correct execution of the computation specified by the provided `air`.
149fn perform_verification<A, E, H, R, V>(
150 air: A,
151 mut channel: VerifierChannel<E, H, V>,
152 mut public_coin: R,
153) -> Result<(), VerifierError>
154where
155 E: FieldElement<BaseField = A::BaseField>,
156 A: Air,
157 H: ElementHasher<BaseField = A::BaseField>,
158 R: RandomCoin<BaseField = A::BaseField, Hasher = H>,
159 V: VectorCommitment<H>,
160{
161 // 1 ----- trace commitment -------------------------------------------------------------------
162 // Read the commitments to evaluations of the trace polynomials over the LDE domain sent by the
163 // prover. The commitments are used to update the public coin, and draw sets of random elements
164 // from the coin (in the interactive version of the protocol the verifier sends these random
165 // elements to the prover after each commitment is made). When there are multiple trace
166 // commitments (i.e., the trace consists of more than one segment), each previous commitment is
167 // used to draw random elements needed to construct the next trace segment. The last trace
168 // commitment is used to draw a set of random coefficients which the prover uses to compute
169 // constraint composition polynomial.
170 const MAIN_TRACE_IDX: usize = 0;
171 const AUX_TRACE_IDX: usize = 1;
172 let trace_commitments = channel.read_trace_commitments();
173
174 // reseed the coin with the commitment to the main trace segment
175 public_coin.reseed(trace_commitments[MAIN_TRACE_IDX]);
176
177 // process auxiliary trace segments (if any), to build a set of random elements for each segment
178 let aux_trace_rand_elements = if air.trace_info().is_multi_segment() {
179 let aux_rand_elements = air
180 .get_aux_rand_elements(&mut public_coin)
181 .expect("failed to generate the random elements needed to build the auxiliary trace");
182
183 public_coin.reseed(trace_commitments[AUX_TRACE_IDX]);
184
185 Some(aux_rand_elements)
186 } else {
187 None
188 };
189
190 // build random coefficients for the composition polynomial
191 let constraint_coeffs = air
192 .get_constraint_composition_coefficients(&mut public_coin)
193 .map_err(|_| VerifierError::RandomCoinError)?;
194
195 // 2 ----- constraint commitment --------------------------------------------------------------
196 // read the commitment to evaluations of the constraint composition polynomial over the LDE
197 // domain sent by the prover, use it to update the public coin, and draw an out-of-domain point
198 // z from the coin; in the interactive version of the protocol, the verifier sends this point z
199 // to the prover, and the prover evaluates trace and constraint composition polynomials at z,
200 // and sends the results back to the verifier.
201 let constraint_commitment = channel.read_constraint_commitment();
202 public_coin.reseed(constraint_commitment);
203 let z = public_coin.draw::<E>().map_err(|_| VerifierError::RandomCoinError)?;
204
205 // 3 ----- OOD consistency check --------------------------------------------------------------
206 // make sure that evaluations obtained by evaluating constraints over the out-of-domain frame
207 // are consistent with the evaluations of composition polynomial columns sent by the prover
208
209 // read the out-of-domain trace frames (the main trace frame and auxiliary trace frame, if
210 // provided) sent by the prover and evaluate constraints over them.
211 let ood_trace_frame = channel.read_ood_trace_frame();
212 let ood_main_trace_frame = ood_trace_frame.main_frame();
213 let ood_aux_trace_frame = ood_trace_frame.aux_frame();
214 let ood_constraint_evaluation_1 = evaluate_constraints(
215 &air,
216 constraint_coeffs,
217 &ood_main_trace_frame,
218 &ood_aux_trace_frame,
219 aux_trace_rand_elements.as_ref(),
220 z,
221 );
222
223 // read evaluations of composition polynomial columns sent by the prover, and reduce
224 // the evaluations at z into a single value by computing
225 // \sum_{i=0}^{m-1}(z^(i * l) * value_i), where value_i is the
226 // evaluation of the ith column polynomial H_i(X) at z, l is the trace length and m is
227 // the number of composition column polynomials. This computes H(z) (i.e.
228 // the evaluation of the composition polynomial at z) using the fact that
229 // H(X) = \sum_{i=0}^{m-1} X^{i * l} H_i(X).
230 let ood_constraint_evaluations = channel.read_ood_constraint_frame();
231 let ood_constraint_evaluation_2 = ood_constraint_evaluations
232 .current_row()
233 .iter()
234 .enumerate()
235 .fold(E::ZERO, |result, (i, &value)| {
236 result + z.exp_vartime(((i * (air.trace_length())) as u32).into()) * value
237 });
238
239 // finally, make sure the values are the same
240 if ood_constraint_evaluation_1 != ood_constraint_evaluation_2 {
241 return Err(VerifierError::InconsistentOodConstraintEvaluations);
242 }
243
244 // reseed the public coin with OOD evaluations
245 let ood_evals = merge_ood_evaluations(&ood_trace_frame, &ood_constraint_evaluations);
246 let digest = H::hash_elements(&ood_evals);
247 public_coin.reseed(digest);
248
249 // 4 ----- FRI commitments --------------------------------------------------------------------
250 // draw coefficients for computing DEEP composition polynomial from the public coin; in the
251 // interactive version of the protocol, the verifier sends these coefficients to the prover
252 // and the prover uses them to compute the DEEP composition polynomial. the prover, then
253 // applies FRI protocol to the evaluations of the DEEP composition polynomial.
254 let deep_coefficients = air
255 .get_deep_composition_coefficients::<E, R>(&mut public_coin)
256 .map_err(|_| VerifierError::RandomCoinError)?;
257
258 // instantiates a FRI verifier with the FRI layer commitments read from the channel. From the
259 // verifier's perspective, this is equivalent to executing the commit phase of the FRI protocol.
260 // The verifier uses these commitments to update the public coin and draw random points alpha
261 // from them; in the interactive version of the protocol, the verifier sends these alphas to
262 // the prover, and the prover uses them to compute and commit to the subsequent FRI layers.
263 let fri_verifier = FriVerifier::new(
264 &mut channel,
265 &mut public_coin,
266 air.options().to_fri_options(),
267 air.trace_poly_degree(),
268 )
269 .map_err(VerifierError::FriVerificationFailed)?;
270 // TODO: make sure air.lde_domain_size() == fri_verifier.domain_size()
271
272 // 5 ----- trace and constraint queries -------------------------------------------------------
273 // read proof-of-work nonce sent by the prover
274 let pow_nonce = channel.read_pow_nonce();
275
276 // make sure the proof-of-work specified by the grinding factor is satisfied
277 if public_coin.check_leading_zeros(pow_nonce) < air.options().grinding_factor() {
278 return Err(VerifierError::QuerySeedProofOfWorkVerificationFailed);
279 }
280
281 // draw pseudo-random query positions for the LDE domain from the public coin; in the
282 // interactive version of the protocol, the verifier sends these query positions to the prover,
283 // and the prover responds with decommitments against these positions for trace and constraint
284 // composition polynomial evaluations.
285 let mut query_positions = public_coin
286 .draw_integers(air.options().num_queries(), air.lde_domain_size(), pow_nonce)
287 .map_err(|_| VerifierError::RandomCoinError)?;
288
289 // remove any potential duplicates from the positions as the prover will send openings only
290 // for unique queries
291 query_positions.sort_unstable();
292 query_positions.dedup();
293
294 // read evaluations of trace and constraint composition polynomials at the queried positions;
295 // this also checks that the read values are valid against trace and constraint commitments
296 let (queried_main_trace_states, queried_aux_trace_states) =
297 channel.read_queried_trace_states(&query_positions)?;
298 let queried_constraint_evaluations = channel.read_constraint_evaluations(&query_positions)?;
299
300 // 6 ----- DEEP composition -------------------------------------------------------------------
301 // compute evaluations of the DEEP composition polynomial at the queried positions
302 let composer = DeepComposer::new(&air, &query_positions, z, deep_coefficients);
303 let deep_evaluations = composer.compose_columns(
304 queried_main_trace_states,
305 queried_aux_trace_states,
306 queried_constraint_evaluations,
307 ood_main_trace_frame,
308 ood_aux_trace_frame,
309 ood_constraint_evaluations,
310 );
311
312 // 7 ----- Verify low-degree proof -------------------------------------------------------------
313 // make sure that evaluations of the DEEP composition polynomial we computed in the previous
314 // step are in fact evaluations of a polynomial of degree equal to trace polynomial degree
315 fri_verifier
316 .verify(&mut channel, &deep_evaluations, &query_positions)
317 .map_err(VerifierError::FriVerificationFailed)
318}
319
320// ACCEPTABLE OPTIONS
321// ================================================================================================
322// Specifies either the minimal, conjectured or proven, security level or a set of
323// `ProofOptions` that are acceptable by the verification procedure.
324pub enum AcceptableOptions {
325 /// Minimal acceptable conjectured security level
326 MinConjecturedSecurity(u32),
327 /// Minimal acceptable proven security level
328 MinProvenSecurity(u32),
329 /// Set of acceptable proof parameters
330 OptionSet(Vec<ProofOptions>),
331}
332
333impl AcceptableOptions {
334 /// Checks that a proof was generated using an acceptable set of parameters.
335 pub fn validate<H: Hasher>(&self, proof: &Proof) -> Result<(), VerifierError> {
336 match self {
337 AcceptableOptions::MinConjecturedSecurity(minimal_security) => {
338 let conjectured_security = proof.conjectured_security::<H>();
339 if !conjectured_security.is_at_least(*minimal_security) {
340 return Err(VerifierError::InsufficientConjecturedSecurity(
341 *minimal_security,
342 conjectured_security.bits(),
343 ));
344 }
345 },
346 AcceptableOptions::MinProvenSecurity(minimal_security) => {
347 let proven_security = proof.proven_security::<H>();
348 if !proven_security.is_at_least(*minimal_security) {
349 return Err(VerifierError::InsufficientProvenSecurity(
350 *minimal_security,
351 cmp::max(proven_security.ldr_bits(), proven_security.udr_bits()),
352 ));
353 }
354 },
355 AcceptableOptions::OptionSet(options) => {
356 if !options.iter().any(|opt| opt == proof.options()) {
357 return Err(VerifierError::UnacceptableProofOptions);
358 }
359 },
360 }
361 Ok(())
362 }
363}