diff --git a/air/src/air/boundary/constraint.rs b/air/src/air/boundary/constraint.rs index e367ea134..5257663ce 100644 --- a/air/src/air/boundary/constraint.rs +++ b/air/src/air/boundary/constraint.rs @@ -9,6 +9,7 @@ use math::{fft, polynom}; // BOUNDARY CONSTRAINT // ================================================================================================ + /// The numerator portion of a boundary constraint. /// /// A boundary constraint is described by a rational function $\frac{f(x) - b(x)}{z(x)}$, where: @@ -45,8 +46,9 @@ where F: FieldElement, E: FieldElement + ExtensionOf, { - // CONSTRUCTOR + // CONSTRUCTORS // -------------------------------------------------------------------------------------------- + /// Creates a new boundary constraint from the specified assertion. pub(super) fn new( assertion: Assertion, diff --git a/air/src/air/coefficients.rs b/air/src/air/coefficients.rs index b6d2196c5..144c5846a 100644 --- a/air/src/air/coefficients.rs +++ b/air/src/air/coefficients.rs @@ -66,8 +66,16 @@ impl Default for AuxTraceRandElements { pub struct ConstraintCompositionCoefficients { pub transition: Vec, pub boundary: Vec, + pub lagrange: Option>, } +/// Stores the constraint composition coefficients for the Lagrange kernel transition and boundary +/// constraints. +#[derive(Debug, Clone)] +pub struct LagrangeConstraintsCompositionCoefficients { + pub transition: Vec, + pub boundary: E, +} // DEEP COMPOSITION COEFFICIENTS // ================================================================================================ /// Coefficients used in construction of DEEP composition polynomial. diff --git a/air/src/air/context.rs b/air/src/air/context.rs index cac0b4654..67117cea9 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -19,6 +19,7 @@ pub struct AirContext { pub(super) aux_transition_constraint_degrees: Vec, pub(super) num_main_assertions: usize, pub(super) num_aux_assertions: usize, + pub(super) lagrange_kernel_aux_column_idx: Option, pub(super) ce_blowup_factor: usize, pub(super) trace_domain_generator: B, pub(super) lde_domain_generator: B, @@ -59,6 +60,7 @@ impl AirContext { Vec::new(), num_assertions, 0, + None, options, ) } @@ -91,6 +93,7 @@ impl AirContext { aux_transition_constraint_degrees: Vec, num_main_assertions: usize, num_aux_assertions: usize, + lagrange_kernel_aux_column_idx: Option, options: ProofOptions, ) -> Self { assert!( @@ -103,7 +106,7 @@ impl AirContext { assert!( !aux_transition_constraint_degrees.is_empty(), "at least one transition constraint degree must be specified for auxiliary trace segments" - ); + ); assert!( num_aux_assertions > 0, "at least one assertion must be specified against auxiliary trace segments" @@ -119,6 +122,11 @@ impl AirContext { ); } + // validate Lagrange kernel aux column, if any + if let Some(lagrange_kernel_aux_column_idx) = lagrange_kernel_aux_column_idx { + assert!(lagrange_kernel_aux_column_idx < trace_info.get_aux_segment_width(0), "Lagrange kernel column index out of bounds: index={}, but only {} columns in segment", lagrange_kernel_aux_column_idx, trace_info.get_aux_segment_width(0)); + } + // determine minimum blowup factor needed to evaluate transition constraints by taking // the blowup factor of the highest degree constraint let mut ce_blowup_factor = 0; @@ -151,6 +159,7 @@ impl AirContext { aux_transition_constraint_degrees, num_main_assertions, num_aux_assertions, + lagrange_kernel_aux_column_idx, ce_blowup_factor, trace_domain_generator: B::get_root_of_unity(trace_length.ilog2()), lde_domain_generator: B::get_root_of_unity(lde_domain_size.ilog2()), @@ -161,9 +170,14 @@ impl AirContext { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- + /// Returns the trace info for an instance of a computation. + pub fn trace_info(&self) -> &TraceInfo { + &self.trace_info + } + /// Returns length of the execution trace for an instance of a computation. /// - // This is guaranteed to be a power of two greater than or equal to 8. + /// This is guaranteed to be a power of two greater than or equal to 8. pub fn trace_len(&self) -> usize { self.trace_info.length() } @@ -189,12 +203,13 @@ impl AirContext { self.trace_info.length() * self.options.blowup_factor() } - /// Returns the number of transition constraints for a computation. + /// Returns the number of transition constraints for a computation, excluding the Lagrange + /// kernel transition constraints, which are managed separately. /// - /// The number of transition constraints is defined by the total number of transition - /// constraint degree descriptors (for both the main and the auxiliary trace constraints). - /// This number is used to determine how many transition constraint coefficients need to be - /// generated for merging transition constraints into a composition polynomial. + /// The number of transition constraints is defined by the total number of transition constraint + /// degree descriptors (for both the main and the auxiliary trace constraints). This number is + /// used to determine how many transition constraint coefficients need to be generated for + /// merging transition constraints into a constraint composition polynomial. pub fn num_transition_constraints(&self) -> usize { self.main_transition_constraint_degrees.len() + self.aux_transition_constraint_degrees.len() } @@ -209,7 +224,18 @@ impl AirContext { self.aux_transition_constraint_degrees.len() } - /// Returns the total number of assertions defined for a computation. + /// Returns the index of the auxiliary column which implements the Lagrange kernel, if any + pub fn lagrange_kernel_aux_column_idx(&self) -> Option { + self.lagrange_kernel_aux_column_idx + } + + /// Returns true if the auxiliary trace segment contains a Lagrange kernel column + pub fn has_lagrange_kernel_aux_column(&self) -> bool { + self.lagrange_kernel_aux_column_idx().is_some() + } + + /// Returns the total number of assertions defined for a computation, excluding the Lagrange + /// kernel assertion, which is managed separately. /// /// The number of assertions consists of the assertions placed against the main segment of an /// execution trace as well as assertions placed against all auxiliary trace segments. @@ -230,24 +256,26 @@ impl AirContext { /// Returns the number of columns needed to store the constraint composition polynomial. /// /// This is the maximum of: - /// 1. The maximum evaluation degree over all transition constraints minus the degree - /// of the transition constraint divisor divided by trace length. + /// 1. The maximum evaluation degree over all transition constraints minus the degree of the + /// transition constraint divisor divided by trace length. /// 2. `1`, because the constraint composition polynomial requires at least one column. /// - /// Since the degree of a constraint `C(x)` can be well approximated by - /// `[constraint.base + constraint.cycles.len()] * [trace_length - 1]` the degree of the - /// constraint composition polynomial can be approximated by: - /// `([constraint.base + constraint.cycles.len()] * [trace_length - 1] - [trace_length - n])` - /// where `constraint` is the constraint attaining the maximum and `n` is the number of - /// exemption points. - /// In the case `n = 1`, the expression simplifies to: - /// `[constraint.base + constraint.cycles.len() - 1] * [trace_length - 1]` - /// Thus, if each column is of length `trace_length`, we would need - /// `[constraint.base + constraint.cycles.len() - 1]` columns to store the coefficients of - /// the constraint composition polynomial. - /// This means that if the highest constraint degree is equal to `5`, the constraint - /// composition polynomial will require four columns and if the highest constraint degree is - /// equal to `7`, it will require six columns to store. + /// Since the degree of a constraint `C(x)` can be computed as `[constraint.base + + /// constraint.cycles.len()] * [trace_length - 1]` the degree of the constraint composition + /// polynomial can be computed as: `([constraint.base + constraint.cycles.len()] * [trace_length + /// - 1] - [trace_length - n])` where `constraint` is the constraint attaining the maximum and + /// `n` is the number of exemption points. In the case `n = 1`, the expression simplifies to: + /// `[constraint.base + constraint.cycles.len() - 1] * [trace_length - 1]` Thus, if each column + /// is of length `trace_length`, we would need `[constraint.base + constraint.cycles.len() - 1]` + /// columns to store the coefficients of the constraint composition polynomial. This means that + /// if the highest constraint degree is equal to `5`, the constraint composition polynomial will + /// require four columns and if the highest constraint degree is equal to `7`, it will require + /// six columns to store. + /// + /// Note that the Lagrange kernel constraints require only 1 column, since the degree of the + /// numerator is `trace_len - 1` for all transition constraints (i.e. the base degree is 1). + /// Hence, no matter what the degree of the divisor is for each, the degree of the fraction will + /// be at most `trace_len - 1`. pub fn num_constraint_composition_columns(&self) -> usize { let mut highest_constraint_degree = 0_usize; for degree in self diff --git a/air/src/air/divisor.rs b/air/src/air/divisor.rs index 61fba7ad5..4b5087c02 100644 --- a/air/src/air/divisor.rs +++ b/air/src/air/divisor.rs @@ -44,24 +44,24 @@ impl ConstraintDivisor { /// /// For transition constraints, the divisor polynomial $z(x)$ is always the same: /// - /// $$ - /// z(x) = \frac{x^n - 1}{ \prod_{i=1}^k (x - g^{n-i})} - /// $$ + /// $$ z(x) = \frac{x^n - 1}{ \prod_{i=1}^k (x - g^{n-i})} $$ /// - /// where, $n$ is the length of the execution trace, $g$ is the generator of the trace - /// domain, and $k$ is the number of exemption points. The default value for $k$ is $1$. + /// where, $n$ is the length of the execution trace, $g$ is the generator of the trace domain, + /// and $k$ is the number of exemption points. The default value for $k$ is $1$. /// /// The above divisor specifies that transition constraints must hold on all steps of the - /// execution trace except for the last $k$ steps. - pub fn from_transition(trace_length: usize, num_exemptions: usize) -> Self { - assert!( - num_exemptions > 0, - "invalid number of transition exemptions: must be greater than zero" - ); - let exemptions = (trace_length - num_exemptions..trace_length) - .map(|step| get_trace_domain_value_at::(trace_length, step)) + /// constraint enforcement domain except for the last $k$ steps. The constraint enforcement + /// domain is the entire trace in the case of transition constraints, but only a subset of the + /// trace for Lagrange kernel transition constraints. + pub fn from_transition( + constraint_enforcement_domain_size: usize, + num_exemptions: usize, + ) -> Self { + let exemptions = (constraint_enforcement_domain_size - num_exemptions + ..constraint_enforcement_domain_size) + .map(|step| get_trace_domain_value_at::(constraint_enforcement_domain_size, step)) .collect(); - Self::new(vec![(trace_length, B::ONE)], exemptions) + Self::new(vec![(constraint_enforcement_domain_size, B::ONE)], exemptions) } /// Builds a divisor for a boundary constraint described by the assertion. diff --git a/air/src/air/lagrange/boundary.rs b/air/src/air/lagrange/boundary.rs new file mode 100644 index 000000000..e3c1b258d --- /dev/null +++ b/air/src/air/lagrange/boundary.rs @@ -0,0 +1,63 @@ +use math::FieldElement; + +use crate::LagrangeKernelEvaluationFrame; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct LagrangeKernelBoundaryConstraint +where + E: FieldElement, +{ + assertion_value: E, + composition_coefficient: E, +} + +impl LagrangeKernelBoundaryConstraint +where + E: FieldElement, +{ + /// Creates a new Lagrange kernel boundary constraint. + pub fn new(composition_coefficient: E, lagrange_kernel_rand_elements: &[E]) -> Self { + Self { + assertion_value: Self::assertion_value(lagrange_kernel_rand_elements), + composition_coefficient, + } + } + + /// Returns the evaluation of the boundary constraint at `x`, multiplied by the composition + /// coefficient. + /// + /// `frame` is the evaluation frame of the Lagrange kernel column `c`, starting at `c(x)` + pub fn evaluate_at(&self, x: E, frame: &LagrangeKernelEvaluationFrame) -> E { + let numerator = self.evaluate_numerator_at(frame); + let denominator = self.evaluate_denominator_at(x); + + numerator / denominator + } + + /// Returns the evaluation of the boundary constraint numerator, multiplied by the composition + /// coefficient. + /// + /// `frame` is the evaluation frame of the Lagrange kernel column `c`, starting at `c(x)` for + /// some `x` + pub fn evaluate_numerator_at(&self, frame: &LagrangeKernelEvaluationFrame) -> E { + let trace_value = frame.inner()[0]; + let constraint_evaluation = trace_value - self.assertion_value; + + constraint_evaluation * self.composition_coefficient + } + + /// Returns the evaluation of the boundary constraint denominator at point `x`. + pub fn evaluate_denominator_at(&self, x: E) -> E { + x - E::ONE + } + + /// Computes the assertion value given the provided random elements. + pub fn assertion_value(lagrange_kernel_rand_elements: &[E]) -> E { + let mut assertion_value = E::ONE; + for &rand_ele in lagrange_kernel_rand_elements { + assertion_value *= E::ONE - rand_ele; + } + + assertion_value + } +} diff --git a/air/src/air/lagrange/frame.rs b/air/src/air/lagrange/frame.rs new file mode 100644 index 000000000..2bfb6a62b --- /dev/null +++ b/air/src/air/lagrange/frame.rs @@ -0,0 +1,80 @@ +use alloc::vec::Vec; +use math::{polynom, FieldElement, StarkField}; + +/// The evaluation frame for the Lagrange kernel. +/// +/// The Lagrange kernel's evaluation frame is different from [`crate::EvaluationFrame`]. +/// Specifically, +/// - it only contains evaluations from the Lagrange kernel column compared to all columns in the +/// case of [`crate::EvaluationFrame`]) +/// - The column is evaluated at points `x`, `gx`, `g^2 x`, ..., `g^(2^(v-1)) x`, where `x` is an +/// arbitrary point, and `g` is the trace domain generator +#[derive(Debug, Clone)] +pub struct LagrangeKernelEvaluationFrame { + frame: Vec, +} + +impl LagrangeKernelEvaluationFrame { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Constructs a Lagrange kernel evaluation frame from the raw column polynomial evaluations. + pub fn new(frame: Vec) -> Self { + Self { frame } + } + + /// Constructs an empty Lagrange kernel evaluation frame from the raw column polynomial + /// evaluations. The frame can subsequently be filled using [`Self::frame_mut`]. + pub fn new_empty() -> Self { + Self { frame: Vec::new() } + } + + /// Constructs the frame from the Lagrange kernel column trace polynomial coefficients for an + /// evaluation point. + pub fn from_lagrange_kernel_column_poly(lagrange_kernel_col_poly: &[E], z: E) -> Self { + let log_trace_len = lagrange_kernel_col_poly.len().ilog2(); + let g = E::from(E::BaseField::get_root_of_unity(log_trace_len)); + + let mut frame = Vec::with_capacity(log_trace_len as usize + 1); + + // push c(x) + frame.push(polynom::eval(lagrange_kernel_col_poly, z)); + + // push c(z * g), c(z * g^2), c(z * g^4), ..., c(z * g^(2^(v-1))) + let mut g_exp = g; + for _ in 0..log_trace_len { + let x = g_exp * z; + let lagrange_poly_at_x = polynom::eval(lagrange_kernel_col_poly, x); + + frame.push(lagrange_poly_at_x); + + // takes on the values `g`, `g^2`, `g^4`, `g^8`, ... + g_exp *= g_exp; + } + + Self { frame } + } + + // MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Returns a mutable reference to the inner frame. + pub fn frame_mut(&mut self) -> &mut Vec { + &mut self.frame + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a reference to the inner frame. + pub fn inner(&self) -> &[E] { + &self.frame + } + + /// Returns the number of rows in the frame. + /// + /// This is equal to `log(trace_length) + 1`. + pub fn num_rows(&self) -> usize { + self.frame.len() + } +} diff --git a/air/src/air/lagrange/mod.rs b/air/src/air/lagrange/mod.rs new file mode 100644 index 000000000..560a080ab --- /dev/null +++ b/air/src/air/lagrange/mod.rs @@ -0,0 +1,38 @@ +mod boundary; +pub use boundary::LagrangeKernelBoundaryConstraint; + +mod frame; +pub use frame::LagrangeKernelEvaluationFrame; + +mod transition; +use math::FieldElement; +pub use transition::LagrangeKernelTransitionConstraints; + +use crate::LagrangeConstraintsCompositionCoefficients; + +/// Represents the Lagrange kernel transition and boundary constraints. +pub struct LagrangeKernelConstraints { + pub transition: LagrangeKernelTransitionConstraints, + pub boundary: LagrangeKernelBoundaryConstraint, + pub lagrange_kernel_col_idx: usize, +} + +impl LagrangeKernelConstraints { + /// Constructs a new [`LagrangeKernelConstraints`]. + pub fn new( + lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, + lagrange_kernel_rand_elements: &[E], + lagrange_kernel_col_idx: usize, + ) -> Self { + Self { + transition: LagrangeKernelTransitionConstraints::new( + lagrange_composition_coefficients.transition, + ), + boundary: LagrangeKernelBoundaryConstraint::new( + lagrange_composition_coefficients.boundary, + lagrange_kernel_rand_elements, + ), + lagrange_kernel_col_idx, + } + } +} diff --git a/air/src/air/lagrange/transition.rs b/air/src/air/lagrange/transition.rs new file mode 100644 index 000000000..512c8f909 --- /dev/null +++ b/air/src/air/lagrange/transition.rs @@ -0,0 +1,136 @@ +use alloc::vec::Vec; + +use math::{ExtensionOf, FieldElement}; + +use crate::{ConstraintDivisor, LagrangeKernelEvaluationFrame}; + +/// Represents the transition constraints for the Lagrange kernel column, as well as the random +/// coefficients used to linearly combine all the constraints. +/// +/// There are `log(trace_len)` constraints, each with its own divisor, as described in +/// [this issue](https://github.com/facebook/winterfell/issues/240). +pub struct LagrangeKernelTransitionConstraints { + lagrange_constraint_coefficients: Vec, + divisors: Vec>, +} + +impl LagrangeKernelTransitionConstraints { + /// Creates a new [`LagrangeKernelTransitionConstraints`], which represents the Lagrange kernel + /// transition constraints as well as the random coefficients necessary to combine the + /// constraints together. + pub fn new(lagrange_constraint_coefficients: Vec) -> Self { + let num_lagrange_kernel_transition_constraints = lagrange_constraint_coefficients.len(); + + let divisors = { + let mut divisors = Vec::with_capacity(num_lagrange_kernel_transition_constraints); + for i in 0..num_lagrange_kernel_transition_constraints { + let constraint_domain_size = 2_usize.pow(i as u32); + let divisor = ConstraintDivisor::from_transition(constraint_domain_size, 0); + + divisors.push(divisor); + } + divisors + }; + + Self { + lagrange_constraint_coefficients, + divisors, + } + } + + /// Evaluates the numerator of the `constraint_idx`th transition constraint. + pub fn evaluate_ith_numerator( + &self, + lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, + lagrange_kernel_rand_elements: &[E], + constraint_idx: usize, + ) -> E + where + F: FieldElement, + E: ExtensionOf, + { + let c = lagrange_kernel_column_frame.inner(); + let v = c.len() - 1; + let r = lagrange_kernel_rand_elements; + let k = constraint_idx + 1; + + let eval = (r[v - k] * c[0]) - ((E::ONE - r[v - k]) * c[v - k + 1]); + + self.lagrange_constraint_coefficients[constraint_idx].mul_base(eval) + } + + /// Evaluates the divisor of the `constraint_idx`th transition constraint. + pub fn evaluate_ith_divisor(&self, constraint_idx: usize, x: F) -> E + where + F: FieldElement, + E: ExtensionOf, + { + self.divisors[constraint_idx].evaluate_at(x.into()) + } + + /// Evaluates the transition constraints over the specificed Lagrange kernel evaluation frame, + /// and combines them. + /// + /// By "combining transition constraints evaluations", we mean computing a linear combination of + /// each transition constraint evaluation, where each transition evaluation is divided by its + /// corresponding divisor. + pub fn evaluate_and_combine( + &self, + lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, + lagrange_kernel_rand_elements: &[E], + x: F, + ) -> E + where + F: FieldElement, + E: ExtensionOf, + { + let numerators = self + .evaluate_numerators::(lagrange_kernel_column_frame, lagrange_kernel_rand_elements); + + numerators + .iter() + .zip(self.divisors.iter()) + .fold(E::ZERO, |acc, (&numerator, divisor)| { + let z = divisor.evaluate_at(x); + + acc + (numerator / z.into()) + }) + } + + /// Returns the number of constraints. + pub fn num_constraints(&self) -> usize { + self.lagrange_constraint_coefficients.len() + } + + // HELPERS + // --------------------------------------------------------------------------------------------- + + /// Evaluates the transition constraints' numerators over the specificed Lagrange kernel + /// evaluation frame. + fn evaluate_numerators( + &self, + lagrange_kernel_column_frame: &LagrangeKernelEvaluationFrame, + lagrange_kernel_rand_elements: &[E], + ) -> Vec + where + F: FieldElement, + E: ExtensionOf, + { + let log2_trace_len = lagrange_kernel_column_frame.num_rows() - 1; + let mut transition_evals = E::zeroed_vector(log2_trace_len); + + let c = lagrange_kernel_column_frame.inner(); + let v = c.len() - 1; + let r = lagrange_kernel_rand_elements; + + for k in 1..v + 1 { + transition_evals[k - 1] = (r[v - k] * c[0]) - ((E::ONE - r[v - k]) * c[v - k + 1]); + } + + transition_evals + .into_iter() + .zip(self.lagrange_constraint_coefficients.iter()) + .map(|(transition_eval, &coeff)| coeff.mul_base(transition_eval)) + .collect() + } +} diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index 43956e5b6..1fb7e6b46 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -23,9 +23,16 @@ pub use boundary::{BoundaryConstraint, BoundaryConstraintGroup, BoundaryConstrai mod transition; pub use transition::{EvaluationFrame, TransitionConstraintDegree, TransitionConstraints}; +mod lagrange; +pub use lagrange::{ + LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, + LagrangeKernelTransitionConstraints, +}; + mod coefficients; pub use coefficients::{ AuxTraceRandElements, ConstraintCompositionCoefficients, DeepCompositionCoefficients, + LagrangeConstraintsCompositionCoefficients, }; mod divisor; @@ -163,7 +170,7 @@ const MIN_CYCLE_LENGTH: usize = 2; /// trait: /// * The [AirContext] struct returned from [Air::context()] method must be instantiated using /// [AirContext::new_multi_segment()] constructor. When building AIR context in this way, you -/// will need to provide a [TraceLayout] which describes the shape of a multi-segment execution +/// will need to provide a [`crate::TraceInfo`] which describes the shape of a multi-segment execution /// trace. /// * Override [Air::evaluate_aux_transition()] method. This method is similar to the /// [Air::evaluate_transition()] method but it also accepts two extra parameters: @@ -285,6 +292,22 @@ pub trait Air: Send + Sync { // PROVIDED METHODS // -------------------------------------------------------------------------------------------- + /// Returns a new [`LagrangeKernelConstraints`] if a Lagrange kernel auxiliary column is present + /// in the trace, or `None` otherwise. + fn get_lagrange_kernel_constraints>( + &self, + lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, + lagrange_kernel_rand_elements: &[E], + ) -> Option> { + self.context().lagrange_kernel_aux_column_idx().map(|col_idx| { + LagrangeKernelConstraints::new( + lagrange_composition_coefficients, + lagrange_kernel_rand_elements, + col_idx, + ) + }) + } + /// Returns values for all periodic columns used in the computation. /// /// These values will be used to compute column values at specific states of the computation @@ -501,9 +524,26 @@ pub trait Air: Send + Sync { b_coefficients.push(public_coin.draw()?); } + let lagrange = if self.context().has_lagrange_kernel_aux_column() { + let mut lagrange_kernel_t_coefficients = Vec::new(); + for _ in 0..self.context().trace_len().ilog2() { + lagrange_kernel_t_coefficients.push(public_coin.draw()?); + } + + let lagrange_kernel_boundary = public_coin.draw()?; + + Some(LagrangeConstraintsCompositionCoefficients { + transition: lagrange_kernel_t_coefficients, + boundary: lagrange_kernel_boundary, + }) + } else { + None + }; + Ok(ConstraintCompositionCoefficients { transition: t_coefficients, boundary: b_coefficients, + lagrange, }) } diff --git a/air/src/air/trace_info.rs b/air/src/air/trace_info.rs index 262b6da8a..29d0d69dd 100644 --- a/air/src/air/trace_info.rs +++ b/air/src/air/trace_info.rs @@ -288,10 +288,10 @@ impl Serializable for TraceInfo { } impl Deserializable for TraceInfo { - /// Reads [TraceLayout] from the specified `source` and returns the result. + /// Reads [`TraceInfo`] from the specified `source` and returns the result. /// /// # Errors - /// Returns an error of a valid [TraceLayout] struct could not be read from the specified + /// Returns an error of a valid [`TraceInfo`] struct could not be read from the specified /// `source`. fn read_from(source: &mut R) -> Result { let main_segment_width = source.read_u8()? as usize; diff --git a/air/src/air/transition/frame.rs b/air/src/air/transition/frame.rs index 00de4eed6..94f5c9e74 100644 --- a/air/src/air/transition/frame.rs +++ b/air/src/air/transition/frame.rs @@ -8,6 +8,7 @@ use alloc::vec::Vec; // EVALUATION FRAME // ================================================================================================ + /// A set of execution trace rows required for evaluation of transition constraints. /// /// In the current implementation, an evaluation frame always contains two consecutive rows of the diff --git a/air/src/air/transition/mod.rs b/air/src/air/transition/mod.rs index b5282b04e..c3f065dcc 100644 --- a/air/src/air/transition/mod.rs +++ b/air/src/air/transition/mod.rs @@ -17,8 +17,9 @@ pub use degree::TransitionConstraintDegree; const MIN_CYCLE_LENGTH: usize = 2; -// TRANSITION CONSTRAINT INFO +// TRANSITION CONSTRAINTS INFO // ================================================================================================ + /// Metadata for transition constraints of a computation. /// /// This metadata includes: diff --git a/air/src/lib.rs b/air/src/lib.rs index a8d2eaaa5..936b76fab 100644 --- a/air/src/lib.rs +++ b/air/src/lib.rs @@ -44,6 +44,8 @@ mod air; pub use air::{ Air, AirContext, Assertion, AuxTraceRandElements, BoundaryConstraint, BoundaryConstraintGroup, BoundaryConstraints, ConstraintCompositionCoefficients, ConstraintDivisor, - DeepCompositionCoefficients, EvaluationFrame, TraceInfo, TransitionConstraintDegree, + DeepCompositionCoefficients, EvaluationFrame, LagrangeConstraintsCompositionCoefficients, + LagrangeKernelBoundaryConstraint, LagrangeKernelConstraints, LagrangeKernelEvaluationFrame, + LagrangeKernelTransitionConstraints, TraceInfo, TransitionConstraintDegree, TransitionConstraints, }; diff --git a/air/src/proof/mod.rs b/air/src/proof/mod.rs index 69b5e1464..c5dd8d733 100644 --- a/air/src/proof/mod.rs +++ b/air/src/proof/mod.rs @@ -23,7 +23,7 @@ mod queries; pub use queries::Queries; mod ood_frame; -pub use ood_frame::OodFrame; +pub use ood_frame::{OodFrame, OodFrameTraceStates, ParsedOodFrame}; mod table; pub use table::Table; diff --git a/air/src/proof/ood_frame.rs b/air/src/proof/ood_frame.rs index b5257411b..e63bac667 100644 --- a/air/src/proof/ood_frame.rs +++ b/air/src/proof/ood_frame.rs @@ -9,18 +9,25 @@ use utils::{ ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader, }; -// TYPE ALIASES -// ================================================================================================ - -type ParsedOodFrame = (Vec, Vec); +use crate::LagrangeKernelEvaluationFrame; // OUT-OF-DOMAIN FRAME // ================================================================================================ + +/// Represents an [`OodFrame`] where the trace and constraint evaluations have been parsed out. +pub struct ParsedOodFrame { + pub trace_evaluations: Vec, + pub lagrange_kernel_trace_evaluations: Option>, + pub constraint_evaluations: Vec, +} + /// Trace and constraint polynomial evaluations at an out-of-domain point. /// /// This struct contains the following evaluations: /// * Evaluations of all trace polynomials at *z*. /// * Evaluations of all trace polynomials at *z * g*. +/// * Evaluations of Lagrange kernel trace polynomial (if any) at *z*, *z * g*, *z * g^2*, ..., +/// *z * g^(2^(v-1))*, where `v == log(trace_len)` /// * Evaluations of constraint composition column polynomials at *z*. /// /// where *z* is an out-of-domain point and *g* is the generator of the trace domain. @@ -30,6 +37,7 @@ type ParsedOodFrame = (Vec, Vec); #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct OodFrame { trace_states: Vec, + lagrange_kernel_trace_states: Vec, evaluations: Vec, } @@ -38,29 +46,48 @@ impl OodFrame { // -------------------------------------------------------------------------------------------- /// Updates the trace state portion of this out-of-domain frame. This also returns a compacted - /// version of the out-of-domain frame with the rows interleaved. This is done so that reseeding - /// of the random coin needs to be done only once as opposed to once per each row. + /// version of the out-of-domain frame (including the Lagrange kernel frame, if any) with the + /// rows interleaved. This is done so that reseeding of the random coin needs to be done only + /// once as opposed to once per each row. /// /// # Panics /// Panics if evaluation frame has already been set. - pub fn set_trace_states(&mut self, trace_states: &[Vec]) -> Vec { + pub fn set_trace_states( + &mut self, + trace_states: &OodFrameTraceStates, + ) -> Vec { assert!(self.trace_states.is_empty(), "trace sates have already been set"); // save the evaluations with the current and next evaluations interleaved for each polynomial - let frame_size = trace_states.len(); - let width = trace_states[0].len(); let mut result = vec![]; - for i in 0..width { - for row in trace_states.iter() { - result.push(row[i]); - } + for col in 0..trace_states.num_columns() { + result.push(trace_states.current_row[col]); + result.push(trace_states.next_row[col]); } - debug_assert!(frame_size <= u8::MAX as usize); - self.trace_states.write_u8(frame_size as u8); + + // there are 2 frames: current and next + let frame_size: u8 = 2; + + self.trace_states.write_u8(frame_size); self.trace_states.write_many(&result); - result + // save the Lagrange kernel evaluation frame (if any) + let lagrange_trace_states = { + let lagrange_trace_states = match trace_states.lagrange_kernel_frame { + Some(ref lagrange_trace_states) => lagrange_trace_states.inner().to_vec(), + None => Vec::new(), + }; + + // trace states length will be smaller than u8::MAX, since it is `== log2(trace_len) + 1` + debug_assert!(lagrange_trace_states.len() < u8::MAX.into()); + self.lagrange_kernel_trace_states.write_u8(lagrange_trace_states.len() as u8); + self.lagrange_kernel_trace_states.write_many(&lagrange_trace_states); + + lagrange_trace_states + }; + + result.into_iter().chain(lagrange_trace_states).collect() } /// Updates constraint evaluation portion of this out-of-domain frame. @@ -85,7 +112,7 @@ impl OodFrame { /// /// # Errors /// Returns an error if: - /// * Valid [EvaluationFrame]s for the specified `main_trace_width` and `aux_trace_width` + /// * Valid [`crate::EvaluationFrame`]s for the specified `main_trace_width` and `aux_trace_width` /// could not be parsed from the internal bytes. /// * A vector of evaluations specified by `num_evaluations` could not be parsed from the /// internal bytes. @@ -103,10 +130,20 @@ impl OodFrame { let mut reader = SliceReader::new(&self.trace_states); let frame_size = reader.read_u8()? as usize; let trace = reader.read_many((main_trace_width + aux_trace_width) * frame_size)?; + if reader.has_more_bytes() { return Err(DeserializationError::UnconsumedBytes); } + // parse Lagrange kernel column trace + let mut reader = SliceReader::new(&self.lagrange_kernel_trace_states); + let lagrange_kernel_frame_size = reader.read_u8()? as usize; + let lagrange_kernel_trace = if lagrange_kernel_frame_size > 0 { + Some(reader.read_many(lagrange_kernel_frame_size)?) + } else { + None + }; + // parse the constraint evaluations let mut reader = SliceReader::new(&self.evaluations); let evaluations = reader.read_many(num_evaluations)?; @@ -114,7 +151,11 @@ impl OodFrame { return Err(DeserializationError::UnconsumedBytes); } - Ok((trace, evaluations)) + Ok(ParsedOodFrame { + trace_evaluations: trace, + lagrange_kernel_trace_evaluations: lagrange_kernel_trace, + constraint_evaluations: evaluations, + }) } } @@ -128,6 +169,10 @@ impl Serializable for OodFrame { target.write_u16(self.trace_states.len() as u16); target.write_bytes(&self.trace_states); + // write Lagrange kernel column trace rows + target.write_u16(self.lagrange_kernel_trace_states.len() as u16); + target.write_bytes(&self.lagrange_kernel_trace_states); + // write constraint evaluations row target.write_u16(self.evaluations.len() as u16); target.write_bytes(&self.evaluations) @@ -149,13 +194,68 @@ impl Deserializable for OodFrame { let num_trace_state_bytes = source.read_u16()? as usize; let trace_states = source.read_vec(num_trace_state_bytes)?; + // read Lagrange kernel column trace rows + let num_lagrange_state_bytes = source.read_u16()? as usize; + let lagrange_kernel_trace_states = source.read_vec(num_lagrange_state_bytes)?; + // read constraint evaluations row let num_constraint_evaluation_bytes = source.read_u16()? as usize; let evaluations = source.read_vec(num_constraint_evaluation_bytes)?; Ok(OodFrame { trace_states, + lagrange_kernel_trace_states, evaluations, }) } } + +// OOD FRAME TRACE STATES +// ================================================================================================ + +/// Stores the trace evaluations at `z` and `gz`, where `z` is a random Field element. If +/// the Air contains a Lagrange kernel auxiliary column, then that column interpolated polynomial +/// will be evaluated at `z`, `gz`, `g^2 z`, ... `g^(2^(v-1)) z`, where `v == log(trace_len)`, and +/// stored in `lagrange_kernel_frame`. +pub struct OodFrameTraceStates { + current_row: Vec, + next_row: Vec, + lagrange_kernel_frame: Option>, +} + +impl OodFrameTraceStates { + /// Creates a new [`OodFrameTraceStates`] from current, next and optionally Lagrange kernel frames. + pub fn new( + current_frame: Vec, + next_frame: Vec, + lagrange_kernel_frame: Option>, + ) -> Self { + assert_eq!(current_frame.len(), next_frame.len()); + + Self { + current_row: current_frame, + next_row: next_frame, + lagrange_kernel_frame, + } + } + + /// Returns the number of columns for the current and next frames. + pub fn num_columns(&self) -> usize { + self.current_row.len() + } + + /// Returns the current frame. + pub fn current_frame(&self) -> &[E] { + &self.current_row + } + + /// Returns the next frame. + pub fn next_frame(&self) -> &[E] { + &self.next_row + } + + /// Returns the Lagrange kernel frame, if any. + pub fn lagrange_kernel_frame(&self) -> Option<&LagrangeKernelEvaluationFrame> { + self.lagrange_kernel_frame.as_ref() + } +} diff --git a/examples/src/rescue_raps/air.rs b/examples/src/rescue_raps/air.rs index 012fb2f20..5a392c92c 100644 --- a/examples/src/rescue_raps/air.rs +++ b/examples/src/rescue_raps/air.rs @@ -77,6 +77,7 @@ impl Air for RescueRapsAir { aux_degrees, 8, 2, + None, options, ), result: pub_inputs.result, diff --git a/examples/src/rescue_raps/custom_trace_table.rs b/examples/src/rescue_raps/custom_trace_table.rs index 7550c0e72..1bf8fcf14 100644 --- a/examples/src/rescue_raps/custom_trace_table.rs +++ b/examples/src/rescue_raps/custom_trace_table.rs @@ -175,6 +175,7 @@ impl Trace for RapTraceTable { &mut self, aux_segments: &[ColMatrix], rand_elements: &[E], + _lagrange_rand_elements: Option<&[E]>, ) -> Option> where E: FieldElement, diff --git a/math/src/lib.rs b/math/src/lib.rs index ae565ff1e..4a814a421 100644 --- a/math/src/lib.rs +++ b/math/src/lib.rs @@ -112,5 +112,5 @@ pub mod fields { mod utils; pub use crate::utils::{ - add_in_place, batch_inversion, get_power_series, get_power_series_with_offset, log2, mul_acc, + add_in_place, batch_inversion, get_power_series, get_power_series_with_offset, mul_acc, }; diff --git a/math/src/utils/mod.rs b/math/src/utils/mod.rs index 9c4419144..9add32096 100644 --- a/math/src/utils/mod.rs +++ b/math/src/utils/mod.rs @@ -183,24 +183,6 @@ where result } -/// Returns base 2 logarithm of `n`, where `n` is a power of two. -/// -/// # Panics -/// Panics if `n` is not a power of two. -/// -/// # Examples -/// ``` -/// # use winter_math::log2; -/// assert_eq!(log2(1), 0); -/// assert_eq!(log2(16), 4); -/// assert_eq!(log2(1 << 20), 20); -/// assert_eq!(log2(2usize.pow(20)), 20); -/// ``` -pub fn log2(n: usize) -> u32 { - assert!(n.is_power_of_two(), "n must be a power of two"); - n.trailing_zeros() -} - // HELPER FUNCTIONS // ------------------------------------------------------------------------------------------------ diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 62d1c52db..30d8143ea 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -19,6 +19,10 @@ bench = false name = "row_matrix" harness = false +[[bench]] +name = "lagrange_kernel" +harness = false + [features] concurrent = ["crypto/concurrent", "math/concurrent", "fri/concurrent", "utils/concurrent", "std"] default = ["std"] diff --git a/prover/benches/lagrange_kernel.rs b/prover/benches/lagrange_kernel.rs new file mode 100644 index 000000000..2da94d6dd --- /dev/null +++ b/prover/benches/lagrange_kernel.rs @@ -0,0 +1,274 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +use std::time::Duration; + +use air::{ + Air, AirContext, Assertion, AuxTraceRandElements, ConstraintCompositionCoefficients, + EvaluationFrame, FieldExtension, ProofOptions, TraceInfo, TransitionConstraintDegree, +}; +use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; +use crypto::{hashers::Blake3_256, DefaultRandomCoin}; +use math::{fields::f64::BaseElement, ExtensionOf, FieldElement}; +use winter_prover::{ + matrix::ColMatrix, DefaultConstraintEvaluator, DefaultTraceLde, Prover, StarkDomain, Trace, + TracePolyTable, +}; + +const TRACE_LENS: [usize; 2] = [2_usize.pow(16), 2_usize.pow(20)]; + +fn prove_with_lagrange_kernel(c: &mut Criterion) { + let mut group = c.benchmark_group("Lagrange kernel column"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(20)); + + for &trace_len in TRACE_LENS.iter() { + group.bench_function(BenchmarkId::new("prover", trace_len), |b| { + let trace = LagrangeTrace::new(trace_len, 2); + let prover = LagrangeProver::new(); + b.iter_batched( + || trace.clone(), + |trace| prover.prove(trace).unwrap(), + BatchSize::SmallInput, + ) + }); + } +} + +criterion_group!(lagrange_kernel_group, prove_with_lagrange_kernel); +criterion_main!(lagrange_kernel_group); + +// TRACE +// ================================================================================================= + +#[derive(Clone, Debug)] +struct LagrangeTrace { + // dummy main trace + main_trace: ColMatrix, + info: TraceInfo, +} + +impl LagrangeTrace { + fn new(trace_len: usize, aux_segment_width: usize) -> Self { + assert!(trace_len < u32::MAX.try_into().unwrap()); + + let main_trace_col: Vec = + (0..trace_len).map(|idx| BaseElement::from(idx as u32)).collect(); + + let num_aux_segment_rands = trace_len.ilog2() as usize; + + Self { + main_trace: ColMatrix::new(vec![main_trace_col]), + info: TraceInfo::new_multi_segment( + 1, + [aux_segment_width], + [num_aux_segment_rands], + trace_len, + vec![], + ), + } + } + + fn len(&self) -> usize { + self.main_trace.num_rows() + } +} + +impl Trace for LagrangeTrace { + type BaseField = BaseElement; + + fn info(&self) -> &TraceInfo { + &self.info + } + + fn main_segment(&self) -> &ColMatrix { + &self.main_trace + } + + /// Each non-Lagrange kernel segment will simply take the sum the random elements, and multiply + /// by the main column + fn build_aux_segment>( + &mut self, + aux_segments: &[ColMatrix], + rand_elements: &[E], + lagrange_kernel_rand_elements: Option<&[E]>, + ) -> Option> { + assert!(aux_segments.is_empty()); + + let mut columns = Vec::new(); + + // first build the Lagrange kernel column + { + let r = lagrange_kernel_rand_elements.unwrap(); + + let mut lagrange_col = Vec::with_capacity(self.len()); + + for row_idx in 0..self.len() { + let mut row_value = E::ONE; + for (bit_idx, &r_i) in r.iter().enumerate() { + if row_idx & (1 << bit_idx) == 0 { + row_value *= E::ONE - r_i; + } else { + row_value *= r_i; + } + } + lagrange_col.push(row_value); + } + + columns.push(lagrange_col); + } + + // Then all other auxiliary columns + let rand_summed = rand_elements.iter().fold(E::ZERO, |acc, &r| acc + r); + for _ in 1..self.aux_trace_width() { + // building a dummy auxiliary column + let column = self + .main_segment() + .get_column(0) + .iter() + .map(|row_val| rand_summed.mul_base(*row_val)) + .collect(); + + columns.push(column); + } + + Some(ColMatrix::new(columns)) + } + + fn read_main_frame(&self, row_idx: usize, frame: &mut air::EvaluationFrame) { + let next_row_idx = row_idx + 1; + assert_ne!(next_row_idx, self.len()); + + self.main_trace.read_row_into(row_idx, frame.current_mut()); + self.main_trace.read_row_into(next_row_idx, frame.next_mut()); + } +} + +// AIR +// ================================================================================================= + +struct LagrangeKernelAir { + context: AirContext, +} + +impl Air for LagrangeKernelAir { + type BaseField = BaseElement; + + type PublicInputs = (); + + fn new(trace_info: TraceInfo, _pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self { + Self { + context: AirContext::new_multi_segment( + trace_info, + vec![TransitionConstraintDegree::new(1)], + vec![TransitionConstraintDegree::new(1)], + 1, + 1, + Some(0), + options, + ), + } + } + + fn context(&self) -> &AirContext { + &self.context + } + + fn evaluate_transition>( + &self, + frame: &EvaluationFrame, + _periodic_values: &[E], + result: &mut [E], + ) { + let current = frame.current()[0]; + let next = frame.next()[0]; + + // increments by 1 + result[0] = next - current - E::ONE; + } + + fn get_assertions(&self) -> Vec> { + vec![Assertion::single(0, 0, BaseElement::ZERO)] + } + + fn evaluate_aux_transition( + &self, + _main_frame: &EvaluationFrame, + _aux_frame: &EvaluationFrame, + _periodic_values: &[F], + _aux_rand_elements: &AuxTraceRandElements, + _result: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf, + { + // do nothing + } + + fn get_aux_assertions>( + &self, + _aux_rand_elements: &AuxTraceRandElements, + ) -> Vec> { + vec![Assertion::single(1, 0, E::ZERO)] + } +} + +// LagrangeProver +// ================================================================================================ + +struct LagrangeProver { + options: ProofOptions, +} + +impl LagrangeProver { + fn new() -> Self { + Self { + options: ProofOptions::new(1, 2, 0, FieldExtension::None, 2, 1), + } + } +} + +impl Prover for LagrangeProver { + type BaseField = BaseElement; + type Air = LagrangeKernelAir; + type Trace = LagrangeTrace; + type HashFn = Blake3_256; + type RandomCoin = DefaultRandomCoin; + type TraceLde> = DefaultTraceLde; + type ConstraintEvaluator<'a, E: FieldElement> = + DefaultConstraintEvaluator<'a, LagrangeKernelAir, E>; + + fn get_pub_inputs(&self, _trace: &Self::Trace) -> <::Air as Air>::PublicInputs { + () + } + + fn options(&self) -> &ProofOptions { + &self.options + } + + fn new_trace_lde( + &self, + trace_info: &TraceInfo, + main_trace: &ColMatrix, + domain: &StarkDomain, + ) -> (Self::TraceLde, TracePolyTable) + where + E: math::FieldElement, + { + DefaultTraceLde::new(trace_info, main_trace, domain) + } + + fn new_evaluator<'a, E>( + &self, + air: &'a Self::Air, + aux_rand_elements: AuxTraceRandElements, + composition_coefficients: ConstraintCompositionCoefficients, + ) -> Self::ConstraintEvaluator<'a, E> + where + E: math::FieldElement, + { + DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients) + } +} diff --git a/prover/src/channel.rs b/prover/src/channel.rs index 89ab02883..b782c2b2e 100644 --- a/prover/src/channel.rs +++ b/prover/src/channel.rs @@ -4,7 +4,7 @@ // LICENSE file in the root directory of this source tree. use air::{ - proof::{Commitments, Context, OodFrame, Queries, StarkProof}, + proof::{Commitments, Context, OodFrame, OodFrameTraceStates, Queries, StarkProof}, Air, ConstraintCompositionCoefficients, DeepCompositionCoefficients, }; use alloc::vec::Vec; @@ -85,7 +85,7 @@ where /// Saves the evaluations of trace polynomials over the out-of-domain evaluation frame. This /// also reseeds the public coin with the hashes of the evaluation frame states. - pub fn send_ood_trace_states(&mut self, trace_states: &[Vec]) { + pub fn send_ood_trace_states(&mut self, trace_states: &OodFrameTraceStates) { let result = self.ood_frame.set_trace_states(trace_states); self.public_coin.reseed(H::hash_elements(&result)); } diff --git a/prover/src/composer/mod.rs b/prover/src/composer/mod.rs index 3b07f52fe..478236efe 100644 --- a/prover/src/composer/mod.rs +++ b/prover/src/composer/mod.rs @@ -4,7 +4,7 @@ // LICENSE file in the root directory of this source tree. use super::{constraints::CompositionPoly, StarkDomain, TracePolyTable}; -use air::DeepCompositionCoefficients; +use air::{proof::OodFrameTraceStates, DeepCompositionCoefficients}; use alloc::vec::Vec; use math::{add_in_place, fft, mul_acc, polynom, ExtensionOf, FieldElement, StarkField}; use utils::iter_mut; @@ -64,7 +64,7 @@ impl DeepCompositionPoly { pub fn add_trace_polys( &mut self, trace_polys: TracePolyTable, - ood_trace_states: Vec>, + ood_trace_states: OodFrameTraceStates, ) { assert!(self.coefficients.is_empty()); @@ -89,7 +89,7 @@ impl DeepCompositionPoly { acc_trace_poly::( &mut t1_composition, poly, - ood_trace_states[0][i], + ood_trace_states.current_frame()[i], self.cc.trace[i], ); @@ -98,7 +98,7 @@ impl DeepCompositionPoly { acc_trace_poly::( &mut t2_composition, poly, - ood_trace_states[1][i], + ood_trace_states.next_frame()[i], self.cc.trace[i], ); @@ -112,7 +112,7 @@ impl DeepCompositionPoly { acc_trace_poly::( &mut t1_composition, poly, - ood_trace_states[0][i], + ood_trace_states.current_frame()[i], self.cc.trace[i], ); @@ -121,7 +121,7 @@ impl DeepCompositionPoly { acc_trace_poly::( &mut t2_composition, poly, - ood_trace_states[1][i], + ood_trace_states.next_frame()[i], self.cc.trace[i], ); diff --git a/prover/src/constraints/evaluation_table.rs b/prover/src/constraints/evaluation_table.rs index c29051776..b57897f4a 100644 --- a/prover/src/constraints/evaluation_table.rs +++ b/prover/src/constraints/evaluation_table.rs @@ -3,7 +3,7 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use super::{CompositionPolyTrace, ConstraintDivisor, StarkDomain}; +use super::{ConstraintDivisor, StarkDomain}; use alloc::vec::Vec; use math::{batch_inversion, FieldElement, StarkField}; use utils::{batch_iter_mut, iter_mut, uninit_vector}; @@ -161,7 +161,7 @@ impl<'a, E: FieldElement> ConstraintEvaluationTable<'a, E> { // -------------------------------------------------------------------------------------------- /// Divides constraint evaluation columns by their respective divisor (in evaluation form) and /// combines the results into a single column. - pub fn combine(self) -> CompositionPolyTrace { + pub fn combine(self) -> Vec { // allocate memory for the combined polynomial let mut combined_poly = E::zeroed_vector(self.num_rows()); @@ -173,7 +173,7 @@ impl<'a, E: FieldElement> ConstraintEvaluationTable<'a, E> { acc_column(column, divisor, self.domain, &mut combined_poly); } - CompositionPolyTrace::new(combined_poly) + combined_poly } // DEBUG HELPERS diff --git a/prover/src/constraints/evaluator/default.rs b/prover/src/constraints/evaluator/default.rs index ff0ec0d5f..6d4607d00 100644 --- a/prover/src/constraints/evaluator/default.rs +++ b/prover/src/constraints/evaluator/default.rs @@ -4,8 +4,9 @@ // LICENSE file in the root directory of this source tree. use super::{ - super::EvaluationTableFragment, BoundaryConstraints, CompositionPolyTrace, - ConstraintEvaluationTable, ConstraintEvaluator, PeriodicValueTable, StarkDomain, TraceLde, + super::EvaluationTableFragment, lagrange::LagrangeKernelConstraintsBatchEvaluator, + BoundaryConstraints, CompositionPolyTrace, ConstraintEvaluationTable, ConstraintEvaluator, + PeriodicValueTable, StarkDomain, TraceLde, }; use air::{ Air, AuxTraceRandElements, ConstraintCompositionCoefficients, EvaluationFrame, @@ -39,6 +40,7 @@ pub struct DefaultConstraintEvaluator<'a, A: Air, E: FieldElement, transition_constraints: TransitionConstraints, + lagrange_constraints_evaluator: Option>, aux_rand_elements: AuxTraceRandElements, periodic_values: PeriodicValueTable, } @@ -107,8 +109,16 @@ where #[cfg(debug_assertions)] evaluation_table.validate_transition_degrees(); - // combine all evaluations into a single column and return - evaluation_table.combine() + // combine all constraint evaluations into a single column, including the evaluations of the + // Lagrange kernel constraints (if present) + let combined_evaluations = { + let mut constraints_evaluations = evaluation_table.combine(); + self.evaluate_lagrange_kernel_constraints(trace, domain, &mut constraints_evaluations); + + constraints_evaluations + }; + + CompositionPolyTrace::new(combined_evaluations) } } @@ -130,7 +140,6 @@ where // evaluations let transition_constraints = air.get_transition_constraints(&composition_coefficients.transition); - // build periodic value table let periodic_values = PeriodicValueTable::new(air); @@ -139,10 +148,25 @@ where let boundary_constraints = BoundaryConstraints::new(air, &aux_rand_elements, &composition_coefficients.boundary); + let lagrange_constraints_evaluator = if air.context().has_lagrange_kernel_aux_column() { + let num_rand_elements = air.context().trace_len().ilog2() as usize; + + Some(LagrangeKernelConstraintsBatchEvaluator::new( + air, + aux_rand_elements.get_segment_elements(0)[0..num_rand_elements].to_vec(), + composition_coefficients + .lagrange + .expect("expected Lagrange kernel composition coefficients to be present"), + )) + } else { + None + }; + DefaultConstraintEvaluator { air, boundary_constraints, transition_constraints, + lagrange_constraints_evaluator, aux_rand_elements, periodic_values, } @@ -233,6 +257,7 @@ where // evaluations buffer; we evaluate and compose constraints in the same function, we // can just add up the results of evaluating main and auxiliary constraints. evaluations[0] = self.evaluate_main_transition(&main_frame, step, &mut tm_evaluations); + evaluations[0] += self.evaluate_aux_transition(&main_frame, &aux_frame, step, &mut ta_evaluations); @@ -257,6 +282,29 @@ where } } + /// If present, evaluates the Lagrange kernel constraints over the constraint evaluation domain. + /// The evaluation of each constraint (both boundary and transition) is divided by its divisor, + /// multiplied by its composition coefficient, the result of which is added to + /// `combined_evaluations_accumulator`. + /// + /// Specifically, `combined_evaluations_accumulator` is a buffer whose length is the size of the + /// constraint evaluation domain, where each index contains combined evaluations of other + /// constraints in the system. + fn evaluate_lagrange_kernel_constraints>( + &self, + trace: &T, + domain: &StarkDomain, + combined_evaluations_accumulator: &mut [E], + ) { + if let Some(ref lagrange_constraints_evaluator) = self.lagrange_constraints_evaluator { + lagrange_constraints_evaluator.evaluate_constraints( + trace, + domain, + combined_evaluations_accumulator, + ) + } + } + // TRANSITION CONSTRAINT EVALUATORS // -------------------------------------------------------------------------------------------- diff --git a/prover/src/constraints/evaluator/lagrange.rs b/prover/src/constraints/evaluator/lagrange.rs new file mode 100644 index 000000000..8cab64801 --- /dev/null +++ b/prover/src/constraints/evaluator/lagrange.rs @@ -0,0 +1,289 @@ +use air::{ + Air, LagrangeConstraintsCompositionCoefficients, LagrangeKernelConstraints, + LagrangeKernelEvaluationFrame, +}; +use alloc::vec::Vec; +use math::{batch_inversion, FieldElement}; + +use crate::{StarkDomain, TraceLde}; + +/// Contains a specific strategy for evaluating the Lagrange kernel boundary and transition +/// constraints where the divisors' evaluation is batched. +/// +/// Specifically, [`batch_inversion`] is used to reduce the number of divisions performed. +pub struct LagrangeKernelConstraintsBatchEvaluator { + lagrange_kernel_constraints: LagrangeKernelConstraints, + rand_elements: Vec, +} + +impl LagrangeKernelConstraintsBatchEvaluator { + /// Constructs a new [`LagrangeConstraintsBatchEvaluator`]. + pub fn new( + air: &A, + lagrange_kernel_rand_elements: Vec, + lagrange_composition_coefficients: LagrangeConstraintsCompositionCoefficients, + ) -> Self + where + E: FieldElement, + { + Self { + lagrange_kernel_constraints: air + .get_lagrange_kernel_constraints( + lagrange_composition_coefficients, + &lagrange_kernel_rand_elements, + ) + .expect("expected Lagrange kernel constraints to be present"), + rand_elements: lagrange_kernel_rand_elements, + } + } + + /// Evaluates the transition and boundary constraints. Specifically, the constraint evaluations + /// are divided by their corresponding divisors, and the resulting terms are linearly combined + /// using the composition coefficients. + /// + /// Writes the evaluations in `combined_evaluations_acc` at the corresponding (constraint + /// evaluation) domain index. + pub fn evaluate_constraints( + &self, + trace: &T, + domain: &StarkDomain, + combined_evaluations_acc: &mut [E], + ) where + T: TraceLde, + { + let lde_shift = domain.ce_to_lde_blowup().trailing_zeros(); + let trans_constraints_divisors = LagrangeKernelTransitionConstraintsDivisor::new( + self.lagrange_kernel_constraints.transition.num_constraints(), + domain, + ); + let boundary_divisors_inv = self.compute_boundary_divisors_inv(domain); + + let mut frame = LagrangeKernelEvaluationFrame::new_empty(); + + for step in 0..domain.ce_domain_size() { + // compute Lagrange kernel frame + trace.read_lagrange_kernel_frame_into( + step << lde_shift, + self.lagrange_kernel_constraints.lagrange_kernel_col_idx, + &mut frame, + ); + + // Compute the combined transition and boundary constraints evaluations for this row + let combined_evaluations = { + let mut combined_evaluations = E::ZERO; + + // combine transition constraints + for trans_constraint_idx in + 0..self.lagrange_kernel_constraints.transition.num_constraints() + { + let numerator = self + .lagrange_kernel_constraints + .transition + .evaluate_ith_numerator(&frame, &self.rand_elements, trans_constraint_idx); + let inv_divisor = trans_constraints_divisors + .get_inverse_divisor_eval(trans_constraint_idx, step); + + combined_evaluations += numerator * inv_divisor; + } + + // combine boundary constraints + { + let boundary_numerator = + self.lagrange_kernel_constraints.boundary.evaluate_numerator_at(&frame); + + combined_evaluations += boundary_numerator * boundary_divisors_inv[step]; + } + + combined_evaluations + }; + + combined_evaluations_acc[step] += combined_evaluations; + } + } + + // HELPERS + // --------------------------------------------------------------------------------------------- + + /// Computes the inverse boundary divisor at every point of the constraint evaluation domain. + /// That is, returns a vector of the form `[1 / div_0, ..., 1 / div_n]`, where `div_i` is the + /// divisor for the Lagrange kernel boundary constraint at the i'th row of the constraint + /// evaluation domain. + fn compute_boundary_divisors_inv(&self, domain: &StarkDomain) -> Vec { + let mut boundary_denominator_evals = Vec::with_capacity(domain.ce_domain_size()); + for step in 0..domain.ce_domain_size() { + let domain_point = domain.get_ce_x_at(step); + let boundary_denominator = self + .lagrange_kernel_constraints + .boundary + .evaluate_denominator_at(domain_point.into()); + boundary_denominator_evals.push(boundary_denominator); + } + + batch_inversion(&boundary_denominator_evals) + } +} + +/// Holds all the transition constraint inverse divisor evaluations over the constraint evaluation +/// domain. +/// +/// [`LagrangeKernelTransitionConstraintsDivisor`] takes advantage of some structure in the +/// divisors' evaluations. Recall that the divisor for the i'th transition constraint is `x^(2^i) - +/// 1`. When substituting `x` for each value of the constraint evaluation domain, for constraints +/// `i>0`, the divisor evaluations "wrap-around" such that some values repeat. For example, +/// +/// i=0: no repetitions +/// i=1: the first half of the buffer is equal to the second half +/// i=2: each 1/4th of the buffer are equal +/// i=3: each 1/8th of the buffer are equal +/// ... +/// Therefore, we only compute the non-repeating section of the buffer in each iteration, and index +/// into it accordingly. +struct LagrangeKernelTransitionConstraintsDivisor { + divisor_evals_inv: Vec, + + // Precompute the indices into `divisors_evals_inv` of the slices that correspond to each + // transition constraint. + // + // For example, for a CE domain size `n=8`, `slice_indices_precomputes = [0, 8, 12, 14]`, such + // that transition constraint `idx` owns the range: + // idx=0: [0, 8) + // idx=1: [8, 12) + // idx=2: [12, 14) + slice_indices_precomputes: Vec, +} + +impl LagrangeKernelTransitionConstraintsDivisor { + pub fn new( + num_lagrange_transition_constraints: usize, + domain: &StarkDomain, + ) -> Self { + let divisor_evals_inv = { + let divisor_evaluator = TransitionDivisorEvaluator::::new( + num_lagrange_transition_constraints, + domain.offset(), + ); + + // The number of divisor evaluations is + // `ce_domain_size + ce_domain_size/2 + ce_domain_size/4 + ... + ce_domain_size/(log(ce_domain_size)-1)`, + // which is slightly smaller than `ce_domain_size * 2` + let mut divisor_evals: Vec = Vec::with_capacity(domain.ce_domain_size() * 2); + + for trans_constraint_idx in 0..num_lagrange_transition_constraints { + let num_non_repeating_denoms = + domain.ce_domain_size() / 2_usize.pow(trans_constraint_idx as u32); + + for step in 0..num_non_repeating_denoms { + let divisor_eval = + divisor_evaluator.evaluate_ith_divisor(trans_constraint_idx, domain, step); + + divisor_evals.push(divisor_eval.into()); + } + } + + batch_inversion(&divisor_evals) + }; + + let slice_indices_precomputes = { + let num_indices = num_lagrange_transition_constraints + 1; + let mut slice_indices_precomputes = Vec::with_capacity(num_indices); + + slice_indices_precomputes.push(0); + + let mut current_slice_len = domain.ce_domain_size(); + for i in 1..num_indices { + let next_precompute_index = slice_indices_precomputes[i - 1] + current_slice_len; + slice_indices_precomputes.push(next_precompute_index); + + current_slice_len /= 2; + } + + slice_indices_precomputes + }; + + Self { + divisor_evals_inv, + slice_indices_precomputes, + } + } + + /// Returns the evaluation `1 / divisor`, where `divisor` is the divisor for the given + /// transition constraint, at the given row of the constraint evaluation domain + pub fn get_inverse_divisor_eval(&self, trans_constraint_idx: usize, row_idx: usize) -> E { + let inv_divisors_slice_for_constraint = + self.get_transition_constraint_slice(trans_constraint_idx); + + inv_divisors_slice_for_constraint[row_idx % inv_divisors_slice_for_constraint.len()] + } + + // HELPERS + // --------------------------------------------------------------------------------------------- + + /// Returns a slice containing all the inverse divisor evaluations for the given transition + /// constraint. + fn get_transition_constraint_slice(&self, trans_constraint_idx: usize) -> &[E] { + let start = self.slice_indices_precomputes[trans_constraint_idx]; + let end = self.slice_indices_precomputes[trans_constraint_idx + 1]; + + &self.divisor_evals_inv[start..end] + } +} + +/// Encapsulates the efficient evaluation of the Lagrange kernel transition constraints divisors. +/// +/// `s` stands for the domain offset (i.e. coset shift element). The key concept in this +/// optimization is to realize that the computation of the first transition constraint divisor can +/// be reused for all the other divisors (call the evaluations `d`). +/// +/// Specifically, each subsequent transition constraint divisor evaluation is equivalent to +/// multiplying an element `d` by a fixed number. For example, the multiplier for the transition +/// constraints are: +/// +/// - transition constraint 1's multiplier: s +/// - transition constraint 2's multiplier: s^3 +/// - transition constraint 3's multiplier: s^7 +/// - transition constraint 4's multiplier: s^15 +/// - ... +/// +/// This is what `s_precomputes` stores. +/// +/// Finally, recall that the ith Lagrange kernel divisor is `x^(2^i) - 1`. +/// [`TransitionDivisorEvaluator`] is only concerned with values of `x` in the constraint evaluation +/// domain, where the j'th element is `s * g^j`, where `g` is the group generator. To understand the +/// implementation of [`Self::evaluate_ith_divisor`], plug in `x = s * g^j` into `x^(2^i) - 1`. +pub struct TransitionDivisorEvaluator { + s_precomputes: Vec, +} + +impl TransitionDivisorEvaluator { + /// Constructs a new [`TransitionDivisorEvaluator`] + pub fn new(num_lagrange_transition_constraints: usize, domain_offset: E::BaseField) -> Self { + let s_precomputes = { + // s_precomputes = [1, s, s^3, s^7, s^15, ...] (where s = domain_offset) + let mut s_precomputes = Vec::with_capacity(num_lagrange_transition_constraints); + + let mut s_exp = E::BaseField::ONE; + for _ in 0..num_lagrange_transition_constraints { + s_precomputes.push(s_exp); + s_exp = s_exp * s_exp * domain_offset; + } + + s_precomputes + }; + + Self { s_precomputes } + } + + /// Evaluates the divisor of the `trans_constraint_idx`'th transition constraint. See + /// [`TransitionDivisorEvaluator`] for a more in-depth description of the algorithm. + pub fn evaluate_ith_divisor( + &self, + trans_constraint_idx: usize, + domain: &StarkDomain, + ce_domain_step: usize, + ) -> E::BaseField { + let domain_idx = ((1 << trans_constraint_idx) * ce_domain_step) % domain.ce_domain_size(); + + self.s_precomputes[trans_constraint_idx] * domain.get_ce_x_at(domain_idx) + - E::BaseField::ONE + } +} diff --git a/prover/src/constraints/evaluator/mod.rs b/prover/src/constraints/evaluator/mod.rs index e91e1c9c6..4728a8084 100644 --- a/prover/src/constraints/evaluator/mod.rs +++ b/prover/src/constraints/evaluator/mod.rs @@ -13,6 +13,8 @@ pub use default::DefaultConstraintEvaluator; mod boundary; use boundary::BoundaryConstraints; +mod lagrange; + mod periodic_table; use periodic_table::PeriodicValueTable; diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 8cd5061f0..69073145c 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -289,10 +289,15 @@ pub trait Prover { // draw a set of random elements required to build an auxiliary trace segment let rand_elements = channel.get_aux_trace_segment_rand_elements(i); + let lagrange_rand_elements = if air.context().has_lagrange_kernel_aux_column() { + Some(rand_elements.as_ref()) + } else { + None + }; // build the trace segment let aux_segment = trace - .build_aux_segment(&aux_trace_segments, &rand_elements) + .build_aux_segment(&aux_trace_segments, &rand_elements, lagrange_rand_elements) .expect("failed build auxiliary trace segment"); drop(span); @@ -317,7 +322,8 @@ pub trait Prover { aux_segment_polys }; - trace_polys.add_aux_segment(aux_segment_polys); + trace_polys + .add_aux_segment(aux_segment_polys, air.context().lagrange_kernel_aux_column_idx()); aux_trace_rand_elements.add_segment_elements(rand_elements); aux_trace_segments.push(aux_segment); } @@ -381,8 +387,10 @@ pub trait Prover { let z = channel.get_ood_point(); // evaluate trace and constraint polynomials at the OOD point z, and send the results to - // the verifier. the trace polynomials are actually evaluated over two points: z and - // z * g, where g is the generator of the trace domain. + // the verifier. the trace polynomials are actually evaluated over two points: z and z * + // g, where g is the generator of the trace domain. Additionally, if the Lagrange kernel + // auxiliary column is present, we also evaluate that column over the points: z, z * g, + // z * g^2, z * g^4, ..., z * g^(2^(v-1)), where v = log(trace_len). let ood_trace_states = trace_polys.get_ood_frame(z); channel.send_ood_trace_states(&ood_trace_states); diff --git a/prover/src/trace/mod.rs b/prover/src/trace/mod.rs index 8c0ab411c..837025cd6 100644 --- a/prover/src/trace/mod.rs +++ b/prover/src/trace/mod.rs @@ -4,7 +4,9 @@ // LICENSE file in the root directory of this source tree. use super::{matrix::MultiColumnIter, ColMatrix}; -use air::{Air, AuxTraceRandElements, EvaluationFrame, TraceInfo}; +use air::{ + Air, AuxTraceRandElements, EvaluationFrame, LagrangeKernelBoundaryConstraint, TraceInfo, +}; use math::{polynom, FieldElement, StarkField}; mod trace_lde; @@ -49,17 +51,21 @@ pub trait Trace: Sized { /// Returns a reference to a [Matrix] describing the main segment of this trace. fn main_segment(&self) -> &ColMatrix; - /// Builds and returns the next auxiliary trace segment. If there are no more segments to - /// build (i.e., the trace is complete), None is returned. + /// Builds and returns the next auxiliary trace segment. If there are no more segments to build + /// (i.e., the trace is complete), None is returned. /// - /// The `aux_segments` slice contains a list of auxiliary trace segments built as a result - /// of prior invocations of this function. Thus, for example, on the first invocation, - /// `aux_segments` will be empty; on the second invocation, it will contain a single matrix - /// (the one built during the first invocation) etc. + /// The `aux_segments` slice contains a list of auxiliary trace segments built as a result of + /// prior invocations of this function. Thus, for example, on the first invocation, + /// `aux_segments` will be empty; on the second invocation, it will contain a single matrix (the + /// one built during the first invocation) etc. + /// + /// The `rand_elements` slice contains the random elements to use to build the aux segment. If a + /// Lagrange kernel column is present, the `lagrange_kernel_rand_elements` should be used. fn build_aux_segment>( &mut self, aux_segments: &[ColMatrix], rand_elements: &[E], + lagrange_kernel_rand_elements: Option<&[E]>, ) -> Option>; /// Reads an evaluation frame from the main trace segment at the specified row. @@ -146,6 +152,19 @@ pub trait Trace: Sized { }); } + // then, check the Lagrange kernel assertion, if any + if let Some(lagrange_kernel_col_idx) = air.context().lagrange_kernel_aux_column_idx() { + let boundary_constraint_assertion_value = + LagrangeKernelBoundaryConstraint::assertion_value( + aux_rand_elements.get_segment_elements(0), + ); + + assert_eq!( + boundary_constraint_assertion_value, + aux_segments[0].get(lagrange_kernel_col_idx, 0) + ); + } + // --- 2. make sure this trace satisfies all transition constraints ----------------------- // collect the info needed to build periodic values for a specific step @@ -208,6 +227,35 @@ pub trait Trace: Sized { // update x coordinate of the domain x *= g; } + + // evaluate transition constraints for Lagrange kernel column (if any) and make sure + // they all evaluate to zeros + if let Some(col_idx) = air.context().lagrange_kernel_aux_column_idx() { + let c = aux_segments[0].get_column(col_idx); + let v = self.length().ilog2() as usize; + let r = aux_rand_elements.get_segment_elements(0); + + // Loop over every constraint + for constraint_idx in 1..v + 1 { + let domain_step = 2_usize.pow((v - constraint_idx + 1) as u32); + let domain_half_step = 2_usize.pow((v - constraint_idx) as u32); + + // Every transition constraint has a different enforcement domain (i.e. the rows to which it applies). + let enforcement_dom_len = self.length() / domain_step; + for dom_idx in 0..enforcement_dom_len { + let x_current = dom_idx * domain_step; + let x_next = x_current + domain_half_step; + + let evaluation = (r[v - constraint_idx] * c[x_current]) + - ((E::ONE - r[v - constraint_idx]) * c[x_next]); + + assert!( + evaluation == E::ZERO, + "Lagrange transition constraint {constraint_idx} did not evaluate to ZERO at step {x_current}" + ); + } + } + } } } diff --git a/prover/src/trace/poly_table.rs b/prover/src/trace/poly_table.rs index e6748ea2b..993a21462 100644 --- a/prover/src/trace/poly_table.rs +++ b/prover/src/trace/poly_table.rs @@ -7,6 +7,7 @@ use crate::{ matrix::{ColumnIter, MultiColumnIter}, ColMatrix, }; +use air::{proof::OodFrameTraceStates, LagrangeKernelEvaluationFrame}; use alloc::vec::Vec; use math::{FieldElement, StarkField}; @@ -21,6 +22,7 @@ use math::{FieldElement, StarkField}; pub struct TracePolyTable { main_segment_polys: ColMatrix, aux_segment_polys: Vec>, + lagrange_kernel_column_idx: Option, } impl TracePolyTable { @@ -31,6 +33,7 @@ impl TracePolyTable { Self { main_segment_polys: main_trace_polys, aux_segment_polys: Vec::new(), + lagrange_kernel_column_idx: None, } } @@ -38,13 +41,18 @@ impl TracePolyTable { // -------------------------------------------------------------------------------------------- /// Adds the provided auxiliary segment polynomials to this polynomial table. - pub fn add_aux_segment(&mut self, aux_segment_polys: ColMatrix) { + pub fn add_aux_segment( + &mut self, + aux_segment_polys: ColMatrix, + lagrange_kernel_column_idx: Option, + ) { assert_eq!( self.main_segment_polys.num_rows(), aux_segment_polys.num_rows(), "polynomials in auxiliary segment must be of the same size as in the main segment" ); self.aux_segment_polys.push(aux_segment_polys); + self.lagrange_kernel_column_idx = lagrange_kernel_column_idx; } // PUBLIC ACCESSORS @@ -64,11 +72,27 @@ impl TracePolyTable { result } - /// Returns an out-of-domain evaluation frame constructed by evaluating trace polynomials - /// for all columns at points z and z * g, where g is the generator of the trace domain. - pub fn get_ood_frame(&self, z: E) -> Vec> { - let g = E::from(E::BaseField::get_root_of_unity(self.poly_size().ilog2())); - vec![self.evaluate_at(z), self.evaluate_at(z * g)] + /// Returns an out-of-domain evaluation frame constructed by evaluating trace polynomials for + /// all columns at points z and z * g, where g is the generator of the trace domain. + /// Additionally, if the Lagrange kernel auxiliary column is present, we also evaluate that + /// column over the points: z, z * g, z * g^2, z * g^4, ..., z * g^(2^(v-1)), where v = + /// log(trace_len). + pub fn get_ood_frame(&self, z: E) -> OodFrameTraceStates { + let log_trace_len = self.poly_size().ilog2(); + let g = E::from(E::BaseField::get_root_of_unity(log_trace_len)); + let current_frame = self.evaluate_at(z); + let next_frame = self.evaluate_at(z * g); + + let lagrange_kernel_frame = self.lagrange_kernel_column_idx.map(|col_idx| { + let lagrange_kernel_col_poly = self.aux_segment_polys[0].get_column(col_idx); + + LagrangeKernelEvaluationFrame::from_lagrange_kernel_column_poly( + lagrange_kernel_col_poly, + z, + ) + }); + + OodFrameTraceStates::new(current_frame, next_frame, lagrange_kernel_frame) } /// Returns an iterator over the polynomials of the main trace segment. diff --git a/prover/src/trace/trace_lde/default/mod.rs b/prover/src/trace/trace_lde/default/mod.rs index b77ea83f2..8d7fa9c92 100644 --- a/prover/src/trace/trace_lde/default/mod.rs +++ b/prover/src/trace/trace_lde/default/mod.rs @@ -8,6 +8,7 @@ use super::{ TraceInfo, TraceLde, TracePolyTable, }; use crate::{RowMatrix, DEFAULT_SEGMENT_WIDTH}; +use air::LagrangeKernelEvaluationFrame; use alloc::vec::Vec; use crypto::MerkleTree; use tracing::info_span; @@ -174,6 +175,28 @@ where frame.next_mut().copy_from_slice(segment.row(next_lde_step)); } + fn read_lagrange_kernel_frame_into( + &self, + lde_step: usize, + lagrange_kernel_aux_column_idx: usize, + frame: &mut LagrangeKernelEvaluationFrame, + ) { + let frame = frame.frame_mut(); + frame.truncate(0); + + let aux_segment = &self.aux_segment_ldes[0]; + + frame.push(aux_segment.get(lagrange_kernel_aux_column_idx, lde_step)); + + let frame_length = self.trace_info.length().ilog2() as usize + 1; + for i in 0..frame_length - 1 { + let shift = self.blowup() * (1 << i); + let next_lde_step = (lde_step + shift) % self.trace_len(); + + frame.push(aux_segment.get(lagrange_kernel_aux_column_idx, next_lde_step)); + } + } + /// Returns trace table rows at the specified positions along with Merkle authentication paths /// from the commitment root to these rows. fn query(&self, positions: &[usize]) -> Vec { diff --git a/prover/src/trace/trace_lde/default/tests.rs b/prover/src/trace/trace_lde/default/tests.rs index 4494b41f8..777e1ada8 100644 --- a/prover/src/trace/trace_lde/default/tests.rs +++ b/prover/src/trace/trace_lde/default/tests.rs @@ -26,7 +26,7 @@ fn extend_trace_table() { // build the trace polynomials, extended trace, and commitment using the default TraceLde impl let (trace_lde, trace_polys) = - DefaultTraceLde::::new(trace.info(), trace.main_segment(), &domain); + DefaultTraceLde::::new(&trace.info(), trace.main_segment(), &domain); // check the width and length of the extended trace assert_eq!(2, trace_lde.main_segment_width()); @@ -73,7 +73,7 @@ fn commit_trace_table() { // build the trace polynomials, extended trace, and commitment using the default TraceLde impl let (trace_lde, _) = - DefaultTraceLde::::new(trace.info(), trace.main_segment(), &domain); + DefaultTraceLde::::new(&trace.info(), trace.main_segment(), &domain); // build Merkle tree from trace rows let mut hashed_states = Vec::new(); diff --git a/prover/src/trace/trace_lde/mod.rs b/prover/src/trace/trace_lde/mod.rs index 4f66e5c31..5243c695c 100644 --- a/prover/src/trace/trace_lde/mod.rs +++ b/prover/src/trace/trace_lde/mod.rs @@ -5,7 +5,7 @@ use super::{ColMatrix, EvaluationFrame, FieldElement, TracePolyTable}; use crate::StarkDomain; -use air::{proof::Queries, TraceInfo}; +use air::{proof::Queries, LagrangeKernelEvaluationFrame, TraceInfo}; use alloc::vec::Vec; use crypto::{ElementHasher, Hasher}; @@ -56,6 +56,18 @@ pub trait TraceLde: Sync { /// Reads current and next rows from the auxiliary trace segment into the specified frame. fn read_aux_trace_frame_into(&self, lde_step: usize, frame: &mut EvaluationFrame); + /// Populates the provided Lagrange kernel frame starting at the current row (as defined by + /// `lde_step`). + /// + /// Note that unlike [`EvaluationFrame`], the Lagrange kernel frame includes only the Lagrange + /// kernel column (as opposed to all columns). + fn read_lagrange_kernel_frame_into( + &self, + lde_step: usize, + col_idx: usize, + frame: &mut LagrangeKernelEvaluationFrame, + ); + /// Returns trace table rows at the specified positions along with Merkle authentication paths /// from the commitment root to these rows. fn query(&self, positions: &[usize]) -> Vec; diff --git a/prover/src/trace/trace_table.rs b/prover/src/trace/trace_table.rs index eb9ee5a66..87aec2a19 100644 --- a/prover/src/trace/trace_table.rs +++ b/prover/src/trace/trace_table.rs @@ -303,6 +303,7 @@ impl Trace for TraceTable { &mut self, _aux_segments: &[ColMatrix], _rand_elements: &[E], + _lagrange_rand_elements: Option<&[E]>, ) -> Option> where E: FieldElement, diff --git a/verifier/src/channel.rs b/verifier/src/channel.rs index 7f76e0551..fa8c28e86 100644 --- a/verifier/src/channel.rs +++ b/verifier/src/channel.rs @@ -5,8 +5,8 @@ use crate::VerifierError; use air::{ - proof::{Queries, StarkProof, Table}, - Air, EvaluationFrame, + proof::{ParsedOodFrame, Queries, StarkProof, Table}, + Air, EvaluationFrame, LagrangeKernelEvaluationFrame, }; use alloc::{string::ToString, vec::Vec}; use crypto::{BatchMerkleProof, ElementHasher, MerkleTree}; @@ -92,11 +92,19 @@ impl> VerifierChanne .map_err(|err| VerifierError::ProofDeserializationError(err.to_string()))?; // --- parse out-of-domain evaluation frame ----------------------------------------------- - let (ood_trace_evaluations, ood_constraint_evaluations) = ood_frame + let ParsedOodFrame { + trace_evaluations: ood_trace_evaluations, + lagrange_kernel_trace_evaluations: ood_lagrange_kernel_trace_evaluations, + constraint_evaluations: ood_constraint_evaluations, + } = ood_frame .parse(main_trace_width, aux_trace_width, constraint_frame_width) .map_err(|err| VerifierError::ProofDeserializationError(err.to_string()))?; - let ood_trace_frame = - TraceOodFrame::new(ood_trace_evaluations, main_trace_width, aux_trace_width); + let ood_trace_frame = TraceOodFrame::new( + ood_trace_evaluations, + main_trace_width, + aux_trace_width, + ood_lagrange_kernel_trace_evaluations, + ); Ok(VerifierChannel { // trace queries @@ -342,22 +350,38 @@ impl> ConstraintQuer // ================================================================================================ pub struct TraceOodFrame { - values: Vec, + main_and_aux_evaluations: Vec, main_trace_width: usize, aux_trace_width: usize, + lagrange_kernel_frame: Option>, } impl TraceOodFrame { - pub fn new(values: Vec, main_trace_width: usize, aux_trace_width: usize) -> Self { + pub fn new( + main_and_aux_evaluations: Vec, + main_trace_width: usize, + aux_trace_width: usize, + lagrange_kernel_evaluations: Option>, + ) -> Self { Self { - values, + main_and_aux_evaluations, main_trace_width, aux_trace_width, + lagrange_kernel_frame: lagrange_kernel_evaluations + .map(|evals| LagrangeKernelEvaluationFrame::new(evals)), } } - pub fn values(&self) -> &[E] { - &self.values + pub fn values(&self) -> Vec { + match self.lagrange_kernel_frame { + Some(ref lagrange_kernel_frame) => self + .main_and_aux_evaluations + .clone() + .into_iter() + .chain(lagrange_kernel_frame.inner().iter().cloned()) + .collect(), + None => self.main_and_aux_evaluations.clone(), + } } // The out-of-domain frame is stored as one vector of interleaved values, one from the @@ -379,7 +403,9 @@ impl TraceOodFrame { let mut current = vec![E::ZERO; self.main_trace_width]; let mut next = vec![E::ZERO; self.main_trace_width]; - for (i, a) in self.values.chunks(2).take(self.main_trace_width).enumerate() { + for (i, a) in + self.main_and_aux_evaluations.chunks(2).take(self.main_trace_width).enumerate() + { current[i] = a[0]; next[i] = a[1]; } @@ -407,11 +433,18 @@ impl TraceOodFrame { let mut current_aux = vec![E::ZERO; self.aux_trace_width]; let mut next_aux = vec![E::ZERO; self.aux_trace_width]; - for (i, a) in self.values.chunks(2).skip(self.main_trace_width).enumerate() { + for (i, a) in + self.main_and_aux_evaluations.chunks(2).skip(self.main_trace_width).enumerate() + { current_aux[i] = a[0]; next_aux[i] = a[1]; } Some(EvaluationFrame::from_rows(current_aux, next_aux)) } } + + /// Returns the Lagrange kernel evaluation frame, if any. + pub fn lagrange_kernel_frame(&self) -> Option<&LagrangeKernelEvaluationFrame> { + self.lagrange_kernel_frame.as_ref() + } } diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index 454d5a5f5..d1b60e872 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -3,7 +3,10 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use air::{Air, AuxTraceRandElements, ConstraintCompositionCoefficients, EvaluationFrame}; +use air::{ + Air, AuxTraceRandElements, ConstraintCompositionCoefficients, EvaluationFrame, + LagrangeKernelEvaluationFrame, +}; use alloc::vec::Vec; use math::{polynom, FieldElement}; @@ -16,6 +19,7 @@ pub fn evaluate_constraints>( composition_coefficients: ConstraintCompositionCoefficients, main_trace_frame: &EvaluationFrame, aux_trace_frame: &Option>, + lagrange_kernel_frame: Option<&LagrangeKernelEvaluationFrame>, aux_rand_elements: AuxTraceRandElements, x: E, ) -> E { @@ -78,5 +82,33 @@ pub fn evaluate_constraints>( } } + // 3 ----- evaluate Lagrange kernel constraints ------------------------------------ + + if let Some(lagrange_kernel_column_frame) = lagrange_kernel_frame { + let lagrange_coefficients = composition_coefficients + .lagrange + .expect("expected Lagrange kernel composition coefficients to be present"); + let lagrange_kernel_aux_rand_elements = { + let num_rand_elements = air.context().trace_len().ilog2() as usize; + + &aux_rand_elements.get_segment_elements(0)[0..num_rand_elements] + }; + + let lagrange_constraints = air + .get_lagrange_kernel_constraints( + lagrange_coefficients, + lagrange_kernel_aux_rand_elements, + ) + .expect("expected Lagrange kernel constraints to be present"); + + result += lagrange_constraints.transition.evaluate_and_combine::( + lagrange_kernel_column_frame, + lagrange_kernel_aux_rand_elements, + x, + ); + + result += lagrange_constraints.boundary.evaluate_at(x, lagrange_kernel_column_frame); + } + result } diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index 14c54f197..63e541fb0 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -200,15 +200,17 @@ where let ood_trace_frame = channel.read_ood_trace_frame(); let ood_main_trace_frame = ood_trace_frame.main_frame(); let ood_aux_trace_frame = ood_trace_frame.aux_frame(); + let ood_lagrange_kernel_frame = ood_trace_frame.lagrange_kernel_frame(); let ood_constraint_evaluation_1 = evaluate_constraints( &air, constraint_coeffs, &ood_main_trace_frame, &ood_aux_trace_frame, + ood_lagrange_kernel_frame, aux_trace_rand_elements, z, ); - public_coin.reseed(H::hash_elements(ood_trace_frame.values())); + public_coin.reseed(H::hash_elements(&ood_trace_frame.values())); // read evaluations of composition polynomial columns sent by the prover, and reduce them into // a single value by computing \sum_{i=0}^{m-1}(z^(i * l) * value_i), where value_i is the diff --git a/winterfell/src/lib.rs b/winterfell/src/lib.rs index a09b69cb2..41fddb4e9 100644 --- a/winterfell/src/lib.rs +++ b/winterfell/src/lib.rs @@ -580,6 +580,9 @@ #![no_std] +#[cfg(test)] +extern crate std; + pub use prover::{ crypto, iterators, math, matrix, Air, AirContext, Assertion, AuxTraceRandElements, BoundaryConstraint, BoundaryConstraintGroup, ByteReader, ByteWriter, CompositionPolyTrace, @@ -590,3 +593,6 @@ pub use prover::{ TraceTable, TraceTableFragment, TransitionConstraintDegree, }; pub use verifier::{verify, AcceptableOptions, VerifierError}; + +#[cfg(test)] +mod tests; diff --git a/winterfell/src/tests.rs b/winterfell/src/tests.rs new file mode 100644 index 000000000..8b96cc972 --- /dev/null +++ b/winterfell/src/tests.rs @@ -0,0 +1,479 @@ +use super::*; +use prover::{ + crypto::{hashers::Blake3_256, DefaultRandomCoin}, + math::{fields::f64::BaseElement, ExtensionOf, FieldElement}, + matrix::ColMatrix, +}; +use std::vec; +use std::vec::Vec; + +#[test] +fn test_simple_lagrange_kernel_air() { + let trace = LagrangeSimpleTrace::new(); + let prover = LagrangeSimpleProver::new(); + + let proof = prover.prove(trace).unwrap(); + + verify::< + LagrangeKernelSimpleAir, + Blake3_256, + DefaultRandomCoin>, + >(proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) + .unwrap() +} + +// LagrangeSimpleTrace +// ================================================================================================ + +struct LagrangeSimpleTrace { + // dummy main trace + main_trace: ColMatrix, + info: TraceInfo, +} + +impl LagrangeSimpleTrace { + const TRACE_LENGTH: usize = 8; + + fn new() -> Self { + let col = vec![ + BaseElement::ZERO, + BaseElement::from(1_u32), + BaseElement::from(2_u32), + BaseElement::from(3_u32), + BaseElement::from(4_u32), + BaseElement::from(5_u32), + BaseElement::from(6_u32), + BaseElement::from(7_u32), + ]; + + Self { + main_trace: ColMatrix::new(vec![col]), + info: TraceInfo::new_multi_segment(1, [2], [3], Self::TRACE_LENGTH, vec![]), + } + } +} + +impl Trace for LagrangeSimpleTrace { + type BaseField = BaseElement; + + fn info(&self) -> &TraceInfo { + &self.info + } + + fn main_segment(&self) -> &ColMatrix { + &self.main_trace + } + + fn build_aux_segment>( + &mut self, + aux_segments: &[ColMatrix], + _rand_elements: &[E], + lagrange_rand_elements: Option<&[E]>, + ) -> Option> { + assert!(aux_segments.is_empty()); + + let lagrange_rand_elements = lagrange_rand_elements.unwrap(); + + let r0 = lagrange_rand_elements[0]; + let r1 = lagrange_rand_elements[1]; + let r2 = lagrange_rand_elements[2]; + + let lagrange_col = vec![ + (E::ONE - r2) * (E::ONE - r1) * (E::ONE - r0), + (E::ONE - r2) * (E::ONE - r1) * r0, + (E::ONE - r2) * r1 * (E::ONE - r0), + (E::ONE - r2) * r1 * r0, + r2 * (E::ONE - r1) * (E::ONE - r0), + r2 * (E::ONE - r1) * r0, + r2 * r1 * (E::ONE - r0), + r2 * r1 * r0, + ]; + + let dummy_col = vec![E::ZERO; 8]; + + Some(ColMatrix::new(vec![lagrange_col, dummy_col])) + } + + fn read_main_frame(&self, row_idx: usize, frame: &mut EvaluationFrame) { + let next_row_idx = row_idx + 1; + assert_ne!(next_row_idx, Self::TRACE_LENGTH); + + self.main_trace.read_row_into(row_idx, frame.current_mut()); + self.main_trace.read_row_into(next_row_idx, frame.next_mut()); + } +} + +// LagrangeMockAir +// ================================================================================================ + +/// An Air with one Lagrange kernel auxiliary column +struct LagrangeKernelSimpleAir { + context: AirContext, +} + +impl Air for LagrangeKernelSimpleAir { + type BaseField = BaseElement; + + type PublicInputs = (); + + fn new(trace_info: TraceInfo, _pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self { + Self { + context: AirContext::new_multi_segment( + trace_info, + vec![TransitionConstraintDegree::new(1)], + vec![TransitionConstraintDegree::new(1)], + 1, + 1, + Some(0), + options, + ), + } + } + + fn context(&self) -> &AirContext { + &self.context + } + + fn evaluate_transition>( + &self, + frame: &EvaluationFrame, + _periodic_values: &[E], + result: &mut [E], + ) { + let current = frame.current()[0]; + let next = frame.next()[0]; + + // increments by 1 + result[0] = next - current - E::ONE; + } + + fn get_assertions(&self) -> Vec> { + vec![Assertion::single(0, 0, BaseElement::ZERO)] + } + + fn evaluate_aux_transition( + &self, + _main_frame: &EvaluationFrame, + _aux_frame: &EvaluationFrame, + _periodic_values: &[F], + _aux_rand_elements: &AuxTraceRandElements, + _result: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf, + { + // do nothing + } + + fn get_aux_assertions>( + &self, + _aux_rand_elements: &AuxTraceRandElements, + ) -> Vec> { + vec![Assertion::single(1, 0, E::ZERO)] + } +} + +// LagrangeSimpleProver +// ================================================================================================ + +struct LagrangeSimpleProver { + options: ProofOptions, +} + +impl LagrangeSimpleProver { + fn new() -> Self { + Self { + options: ProofOptions::new(1, 2, 0, FieldExtension::None, 2, 1), + } + } +} + +impl Prover for LagrangeSimpleProver { + type BaseField = BaseElement; + type Air = LagrangeKernelSimpleAir; + type Trace = LagrangeSimpleTrace; + type HashFn = Blake3_256; + type RandomCoin = DefaultRandomCoin; + type TraceLde> = DefaultTraceLde; + type ConstraintEvaluator<'a, E: FieldElement> = + DefaultConstraintEvaluator<'a, LagrangeKernelSimpleAir, E>; + + fn get_pub_inputs(&self, _trace: &Self::Trace) -> <::Air as Air>::PublicInputs { + () + } + + fn options(&self) -> &ProofOptions { + &self.options + } + + fn new_trace_lde( + &self, + trace_info: &TraceInfo, + main_trace: &ColMatrix, + domain: &StarkDomain, + ) -> (Self::TraceLde, TracePolyTable) + where + E: math::FieldElement, + { + DefaultTraceLde::new(trace_info, main_trace, domain) + } + + fn new_evaluator<'a, E>( + &self, + air: &'a Self::Air, + aux_rand_elements: AuxTraceRandElements, + composition_coefficients: ConstraintCompositionCoefficients, + ) -> Self::ConstraintEvaluator<'a, E> + where + E: math::FieldElement, + { + DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients) + } +} + +#[test] +fn test_complex_lagrange_kernel_air() { + let trace = LagrangeComplexTrace::new(2_usize.pow(10), 2); + let prover = LagrangeComplexProver::new(); + let proof = prover.prove(trace).unwrap(); + + verify::< + LagrangeKernelComplexAir, + Blake3_256, + DefaultRandomCoin>, + >(proof, (), &AcceptableOptions::MinConjecturedSecurity(0)) + .unwrap() +} + +// LagrangeComplexTrace +// ================================================================================================= + +#[derive(Clone, Debug)] +struct LagrangeComplexTrace { + // dummy main trace + main_trace: ColMatrix, + info: TraceInfo, +} + +impl LagrangeComplexTrace { + fn new(trace_len: usize, aux_segment_width: usize) -> Self { + assert!(trace_len < u32::MAX.try_into().unwrap()); + + let main_trace_col: Vec = + (0..trace_len).map(|idx| BaseElement::from(idx as u32)).collect(); + + let num_aux_segment_rands = trace_len.ilog2() as usize; + + Self { + main_trace: ColMatrix::new(vec![main_trace_col]), + info: TraceInfo::new_multi_segment( + 1, + [aux_segment_width], + [num_aux_segment_rands], + trace_len, + vec![], + ), + } + } + + fn len(&self) -> usize { + self.main_trace.num_rows() + } +} + +impl Trace for LagrangeComplexTrace { + type BaseField = BaseElement; + + fn info(&self) -> &TraceInfo { + &self.info + } + + fn main_segment(&self) -> &ColMatrix { + &self.main_trace + } + + /// Each non-Lagrange kernel segment will simply take the sum the random elements, and multiply + /// by the main column + fn build_aux_segment>( + &mut self, + aux_segments: &[ColMatrix], + rand_elements: &[E], + lagrange_kernel_rand_elements: Option<&[E]>, + ) -> Option> { + assert!(aux_segments.is_empty()); + + let mut columns = Vec::new(); + + // first build the Lagrange kernel column + { + let r = lagrange_kernel_rand_elements.unwrap(); + + let mut lagrange_col = Vec::with_capacity(self.len()); + + for row_idx in 0..self.len() { + let mut row_value = E::ONE; + for (bit_idx, &r_i) in r.iter().enumerate() { + if row_idx & (1 << bit_idx) == 0 { + row_value *= E::ONE - r_i; + } else { + row_value *= r_i; + } + } + lagrange_col.push(row_value); + } + + columns.push(lagrange_col); + } + + // Then all other auxiliary columns + let rand_summed = rand_elements.iter().fold(E::ZERO, |acc, &r| acc + r); + for _ in 1..self.aux_trace_width() { + // building a dummy auxiliary column + let column = self + .main_segment() + .get_column(0) + .iter() + .map(|main_row_val| rand_summed.mul_base(*main_row_val)) + .collect(); + + columns.push(column); + } + + Some(ColMatrix::new(columns)) + } + + fn read_main_frame(&self, row_idx: usize, frame: &mut EvaluationFrame) { + let next_row_idx = row_idx + 1; + assert_ne!(next_row_idx, self.len()); + + self.main_trace.read_row_into(row_idx, frame.current_mut()); + self.main_trace.read_row_into(next_row_idx, frame.next_mut()); + } +} + +// AIR +// ================================================================================================= + +struct LagrangeKernelComplexAir { + context: AirContext, +} + +impl Air for LagrangeKernelComplexAir { + type BaseField = BaseElement; + + type PublicInputs = (); + + fn new(trace_info: TraceInfo, _pub_inputs: Self::PublicInputs, options: ProofOptions) -> Self { + Self { + context: AirContext::new_multi_segment( + trace_info, + vec![TransitionConstraintDegree::new(1)], + vec![TransitionConstraintDegree::new(1)], + 1, + 1, + Some(0), + options, + ), + } + } + + fn context(&self) -> &AirContext { + &self.context + } + + fn evaluate_transition>( + &self, + frame: &EvaluationFrame, + _periodic_values: &[E], + result: &mut [E], + ) { + let current = frame.current()[0]; + let next = frame.next()[0]; + + // increments by 1 + result[0] = next - current - E::ONE; + } + + fn get_assertions(&self) -> Vec> { + vec![Assertion::single(0, 0, BaseElement::ZERO)] + } + + fn evaluate_aux_transition( + &self, + _main_frame: &EvaluationFrame, + _aux_frame: &EvaluationFrame, + _periodic_values: &[F], + _aux_rand_elements: &AuxTraceRandElements, + _result: &mut [E], + ) where + F: FieldElement, + E: FieldElement + ExtensionOf, + { + // do nothing + } + + fn get_aux_assertions>( + &self, + _aux_rand_elements: &AuxTraceRandElements, + ) -> Vec> { + vec![Assertion::single(1, 0, E::ZERO)] + } +} + +// LagrangeComplexProver +// ================================================================================================ + +struct LagrangeComplexProver { + options: ProofOptions, +} + +impl LagrangeComplexProver { + fn new() -> Self { + Self { + options: ProofOptions::new(1, 2, 0, FieldExtension::None, 2, 1), + } + } +} + +impl Prover for LagrangeComplexProver { + type BaseField = BaseElement; + type Air = LagrangeKernelComplexAir; + type Trace = LagrangeComplexTrace; + type HashFn = Blake3_256; + type RandomCoin = DefaultRandomCoin; + type TraceLde> = DefaultTraceLde; + type ConstraintEvaluator<'a, E: FieldElement> = + DefaultConstraintEvaluator<'a, LagrangeKernelComplexAir, E>; + + fn get_pub_inputs(&self, _trace: &Self::Trace) -> <::Air as Air>::PublicInputs { + () + } + + fn options(&self) -> &ProofOptions { + &self.options + } + + fn new_trace_lde( + &self, + trace_info: &TraceInfo, + main_trace: &ColMatrix, + domain: &StarkDomain, + ) -> (Self::TraceLde, TracePolyTable) + where + E: math::FieldElement, + { + DefaultTraceLde::new(trace_info, main_trace, domain) + } + + fn new_evaluator<'a, E>( + &self, + air: &'a Self::Air, + aux_rand_elements: AuxTraceRandElements, + composition_coefficients: ConstraintCompositionCoefficients, + ) -> Self::ConstraintEvaluator<'a, E> + where + E: math::FieldElement, + { + DefaultConstraintEvaluator::new(air, aux_rand_elements, composition_coefficients) + } +}