diff --git a/circuits/src/bitshift/stark.rs b/circuits/src/bitshift/stark.rs index 733abd1ef..0cc13bbc4 100644 --- a/circuits/src/bitshift/stark.rs +++ b/circuits/src/bitshift/stark.rs @@ -1,108 +1,69 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::BitshiftView; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::unstark::NoColumns; /// Bitshift Trace Constraints #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct BitshiftStark { - pub _f: PhantomData, -} - -impl HasNamedColumns for BitshiftStark { - type Columns = BitshiftView; -} +pub struct BitshiftConstraints {} const COLUMNS: usize = BitshiftView::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values.executed; - let nv = vars.next_values.executed; - let mut constraints = ConstraintBuilder::default(); - - // Constraints on shift amount - // They ensure: - // 1. Shift amount increases with each row by 0 or 1. - // (We allow increases of 0 in order to allow the table to add - // multiple same value rows. This is needed when we have multiple - // `SHL` or `SHR` operations with the same shift amount.) - // 2. We have shift amounts starting from 0 to max possible value of 31. - // (This is due to RISC-V max shift amount being 31.) - - let diff = nv.amount - lv.amount; - // Check: initial amount value is set to 0 - constraints.first_row(lv.amount); - // Check: amount value is increased by 1 or kept unchanged - constraints.transition(diff * (diff - 1)); - // Check: last amount value is set to 31 - constraints.last_row(lv.amount - 31); - - // Constraints on multiplier - // They ensure: - // 1. Shift multiplier is multiplied by 2 only if amount increases. - // 2. We have shift multiplier from 1 to max possible value of 2^31. - - // Check: initial multiplier value is set to 1 = 2^0 - constraints.first_row(lv.multiplier - 1); - // Check: multiplier value is doubled if amount is increased - constraints.transition(nv.multiplier - (1 + diff) * lv.multiplier); - // Check: last multiplier value is set to 2^31 - // (Note that based on the previous constraint, this is already - // satisfied if the last amount value is 31. We leave it for readability.) - constraints.last_row(lv.multiplier - (1 << 31)); - - constraints -} - -impl, const D: usize> Stark for BitshiftStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - constraint_consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let expr_builder = ExprBuilder::default(); - let constraints = generate_constraints(&expr_builder.to_typed_starkframe(vars)); - build_packed(constraints, constraint_consumer); - } +#[allow(clippy::module_name_repetitions)] +pub type BitshiftStark = + StarkFrom; - fn constraint_degree(&self) -> usize { 3 } +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for BitshiftConstraints { + type PublicInputs = NoColumns; + type View = BitshiftView; - fn eval_ext_circuit( + fn generate_constraints<'a, T: Debug + Copy>( &self, - circuit_builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - constraint_consumer: &mut RecursiveConstraintConsumer, - ) { - let expr_builder = ExprBuilder::default(); - let constraints = generate_constraints(&expr_builder.to_typed_starkframe(vars)); - build_ext(constraints, circuit_builder, constraint_consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values.executed; + let nv = vars.next_values.executed; + let mut constraints = ConstraintBuilder::default(); + + // Constraints on shift amount + // They ensure: + // 1. Shift amount increases with each row by 0 or 1. + // (We allow increases of 0 in order to allow the table to add + // multiple same value rows. This is needed when we have multiple + // `SHL` or `SHR` operations with the same shift amount.) + // 2. We have shift amounts starting from 0 to max possible value of 31. + // (This is due to RISC-V max shift amount being 31.) + + let diff = nv.amount - lv.amount; + // Check: initial amount value is set to 0 + constraints.first_row(lv.amount); + // Check: amount value is increased by 1 or kept unchanged + constraints.transition(diff * (diff - 1)); + // Check: last amount value is set to 31 + constraints.last_row(lv.amount - 31); + + // Constraints on multiplier + // They ensure: + // 1. Shift multiplier is multiplied by 2 only if amount increases. + // 2. We have shift multiplier from 1 to max possible value of 2^31. + + // Check: initial multiplier value is set to 1 = 2^0 + constraints.first_row(lv.multiplier - 1); + // Check: multiplier value is doubled if amount is increased + constraints.transition(nv.multiplier - (1 + diff) * lv.multiplier); + // Check: last multiplier value is set to 2^31 + // (Note that based on the previous constraint, this is already + // satisfied if the last amount value is 31. We leave it for readability.) + constraints.last_row(lv.multiplier - (1 << 31)); + + constraints } } diff --git a/circuits/src/columns_view.rs b/circuits/src/columns_view.rs index b2daf2eef..01a2ae961 100644 --- a/circuits/src/columns_view.rs +++ b/circuits/src/columns_view.rs @@ -27,10 +27,6 @@ pub(crate) const unsafe fn transmute_ref(t: &T) -> &U { &*(std::ptr::from_ref::(t).cast::()) } -pub trait HasNamedColumns { - type Columns; -} - pub trait NumberOfColumns { const NUMBER_OF_COLUMNS: usize; } diff --git a/circuits/src/cpu/stark.rs b/circuits/src/cpu/stark.rs index d34744c04..64c54b1d9 100644 --- a/circuits/src/cpu/stark.rs +++ b/circuits/src/cpu/stark.rs @@ -1,35 +1,25 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::{CpuState, OpSelectors}; use super::{bitwise, branches, div, ecall, jalr, memory, mul, signed_comparison, sub}; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; +use crate::columns_view::NumberOfColumns; use crate::cpu::shift; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::unstark::NoColumns; +// TODO: fix StarkNameDisplay? +#[derive(Copy, Clone, Default, StarkNameDisplay)] +#[allow(clippy::module_name_repetitions)] +pub struct CpuConstraints {} + /// A Gadget for CPU Instructions /// /// Instructions are either handled directly or through cross table lookup -#[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct CpuStark { - pub _f: PhantomData, -} - -impl HasNamedColumns for CpuStark { - type Columns = CpuState; -} +pub type CpuStark = StarkFrom; /// Ensure that if opcode is straight line, then program counter is incremented /// by 4. @@ -78,69 +68,40 @@ fn populate_op2_value<'a, P: Copy>( const COLUMNS: usize = CpuState::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = &vars.local_values; - let mut constraints = ConstraintBuilder::default(); - - pc_ticks_up(lv, &mut constraints); - - binary_selectors(&lv.inst.ops, &mut constraints); - - // Registers - populate_op2_value(lv, &mut constraints); - - // ADD is now handled by its own table. - constraints.always(lv.inst.ops.add); - sub::constraints(lv, &mut constraints); - bitwise::constraints(lv, &mut constraints); - branches::comparison_constraints(lv, &mut constraints); - branches::constraints(lv, &mut constraints); - memory::constraints(lv, &mut constraints); - signed_comparison::signed_constraints(lv, &mut constraints); - signed_comparison::slt_constraints(lv, &mut constraints); - shift::constraints(lv, &mut constraints); - div::constraints(lv, &mut constraints); - mul::constraints(lv, &mut constraints); - jalr::constraints(lv, &mut constraints); - ecall::constraints(lv, &mut constraints); - - constraints -} - -impl, const D: usize> Stark for CpuStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - constraint_consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let expr_builder = ExprBuilder::default(); - let constraints = generate_constraints(&expr_builder.to_typed_starkframe(vars)); - build_packed(constraints, constraint_consumer); - } - - fn constraint_degree(&self) -> usize { 3 } +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for CpuConstraints { + type PublicInputs = NoColumns; + type View = CpuState; - fn eval_ext_circuit( + fn generate_constraints<'a, T: Debug + Copy>( &self, - circuit_builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - constraint_consumer: &mut RecursiveConstraintConsumer, - ) { - let expr_builder = ExprBuilder::default(); - let constraints = generate_constraints(&expr_builder.to_typed_starkframe(vars)); - build_ext(constraints, circuit_builder, constraint_consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = &vars.local_values; + let mut constraints = ConstraintBuilder::default(); + + pc_ticks_up(lv, &mut constraints); + + binary_selectors(&lv.inst.ops, &mut constraints); + + // Registers + populate_op2_value(lv, &mut constraints); + + // ADD is now handled by its own table. + constraints.always(lv.inst.ops.add); + sub::constraints(lv, &mut constraints); + bitwise::constraints(lv, &mut constraints); + branches::comparison_constraints(lv, &mut constraints); + branches::constraints(lv, &mut constraints); + memory::constraints(lv, &mut constraints); + signed_comparison::signed_constraints(lv, &mut constraints); + signed_comparison::slt_constraints(lv, &mut constraints); + shift::constraints(lv, &mut constraints); + div::constraints(lv, &mut constraints); + mul::constraints(lv, &mut constraints); + jalr::constraints(lv, &mut constraints); + ecall::constraints(lv, &mut constraints); + + constraints } } diff --git a/circuits/src/cpu_skeleton/stark.rs b/circuits/src/cpu_skeleton/stark.rs index f172484a8..5d195fdf1 100644 --- a/circuits/src/cpu_skeleton/stark.rs +++ b/circuits/src/cpu_skeleton/stark.rs @@ -1,105 +1,66 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::CpuSkeleton; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::stark::mozak_stark::PublicInputs; +// TODO: fix StarkNameDisplay? #[derive(Clone, Copy, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct CpuSkeletonStark { - pub _f: PhantomData, -} +pub struct CpuSkeletonConstraints {} -impl HasNamedColumns for CpuSkeletonStark { - type Columns = CpuSkeleton; -} +#[allow(clippy::module_name_repetitions)] +pub type CpuSkeletonStark = + StarkFrom; const COLUMNS: usize = CpuSkeleton::<()>::NUMBER_OF_COLUMNS; // Public inputs: [PC of the first row] const PUBLIC_INPUTS: usize = PublicInputs::<()>::NUMBER_OF_COLUMNS; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, PublicInputs>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let nv = vars.next_values; - let public_inputs = vars.public_inputs; - let mut constraints = ConstraintBuilder::default(); - - constraints.first_row(lv.pc - public_inputs.entry_point); - // Clock starts at 2. This is to differentiate - // execution clocks (2 and above) from - // clk values `0` and `1` which are reserved for - // elf initialisation and zero initialisation respectively. - constraints.first_row(lv.clk - 2); - - let clock_diff = nv.clk - lv.clk; - constraints.transition(clock_diff.is_binary()); - - // clock only counts up when we are still running. - constraints.transition(clock_diff - lv.is_running); - - // We start in running state. - constraints.first_row(lv.is_running - 1); - - // We may transition to a non-running state. - constraints.transition(nv.is_running * (nv.is_running - lv.is_running)); - - // We end in a non-running state. - constraints.last_row(lv.is_running); +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for CpuSkeletonConstraints { + type PublicInputs = PublicInputs; + type View = CpuSkeleton; - // NOTE: in our old CPU table we had constraints that made sure nothing - // changes anymore, once we are halted. We don't need those - // anymore: the only thing that can change are memory or registers. And - // our CTLs make sure, that after we are halted, no more memory - // or register changes are allowed. - constraints -} - -impl, const D: usize> Stark for CpuSkeletonStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( + fn generate_constraints<'a, T: Debug + Copy>( &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let expr_builder = ExprBuilder::default(); - let vars = expr_builder.to_typed_starkframe(vars); - let constraints = generate_constraints(&vars); - build_packed(constraints, consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let nv = vars.next_values; + let public_inputs = vars.public_inputs; + let mut constraints = ConstraintBuilder::default(); + + constraints.first_row(lv.pc - public_inputs.entry_point); + // Clock starts at 2. This is to differentiate + // execution clocks (2 and above) from + // clk values `0` and `1` which are reserved for + // elf initialisation and zero initialisation respectively. + constraints.first_row(lv.clk - 2); + + let clock_diff = nv.clk - lv.clk; + constraints.transition(clock_diff.is_binary()); + + // clock only counts up when we are still running. + constraints.transition(clock_diff - lv.is_running); + + // We start in running state. + constraints.first_row(lv.is_running - 1); + + // We may transition to a non-running state. + constraints.transition(nv.is_running * (nv.is_running - lv.is_running)); + + // We end in a non-running state. + constraints.last_row(lv.is_running); + + // NOTE: in our old CPU table we had constraints that made sure nothing + // changes anymore, once we are halted. We don't need those + // anymore: the only thing that can change are memory or registers. And + // our CTLs make sure, that after we are halted, no more memory + // or register changes are allowed. + constraints } - - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); - } - - fn constraint_degree(&self) -> usize { 3 } } diff --git a/circuits/src/expr.rs b/circuits/src/expr.rs index 50f790f75..55ca8e4af 100644 --- a/circuits/src/expr.rs +++ b/circuits/src/expr.rs @@ -1,13 +1,18 @@ +use core::fmt::Debug; +use std::fmt::Display; +use std::marker::{Copy, PhantomData}; use std::panic::Location; pub use expr::PureEvaluator; -use expr::{BinOp, Cached, Evaluator, Expr, UnaOp}; +use expr::{BinOp, Cached, Evaluator, Expr, ExprBuilder, StarkFrameTyped, UnaOp}; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::packed::PackedField; use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::plonk::circuit_builder::CircuitBuilder; use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use starky::evaluation_frame::StarkFrame; +use starky::stark::Stark; struct CircuitBuilderEvaluator<'a, F, const D: usize> where @@ -69,9 +74,9 @@ where #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Constraint { - constraint_type: ConstraintType, - location: &'static Location<'static>, - term: E, + pub constraint_type: ConstraintType, + pub location: &'static Location<'static>, + pub term: E, } impl Constraint { @@ -87,7 +92,7 @@ impl Constraint { } #[derive(PartialEq, Eq, PartialOrd, Ord, Default, Debug)] -enum ConstraintType { +pub enum ConstraintType { FirstRow, #[default] Always, @@ -160,6 +165,22 @@ pub fn build_ext( } } +#[must_use] +pub fn build_debug( + cb: ConstraintBuilder>, +) -> Vec> +where + F: RichField, + F: Extendable, + FE: FieldExtension, + P: PackedField, { + let mut evaluator = Cached::from(packed_field_evaluator()); + cb.constraints + .into_iter() + .map(|c| c.map(|constraint| evaluator.eval(constraint))) + .collect() +} + pub fn build_packed( cb: ConstraintBuilder>, yield_constr: &mut ConstraintConsumer

