diff --git a/README.md b/README.md index da5afbce0..0d7e94def 100644 --- a/README.md +++ b/README.md @@ -176,8 +176,14 @@ impl Air for WorkAir { // constraints don't match, an error will be thrown in the debug mode, but in release // mode, an invalid proof will be generated which will not be accepted by any verifier. let degrees = vec![TransitionConstraintDegree::new(3)]; + + // We also need to specify the exact number of assertions we will place against the + // execution trace. This number must be the same as the number of items in a vector + // returned from the get_assertions() method below. + let num_assertions = 2; + WorkAir { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, num_assertions, options), start: pub_inputs.start, result: pub_inputs.result, } diff --git a/air/README.md b/air/README.md index 1e23aa9f4..f828e685b 100644 --- a/air/README.md +++ b/air/README.md @@ -39,10 +39,10 @@ Keep in mind is that since transition constraints define algebraic relations, th #### Constraint degrees One of the main factors impacting proof generation time and proof size is the maximum degree of transition constraints. The higher is this degree, the larger our blowup factor needs to be. Usually, we want to keep this degree as low as possible - e.g. under 4 or 8. To accurately describe degrees of your transition constraints, keep the following in mind: -* All trace registers have degree `1`. -* When multiplying trace registers together, the degree increases by `1`. For example, if our constraint involves multiplication of two registers, the degree of this constraint will be `2`. We can describe this constraint using `TransitionConstraintDegree` struct as follows: `TransitionConstraintDegree::new(2)`. +* All trace columns have degree `1`. +* When multiplying trace columns together, the degree increases by `1`. For example, if our constraint involves multiplication of two columns, the degree of this constraint will be `2`. We can describe this constraint using `TransitionConstraintDegree` struct as follows: `TransitionConstraintDegree::new(2)`. * Degrees of periodic columns depend on the length of their cycles, but in most cases, these degrees are very close to `1`. -* To describe a degree of a constraint involving multiplication of trace registers and periodic columns, use the `with_cycles()` constructor of `TransitionConstraintDegree` struct. For example, if our constraint involves multiplication of one trace register and one periodic column with a cycle of 32 steps, the degree can be described as: `TransitionConstraintDegree::with_cycles(1, vec![32])`. +* To describe a degree of a constraint involving multiplication of trace columns and periodic columns, use the `with_cycles()` constructor of `TransitionConstraintDegree` struct. For example, if our constraint involves multiplication of one trace column and one periodic column with a cycle of 32 steps, the degree can be described as: `TransitionConstraintDegree::with_cycles(1, vec![32])`. In general, multiplications should be used judiciously - though, there are ways to ease this restriction a bit (check out [mulfib8](../examples/src/fibonacci/mulfib8/air.rs) example). @@ -51,14 +51,14 @@ Assertions are used to specify that a valid execution trace of a computation mus To define assertions for your computation, you'll need to implement `get_assertions()` function of the `Air` trait. Every computation must have at least one assertion. Assertions can be of the following types: -* A single assertion - such assertion specifies that a single cell of an execution trace must be equal to a specific value. For example: *value in register 0, step 0, must be equal to 1*. -* A periodic assertion - such assertion specifies that values in a given register at specified intervals should be equal to some values. For example: *values in register 0, steps 0, 8, 16, 24 etc. must be equal to 2*. -* A sequence assertion - such assertion specifies that values in a given register at specific intervals must be equal to a sequence of provided values. For example: *values in register 0, step 0 must be equal to 1, step 8 must be equal to 2, step 16 must be equal to 3 etc.* +* A single assertion - such assertion specifies that a single cell of an execution trace must be equal to a specific value. For example: *value in column 0, step 0, must be equal to 1*. +* A periodic assertion - such assertion specifies that values in a given column at specified intervals should be equal to some values. For example: *values in column 0, steps 0, 8, 16, 24 etc. must be equal to 2*. +* A sequence assertion - such assertion specifies that values in a given column at specific intervals must be equal to a sequence of provided values. For example: *values in column 0, step 0 must be equal to 1, step 8 must be equal to 2, step 16 must be equal to 3 etc.* For more information on how to define assertions see the [assertions](src/air/assertions/mod.rs) module and check out the examples in the [examples crate](../examples). ### Periodic values -Sometimes, it may be useful to define a column in an execution trace which contains a set of repeating values. For example, let's say we have a register which contains value 1 on every 4th step, and 0 otherwise. Such a column can be described with a simple periodic sequence of `[1, 0, 0, 0]`. +Sometimes, it may be useful to define a column in an execution trace which contains a set of repeating values. For example, let's say we have a column which contains value 1 on every 4th step, and 0 otherwise. Such a column can be described with a simple periodic sequence of `[1, 0, 0, 0]`. To define such columns for your computation, you can override `get_periodic_column_values()` method of the `Air` trait. The values of the periodic columns at a given step of the computation will be supplied to the `evaluate_transition()` method via the `periodic_values` parameter. diff --git a/air/src/air/assertions/mod.rs b/air/src/air/assertions/mod.rs index d9993d35e..71743f594 100644 --- a/air/src/air/assertions/mod.rs +++ b/air/src/air/assertions/mod.rs @@ -25,19 +25,19 @@ const NO_STRIDE: usize = 0; /// An assertion made against an execution trace. /// -/// An assertion is always placed against a single register of an execution trace, but can cover +/// An assertion is always placed against a single column of an execution trace, but can cover /// multiple steps and multiple values. Specifically, there are three kinds of assertions: /// /// 1. **Single** assertion - which requires that a value in a single cell of an execution trace /// is equal to the specified value. -/// 2. **Periodic** assertion - which requires that values in multiple cells of a single register +/// 2. **Periodic** assertion - which requires that values in multiple cells of a single column /// are equal to the specified value. The cells must be evenly spaced at intervals with lengths -/// equal to powers of two. For example, we can specify that values in a register must be equal +/// equal to powers of two. For example, we can specify that values in a column must be equal /// to 0 at steps 0, 8, 16, 24, 32 etc. Steps can also start at some offset - e.g., 1, 9, 17, /// 25, 33 is also a valid sequence of steps. -/// 3. **Sequence** assertion - which requires that multiple cells in a single register are equal +/// 3. **Sequence** assertion - which requires that multiple cells in a single column are equal /// to the values from the provided list. The cells must be evenly spaced at intervals with -/// lengths equal to powers of two. For example, we can specify that values in a register must +/// lengths equal to powers of two. For example, we can specify that values in a column must /// be equal to a sequence 1, 2, 3, 4 at steps 0, 8, 16, 24. That is, value at step 0 should be /// equal to 1, value at step 8 should be equal to 2 etc. /// @@ -47,7 +47,7 @@ const NO_STRIDE: usize = 0; /// this linear complexity should be negligible. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Assertion { - pub(super) register: usize, + pub(super) column: usize, pub(super) first_step: usize, pub(super) stride: usize, pub(super) values: Vec, @@ -58,20 +58,20 @@ impl Assertion { // -------------------------------------------------------------------------------------------- /// Returns an assertion against a single cell of an execution trace. /// - /// The returned assertion requires that the value in the specified `register` at the specified + /// The returned assertion requires that the value in the specified `column` at the specified /// `step` is equal to the provided `value`. - pub fn single(register: usize, step: usize, value: E) -> Self { + pub fn single(column: usize, step: usize, value: E) -> Self { Assertion { - register, + column, first_step: step, stride: NO_STRIDE, values: vec![value], } } - /// Returns an single-value assertion against multiple cells of a single register. + /// Returns an single-value assertion against multiple cells of a single column. /// - /// The returned assertion requires that values in the specified `register` must be equal to + /// The returned assertion requires that values in the specified `column` must be equal to /// the specified `value` at steps which start at `first_step` and repeat in equal intervals /// specified by `stride`. /// @@ -79,19 +79,19 @@ impl Assertion { /// Panics if: /// * `stride` is not a power of two, or is smaller than 2. /// * `first_step` is greater than `stride`. - pub fn periodic(register: usize, first_step: usize, stride: usize, value: E) -> Self { - validate_stride(stride, first_step, register); + pub fn periodic(column: usize, first_step: usize, stride: usize, value: E) -> Self { + validate_stride(stride, first_step, column); Assertion { - register, + column, first_step, stride, values: vec![value], } } - /// Returns a multi-value assertion against multiple cells of a single register. + /// Returns a multi-value assertion against multiple cells of a single column. /// - /// The returned assertion requires that values in the specified `register` must be equal to + /// The returned assertion requires that values in the specified `column` must be equal to /// the provided `values` at steps which start at `first_step` and repeat in equal intervals /// specified by `stride` until all values have been consumed. /// @@ -100,21 +100,21 @@ impl Assertion { /// * `stride` is not a power of two, or is smaller than 2. /// * `first_step` is greater than `stride`. /// * `values` is empty or number of values in not a power of two. - pub fn sequence(register: usize, first_step: usize, stride: usize, values: Vec) -> Self { - validate_stride(stride, first_step, register); + pub fn sequence(column: usize, first_step: usize, stride: usize, values: Vec) -> Self { + validate_stride(stride, first_step, column); assert!( !values.is_empty(), - "invalid assertion for register {}: number of asserted values must be greater than zero", - register + "invalid assertion for column {}: number of asserted values must be greater than zero", + column ); assert!( values.len().is_power_of_two(), - "invalid assertion for register {}: number of asserted values must be a power of two, but was {}", - register, + "invalid assertion for column {}: number of asserted values must be a power of two, but was {}", + column, values.len() ); Assertion { - register, + column, first_step, stride: if values.len() == 1 { NO_STRIDE } else { stride }, values, @@ -124,9 +124,9 @@ impl Assertion { // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns index of the register against which this assertion is placed. - pub fn register(&self) -> usize { - self.register + /// Returns index of the column against which this assertion is placed. + pub fn column(&self) -> usize { + self.column } /// Returns the first step of the execution trace against which this assertion is placed. @@ -170,9 +170,9 @@ impl Assertion { /// Checks if this assertion overlaps with the provided assertion. /// - /// Overlap is defined as asserting a value for the same step in the same register. + /// Overlap is defined as asserting a value for the same step in the same column. pub fn overlaps_with(&self, other: &Assertion) -> bool { - if self.register != other.register { + if self.column != other.column { return false; } if self.first_step == other.first_step { @@ -182,7 +182,7 @@ impl Assertion { return false; } - // at this point we know that assertions are for the same register but they start + // at this point we know that assertions are for the same column but they start // on different steps and also have different strides if self.first_step < other.first_step { @@ -208,11 +208,8 @@ impl Assertion { /// Panics if the assertion cannot be placed against an execution trace of the specified width. pub fn validate_trace_width(&self, trace_width: usize) -> Result<(), AssertionError> { - if self.register >= trace_width { - return Err(AssertionError::TraceWidthTooShort( - self.register, - trace_width, - )); + if self.column >= trace_width { + return Err(AssertionError::TraceWidthTooShort(self.column, trace_width)); } Ok(()) } @@ -309,12 +306,12 @@ impl Assertion { // ================================================================================================= /// We define ordering of assertions to be first by stride, then by first_step, and finally by -/// register in ascending order. +/// column in ascending order. impl Ord for Assertion { fn cmp(&self, other: &Self) -> Ordering { if self.stride == other.stride { if self.first_step == other.first_step { - self.register.partial_cmp(&other.register).unwrap() + self.column.partial_cmp(&other.column).unwrap() } else { self.first_step.partial_cmp(&other.first_step).unwrap() } @@ -332,7 +329,7 @@ impl PartialOrd for Assertion { impl Display for Assertion { fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { - write!(f, "(register={}, ", self.register)?; + write!(f, "(column={}, ", self.column)?; match self.stride { 0 => write!(f, "step={}, ", self.first_step)?, _ => { @@ -351,24 +348,24 @@ impl Display for Assertion { // HELPER FUNCTIONS // ================================================================================================= -fn validate_stride(stride: usize, first_step: usize, register: usize) { +fn validate_stride(stride: usize, first_step: usize, column: usize) { assert!( stride.is_power_of_two(), - "invalid assertion for register {}: stride must be a power of two, but was {}", - register, + "invalid assertion for column {}: stride must be a power of two, but was {}", + column, stride ); assert!( stride >= MIN_STRIDE_LENGTH, - "invalid assertion for register {}: stride must be at least {}, but was {}", - register, + "invalid assertion for column {}: stride must be at least {}, but was {}", + column, MIN_STRIDE_LENGTH, stride ); assert!( first_step < stride, - "invalid assertion for register {}: first step must be smaller than stride ({} steps), but was {}", - register, + "invalid assertion for column {}: first step must be smaller than stride ({} steps), but was {}", + column, stride, first_step ); diff --git a/air/src/air/assertions/tests.rs b/air/src/air/assertions/tests.rs index e51b1818a..249280b6f 100644 --- a/air/src/air/assertions/tests.rs +++ b/air/src/air/assertions/tests.rs @@ -14,7 +14,7 @@ use utils::collections::Vec; fn single_assertion() { let value = rand_value::(); let a = Assertion::single(2, 8, value); - assert_eq!(2, a.register); + assert_eq!(2, a.column); assert_eq!(8, a.first_step); assert_eq!(vec![value], a.values); assert_eq!(0, a.stride); @@ -46,7 +46,7 @@ fn single_assertion() { fn periodic_assertion() { let value = rand_value::(); let a = Assertion::periodic(4, 1, 16, value); - assert_eq!(4, a.register); + assert_eq!(4, a.column); assert_eq!(1, a.first_step); assert_eq!(vec![value], a.values); assert_eq!(16, a.stride); @@ -79,21 +79,21 @@ fn periodic_assertion() { #[test] #[should_panic( - expected = "invalid assertion for register 0: stride must be a power of two, but was 3" + expected = "invalid assertion for column 0: stride must be a power of two, but was 3" )] fn periodic_assertion_stride_not_power_of_two() { let _ = Assertion::periodic(0, 1, 3, BaseElement::ONE); } #[test] -#[should_panic(expected = "invalid assertion for register 0: stride must be at least 2, but was 1")] +#[should_panic(expected = "invalid assertion for column 0: stride must be at least 2, but was 1")] fn periodic_assertion_stride_too_small() { let _ = Assertion::periodic(0, 1, 1, BaseElement::ONE); } #[test] #[should_panic( - expected = "invalid assertion for register 0: first step must be smaller than stride (4 steps), but was 5" + expected = "invalid assertion for column 0: first step must be smaller than stride (4 steps), but was 5" )] fn periodic_assertion_first_step_greater_than_stride() { let _ = Assertion::periodic(0, 5, 4, BaseElement::ONE); @@ -115,7 +115,7 @@ fn periodic_assertion_get_num_steps_error() { fn sequence_assertion() { let values = rand_vector::(2); let a = Assertion::sequence(3, 2, 4, values.clone()); - assert_eq!(3, a.register); + assert_eq!(3, a.column); assert_eq!(2, a.first_step); assert_eq!(values, a.values); assert_eq!(4, a.stride); @@ -151,21 +151,21 @@ fn sequence_assertion() { #[test] #[should_panic( - expected = "invalid assertion for register 3: stride must be a power of two, but was 5" + expected = "invalid assertion for column 3: stride must be a power of two, but was 5" )] fn sequence_assertion_stride_not_power_of_two() { let _ = Assertion::sequence(3, 2, 5, vec![BaseElement::ONE, BaseElement::ZERO]); } #[test] -#[should_panic(expected = "invalid assertion for register 3: stride must be at least 2, but was 1")] +#[should_panic(expected = "invalid assertion for column 3: stride must be at least 2, but was 1")] fn sequence_assertion_stride_too_small() { let _ = Assertion::sequence(3, 2, 1, vec![BaseElement::ONE, BaseElement::ZERO]); } #[test] #[should_panic( - expected = "invalid assertion for register 3: first step must be smaller than stride (4 steps), but was 5" + expected = "invalid assertion for column 3: first step must be smaller than stride (4 steps), but was 5" )] fn sequence_assertion_first_step_greater_than_stride() { let _ = Assertion::sequence(3, 5, 4, vec![BaseElement::ONE, BaseElement::ZERO]); @@ -180,7 +180,7 @@ fn sequence_assertion_inconsistent_trace() { #[test] #[should_panic( - expected = "invalid assertion for register 3: number of asserted values must be greater than zero" + expected = "invalid assertion for column 3: number of asserted values must be greater than zero" )] fn sequence_assertion_empty_values() { let _ = Assertion::sequence(3, 2, 4, Vec::::new()); @@ -188,7 +188,7 @@ fn sequence_assertion_empty_values() { #[test] #[should_panic( - expected = "invalid assertion for register 3: number of asserted values must be a power of two, but was 3" + expected = "invalid assertion for column 3: number of asserted values must be a power of two, but was 3" )] fn sequence_assertion_num_values_not_power_of_two() { let _ = Assertion::sequence( @@ -210,7 +210,7 @@ fn assertion_overlap() { let b = Assertion::single(3, 2, BaseElement::ONE); assert!(a.overlaps_with(&b)); - // different registers: no overlap + // different columns: no overlap let b = Assertion::single(1, 2, BaseElement::ONE); assert!(!a.overlaps_with(&b)); @@ -233,7 +233,7 @@ fn assertion_overlap() { assert!(a.overlaps_with(&b)); assert!(b.overlaps_with(&a)); - // different registers: no overlap + // different columns: no overlap let b = Assertion::single(1, 2, BaseElement::ONE); assert!(!a.overlaps_with(&b)); assert!(!b.overlaps_with(&a)); @@ -259,7 +259,7 @@ fn assertion_overlap() { assert!(a.overlaps_with(&b)); assert!(b.overlaps_with(&a)); - // different registers: no overlap + // different columns: no overlap let b = Assertion::single(1, 2, BaseElement::ONE); assert!(!a.overlaps_with(&b)); assert!(!b.overlaps_with(&a)); @@ -284,7 +284,7 @@ fn assertion_overlap() { assert!(a.overlaps_with(&b)); assert!(b.overlaps_with(&a)); - // different registers: no overlap + // different columns: no overlap let b = Assertion::periodic(1, 4, 8, BaseElement::ONE); assert!(!a.overlaps_with(&b)); assert!(!b.overlaps_with(&a)); @@ -316,7 +316,7 @@ fn assertion_overlap() { assert!(a.overlaps_with(&b)); assert!(b.overlaps_with(&a)); - // different registers: no overlap + // different columns: no overlap let b = Assertion::sequence(1, 4, 8, values.clone()); assert!(!a.overlaps_with(&b)); assert!(!b.overlaps_with(&a)); @@ -348,7 +348,7 @@ fn assertion_overlap() { assert!(a.overlaps_with(&b)); assert!(b.overlaps_with(&a)); - // different registers: no overlap + // different columns: no overlap let b = Assertion::periodic(1, 4, 8, BaseElement::ONE); assert!(!a.overlaps_with(&b)); assert!(!b.overlaps_with(&a)); diff --git a/air/src/air/boundary/constraint.rs b/air/src/air/boundary/constraint.rs new file mode 100644 index 000000000..fc63a9e90 --- /dev/null +++ b/air/src/air/boundary/constraint.rs @@ -0,0 +1,143 @@ +// 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 super::{Assertion, BTreeMap, ExtensionOf, FieldElement, Vec}; +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: +/// +/// * $f(x)$ is a trace polynomial for the column against which the constraint is placed. +/// * $b(b)$ is the value polynomial for this constraint. +/// * $z(x)$ is the constraint divisor polynomial. +/// +/// In addition to the value polynomial, a [BoundaryConstraint] also contains info needed to +/// evaluate the constraint and to compose constraint evaluations with other constraints (i.e., +/// constraint composition coefficients). +/// +/// When the protocol is run in a large field, types `F` and `E` are the same. However, when +/// working with small fields, `F` and `E` can be set as follows: +/// * `F` could be the base field of the protocol, in which case `E` is the extension field used. +/// * `F` could be the extension field, in which case `F` and `E` are the same type. +/// +/// Boundary constraints cannot be instantiated directly, they are created internally from +/// [Assertions](Assertion). +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct BoundaryConstraint +where + F: FieldElement, + E: FieldElement + ExtensionOf, +{ + column: usize, + poly: Vec, + poly_offset: (usize, F), + cc: (E, E), +} + +impl BoundaryConstraint +where + F: FieldElement, + E: FieldElement + ExtensionOf, +{ + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + /// Creates a new boundary constraint from the specified assertion. + pub(super) fn new( + assertion: Assertion, + inv_g: F::BaseField, + twiddle_map: &mut BTreeMap>, + composition_coefficients: (E, E), + ) -> Self { + // build a polynomial which evaluates to constraint values at asserted steps; for + // single-value assertions we use the value as constant coefficient of degree 0 + // polynomial; but for multi-value assertions, we need to interpolate the values + // into a polynomial using inverse FFT + let mut poly_offset = (0, F::ONE); + let mut poly = assertion.values; + if poly.len() > 1 { + // get the twiddles from the map; if twiddles for this domain haven't been built + // yet, build them and add them to the map + let inv_twiddles = twiddle_map + .entry(poly.len()) + .or_insert_with(|| fft::get_inv_twiddles(poly.len())); + // interpolate the values into a polynomial + fft::interpolate_poly(&mut poly, inv_twiddles); + if assertion.first_step != 0 { + // if the assertions don't fall on the steps which are powers of two, we can't + // use FFT to interpolate the values into a polynomial. This would make such + // assertions quite impractical. To get around this, we still use FFT to build + // the polynomial, but then we evaluate it as f(x * offset) instead of f(x) + let x_offset = F::from(inv_g.exp((assertion.first_step as u64).into())); + poly_offset = (assertion.first_step, x_offset); + } + } + + BoundaryConstraint { + column: assertion.column, + poly, + poly_offset, + cc: composition_coefficients, + } + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns index of the column against which this constraint applies. + pub fn column(&self) -> usize { + self.column + } + + /// Returns a value polynomial for this constraint. + pub fn poly(&self) -> &[F] { + &self.poly + } + + /// Returns offset by which we need to shift the domain before evaluating this constraint. + /// + /// The offset is returned as a tuple describing both, the number of steps by which the + /// domain needs to be shifted, and field element by which a domain element needs to be + /// multiplied to achieve the desired shift. + pub fn poly_offset(&self) -> (usize, F) { + self.poly_offset + } + + /// Returns composition coefficients for this constraint. + pub fn cc(&self) -> &(E, E) { + &self.cc + } + + // CONSTRAINT EVALUATOR + // -------------------------------------------------------------------------------------------- + /// Evaluates this constraint at the specified point `x`. + /// + /// The constraint is evaluated by computing $f(x) - b(x)$, where: + /// * $f$ is a trace polynomial for the column against which the constraint is placed. + /// * $f(x)$ = `trace_value` + /// * $b$ is the value polynomial for this constraint. + /// + /// For boundary constraints derived from single and periodic assertions, $b(x)$ is a constant. + pub fn evaluate_at(&self, x: E, trace_value: E) -> E { + let assertion_value = if self.poly.len() == 1 { + // if the value polynomial consists of just a constant, use that constant + E::from(self.poly[0]) + } else { + // otherwise, we need to evaluate the polynomial at `x`; for assertions which don't + // fall on steps that are powers of two, we need to evaluate the value polynomial + // at x * offset (instead of just x). + // + // note that while the coefficients of the value polynomial are in the base field, + // if we are working in an extension field, the result of the evaluation will be a + // value in the extension field. + let x = x * E::from(self.poly_offset.1); + polynom::eval(&self.poly, x) + }; + // subtract assertion value from trace value + trace_value - assertion_value + } +} diff --git a/air/src/air/boundary/constraint_group.rs b/air/src/air/boundary/constraint_group.rs new file mode 100644 index 000000000..c33f9897f --- /dev/null +++ b/air/src/air/boundary/constraint_group.rs @@ -0,0 +1,148 @@ +// 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 super::{ + Assertion, BTreeMap, BoundaryConstraint, ConstraintDivisor, ExtensionOf, FieldElement, Vec, +}; + +// BOUNDARY CONSTRAINT GROUP +// ================================================================================================ +/// A group of boundary constraints all having the same divisor. +/// +/// A boundary constraint is described by a rational function $\frac{f(x) - b(x)}{z(x)}$, where: +/// +/// * $f(x)$ is a trace polynomial for the column against which the constraint is placed. +/// * $b(x)$ is the value polynomial for the constraint. +/// * $z(x)$ is the constraint divisor polynomial. +/// +/// A boundary constraint group groups together all boundary constraints where polynomial $z$ is +/// the same. The constraints stored in the group describe polynomials $b$. At the time of +/// constraint evaluation, a prover or a verifier provides evaluations of the relevant polynomial +/// $f$ so that the value of the constraint can be computed. +/// +/// When the protocol is run in a large field, types `F` and `E` are the same. However, when +/// working with small fields, `F` and `E` can be set as follows: +/// * `F` could be the base field of the protocol, in which case `E` is the extension field used. +/// * `F` could be the extension field, in which case `F` and `E` are the same type. +/// +/// The above arrangement allows use to describe boundary constraints for main and auxiliary +/// segments of the execution trace. Specifically: +/// * For the constraints against columns of the main execution trace, `F` is set to the base field +/// of the protocol, and `E` is set to the extension field. +/// * For the constraints against columns of auxiliary trace segments, both `F` and `E` are set to +/// the extension field. +#[derive(Debug, Clone)] +pub struct BoundaryConstraintGroup +where + F: FieldElement, + E: FieldElement + ExtensionOf, +{ + constraints: Vec>, + divisor: ConstraintDivisor, + degree_adjustment: u32, +} + +impl BoundaryConstraintGroup +where + F: FieldElement, + E: FieldElement + ExtensionOf, +{ + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + /// Returns a new boundary constraint group to hold constraints with the specified divisor. + pub(super) fn new( + divisor: ConstraintDivisor, + trace_poly_degree: usize, + composition_degree: usize, + ) -> 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. + // 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; + + BoundaryConstraintGroup { + constraints: Vec::new(), + divisor, + degree_adjustment, + } + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a list of boundary constraints in this group. + pub fn constraints(&self) -> &[BoundaryConstraint] { + &self.constraints + } + + /// Returns a divisor applicable to all boundary constraints in this group. + pub fn divisor(&self) -> &ConstraintDivisor { + &self.divisor + } + + /// Returns a degree adjustment factor for all boundary constraints in this group. + pub fn degree_adjustment(&self) -> u32 { + self.degree_adjustment + } + + // PUBLIC METHODS + // -------------------------------------------------------------------------------------------- + + /// Creates a new boundary constraint from the specified assertion and adds it to the group. + pub(super) fn add( + &mut self, + assertion: Assertion, + inv_g: F::BaseField, + twiddle_map: &mut BTreeMap>, + composition_coefficients: (E, E), + ) { + self.constraints.push(BoundaryConstraint::new( + assertion, + inv_g, + twiddle_map, + composition_coefficients, + )); + } + + /// Evaluates all constraints in this group at the specified point `x`. + /// + /// `xp` is a degree adjustment multiplier which must be computed as `x^degree_adjustment`. + /// This value is provided as an argument to this function for optimization purposes. + /// + /// Constraint evaluations are merges into a single value by computing their random linear + /// combination and dividing the result by the divisor of this constraint group as follows: + /// $$ + /// \frac{\sum_{i=0}^{k-1}{C_i(x) \cdot (\alpha_i + \beta_i \cdot x^d)}}{z(x)} + /// $$ + /// where: + /// * $C_i(x)$ is the evaluation of the $i$th constraint at `x` computed as $f(x) - b(x)$. + /// * $\alpha$ and $\beta$ are random field elements. In the interactive version of the + /// protocol, these are provided by the verifier. + /// * $z(x)$ is the evaluation of the divisor polynomial for this group at $x$. + /// * $d$ is the degree adjustment factor computed as $D - deg(C_i(x)) + deg(z(x))$, where + /// $D$ is the degree of the composition polynomial. + /// + /// Thus, the merged evaluations represent a polynomial of degree $D$, as the degree of the + /// numerator is $D + deg(z(x))$, and the division by $z(x)$ reduces the degree by $deg(z(x))$. + pub fn evaluate_at(&self, state: &[E], x: E, xp: E) -> E { + debug_assert_eq!( + x.exp(self.degree_adjustment.into()), + xp, + "inconsistent degree adjustment" + ); + let mut numerator = E::ZERO; + for constraint in self.constraints().iter() { + let trace_value = state[constraint.column()]; + let evaluation = constraint.evaluate_at(x, trace_value); + numerator += evaluation * (constraint.cc().0 + constraint.cc().1 * xp); + } + + let denominator = self.divisor.evaluate_at(x); + + numerator / denominator + } +} diff --git a/air/src/air/boundary/mod.rs b/air/src/air/boundary/mod.rs index 9b7af9a2a..d45831f81 100644 --- a/air/src/air/boundary/mod.rs +++ b/air/src/air/boundary/mod.rs @@ -3,252 +3,222 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use super::{Assertion, ConstraintDivisor}; -use math::{fft, polynom, FieldElement, StarkField}; -use utils::collections::{BTreeMap, Vec}; +use super::{AirContext, Assertion, ConstraintDivisor}; +use math::{ExtensionOf, FieldElement, StarkField}; +use utils::collections::{BTreeMap, BTreeSet, Vec}; + +mod constraint; +pub use constraint::BoundaryConstraint; + +mod constraint_group; +pub use constraint_group::BoundaryConstraintGroup; #[cfg(test)] mod tests; -// BOUNDARY CONSTRAINT GROUP +// BOUNDARY CONSTRAINT INFO // ================================================================================================ -/// A group of boundary constraints all having the same divisor. -/// -/// A boundary constraint is described by a rational function $\frac{f(x) - b(x)}{z(x)}$, where: + +/// Boundary constraints for a computation. /// -/// * $f(x)$ is a trace polynomial for the register against which the constraint is placed. -/// * $b(x)$ is the value polynomial for the constraint. -/// * $z(x)$ is the constraint divisor polynomial. +/// Boundary constraints are arranged into two categories: constraints against columns of the main +/// trace segment, and constraints against columns of auxiliary trace segments. Within each +/// category, the constraints are grouped by their divisor (see [BoundaryConstraintGroup] for +/// more info on boundary constraint structure). /// -/// A boundary constraint group groups together all boundary constraints where polynomial $z$ is -/// the same. The constraints stored in the group describe polynomials $b$. At the time of -/// constraint evaluation, a prover or a verifier provides evaluations of the relevant polynomial -/// $f$ so that the value of the constraint can be computed. -#[derive(Debug, Clone)] -pub struct BoundaryConstraintGroup> { - constraints: Vec>, - divisor: ConstraintDivisor, - degree_adjustment: u32, +/// When the protocol is run in a large field, types `B` and `E` are the same. However, when +/// working with small fields, these types are used as follows: +/// * Constraints against columns of the main segment of the execution trace are defined over the +/// field specified by `B`. +/// * Constraints against columns of auxiliary segments of the execution trace (if any) are defined +/// over the field specified by `E`. +/// * Constraint composition coefficients are defined over the field specified by `E`. +/// * Constraint divisors are defined over the field specified by `B`. +pub struct BoundaryConstraints> { + main_constraints: Vec>, + aux_constraints: Vec>, } -impl> BoundaryConstraintGroup { +impl> BoundaryConstraints { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Returns a new boundary constraint group to hold constraints with the specified divisor. - pub(super) fn new( - divisor: ConstraintDivisor, - trace_poly_degree: usize, - composition_degree: usize, + /// Returns a new instance of [BoundaryConstraints] for a computation described by the provided + /// assertions and AIR context. + /// + /// # Panics + /// Panics if: + /// * The number of provided assertions does not match the number of assertions described by + /// the context. + /// * The number of assertions does not match the number of the provided composition + /// coefficients. + /// * The specified assertions are not valid in the context of the computation (e.g., assertion + /// column index is out of bounds). + pub fn new( + context: &AirContext, + main_assertions: Vec>, + aux_assertions: Vec>, + composition_coefficients: &[(E, E)], ) -> 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. - // 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; - - BoundaryConstraintGroup { - constraints: Vec::new(), - divisor, - degree_adjustment, - } - } + // make sure the provided assertions are consistent with the specified context + assert_eq!( + main_assertions.len(), + context.num_main_assertions, + "expected {} assertions against main trace segment, but received {}", + context.num_main_assertions, + main_assertions.len(), + ); - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- + assert_eq!( + aux_assertions.len(), + context.num_aux_assertions, + "expected {} assertions against auxiliary trace segments, but received {}", + context.num_aux_assertions, + aux_assertions.len(), + ); - /// Returns a list of boundary constraints in this group. - pub fn constraints(&self) -> &[BoundaryConstraint] { - &self.constraints - } + assert_eq!( + context.num_assertions(), + composition_coefficients.len(), + "number of assertions must match the number of composition coefficient tuples" + ); - /// Returns a divisor applicable to all boundary constraints in this group. - pub fn divisor(&self) -> &ConstraintDivisor { - &self.divisor - } + let trace_length = context.trace_info.length(); + let main_trace_width = context.trace_info.layout().main_trace_width(); + let aux_trace_width = context.trace_info.layout().aux_trace_width(); + + // make sure the assertions are valid in he context of their respective trace segments; + // also, sort the assertions in the deterministic order so that changing the order of + // assertions does not change random coefficients that get assigned to them. + let main_assertions = prepare_assertions(main_assertions, main_trace_width, trace_length); + let aux_assertions = prepare_assertions(aux_assertions, aux_trace_width, trace_length); + + // compute inverse of the trace domain generator; this will be used for offset + // computations when creating sequence constraints + let inv_g = context.trace_domain_generator.inv(); + + // cache inverse twiddles for multi-value assertions in this map so that we don't have + // to re-build them for assertions with identical strides + let mut twiddle_map = BTreeMap::new(); + + // split composition coefficients into main and auxiliary parts + let (main_composition_coefficients, aux_composition_coefficients) = + composition_coefficients.split_at(main_assertions.len()); + + // build constraints for the assertions against the main trace segment + let main_constraints = group_constraints( + main_assertions, + context, + main_composition_coefficients, + inv_g, + &mut twiddle_map, + ); - /// Returns a degree adjustment factor for all boundary constraints in this group. - pub fn degree_adjustment(&self) -> u32 { - self.degree_adjustment + // build constraints for the assertions against auxiliary trace segments + let aux_constraints = group_constraints( + aux_assertions, + context, + aux_composition_coefficients, + inv_g, + &mut twiddle_map, + ); + + Self { + main_constraints, + aux_constraints, + } } - // PUBLIC METHODS + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Creates a new boundary constraint from the specified assertion and adds it to the group. - pub(super) fn add( - &mut self, - assertion: Assertion, - inv_g: B, - twiddle_map: &mut BTreeMap>, - coefficients: (E, E), - ) { - self.constraints.push(BoundaryConstraint::new( - assertion, - inv_g, - twiddle_map, - coefficients, - )); + /// Returns a reference to the boundary constraints against the main segment of an execution + /// trace. The constraints are grouped by their divisors. + pub fn main_constraints(&self) -> &[BoundaryConstraintGroup] { + &self.main_constraints } - /// Evaluates all constraints in this group at the specified point `x`. - /// - /// `xp` is a degree adjustment multiplier which must be computed as `x^degree_adjustment`. - /// This value is provided as an argument to this function for optimization purposes. - /// - /// Constraint evaluations are merges into a single value by computing their random linear - /// combination and dividing the result by the divisor of this constraint group as follows: - /// $$ - /// \frac{\sum_{i=0}^{k-1}{C_i(x) \cdot (\alpha_i + \beta_i \cdot x^d)}}{z(x)} - /// $$ - /// where: - /// * $C_i(x)$ is the evaluation of the $i$th constraint at `x` computed as $f(x) - b(x)$. - /// * $\alpha$ and $\beta$ are random field elements. In the interactive version of the - /// protocol, these are provided by the verifier. - /// * $z(x)$ is the evaluation of the divisor polynomial for this group at $x$. - /// * $d$ is the degree adjustment factor computed as $D - deg(C_i(x)) + deg(z(x))$, where - /// $D$ is the degree of the composition polynomial. - /// - /// Thus, the merged evaluations represent a polynomial of degree $D$, as the degree of the - /// numerator is $D + deg(z(x))$, and the division by $z(x)$ reduces the degree by $deg(z(x))$. - pub fn evaluate_at(&self, state: &[E], x: E, xp: E) -> E { - debug_assert_eq!( - x.exp(self.degree_adjustment.into()), - xp, - "inconsistent degree adjustment" - ); - let mut numerator = E::ZERO; - for constraint in self.constraints().iter() { - let trace_value = state[constraint.register()]; - let evaluation = constraint.evaluate_at(x, trace_value); - numerator += evaluation * (constraint.cc().0 + constraint.cc().1 * xp); - } - - let denominator = self.divisor.evaluate_at(x); - - numerator / denominator + /// Returns a reference to the boundary constraints against auxiliary segments of an execution + /// trace. The constraints are grouped by their divisors. + pub fn aux_constraints(&self) -> &[BoundaryConstraintGroup] { + &self.aux_constraints } } -// BOUNDARY CONSTRAINT +// HELPER FUNCTIONS // ================================================================================================ -/// The numerator portion of a boundary constraint. -/// -/// A boundary constraint is described by a rational function $\frac{f(x) - b(x)}{z(x)}$, where: -/// -/// * $f(x)$ is a trace polynomial for the register against which the constraint is placed. -/// * $b(b)$ is the value polynomial for this constraint. -/// * $z(x)$ is the constraint divisor polynomial. -/// -/// In addition to the value polynomial, a `BoundaryConstraint` also contains info needed to -/// evaluate the constraint and to compose constraint evaluations with other constraints (i.e., -/// constraint composition coefficients). -/// -/// `BoundaryConstraint`s cannot be instantiated directly, they are created internally from -/// [Assertions](Assertion). -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct BoundaryConstraint> { - register: usize, - poly: Vec, - poly_offset: (usize, B), - cc: (E, E), -} -impl> BoundaryConstraint { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Creates a new boundary constraint from the specified assertion. - pub(super) fn new( - assertion: Assertion, - inv_g: B, - twiddle_map: &mut BTreeMap>, - cc: (E, E), - ) -> Self { - // build a polynomial which evaluates to constraint values at asserted steps; for - // single-value assertions we use the value as constant coefficient of degree 0 - // polynomial; but for multi-value assertions, we need to interpolate the values - // into a polynomial using inverse FFT - let mut poly_offset = (0, B::ONE); - let mut poly = assertion.values; - if poly.len() > 1 { - // get the twiddles from the map; if twiddles for this domain haven't been built - // yet, build them and add them to the map - let inv_twiddles = twiddle_map - .entry(poly.len()) - .or_insert_with(|| fft::get_inv_twiddles(poly.len())); - // interpolate the values into a polynomial - fft::interpolate_poly(&mut poly, inv_twiddles); - if assertion.first_step != 0 { - // if the assertions don't fall on the steps which are powers of two, we can't - // use FFT to interpolate the values into a polynomial. This would make such - // assertions quite impractical. To get around this, we still use FFT to build - // the polynomial, but then we evaluate it as f(x * offset) instead of f(x) - let x_offset = inv_g.exp((assertion.first_step as u64).into()); - poly_offset = (assertion.first_step, x_offset); - } - } - - BoundaryConstraint { - register: assertion.register, - poly, - poly_offset, - cc, - } +/// Translates the provided assertions into boundary constraints, groups the constraints by their +/// divisor, and sorts the resulting groups by the degree adjustment factor. +fn group_constraints( + assertions: Vec>, + context: &AirContext, + composition_coefficients: &[(E, E)], + inv_g: F::BaseField, + twiddle_map: &mut BTreeMap>, +) -> Vec> +where + F: FieldElement, + E: FieldElement + ExtensionOf, +{ + // iterate over all assertions, which are sorted first by stride and then by first_step + // in ascending order + let mut groups = BTreeMap::new(); + for (assertion, &cc) in assertions.into_iter().zip(composition_coefficients) { + let key = (assertion.stride(), assertion.first_step()); + let group = groups.entry(key).or_insert_with(|| { + BoundaryConstraintGroup::new( + ConstraintDivisor::from_assertion(&assertion, context.trace_len()), + context.trace_poly_degree(), + context.composition_degree(), + ) + }); + + // add a new assertion constraint to the current group (last group in the list) + group.add(assertion, inv_g, twiddle_map, cc); } - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns index of the register against which this constraint applies. - pub fn register(&self) -> usize { - self.register - } + // make sure groups are sorted by adjustment degree + let mut groups = groups.into_iter().map(|e| e.1).collect::>(); + groups.sort_by_key(|c| c.degree_adjustment()); - /// Returns a value polynomial for this constraint. - pub fn poly(&self) -> &[B] { - &self.poly - } + groups +} - /// Returns offset by which we need to shift the domain before evaluating this constraint. - /// - /// The offset is returned as a tuple describing both, the number of steps by which the - /// domain needs to be shifted, and field element by which a domain element needs to be - /// multiplied to achieve the desired shift. - pub fn poly_offset(&self) -> (usize, B) { - self.poly_offset - } +/// Makes sure the assertions are valid in the context of this computation and don't overlap with +/// each other - i.e. no two assertions are placed against the same column and step combination. +/// +/// This also sorts the assertions in their 'natural order'. The natural order is defined as +/// sorting first by stride, then by first step, and finally by column, all in ascending order. +fn prepare_assertions( + assertions: Vec>, + trace_width: usize, + trace_length: usize, +) -> Vec> { + // we use a sorted set to help us sort the assertions by their 'natural' order + let mut result = BTreeSet::>::new(); + + for assertion in assertions.into_iter() { + assertion + .validate_trace_width(trace_width) + .unwrap_or_else(|err| { + panic!("assertion {} is invalid: {}", assertion, err); + }); + assertion + .validate_trace_length(trace_length) + .unwrap_or_else(|err| { + panic!("assertion {} is invalid: {}", assertion, err); + }); + for a in result.iter().filter(|a| a.column == assertion.column) { + assert!( + !a.overlaps_with(&assertion), + "assertion {} overlaps with assertion {}", + assertion, + a + ); + } - /// Returns composition coefficients for this constraint. - pub fn cc(&self) -> &(E, E) { - &self.cc + result.insert(assertion); } - // CONSTRAINT EVALUATOR - // -------------------------------------------------------------------------------------------- - /// Evaluates this constraint at the specified point `x`. - /// - /// The constraint is evaluated by computing $f(x) - b(x)$, where: - /// * $f$ is a trace polynomial for the register against which the constraint is placed. - /// * $f(x)$ = `trace_value` - /// * $b$ is the value polynomial for this constraint. - /// - /// For boundary constraints derived from single and periodic assertions, $b(x)$ is a constant. - pub fn evaluate_at(&self, x: E, trace_value: E) -> E { - let assertion_value = if self.poly.len() == 1 { - // if the value polynomial consists of just a constant, use that constant - E::from(self.poly[0]) - } else { - // otherwise, we need to evaluate the polynomial at `x`; for assertions which don't - // fall on steps that are powers of two, we need to evaluate the value polynomial - // at x * offset (instead of just x). - // - // note that while the coefficients of the value polynomial are in the base field, - // if we are working in an extension field, the result of the evaluation will be a - // value in the extension field. - let x = x * E::from(self.poly_offset.1); - polynom::eval(&self.poly, x) - }; - // subtract assertion value from trace value - trace_value - assertion_value - } + result.into_iter().collect() } diff --git a/air/src/air/boundary/tests.rs b/air/src/air/boundary/tests.rs index 9c56a61f9..8e5a57a98 100644 --- a/air/src/air/boundary/tests.rs +++ b/air/src/air/boundary/tests.rs @@ -9,7 +9,7 @@ use super::{ }; use crypto::{hashers::Blake3_256, RandomCoin}; use math::{fields::f128::BaseElement, log2, polynom, FieldElement, StarkField}; -use rand_utils::{rand_value, rand_vector}; +use rand_utils::{rand_value, rand_vector, shuffle}; use utils::collections::{BTreeMap, Vec}; // BOUNDARY CONSTRAINT TESTS @@ -20,7 +20,7 @@ fn boundary_constraint_from_single_assertion() { let mut test_prng = build_prng(); let (inv_g, mut twiddle_map, mut prng) = build_constraint_params(16); - // constraint should be built correctly for register 0, step 0 + // constraint should be built correctly for column 0, step 0 let value = rand_value::(); let assertion = Assertion::single(0, 0, value); let constraint = BoundaryConstraint::::new( @@ -29,10 +29,13 @@ fn boundary_constraint_from_single_assertion() { &mut twiddle_map, prng.draw_pair().unwrap(), ); - assert_eq!(0, constraint.register()); + assert_eq!(0, constraint.column()); assert_eq!(vec![value], constraint.poly()); assert_eq!((0, BaseElement::ONE), constraint.poly_offset()); - assert_eq!(test_prng.draw_pair::().unwrap(), constraint.cc); + assert_eq!( + &test_prng.draw_pair::().unwrap(), + constraint.cc() + ); // single value constraints should evaluate to trace_value - value let trace_value = rand_value::(); @@ -41,7 +44,7 @@ fn boundary_constraint_from_single_assertion() { constraint.evaluate_at(rand_value::(), trace_value) ); - // constraint is build correctly for register 1 step 8 + // constraint is build correctly for column 1 step 8 let value = rand_value::(); let assertion = Assertion::single(1, 8, value); let constraint = BoundaryConstraint::::new( @@ -50,10 +53,13 @@ fn boundary_constraint_from_single_assertion() { &mut twiddle_map, prng.draw_pair().unwrap(), ); - assert_eq!(1, constraint.register()); + assert_eq!(1, constraint.column()); assert_eq!(vec![value], constraint.poly()); assert_eq!((0, BaseElement::ONE), constraint.poly_offset()); - assert_eq!(test_prng.draw_pair::().unwrap(), constraint.cc); + assert_eq!( + &test_prng.draw_pair::().unwrap(), + constraint.cc() + ); // single value constraints should evaluate to trace_value - value let trace_value = rand_value::(); @@ -71,7 +77,7 @@ fn boundary_constraint_from_periodic_assertion() { let mut test_prng = build_prng(); let (inv_g, mut twiddle_map, mut prng) = build_constraint_params(16); - // constraint should be built correctly for register 0, step 0, stride 4 + // constraint should be built correctly for column 0, step 0, stride 4 let value = rand_value::(); let assertion = Assertion::periodic(0, 0, 4, value); let constraint = BoundaryConstraint::::new( @@ -80,10 +86,13 @@ fn boundary_constraint_from_periodic_assertion() { &mut twiddle_map, prng.draw_pair().unwrap(), ); - assert_eq!(0, constraint.register()); + assert_eq!(0, constraint.column()); assert_eq!(vec![value], constraint.poly()); assert_eq!((0, BaseElement::ONE), constraint.poly_offset()); - assert_eq!(test_prng.draw_pair::().unwrap(), constraint.cc); + assert_eq!( + &test_prng.draw_pair::().unwrap(), + constraint.cc() + ); // periodic value constraints should evaluate to trace_value - value let trace_value = rand_value::(); @@ -92,7 +101,7 @@ fn boundary_constraint_from_periodic_assertion() { constraint.evaluate_at(rand_value::(), trace_value) ); - // constraint should be built correctly for register 2, first step 3, stride 8 + // constraint should be built correctly for column 2, first step 3, stride 8 let value = rand_value::(); let assertion = Assertion::periodic(2, 3, 8, value); let constraint = BoundaryConstraint::::new( @@ -101,10 +110,13 @@ fn boundary_constraint_from_periodic_assertion() { &mut twiddle_map, prng.draw_pair().unwrap(), ); - assert_eq!(2, constraint.register()); + assert_eq!(2, constraint.column()); assert_eq!(vec![value], constraint.poly()); assert_eq!((0, BaseElement::ONE), constraint.poly_offset()); - assert_eq!(test_prng.draw_pair::().unwrap(), constraint.cc); + assert_eq!( + &test_prng.draw_pair::().unwrap(), + constraint.cc() + ); // periodic value constraints should evaluate to trace_value - value let trace_value = rand_value::(); @@ -122,7 +134,7 @@ fn boundary_constraint_from_sequence_assertion() { let mut test_prng = build_prng(); let (inv_g, mut twiddle_map, mut prng) = build_constraint_params(16); - // constraint should be built correctly for register 0, first step 0, stride 4 + // constraint should be built correctly for column 0, first step 0, stride 4 let values = rand_vector::(4); let constraint_poly = build_sequence_poly(&values, 16); let assertion = Assertion::sequence(0, 0, 4, values); @@ -132,10 +144,13 @@ fn boundary_constraint_from_sequence_assertion() { &mut twiddle_map, prng.draw_pair().unwrap(), ); - assert_eq!(0, constraint.register()); + assert_eq!(0, constraint.column()); assert_eq!(constraint_poly, constraint.poly()); assert_eq!((0, BaseElement::ONE), constraint.poly_offset()); - assert_eq!(test_prng.draw_pair::().unwrap(), constraint.cc); + assert_eq!( + &test_prng.draw_pair::().unwrap(), + constraint.cc() + ); assert_eq!(1, twiddle_map.len()); // sequence value constraints with no offset should evaluate to @@ -147,7 +162,7 @@ fn boundary_constraint_from_sequence_assertion() { constraint.evaluate_at(x, trace_value) ); - // constraint should be built correctly for register 0, first step 3, stride 8 + // constraint should be built correctly for column 0, first step 3, stride 8 let values = rand_vector::(2); let constraint_poly = build_sequence_poly(&values, 16); let assertion = Assertion::sequence(0, 3, 8, values.clone()); @@ -157,10 +172,13 @@ fn boundary_constraint_from_sequence_assertion() { &mut twiddle_map, prng.draw_pair().unwrap(), ); - assert_eq!(0, constraint.register()); + assert_eq!(0, constraint.column()); assert_eq!(constraint_poly, constraint.poly()); assert_eq!((3, inv_g.exp(3)), constraint.poly_offset()); - assert_eq!(test_prng.draw_pair::().unwrap(), constraint.cc); + assert_eq!( + &test_prng.draw_pair::().unwrap(), + constraint.cc() + ); assert_eq!(2, twiddle_map.len()); // sequence value constraints with offset should evaluate to @@ -173,6 +191,81 @@ fn boundary_constraint_from_sequence_assertion() { ); } +// PREPARE ASSERTIONS +// ================================================================================================ + +#[test] +fn prepare_assertions() { + let values = vec![ + BaseElement::new(1), + BaseElement::new(2), + BaseElement::new(3), + BaseElement::new(4), + ]; + + let mut assertions = vec![ + Assertion::single(0, 9, BaseElement::new(5)), // column 0, step 9 + Assertion::single(0, 0, BaseElement::new(3)), // column 0, step 0 + Assertion::sequence(0, 3, 4, values.clone()), // column 1, steps 2, 6, 10, 14 + Assertion::sequence(0, 2, 4, values.clone()), // column 0, steps 2, 6, 10, 14 + Assertion::periodic(1, 3, 8, BaseElement::new(7)), // column 1, steps 3, 11 + Assertion::sequence(1, 0, 8, values[..2].to_vec()), // column 1, steps 0, 8 + ]; + + // assertions should be sorted by stride, first step, and column + let expected = vec![ + Assertion::single(0, 0, BaseElement::new(3)), // column 0, step 0 + Assertion::single(0, 9, BaseElement::new(5)), // column 0, step 9 + Assertion::sequence(0, 2, 4, values.clone()), // column 0, steps 2, 6, 10, 14 + Assertion::sequence(0, 3, 4, values.clone()), // column 1, steps 2, 6, 10, 14 + Assertion::sequence(1, 0, 8, values[..2].to_vec()), // column 1, steps 0, 8 + Assertion::periodic(1, 3, 8, BaseElement::new(7)), // column 1, steps 3, 11 + ]; + + let trace_width = 2; + let trace_length = 16; + let result = super::prepare_assertions(assertions.clone(), trace_width, trace_length); + assert_eq!(expected, result); + + shuffle(&mut assertions); + let result = super::prepare_assertions(assertions.clone(), trace_width, trace_length); + assert_eq!(expected, result); + + shuffle(&mut assertions); + let result = super::prepare_assertions(assertions.clone(), trace_width, trace_length); + assert_eq!(expected, result); +} + +#[test] +#[should_panic( + expected = "assertion (column=0, steps=[1, 9, ...], value=7) overlaps with assertion (column=0, step=9, value=5)" +)] +fn prepare_assertions_with_overlap() { + let assertions = vec![ + Assertion::single(0, 9, BaseElement::new(5)), + Assertion::periodic(0, 1, 8, BaseElement::new(7)), + ]; + let _ = super::prepare_assertions(assertions.clone(), 2, 16); +} + +#[test] +#[should_panic( + expected = "assertion (column=0, step=16, value=5) is invalid: expected trace length to be at least 32, but was 16" +)] +fn prepare_assertions_with_invalid_trace_length() { + let assertions = vec![Assertion::single(0, 16, BaseElement::new(5))]; + let _ = super::prepare_assertions(assertions.clone(), 2, 16); +} + +#[test] +#[should_panic( + expected = "assertion (column=3, step=17, value=5) is invalid: expected trace width to be at least 3, but was 2" +)] +fn prepare_assertions_with_invalid_trace_width() { + let assertions = vec![Assertion::single(3, 17, BaseElement::new(5))]; + let _ = super::prepare_assertions(assertions.clone(), 2, 16); +} + // HELPER FUNCTIONS // ================================================================================================ fn build_constraint_params( diff --git a/air/src/air/context.rs b/air/src/air/context.rs index 94f71c13a..24ecc95ce 100644 --- a/air/src/air/context.rs +++ b/air/src/air/context.rs @@ -16,6 +16,8 @@ pub struct AirContext { pub(super) trace_info: TraceInfo, pub(super) main_transition_constraint_degrees: Vec, pub(super) aux_transition_constraint_degrees: Vec, + pub(super) num_main_assertions: usize, + pub(super) num_aux_assertions: usize, pub(super) ce_blowup_factor: usize, pub(super) trace_domain_generator: B, pub(super) lde_domain_generator: B, @@ -33,16 +35,28 @@ impl AirContext { /// in the order defined by this list. /// /// # Panics - /// Panics if `transition_constraint_degrees` is an empty vector. + /// Panics if + /// * `transition_constraint_degrees` is an empty vector. + /// * `num_main_assertions` is zero. + /// * Blowup factor specified by the provided `options` is too small to accommodate degrees + /// of the specified transition constraints. + /// * `trace_info` describes an multi-segment execution trace. pub fn new( trace_info: TraceInfo, transition_constraint_degrees: Vec, + num_assertions: usize, options: ProofOptions, ) -> Self { + assert!( + !trace_info.is_multi_segment(), + "provided trace info describes a multi-segment execution trace" + ); Self::new_multi_segment( trace_info, transition_constraint_degrees, Vec::new(), + num_assertions, + 0, options, ) } @@ -58,17 +72,53 @@ impl AirContext { /// are expected to be in the order defined by `aux_transition_constraint_degrees` list. /// /// # Panics - /// Panics if `transition_constraint_degrees` is an empty vector. + /// Panics if + /// * `main_transition_constraint_degrees` is an empty vector. + /// * `num_main_assertions` is zero. + /// * `trace_info.is_multi_segment() == true` but: + /// - `aux_transition_constraint_degrees` is an empty vector. + /// - `num_aux_assertions` is zero. + /// * `trace_info.is_multi_segment() == false` but: + /// - `aux_transition_constraint_degrees` is a non-empty vector. + /// - `num_aux_assertions` is greater than zero. + /// * Blowup factor specified by the provided `options` is too small to accommodate degrees + /// of the specified transition constraints. pub fn new_multi_segment( trace_info: TraceInfo, main_transition_constraint_degrees: Vec, aux_transition_constraint_degrees: Vec, + num_main_assertions: usize, + num_aux_assertions: usize, options: ProofOptions, ) -> Self { assert!( !main_transition_constraint_degrees.is_empty(), "at least one transition constraint degree must be specified" ); + assert!( + num_main_assertions > 0, + "at least one assertion must be specified" + ); + + if trace_info.is_multi_segment() { + 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" + ); + } else { + assert!( + aux_transition_constraint_degrees.is_empty(), + "auxiliary transition constraint degrees specified for a single-segment trace" + ); + assert!( + num_aux_assertions == 0, + "auxiliary assertions specified for a single-segment trace" + ); + } // determine minimum blowup factor needed to evaluate transition constraints by taking // the blowup factor of the highest degree constraint @@ -100,6 +150,8 @@ impl AirContext { trace_info, main_transition_constraint_degrees, aux_transition_constraint_degrees, + num_main_assertions, + num_aux_assertions, ce_blowup_factor, trace_domain_generator: B::get_root_of_unity(log2(trace_length)), lde_domain_generator: B::get_root_of_unity(log2(lde_domain_size)), @@ -154,4 +206,12 @@ impl AirContext { pub fn num_transition_constraints(&self) -> usize { self.main_transition_constraint_degrees.len() + self.aux_transition_constraint_degrees.len() } + + /// Returns the total number of assertions defined for a computation. + /// + /// 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. + pub fn num_assertions(&self) -> usize { + self.num_main_assertions + self.num_aux_assertions + } } diff --git a/air/src/air/divisor.rs b/air/src/air/divisor.rs index 36ce89d0f..264b9e549 100644 --- a/air/src/air/divisor.rs +++ b/air/src/air/divisor.rs @@ -78,7 +78,10 @@ impl ConstraintDivisor { /// /// # Panics /// Panics of the specified `trace_length` is inconsistent with the specified `assertion`. - pub fn from_assertion(assertion: &Assertion, trace_length: usize) -> Self { + pub fn from_assertion(assertion: &Assertion, trace_length: usize) -> Self + where + E: FieldElement, + { let num_steps = assertion.get_num_steps(trace_length); if assertion.first_step == 0 { Self::new(vec![(num_steps, B::ONE)], vec![]) diff --git a/air/src/air/mod.rs b/air/src/air/mod.rs index dc84f3021..6ca47adee 100644 --- a/air/src/air/mod.rs +++ b/air/src/air/mod.rs @@ -5,9 +5,9 @@ use crate::ProofOptions; use crypto::{Hasher, RandomCoin, RandomCoinError}; -use math::{fft, ExtensibleField, FieldElement, StarkField}; +use math::{fft, ExtensibleField, ExtensionOf, FieldElement, StarkField}; use utils::{ - collections::{BTreeMap, BTreeSet, Vec}, + collections::{BTreeMap, Vec}, Serializable, }; @@ -21,7 +21,7 @@ mod assertions; pub use assertions::Assertion; mod boundary; -pub use boundary::{BoundaryConstraint, BoundaryConstraintGroup}; +pub use boundary::{BoundaryConstraint, BoundaryConstraintGroup, BoundaryConstraints}; mod transition; pub use transition::{ @@ -99,16 +99,16 @@ const MIN_CYCLE_LENGTH: usize = 2; /// Usually, we want to keep this degree as low as possible - e.g. under 4 or 8. To accurately /// describe degrees of your transition constraints, keep the following in mind: /// -/// * All trace registers have degree `1`. -/// * When multiplying trace registers together, the degree increases by `1`. For example, if our -/// constraint involves multiplication of two registers, the degree of this constraint will be +/// * All trace columns have degree `1`. +/// * When multiplying trace columns together, the degree increases by `1`. For example, if our +/// constraint involves multiplication of two columns, the degree of this constraint will be /// `2`. We can describe this constraint using [TransitionConstraintDegree] struct as follows: /// `TransitionConstraintDegree::new(2)`. /// * Degrees of periodic columns depend on the length of their cycles, but in most cases, these /// degrees are very close to `1`. -/// * To describe a degree of a constraint involving multiplication of trace registers and +/// * To describe a degree of a constraint involving multiplication of trace columns and /// periodic columns, use the [TransitionConstraintDegree::with_cycles()] constructor. For -/// example, if our constraint involves multiplication of one trace register and one periodic +/// example, if our constraint involves multiplication of one trace column and one periodic /// column with a cycle of 32 steps, the degree can be described as: /// `TransitionConstraintDegree::with_cycles(1, vec![32])`. /// @@ -126,19 +126,19 @@ const MIN_CYCLE_LENGTH: usize = 2; /// least one assertion. Assertions can be of the following types: /// /// * A single assertion - such assertion specifies that a single cell of an execution trace must -/// be equal to a specific value. For example: *value in register 0, at step 0, must be equal +/// be equal to a specific value. For example: *value in column 0, at step 0, must be equal /// to 1*. -/// * A periodic assertion - such assertion specifies that values in a given register at specified -/// intervals should be equal to some value. For example: *values in register 0, at steps 0, 8, +/// * A periodic assertion - such assertion specifies that values in a given column at specified +/// intervals should be equal to some value. For example: *values in column 0, at steps 0, 8, /// 16, 24 etc. must be equal to 2*. -/// * A sequence assertion - such assertion specifies that values in a given register at specific -/// intervals must be equal to a sequence of provided values. For example: *values in register 0, +/// * A sequence assertion - such assertion specifies that values in a given column at specific +/// intervals must be equal to a sequence of provided values. For example: *values in column 0, /// at step 0 must be equal to 1, at step 8 must be equal to 2, at step 16 must be equal to 3 /// etc.* /// /// ### Periodic values /// Sometimes, it may be useful to define a column in an execution trace which contains a set of -/// repeating values. For example, let's say we have a register which contains value 1 on every +/// repeating values. For example, let's say we have a column which contains value 1 on every /// 4th step, and 0 otherwise. Such a column can be described with a simple periodic sequence of /// `[1, 0, 0, 0]`. /// @@ -178,6 +178,10 @@ pub trait Air: Send + Sync { /// the order of transition constraint degree descriptors used to instantiate [AirContext] /// for this AIR. Thus, the length of the `result` slice will equal to the number of /// transition constraints defined for this computation. + /// + /// We define type `E` separately from `Self::BaseField` to allow evaluation of constraints + /// over the out-of-domain evaluation frame, which may be defined over an extension field + /// (when extension fields are used). fn evaluate_transition>( &self, frame: &EvaluationFrame, @@ -188,6 +192,66 @@ pub trait Air: Send + Sync { /// Returns a set of assertions against a concrete execution trace of this computation. fn get_assertions(&self) -> Vec>; + // AUXILIARY TRACE CONSTRAINTS + // -------------------------------------------------------------------------------------------- + + /// Evaluates transition constraints over the specified evaluation frames for the main and + /// auxiliary trace segments. + /// + /// The evaluations should be written into the `results` slice in the same order as the + /// the order of auxiliary transition constraint degree descriptors used to instantiate + /// [AirContext] for this AIR. Thus, the length of the `result` slice will equal to the number + /// of auxiliary transition constraints defined for this computation. + /// + /// The default implementation of this function panics. It must be overridden for AIRs + /// describing computations which require multiple trace segments. + /// + /// The types for main and auxiliary trace evaluation frames are defined as follows: + /// * When the entire protocol is executed in a prime field, types `F` and `E` are the same, + /// and thus, both the main and the auxiliary trace frames are defined over the base filed. + /// * When the protocol is executed in an extension field, the main trace frame is defined + /// over the base field, while the auxiliary trace frame is defined over the extension field. + /// + /// We define type `F` separately from `Self::BaseField` to allow evaluation of constraints + /// over the out-of-domain evaluation frame, which may be defined over an extension field + /// (when extension fields are used). The type bounds specified for this function allow the + /// following: + /// * `F` and `E` could be the same [StarkField] or extensions of the same [StarkField]. + /// * `F` and `E` could be the same field, because a field is always an extension of itself. + /// * If `F` and `E` are different, then `E` must be an extension of `F`. + #[allow(unused_variables)] + fn evaluate_aux_transition( + &self, + main_frame: &EvaluationFrame, + aux_frame: &EvaluationFrame, + periodic_values: &[F], + aux_rand_elements: &AuxTraceRandElements, + result: &mut [E], + ) -> Vec> + where + F: FieldElement, + E: FieldElement + ExtensionOf, + { + unimplemented!("evaluation of auxiliary transition constraints has not been implemented"); + } + + /// Returns a set of assertions placed against auxiliary trace segments. + /// + /// When the protocol is executed using an extension field, auxiliary assertions are defined + /// over the extension field. This is in contrast with the assertions returned from + /// [get_assertions()](Air::get_assertions) function, which always returns assertions defined + /// over the base field of the protocol. + /// + /// The default implementation of this function returns an empty vector. It should be + /// overridden only if the computation relies on auxiliary trace segments. In such a case, + /// the vector returned from this function must contain at least one assertion. + fn get_aux_assertions(&self) -> Vec> + where + E: FieldElement, + { + Vec::new() + } + // PROVIDED METHODS // -------------------------------------------------------------------------------------------- @@ -246,9 +310,10 @@ pub trait Air: Send + Sync { /// Groups transition constraints together by their degree. /// - /// This function also assigns coefficients to each constraint. These coefficients will be - /// used to compute a random linear combination of transition constraints evaluations during - /// constraint merging performed by [TransitionConstraintGroup::merge_evaluations()] function. + /// This function also assigns composition coefficients to each constraint. These coefficients + /// will be used to compute a random linear combination of transition constraints evaluations + /// during constraint merging performed by [TransitionConstraintGroup::merge_evaluations()] + /// function. fn get_transition_constraints>( &self, composition_coefficients: &[(E, E)], @@ -256,57 +321,22 @@ pub trait Air: Send + Sync { TransitionConstraints::new(self.context(), composition_coefficients) } - /// Convert assertions returned from [get_assertions()](Air::get_assertions) method into - /// boundary constraints. + /// Convert assertions returned from [get_assertions()](Air::get_assertions) and + /// [get_aux_assertions()](Air::get_aux_assertions) methods into boundary constraints. /// - /// This function also assign coefficients to each constraint, and group the constraints by - /// denominator. The coefficients will be used to compute random linear combination of boundary - /// constraints during constraint merging. + /// This function also assign composition coefficients to each constraint, and group the + /// constraints by denominator. The coefficients will be used to compute random linear + /// combination of boundary constraints during constraint merging. fn get_boundary_constraints>( &self, - coefficients: &[(E, E)], - ) -> Vec> { - // compute inverse of the trace domain generator; this will be used for offset - // computations when creating sequence constraints - let inv_g = self.trace_domain_generator().inv(); - - // cache inverse twiddles for multi-value assertions in this map so that we don't have - // to re-build them for assertions with identical strides - let mut twiddle_map = BTreeMap::new(); - - // get the assertions for this computation and make sure that they are all valid in - // the context of this computation; also, sort the assertions in the deterministic order - // so that changing the order of assertions does not change random coefficients that - // get assigned to them - let assertions = prepare_assertions(self.get_assertions(), self.trace_info()); - assert_eq!( - assertions.len(), - coefficients.len(), - "number of assertions must match the number of coefficient tuples" - ); - - // iterate over all assertions, which are sorted first by stride and then by first_step - // in ascending order - let mut groups = BTreeMap::new(); - for (i, assertion) in assertions.into_iter().enumerate() { - let key = (assertion.stride(), assertion.first_step()); - let group = groups.entry(key).or_insert_with(|| { - BoundaryConstraintGroup::new( - ConstraintDivisor::from_assertion(&assertion, self.trace_length()), - self.trace_poly_degree(), - self.composition_degree(), - ) - }); - - // add a new assertion constraint to the current group (last group in the list) - group.add(assertion, inv_g, &mut twiddle_map, coefficients[i]); - } - - // make sure groups are sorted by adjustment degree - let mut groups = groups.into_iter().map(|e| e.1).collect::>(); - groups.sort_by_key(|c| c.degree_adjustment()); - - groups + composition_coefficients: &[(E, E)], + ) -> BoundaryConstraints { + BoundaryConstraints::new( + self.context(), + self.get_assertions(), + self.get_aux_assertions(), + composition_coefficients, + ) } // PUBLIC ACCESSORS @@ -455,10 +485,8 @@ pub trait Air: Send + Sync { t_coefficients.push(public_coin.draw_pair()?); } - // TODO: calling self.get_assertions() is heavy; find a better way to specify the number - // assertions let mut b_coefficients = Vec::new(); - for _ in 0..self.get_assertions().len() { + for _ in 0..self.context().num_assertions() { b_coefficients.push(public_coin.draw_pair()?); } @@ -495,64 +523,4 @@ pub trait Air: Send + Sync { degree: public_coin.draw_pair()?, }) } - - // AUXILIARY TRACE CONSTRAINTS - // -------------------------------------------------------------------------------------------- - - // TODO: add docs - #[allow(unused_variables)] - fn evaluate_aux_transition( - &self, - frame: &EvaluationFrame, - aux_frame: &EvaluationFrame, - periodic_values: &[E1], - aux_rand_elements: &AuxTraceRandElements, - result: &mut [E2], - ) where - E1: FieldElement, - E2: FieldElement + From, - { - unimplemented!("evaluation of auxiliary transition constraints has not been implemented"); - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Makes sure the assertions are valid in the context of this computation and don't overlap with -/// each other - i.e. no two assertions are placed against the same register and step combination. -fn prepare_assertions( - assertions: Vec>, - trace_info: &TraceInfo, -) -> Vec> { - // we use a sorted set to help us sort the assertions by their 'natural' order. The natural - // order is defined as sorting first by stride, then by first step, and finally by register, - // all in ascending order. - let mut result = BTreeSet::>::new(); - - let main_trace_width = trace_info.layout().main_trace_width(); - for assertion in assertions.into_iter() { - assertion - .validate_trace_width(main_trace_width) - .unwrap_or_else(|err| { - panic!("assertion {} is invalid: {}", assertion, err); - }); - assertion - .validate_trace_length(trace_info.length()) - .unwrap_or_else(|err| { - panic!("assertion {} is invalid: {}", assertion, err); - }); - for a in result.iter().filter(|a| a.register == assertion.register) { - assert!( - !a.overlaps_with(&assertion), - "assertion {} overlaps with assertion {}", - assertion, - a - ); - } - - result.insert(assertion); - } - - result.into_iter().collect() } diff --git a/air/src/air/tests.rs b/air/src/air/tests.rs index 9c4c40cf5..a7e7a18e0 100644 --- a/air/src/air/tests.rs +++ b/air/src/air/tests.rs @@ -10,7 +10,6 @@ use super::{ use crate::{FieldExtension, HashFunction}; use crypto::{hashers::Blake3_256, RandomCoin}; use math::{fields::f128::BaseElement, get_power_series, log2, polynom, FieldElement, StarkField}; -use rand_utils::shuffle; use utils::collections::{BTreeMap, Vec}; // PERIODIC COLUMNS @@ -75,14 +74,14 @@ fn get_boundary_constraints() { BaseElement::new(4), ]; let assertions = vec![ - Assertion::single(0, 0, BaseElement::new(3)), // register 0, step 0 -> group 0 - Assertion::single(0, 9, BaseElement::new(5)), // register 0, step 9 -> group 1 - Assertion::single(1, 9, BaseElement::new(9)), // register 0, step 9 -> group 1 - Assertion::sequence(0, 2, 4, values.clone()), // register 0, steps 2, 6, 10, 14 -> group 4 - Assertion::sequence(1, 2, 4, values.clone()), // register 1, steps 2, 6, 10, 14 -> group 4 - Assertion::sequence(1, 0, 8, values[..2].to_vec()), // register 1, steps 0, 8 -> group 2 - Assertion::sequence(0, 3, 8, values[..2].to_vec()), // register 0, steps 3, 11 -> group 3 - Assertion::periodic(1, 3, 8, BaseElement::new(7)), // register 1, steps 3, 11 -> group 3 + Assertion::single(0, 0, BaseElement::new(3)), // column 0, step 0 -> group 0 + Assertion::single(0, 9, BaseElement::new(5)), // column 0, step 9 -> group 1 + Assertion::single(1, 9, BaseElement::new(9)), // column 0, step 9 -> group 1 + Assertion::sequence(0, 2, 4, values.clone()), // column 0, steps 2, 6, 10, 14 -> group 4 + Assertion::sequence(1, 2, 4, values.clone()), // column 1, steps 2, 6, 10, 14 -> group 4 + Assertion::sequence(1, 0, 8, values[..2].to_vec()), // column 1, steps 0, 8 -> group 2 + Assertion::sequence(0, 3, 8, values[..2].to_vec()), // column 0, steps 3, 11 -> group 3 + Assertion::periodic(1, 3, 8, BaseElement::new(7)), // column 1, steps 3, 11 -> group 3 ]; // instantiate mock AIR @@ -92,7 +91,7 @@ fn get_boundary_constraints() { let g = BaseElement::get_root_of_unity(log2(trace_length)); // trace domain generator // build coefficients for random liner combination; these will be derived for assertions - // sorted first by stride, then by first step, and finally by register (similar to the order) + // sorted first by stride, then by first step, and finally by column (similar to the order) // of assertions above let mut prng = build_prng(); let mut expected_cc = BTreeMap::::new(); @@ -111,7 +110,9 @@ fn get_boundary_constraints() { let coefficients = (0..8) .map(|_| prng.draw_pair().unwrap()) .collect::>(); - let mut groups = air.get_boundary_constraints(&coefficients); + let constraints = air.get_boundary_constraints(&coefficients); + let mut groups = constraints.main_constraints().to_vec(); + groups.sort_by(|g1, g2| { if g1.degree_adjustment() == g2.degree_adjustment() { let n1 = &g1.divisor().numerator()[0].1; @@ -132,7 +133,7 @@ fn get_boundary_constraints() { assert_eq!(1, group.constraints().len()); let constraint = &group.constraints()[0]; - assert_eq!(0, constraint.register()); + assert_eq!(0, constraint.column()); assert_eq!(vec![BaseElement::new(3)], constraint.poly()); assert_eq!(no_poly_offset, constraint.poly_offset()); assert_eq!(expected_cc[&0], constraint.cc().clone()); @@ -144,13 +145,13 @@ fn get_boundary_constraints() { assert_eq!(2, group.constraints().len()); let constraint = &group.constraints()[0]; - assert_eq!(0, constraint.register()); + assert_eq!(0, constraint.column()); assert_eq!(vec![BaseElement::new(5)], constraint.poly()); assert_eq!(no_poly_offset, constraint.poly_offset()); assert_eq!(expected_cc[&1], constraint.cc().clone()); let constraint = &group.constraints()[1]; - assert_eq!(1, constraint.register()); + assert_eq!(1, constraint.column()); assert_eq!(vec![BaseElement::new(9)], constraint.poly()); assert_eq!(no_poly_offset, constraint.poly_offset()); assert_eq!(expected_cc[&2], constraint.cc().clone()); @@ -162,7 +163,7 @@ fn get_boundary_constraints() { assert_eq!(1, group.constraints().len()); let constraint = &group.constraints()[0]; - assert_eq!(1, constraint.register()); + assert_eq!(1, constraint.column()); assert_eq!( build_sequence_poly(&values[..2], trace_length), constraint.poly() @@ -177,7 +178,7 @@ fn get_boundary_constraints() { assert_eq!(2, group.constraints().len()); let constraint = &group.constraints()[0]; - assert_eq!(0, constraint.register()); + assert_eq!(0, constraint.column()); assert_eq!( build_sequence_poly(&values[..2], trace_length), constraint.poly() @@ -186,7 +187,7 @@ fn get_boundary_constraints() { assert_eq!(expected_cc[&4], constraint.cc().clone()); let constraint = &group.constraints()[1]; - assert_eq!(1, constraint.register()); + assert_eq!(1, constraint.column()); assert_eq!(vec![BaseElement::new(7)], constraint.poly()); assert_eq!(no_poly_offset, constraint.poly_offset()); assert_eq!(expected_cc[&5], constraint.cc().clone()); @@ -198,7 +199,7 @@ fn get_boundary_constraints() { assert_eq!(2, group.constraints().len()); let constraint = &group.constraints()[0]; - assert_eq!(0, constraint.register()); + assert_eq!(0, constraint.column()); assert_eq!( build_sequence_poly(&values, trace_length), constraint.poly() @@ -207,7 +208,7 @@ fn get_boundary_constraints() { assert_eq!(expected_cc[&6], constraint.cc().clone()); let constraint = &group.constraints()[1]; - assert_eq!(1, constraint.register()); + assert_eq!(1, constraint.column()); assert_eq!( build_sequence_poly(&values, trace_length), constraint.poly() @@ -216,83 +217,6 @@ fn get_boundary_constraints() { assert_eq!(expected_cc[&7], constraint.cc().clone()); } -// PREPARE ASSERTIONS -// ================================================================================================ - -#[test] -fn prepare_assertions() { - let values = vec![ - BaseElement::new(1), - BaseElement::new(2), - BaseElement::new(3), - BaseElement::new(4), - ]; - - let mut assertions = vec![ - Assertion::single(0, 9, BaseElement::new(5)), // register 0, step 9 - Assertion::single(0, 0, BaseElement::new(3)), // register 0, step 0 - Assertion::sequence(0, 3, 4, values.clone()), // register 1, steps 2, 6, 10, 14 - Assertion::sequence(0, 2, 4, values.clone()), // register 0, steps 2, 6, 10, 14 - Assertion::periodic(1, 3, 8, BaseElement::new(7)), //register 1, steps 3, 11 - Assertion::sequence(1, 0, 8, values[..2].to_vec()), // register 1, steps 0, 8 - ]; - - // assertions should be sorted by stride, first step, and register - let expected = vec![ - Assertion::single(0, 0, BaseElement::new(3)), // register 0, step 0 - Assertion::single(0, 9, BaseElement::new(5)), // register 0, step 9 - Assertion::sequence(0, 2, 4, values.clone()), // register 0, steps 2, 6, 10, 14 - Assertion::sequence(0, 3, 4, values.clone()), // register 1, steps 2, 6, 10, 14 - Assertion::sequence(1, 0, 8, values[..2].to_vec()), // register 1, steps 0, 8 - Assertion::periodic(1, 3, 8, BaseElement::new(7)), //register 1, steps 3, 11 - ]; - - let trace_info = TraceInfo::new(2, 16); - let result = super::prepare_assertions(assertions.clone(), &trace_info); - assert_eq!(expected, result); - - shuffle(&mut assertions); - let result = super::prepare_assertions(assertions.clone(), &trace_info); - assert_eq!(expected, result); - - shuffle(&mut assertions); - let result = super::prepare_assertions(assertions.clone(), &trace_info); - assert_eq!(expected, result); -} - -#[test] -#[should_panic( - expected = "assertion (register=0, steps=[1, 9, ...], value=7) overlaps with assertion (register=0, step=9, value=5)" -)] -fn prepare_assertions_with_overlap() { - let assertions = vec![ - Assertion::single(0, 9, BaseElement::new(5)), - Assertion::periodic(0, 1, 8, BaseElement::new(7)), - ]; - let trace_info = TraceInfo::new(2, 16); - let _ = super::prepare_assertions(assertions.clone(), &trace_info); -} - -#[test] -#[should_panic( - expected = "assertion (register=0, step=16, value=5) is invalid: expected trace length to be at least 32, but was 16" -)] -fn prepare_assertions_with_invalid_trace_length() { - let assertions = vec![Assertion::single(0, 16, BaseElement::new(5))]; - let trace_info = TraceInfo::new(2, 16); - let _ = super::prepare_assertions(assertions.clone(), &trace_info); -} - -#[test] -#[should_panic( - expected = "assertion (register=3, step=17, value=5) is invalid: expected trace width to be at least 3, but was 2" -)] -fn prepare_assertions_with_invalid_trace_width() { - let assertions = vec![Assertion::single(3, 17, BaseElement::new(5))]; - let trace_info = TraceInfo::new(2, 16); - let _ = super::prepare_assertions(assertions.clone(), &trace_info); -} - // MOCK AIR // ================================================================================================ @@ -308,7 +232,7 @@ impl MockAir { trace_length: usize, ) -> Self { let mut result = Self::new( - TraceInfo::new(4, trace_length), + TraceInfo::with_meta(4, trace_length, vec![1]), (), ProofOptions::new( 32, @@ -326,7 +250,7 @@ impl MockAir { pub fn with_assertions(assertions: Vec>, trace_length: usize) -> Self { let mut result = Self::new( - TraceInfo::new(4, trace_length), + TraceInfo::with_meta(4, trace_length, vec![assertions.len() as u8]), (), ProofOptions::new( 32, @@ -348,7 +272,8 @@ impl Air for MockAir { type PublicInputs = (); fn new(trace_info: TraceInfo, _pub_inputs: (), _options: ProofOptions) -> Self { - let context = build_context(trace_info.length(), trace_info.width()); + let num_assertions = trace_info.meta()[0] as usize; + let context = build_context(trace_info.length(), trace_info.width(), num_assertions); MockAir { context, assertions: Vec::new(), @@ -380,7 +305,11 @@ impl Air for MockAir { // UTILITY FUNCTIONS // ================================================================================================ -pub fn build_context(trace_length: usize, trace_width: usize) -> AirContext { +pub fn build_context( + trace_length: usize, + trace_width: usize, + num_assertions: usize, +) -> AirContext { let options = ProofOptions::new( 32, 8, @@ -392,7 +321,7 @@ pub fn build_context(trace_length: usize, trace_width: usize) -> ); let t_degrees = vec![TransitionConstraintDegree::new(2)]; let trace_info = TraceInfo::new(trace_width, trace_length); - AirContext::new(trace_info, t_degrees, options) + AirContext::new(trace_info, t_degrees, num_assertions, options) } pub fn build_prng() -> RandomCoin> { diff --git a/air/src/air/trace_info.rs b/air/src/air/trace_info.rs index 52275a420..ea1f70af1 100644 --- a/air/src/air/trace_info.rs +++ b/air/src/air/trace_info.rs @@ -129,6 +129,11 @@ impl TraceInfo { pub fn meta(&self) -> &[u8] { &self.meta } + + /// Returns true if an execution trace contains more than one segment. + pub fn is_multi_segment(&self) -> bool { + self.layout.num_aux_segments > 0 + } } // TRACE LAYOUT diff --git a/air/src/air/transition/degree.rs b/air/src/air/transition/degree.rs index d558f66a4..ed00c8d61 100644 --- a/air/src/air/transition/degree.rs +++ b/air/src/air/transition/degree.rs @@ -11,9 +11,9 @@ use core::cmp; /// Degree descriptor of a transition constraint. /// /// Describes constraint degree as a combination of multiplications of periodic and trace -/// registers. For example, degree of a constraint which requires multiplication of two trace -/// registers can be described as: `base: 2, cycles: []`. A constraint which requires -/// multiplication of 3 trace registers and a periodic register with a period of 32 steps can be +/// columns. For example, degree of a constraint which requires multiplication of two trace +/// columns can be described as: `base: 2, cycles: []`. A constraint which requires +/// multiplication of 3 trace columns and a periodic column with a period of 32 steps can be /// described as: `base: 3, cycles: [32]`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct TransitionConstraintDegree { @@ -23,10 +23,10 @@ pub struct TransitionConstraintDegree { impl TransitionConstraintDegree { /// Creates a new transition constraint degree descriptor for constraints which involve - /// multiplications of trace registers only. + /// multiplications of trace columns only. /// - /// For example, if a constraint involves multiplication of two trace registers, `degree` - /// should be set to 2. If a constraint involves multiplication of three trace registers, + /// For example, if a constraint involves multiplication of two trace columns, `degree` + /// should be set to 2. If a constraint involves multiplication of three trace columns, /// `degree` should be set to 3 etc. /// /// # Panics @@ -43,9 +43,9 @@ impl TransitionConstraintDegree { } /// Creates a new transition degree descriptor for constraints which involve multiplication - /// of trace registers and periodic columns. + /// of trace columns and periodic columns. /// - /// For example, if a constraint involves multiplication of two trace registers and one + /// For example, if a constraint involves multiplication of two trace columns and one /// periodic column with a period length of 32 steps, `base_degree` should be set to 2, /// and `cycles` should be set to `vec![32]`. /// @@ -93,7 +93,7 @@ impl TransitionConstraintDegree { /// descriptor. /// /// Thus, evaluation degree of a transition constraint which involves multiplication of two - /// trace registers and one periodic column with a period length of 32 steps when evaluated + /// trace columns and one periodic column with a period length of 32 steps when evaluated /// over an execution trace of 64 steps would be: /// /// $$ diff --git a/air/src/air/transition/frame.rs b/air/src/air/transition/frame.rs index b2e79c53c..03a32dd33 100644 --- a/air/src/air/transition/frame.rs +++ b/air/src/air/transition/frame.rs @@ -22,18 +22,18 @@ impl EvaluationFrame { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new evaluation frame instantiated with the specified number of registers. + /// Returns a new evaluation frame instantiated with the specified number of columns. /// /// # Panics - /// Panics if `num_registers` is zero. - pub fn new(num_registers: usize) -> Self { + /// Panics if `num_columns` is zero. + pub fn new(num_columns: usize) -> Self { assert!( - num_registers > 0, - "number of registers must be greater than zero" + num_columns > 0, + "number of columns must be greater than zero" ); EvaluationFrame { - current: E::zeroed_vector(num_registers), - next: E::zeroed_vector(num_registers), + current: E::zeroed_vector(num_columns), + next: E::zeroed_vector(num_columns), } } diff --git a/air/src/errors.rs b/air/src/errors.rs index af924bd61..399d08f40 100644 --- a/air/src/errors.rs +++ b/air/src/errors.rs @@ -11,7 +11,7 @@ use core::fmt; #[derive(Debug, PartialEq)] pub enum AssertionError { /// This error occurs when an assertion is evaluated against an execution trace which does not - /// contain a register specified by the assertion. + /// contain a column specified by the assertion. TraceWidthTooShort(usize, usize), /// This error occurs when an assertion is evaluated against an execution trace with length /// which is not a power of two. diff --git a/examples/README.md b/examples/README.md index ea7f7b84b..62f48cb02 100644 --- a/examples/README.md +++ b/examples/README.md @@ -35,12 +35,12 @@ Available examples are described below. ### Fibonacci sequence There are several examples illustrating how to generate (and verify) proofs for computing an n-th term of the [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_number). The examples illustrate different ways of describing this simple computation using AIR. The examples are: -* `fib` - computes the n-th term of a Fibonacci sequence using trace table with 2 registers. Each step in the trace table advances Fibonacci sequence by 2 terms. -* `fib8` - also computes the n-th term of a Fibonacci sequence and also uses trace table with 2 registers. But unlike the previous example, each step in the trace table advances Fibonacci sequence by 8 terms. -* `mulfib` - a variation on Fibonacci sequence where addition is replaced with multiplication. The example uses a trace table with 2 registers, and each step in the trace table advances the sequence by 2 terms. -* `mulfib8` - also computes the n-th term of the multiplicative Fibonacci sequence, but unlike the previous example, each step in the trace table advances the sequence by 8 terms. Unlike `fib8` example, this example uses a trace table with 8 registers. +* `fib` - computes the n-th term of a Fibonacci sequence using trace table with 2 columns. Each step in the trace table advances Fibonacci sequence by 2 terms. +* `fib8` - also computes the n-th term of a Fibonacci sequence and also uses trace table with 2 columns. But unlike the previous example, each step in the trace table advances Fibonacci sequence by 8 terms. +* `mulfib` - a variation on Fibonacci sequence where addition is replaced with multiplication. The example uses a trace table with 2 columns, and each step in the trace table advances the sequence by 2 terms. +* `mulfib8` - also computes the n-th term of the multiplicative Fibonacci sequence, but unlike the previous example, each step in the trace table advances the sequence by 8 terms. Unlike `fib8` example, this example uses a trace table with 8 columns. -It is interesting to note that `fib`/`fib8` and `mulfib`/`mulfib8` examples encode identical computations but these different encodings have significant impact on performance. Specifically, proving time for `fib8` example is 4x times faster than for `fib` example, while proving time for `mulfib8` example is about 2.4x times faster than for `mulfib` example. The difference stems from the fact that when we deal with additions only, we can omit intermediate states from the execution trace. But when multiplications are involved, we need to introduce additional registers to record intermediate results (another option would be to increase constraint degree, but this is not covered here). +It is interesting to note that `fib`/`fib8` and `mulfib`/`mulfib8` examples encode identical computations but these different encodings have significant impact on performance. Specifically, proving time for `fib8` example is 4x times faster than for `fib` example, while proving time for `mulfib8` example is about 2.4x times faster than for `mulfib` example. The difference stems from the fact that when we deal with additions only, we can omit intermediate states from the execution trace. But when multiplications are involved, we need to introduce additional columns to record intermediate results (another option would be to increase constraint degree, but this is not covered here). Additionally, proof sizes for `fib8` and `mulfib8` are about 15% smaller than their "uncompressed" counterparts. diff --git a/examples/src/fibonacci/fib2/air.rs b/examples/src/fibonacci/fib2/air.rs index 1ec20a158..dfa7f6d46 100644 --- a/examples/src/fibonacci/fib2/air.rs +++ b/examples/src/fibonacci/fib2/air.rs @@ -30,7 +30,7 @@ impl Air for FibAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); FibAir { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, 3, options), result: pub_inputs, } } diff --git a/examples/src/fibonacci/fib8/air.rs b/examples/src/fibonacci/fib8/air.rs index b195dcb6a..d0b549524 100644 --- a/examples/src/fibonacci/fib8/air.rs +++ b/examples/src/fibonacci/fib8/air.rs @@ -31,7 +31,7 @@ impl Air for Fib8Air { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); Fib8Air { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, 3, options), result: pub_inputs, } } diff --git a/examples/src/fibonacci/mulfib2/air.rs b/examples/src/fibonacci/mulfib2/air.rs index 8f66639b5..c7eb16899 100644 --- a/examples/src/fibonacci/mulfib2/air.rs +++ b/examples/src/fibonacci/mulfib2/air.rs @@ -33,7 +33,7 @@ impl Air for MulFib2Air { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); MulFib2Air { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, 3, options), result: pub_inputs, } } diff --git a/examples/src/fibonacci/mulfib8/air.rs b/examples/src/fibonacci/mulfib8/air.rs index e543e106c..4d16add1b 100644 --- a/examples/src/fibonacci/mulfib8/air.rs +++ b/examples/src/fibonacci/mulfib8/air.rs @@ -39,7 +39,7 @@ impl Air for MulFib8Air { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); MulFib8Air { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, 3, options), result: pub_inputs, } } diff --git a/examples/src/lamport/aggregate/air.rs b/examples/src/lamport/aggregate/air.rs index 5077e4807..8888df4a1 100644 --- a/examples/src/lamport/aggregate/air.rs +++ b/examples/src/lamport/aggregate/air.rs @@ -82,7 +82,7 @@ impl Air for LamportAggregateAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); LamportAggregateAir { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, 22, options), pub_keys: pub_inputs.pub_keys, messages: pub_inputs.messages, } diff --git a/examples/src/lamport/threshold/air.rs b/examples/src/lamport/threshold/air.rs index ab16e3e7c..af2fab470 100644 --- a/examples/src/lamport/threshold/air.rs +++ b/examples/src/lamport/threshold/air.rs @@ -95,7 +95,7 @@ impl Air for LamportThresholdAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); LamportThresholdAir { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, 26, options), pub_key_root: pub_inputs.pub_key_root, num_pub_keys: pub_inputs.num_pub_keys, num_signatures: pub_inputs.num_signatures, diff --git a/examples/src/merkle/air.rs b/examples/src/merkle/air.rs index 119cbbda4..8cdd74cd9 100644 --- a/examples/src/merkle/air.rs +++ b/examples/src/merkle/air.rs @@ -46,7 +46,7 @@ impl Air for MerkleAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); MerkleAir { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, 4, options), tree_root: pub_inputs.tree_root, } } diff --git a/examples/src/rescue/air.rs b/examples/src/rescue/air.rs index f02f46e75..c21208364 100644 --- a/examples/src/rescue/air.rs +++ b/examples/src/rescue/air.rs @@ -69,7 +69,7 @@ impl Air for RescueAir { ]; assert_eq!(TRACE_WIDTH, trace_info.width()); RescueAir { - context: AirContext::new(trace_info, degrees, options), + context: AirContext::new(trace_info, degrees, 4, options), seed: pub_inputs.seed, result: pub_inputs.result, } diff --git a/prover/src/composer/mod.rs b/prover/src/composer/mod.rs index 269d752d1..e49861e54 100644 --- a/prover/src/composer/mod.rs +++ b/prover/src/composer/mod.rs @@ -58,7 +58,7 @@ impl> DeepCompositionPoly> DeepCompositionPoly> BoundaryConstraintGroup>( - group: air::BoundaryConstraintGroup, + group: &air::BoundaryConstraintGroup, air: &A, twiddle_map: &mut BTreeMap>, ) -> BoundaryConstraintGroup { @@ -44,13 +44,13 @@ impl> BoundaryConstraintGroup> BoundaryConstraintGroup> BoundaryConstraintGroup> { - register: usize, + column: usize, value: B, coefficients: (E, E), } impl> SingleValueConstraint { pub fn evaluate(&self, state: &[B], xp: E) -> E { - let evaluation = E::from(state[self.register] - self.value); + let evaluation = E::from(state[self.column] - self.value); evaluation * (self.coefficients.0 + self.coefficients.1 * xp) } } @@ -128,7 +128,7 @@ impl> SingleValueConstraint /// polynomial describing a set of asserted values. This specialization is useful when the // degree of c(x) is relatively small, and thus, is cheap to evaluate on the fly. struct SmallPolyConstraint> { - register: usize, + column: usize, poly: Vec, x_offset: B, coefficients: (E, E), @@ -139,7 +139,7 @@ impl> SmallPolyConstraint { let x = x * self.x_offset; // evaluate constraint polynomial as x * offset let assertion_value = polynom::eval(&self.poly, x); - let evaluation = E::from(state[self.register] - assertion_value); + let evaluation = E::from(state[self.column] - assertion_value); evaluation * (self.coefficients.0 + self.coefficients.1 * xp) } } @@ -148,7 +148,7 @@ impl> SmallPolyConstraint { /// polynomial. In such cases, we pre-compute evaluations of c(x) by evaluating it over the /// entire constraint evaluation domain (using FFT). struct LargePolyConstraint> { - register: usize, + column: usize, values: Vec, step_offset: usize, coefficients: (E, E), @@ -168,7 +168,7 @@ impl> LargePolyConstraint { } else { ce_step }; - let evaluation = E::from(state[self.register] - self.values[value_index]); + let evaluation = E::from(state[self.column] - self.values[value_index]); evaluation * (self.coefficients.0 + self.coefficients.1 * xp) } } diff --git a/prover/src/constraints/evaluator.rs b/prover/src/constraints/evaluator.rs index 22ae7237e..8eee664b6 100644 --- a/prover/src/constraints/evaluator.rs +++ b/prover/src/constraints/evaluator.rs @@ -70,7 +70,8 @@ impl<'a, A: Air, E: FieldElement> ConstraintEvaluator< let mut twiddle_map = BTreeMap::new(); let boundary_constraints = air .get_boundary_constraints(&coefficients.boundary) - .into_iter() + .main_constraints() + .iter() .map(|group| { divisors.push(group.divisor().clone()); BoundaryConstraintGroup::new(group, air, &mut twiddle_map) diff --git a/prover/src/tests/mod.rs b/prover/src/tests/mod.rs index 5ea58b6d4..d9c64fd3f 100644 --- a/prover/src/tests/mod.rs +++ b/prover/src/tests/mod.rs @@ -99,7 +99,7 @@ impl Air for MockAir { type PublicInputs = (); fn new(trace_info: TraceInfo, _pub_inputs: (), _options: ProofOptions) -> Self { - let context = build_context(trace_info, 8); + let context = build_context(trace_info, 8, 1); MockAir { context, assertions: Vec::new(), @@ -131,7 +131,11 @@ impl Air for MockAir { // HELPER FUNCTIONS // ================================================================================================ -fn build_context(trace_info: TraceInfo, blowup_factor: usize) -> AirContext { +fn build_context( + trace_info: TraceInfo, + blowup_factor: usize, + num_assertions: usize, +) -> AirContext { let options = ProofOptions::new( 32, blowup_factor, @@ -142,5 +146,5 @@ fn build_context(trace_info: TraceInfo, blowup_factor: usize) -> 256, ); let t_degrees = vec![TransitionConstraintDegree::new(2)]; - AirContext::new(trace_info, t_degrees, options) + AirContext::new(trace_info, t_degrees, num_assertions, options) } diff --git a/prover/src/trace/mod.rs b/prover/src/trace/mod.rs index bd106dfd1..ecaf7f41f 100644 --- a/prover/src/trace/mod.rs +++ b/prover/src/trace/mod.rs @@ -28,7 +28,7 @@ mod tests; /// /// Execution trace can be reduced to a two-dimensional matrix in which each row represents the /// state of a computation at a single point in time and each column corresponds to an algebraic -/// register tracked over all steps of the computation. +/// column tracked over all steps of the computation. /// /// Building a trace is required for STARK proof generation. An execution trace of a specific /// instance of a computation must be supplied to [Prover::prove()](super::Prover::prove) method @@ -109,9 +109,9 @@ pub trait Trace: Sized { for assertion in air.get_assertions() { assertion.apply(self.length(), |step, value| { assert!( - value == self.get(assertion.register(), step), + value == self.get(assertion.column(), step), "trace does not satisfy assertion trace({}, {}) == {}", - assertion.register(), + assertion.column(), step, value ); diff --git a/prover/src/trace/poly_table.rs b/prover/src/trace/poly_table.rs index d56b28888..1fbffb886 100644 --- a/prover/src/trace/poly_table.rs +++ b/prover/src/trace/poly_table.rs @@ -70,7 +70,7 @@ impl> TracePolyTable { } /// Returns an out-of-domain evaluation frame constructed by evaluating trace polynomials - /// for all registers at points z and z * g, where g is the generator of the trace domain. + /// 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) -> EvaluationFrame { let g = E::from(B::get_root_of_unity(log2(self.poly_size()))); EvaluationFrame::from_rows(self.evaluate_at(z), self.evaluate_at(z * g)) diff --git a/prover/src/trace/tests.rs b/prover/src/trace/tests.rs index ee485f041..7b1f2b22a 100644 --- a/prover/src/trace/tests.rs +++ b/prover/src/trace/tests.rs @@ -79,7 +79,7 @@ fn extend_trace_table() { polynom::eval_many(trace_polys.get_main_trace_poly(1), &trace_domain) ); - // make sure register values are consistent with trace polynomials + // make sure column values are consistent with trace polynomials let lde_domain = build_lde_domain(domain.lde_domain_size()); assert_eq!( trace_polys.get_main_trace_poly(0), diff --git a/prover/src/trace/trace_table.rs b/prover/src/trace/trace_table.rs index 04e6a248e..47298392d 100644 --- a/prover/src/trace/trace_table.rs +++ b/prover/src/trace/trace_table.rs @@ -98,7 +98,7 @@ impl TraceTable { pub fn with_meta(width: usize, length: usize, meta: Vec) -> Self { assert!( width > 0, - "execution trace must consist of at least one register" + "execution trace must consist of at least one column" ); assert!( width <= TraceInfo::MAX_TRACE_WIDTH, @@ -350,8 +350,8 @@ impl Trace for TraceTable { &self.meta } - fn get(&self, register: usize, step: usize) -> B { - self.trace.get(register, step) + fn get(&self, column: usize, step: usize) -> B { + self.trace.get(column, step) } fn read_row_into(&self, step: usize, target: &mut [B]) { diff --git a/verifier/src/composer.rs b/verifier/src/composer.rs index 992d65693..fa14bcbc7 100644 --- a/verifier/src/composer.rs +++ b/verifier/src/composer.rs @@ -43,10 +43,10 @@ impl> DeepComposer { } } - /// For each queried trace state, combines register values into a single value by computing + /// For each queried trace state, combines column values into a single value by computing /// their random linear combinations as follows: /// - /// - Assume each register value is an evaluation of a trace polynomial T_i(x). + /// - Assume each column value is an evaluation of a trace polynomial T_i(x). /// - For each T_i(x) compute T'_i(x) = (T_i(x) - T_i(z)) / (x - z) and /// T''_i = (T_i(x) - T_i(z * g)) / (x - z * g), where z is the out-of-domain point and /// g is the generation of the LDE domain. @@ -60,7 +60,7 @@ impl> DeepComposer { /// /// Note that values of T_i(z) and T_i(z * g) are received from teh prover and passed into /// this function via the `ood_frame` parameter. - pub fn compose_registers( + pub fn compose_columns( &self, queried_trace_states: Vec>, ood_frame: EvaluationFrame, @@ -73,10 +73,10 @@ impl> DeepComposer { let conjugate_values = get_conjugate_values(self.field_extension, trace_at_z1, self.z); let mut result = Vec::with_capacity(queried_trace_states.len()); - for (registers, &x) in queried_trace_states.iter().zip(&self.x_coordinates) { + for (columns, &x) in queried_trace_states.iter().zip(&self.x_coordinates) { let x = E::from(x); let mut composition = E::ZERO; - for (i, &value) in registers.iter().enumerate() { + for (i, &value) in columns.iter().enumerate() { let value = E::from(value); // compute T'_i(x) = (T_i(x) - T_i(z)) / (x - z) let t1 = (value - trace_at_z1[i]) / (x - self.z); diff --git a/verifier/src/evaluator.rs b/verifier/src/evaluator.rs index 95cb60fb0..4150843ea 100644 --- a/verifier/src/evaluator.rs +++ b/verifier/src/evaluator.rs @@ -59,10 +59,10 @@ pub fn evaluate_constraints>( // constraints in each group and add them to the evaluations vector // cache power of x here so that we only re-compute it when degree_adjustment changes - let mut degree_adjustment = b_constraints[0].degree_adjustment(); + let mut degree_adjustment = b_constraints.main_constraints()[0].degree_adjustment(); let mut xp = x.exp(degree_adjustment.into()); - for group in b_constraints.iter() { + for group in b_constraints.main_constraints().iter() { // if adjustment degree hasn't changed, no need to recompute `xp` - so just reuse the // previous value; otherwise, compute new `xp` if group.degree_adjustment() != degree_adjustment { diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index e27ab923e..e484b1b56 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -296,7 +296,7 @@ where // 6 ----- DEEP composition ------------------------------------------------------------------- // compute evaluations of the DEEP composition polynomial at the queried positions let composer = DeepComposer::new(&air, &query_positions, z, deep_coefficients); - let t_composition = composer.compose_registers(queried_trace_states, ood_frame); + let t_composition = composer.compose_columns(queried_trace_states, ood_frame); let c_composition = composer.compose_constraints(queried_evaluations, ood_evaluations); let deep_evaluations = composer.combine_compositions(t_composition, c_composition); diff --git a/winterfell/src/lib.rs b/winterfell/src/lib.rs index 4e8db8bc1..be8bd5e72 100644 --- a/winterfell/src/lib.rs +++ b/winterfell/src/lib.rs @@ -195,11 +195,16 @@ //! // is defined in the evaluate_transition() method below, but here we need to specify //! // the expected degree of the constraint. If the expected and actual degrees of the //! // constraints don't match, an error will be thrown in the debug mode, but in release -//! // mode, an invalid proof will be generated which will not be accepted by any -//! // verifier. +//! // mode, an invalid proof will be generated which will not be accepted by any verifier. //! let degrees = vec![TransitionConstraintDegree::new(3)]; +//! +//! // We also need to specify the exact number of assertions we will place against the +//! // execution trace. This number must be the same as the number of items in a vector +//! // returned from the get_assertions() method below. +//! let num_assertions = 2; +//! //! WorkAir { -//! context: AirContext::new(trace_info, degrees, options), +//! context: AirContext::new(trace_info, degrees, num_assertions, options), //! start: pub_inputs.start, //! result: pub_inputs.result, //! } @@ -286,7 +291,7 @@ //! # assert_eq!(1, trace_info.width()); //! # let degrees = vec![TransitionConstraintDegree::new(3)]; //! # WorkAir { -//! # context: AirContext::new(trace_info, degrees, options), +//! # context: AirContext::new(trace_info, degrees, 2, options), //! # start: pub_inputs.start, //! # result: pub_inputs.result, //! # } @@ -407,7 +412,7 @@ //! # assert_eq!(1, trace_info.width()); //! # let degrees = vec![TransitionConstraintDegree::new(3)]; //! # WorkAir { -//! # context: AirContext::new(trace_info, degrees, options), +//! # context: AirContext::new(trace_info, degrees, 2, options), //! # start: pub_inputs.start, //! # result: pub_inputs.result, //! # }