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}