winter_prover/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 prover.
7//!
8//! This prover can be used to generate proofs of computational integrity using the
9//! [STARK](https://eprint.iacr.org/2018/046) (Scalable Transparent ARguments of Knowledge)
10//! protocol.
11//!
12//! When the crate is compiled with `concurrent` feature enabled, proof generation will be
13//! performed in multiple threads (usually, as many threads as there are logical cores on the
14//! machine). The number of threads can be configured via `RAYON_NUM_THREADS` environment
15//! variable.
16//!
17//! # Usage
18//! To generate a proof that a computation was executed correctly, you'll need to do the
19//! following:
20//!
21//! 1. Define an *algebraic intermediate representation* (AIR) for your computation. This can be
22//! done by implementing [Air] trait.
23//! 2. Define an execution trace for your computation. This can be done by implementing [Trace]
24//! trait. Alternatively, you can use [TraceTable] struct which already implements [Trace] trait
25//! in cases when this generic implementation works for your use case.
26//! 3. Execute your computation and record its execution trace.
27//! 4. Define your prover by implementing [Prover] trait. Then execute [Prover::prove()] function
28//! passing the trace generated in the previous step into it as a parameter. The function will
29//! return a instance of [Proof].
30//!
31//! This [Proof] can be serialized and sent to a STARK verifier for verification. The size
32//! of proof depends on the specifics of a given computation, but for most computations it should
33//! be in the range between 15 KB (for very small computations) and 300 KB (for very large
34//! computations).
35//!
36//! Proof generation time is also highly dependent on the specifics of a given computation, but
37//! also depends on the capabilities of the machine used to generate the proofs (i.e. on number
38//! of CPU cores and memory bandwidth).
39
40#![no_std]
41
42#[macro_use]
43extern crate alloc;
44
45pub use air::{
46 proof, proof::Proof, Air, AirContext, Assertion, BoundaryConstraint, BoundaryConstraintGroup,
47 ConstraintCompositionCoefficients, ConstraintDivisor, DeepCompositionCoefficients,
48 EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree,
49};
50use air::{AuxRandElements, PartitionOptions};
51pub use crypto;
52use crypto::{ElementHasher, RandomCoin, VectorCommitment};
53use fri::FriProver;
54pub use math;
55use math::{
56 fft::infer_degree,
57 fields::{CubeExtension, QuadExtension},
58 ExtensibleField, FieldElement, StarkField, ToElements,
59};
60use tracing::{event, info_span, instrument, Level};
61pub use utils::{
62 iterators, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
63 SliceReader,
64};
65
66mod domain;
67pub use domain::StarkDomain;
68
69pub mod matrix;
70use matrix::{ColMatrix, RowMatrix};
71
72mod constraints;
73pub use constraints::{
74 CompositionPoly, CompositionPolyTrace, ConstraintCommitment, ConstraintEvaluator,
75 DefaultConstraintCommitment, DefaultConstraintEvaluator,
76};
77
78mod composer;
79use composer::DeepCompositionPoly;
80
81mod trace;
82use maybe_async::{maybe_async, maybe_await};
83pub use trace::{
84 AuxTraceWithMetadata, DefaultTraceLde, Trace, TraceLde, TracePolyTable, TraceTable,
85 TraceTableFragment,
86};
87
88mod channel;
89use channel::ProverChannel;
90
91mod errors;
92pub use errors::ProverError;
93
94#[cfg(test)]
95pub mod tests;
96
97// PROVER
98// ================================================================================================
99
100// this segment width seems to give the best performance for small fields (i.e., 64 bits)
101const DEFAULT_SEGMENT_WIDTH: usize = 8;
102
103/// Defines a STARK prover for a computation.
104///
105/// A STARK prover can be used to generate STARK proofs. The prover contains definitions of a
106/// computation's AIR (specified via [Air](Prover::Air) associated type), execution trace
107/// (specified via [Trace](Prover::Trace) associated type) and hash function to be used (specified
108/// via [HashFn](Prover::HashFn) associated type), and exposes [prove()](Prover::prove) method which
109/// can be used to build STARK proofs for provided execution traces.
110///
111/// Thus, once a prover is defined and instantiated, generating a STARK proof consists of two
112/// steps:
113/// 1. Build an execution trace for a specific instance of the computation.
114/// 2. Invoke [Prover::prove()] method generate a proof using the trace from the previous step as a
115/// witness.
116///
117/// The generated proof is built using protocol parameters defined by the [ProofOptions] struct
118/// return from [Prover::options] method.
119///
120/// To further customize the prover, implementers can specify custom implementations of the
121/// [RandomCoin], [TraceLde], and [ConstraintEvaluator] associated types (default implementations
122/// of these types are provided with the prover). For example, providing custom implementations
123/// of [TraceLde] and/or [ConstraintEvaluator] can be beneficial when some steps of proof
124/// generation can be delegated to non-CPU hardware (e.g., GPUs).
125pub trait Prover {
126 /// Base field for the computation described by this prover.
127 type BaseField: StarkField + ExtensibleField<2> + ExtensibleField<3>;
128
129 /// Algebraic intermediate representation (AIR) for the computation described by this prover.
130 type Air: Air<BaseField = Self::BaseField>;
131
132 /// Execution trace of the computation described by this prover.
133 type Trace: Trace<BaseField = Self::BaseField> + Send + Sync;
134
135 /// Hash function to be used.
136 type HashFn: ElementHasher<BaseField = Self::BaseField>;
137
138 /// Vector commitment scheme to be used.
139 type VC: VectorCommitment<Self::HashFn>;
140
141 /// PRNG to be used for generating random field elements.
142 type RandomCoin: RandomCoin<BaseField = Self::BaseField, Hasher = Self::HashFn>;
143
144 /// Trace low-degree extension for building the LDEs of trace segments and their commitments.
145 type TraceLde<E>: TraceLde<E, HashFn = Self::HashFn, VC = Self::VC>
146 where
147 E: FieldElement<BaseField = Self::BaseField>;
148
149 /// Constraints evaluator used to evaluate AIR constraints over the extended execution trace.
150 type ConstraintEvaluator<'a, E>: ConstraintEvaluator<E, Air = Self::Air>
151 where
152 E: FieldElement<BaseField = Self::BaseField>;
153
154 /// Constraint low-degree extension for building the LDEs of composition polynomial columns and
155 /// their commitments.
156 type ConstraintCommitment<E>: ConstraintCommitment<E, HashFn = Self::HashFn, VC = Self::VC>
157 where
158 E: FieldElement<BaseField = Self::BaseField>;
159
160 // REQUIRED METHODS
161 // --------------------------------------------------------------------------------------------
162
163 /// Returns a set of public inputs for an instance of the computation defined by the provided
164 /// trace.
165 ///
166 /// Public inputs need to be shared with the verifier in order for them to verify a proof.
167 fn get_pub_inputs(&self, trace: &Self::Trace) -> <<Self as Prover>::Air as Air>::PublicInputs;
168
169 /// Returns [ProofOptions] which this prover uses to generate STARK proofs.
170 ///
171 /// Proof options defines basic protocol parameters such as: number of queries, blowup factor,
172 /// grinding factor etc. These properties directly inform such metrics as proof generation time,
173 /// proof size, and proof security level.
174 fn options(&self) -> &ProofOptions;
175
176 /// Takes the main trace segment columns as input, interpolates them into polynomials in
177 /// coefficient form, and evaluates the polynomials over the LDE domain.
178 ///
179 /// Returns a tuple containing a [TracePolyTable] with the trace polynomials for the main trace
180 /// and a new [TraceLde] instance from which the LDE and trace commitments can be obtained.
181 #[maybe_async]
182 fn new_trace_lde<E>(
183 &self,
184 trace_info: &TraceInfo,
185 main_trace: &ColMatrix<Self::BaseField>,
186 domain: &StarkDomain<Self::BaseField>,
187 partition_option: PartitionOptions,
188 ) -> (Self::TraceLde<E>, TracePolyTable<E>)
189 where
190 E: FieldElement<BaseField = Self::BaseField>;
191
192 /// Returns a new constraint evaluator which can be used to evaluate transition and boundary
193 /// constraints over the extended execution trace.
194 #[maybe_async]
195 fn new_evaluator<'a, E>(
196 &self,
197 air: &'a Self::Air,
198 aux_rand_elements: Option<AuxRandElements<E>>,
199 composition_coefficients: ConstraintCompositionCoefficients<E>,
200 ) -> Self::ConstraintEvaluator<'a, E>
201 where
202 E: FieldElement<BaseField = Self::BaseField>;
203
204 /// Extends constraint composition polynomial over the LDE domain and builds a commitment to
205 /// its evaluations.
206 ///
207 /// The extension is done by first interpolating the evaluations of the polynomial so that we
208 /// get the composition polynomial in coefficient form; then breaking the polynomial into
209 /// columns each of size equal to trace length, and finally evaluating each composition
210 /// polynomial column over the LDE domain.
211 ///
212 /// The commitment is computed by building a vector containing the hashes of each row in
213 /// the evaluation matrix, and then building vector commitment of the resulting vector.
214 #[maybe_async]
215 fn build_constraint_commitment<E>(
216 &self,
217 composition_poly_trace: CompositionPolyTrace<E>,
218 num_constraint_composition_columns: usize,
219 domain: &StarkDomain<Self::BaseField>,
220 partition_options: PartitionOptions,
221 ) -> (Self::ConstraintCommitment<E>, CompositionPoly<E>)
222 where
223 E: FieldElement<BaseField = Self::BaseField>;
224
225 // PROVIDED METHODS
226 // --------------------------------------------------------------------------------------------
227
228 /// Builds and returns the auxiliary trace.
229 #[allow(unused_variables)]
230 #[maybe_async]
231 #[instrument(skip_all)]
232 fn build_aux_trace<E>(
233 &self,
234 main_trace: &Self::Trace,
235 aux_rand_elements: &AuxRandElements<E>,
236 ) -> ColMatrix<E>
237 where
238 E: FieldElement<BaseField = Self::BaseField>,
239 {
240 unimplemented!("`Prover::build_aux_trace` needs to be implemented when the trace has an auxiliary segment.")
241 }
242
243 /// Returns a STARK proof attesting to a correct execution of a computation defined by the
244 /// provided trace.
245 ///
246 /// The returned [Proof] attests that the specified `trace` is a valid execution trace of the
247 /// computation described by [Self::Air](Prover::Air) and generated using some set of secret and
248 /// public inputs.
249 #[maybe_async]
250 fn prove(&self, trace: Self::Trace) -> Result<Proof, ProverError>
251 where
252 <Self::Air as Air>::PublicInputs: Send,
253 {
254 // figure out which version of the generic proof generation procedure to run. this is a sort
255 // of static dispatch for selecting two generic parameter: extension field and hash
256 // function.
257 match self.options().field_extension() {
258 FieldExtension::None => maybe_await!(self.generate_proof::<Self::BaseField>(trace)),
259 FieldExtension::Quadratic => {
260 if !<QuadExtension<Self::BaseField>>::is_supported() {
261 return Err(ProverError::UnsupportedFieldExtension(2));
262 }
263 maybe_await!(self.generate_proof::<QuadExtension<Self::BaseField>>(trace))
264 },
265 FieldExtension::Cubic => {
266 if !<CubeExtension<Self::BaseField>>::is_supported() {
267 return Err(ProverError::UnsupportedFieldExtension(3));
268 }
269 maybe_await!(self.generate_proof::<CubeExtension<Self::BaseField>>(trace))
270 },
271 }
272 }
273
274 // HELPER METHODS
275 // --------------------------------------------------------------------------------------------
276
277 /// Performs the actual proof generation procedure, generating the proof that the provided
278 /// execution `trace` is valid against this prover's AIR.
279 /// TODO: make this function un-callable externally?
280 #[doc(hidden)]
281 #[maybe_async]
282 fn generate_proof<E>(&self, trace: Self::Trace) -> Result<Proof, ProverError>
283 where
284 E: FieldElement<BaseField = Self::BaseField>,
285 <Self::Air as Air>::PublicInputs: Send,
286 {
287 // 0 ----- instantiate AIR and prover channel ---------------------------------------------
288
289 // serialize public inputs; these will be included in the seed for the public coin
290 let pub_inputs = self.get_pub_inputs(&trace);
291 let pub_inputs_elements = pub_inputs.to_elements();
292
293 // create an instance of AIR for the provided parameters. This takes a generic description
294 // of the computation (provided via AIR type), and creates a description of a specific
295 // execution of the computation for the provided public inputs.
296 let air = Self::Air::new(trace.info().clone(), pub_inputs, self.options().clone());
297
298 // create a channel which is used to simulate interaction between the prover and the
299 // verifier; the channel will be used to commit to values and to draw randomness that
300 // should come from the verifier.
301 let mut channel =
302 ProverChannel::<Self::Air, E, Self::HashFn, Self::RandomCoin, Self::VC>::new(
303 &air,
304 pub_inputs_elements,
305 );
306
307 // 1 ----- Commit to the execution trace --------------------------------------------------
308
309 // build computation domain; this is used later for polynomial evaluations
310 let lde_domain_size = air.lde_domain_size();
311 let trace_length = air.trace_length();
312 let domain = info_span!("build_domain", trace_length, lde_domain_size)
313 .in_scope(|| StarkDomain::new(&air));
314 assert_eq!(domain.lde_domain_size(), lde_domain_size);
315 assert_eq!(domain.trace_length(), trace_length);
316
317 // commit to the main trace segment
318 let (mut trace_lde, mut trace_polys) =
319 maybe_await!(self.commit_to_main_trace_segment(&trace, &domain, &mut channel));
320
321 // build the auxiliary trace segment, and append the resulting segments to trace commitment
322 // and trace polynomial table structs
323 let aux_trace_with_metadata = if air.trace_info().is_multi_segment() {
324 let aux_rand_elements = air
325 .get_aux_rand_elements(channel.public_coin())
326 .expect("failed to draw random elements for the auxiliary trace segment");
327
328 let aux_trace = maybe_await!(self.build_aux_trace(&trace, &aux_rand_elements));
329
330 // commit to the auxiliary trace segment
331 let aux_segment_polys = {
332 // extend the auxiliary trace segment and commit to the extended trace
333 let span = info_span!("commit_to_aux_trace_segment").entered();
334 let (aux_segment_polys, aux_segment_commitment) =
335 trace_lde.set_aux_trace(&aux_trace, &domain);
336
337 // commit to the LDE of the extended auxiliary trace segment by writing its
338 // commitment into the channel
339 channel.commit_trace(aux_segment_commitment);
340
341 drop(span);
342 aux_segment_polys
343 };
344
345 trace_polys.add_aux_segment(aux_segment_polys);
346
347 Some(AuxTraceWithMetadata { aux_trace, aux_rand_elements })
348 } else {
349 None
350 };
351
352 // make sure the specified trace (including auxiliary segment) is valid against the AIR.
353 // This checks validity of both, assertions and state transitions. We do this in debug
354 // mode only because this is a very expensive operation.
355 #[cfg(debug_assertions)]
356 trace.validate(&air, aux_trace_with_metadata.as_ref());
357
358 // Destructure `aux_trace_with_metadata`.
359 let (aux_trace, aux_rand_elements) = match aux_trace_with_metadata {
360 Some(atm) => (Some(atm.aux_trace), Some(atm.aux_rand_elements)),
361 None => (None, None),
362 };
363
364 // drop the main trace and aux trace segment as they are no longer needed
365 drop(trace);
366 drop(aux_trace);
367
368 // 2 ----- evaluate constraints -----------------------------------------------------------
369 // evaluate constraints specified by the AIR over the constraint evaluation domain, and
370 // compute random linear combinations of these evaluations using coefficients drawn from
371 // the channel
372 let ce_domain_size = air.ce_domain_size();
373 let composition_poly_trace = maybe_await!(self.new_evaluator(
374 &air,
375 aux_rand_elements,
376 channel.get_constraint_composition_coeffs()
377 ))
378 .evaluate(&trace_lde, &domain);
379 assert_eq!(composition_poly_trace.num_rows(), ce_domain_size);
380
381 // 3 ----- commit to constraint evaluations -----------------------------------------------
382 let (constraint_commitment, composition_poly) = maybe_await!(self
383 .commit_to_constraint_evaluations(&air, composition_poly_trace, &domain, &mut channel));
384
385 // 4 ----- build DEEP composition polynomial ----------------------------------------------
386 let deep_composition_poly = {
387 let span = info_span!("build_deep_composition_poly").entered();
388 // draw an out-of-domain point z. Depending on the type of E, the point is drawn either
389 // from the base field or from an extension field defined by E.
390 //
391 // The purpose of sampling from the extension field here (instead of the base field) is
392 // to increase security. Soundness is limited by the size of the field that the random
393 // point is drawn from, and we can potentially save on performance by only drawing this
394 // point from an extension field, rather than increasing the size of the field overall.
395 let z = channel.get_ood_point();
396
397 // evaluate trace and constraint polynomials at the OOD point z and gz, where g is
398 // the generator of the trace domain. and send the results to the verifier
399 let ood_trace_states = trace_polys.get_ood_frame(z);
400 let ood_evaluations = composition_poly.get_ood_frame(z);
401 channel.send_ood_evaluations(&ood_trace_states, &ood_evaluations);
402
403 // draw random coefficients to use during DEEP polynomial composition, and use them to
404 // initialize the DEEP composition polynomial
405 let deep_coefficients = channel.get_deep_composition_coeffs();
406 let mut deep_composition_poly = DeepCompositionPoly::new(z, deep_coefficients);
407
408 // combine all polynomials together and merge them into the DEEP composition
409 // polynomial
410 deep_composition_poly.add_trace_polys(
411 trace_polys,
412 composition_poly,
413 ood_trace_states,
414 ood_evaluations,
415 );
416
417 event!(Level::DEBUG, "degree: {}", deep_composition_poly.degree());
418
419 drop(span);
420 deep_composition_poly
421 };
422
423 // make sure the degree of the DEEP composition polynomial is equal to trace polynomial
424 // degree minus 1.
425 assert_eq!(trace_length - 2, deep_composition_poly.degree());
426
427 // 5 ----- evaluate DEEP composition polynomial over LDE domain ---------------------------
428 let deep_evaluations = {
429 let span = info_span!("evaluate_deep_composition_poly").entered();
430 let deep_evaluations = deep_composition_poly.evaluate(&domain);
431 // we check the following condition in debug mode only because infer_degree is an
432 // expensive operation
433 debug_assert_eq!(trace_length - 2, infer_degree(&deep_evaluations, domain.offset()));
434
435 drop(span);
436 deep_evaluations
437 };
438
439 // 6 ----- compute FRI layers for the composition polynomial ------------------------------
440 let fri_options = air.options().to_fri_options();
441 let num_layers = fri_options.num_fri_layers(lde_domain_size);
442 let mut fri_prover = FriProver::<_, _, _, Self::VC>::new(fri_options);
443 info_span!("compute_fri_layers", num_layers)
444 .in_scope(|| fri_prover.build_layers(&mut channel, deep_evaluations));
445
446 // 7 ----- determine query positions ------------------------------------------------------
447 let query_positions = {
448 let grinding_factor = air.options().grinding_factor();
449 let num_positions = air.options().num_queries();
450 let span =
451 info_span!("determine_query_positions", grinding_factor, num_positions,).entered();
452
453 // apply proof-of-work to the query seed
454 channel.grind_query_seed();
455
456 // generate pseudo-random query positions
457 let query_positions = channel.get_query_positions();
458 event!(Level::DEBUG, "query_positions_len: {}", query_positions.len());
459
460 drop(span);
461 query_positions
462 };
463
464 // 8 ----- build proof object -------------------------------------------------------------
465 let proof = {
466 let span = info_span!("build_proof_object").entered();
467 // generate FRI proof
468 let fri_proof = fri_prover.build_proof(&query_positions);
469
470 // query the execution trace at the selected position; for each query, we need the
471 // state of the trace at that position and a batch opening proof at specified queries
472 let trace_queries = trace_lde.query(&query_positions);
473
474 // query the constraint commitment at the selected positions; for each query, we need
475 // the state of the trace at that position and a batch opening proof at specified
476 // queries
477 let constraint_queries = constraint_commitment.query(&query_positions);
478
479 // build the proof object
480 let proof = channel.build_proof(
481 trace_queries,
482 constraint_queries,
483 fri_proof,
484 query_positions.len(),
485 );
486
487 drop(span);
488 proof
489 };
490
491 Ok(proof)
492 }
493
494 #[doc(hidden)]
495 #[instrument(skip_all)]
496 #[maybe_async]
497 fn commit_to_main_trace_segment<E>(
498 &self,
499 trace: &Self::Trace,
500 domain: &StarkDomain<Self::BaseField>,
501 channel: &mut ProverChannel<'_, Self::Air, E, Self::HashFn, Self::RandomCoin, Self::VC>,
502 ) -> (Self::TraceLde<E>, TracePolyTable<E>)
503 where
504 E: FieldElement<BaseField = Self::BaseField>,
505 {
506 // extend the main execution trace and commit to the extended trace
507 let (trace_lde, trace_polys) = maybe_await!(self.new_trace_lde(
508 trace.info(),
509 trace.main_segment(),
510 domain,
511 self.options().partition_options(),
512 ));
513
514 // get the commitment to the main trace segment LDE
515 let main_trace_commitment = trace_lde.get_main_trace_commitment();
516
517 // commit to the LDE of the main trace by writing the the commitment string into
518 // the channel
519 channel.commit_trace(main_trace_commitment);
520
521 (trace_lde, trace_polys)
522 }
523
524 #[doc(hidden)]
525 #[instrument(skip_all)]
526 #[maybe_async]
527 fn commit_to_constraint_evaluations<E>(
528 &self,
529 air: &Self::Air,
530 composition_poly_trace: CompositionPolyTrace<E>,
531 domain: &StarkDomain<Self::BaseField>,
532 channel: &mut ProverChannel<'_, Self::Air, E, Self::HashFn, Self::RandomCoin, Self::VC>,
533 ) -> (Self::ConstraintCommitment<E>, CompositionPoly<E>)
534 where
535 E: FieldElement<BaseField = Self::BaseField>,
536 {
537 // first, build a commitment to the evaluations of the constraint composition polynomial
538 // columns
539 let (constraint_commitment, composition_poly) = maybe_await!(self
540 .build_constraint_commitment::<E>(
541 composition_poly_trace,
542 air.context().num_constraint_composition_columns(),
543 domain,
544 self.options().partition_options()
545 ));
546
547 // then, commit to the evaluations of constraints by writing the commitment string of
548 // the constraint commitment into the channel
549 channel.commit_constraints(constraint_commitment.commitment());
550
551 (constraint_commitment, composition_poly)
552 }
553}