, @@ -184,3 +205,88 @@ pub fn build_packed( })(yield_constr, c.term); } } + +/// Convenience alias to `G::PublicInputs`. +pub type PublicInputsOf = + >::PublicInputs; + +/// Convenience alias to `G::View`. +pub type ViewOf = + >::View; + +/// Convenience alias to `StarkFrameTyped` that will be defined over +/// `G::View>` and `G::PublicInputs>` +pub type Vars<'a, G, T, const COLUMNS: usize, const PUBLIC_INPUTS: usize> = StarkFrameTyped< + ViewOf, COLUMNS, PUBLIC_INPUTS>, + PublicInputsOf, COLUMNS, PUBLIC_INPUTS>, +>; + +/// Trait for generating constraints independently from the type of the field +/// and independently from the API of the proving system. +pub trait GenerateConstraints { + type View: From<[E; COLUMNS]> + FromIterator; + type PublicInputs: From<[E; PUBLIC_INPUTS]> + FromIterator; + + fn generate_constraints<'a, T: Copy + Debug>( + &self, + _vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + ConstraintBuilder::default() + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct StarkFrom { + pub witness: G, + pub _f: PhantomData, +} + +impl Display + for StarkFrom +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.witness.fmt(f) } +} + +impl Stark + for StarkFrom +where + G: Sync + GenerateConstraints + Copy, + F: RichField + Extendable + Debug, +{ + type EvaluationFrame = StarkFrame + + where + FE: FieldExtension, + P: PackedField; + type EvaluationFrameTarget = + StarkFrame, ExtensionTarget, { COLUMNS }, { PUBLIC_INPUTS }>; + + fn eval_packed_generic( + &self, + vars: &Self::EvaluationFrame, + constraint_consumer: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField + Debug + Copy, { + let expr_builder = ExprBuilder::default(); + let constraints = self + .witness + .generate_constraints::<_>(&expr_builder.to_typed_starkframe(vars)); + build_packed(constraints, constraint_consumer); + } + + fn eval_ext_circuit( + &self, + circuit_builder: &mut CircuitBuilder, + vars: &Self::EvaluationFrameTarget, + constraint_consumer: &mut RecursiveConstraintConsumer, + ) { + let expr_builder = ExprBuilder::default(); + let constraints = self + .witness + .generate_constraints(&expr_builder.to_typed_starkframe(vars)); + build_ext(constraints, circuit_builder, constraint_consumer); + } + + fn constraint_degree(&self) -> usize { 3 } +} diff --git a/circuits/src/generation.rs b/circuits/src/generation.rs index 2f6a2ca5d..8883dbb85 100644 --- a/circuits/src/generation.rs +++ b/circuits/src/generation.rs @@ -4,6 +4,7 @@ use std::borrow::Borrow; use std::fmt::{Debug, Display}; +use expr::{ExprBuilder, StarkFrameTyped}; use itertools::{izip, Itertools}; use log::debug; use mozak_runner::elf::Program; @@ -14,14 +15,12 @@ use plonky2::field::polynomial::PolynomialValues; use plonky2::hash::hash_types::RichField; use plonky2::util::timing::TimingTree; use plonky2::util::transpose; -use starky::constraint_consumer::ConstraintConsumer; -use starky::evaluation_frame::StarkEvaluationFrame; use starky::stark::Stark; use crate::bitshift::generation::generate_shift_amount_trace; -use crate::columns_view::HasNamedColumns; use crate::cpu::generation::{generate_cpu_trace, generate_program_mult_trace}; use crate::cpu_skeleton::generation::generate_cpu_skeleton_trace; +use crate::expr::{build_debug, ConstraintType, GenerateConstraints, StarkFrom, Vars, ViewOf}; use crate::memory::generation::generate_memory_trace; use crate::memory_fullword::generation::generate_fullword_memory_trace; use crate::memory_halfword::generation::generate_halfword_memory_trace; @@ -209,36 +208,67 @@ pub fn debug_traces, const D: usize>( .build(); all_starks!(mozak_stark, |stark, kind| { - debug_single_trace::(stark, &traces_poly_values[kind], public_inputs[kind]); + debug_single_trace::(stark, &traces_poly_values[kind], public_inputs[kind]); }); } pub fn debug_single_trace< - F: RichField + Extendable + Debug, + 'a, + F: RichField + Extendable, const D: usize, - S: Stark + Display + HasNamedColumns, + G, + const COLUMNS: usize, + const PUBLIC_INPUTS: usize, >( - stark: &S, - trace_rows: &[PolynomialValues], - public_inputs: &[F], + stark: &'a StarkFrom, + trace_rows: &'a [PolynomialValues], + public_inputs: &'a [F], ) where - S::Columns: FromIterator + Debug, { - transpose_polys::(trace_rows.to_vec()) + G: Sync + Copy + Display + GenerateConstraints, + ViewOf: Debug, { + transpose_polys::>(trace_rows.to_vec()) .iter() .enumerate() .circular_tuple_windows() .for_each(|((lv_row, lv), (nv_row, nv))| { - let mut consumer = ConstraintConsumer::new_debug_api(lv_row == 0, nv_row == 0); - let vars = - StarkEvaluationFrame::from_values(lv.as_slice(), nv.as_slice(), public_inputs); - stark.eval_packed_generic(&vars, &mut consumer); - if consumer.debug_api_has_constraint_failed() { - let lv: S::Columns = lv.iter().copied().collect(); - let nv: S::Columns = nv.iter().copied().collect(); + let expr_builder = ExprBuilder::default(); + let frame: StarkFrameTyped, Vec> = + StarkFrameTyped::from_values(lv, nv, public_inputs); + let vars: Vars = expr_builder.inject_starkframe(frame); + let constraints = stark.witness.generate_constraints(&vars); + let evaluated = build_debug(constraints); + + // Filter out only applicable constraints + let is_first_row = lv_row == 0; + let is_last_row = nv_row == 0; + let applicable = evaluated.into_iter().filter(|c| match c.constraint_type { + ConstraintType::FirstRow => is_first_row, + ConstraintType::Always => true, + ConstraintType::Transition => !is_last_row, + ConstraintType::LastRow => is_last_row, + }); + + // Get failed constraints + let failed: Vec<_> = applicable.filter(|c| !c.term.is_zeros()).collect(); + + let any_failed = !failed.is_empty(); + + if any_failed { + for c in failed { + log::error!( + "debug_single_trace :: non-zero constraint at {} = {}", + c.location, + c.term + ); + } + + let lv: ViewOf = lv.iter().copied().collect(); + let nv: ViewOf = nv.iter().copied().collect(); log::error!("Debug constraints for {stark}"); log::error!("lv-row[{lv_row}] - values: {lv:?}"); log::error!("nv-row[{nv_row}] - values: {nv:?}"); } - assert!(!consumer.debug_api_has_constraint_failed()); + + assert!(!any_failed); }); } diff --git a/circuits/src/lib.rs b/circuits/src/lib.rs index db07c3cbc..8668a5541 100644 --- a/circuits/src/lib.rs +++ b/circuits/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::missing_panics_doc)] #![allow(clippy::missing_errors_doc)] #![feature(const_trait_impl)] +#![feature(generic_arg_infer)] pub mod bitshift; pub mod columns_view; diff --git a/circuits/src/memory/stark.rs b/circuits/src/memory/stark.rs index 88185f19a..e266ef33d 100644 --- a/circuits/src/memory/stark.rs +++ b/circuits/src/memory/stark.rs @@ -1,118 +1,79 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; - -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; + +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::memory::columns::Memory; use crate::unstark::NoColumns; #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct MemoryStark { - pub _f: PhantomData, -} +pub struct MemoryConstraints {} -impl HasNamedColumns for MemoryStark { - type Columns = Memory; -} +#[allow(clippy::module_name_repetitions)] +pub type MemoryStark = + StarkFrom; const COLUMNS: usize = Memory::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let nv = vars.next_values; - let mut constraints = ConstraintBuilder::default(); - - // Boolean constraints - // ------------------- - // Constrain certain columns of the memory table to be only - // boolean values. - constraints.always(lv.is_writable.is_binary()); - constraints.always(lv.is_store.is_binary()); - constraints.always(lv.is_load.is_binary()); - constraints.always(lv.is_init.is_binary()); - constraints.always(lv.is_executed().is_binary()); - - // Address constraints - // ------------------- - - // We start address at 0 and end at u32::MAX - // This saves rangechecking the addresses - // themselves, we only rangecheck their difference. - constraints.first_row(lv.addr - 0); - constraints.last_row(lv.addr - i64::from(u32::MAX)); - - // Address can only change for init in the new row... - constraints.always((1 - nv.is_init) * (nv.addr - lv.addr)); - // ... and we have a range-check to make sure that addresses go up for each - // init. - - // Operation constraints - // --------------------- - - // writeable only changes for init: - constraints.always((1 - nv.is_init) * (nv.is_writable - lv.is_writable)); - - // No `SB` operation can be seen if memory address is not marked `writable` - constraints.always((1 - lv.is_writable) * lv.is_store); - - // For all "load" operations, the value cannot change between rows - constraints.always(nv.is_load * (nv.value - lv.value)); - - // Padding constraints - // ------------------- - // Once we have padding, all subsequent rows are padding; ie not - // `is_executed`. - constraints.transition((lv.is_executed() - nv.is_executed()) * nv.is_executed()); - - constraints -} - -impl, const D: usize> Stark for MemoryStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } - - fn constraint_degree(&self) -> usize { 3 } +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for MemoryConstraints { + type PublicInputs = NoColumns; + type View = Memory; - fn eval_ext_circuit( + fn generate_constraints<'a, T: Debug + Copy>( &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let nv = vars.next_values; + let mut constraints = ConstraintBuilder::default(); + + // Boolean constraints + // ------------------- + // Constrain certain columns of the memory table to be only + // boolean values. + constraints.always(lv.is_writable.is_binary()); + constraints.always(lv.is_store.is_binary()); + constraints.always(lv.is_load.is_binary()); + constraints.always(lv.is_init.is_binary()); + constraints.always(lv.is_executed().is_binary()); + + // Address constraints + // ------------------- + + // We start address at 0 and end at u32::MAX + // This saves rangechecking the addresses + // themselves, we only rangecheck their difference. + constraints.first_row(lv.addr - 0); + constraints.last_row(lv.addr - i64::from(u32::MAX)); + + // Address can only change for init in the new row... + constraints.always((1 - nv.is_init) * (nv.addr - lv.addr)); + // ... and we have a range-check to make sure that addresses go up for each + // init. + + // Operation constraints + // --------------------- + + // writeable only changes for init: + constraints.always((1 - nv.is_init) * (nv.is_writable - lv.is_writable)); + + // No `SB` operation can be seen if memory address is not marked `writable` + constraints.always((1 - lv.is_writable) * lv.is_store); + + // For all "load" operations, the value cannot change between rows + constraints.always(nv.is_load * (nv.value - lv.value)); + + // Padding constraints + // ------------------- + // Once we have padding, all subsequent rows are padding; ie not + // `is_executed`. + constraints.transition((lv.is_executed() - nv.is_executed()) * nv.is_executed()); + + constraints } } diff --git a/circuits/src/memory_fullword/stark.rs b/circuits/src/memory_fullword/stark.rs index 2f76c68fa..858dbba24 100644 --- a/circuits/src/memory_fullword/stark.rs +++ b/circuits/src/memory_fullword/stark.rs @@ -1,91 +1,50 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use itertools::izip; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; - -use crate::columns_view::HasNamedColumns; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; + +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::memory_fullword::columns::{FullWordMemory, NUM_HW_MEM_COLS}; use crate::unstark::NoColumns; #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct FullWordMemoryStark { - pub _f: PhantomData, -} +pub struct FullWordMemoryConstraints {} -impl HasNamedColumns for FullWordMemoryStark { - type Columns = FullWordMemory; -} +#[allow(clippy::module_name_repetitions)] +pub type FullWordMemoryStark = + StarkFrom; const COLUMNS: usize = NUM_HW_MEM_COLS; const PUBLIC_INPUTS: usize = 0; -// Design description - https://docs.google.com/presentation/d/1J0BJd49BMQh3UR5TrOhe3k67plHxnohFtFVrMpDJ1oc/edit?usp=sharing -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let mut constraints = ConstraintBuilder::default(); - - constraints.always(lv.ops.is_store.is_binary()); - constraints.always(lv.ops.is_load.is_binary()); - constraints.always(lv.is_executed().is_binary()); - - // Check: the resulting sum is wrapped if necessary. - // As the result is range checked, this make the choice deterministic, - // even for a malicious prover. - for (i, addr) in izip!(0.., lv.addrs).skip(1) { - let target = lv.addrs[0] + i; - constraints.always(lv.is_executed() * (addr - target) * (addr + (1 << 32) - target)); - } - - constraints -} - -impl, const D: usize> Stark for FullWordMemoryStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for FullWordMemoryConstraints { + type PublicInputs = NoColumns; + type View = FullWordMemory; // Design description - https://docs.google.com/presentation/d/1J0BJd49BMQh3UR5TrOhe3k67plHxnohFtFVrMpDJ1oc/edit?usp=sharing - fn eval_packed_generic( + fn generate_constraints<'a, T: Debug + Copy>( &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let mut constraints = ConstraintBuilder::default(); + + constraints.always(lv.ops.is_store.is_binary()); + constraints.always(lv.ops.is_load.is_binary()); + constraints.always(lv.is_executed().is_binary()); + + // Check: the resulting sum is wrapped if necessary. + // As the result is range checked, this make the choice deterministic, + // even for a malicious prover. + for (i, addr) in izip!(0.., lv.addrs).skip(1) { + let target = lv.addrs[0] + i; + constraints.always(lv.is_executed() * (addr - target) * (addr + (1 << 32) - target)); + } - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); + constraints } - - fn constraint_degree(&self) -> usize { 3 } } #[cfg(test)] diff --git a/circuits/src/memory_halfword/stark.rs b/circuits/src/memory_halfword/stark.rs index 9917e1fa3..95310803f 100644 --- a/circuits/src/memory_halfword/stark.rs +++ b/circuits/src/memory_halfword/stark.rs @@ -1,89 +1,50 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; - -use crate::columns_view::HasNamedColumns; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; + +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::memory_halfword::columns::{HalfWordMemory, NUM_HW_MEM_COLS}; use crate::unstark::NoColumns; #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct HalfWordMemoryStark { - pub _f: PhantomData, -} +pub struct HalfWordMemoryConstraints {} -impl HasNamedColumns for HalfWordMemoryStark { - type Columns = HalfWordMemory; -} +#[allow(clippy::module_name_repetitions)] +pub type HalfWordMemoryStark = + StarkFrom; -// Design description - https://docs.google.com/presentation/d/1J0BJd49BMQh3UR5TrOhe3k67plHxnohFtFVrMpDJ1oc/edit?usp=sharing -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let mut constraints = ConstraintBuilder::default(); +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for HalfWordMemoryConstraints { + type PublicInputs = NoColumns; + type View = HalfWordMemory; - constraints.always(lv.ops.is_store.is_binary()); - constraints.always(lv.ops.is_load.is_binary()); - constraints.always(lv.is_executed().is_binary()); + // Design description - https://docs.google.com/presentation/d/1J0BJd49BMQh3UR5TrOhe3k67plHxnohFtFVrMpDJ1oc/edit?usp=sharing + fn generate_constraints<'a, T: Debug + Copy>( + &self, + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let mut constraints = ConstraintBuilder::default(); - let added = lv.addrs[0] + 1; - let wrapped = added - (1 << 32); + constraints.always(lv.ops.is_store.is_binary()); + constraints.always(lv.ops.is_load.is_binary()); + constraints.always(lv.is_executed().is_binary()); - // Check: the resulting sum is wrapped if necessary. - // As the result is range checked, this make the choice deterministic, - // even for a malicious prover. - constraints.always(lv.is_executed() * (lv.addrs[1] - added) * (lv.addrs[1] - wrapped)); + let added = lv.addrs[0] + 1; + let wrapped = added - (1 << 32); - constraints + // Check: the resulting sum is wrapped if necessary. + // As the result is range checked, this make the choice deterministic, + // even for a malicious prover. + constraints.always(lv.is_executed() * (lv.addrs[1] - added) * (lv.addrs[1] - wrapped)); + + constraints + } } const COLUMNS: usize = NUM_HW_MEM_COLS; const PUBLIC_INPUTS: usize = 0; -impl, const D: usize> Stark for HalfWordMemoryStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } - - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); - } - - fn constraint_degree(&self) -> usize { 3 } -} #[cfg(test)] mod tests { diff --git a/circuits/src/memory_zeroinit/stark.rs b/circuits/src/memory_zeroinit/stark.rs index 6d9785971..ff86ba905 100644 --- a/circuits/src/memory_zeroinit/stark.rs +++ b/circuits/src/memory_zeroinit/stark.rs @@ -1,78 +1,39 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::MemoryZeroInit; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::unstark::NoColumns; #[derive(Clone, Copy, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct MemoryZeroInitStark { - pub _f: PhantomData, -} +pub struct MemoryZeroInitConstraints {} -impl HasNamedColumns for MemoryZeroInitStark { - type Columns = MemoryZeroInit; -} +#[allow(clippy::module_name_repetitions)] +pub type MemoryZeroInitStark = + StarkFrom; const COLUMNS: usize = MemoryZeroInit::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let mut constraints = ConstraintBuilder::default(); - - constraints.always(lv.filter.is_binary()); - - constraints -} - -impl, const D: usize> Stark for MemoryZeroInitStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for MemoryZeroInitConstraints { + type PublicInputs = NoColumns; + type View = MemoryZeroInit; - fn eval_packed_generic( + fn generate_constraints<'a, T: Debug + Copy>( &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let mut constraints = ConstraintBuilder::default(); - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); - } + constraints.always(lv.filter.is_binary()); - fn constraint_degree(&self) -> usize { 3 } + constraints + } } #[cfg(test)] diff --git a/circuits/src/memoryinit/stark.rs b/circuits/src/memoryinit/stark.rs index 47dd7ab8f..88004ef94 100644 --- a/circuits/src/memoryinit/stark.rs +++ b/circuits/src/memoryinit/stark.rs @@ -1,78 +1,39 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::MemoryInit; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::unstark::NoColumns; #[derive(Clone, Copy, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct MemoryInitStark { - pub _f: PhantomData, -} +pub struct MemoryInitConstraints {} -impl HasNamedColumns for MemoryInitStark { - type Columns = MemoryInit; -} +#[allow(clippy::module_name_repetitions)] +pub type MemoryInitStark = + StarkFrom; const COLUMNS: usize = MemoryInit::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let mut constraints = ConstraintBuilder::default(); - - constraints.always(lv.filter.is_binary()); - - constraints -} - -impl, const D: usize> Stark for MemoryInitStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for MemoryInitConstraints { + type PublicInputs = NoColumns; + type View = MemoryInit; - fn eval_packed_generic( + fn generate_constraints<'a, T: Debug + Copy>( &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let mut constraints = ConstraintBuilder::default(); - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); - } + constraints.always(lv.filter.is_binary()); - fn constraint_degree(&self) -> usize { 3 } + constraints + } } #[cfg(test)] diff --git a/circuits/src/ops/add/stark.rs b/circuits/src/ops/add/stark.rs index f4a19e966..3f7838637 100644 --- a/circuits/src/ops/add/stark.rs +++ b/circuits/src/ops/add/stark.rs @@ -1,82 +1,43 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::Add; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; +use crate::unstark::NoColumns; #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct AddStark { - pub _f: PhantomData, -} +pub struct AddConstraints {} -impl HasNamedColumns for AddStark { - type Columns = Add; -} +#[allow(clippy::module_name_repetitions)] +pub type AddStark = + StarkFrom; const COLUMNS: usize = Add::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy, U>( - vars: &StarkFrameTyped>, Vec>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let mut constraints = ConstraintBuilder::default(); - - let added = lv.op1_value + lv.op2_value + lv.inst.imm_value; - let wrapped = added - (1 << 32); - - // Check: the resulting sum is wrapped if necessary. - // As the result is range checked, this make the choice deterministic, - // even for a malicious prover. - constraints.always((lv.dst_value - added) * (lv.dst_value - wrapped)); - - constraints -} +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for AddConstraints { + type PublicInputs = NoColumns; + type View = Add; -impl, const D: usize> Stark for AddStark { - type EvaluationFrame = StarkFrame + fn generate_constraints<'a, T: Copy + Debug>( + &self, + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let mut constraints = ConstraintBuilder::default(); - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; + let added = lv.op1_value + lv.op2_value + lv.inst.imm_value; + let wrapped = added - (1 << 32); - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - constraint_consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let expr_builder = ExprBuilder::default(); - let vars = expr_builder.to_typed_starkframe(vars); - let constraints = generate_constraints(&vars); - build_packed(constraints, constraint_consumer); - } + // Check: the resulting sum is wrapped if necessary. + // As the result is range checked, this make the choice deterministic, + // even for a malicious prover. + constraints.always((lv.dst_value - added) * (lv.dst_value - wrapped)); - fn eval_ext_circuit( - &self, - circuit_builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - constraint_consumer: &mut RecursiveConstraintConsumer, - ) { - let expr_builder = ExprBuilder::default(); - let constraints = generate_constraints(&expr_builder.to_typed_starkframe(vars)); - build_ext(constraints, circuit_builder, constraint_consumer); + constraints } - - fn constraint_degree(&self) -> usize { 3 } } diff --git a/circuits/src/ops/blt_taken/stark.rs b/circuits/src/ops/blt_taken/stark.rs index 15f6b8e31..247ed8242 100644 --- a/circuits/src/ops/blt_taken/stark.rs +++ b/circuits/src/ops/blt_taken/stark.rs @@ -1,7 +1,3 @@ -use super::columns::BltTaken; -use crate::columns_view::NumberOfColumns; -use crate::unstark::Unstark; +use crate::unstark::unstark; -#[allow(clippy::module_name_repetitions)] -pub type BltTakenStark = - Unstark, { BltTaken::<()>::NUMBER_OF_COLUMNS }>; +unstark!(BltTakenStark, super::columns::BltTaken); diff --git a/circuits/src/poseidon2/stark.rs b/circuits/src/poseidon2/stark.rs index 9d3f24034..497f2da5f 100644 --- a/circuits/src/poseidon2/stark.rs +++ b/circuits/src/poseidon2/stark.rs @@ -1,47 +1,37 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::goldilocks_field::GoldilocksField; -use plonky2::field::packed::PackedField; use plonky2::field::types::{Field, PrimeField64}; -use plonky2::hash::hash_types::RichField; use plonky2::hash::poseidon2::Poseidon2; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::Poseidon2State; -use crate::columns_view::HasNamedColumns; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::poseidon2::columns::{NUM_POSEIDON2_COLS, ROUNDS_F, ROUNDS_P, STATE_SIZE}; use crate::unstark::NoColumns; fn from_u64(u: u64) -> i64 { GoldilocksField::from_noncanonical_u64(u).to_canonical_i64() } // degree: 1 -fn add_rc(state: &mut [Expr; STATE_SIZE], r: usize) +fn add_rc(state: &mut [Expr; STATE_SIZE], r: usize) where - V: Copy, - W: Poseidon2, { + T: Copy, { for (i, val) in state.iter_mut().enumerate() { - *val += from_u64(::RC12[r + i]); + *val += from_u64(GoldilocksField::RC12[r + i]); } } // degree: 3 -fn sbox_p<'a, V>(x: &mut Expr<'a, V>, x_qube: &Expr<'a, V>) +fn sbox_p<'a, T>(x: &mut Expr<'a, T>, x_qube: &Expr<'a, T>) where - V: Copy, { + T: Copy, { *x *= *x_qube * *x_qube; } -fn matmul_m4(state: &mut [Expr<'_, V>; STATE_SIZE]) +fn matmul_m4(state: &mut [Expr<'_, T>; STATE_SIZE]) where - V: Copy, { + T: Copy, { // input x = (x0, x1, x2, x3) let t4 = STATE_SIZE / 4; @@ -83,9 +73,9 @@ where } } -fn matmul_external12(state: &mut [Expr<'_, V>; STATE_SIZE]) +fn matmul_external12(state: &mut [Expr<'_, T>; STATE_SIZE]) where - V: Copy, { + T: Copy, { matmul_m4(state); let t4 = STATE_SIZE / 4; @@ -103,27 +93,24 @@ where } // degree: 1 -fn matmul_internal12<'a, V, U, const STATE_SIZE: usize>(state: &mut [Expr<'a, V>; STATE_SIZE]) +fn matmul_internal12<'a, T, const STATE_SIZE: usize>(state: &mut [Expr<'a, T>; STATE_SIZE]) where - V: Copy, - U: Poseidon2, { - let sum = state.iter().sum::>(); + T: Copy, { + let sum = state.iter().sum::>(); for (i, val) in state.iter_mut().enumerate() { - *val *= from_u64(::MAT_DIAG12_M_1[i]) - 1; + *val *= from_u64(GoldilocksField::MAT_DIAG12_M_1[i]) - 1; *val += sum; } } #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct Poseidon2_12Stark { - pub _f: PhantomData, -} +pub struct Poseidon2_12Constraints {} -impl HasNamedColumns for Poseidon2_12Stark { - type Columns = Poseidon2State; -} +#[allow(clippy::module_name_repetitions)] +pub type Poseidon2_12Stark = + StarkFrom; const COLUMNS: usize = NUM_POSEIDON2_COLS; const PUBLIC_INPUTS: usize = 0; @@ -131,103 +118,75 @@ const PUBLIC_INPUTS: usize = 0; // Compile time assertion that STATE_SIZE equals 12 const _UNUSED_STATE_SIZE_IS_12: [(); STATE_SIZE - 12] = []; -// NOTE: This one has extra constraints compared to different implementations of -// `generate_constraints` that were have written so far. It will be something -// to take into account when providing a more geneeral API to plonky. -fn generate_constraints<'a, V: Copy, U: Poseidon2>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let mut constraints = ConstraintBuilder::default(); - - // row can be execution or padding. - constraints.always(lv.is_exe.is_binary()); - - let mut state = lv.input; - matmul_external12(&mut state); - // first full rounds - for r in 0..(ROUNDS_F / 2) { - add_rc::(&mut state, r); - for (i, item) in state.iter_mut().enumerate() { - sbox_p( - item, - &lv.s_box_input_qube_first_full_rounds[r * STATE_SIZE + i], - ); - } - matmul_external12(&mut state); - for (i, state_i) in state.iter_mut().enumerate() { - constraints.always(*state_i - lv.state_after_first_full_rounds[r * STATE_SIZE + i]); - *state_i = lv.state_after_first_full_rounds[r * STATE_SIZE + i]; - } - } +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for Poseidon2_12Constraints { + type PublicInputs = NoColumns; + type View = Poseidon2State; - // partial rounds - for i in 0..ROUNDS_P { - state[0] += from_u64(::RC12_MID[i]); - sbox_p(&mut state[0], &lv.s_box_input_qube_partial_rounds[i]); - matmul_internal12::(&mut state); - constraints.always(state[0] - lv.state0_after_partial_rounds[i]); - state[0] = lv.state0_after_partial_rounds[i]; - } + // NOTE: This one has extra constraints compared to different implementations of + // `generate_constraints` that were have written so far. It will be something + // to take into account when providing a more geneeral API to plonky. + fn generate_constraints<'a, T: Copy + Debug>( + &self, + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let mut constraints = ConstraintBuilder::default(); - // the state before last full rounds - for (i, state_i) in state.iter_mut().enumerate() { - constraints.always(*state_i - lv.state_after_partial_rounds[i]); - *state_i = lv.state_after_partial_rounds[i]; - } + // row can be execution or padding. + constraints.always(lv.is_exe.is_binary()); - // last full rounds - for i in 0..(ROUNDS_F / 2) { - let r = (ROUNDS_F / 2) + i; - add_rc::(&mut state, r); - for (j, item) in state.iter_mut().enumerate() { - sbox_p( - item, - &lv.s_box_input_qube_second_full_rounds[i * STATE_SIZE + j], - ); - } + let mut state = lv.input; matmul_external12(&mut state); - for (j, state_j) in state.iter_mut().enumerate() { - constraints.always(*state_j - lv.state_after_second_full_rounds[i * STATE_SIZE + j]); - *state_j = lv.state_after_second_full_rounds[i * STATE_SIZE + j]; + // first full rounds + for r in 0..(ROUNDS_F / 2) { + add_rc::(&mut state, r); + for (i, item) in state.iter_mut().enumerate() { + sbox_p( + item, + &lv.s_box_input_qube_first_full_rounds[r * STATE_SIZE + i], + ); + } + matmul_external12(&mut state); + for (i, state_i) in state.iter_mut().enumerate() { + constraints.always(*state_i - lv.state_after_first_full_rounds[r * STATE_SIZE + i]); + *state_i = lv.state_after_first_full_rounds[r * STATE_SIZE + i]; + } } - } - - constraints -} -impl, const D: usize> Stark for Poseidon2_12Stark { - type EvaluationFrame = StarkFrame - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; + // partial rounds + for i in 0..ROUNDS_P { + state[0] += from_u64(GoldilocksField::RC12_MID[i]); + sbox_p(&mut state[0], &lv.s_box_input_qube_partial_rounds[i]); + matmul_internal12::(&mut state); + constraints.always(state[0] - lv.state0_after_partial_rounds[i]); + state[0] = lv.state0_after_partial_rounds[i]; + } - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints::(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } + // the state before last full rounds + for (i, state_i) in state.iter_mut().enumerate() { + constraints.always(*state_i - lv.state_after_partial_rounds[i]); + *state_i = lv.state_after_partial_rounds[i]; + } - fn constraint_degree(&self) -> usize { 3 } + // last full rounds + for i in 0..(ROUNDS_F / 2) { + let r = (ROUNDS_F / 2) + i; + add_rc::(&mut state, r); + for (j, item) in state.iter_mut().enumerate() { + sbox_p( + item, + &lv.s_box_input_qube_second_full_rounds[i * STATE_SIZE + j], + ); + } + matmul_external12(&mut state); + for (j, state_j) in state.iter_mut().enumerate() { + constraints + .always(*state_j - lv.state_after_second_full_rounds[i * STATE_SIZE + j]); + *state_j = lv.state_after_second_full_rounds[i * STATE_SIZE + j]; + } + } - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = - generate_constraints::, F>(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); + constraints } } diff --git a/circuits/src/poseidon2_output_bytes/stark.rs b/circuits/src/poseidon2_output_bytes/stark.rs index b3ba8d492..4841061f6 100644 --- a/circuits/src/poseidon2_output_bytes/stark.rs +++ b/circuits/src/poseidon2_output_bytes/stark.rs @@ -1,88 +1,49 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::{FIELDS_COUNT, NUM_POSEIDON2_OUTPUT_BYTES_COLS}; -use crate::columns_view::HasNamedColumns; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::poseidon2_output_bytes::columns::Poseidon2OutputBytes; use crate::unstark::NoColumns; #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct Poseidon2OutputBytesStark { - pub _f: PhantomData, -} +pub struct Poseidon2OutputBytesConstraints {} -impl HasNamedColumns for Poseidon2OutputBytesStark { - type Columns = Poseidon2OutputBytes; -} +#[allow(clippy::module_name_repetitions)] +pub type Poseidon2OutputBytesStark = + StarkFrom; const COLUMNS: usize = NUM_POSEIDON2_OUTPUT_BYTES_COLS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let mut constraints = ConstraintBuilder::default(); - - constraints.always(lv.is_executed.is_binary()); - for i in 0..FIELDS_COUNT { - let start_index = i * 8; - let end_index = i * 8 + 8; - constraints.always( - Expr::reduce_with_powers::>>( - lv.output_bytes[start_index..end_index].into(), - 256, - ) - lv.output_fields[i], - ); - } - - constraints -} +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for Poseidon2OutputBytesConstraints { + type PublicInputs = NoColumns; + type View = Poseidon2OutputBytes; -impl, const D: usize> Stark for Poseidon2OutputBytesStark { - type EvaluationFrame = StarkFrame - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( + fn generate_constraints<'a, T: Copy + Debug>( &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let mut constraints = ConstraintBuilder::default(); + + constraints.always(lv.is_executed.is_binary()); + for i in 0..FIELDS_COUNT { + let start_index = i * 8; + let end_index = i * 8 + 8; + constraints.always( + Expr::reduce_with_powers::>>( + lv.output_bytes[start_index..end_index].into(), + 256, + ) - lv.output_fields[i], + ); + } + + constraints } - - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); - } - - fn constraint_degree(&self) -> usize { 3 } } #[cfg(test)] diff --git a/circuits/src/poseidon2_sponge/stark.rs b/circuits/src/poseidon2_sponge/stark.rs index e16fdc405..a044504f7 100644 --- a/circuits/src/poseidon2_sponge/stark.rs +++ b/circuits/src/poseidon2_sponge/stark.rs @@ -1,147 +1,104 @@ +use core::fmt::Debug; use std::marker::PhantomData; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; use plonky2::hash::hashing::PlonkyPermutation; -use plonky2::hash::poseidon2::Poseidon2Permutation; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; +use plonky2::hash::poseidon2::{Poseidon2, Poseidon2Permutation}; use super::columns::NUM_POSEIDON2_SPONGE_COLS; -use crate::columns_view::HasNamedColumns; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::poseidon2_sponge::columns::Poseidon2Sponge; use crate::unstark::NoColumns; #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct Poseidon2SpongeStark { - pub _f: PhantomData, +pub struct Poseidon2SpongeConstraints { + _f: PhantomData, } -impl HasNamedColumns for Poseidon2SpongeStark { - type Columns = Poseidon2Sponge; -} +#[allow(clippy::module_name_repetitions)] +pub type Poseidon2SpongeStark = + StarkFrom, { D }, { COLUMNS }, { PUBLIC_INPUTS }>; const COLUMNS: usize = NUM_POSEIDON2_SPONGE_COLS; const PUBLIC_INPUTS: usize = 0; -// For design check https://docs.google.com/presentation/d/10Dv00xL3uggWTPc0L91cgu_dWUzhM7l1EQ5uDEI_cjg/edit?usp=sharing -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, - rate: usize, - state_size: usize, -) -> ConstraintBuilder> { - // NOTE: clk and address will be used for CTL to CPU for is_init_permute rows - // only, and not be used for permute rows. - // For all non dummy rows we have CTL to Poseidon2 permute stark, with preimage - // and output columns. - - let rate = u8::try_from(rate).expect("rate > 255"); - let state_size = u8::try_from(state_size).expect("state_size > 255"); - let rate_scalar = i64::from(rate); - let lv = vars.local_values; - let nv = vars.next_values; - let mut constraints = ConstraintBuilder::default(); - - for val in [lv.ops.is_permute, lv.ops.is_init_permute, lv.gen_output] { - constraints.always(val.is_binary()); - } - let is_exe = lv.ops.is_init_permute + lv.ops.is_permute; - constraints.always(is_exe.is_binary()); - - let is_dummy = 1 - is_exe; - - // dummy row does not generate output - constraints.always(is_dummy * lv.gen_output); - - // if row generates output then it must be last rate sized - // chunk of input. - constraints.always(lv.gen_output * (lv.input_len - rate_scalar)); - - let is_init_or_dummy = |vars: &Poseidon2Sponge>| { - (1 - vars.ops.is_init_permute) * (vars.ops.is_init_permute + vars.ops.is_permute) - }; - - // First row must be init permute or dummy row. - constraints.first_row(is_init_or_dummy(&lv)); - // if row generates output then next row can be dummy or start of next hashing - constraints.always(lv.gen_output * is_init_or_dummy(&nv)); - - // Clk should not change within a sponge - constraints.transition(nv.ops.is_permute * (lv.clk - nv.clk)); - - let not_last_sponge = (1 - lv.gen_output) * (lv.ops.is_permute + lv.ops.is_init_permute); - // if current row consumes input and its not last sponge then next row must have - // length decreases by RATE, note that only actual execution row can consume - // input - constraints.transition(not_last_sponge * (lv.input_len - (nv.input_len + rate_scalar))); - // and input_addr increases by RATE - constraints.transition(not_last_sponge * (lv.input_addr - (nv.input_addr - rate_scalar))); +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> + for Poseidon2SpongeConstraints +{ + type PublicInputs = NoColumns; + type View = Poseidon2Sponge; - // For each init_permute capacity bits are zero. - for i in rate..state_size { - constraints.always(lv.ops.is_init_permute * (lv.preimage[i as usize] - 0)); - } - - // For each permute capacity bits are copied from previous output. - for i in rate..state_size { - constraints.always( - (1 - nv.ops.is_init_permute) - * nv.ops.is_permute - * (nv.preimage[i as usize] - lv.output[i as usize]), - ); - } - constraints -} - -impl, const D: usize> Stark for Poseidon2SpongeStark { - type EvaluationFrame = StarkFrame - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( + // For design check https://docs.google.com/presentation/d/10Dv00xL3uggWTPc0L91cgu_dWUzhM7l1EQ5uDEI_cjg/edit?usp=sharing + fn generate_constraints<'a, T: Copy + Debug>( &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints( - &eb.to_typed_starkframe(vars), - Poseidon2Permutation::::RATE, - Poseidon2Permutation::::WIDTH, - ); - build_packed(constraints, consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let rate = Poseidon2Permutation::::RATE; + let state_size = Poseidon2Permutation::::WIDTH; + // NOTE: clk and address will be used for CTL to CPU for is_init_permute rows + // only, and not be used for permute rows. + // For all non dummy rows we have CTL to Poseidon2 permute stark, with preimage + // and output columns. + + let rate = u8::try_from(rate).expect("rate > 255"); + let state_size = u8::try_from(state_size).expect("state_size > 255"); + let rate_scalar = i64::from(rate); + let lv = vars.local_values; + let nv = vars.next_values; + let mut constraints = ConstraintBuilder::default(); + + for val in [lv.ops.is_permute, lv.ops.is_init_permute, lv.gen_output] { + constraints.always(val.is_binary()); + } + let is_exe = lv.ops.is_init_permute + lv.ops.is_permute; + constraints.always(is_exe.is_binary()); + + let is_dummy = 1 - is_exe; + + // dummy row does not generate output + constraints.always(is_dummy * lv.gen_output); + + // if row generates output then it must be last rate sized + // chunk of input. + constraints.always(lv.gen_output * (lv.input_len - rate_scalar)); + + let is_init_or_dummy = |vars: &Poseidon2Sponge>| { + (1 - vars.ops.is_init_permute) * (vars.ops.is_init_permute + vars.ops.is_permute) + }; + + // First row must be init permute or dummy row. + constraints.first_row(is_init_or_dummy(&lv)); + // if row generates output then next row can be dummy or start of next hashing + constraints.always(lv.gen_output * is_init_or_dummy(&nv)); + + // Clk should not change within a sponge + constraints.transition(nv.ops.is_permute * (lv.clk - nv.clk)); + + let not_last_sponge = (1 - lv.gen_output) * (lv.ops.is_permute + lv.ops.is_init_permute); + // if current row consumes input and its not last sponge then next row must have + // length decreases by RATE, note that only actual execution row can consume + // input + constraints.transition(not_last_sponge * (lv.input_len - (nv.input_len + rate_scalar))); + // and input_addr increases by RATE + constraints.transition(not_last_sponge * (lv.input_addr - (nv.input_addr - rate_scalar))); + + // For each init_permute capacity bits are zero. + for i in rate..state_size { + constraints.always(lv.ops.is_init_permute * (lv.preimage[i as usize] - 0)); + } + + // For each permute capacity bits are copied from previous output. + for i in rate..state_size { + constraints.always( + (1 - nv.ops.is_init_permute) + * nv.ops.is_permute + * (nv.preimage[i as usize] - lv.output[i as usize]), + ); + } + constraints } - - #[allow(clippy::similar_names)] - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints( - &eb.to_typed_starkframe(vars), - Poseidon2Permutation::::RATE, - Poseidon2Permutation::::WIDTH, - ); - build_ext(constraints, builder, consumer); - } - - fn constraint_degree(&self) -> usize { 3 } } #[cfg(test)] diff --git a/circuits/src/program/stark.rs b/circuits/src/program/stark.rs index 89c4e4396..ad988f76c 100644 --- a/circuits/src/program/stark.rs +++ b/circuits/src/program/stark.rs @@ -1,7 +1,3 @@ -use super::columns::ProgramRom; -use crate::columns_view::NumberOfColumns; -use crate::unstark::Unstark; +use crate::unstark::unstark; -#[allow(clippy::module_name_repetitions)] -pub type ProgramStark = - Unstark, { ProgramRom::<()>::NUMBER_OF_COLUMNS }>; +unstark!(ProgramStark, super::columns::ProgramRom); diff --git a/circuits/src/program_multiplicities/stark.rs b/circuits/src/program_multiplicities/stark.rs index 8737e4836..4686e047a 100644 --- a/circuits/src/program_multiplicities/stark.rs +++ b/circuits/src/program_multiplicities/stark.rs @@ -1,7 +1,3 @@ -use super::columns::ProgramMult; -use crate::columns_view::NumberOfColumns; -use crate::unstark::Unstark; +use crate::unstark::unstark; -#[allow(clippy::module_name_repetitions)] -pub type ProgramMultStark = - Unstark, { ProgramMult::<()>::NUMBER_OF_COLUMNS }>; +unstark!(ProgramMultStark, super::columns::ProgramMult); diff --git a/circuits/src/rangecheck/stark.rs b/circuits/src/rangecheck/stark.rs index da9d1865c..993fabcf9 100644 --- a/circuits/src/rangecheck/stark.rs +++ b/circuits/src/rangecheck/stark.rs @@ -1,10 +1,6 @@ -use super::columns::RangeCheckColumnsView; -use crate::columns_view::NumberOfColumns; -use crate::unstark::Unstark; +use crate::unstark::unstark; -#[allow(clippy::module_name_repetitions)] -pub type RangeCheckStark = - Unstark, { RangeCheckColumnsView::<()>::NUMBER_OF_COLUMNS }>; +unstark!(RangeCheckStark, super::columns::RangeCheckColumnsView); #[cfg(test)] mod tests { diff --git a/circuits/src/rangecheck_u8/stark.rs b/circuits/src/rangecheck_u8/stark.rs index 0ad55db4a..4bcc83c19 100644 --- a/circuits/src/rangecheck_u8/stark.rs +++ b/circuits/src/rangecheck_u8/stark.rs @@ -1,82 +1,43 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::RangeCheckU8; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::unstark::NoColumns; #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct RangeCheckU8Stark { - pub _f: PhantomData, -} +pub struct RangeCheckU8Constraints {} -impl HasNamedColumns for RangeCheckU8Stark { - type Columns = RangeCheckU8; -} +#[allow(clippy::module_name_repetitions)] +pub type RangeCheckU8Stark = + StarkFrom; const COLUMNS: usize = RangeCheckU8::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let nv = vars.next_values; - let mut constraints = ConstraintBuilder::default(); - - // Check: the `element`s form a sequence from 0 to 255 - constraints.first_row(lv.value); - constraints.transition(nv.value - lv.value - 1); - constraints.last_row(lv.value - i64::from(u8::MAX)); - - constraints -} - -impl, const D: usize> Stark for RangeCheckU8Stark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for RangeCheckU8Constraints { + type PublicInputs = NoColumns; + type View = RangeCheckU8; - fn eval_ext_circuit( + fn generate_constraints<'a, T: Copy + Debug>( &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let nv = vars.next_values; + let mut constraints = ConstraintBuilder::default(); + + // Check: the `element`s form a sequence from 0 to 255 + constraints.first_row(lv.value); + constraints.transition(nv.value - lv.value - 1); + constraints.last_row(lv.value - i64::from(u8::MAX)); + + constraints } - - fn constraint_degree(&self) -> usize { 3 } } #[cfg(test)] diff --git a/circuits/src/register/general/stark.rs b/circuits/src/register/general/stark.rs index bb3675637..a53495a19 100644 --- a/circuits/src/register/general/stark.rs +++ b/circuits/src/register/general/stark.rs @@ -1,123 +1,85 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::Register; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::unstark::NoColumns; #[derive(Clone, Copy, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct RegisterStark { - pub _f: PhantomData, -} +pub struct RegisterConstraints {} -impl HasNamedColumns for RegisterStark { - type Columns = Register; -} +#[allow(clippy::module_name_repetitions)] +pub type RegisterStark = + StarkFrom; const COLUMNS: usize = Register::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -/// Constraints for the [`RegisterStark`]: -/// -/// 1) `is_init`, `is_read`, `is_write`, and the virtual `is_used` column are -/// binary columns. The `is_used` column is the sum of all the other ops -/// columns combined, to differentiate between real trace rows and padding -/// rows. -/// 2) The virtual `is_used` column only take values 0 or 1. -/// 3) Only rd changes. -/// 4) Address changes only when `nv.is_init` == 1. -/// 5) Address either stays the same or increments by 1. -/// 6) Addresses go from 1 to 31. Address 0 is handled by `RegisterZeroStark`. -/// -/// For more details, refer to the [Notion -/// document](https://www.notion.so/0xmozak/Register-File-STARK-62459d68aea648a0abf4e97aa0093ea2). -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let nv = vars.next_values; - let mut constraints = ConstraintBuilder::default(); - - // Constraint 1: filter columns take 0 or 1 values only. - constraints.always(lv.ops.is_init.is_binary()); - constraints.always(lv.ops.is_read.is_binary()); - constraints.always(lv.ops.is_write.is_binary()); - constraints.always(lv.is_used().is_binary()); - - // Constraint 2: virtual `is_used` column can only take values 0 or 1. - // (lv.is_used() - nv.is_used() - 1) is expressed as such, because - // lv.is_used() = 1 in the last real row, and - // nv.is_used() = 0 in the first padding row. - constraints.transition(nv.is_used() * (nv.is_used() - lv.is_used())); - - // Constraint 3: only rd changes. - // We reformulate the above constraint as such: - // For any register, only `is_write`, `is_init` or the virtual `is_used` - // column should be able to change values of registers. - // `is_read` should not change the values of registers. - constraints.transition(nv.ops.is_read * (nv.value - lv.value)); - - // Constraint 4: Address changes only when nv.is_init == 1. - // We reformulate the above constraint to be: - // if next `is_read` == 1 or next `is_write` == 1, the address cannot - // change. - constraints.transition((nv.ops.is_read + nv.ops.is_write) * (nv.addr - lv.addr)); - - // Constraint 5: Address either stays the same or increments by 1. - constraints.transition((nv.addr - lv.addr) * (nv.addr - lv.addr - 1)); - - // Constraint 6: addresses go from 1 to 31. - constraints.first_row(lv.addr - 1); - constraints.last_row(lv.addr - 31); - - constraints -} - -impl, const D: usize> Stark for RegisterStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for RegisterConstraints { + type PublicInputs = NoColumns; + type View = Register; + + /// Constraints for the [`RegisterStark`]: + /// + /// 1) `is_init`, `is_read`, `is_write`, and the virtual `is_used` column + /// are binary columns. The `is_used` column is the sum of all the other + /// ops columns combined, to differentiate between real trace rows and + /// padding rows. + /// 2) The virtual `is_used` column only take values 0 or 1. + /// 3) Only rd changes. + /// 4) Address changes only when `nv.is_init` == 1. + /// 5) Address either stays the same or increments by 1. + /// 6) Addresses go from 1 to 31. Address 0 is handled by + /// `RegisterZeroStark`. + /// + /// For more details, refer to the [Notion + /// document](https://www.notion.so/0xmozak/Register-File-STARK-62459d68aea648a0abf4e97aa0093ea2). + fn generate_constraints<'a, T: Copy + Debug>( &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let nv = vars.next_values; + let mut constraints = ConstraintBuilder::default(); + + // Constraint 1: filter columns take 0 or 1 values only. + constraints.always(lv.ops.is_init.is_binary()); + constraints.always(lv.ops.is_read.is_binary()); + constraints.always(lv.ops.is_write.is_binary()); + constraints.always(lv.is_used().is_binary()); + + // Constraint 2: virtual `is_used` column can only take values 0 or 1. + // (lv.is_used() - nv.is_used() - 1) is expressed as such, because + // lv.is_used() = 1 in the last real row, and + // nv.is_used() = 0 in the first padding row. + constraints.transition(nv.is_used() * (nv.is_used() - lv.is_used())); + + // Constraint 3: only rd changes. + // We reformulate the above constraint as such: + // For any register, only `is_write`, `is_init` or the virtual `is_used` + // column should be able to change values of registers. + // `is_read` should not change the values of registers. + constraints.transition(nv.ops.is_read * (nv.value - lv.value)); + + // Constraint 4: Address changes only when nv.is_init == 1. + // We reformulate the above constraint to be: + // if next `is_read` == 1 or next `is_write` == 1, the address cannot + // change. + constraints.transition((nv.ops.is_read + nv.ops.is_write) * (nv.addr - lv.addr)); + + // Constraint 5: Address either stays the same or increments by 1. + constraints.transition((nv.addr - lv.addr) * (nv.addr - lv.addr - 1)); + + // Constraint 6: addresses go from 1 to 31. + constraints.first_row(lv.addr - 1); + constraints.last_row(lv.addr - 31); + + constraints } - - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); - } - - fn constraint_degree(&self) -> usize { 3 } } #[cfg(test)] diff --git a/circuits/src/register/init/stark.rs b/circuits/src/register/init/stark.rs index 0f4e497ac..097d884e5 100644 --- a/circuits/src/register/init/stark.rs +++ b/circuits/src/register/init/stark.rs @@ -1,10 +1,6 @@ -use super::columns::RegisterInit; -use crate::columns_view::NumberOfColumns; -use crate::unstark::Unstark; +use crate::unstark::unstark; -/// For sanity check, we can constrain the register address column to be in -/// a running sum from 0..=31, but since this fixed table is known to -/// both prover and verifier, we do not need to do so here. -#[allow(clippy::module_name_repetitions)] -pub type RegisterInitStark = - Unstark, { RegisterInit::<()>::NUMBER_OF_COLUMNS }>; +// For sanity check, we can constrain the register address column to be in +// a running sum from 0..=31, but since this fixed table is known to +// both prover and verifier, we do not need to do so here. +unstark!(RegisterInitStark, super::columns::RegisterInit); diff --git a/circuits/src/register/zero_read/stark.rs b/circuits/src/register/zero_read/stark.rs index b6975b97b..86493861a 100644 --- a/circuits/src/register/zero_read/stark.rs +++ b/circuits/src/register/zero_read/stark.rs @@ -1,7 +1,3 @@ -use super::columns::RegisterZeroRead; -use crate::columns_view::NumberOfColumns; -use crate::unstark::Unstark; +use crate::unstark::unstark; -#[allow(clippy::module_name_repetitions)] -pub type RegisterZeroReadStark = - Unstark, { RegisterZeroRead::<()>::NUMBER_OF_COLUMNS }>; +unstark!(RegisterZeroReadStark, super::columns::RegisterZeroRead); diff --git a/circuits/src/register/zero_write/stark.rs b/circuits/src/register/zero_write/stark.rs index a5dfa90ec..e968a31e7 100644 --- a/circuits/src/register/zero_write/stark.rs +++ b/circuits/src/register/zero_write/stark.rs @@ -1,7 +1,3 @@ -use super::columns::RegisterZeroWrite; -use crate::columns_view::NumberOfColumns; -use crate::unstark::Unstark; +use crate::unstark::unstark; -#[allow(clippy::module_name_repetitions)] -pub type RegisterZeroWriteStark = - Unstark, { RegisterZeroWrite::<()>::NUMBER_OF_COLUMNS }>; +unstark!(RegisterZeroWriteStark, super::columns::RegisterZeroWrite); diff --git a/circuits/src/storage_device/stark.rs b/circuits/src/storage_device/stark.rs index 6ed9de221..cee6cfccf 100644 --- a/circuits/src/storage_device/stark.rs +++ b/circuits/src/storage_device/stark.rs @@ -1,115 +1,77 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; - -use crate::columns_view::HasNamedColumns; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; + +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::storage_device::columns::{StorageDevice, NUM_STORAGE_DEVICE_COLS}; use crate::unstark::NoColumns; #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct StorageDeviceStark { - pub _f: PhantomData, -} +pub struct StorageDeviceConstraints {} -impl HasNamedColumns for StorageDeviceStark { - type Columns = StorageDevice; -} +#[allow(clippy::module_name_repetitions)] +pub type StorageDeviceStark = + StarkFrom; const COLUMNS: usize = NUM_STORAGE_DEVICE_COLS; const PUBLIC_INPUTS: usize = 0; -// Design description - https://docs.google.com/presentation/d/1J0BJd49BMQh3UR5TrOhe3k67plHxnohFtFVrMpDJ1oc/edit?usp=sharing -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let nv = vars.next_values; - let mut constraints = ConstraintBuilder::default(); - - constraints.always(lv.ops.is_memory_store.is_binary()); - constraints.always(lv.ops.is_storage_device.is_binary()); - constraints.always(lv.is_executed().is_binary()); - - // If nv.is_storage_device() == 1: lv.size == 0, also forces the last row to be - // size == 0 ! This constraints ensures loop unrolling was done correctly - constraints.always(nv.ops.is_storage_device * lv.size); - // If lv.is_lv_and_nv_are_memory_rows == 1: - // nv.address == lv.address + 1 (wrapped) - // nv.size == lv.size - 1 (not-wrapped) - let added = lv.addr + 1; - let wrapped = added - (1 << 32); - // nv.address == lv.address + 1 (wrapped) - constraints.always(lv.is_lv_and_nv_are_memory_rows * (nv.addr - added) * (nv.addr - wrapped)); - // nv.size == lv.size - 1 (not-wrapped) - constraints.transition(nv.is_lv_and_nv_are_memory_rows * (nv.size - (lv.size - 1))); - // Edge cases: - // a) - storage_device with size = 0: <-- this case is solved since CTL from - // CPU a.1) is_lv_and_nv_are_memory_rows = 0 (no memory rows - // inserted) b) - storage_device with size = 1: <-- this case needs to be - // solved separately b.1) is_lv_and_nv_are_memory_rows = 0 (only one - // memory row inserted) To solve case-b: - // If lv.is_storage_device() == 1 && lv.size != 0: - // lv.addr == nv.addr <-- next row address must be the same !!! - // lv.size === nv.size - 1 <-- next row size is decreased - constraints.transition(lv.ops.is_storage_device * lv.size * (nv.addr - lv.addr)); - constraints.transition(lv.ops.is_storage_device * lv.size * (nv.size - (lv.size - 1))); - // If lv.is_storage_device() == 1 && lv.size == 0: - // nv.is_memory() == 0 <-- next op can be only io - since size == 0 - // This one is ensured by: - // 1) is_binary(storage_device or memory) - // 2) if nv.is_storage_device() == 1: lv.size == 0 - - // If lv.is_storage_device() == 1 && nv.size != 0: - // nv.is_lv_and_nv_are_memory_rows == 1 - constraints.always(lv.ops.is_storage_device * nv.size * (nv.is_lv_and_nv_are_memory_rows - 1)); - - constraints -} - -impl, const D: usize> Stark for StorageDeviceStark { - type EvaluationFrame = StarkFrame +impl GenerateConstraints<{ COLUMNS }, { PUBLIC_INPUTS }> for StorageDeviceConstraints { + type PublicInputs = NoColumns; + type View = StorageDevice; - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } - - fn eval_ext_circuit( + // Design description - https://docs.google.com/presentation/d/1J0BJd49BMQh3UR5TrOhe3k67plHxnohFtFVrMpDJ1oc/edit?usp=sharing + fn generate_constraints<'a, T: Copy + Debug>( &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let nv = vars.next_values; + let mut constraints = ConstraintBuilder::default(); + + constraints.always(lv.ops.is_memory_store.is_binary()); + constraints.always(lv.ops.is_storage_device.is_binary()); + constraints.always(lv.is_executed().is_binary()); + + // If nv.is_storage_device() == 1: lv.size == 0, also forces the last row to be + // size == 0 ! This constraints ensures loop unrolling was done correctly + constraints.always(nv.ops.is_storage_device * lv.size); + // If lv.is_lv_and_nv_are_memory_rows == 1: + // nv.address == lv.address + 1 (wrapped) + // nv.size == lv.size - 1 (not-wrapped) + let added = lv.addr + 1; + let wrapped = added - (1 << 32); + // nv.address == lv.address + 1 (wrapped) + constraints + .always(lv.is_lv_and_nv_are_memory_rows * (nv.addr - added) * (nv.addr - wrapped)); + // nv.size == lv.size - 1 (not-wrapped) + constraints.transition(nv.is_lv_and_nv_are_memory_rows * (nv.size - (lv.size - 1))); + // Edge cases: + // a) - storage_device with size = 0: <-- this case is solved since CTL from + // CPU a.1) is_lv_and_nv_are_memory_rows = 0 (no memory rows + // inserted) b) - storage_device with size = 1: <-- this case needs to be + // solved separately b.1) is_lv_and_nv_are_memory_rows = 0 (only one + // memory row inserted) To solve case-b: + // If lv.is_storage_device() == 1 && lv.size != 0: + // lv.addr == nv.addr <-- next row address must be the same !!! + // lv.size === nv.size - 1 <-- next row size is decreased + constraints.transition(lv.ops.is_storage_device * lv.size * (nv.addr - lv.addr)); + constraints.transition(lv.ops.is_storage_device * lv.size * (nv.size - (lv.size - 1))); + // If lv.is_storage_device() == 1 && lv.size == 0: + // nv.is_memory() == 0 <-- next op can be only io - since size == 0 + // This one is ensured by: + // 1) is_binary(storage_device or memory) + // 2) if nv.is_storage_device() == 1: lv.size == 0 + + // If lv.is_storage_device() == 1 && nv.size != 0: + // nv.is_lv_and_nv_are_memory_rows == 1 + constraints + .always(lv.ops.is_storage_device * nv.size * (nv.is_lv_and_nv_are_memory_rows - 1)); + + constraints } - - fn constraint_degree(&self) -> usize { 3 } } #[cfg(test)] diff --git a/circuits/src/tape_commitments/stark.rs b/circuits/src/tape_commitments/stark.rs index 3c55b7581..306655848 100644 --- a/circuits/src/tape_commitments/stark.rs +++ b/circuits/src/tape_commitments/stark.rs @@ -1,85 +1,48 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::TapeCommitments; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::unstark::NoColumns; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv: &TapeCommitments> = &vars.local_values; - let mut constraint = ConstraintBuilder::default(); - constraint.always(lv.is_event_commitment_tape_row.is_binary()); - constraint.always(lv.is_castlist_commitment_tape_row.is_binary()); - constraint - .always((lv.is_castlist_commitment_tape_row + lv.is_event_commitment_tape_row).is_binary()); - constraint - .always(lv.event_commitment_tape_multiplicity * (1 - lv.is_event_commitment_tape_row)); - constraint.always( - lv.castlist_commitment_tape_multiplicity * (1 - lv.is_castlist_commitment_tape_row), - ); - constraint + +impl GenerateConstraints for TapeCommitmentsConstraints { + type PublicInputs = NoColumns; + type View = TapeCommitments; + + fn generate_constraints<'a, T: Copy + Debug>( + &self, + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv: &TapeCommitments> = &vars.local_values; + let mut constraint = ConstraintBuilder::default(); + constraint.always(lv.is_event_commitment_tape_row.is_binary()); + constraint.always(lv.is_castlist_commitment_tape_row.is_binary()); + constraint.always( + (lv.is_castlist_commitment_tape_row + lv.is_event_commitment_tape_row).is_binary(), + ); + constraint + .always(lv.event_commitment_tape_multiplicity * (1 - lv.is_event_commitment_tape_row)); + constraint.always( + lv.castlist_commitment_tape_multiplicity * (1 - lv.is_castlist_commitment_tape_row), + ); + constraint + } } #[derive(Copy, Clone, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct TapeCommitmentsStark { - pub _f: PhantomData, -} +pub struct TapeCommitmentsConstraints {} -impl HasNamedColumns for TapeCommitmentsStark { - type Columns = TapeCommitments; -} +#[allow(clippy::module_name_repetitions)] +pub type TapeCommitmentsStark = + StarkFrom; const COLUMNS: usize = TapeCommitments::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -impl, const D: usize> Stark for TapeCommitmentsStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } - - fn constraint_degree(&self) -> usize { 3 } - - fn eval_ext_circuit( - &self, - builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let eb = ExprBuilder::default(); - let constraints = generate_constraints(&eb.to_typed_starkframe(vars)); - build_ext(constraints, builder, consumer); - } -} - #[cfg(test)] mod tests { use itertools::chain; diff --git a/circuits/src/unstark.rs b/circuits/src/unstark.rs index 3efcc5595..d67067e2e 100644 --- a/circuits/src/unstark.rs +++ b/circuits/src/unstark.rs @@ -1,73 +1,51 @@ +use core::fmt::Debug; use std::marker::PhantomData; -use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; - -use crate::columns_view::{columns_view_impl, HasNamedColumns, NumberOfColumns}; - -/// Template for a STARK with zero internal constraints. Use this if the STARK -/// itself does not need any built-in constraints, but rely on cross table -/// lookups for provability. -#[derive(Copy, Clone, Default, StarkNameDisplay)] -#[allow(clippy::module_name_repetitions)] -pub struct Unstark { - pub _f: PhantomData, - pub _d: PhantomData, -} - -impl HasNamedColumns - for Unstark -{ - type Columns = Columns; -} - -const PUBLIC_INPUTS: usize = 0; - -impl< - F: RichField + Extendable, - const D: usize, - Columns: Sync + NumberOfColumns, - const COLUMNS: usize, - > Stark for Unstark -{ - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - _vars: &Self::EvaluationFrame, - _yield_constr: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - } - - fn eval_ext_circuit( - &self, - _builder: &mut CircuitBuilder, - _vars: &Self::EvaluationFrameTarget, - _yield_constr: &mut RecursiveConstraintConsumer, - ) { - } - - fn constraint_degree(&self) -> usize { 3 } -} +use crate::columns_view::columns_view_impl; +/// `NoColumns` is a `PhantomData` that supports all the traits and interafaces +/// that we are using for Columns. #[repr(C)] #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct NoColumns { _phantom: PhantomData, } columns_view_impl!(NoColumns); + +macro_rules! unstark { + ($name:ident, $view:ty) => { + type View = $view; + + mod constraints { + use super::View; + use crate::columns_view::NumberOfColumns; + + #[derive(Default, Clone, Copy, Debug)] + pub struct $name {} + + pub const COLUMNS: usize = View::<()>::NUMBER_OF_COLUMNS; + pub const PUBLIC_INPUTS: usize = 0; + + impl crate::expr::GenerateConstraints for $name { + type PublicInputs = crate::unstark::NoColumns; + type View = View; + } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } + } + } + + pub type $name = crate::expr::StarkFrom< + F, + constraints::$name, + { D }, + { constraints::COLUMNS }, + { constraints::PUBLIC_INPUTS }, + >; + }; +} + +pub(crate) use unstark; diff --git a/circuits/src/xor/stark.rs b/circuits/src/xor/stark.rs index c94882ae0..64d860f70 100644 --- a/circuits/src/xor/stark.rs +++ b/circuits/src/xor/stark.rs @@ -1,95 +1,56 @@ -use std::marker::PhantomData; +use core::fmt::Debug; -use expr::{Expr, ExprBuilder, StarkFrameTyped}; +use expr::Expr; use itertools::{chain, izip}; use mozak_circuits_derive::StarkNameDisplay; -use plonky2::field::extension::{Extendable, FieldExtension}; -use plonky2::field::packed::PackedField; -use plonky2::hash::hash_types::RichField; -use plonky2::iop::ext_target::ExtensionTarget; -use plonky2::plonk::circuit_builder::CircuitBuilder; -use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; -use starky::evaluation_frame::StarkFrame; -use starky::stark::Stark; use super::columns::XorColumnsView; -use crate::columns_view::{HasNamedColumns, NumberOfColumns}; -use crate::expr::{build_ext, build_packed, ConstraintBuilder}; +use crate::columns_view::NumberOfColumns; +use crate::expr::{ConstraintBuilder, GenerateConstraints, StarkFrom, Vars}; use crate::unstark::NoColumns; #[derive(Clone, Copy, Default, StarkNameDisplay)] #[allow(clippy::module_name_repetitions)] -pub struct XorStark { - pub _f: PhantomData, -} +pub struct XorConstraints {} -impl HasNamedColumns for XorStark { - type Columns = XorColumnsView; -} +#[allow(clippy::module_name_repetitions)] +pub type XorStark = + StarkFrom; const COLUMNS: usize = XorColumnsView::<()>::NUMBER_OF_COLUMNS; const PUBLIC_INPUTS: usize = 0; -fn generate_constraints<'a, T: Copy>( - vars: &StarkFrameTyped>, NoColumns>>, -) -> ConstraintBuilder> { - let lv = vars.local_values; - let mut constraints = ConstraintBuilder::default(); - - // We first convert both input and output to bit representation - // We then work with the bit representations to check the Xor result. - - // Check: bit representation of inputs and output contains either 0 or 1. - for bit_value in chain!(lv.limbs.a, lv.limbs.b, lv.limbs.out) { - constraints.always(bit_value.is_binary()); - } - - // Check: bit representation of inputs and output were generated correctly. - for (opx, opx_limbs) in izip![lv.execution, lv.limbs] { - constraints.always(Expr::reduce_with_powers(opx_limbs, 2) - opx); - } - - // Check: output bit representation is Xor of input a and b bit representations - for (a, b, out) in izip!(lv.limbs.a, lv.limbs.b, lv.limbs.out) { - // Xor behaves like addition in binary field, i.e. addition with wrap-around: - constraints.always((a + b - out) * (a + b - 2 - out)); - } - - constraints -} - -impl, const D: usize> Stark for XorStark { - type EvaluationFrame = StarkFrame - - where - FE: FieldExtension, - P: PackedField; - type EvaluationFrameTarget = - StarkFrame, ExtensionTarget, COLUMNS, PUBLIC_INPUTS>; - - fn eval_packed_generic( - &self, - vars: &Self::EvaluationFrame, - consumer: &mut ConstraintConsumer

