diff --git a/air/src/air/boundary/constraint_group.rs b/air/src/air/boundary/constraint_group.rs index 9cc296ad7..e5a6d9cb9 100644 --- a/air/src/air/boundary/constraint_group.rs +++ b/air/src/air/boundary/constraint_group.rs @@ -41,7 +41,7 @@ where { constraints: Vec>, divisor: ConstraintDivisor, - degree_adjustment: u32, + degree_adjustment: u64, } impl BoundaryConstraintGroup @@ -62,7 +62,7 @@ where // Boundary constraint degree is always deg(trace). So, the degree adjustment is simply: // deg(composition) + deg(divisor) - deg(trace) let target_degree = composition_degree + divisor.degree(); - let degree_adjustment = (target_degree - trace_poly_degree) as u32; + let degree_adjustment = (target_degree - trace_poly_degree) as u64; BoundaryConstraintGroup { constraints: Vec::new(), @@ -85,7 +85,7 @@ where } /// Returns a degree adjustment factor for all boundary constraints in this group. - pub fn degree_adjustment(&self) -> u32 { + pub fn degree_adjustment(&self) -> u64 { self.degree_adjustment } diff --git a/air/src/air/divisor.rs b/air/src/air/divisor.rs index 54a2196ac..b579a53da 100644 --- a/air/src/air/divisor.rs +++ b/air/src/air/divisor.rs @@ -171,7 +171,7 @@ impl Display for ConstraintDivisor { // ================================================================================================ /// Returns g^step, where g is the generator of trace domain. -pub fn get_trace_domain_value_at(trace_length: usize, step: usize) -> B { +fn get_trace_domain_value_at(trace_length: usize, step: usize) -> B { debug_assert!( step < trace_length, "step must be in the trace domain [0, {})", diff --git a/air/src/air/transition/mod.rs b/air/src/air/transition/mod.rs index 83bae03fc..9882d13a8 100644 --- a/air/src/air/transition/mod.rs +++ b/air/src/air/transition/mod.rs @@ -158,7 +158,7 @@ impl TransitionConstraints { /// * $z(x)$ is the constraint divisor polynomial. /// /// Thus, this function computes a linear combination of $C(x)$ evaluations. For more detail on - /// how this linear combination is computed refer to [TransitionConstraintGroup::merge_evaluations]. + /// how this linear combination is computed refer to [TransitionConstraintGroup::merge_evaluations]. /// /// Since, the divisor polynomial is the same for all transition constraints (see /// [ConstraintDivisor::from_transition]), we can divide the linear combination by the @@ -171,13 +171,15 @@ impl TransitionConstraints { { // merge constraint evaluations for the main trace segment let mut result = self.main_constraints().iter().fold(E::ZERO, |acc, group| { - acc + group.merge_evaluations::(main_evaluations, x) + let xp = x.exp(group.degree_adjustment.into()); + acc + group.merge_evaluations::(main_evaluations, xp) }); // merge constraint evaluations for auxiliary trace segments (if any) if self.num_aux_constraints() > 0 { result += self.aux_constraints().iter().fold(E::ZERO, |acc, group| { - acc + group.merge_evaluations::(aux_evaluations, x) + let xp = x.exp(group.degree_adjustment.into()); + acc + group.merge_evaluations::(aux_evaluations, xp) }); } @@ -199,7 +201,8 @@ impl TransitionConstraints { #[derive(Clone, Debug)] pub struct TransitionConstraintGroup { degree: TransitionConstraintDegree, - degree_adjustment: u32, + degree_adjustment: u64, + domain_offset_exp: E::BaseField, indexes: Vec, coefficients: Vec<(E, E)>, } @@ -213,15 +216,22 @@ impl TransitionConstraintGroup { trace_length: usize, composition_degree: usize, divisor_degree: usize, + domain_offset: E::BaseField, ) -> Self { // We want to make sure that once we divide a constraint polynomial by its divisor, the // degree of the resulting polynomial will be exactly equal to the composition_degree. let target_degree = composition_degree + divisor_degree; let evaluation_degree = degree.get_evaluation_degree(trace_length); - let degree_adjustment = (target_degree - evaluation_degree) as u32; + let degree_adjustment = (target_degree - evaluation_degree) as u64; + + // pre-compute domain offset exponent; this is used only by the prover and is not relevant + // for the verifier + let domain_offset_exp = domain_offset.exp(degree_adjustment.into()); + TransitionConstraintGroup { degree, degree_adjustment, + domain_offset_exp, indexes: vec![], coefficients: vec![], } @@ -240,6 +250,16 @@ impl TransitionConstraintGroup { &self.degree } + /// Returns degree adjustment factor for this constraint group. + pub fn degree_adjustment(&self) -> u64 { + self.degree_adjustment + } + + /// Returns c^degree_adjustment where c is the coset offset. + pub fn domain_offset_exp(&self) -> E::BaseField { + self.domain_offset_exp + } + /// Adds a new constraint to the group. The constraint is identified by an index in the /// evaluation table. pub fn add(&mut self, constraint_idx: usize, coefficients: (E, E)) { @@ -253,10 +273,13 @@ impl TransitionConstraintGroup { /// /// The linear combination is computed as follows: /// $$ - /// \sum_{i=0}^{k-1}{C_i(x) \cdot (\alpha_i + \beta_i \cdot x^d)} + /// \sum_{i=0}^{k-1}{C_i(x) \cdot (\alpha_i + \beta_i \cdot xp)} /// $$ /// where: /// * $C_i(x)$ is the evaluation of the $i$th constraint at `x` (same as `evaluations[i]`). + /// * $xp = x^d$ where $d$ is the degree adjustment factor computed as $D + (n - 1) - deg(C_i(x))$, + /// where $D$ is the degree of the composition polynomial, $n$ is the length of the execution + /// trace, and $deg(C_i(x))$ is the evaluation degree of the $i$th constraint. /// * $\alpha$ and $\beta$ are random field elements. In the interactive version of the /// protocol, these are provided by the verifier. /// * $d$ is the degree adjustment factor computed as $D + (n - 1) - deg(C_i(x))$, where @@ -271,15 +294,12 @@ impl TransitionConstraintGroup { /// them by the divisor later on. The degree of the divisor for transition constraints is /// always $n - 1$. Thus, once we divide out the divisor, the evaluations will represent a /// polynomial of degree $D$. - pub fn merge_evaluations(&self, evaluations: &[F], x: B) -> E + pub fn merge_evaluations(&self, evaluations: &[F], xp: B) -> E where B: FieldElement, F: FieldElement + ExtensionOf, E: FieldElement + ExtensionOf + ExtensionOf, { - // compute degree adjustment factor for this group - let xp = x.exp(self.degree_adjustment.into()); - // compute linear combination of evaluations as D(x) * (cc_0 + cc_1 * x^p), where D(x) // is an evaluation of a particular constraint, and x^p is the degree adjustment factor let mut result = E::ZERO; @@ -312,6 +332,7 @@ fn group_constraints( context.trace_len(), context.composition_degree(), divisor_degree, + context.options.domain_offset(), ) }); group.add(i, coefficients[i]); diff --git a/prover/src/constraints/boundary.rs b/prover/src/constraints/boundary.rs index 32a104140..e2ad05c6d 100644 --- a/prover/src/constraints/boundary.rs +++ b/prover/src/constraints/boundary.rs @@ -3,6 +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::StarkDomain; use air::{Air, AuxTraceRandElements, ConstraintDivisor}; use math::{fft, ExtensionOf, FieldElement}; use utils::collections::{BTreeMap, Vec}; @@ -86,59 +87,37 @@ impl BoundaryConstraints { /// Evaluates boundary constraints against the main segment of an execution trace at the /// specified step of constraint evaluation domain. - /// - /// Specifically, `step` is the step in the constraint evaluation domain, and `x` is the - /// corresponding domain value. That is, x = s * g^step, where g is the generator of the - /// constraint evaluation domain, and s is the domain offset. pub fn evaluate_main( &self, main_state: &[E::BaseField], - x: E::BaseField, + domain: &StarkDomain, step: usize, result: &mut [E], ) { - // compute the adjustment degree outside of the group so that we can re-use - // it for groups which have the same adjustment degree - let mut degree_adjustment = self.0[0].degree_adjustment; - let mut xp: E::BaseField = x.exp(degree_adjustment.into()); - + let x = domain.get_ce_x_at(step); for (group, result) in self.0.iter().zip(result.iter_mut()) { - // recompute adjustment degree only when it has changed - if group.degree_adjustment != degree_adjustment { - degree_adjustment = group.degree_adjustment; - xp = x.exp(degree_adjustment.into()); - } // evaluate the group and save the result + let (power, offset_exp) = (group.degree_adjustment, group.domain_offset_exp); + let xp = domain.get_ce_x_power_at(step, power, offset_exp); *result = group.evaluate_main(main_state, step, x, xp); } } /// Evaluates boundary constraints against all segments of an execution trace at the /// specified step of constraint evaluation domain. - /// - /// Specifically, `step` is the step in the constraint evaluation domain, and `x` is the - /// corresponding domain value. That is, x = s * g^step, where g is the generator of the - /// constraint evaluation domain, and s is the domain offset. pub fn evaluate_all( &self, main_state: &[E::BaseField], aux_state: &[E], - x: E::BaseField, + domain: &StarkDomain, step: usize, result: &mut [E], ) { - // compute the adjustment degree outside of the group so that we can re-use - // it for groups which have the same adjustment degree - let mut degree_adjustment = self.0[0].degree_adjustment; - let mut xp: E::BaseField = x.exp(degree_adjustment.into()); - + let x = domain.get_ce_x_at(step); for (group, result) in self.0.iter().zip(result.iter_mut()) { - // recompute adjustment degree only when it has changed - if group.degree_adjustment != degree_adjustment { - degree_adjustment = group.degree_adjustment; - xp = x.exp(degree_adjustment.into()); - } // evaluate the group and save the result + let (power, offset_exp) = (group.degree_adjustment, group.domain_offset_exp); + let xp = domain.get_ce_x_power_at(step, power, offset_exp); *result = group.evaluate_all(main_state, aux_state, step, x, xp); } } @@ -152,9 +131,13 @@ impl BoundaryConstraints { /// /// The constraints are also separated into constraints against the main segment of the execution /// and the constraints against auxiliary segments of the execution trace (if any). +/// +/// Domain offset exponent is pre-computed to be used later during constraint evaluation process, +/// and thus, to help avoid exponentiations. pub struct BoundaryConstraintGroup { divisor: ConstraintDivisor, - degree_adjustment: u32, + degree_adjustment: u64, + domain_offset_exp: E::BaseField, // main trace constraints main_single_value: Vec>, main_small_poly: Vec>, @@ -171,10 +154,15 @@ impl BoundaryConstraintGroup { /// Returns an empty [BoundaryConstraintGroup] instantiated with the specified divisor and /// degree adjustment factor. - fn new(divisor: ConstraintDivisor, degree_adjustment: u32) -> Self { + fn new( + divisor: ConstraintDivisor, + degree_adjustment: u64, + domain_offset: E::BaseField, + ) -> Self { Self { divisor, degree_adjustment, + domain_offset_exp: domain_offset.exp(degree_adjustment.into()), main_single_value: Vec::new(), main_small_poly: Vec::new(), main_large_poly: Vec::new(), @@ -195,7 +183,11 @@ impl BoundaryConstraintGroup { air: &A, twiddle_map: &mut BTreeMap>, ) -> Self { - let mut result = Self::new(source.divisor().clone(), source.degree_adjustment()); + let mut result = Self::new( + source.divisor().clone(), + source.degree_adjustment(), + air.domain_offset(), + ); for constraint in source.constraints() { if constraint.poly().len() == 1 { @@ -224,7 +216,11 @@ impl BoundaryConstraintGroup { air: &A, twiddle_map: &mut BTreeMap>, ) -> Self { - let mut result = Self::new(group.divisor().clone(), group.degree_adjustment()); + let mut result = Self::new( + group.divisor().clone(), + group.degree_adjustment(), + air.domain_offset(), + ); result.add_aux_constraints(group, air, twiddle_map); result } diff --git a/prover/src/constraints/evaluation_table.rs b/prover/src/constraints/evaluation_table.rs index c6e4148f1..d39db9a47 100644 --- a/prover/src/constraints/evaluation_table.rs +++ b/prover/src/constraints/evaluation_table.rs @@ -21,11 +21,10 @@ const MIN_FRAGMENT_SIZE: usize = 16; // CONSTRAINT EVALUATION TABLE // ================================================================================================ -pub struct ConstraintEvaluationTable { +pub struct ConstraintEvaluationTable<'a, E: FieldElement> { evaluations: Vec>, divisors: Vec>, - domain_offset: E::BaseField, - trace_length: usize, + domain: &'a StarkDomain, #[cfg(debug_assertions)] main_transition_evaluations: Vec>, @@ -35,14 +34,14 @@ pub struct ConstraintEvaluationTable { expected_transition_degrees: Vec, } -impl ConstraintEvaluationTable { +impl<'a, E: FieldElement> ConstraintEvaluationTable<'a, E> { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Returns a new constraint evaluation table with number of columns equal to the number of /// specified divisors, and number of rows equal to the size of constraint evaluation domain. #[cfg(not(debug_assertions))] pub fn new( - domain: &StarkDomain, + domain: &'a StarkDomain, divisors: Vec>, ) -> Self { let num_columns = divisors.len(); @@ -50,8 +49,7 @@ impl ConstraintEvaluationTable { ConstraintEvaluationTable { evaluations: uninit_matrix(num_columns, num_rows), divisors, - domain_offset: domain.offset(), - trace_length: domain.trace_length(), + domain, } } @@ -60,7 +58,7 @@ impl ConstraintEvaluationTable { /// expected degrees match their actual degrees. #[cfg(debug_assertions)] pub fn new( - domain: &StarkDomain, + domain: &'a StarkDomain, divisors: Vec>, transition_constraints: &TransitionConstraints, ) -> Self { @@ -77,8 +75,7 @@ impl ConstraintEvaluationTable { ConstraintEvaluationTable { evaluations: uninit_matrix(num_columns, num_rows), divisors, - domain_offset: domain.offset(), - trace_length: domain.trace_length(), + domain, main_transition_evaluations: uninit_matrix(num_tm_columns, num_rows), aux_transition_evaluations: uninit_matrix(num_ta_columns, num_rows), expected_transition_degrees, @@ -162,8 +159,6 @@ impl ConstraintEvaluationTable { /// combines the results into a single column, and interpolates this column into a composition /// polynomial in coefficient form. pub fn into_poly(self) -> Result, ProverError> { - let domain_offset = self.domain_offset; - // allocate memory for the combined polynomial let mut combined_poly = E::zeroed_vector(self.num_rows()); @@ -174,18 +169,19 @@ impl ConstraintEvaluationTable { // in debug mode, make sure post-division degree of each column matches the expected // degree #[cfg(debug_assertions)] - validate_column_degree(&column, divisor, domain_offset, column.len() - 1)?; + validate_column_degree(&column, divisor, self.domain, column.len() - 1)?; // divide the column by the divisor and accumulate the result into combined_poly - acc_column(column, divisor, domain_offset, &mut combined_poly); + acc_column(column, divisor, self.domain, &mut combined_poly); } // at this point, combined_poly contains evaluations of the combined constraint polynomial; // we interpolate this polynomial to transform it into coefficient form. let inv_twiddles = fft::get_inv_twiddles::(combined_poly.len()); - fft::interpolate_poly_with_offset(&mut combined_poly, &inv_twiddles, domain_offset); + fft::interpolate_poly_with_offset(&mut combined_poly, &inv_twiddles, self.domain.offset()); - Ok(CompositionPoly::new(combined_poly, self.trace_length)) + let trace_length = self.domain.trace_length(); + Ok(CompositionPoly::new(combined_poly, trace_length)) } // DEBUG HELPERS @@ -199,7 +195,7 @@ impl ConstraintEvaluationTable { let div_values = evaluate_divisor::( &self.divisors[0], self.num_rows(), - self.domain_offset, + self.domain.offset(), ); // collect actual degrees for all transition constraints by interpolating saved @@ -232,7 +228,7 @@ impl ConstraintEvaluationTable { // make sure evaluation domain size does not exceed the size required by max degree let expected_domain_size = - core::cmp::max(max_degree, self.trace_length + 1).next_power_of_two(); + core::cmp::max(max_degree, self.domain.trace_length() + 1).next_power_of_two(); assert_eq!( expected_domain_size, self.num_rows(), @@ -330,15 +326,14 @@ fn make_fragments( fn acc_column( column: Vec, divisor: &ConstraintDivisor, - domain_offset: E::BaseField, + domain: &StarkDomain, result: &mut [E], ) { let numerator = divisor.numerator(); assert_eq!(numerator.len(), 1, "complex divisors are not yet supported"); // compute inverse evaluations of the divisor's numerator, which has the form (x^a - b) - let domain_size = column.len(); - let z = get_inv_evaluation(divisor, domain_size, domain_offset); + let z = get_inv_evaluation(divisor, domain); // divide column values by the divisor; for boundary constraints this computed simply as // multiplication of column value by the inverse of divisor numerator; for transition @@ -362,19 +357,14 @@ fn acc_column( // form of (x^a - 1) / e(x), where e(x) describes the exemption points; thus, to divide // the column by the divisor, we compute: value * e(x) * z, where z = 1 / (x^a - 1) and has // already been computed above. - - // set up variables for computing x at every point in the domain - let g = E::BaseField::get_root_of_unity(domain_size.trailing_zeros()); - batch_iter_mut!( result, 128, // min batch size |batch: &mut [E], batch_offset: usize| { - let mut x = domain_offset * g.exp((batch_offset as u64).into()); for (i, acc_value) in batch.iter_mut().enumerate() { // compute value of e(x) and compute next value of x + let x = domain.get_ce_x_at(batch_offset + i); let e = divisor.evaluate_exemptions_at(x); - x *= g; // determine which value of z corresponds to the current domain point let z = z[i % z.len()]; // compute value * e(x) * z and add it to the result @@ -386,18 +376,26 @@ fn acc_column( } /// Computes evaluations of the divisor's numerator over the domain of the specified size and offset. -#[allow(clippy::many_single_char_names)] fn get_inv_evaluation( divisor: &ConstraintDivisor, - domain_size: usize, - domain_offset: B, + domain: &StarkDomain, ) -> Vec { let numerator = divisor.numerator(); let a = numerator[0].0 as u64; // numerator degree let b = numerator[0].1; - let n = domain_size / a as usize; - let g = B::get_root_of_unity(domain_size.trailing_zeros()).exp(a.into()); + // this guarantees that we can use get_ce_x_power_at() below but limits execution trace length + // to be at most 2^32. in the future, we should revisit this to allow execution traces of + // greater length. + assert!( + a <= u32::MAX as u64, + "constraint divisor numerator degree cannot exceed {}, but was {}", + u32::MAX, + a + ); + + let n = domain.ce_domain_size() / a as usize; + let domain_offset_exp = domain.offset().exp(a.into()); // compute x^a - b for all x let mut evaluations = unsafe { uninit_vector(n) }; @@ -405,10 +403,9 @@ fn get_inv_evaluation( &mut evaluations, 128, // min batch size |batch: &mut [B], batch_offset: usize| { - let mut x = domain_offset.exp(a.into()) * g.exp((batch_offset as u64).into()); - for evaluation in batch.iter_mut() { + for (i, evaluation) in batch.iter_mut().enumerate() { + let x = domain.get_ce_x_power_at(batch_offset + i, a, domain_offset_exp); *evaluation = x - b; - x *= g; } } ); @@ -478,11 +475,11 @@ fn get_transition_poly_degree( fn validate_column_degree>( column: &[E], divisor: &ConstraintDivisor, - domain_offset: B, + domain: &StarkDomain, expected_degree: usize, ) -> Result<(), ProverError> { // build domain for divisor evaluation, and evaluate it over this domain - let div_values = evaluate_divisor(divisor, column.len(), domain_offset); + let div_values = evaluate_divisor(divisor, column.len(), domain.offset()); // divide column values by the divisor let mut evaluations = column @@ -493,7 +490,7 @@ fn validate_column_degree>( // interpolate evaluations into a polynomial in coefficient form let inv_twiddles = fft::get_inv_twiddles::(evaluations.len()); - fft::interpolate_poly_with_offset(&mut evaluations, &inv_twiddles, domain_offset); + fft::interpolate_poly_with_offset(&mut evaluations, &inv_twiddles, domain.offset()); let poly = evaluations; if expected_degree != math::polynom::degree_of(&poly) { diff --git a/prover/src/constraints/evaluator.rs b/prover/src/constraints/evaluator.rs index 31a0e201e..95e6fbd85 100644 --- a/prover/src/constraints/evaluator.rs +++ b/prover/src/constraints/evaluator.rs @@ -74,8 +74,8 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< pub fn evaluate( self, trace: &TraceLde, - domain: &StarkDomain, - ) -> ConstraintEvaluationTable { + domain: &'a StarkDomain, + ) -> ConstraintEvaluationTable<'a, E> { assert_eq!( trace.trace_len(), domain.lde_domain_size(), @@ -148,10 +148,6 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< let mut evaluations = vec![E::ZERO; fragment.num_columns()]; let mut t_evaluations = vec![E::BaseField::ZERO; self.num_main_transition_constraints()]; - // pre-compute values needed to determine x coordinates in the constraint evaluation domain - let g = domain.ce_domain_generator(); - let mut x = domain.offset() * g.exp((fragment.offset() as u64).into()); - // this will be used to convert steps in constraint evaluation domain to steps in // LDE domain let lde_shift = domain.ce_to_lde_blowup().trailing_zeros(); @@ -168,7 +164,7 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< // evaluate transition constraints and save the merged result the first slot of the // evaluations buffer evaluations[0] = - self.evaluate_main_transition(&main_frame, x, step, &mut t_evaluations); + self.evaluate_main_transition(&main_frame, domain, step, &mut t_evaluations); // when in debug mode, save transition constraint evaluations #[cfg(debug_assertions)] @@ -177,14 +173,15 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< // evaluate boundary constraints; the results go into remaining slots of the // evaluations buffer let main_state = main_frame.current(); - self.boundary_constraints - .evaluate_main(main_state, x, step, &mut evaluations[1..]); + self.boundary_constraints.evaluate_main( + main_state, + domain, + step, + &mut evaluations[1..], + ); // record the result in the evaluation table fragment.update_row(i, &evaluations); - - // update x to the next value - x *= g; } } @@ -205,10 +202,6 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< let mut ta_evaluations = vec![E::ZERO; self.num_aux_transition_constraints()]; let mut evaluations = vec![E::ZERO; fragment.num_columns()]; - // pre-compute values needed to determine x coordinates in the constraint evaluation domain - let g = domain.ce_domain_generator(); - let mut x = domain.offset() * g.exp((fragment.offset() as u64).into()); - // this will be used to convert steps in constraint evaluation domain to steps in // LDE domain let lde_shift = domain.ce_to_lde_blowup().trailing_zeros(); @@ -224,9 +217,14 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< // 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, x, step, &mut tm_evaluations); - evaluations[0] += - self.evaluate_aux_transition(&main_frame, &aux_frame, x, step, &mut ta_evaluations); + self.evaluate_main_transition(&main_frame, domain, step, &mut tm_evaluations); + evaluations[0] += self.evaluate_aux_transition( + &main_frame, + &aux_frame, + domain, + step, + &mut ta_evaluations, + ); // when in debug mode, save transition constraint evaluations #[cfg(debug_assertions)] @@ -239,16 +237,13 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< self.boundary_constraints.evaluate_all( main_state, aux_state, - x, + domain, step, &mut evaluations[1..], ); // record the result in the evaluation table fragment.update_row(i, &evaluations); - - // update x to the next value - x *= g; } } @@ -264,7 +259,7 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< fn evaluate_main_transition( &self, main_frame: &EvaluationFrame, - x: E::BaseField, + domain: &StarkDomain, step: usize, evaluations: &mut [E::BaseField], ) -> E { @@ -281,7 +276,9 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< // merge transition constraint evaluations into a single value and return it; // we can do this here because all transition constraints have the same divisor. self.transition_constraints.main_constraints().iter().fold(E::ZERO, |result, group| { - result + group.merge_evaluations(evaluations, x) + let (power, offset_exp) = (group.degree_adjustment(), group.domain_offset_exp()); + let xp = domain.get_ce_x_power_at(step, power, offset_exp); + result + group.merge_evaluations(evaluations, xp) }) } @@ -295,7 +292,7 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< &self, main_frame: &EvaluationFrame, aux_frame: &EvaluationFrame, - x: E::BaseField, + domain: &StarkDomain, step: usize, evaluations: &mut [E], ) -> E { @@ -318,7 +315,9 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< // merge transition constraint evaluations into a single value and return it; // we can do this here because all transition constraints have the same divisor. self.transition_constraints.aux_constraints().iter().fold(E::ZERO, |result, group| { - result + group.merge_evaluations::(evaluations, x) + let (power, offset_exp) = (group.degree_adjustment(), group.domain_offset_exp()); + let xp = domain.get_ce_x_power_at(step, power, offset_exp); + result + group.merge_evaluations::(evaluations, xp) }) } diff --git a/prover/src/domain.rs b/prover/src/domain.rs index 2a13641ef..1f38c634f 100644 --- a/prover/src/domain.rs +++ b/prover/src/domain.rs @@ -4,7 +4,7 @@ // LICENSE file in the root directory of this source tree. use air::Air; -use math::{fft, log2, StarkField}; +use math::{fft, get_power_series, log2, StarkField}; use utils::collections::Vec; // TYPES AND INTERFACES @@ -15,12 +15,17 @@ pub struct StarkDomain { /// vector is half the length of the trace domain size. trace_twiddles: Vec, - /// Size of the constraint evaluation domain. - ce_domain_size: usize, + /// [g^i for i in (0..ce_domain_size)] where g is the constraint evaluation domain generator. + ce_domain: Vec, /// LDE domain size / constraint evaluation domain size ce_to_lde_blowup: usize, + /// A mask which can be used to compute (x % ce_domain_size) via binary AND. This takes + /// advantage of the fact that ce_domain_size is a power of two. The mask is then simply + /// ce_domain_size - 1. + ce_domain_mod_mask: usize, + /// Offset of the low-degree extension domain. domain_offset: B, } @@ -32,10 +37,16 @@ impl StarkDomain { /// Returns a new STARK domain initialized with the provided `context`. pub fn new>(air: &A) -> Self { let trace_twiddles = fft::get_twiddles(air.trace_length()); + + // build constraint evaluation domain + let domain_gen = B::get_root_of_unity(log2(air.ce_domain_size())); + let ce_domain = get_power_series(domain_gen, air.ce_domain_size()); + StarkDomain { trace_twiddles, - ce_domain_size: air.ce_domain_size(), + ce_domain, ce_to_lde_blowup: air.lde_domain_size() / air.ce_domain_size(), + ce_domain_mod_mask: air.ce_domain_size() - 1, domain_offset: air.domain_offset(), } } @@ -67,8 +78,9 @@ impl StarkDomain { // -------------------------------------------------------------------------------------------- /// Returns the size of the constraint evaluation domain for this computation. + #[inline(always)] pub fn ce_domain_size(&self) -> usize { - self.ce_domain_size + self.ce_domain.len() } /// Returns the generator of constraint evaluation domain. @@ -81,6 +93,29 @@ impl StarkDomain { self.ce_to_lde_blowup } + /// Returns s * g^step where g is the constraint evaluation domain generator and s is the + /// domain offset. + #[inline(always)] + pub fn get_ce_x_at(&self, step: usize) -> B { + self.ce_domain[step] * self.domain_offset + } + + /// Returns (s * g^step)^power where g is the constraint evaluation domain generator and s is + /// the domain offset. + /// + /// The computation is performed without doing exponentiations. offset_exp is assumed to be + /// s^power which is pre-computed elsewhere. + #[inline(always)] + pub fn get_ce_x_power_at(&self, step: usize, power: u64, offset_exp: B) -> B { + debug_assert_eq!(offset_exp, self.offset().exp(power.into())); + // this computes (step * power) % ce_domain_size. even though both step and power could be + // 64-bit values, we are not concerned about overflow here because we are modding by a + // power of two. this is also the reason why we can do & ce_domain_mod_mask instead of + // performing the actual modulus operation. + let index = step.wrapping_mul(power as usize) & self.ce_domain_mod_mask; + self.ce_domain[index] * offset_exp + } + // LOW-DEGREE EXTENSION DOMAIN // --------------------------------------------------------------------------------------------