, - ) where - FE: FieldExtension, - P: PackedField, { - let expr_builder = ExprBuilder::default(); - let constraints = generate_constraints(&expr_builder.to_typed_starkframe(vars)); - build_packed(constraints, consumer); - } - - fn constraint_degree(&self) -> usize { 3 } +impl GenerateConstraints for XorConstraints { + type PublicInputs = NoColumns; + type View = XorColumnsView; - fn eval_ext_circuit( + fn generate_constraints<'a, T: Copy + Debug>( &self, - circuit_builder: &mut CircuitBuilder, - vars: &Self::EvaluationFrameTarget, - consumer: &mut RecursiveConstraintConsumer, - ) { - let expr_builder = ExprBuilder::default(); - let constraints = generate_constraints(&expr_builder.to_typed_starkframe(vars)); - build_ext(constraints, circuit_builder, consumer); + vars: &Vars<'a, Self, T, COLUMNS, PUBLIC_INPUTS>, + ) -> ConstraintBuilder> { + let lv = vars.local_values; + let mut constraints = ConstraintBuilder::default(); + + // We first convert both input and output to bit representation + // We then work with the bit representations to check the Xor result. + + // Check: bit representation of inputs and output contains either 0 or 1. + for bit_value in chain!(lv.limbs.a, lv.limbs.b, lv.limbs.out) { + constraints.always(bit_value.is_binary()); + } + + // Check: bit representation of inputs and output were generated correctly. + for (opx, opx_limbs) in izip![lv.execution, lv.limbs] { + constraints.always(Expr::reduce_with_powers(opx_limbs, 2) - opx); + } + + // Check: output bit representation is Xor of input a and b bit representations + for (a, b, out) in izip!(lv.limbs.a, lv.limbs.b, lv.limbs.out) { + // Xor behaves like addition in binary field, i.e. addition with wrap-around: + constraints.always((a + b - out) * (a + b - 2 - out)); + } + + constraints } } diff --git a/expr/src/lib.rs b/expr/src/lib.rs index 49d52e2ce..11f26611b 100644 --- a/expr/src/lib.rs +++ b/expr/src/lib.rs @@ -203,38 +203,113 @@ impl ExprBuilder { // We don't actually need the first constraint, but it's useful to make the compiler yell // at us, if we mix things up. See the TODO about fixing `StarkEvaluationFrame` to // give direct access to its contents. - View: From<[Expr<'a, T>; N]> + FromIterator>, - PublicInputs: From<[Expr<'a, T>; N2]> + FromIterator>, { - // TODO: Fix `StarkEvaluationFrame` to give direct access to its contents, no - // need for the reference only access. - StarkFrameTyped { - local_values: vars - .get_local_values() - .iter() - .map(|&v| self.lit(v)) - .collect(), - next_values: vars - .get_next_values() - .iter() - .map(|&v| self.lit(v)) - .collect(), - public_inputs: vars - .get_public_inputs() - .iter() - .map(|&v| self.lit(T::from(v))) - .collect(), - } + View: FromIterator>, + PublicInputs: FromIterator>, { + // NOTE: Rust needs to know all the intermediate types + let frame: StarkFrameTyped, Vec> = StarkFrameTyped::from(vars); + let frame: StarkFrameTyped, Vec> = frame.map_public_inputs(|v| T::from(v)); + self.inject_starkframe(frame) + } + + /// Inject `StarkFrameTypes` into the `ExprBuilder`. + /// + /// This function will decompose `StarkFrameTyped` using the `IntoIterator` + /// instances of `View` and `PublicInputs` and then recompose them back + /// using `FromIterator` instances of `MappedView` and `MappedPublicInputs` + /// respectively. + pub fn inject_starkframe< + 'a, + T: 'a, + U: 'a, + View, + PublicInputs, + MappedView, + MappedPublicInputs, + >( + &'a self, + frame: StarkFrameTyped, + ) -> StarkFrameTyped + where + View: IntoIterator, + PublicInputs: IntoIterator, + MappedView: FromIterator>, + MappedPublicInputs: FromIterator>, { + frame + .map_view(|v| self.lit(v)) + .map_public_inputs(|v| self.lit(v)) } } /// A helper around `StarkFrame` to add types #[derive(Debug)] -pub struct StarkFrameTyped { - pub local_values: Row, - pub next_values: Row, +pub struct StarkFrameTyped { + pub local_values: View, + pub next_values: View, pub public_inputs: PublicInputs, } +impl StarkFrameTyped { + pub fn from_values(lv: &[T], nv: &[T], pis: &[U]) -> Self + where + T: Copy, + U: Copy, + View: FromIterator, + PublicInputs: FromIterator, { + Self { + local_values: lv.iter().copied().collect(), + next_values: nv.iter().copied().collect(), + public_inputs: pis.iter().copied().collect(), + } + } + + pub fn map_view( + self, + mut f: F, + ) -> StarkFrameTyped + where + View: IntoIterator, + MappedView: FromIterator, + F: FnMut(T) -> B, { + StarkFrameTyped { + local_values: self.local_values.into_iter().map(&mut f).collect(), + next_values: self.next_values.into_iter().map(f).collect(), + public_inputs: self.public_inputs, + } + } + + pub fn map_public_inputs( + self, + f: F, + ) -> StarkFrameTyped + where + PublicInputs: IntoIterator, + MappedPublicInputs: FromIterator, + F: FnMut(U) -> C, { + StarkFrameTyped { + local_values: self.local_values, + next_values: self.next_values, + public_inputs: self.public_inputs.into_iter().map(f).collect(), + } + } +} + +impl<'a, T, U, const N: usize, const N2: usize, View, PublicInputs> + From<&'a StarkFrame> for StarkFrameTyped +where + T: Copy + Default, + U: Copy + Default, + View: From<[T; N]> + FromIterator, + PublicInputs: From<[U; N2]> + FromIterator, +{ + fn from(value: &'a StarkFrame) -> Self { + Self::from_values( + value.get_local_values(), + value.get_next_values(), + value.get_public_inputs(), + ) + } +} + /// Enum for binary operations #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] pub enum BinOp {