From 0ac0975d9576fd85816bfa491d88090832dd17e6 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 24 Aug 2022 18:03:13 -0700 Subject: [PATCH 01/97] RandomAccessGate documentation --- plonky2/src/gates/random_access.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/plonky2/src/gates/random_access.rs b/plonky2/src/gates/random_access.rs index 2df392bc..b771ba86 100644 --- a/plonky2/src/gates/random_access.rs +++ b/plonky2/src/gates/random_access.rs @@ -24,9 +24,15 @@ use crate::plonk::vars::{ /// A gate for checking that a particular element of a list matches a given value. #[derive(Copy, Clone, Debug)] pub struct RandomAccessGate, const D: usize> { + // Number of bits in the index (log2 of the list size). pub bits: usize, + + // How many separate copies are packed into one gate. pub num_copies: usize, + + // Leftover wires are used as global scratch space to store constants. pub num_extra_constants: usize, + _phantom: PhantomData, } @@ -41,13 +47,18 @@ impl, const D: usize> RandomAccessGate { } pub fn new_from_config(config: &CircuitConfig, bits: usize) -> Self { + // We can access a list of 2^bits elements. let vec_size = 1 << bits; - // Need `(2 + vec_size) * num_copies` routed wires + + // We need `(2 + vec_size) * num_copies` routed wires. let max_copies = (config.num_routed_wires / (2 + vec_size)).min( - // Need `(2 + vec_size + bits) * num_copies` wires + // We need `(2 + vec_size + bits) * num_copies` wires in total. config.num_wires / (2 + vec_size + bits), ); + + // Any leftover wires can be used for constants. let max_extra_constants = config.num_routed_wires - (2 + vec_size) * max_copies; + Self::new( max_copies, bits, @@ -55,20 +66,24 @@ impl, const D: usize> RandomAccessGate { ) } + // Length of the list being accessed. fn vec_size(&self) -> usize { 1 << self.bits } + // For each copy, a wire containing the claimed index of the element. pub fn wire_access_index(&self, copy: usize) -> usize { debug_assert!(copy < self.num_copies); (2 + self.vec_size()) * copy } + // For each copy, a wire containing the element claimed to be at the index. pub fn wire_claimed_element(&self, copy: usize) -> usize { debug_assert!(copy < self.num_copies); (2 + self.vec_size()) * copy + 1 } + // For each copy, wires containing the entire list. pub fn wire_list_item(&self, i: usize, copy: usize) -> usize { debug_assert!(i < self.vec_size()); debug_assert!(copy < self.num_copies); @@ -84,6 +99,7 @@ impl, const D: usize> RandomAccessGate { self.start_extra_constants() + i } + // All above wires are routed. pub fn num_routed_wires(&self) -> usize { self.start_extra_constants() + self.num_extra_constants } From b93f92e67e8ed13ee97b1eb14e1d769e0670c275 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 25 Aug 2022 09:24:53 -0700 Subject: [PATCH 02/97] comment fix --- plonky2/src/gates/random_access.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plonky2/src/gates/random_access.rs b/plonky2/src/gates/random_access.rs index b771ba86..40ac955c 100644 --- a/plonky2/src/gates/random_access.rs +++ b/plonky2/src/gates/random_access.rs @@ -24,13 +24,13 @@ use crate::plonk::vars::{ /// A gate for checking that a particular element of a list matches a given value. #[derive(Copy, Clone, Debug)] pub struct RandomAccessGate, const D: usize> { - // Number of bits in the index (log2 of the list size). + /// Number of bits in the index (log2 of the list size). pub bits: usize, - // How many separate copies are packed into one gate. + /// How many separate copies are packed into one gate. pub num_copies: usize, - // Leftover wires are used as global scratch space to store constants. + /// Leftover wires are used as global scratch space to store constants. pub num_extra_constants: usize, _phantom: PhantomData, @@ -218,10 +218,12 @@ impl, const D: usize> Gate for RandomAccessGa .collect() } + // Check that the one remaining element after the folding is the claimed element. debug_assert_eq!(list_items.len(), 1); constraints.push(builder.sub_extension(list_items[0], claimed_element)); } + // Check the constant values. constraints.extend((0..self.num_extra_constants).map(|i| { builder.sub_extension( vars.local_constants[i], From ba28919d66f45abc3eb95ee8601038d7a31c06a8 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 25 Aug 2022 14:00:28 -0700 Subject: [PATCH 03/97] more comment fix --- plonky2/src/gates/random_access.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plonky2/src/gates/random_access.rs b/plonky2/src/gates/random_access.rs index 40ac955c..fa365f16 100644 --- a/plonky2/src/gates/random_access.rs +++ b/plonky2/src/gates/random_access.rs @@ -66,24 +66,24 @@ impl, const D: usize> RandomAccessGate { ) } - // Length of the list being accessed. + /// Length of the list being accessed. fn vec_size(&self) -> usize { 1 << self.bits } - // For each copy, a wire containing the claimed index of the element. + /// For each copy, a wire containing the claimed index of the element. pub fn wire_access_index(&self, copy: usize) -> usize { debug_assert!(copy < self.num_copies); (2 + self.vec_size()) * copy } - // For each copy, a wire containing the element claimed to be at the index. + /// For each copy, a wire containing the element claimed to be at the index. pub fn wire_claimed_element(&self, copy: usize) -> usize { debug_assert!(copy < self.num_copies); (2 + self.vec_size()) * copy + 1 } - // For each copy, wires containing the entire list. + /// For each copy, wires containing the entire list. pub fn wire_list_item(&self, i: usize, copy: usize) -> usize { debug_assert!(i < self.vec_size()); debug_assert!(copy < self.num_copies); @@ -99,7 +99,7 @@ impl, const D: usize> RandomAccessGate { self.start_extra_constants() + i } - // All above wires are routed. + /// All above wires are routed. pub fn num_routed_wires(&self) -> usize { self.start_extra_constants() + self.num_extra_constants } From 336046d87211563d3b87135a97d737d35c739f03 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 2 Sep 2022 12:01:16 -0700 Subject: [PATCH 04/97] cleanup for interpolation --- plonky2/src/fri/recursive_verifier.rs | 2 +- plonky2/src/gadgets/interpolation.rs | 178 ------- plonky2/src/gadgets/mod.rs | 1 - .../src/gates/high_degree_interpolation.rs | 363 ++++++++++++++ plonky2/src/gates/interpolation.rs | 453 ++++++------------ plonky2/src/gates/low_degree_interpolation.rs | 2 +- plonky2/src/gates/mod.rs | 1 + 7 files changed, 501 insertions(+), 499 deletions(-) delete mode 100644 plonky2/src/gadgets/interpolation.rs create mode 100644 plonky2/src/gates/high_degree_interpolation.rs diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 1a3739b4..0526ed7e 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -10,7 +10,7 @@ use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpenin use crate::fri::{FriConfig, FriParams}; use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; -use crate::gates::interpolation::HighDegreeInterpolationGate; +use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::gates::random_access::RandomAccessGate; use crate::hash::hash_types::MerkleCapTarget; diff --git a/plonky2/src/gadgets/interpolation.rs b/plonky2/src/gadgets/interpolation.rs deleted file mode 100644 index b22f3b59..00000000 --- a/plonky2/src/gadgets/interpolation.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::ops::Range; - -use plonky2_field::extension::Extendable; - -use crate::gates::gate::Gate; -use crate::hash::hash_types::RichField; -use crate::iop::ext_target::ExtensionTarget; -use crate::iop::target::Target; -use crate::plonk::circuit_builder::CircuitBuilder; - -/// Trait for gates which interpolate a polynomial, whose points are a (base field) coset of the multiplicative subgroup -/// with the given size, and whose values are extension field elements, given by input wires. -/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. -pub(crate) trait InterpolationGate, const D: usize>: - Gate + Copy -{ - fn new(subgroup_bits: usize) -> Self; - - fn num_points(&self) -> usize; - - /// Wire index of the coset shift. - fn wire_shift(&self) -> usize { - 0 - } - - fn start_values(&self) -> usize { - 1 - } - - /// Wire indices of the `i`th interpolant value. - fn wires_value(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_values() + i * D; - start..start + D - } - - fn start_evaluation_point(&self) -> usize { - self.start_values() + self.num_points() * D - } - - /// Wire indices of the point to evaluate the interpolant at. - fn wires_evaluation_point(&self) -> Range { - let start = self.start_evaluation_point(); - start..start + D - } - - fn start_evaluation_value(&self) -> usize { - self.start_evaluation_point() + D - } - - /// Wire indices of the interpolated value. - fn wires_evaluation_value(&self) -> Range { - let start = self.start_evaluation_value(); - start..start + D - } - - fn start_coeffs(&self) -> usize { - self.start_evaluation_value() + D - } - - /// The number of routed wires required in the typical usage of this gate, where the points to - /// interpolate, the evaluation point, and the corresponding value are all routed. - fn num_routed_wires(&self) -> usize { - self.start_coeffs() - } - - /// Wire indices of the interpolant's `i`th coefficient. - fn wires_coeff(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_coeffs() + i * D; - start..start + D - } - - fn end_coeffs(&self) -> usize { - self.start_coeffs() + D * self.num_points() - } -} - -impl, const D: usize> CircuitBuilder { - /// Interpolates a polynomial, whose points are a coset of the multiplicative subgroup with the - /// given size, and whose values are given. Returns the evaluation of the interpolant at - /// `evaluation_point`. - pub(crate) fn interpolate_coset>( - &mut self, - subgroup_bits: usize, - coset_shift: Target, - values: &[ExtensionTarget], - evaluation_point: ExtensionTarget, - ) -> ExtensionTarget { - let gate = G::new(subgroup_bits); - let row = self.add_gate(gate, vec![]); - self.connect(coset_shift, Target::wire(row, gate.wire_shift())); - for (i, &v) in values.iter().enumerate() { - self.connect_extension(v, ExtensionTarget::from_range(row, gate.wires_value(i))); - } - self.connect_extension( - evaluation_point, - ExtensionTarget::from_range(row, gate.wires_evaluation_point()), - ); - - ExtensionTarget::from_range(row, gate.wires_evaluation_value()) - } -} - -#[cfg(test)] -mod tests { - use anyhow::Result; - use plonky2_field::extension::FieldExtension; - use plonky2_field::interpolation::interpolant; - use plonky2_field::types::Field; - - use crate::gates::interpolation::HighDegreeInterpolationGate; - use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; - use crate::iop::witness::PartialWitness; - use crate::plonk::circuit_builder::CircuitBuilder; - use crate::plonk::circuit_data::CircuitConfig; - use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; - use crate::plonk::verifier::verify; - - #[test] - fn test_interpolate() -> Result<()> { - const D: usize = 2; - type C = PoseidonGoldilocksConfig; - type F = >::F; - type FF = >::FE; - let config = CircuitConfig::standard_recursion_config(); - let pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); - - let subgroup_bits = 2; - let len = 1 << subgroup_bits; - let coset_shift = F::rand(); - let g = F::primitive_root_of_unity(subgroup_bits); - let points = F::cyclic_subgroup_coset_known_order(g, coset_shift, len); - let values = FF::rand_vec(len); - - let homogeneous_points = points - .iter() - .zip(values.iter()) - .map(|(&a, &b)| (>::from_basefield(a), b)) - .collect::>(); - - let true_interpolant = interpolant(&homogeneous_points); - - let z = FF::rand(); - let true_eval = true_interpolant.eval(z); - - let coset_shift_target = builder.constant(coset_shift); - - let value_targets = values - .iter() - .map(|&v| (builder.constant_extension(v))) - .collect::>(); - - let zt = builder.constant_extension(z); - - let eval_hd = builder.interpolate_coset::>( - subgroup_bits, - coset_shift_target, - &value_targets, - zt, - ); - let eval_ld = builder.interpolate_coset::>( - subgroup_bits, - coset_shift_target, - &value_targets, - zt, - ); - let true_eval_target = builder.constant_extension(true_eval); - builder.connect_extension(eval_hd, true_eval_target); - builder.connect_extension(eval_ld, true_eval_target); - - let data = builder.build::(); - let proof = data.prove(pw)?; - - verify(proof, &data.verifier_only, &data.common) - } -} diff --git a/plonky2/src/gadgets/mod.rs b/plonky2/src/gadgets/mod.rs index 6309eb3d..a3e50c4e 100644 --- a/plonky2/src/gadgets/mod.rs +++ b/plonky2/src/gadgets/mod.rs @@ -1,7 +1,6 @@ pub mod arithmetic; pub mod arithmetic_extension; pub mod hash; -pub mod interpolation; pub mod polynomial; pub mod random_access; pub mod range_check; diff --git a/plonky2/src/gates/high_degree_interpolation.rs b/plonky2/src/gates/high_degree_interpolation.rs new file mode 100644 index 00000000..1c78a6e6 --- /dev/null +++ b/plonky2/src/gates/high_degree_interpolation.rs @@ -0,0 +1,363 @@ +use std::marker::PhantomData; +use std::ops::Range; + +use plonky2_field::extension::algebra::PolynomialCoeffsAlgebra; +use plonky2_field::extension::{Extendable, FieldExtension}; +use plonky2_field::interpolation::interpolant; +use plonky2_field::polynomial::PolynomialCoeffs; + +use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; +use crate::gates::gate::Gate; +use crate::gates::interpolation::InterpolationGate; +use crate::gates::util::StridedConstraintConsumer; +use crate::hash::hash_types::RichField; +use crate::iop::ext_target::ExtensionTarget; +use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartitionWitness, Witness}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; + +/// One of the instantiations of `InterpolationGate`: allows constraints of variable +/// degree, up to `1<, const D: usize> { + pub subgroup_bits: usize, + _phantom: PhantomData, +} + +impl, const D: usize> InterpolationGate + for HighDegreeInterpolationGate +{ + fn new(subgroup_bits: usize) -> Self { + Self { + subgroup_bits, + _phantom: PhantomData, + } + } + + fn num_points(&self) -> usize { + 1 << self.subgroup_bits + } +} + +impl, const D: usize> HighDegreeInterpolationGate { + /// End of wire indices, exclusive. + fn end(&self) -> usize { + self.start_coeffs() + self.num_points() * D + } + + /// The domain of the points we're interpolating. + fn coset(&self, shift: F) -> impl Iterator { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + // Speed matters here, so we avoid `cyclic_subgroup_coset_known_order` which allocates. + g.powers().take(size).map(move |x| x * shift) + } + + /// The domain of the points we're interpolating. + fn coset_ext(&self, shift: F::Extension) -> impl Iterator { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + g.powers().take(size).map(move |x| shift.scalar_mul(x)) + } + + /// The domain of the points we're interpolating. + fn coset_ext_circuit( + &self, + builder: &mut CircuitBuilder, + shift: ExtensionTarget, + ) -> Vec> { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + g.powers() + .take(size) + .map(move |x| { + let subgroup_element = builder.constant(x); + builder.scalar_mul_ext(subgroup_element, shift) + }) + .collect() + } +} + +impl, const D: usize> Gate + for HighDegreeInterpolationGate +{ + fn id(&self) -> String { + format!("{:?}", self, D) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) + .collect(); + let interpolant = PolynomialCoeffsAlgebra::new(coeffs); + + let coset = self.coset_ext(vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { + let value = vars.get_local_ext_algebra(self.wires_value(i)); + let computed_value = interpolant.eval_base(point); + constraints.extend(&(value - computed_value).to_basefield_array()); + } + + let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval(evaluation_point); + constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + + constraints + } + + fn eval_unfiltered_base_one( + &self, + vars: EvaluationVarsBase, + mut yield_constr: StridedConstraintConsumer, + ) { + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext(self.wires_coeff(i))) + .collect(); + let interpolant = PolynomialCoeffs::new(coeffs); + + let coset = self.coset(vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { + let value = vars.get_local_ext(self.wires_value(i)); + let computed_value = interpolant.eval_base(point); + yield_constr.many((value - computed_value).to_basefield_array()); + } + + let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); + let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval(evaluation_point); + yield_constr.many((evaluation_value - computed_evaluation_value).to_basefield_array()); + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) + .collect(); + let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); + + let coset = self.coset_ext_circuit(builder, vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { + let value = vars.get_local_ext_algebra(self.wires_value(i)); + let computed_value = interpolant.eval_scalar(builder, point); + constraints.extend( + &builder + .sub_ext_algebra(value, computed_value) + .to_ext_target_array(), + ); + } + + let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval(builder, evaluation_point); + constraints.extend( + &builder + .sub_ext_algebra(evaluation_value, computed_evaluation_value) + .to_ext_target_array(), + ); + + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec>> { + let gen = InterpolationGenerator:: { + row, + gate: *self, + _phantom: PhantomData, + }; + vec![Box::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + self.end() + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + // The highest power of x is `num_points - 1`, and then multiplication by the coefficient + // adds 1. + self.num_points() + } + + fn num_constraints(&self) -> usize { + // num_points * D constraints to check for consistency between the coefficients and the + // point-value pairs, plus D constraints for the evaluation value. + self.num_points() * D + D + } +} + +#[derive(Debug)] +struct InterpolationGenerator, const D: usize> { + row: usize, + gate: HighDegreeInterpolationGate, + _phantom: PhantomData, +} + +impl, const D: usize> SimpleGenerator + for InterpolationGenerator +{ + fn dependencies(&self) -> Vec { + let local_target = |column| { + Target::Wire(Wire { + row: self.row, + column, + }) + }; + + let local_targets = |columns: Range| columns.map(local_target); + + let num_points = self.gate.num_points(); + let mut deps = Vec::with_capacity(1 + D + num_points * D); + + deps.push(local_target(self.gate.wire_shift())); + deps.extend(local_targets(self.gate.wires_evaluation_point())); + for i in 0..num_points { + deps.extend(local_targets(self.gate.wires_value(i))); + } + deps + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let local_wire = |column| Wire { + row: self.row, + column, + }; + + let get_local_wire = |column| witness.get_wire(local_wire(column)); + + let get_local_ext = |wire_range: Range| { + debug_assert_eq!(wire_range.len(), D); + let values = wire_range.map(get_local_wire).collect::>(); + let arr = values.try_into().unwrap(); + F::Extension::from_basefield_array(arr) + }; + + // Compute the interpolant. + let points = self.gate.coset(get_local_wire(self.gate.wire_shift())); + let points = points + .into_iter() + .enumerate() + .map(|(i, point)| (point.into(), get_local_ext(self.gate.wires_value(i)))) + .collect::>(); + let interpolant = interpolant(&points); + + for (i, &coeff) in interpolant.coeffs.iter().enumerate() { + let wires = self.gate.wires_coeff(i).map(local_wire); + out_buffer.set_ext_wires(wires, coeff); + } + + let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); + let evaluation_value = interpolant.eval(evaluation_point); + let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); + out_buffer.set_ext_wires(evaluation_value_wires, evaluation_value); + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use anyhow::Result; + use plonky2_field::goldilocks_field::GoldilocksField; + use plonky2_field::polynomial::PolynomialCoeffs; + use plonky2_field::types::Field; + + use crate::gadgets::interpolation::InterpolationGate; + use crate::gates::gate::Gate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; + use crate::hash::hash_types::HashOut; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::vars::EvaluationVars; + + #[test] + fn wire_indices() { + let gate = HighDegreeInterpolationGate:: { + subgroup_bits: 1, + _phantom: PhantomData, + }; + + // The exact indices aren't really important, but we want to make sure we don't have any + // overlaps or gaps. + assert_eq!(gate.wire_shift(), 0); + assert_eq!(gate.wires_value(0), 1..5); + assert_eq!(gate.wires_value(1), 5..9); + assert_eq!(gate.wires_evaluation_point(), 9..13); + assert_eq!(gate.wires_evaluation_value(), 13..17); + assert_eq!(gate.wires_coeff(0), 17..21); + assert_eq!(gate.wires_coeff(1), 21..25); + assert_eq!(gate.num_wires(), 25); + } + + #[test] + fn low_degree() { + test_low_degree::(HighDegreeInterpolationGate::new(2)); + } + + #[test] + fn eval_fns() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + test_eval_fns::(HighDegreeInterpolationGate::new(2)) + } + + #[test] + fn test_gate_constraint() { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type FF = >::FE; + + /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. + fn get_wires( + gate: &HighDegreeInterpolationGate, + shift: F, + coeffs: PolynomialCoeffs, + eval_point: FF, + ) -> Vec { + let points = gate.coset(shift); + let mut v = vec![shift]; + for x in points { + v.extend(coeffs.eval(x.into()).0); + } + v.extend(eval_point.0); + v.extend(coeffs.eval(eval_point).0); + for i in 0..coeffs.len() { + v.extend(coeffs.coeffs[i].0); + } + v.iter().map(|&x| x.into()).collect() + } + + // Get a working row for InterpolationGate. + let shift = F::rand(); + let coeffs = PolynomialCoeffs::new(vec![FF::rand(), FF::rand()]); + let eval_point = FF::rand(); + let gate = HighDegreeInterpolationGate::::new(1); + let vars = EvaluationVars { + local_constants: &[], + local_wires: &get_wires(&gate, shift, coeffs, eval_point), + public_inputs_hash: &HashOut::rand(), + }; + + assert!( + gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), + "Gate constraints are not satisfied." + ); + } +} diff --git a/plonky2/src/gates/interpolation.rs b/plonky2/src/gates/interpolation.rs index a619d1f2..d417fa6b 100644 --- a/plonky2/src/gates/interpolation.rs +++ b/plonky2/src/gates/interpolation.rs @@ -1,361 +1,178 @@ -use std::marker::PhantomData; use std::ops::Range; -use plonky2_field::extension::algebra::PolynomialCoeffsAlgebra; -use plonky2_field::extension::{Extendable, FieldExtension}; -use plonky2_field::interpolation::interpolant; -use plonky2_field::polynomial::PolynomialCoeffs; +use plonky2_field::extension::Extendable; -use crate::gadgets::interpolation::InterpolationGate; -use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; -use crate::gates::util::StridedConstraintConsumer; use crate::hash::hash_types::RichField; use crate::iop::ext_target::ExtensionTarget; -use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; use crate::iop::target::Target; -use crate::iop::wire::Wire; -use crate::iop::witness::{PartitionWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; -use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; -/// Interpolation gate with constraints of degree at most `1<, const D: usize> { - pub subgroup_bits: usize, - _phantom: PhantomData, -} - -impl, const D: usize> InterpolationGate - for HighDegreeInterpolationGate +/// Trait for gates which interpolate a polynomial, whose points are a (base field) coset of the multiplicative subgroup +/// with the given size, and whose values are extension field elements, given by input wires. +/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. +pub(crate) trait InterpolationGate, const D: usize>: + Gate + Copy { - fn new(subgroup_bits: usize) -> Self { - Self { - subgroup_bits, - _phantom: PhantomData, - } - } + fn new(subgroup_bits: usize) -> Self; - fn num_points(&self) -> usize { - 1 << self.subgroup_bits - } -} + fn num_points(&self) -> usize; -impl, const D: usize> HighDegreeInterpolationGate { - /// End of wire indices, exclusive. - fn end(&self) -> usize { - self.start_coeffs() + self.num_points() * D - } - - /// The domain of the points we're interpolating. - fn coset(&self, shift: F) -> impl Iterator { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - // Speed matters here, so we avoid `cyclic_subgroup_coset_known_order` which allocates. - g.powers().take(size).map(move |x| x * shift) - } - - /// The domain of the points we're interpolating. - fn coset_ext(&self, shift: F::Extension) -> impl Iterator { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - g.powers().take(size).map(move |x| shift.scalar_mul(x)) - } - - /// The domain of the points we're interpolating. - fn coset_ext_circuit( - &self, - builder: &mut CircuitBuilder, - shift: ExtensionTarget, - ) -> Vec> { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - g.powers() - .take(size) - .map(move |x| { - let subgroup_element = builder.constant(x); - builder.scalar_mul_ext(subgroup_element, shift) - }) - .collect() - } -} - -impl, const D: usize> Gate - for HighDegreeInterpolationGate -{ - fn id(&self) -> String { - format!("{:?}", self, D) - } - - fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { - let mut constraints = Vec::with_capacity(self.num_constraints()); - - let coeffs = (0..self.num_points()) - .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) - .collect(); - let interpolant = PolynomialCoeffsAlgebra::new(coeffs); - - let coset = self.coset_ext(vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { - let value = vars.get_local_ext_algebra(self.wires_value(i)); - let computed_value = interpolant.eval_base(point); - constraints.extend((value - computed_value).to_basefield_array()); - } - - let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); - let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(evaluation_point); - constraints.extend((evaluation_value - computed_evaluation_value).to_basefield_array()); - - constraints - } - - fn eval_unfiltered_base_one( - &self, - vars: EvaluationVarsBase, - mut yield_constr: StridedConstraintConsumer, - ) { - let coeffs = (0..self.num_points()) - .map(|i| vars.get_local_ext(self.wires_coeff(i))) - .collect(); - let interpolant = PolynomialCoeffs::new(coeffs); - - let coset = self.coset(vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { - let value = vars.get_local_ext(self.wires_value(i)); - let computed_value = interpolant.eval_base(point); - yield_constr.many((value - computed_value).to_basefield_array()); - } - - let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); - let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(evaluation_point); - yield_constr.many((evaluation_value - computed_evaluation_value).to_basefield_array()); - } - - fn eval_unfiltered_circuit( - &self, - builder: &mut CircuitBuilder, - vars: EvaluationTargets, - ) -> Vec> { - let mut constraints = Vec::with_capacity(self.num_constraints()); - - let coeffs = (0..self.num_points()) - .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) - .collect(); - let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); - - let coset = self.coset_ext_circuit(builder, vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { - let value = vars.get_local_ext_algebra(self.wires_value(i)); - let computed_value = interpolant.eval_scalar(builder, point); - constraints.extend( - builder - .sub_ext_algebra(value, computed_value) - .to_ext_target_array(), - ); - } - - let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); - let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(builder, evaluation_point); - constraints.extend( - builder - .sub_ext_algebra(evaluation_value, computed_evaluation_value) - .to_ext_target_array(), - ); - - constraints - } - - fn generators(&self, row: usize, _local_constants: &[F]) -> Vec>> { - let gen = InterpolationGenerator:: { - row, - gate: *self, - _phantom: PhantomData, - }; - vec![Box::new(gen.adapter())] - } - - fn num_wires(&self) -> usize { - self.end() - } - - fn num_constants(&self) -> usize { + /// Wire index of the coset shift. + fn wire_shift(&self) -> usize { 0 } - fn degree(&self) -> usize { - // The highest power of x is `num_points - 1`, and then multiplication by the coefficient - // adds 1. - self.num_points() + fn start_values(&self) -> usize { + 1 } - fn num_constraints(&self) -> usize { - // num_points * D constraints to check for consistency between the coefficients and the - // point-value pairs, plus D constraints for the evaluation value. - self.num_points() * D + D + /// Wire indices of the `i`th interpolant value. + fn wires_value(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_values() + i * D; + start..start + D + } + + fn start_evaluation_point(&self) -> usize { + self.start_values() + self.num_points() * D + } + + /// Wire indices of the point to evaluate the interpolant at. + fn wires_evaluation_point(&self) -> Range { + let start = self.start_evaluation_point(); + start..start + D + } + + fn start_evaluation_value(&self) -> usize { + self.start_evaluation_point() + D + } + + /// Wire indices of the interpolated value. + fn wires_evaluation_value(&self) -> Range { + let start = self.start_evaluation_value(); + start..start + D + } + + fn start_coeffs(&self) -> usize { + self.start_evaluation_value() + D + } + + /// The number of routed wires required in the typical usage of this gate, where the points to + /// interpolate, the evaluation point, and the corresponding value are all routed. + fn num_routed_wires(&self) -> usize { + self.start_coeffs() + } + + /// Wire indices of the interpolant's `i`th coefficient. + fn wires_coeff(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_coeffs() + i * D; + start..start + D + } + + fn end_coeffs(&self) -> usize { + self.start_coeffs() + D * self.num_points() } } -#[derive(Debug)] -struct InterpolationGenerator, const D: usize> { - row: usize, - gate: HighDegreeInterpolationGate, - _phantom: PhantomData, -} - -impl, const D: usize> SimpleGenerator - for InterpolationGenerator -{ - fn dependencies(&self) -> Vec { - let local_target = |column| { - Target::Wire(Wire { - row: self.row, - column, - }) - }; - - let local_targets = |columns: Range| columns.map(local_target); - - let num_points = self.gate.num_points(); - let mut deps = Vec::with_capacity(1 + D + num_points * D); - - deps.push(local_target(self.gate.wire_shift())); - deps.extend(local_targets(self.gate.wires_evaluation_point())); - for i in 0..num_points { - deps.extend(local_targets(self.gate.wires_value(i))); +impl, const D: usize> CircuitBuilder { + /// Interpolates a polynomial, whose points are a coset of the multiplicative subgroup with the + /// given size, and whose values are given. Returns the evaluation of the interpolant at + /// `evaluation_point`. + pub(crate) fn interpolate_coset>( + &mut self, + subgroup_bits: usize, + coset_shift: Target, + values: &[ExtensionTarget], + evaluation_point: ExtensionTarget, + ) -> ExtensionTarget { + let gate = G::new(subgroup_bits); + let row = self.add_gate(gate, vec![]); + self.connect(coset_shift, Target::wire(row, gate.wire_shift())); + for (i, &v) in values.iter().enumerate() { + self.connect_extension(v, ExtensionTarget::from_range(row, gate.wires_value(i))); } - deps - } + self.connect_extension( + evaluation_point, + ExtensionTarget::from_range(row, gate.wires_evaluation_point()), + ); - fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let local_wire = |column| Wire { - row: self.row, - column, - }; - - let get_local_wire = |column| witness.get_wire(local_wire(column)); - - let get_local_ext = |wire_range: Range| { - debug_assert_eq!(wire_range.len(), D); - let values = wire_range.map(get_local_wire).collect::>(); - let arr = values.try_into().unwrap(); - F::Extension::from_basefield_array(arr) - }; - - // Compute the interpolant. - let points = self.gate.coset(get_local_wire(self.gate.wire_shift())); - let points = points - .into_iter() - .enumerate() - .map(|(i, point)| (point.into(), get_local_ext(self.gate.wires_value(i)))) - .collect::>(); - let interpolant = interpolant(&points); - - for (i, &coeff) in interpolant.coeffs.iter().enumerate() { - let wires = self.gate.wires_coeff(i).map(local_wire); - out_buffer.set_ext_wires(wires, coeff); - } - - let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); - let evaluation_value = interpolant.eval(evaluation_point); - let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); - out_buffer.set_ext_wires(evaluation_value_wires, evaluation_value); + ExtensionTarget::from_range(row, gate.wires_evaluation_value()) } } #[cfg(test)] mod tests { - use std::marker::PhantomData; - use anyhow::Result; - use plonky2_field::goldilocks_field::GoldilocksField; - use plonky2_field::polynomial::PolynomialCoeffs; + use plonky2_field::extension::FieldExtension; + use plonky2_field::interpolation::interpolant; use plonky2_field::types::Field; - use crate::gadgets::interpolation::InterpolationGate; - use crate::gates::gate::Gate; - use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; - use crate::gates::interpolation::HighDegreeInterpolationGate; - use crate::hash::hash_types::HashOut; + use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; + use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; + use crate::iop::witness::PartialWitness; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::CircuitConfig; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; - use crate::plonk::vars::EvaluationVars; + use crate::plonk::verifier::verify; #[test] - fn wire_indices() { - let gate = HighDegreeInterpolationGate:: { - subgroup_bits: 1, - _phantom: PhantomData, - }; - - // The exact indices aren't really important, but we want to make sure we don't have any - // overlaps or gaps. - assert_eq!(gate.wire_shift(), 0); - assert_eq!(gate.wires_value(0), 1..5); - assert_eq!(gate.wires_value(1), 5..9); - assert_eq!(gate.wires_evaluation_point(), 9..13); - assert_eq!(gate.wires_evaluation_value(), 13..17); - assert_eq!(gate.wires_coeff(0), 17..21); - assert_eq!(gate.wires_coeff(1), 21..25); - assert_eq!(gate.num_wires(), 25); - } - - #[test] - fn low_degree() { - test_low_degree::(HighDegreeInterpolationGate::new(2)); - } - - #[test] - fn eval_fns() -> Result<()> { - const D: usize = 2; - type C = PoseidonGoldilocksConfig; - type F = >::F; - test_eval_fns::(HighDegreeInterpolationGate::new(2)) - } - - #[test] - fn test_gate_constraint() { + fn test_interpolate() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = >::F; type FF = >::FE; + let config = CircuitConfig::standard_recursion_config(); + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); - /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. - fn get_wires( - gate: &HighDegreeInterpolationGate, - shift: F, - coeffs: PolynomialCoeffs, - eval_point: FF, - ) -> Vec { - let points = gate.coset(shift); - let mut v = vec![shift]; - for x in points { - v.extend(coeffs.eval(x.into()).0); - } - v.extend(eval_point.0); - v.extend(coeffs.eval(eval_point).0); - for i in 0..coeffs.len() { - v.extend(coeffs.coeffs[i].0); - } - v.iter().map(|&x| x.into()).collect() - } + let subgroup_bits = 2; + let len = 1 << subgroup_bits; + let coset_shift = F::rand(); + let g = F::primitive_root_of_unity(subgroup_bits); + let points = F::cyclic_subgroup_coset_known_order(g, coset_shift, len); + let values = FF::rand_vec(len); - // Get a working row for InterpolationGate. - let shift = F::rand(); - let coeffs = PolynomialCoeffs::new(vec![FF::rand(), FF::rand()]); - let eval_point = FF::rand(); - let gate = HighDegreeInterpolationGate::::new(1); - let vars = EvaluationVars { - local_constants: &[], - local_wires: &get_wires(&gate, shift, coeffs, eval_point), - public_inputs_hash: &HashOut::rand(), - }; + let homogeneous_points = points + .iter() + .zip(values.iter()) + .map(|(&a, &b)| (>::from_basefield(a), b)) + .collect::>(); - assert!( - gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), - "Gate constraints are not satisfied." + let true_interpolant = interpolant(&homogeneous_points); + + let z = FF::rand(); + let true_eval = true_interpolant.eval(z); + + let coset_shift_target = builder.constant(coset_shift); + + let value_targets = values + .iter() + .map(|&v| (builder.constant_extension(v))) + .collect::>(); + + let zt = builder.constant_extension(z); + + let eval_hd = builder.interpolate_coset::>( + subgroup_bits, + coset_shift_target, + &value_targets, + zt, ); + let eval_ld = builder.interpolate_coset::>( + subgroup_bits, + coset_shift_target, + &value_targets, + zt, + ); + let true_eval_target = builder.constant_extension(true_eval); + builder.connect_extension(eval_hd, true_eval_target); + builder.connect_extension(eval_ld, true_eval_target); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) } } diff --git a/plonky2/src/gates/low_degree_interpolation.rs b/plonky2/src/gates/low_degree_interpolation.rs index dabadfa4..3ffe5922 100644 --- a/plonky2/src/gates/low_degree_interpolation.rs +++ b/plonky2/src/gates/low_degree_interpolation.rs @@ -7,9 +7,9 @@ use plonky2_field::interpolation::interpolant; use plonky2_field::polynomial::PolynomialCoeffs; use plonky2_field::types::Field; -use crate::gadgets::interpolation::InterpolationGate; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; +use crate::gates::interpolation::InterpolationGate; use crate::gates::util::StridedConstraintConsumer; use crate::hash::hash_types::RichField; use crate::iop::ext_target::ExtensionTarget; diff --git a/plonky2/src/gates/mod.rs b/plonky2/src/gates/mod.rs index 48e319ef..1d2fc058 100644 --- a/plonky2/src/gates/mod.rs +++ b/plonky2/src/gates/mod.rs @@ -7,6 +7,7 @@ pub mod base_sum; pub mod constant; pub mod exponentiation; pub mod gate; +pub mod high_degree_interpolation; pub mod interpolation; pub mod low_degree_interpolation; pub mod multiplication_extension; From 3e388658280e5716924a3530875fad836179cef9 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 31 Aug 2022 14:31:36 -0700 Subject: [PATCH 05/97] documentation --- plonky2/src/gates/low_degree_interpolation.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plonky2/src/gates/low_degree_interpolation.rs b/plonky2/src/gates/low_degree_interpolation.rs index 3ffe5922..6e738bab 100644 --- a/plonky2/src/gates/low_degree_interpolation.rs +++ b/plonky2/src/gates/low_degree_interpolation.rs @@ -20,8 +20,9 @@ use crate::iop::witness::{PartitionWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; -/// Interpolation gate with constraints of degree 2. -/// `eval_unfiltered_recursively` uses more gates than `HighDegreeInterpolationGate`. +/// One of the instantiations of `InterpolationGate`: all constraints are degree <= 2. +/// The lower degree is a tradeoff for more gates (`eval_unfiltered_recursively` for +/// this version uses more gates than `LowDegreeInterpolationGate`). #[derive(Copy, Clone, Debug)] pub struct LowDegreeInterpolationGate, const D: usize> { pub subgroup_bits: usize, From 1ca46f76e56cafc4826a7950aec0092914a2ba94 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 31 Aug 2022 14:35:40 -0700 Subject: [PATCH 06/97] fixes --- plonky2/src/fri/recursive_verifier.rs | 2 +- plonky2/src/gates/high_degree_interpolation.rs | 2 +- plonky2/src/gates/low_degree_interpolation.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 0526ed7e..11e151ec 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -8,7 +8,7 @@ use crate::fri::proof::{ }; use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpeningsTarget}; use crate::fri::{FriConfig, FriParams}; -use crate::gadgets::interpolation::InterpolationGate; +use crate::gates::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; diff --git a/plonky2/src/gates/high_degree_interpolation.rs b/plonky2/src/gates/high_degree_interpolation.rs index 1c78a6e6..57f42545 100644 --- a/plonky2/src/gates/high_degree_interpolation.rs +++ b/plonky2/src/gates/high_degree_interpolation.rs @@ -277,10 +277,10 @@ mod tests { use plonky2_field::polynomial::PolynomialCoeffs; use plonky2_field::types::Field; - use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; + use crate::gates::interpolation::InterpolationGate; use crate::hash::hash_types::HashOut; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use crate::plonk::vars::EvaluationVars; diff --git a/plonky2/src/gates/low_degree_interpolation.rs b/plonky2/src/gates/low_degree_interpolation.rs index 6e738bab..3edc4175 100644 --- a/plonky2/src/gates/low_degree_interpolation.rs +++ b/plonky2/src/gates/low_degree_interpolation.rs @@ -388,9 +388,9 @@ mod tests { use plonky2_field::polynomial::PolynomialCoeffs; use plonky2_field::types::Field; - use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::interpolation::InterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::hash::hash_types::HashOut; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; From c80bc9f2b495920f75fae81077b362504576743f Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 31 Aug 2022 14:38:03 -0700 Subject: [PATCH 07/97] fmt --- plonky2/src/fri/recursive_verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 11e151ec..ac7e3a87 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -8,9 +8,9 @@ use crate::fri::proof::{ }; use crate::fri::structure::{FriBatchInfoTarget, FriInstanceInfoTarget, FriOpeningsTarget}; use crate::fri::{FriConfig, FriParams}; -use crate::gates::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::high_degree_interpolation::HighDegreeInterpolationGate; +use crate::gates::interpolation::InterpolationGate; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::gates::random_access::RandomAccessGate; use crate::hash::hash_types::MerkleCapTarget; From dc69d6afbd378315c1b29f56d2c764fdeb11e403 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 2 Sep 2022 12:01:55 -0700 Subject: [PATCH 08/97] clippy fix: 'needless borrow' --- plonky2/src/gates/high_degree_interpolation.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plonky2/src/gates/high_degree_interpolation.rs b/plonky2/src/gates/high_degree_interpolation.rs index 57f42545..bcdf2276 100644 --- a/plonky2/src/gates/high_degree_interpolation.rs +++ b/plonky2/src/gates/high_degree_interpolation.rs @@ -102,13 +102,13 @@ impl, const D: usize> Gate for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval_base(point); - constraints.extend(&(value - computed_value).to_basefield_array()); + constraints.extend((value - computed_value).to_basefield_array()); } let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval(evaluation_point); - constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + constraints.extend((evaluation_value - computed_evaluation_value).to_basefield_array()); constraints } @@ -153,7 +153,7 @@ impl, const D: usize> Gate let value = vars.get_local_ext_algebra(self.wires_value(i)); let computed_value = interpolant.eval_scalar(builder, point); constraints.extend( - &builder + builder .sub_ext_algebra(value, computed_value) .to_ext_target_array(), ); @@ -163,7 +163,7 @@ impl, const D: usize> Gate let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval(builder, evaluation_point); constraints.extend( - &builder + builder .sub_ext_algebra(evaluation_value, computed_evaluation_value) .to_ext_target_array(), ); From 0a3455ce48ad5de41a10a9b632ce7e413a91d76d Mon Sep 17 00:00:00 2001 From: BGluth Date: Fri, 2 Sep 2022 16:18:54 -0700 Subject: [PATCH 09/97] Added a few derives to `Trie` types - A downstream project needed `Hash` on `Nibbles`, but I also thought it made sense to derive a few other core types as well. --- evm/src/generation/partial_trie.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evm/src/generation/partial_trie.rs b/evm/src/generation/partial_trie.rs index 96751310..5e52e1e0 100644 --- a/evm/src/generation/partial_trie.rs +++ b/evm/src/generation/partial_trie.rs @@ -1,5 +1,6 @@ use ethereum_types::U256; +#[derive(Clone, Debug)] /// A partial trie, or a sub-trie thereof. This mimics the structure of an Ethereum trie, except /// with an additional `Hash` node type, representing a node whose data is not needed to process /// our transaction. @@ -22,6 +23,7 @@ pub enum PartialTrie { Leaf { nibbles: Nibbles, value: Vec }, } +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] /// A sequence of nibbles. pub struct Nibbles { /// The number of nibbles in this sequence. From 99999f1697d2492c5b193f35b8539f0f0a352d3f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 22:28:45 -0700 Subject: [PATCH 10/97] Use `ceil_div_usize` for `PACKED_LEN` --- evm/src/logic.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evm/src/logic.rs b/evm/src/logic.rs index 2499101b..2fa9c810 100644 --- a/evm/src/logic.rs +++ b/evm/src/logic.rs @@ -7,6 +7,7 @@ use plonky2::field::packed::PackedField; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; +use plonky2_util::ceil_div_usize; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; @@ -19,7 +20,7 @@ const VAL_BITS: usize = 256; // Number of bits stored per field element. Ensure that this fits; it is not checked. pub(crate) const PACKED_LIMB_BITS: usize = 32; // Number of field elements needed to store each input/output at the specified packing. -const PACKED_LEN: usize = (VAL_BITS + PACKED_LIMB_BITS - 1) / PACKED_LIMB_BITS; +const PACKED_LEN: usize = ceil_div_usize(VAL_BITS, PACKED_LIMB_BITS); pub(crate) mod columns { use std::cmp::min; From aaf7ace3961fb6b72949c83a0a147b2a891e8c2b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 22:31:56 -0700 Subject: [PATCH 11/97] Remove `JUMPDEST`s --- evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm | 8 -------- evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm | 6 ------ evm/src/cpu/kernel/asm/curve/common.asm | 1 - evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm | 7 ------- evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm | 5 ----- evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm | 4 ---- evm/src/cpu/kernel/asm/memory/memcpy.asm | 2 -- evm/src/cpu/kernel/asm/rlp/decode.asm | 9 --------- evm/src/cpu/kernel/asm/rlp/read_to_memory.asm | 3 --- evm/src/cpu/kernel/asm/transactions/router.asm | 2 -- evm/src/cpu/kernel/asm/transactions/type_0.asm | 1 - evm/src/cpu/kernel/asm/transactions/type_1.asm | 1 - evm/src/cpu/kernel/asm/transactions/type_2.asm | 1 - evm/src/cpu/kernel/asm/util/assertions.asm | 1 - 14 files changed, 51 deletions(-) diff --git a/evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm index 15f9df05..dda82109 100644 --- a/evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm +++ b/evm/src/cpu/kernel/asm/curve/bn254/curve_add.asm @@ -9,7 +9,6 @@ global ec_add: // PUSH 1 // PUSH 0x1bf9384aa3f0b3ad763aee81940cacdde1af71617c06f46e11510f14f3d5d121 // PUSH 0xe7313274bb29566ff0c8220eb9841de1d96c2923c6a4028f7dd3c6a14cee770 - JUMPDEST // stack: x0, y0, x1, y1, retdest // Check if points are valid BN254 points. @@ -38,7 +37,6 @@ global ec_add: // BN254 elliptic curve addition. // Assumption: (x0,y0) and (x1,y1) are valid points. global ec_add_valid_points: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Check if the first point is the identity. @@ -92,7 +90,6 @@ global ec_add_valid_points: // BN254 elliptic curve addition. // Assumption: (x0,y0) == (0,0) ec_add_first_zero: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Just return (x1,y1) %stack (x0, y0, x1, y1, retdest) -> (retdest, x1, y1) @@ -101,7 +98,6 @@ ec_add_first_zero: // BN254 elliptic curve addition. // Assumption: (x1,y1) == (0,0) ec_add_snd_zero: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Just return (x0,y0) @@ -111,7 +107,6 @@ ec_add_snd_zero: // BN254 elliptic curve addition. // Assumption: lambda = (y0 - y1)/(x0 - x1) ec_add_valid_points_with_lambda: - JUMPDEST // stack: lambda, x0, y0, x1, y1, retdest // Compute x2 = lambda^2 - x1 - x0 @@ -159,7 +154,6 @@ ec_add_valid_points_with_lambda: // BN254 elliptic curve addition. // Assumption: (x0,y0) and (x1,y1) are valid points and x0 == x1 ec_add_equal_first_coord: - JUMPDEST // stack: x0, y0, x1, y1, retdest with x0 == x1 // Check if the points are equal @@ -188,7 +182,6 @@ ec_add_equal_first_coord: // Assumption: x0 == x1 and y0 == y1 // Standard doubling formula. ec_add_equal_points: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Compute lambda = 3/2 * x0^2 / y0 @@ -216,7 +209,6 @@ ec_add_equal_points: // Assumption: (x0,y0) is a valid point. // Standard doubling formula. global ec_double: - JUMPDEST // stack: x0, y0, retdest DUP2 // stack: y0, x0, y0, retdest diff --git a/evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm b/evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm index 62cf2235..b1472812 100644 --- a/evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm +++ b/evm/src/cpu/kernel/asm/curve/bn254/curve_mul.asm @@ -6,7 +6,6 @@ global ec_mul: // PUSH 0xd // PUSH 2 // PUSH 1 - JUMPDEST // stack: x, y, s, retdest DUP2 // stack: y, x, y, s, retdest @@ -29,7 +28,6 @@ global ec_mul: // Same algorithm as in `exp.asm` ec_mul_valid_point: - JUMPDEST // stack: x, y, s, retdest DUP3 // stack: s, x, y, s, retdest @@ -38,7 +36,6 @@ ec_mul_valid_point: %jump(ret_zero_ec_mul) step_case: - JUMPDEST // stack: x, y, s, retdest PUSH recursion_return // stack: recursion_return, x, y, s, retdest @@ -58,12 +55,10 @@ step_case: // Assumption: 2(x,y) = (x',y') step_case_contd: - JUMPDEST // stack: x', y', s / 2, recursion_return, x, y, s, retdest %jump(ec_mul_valid_point) recursion_return: - JUMPDEST // stack: x', y', x, y, s, retdest SWAP4 // stack: s, y', x, y, x', retdest @@ -96,6 +91,5 @@ recursion_return: JUMP odd_scalar: - JUMPDEST // stack: x', y', x, y, retdest %jump(ec_add_valid_points) diff --git a/evm/src/cpu/kernel/asm/curve/common.asm b/evm/src/cpu/kernel/asm/curve/common.asm index 107dc63c..9e273c15 100644 --- a/evm/src/cpu/kernel/asm/curve/common.asm +++ b/evm/src/cpu/kernel/asm/curve/common.asm @@ -1,5 +1,4 @@ global ret_zero_ec_mul: - JUMPDEST // stack: x, y, s, retdest %pop3 // stack: retdest diff --git a/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm index 7f9c1fff..790fb116 100644 --- a/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm +++ b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_add.asm @@ -3,7 +3,6 @@ // Secp256k1 elliptic curve addition. // Assumption: (x0,y0) and (x1,y1) are valid points. global ec_add_valid_points_secp: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Check if the first point is the identity. @@ -57,7 +56,6 @@ global ec_add_valid_points_secp: // Secp256k1 elliptic curve addition. // Assumption: (x0,y0) == (0,0) ec_add_first_zero: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Just return (x1,y1) @@ -72,7 +70,6 @@ ec_add_first_zero: // Secp256k1 elliptic curve addition. // Assumption: (x1,y1) == (0,0) ec_add_snd_zero: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Just return (x1,y1) @@ -93,7 +90,6 @@ ec_add_snd_zero: // Secp256k1 elliptic curve addition. // Assumption: lambda = (y0 - y1)/(x0 - x1) ec_add_valid_points_with_lambda: - JUMPDEST // stack: lambda, x0, y0, x1, y1, retdest // Compute x2 = lambda^2 - x1 - x0 @@ -150,7 +146,6 @@ ec_add_valid_points_with_lambda: // Secp256k1 elliptic curve addition. // Assumption: (x0,y0) and (x1,y1) are valid points and x0 == x1 ec_add_equal_first_coord: - JUMPDEST // stack: x0, y0, x1, y1, retdest with x0 == x1 // Check if the points are equal @@ -179,7 +174,6 @@ ec_add_equal_first_coord: // Assumption: x0 == x1 and y0 == y1 // Standard doubling formula. ec_add_equal_points: - JUMPDEST // stack: x0, y0, x1, y1, retdest // Compute lambda = 3/2 * x0^2 / y0 @@ -207,7 +201,6 @@ ec_add_equal_points: // Assumption: (x0,y0) is a valid point. // Standard doubling formula. global ec_double_secp: - JUMPDEST // stack: x0, y0, retdest DUP2 // stack: y0, x0, y0, retdest diff --git a/evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm index f0825e88..892d57c0 100644 --- a/evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm +++ b/evm/src/cpu/kernel/asm/curve/secp256k1/curve_mul.asm @@ -1,6 +1,5 @@ // Same algorithm as in `exp.asm` global ec_mul_valid_point_secp: - JUMPDEST // stack: x, y, s, retdest %stack (x,y) -> (x,y,x,y) %ec_isidentity @@ -13,7 +12,6 @@ global ec_mul_valid_point_secp: %jump(ret_zero_ec_mul) step_case: - JUMPDEST // stack: x, y, s, retdest PUSH recursion_return // stack: recursion_return, x, y, s, retdest @@ -33,12 +31,10 @@ step_case: // Assumption: 2(x,y) = (x',y') step_case_contd: - JUMPDEST // stack: x', y', s / 2, recursion_return, x, y, s, retdest %jump(ec_mul_valid_point_secp) recursion_return: - JUMPDEST // stack: x', y', x, y, s, retdest SWAP4 // stack: s, y', x, y, x', retdest @@ -71,6 +67,5 @@ recursion_return: JUMP odd_scalar: - JUMPDEST // stack: x', y', x, y, retdest %jump(ec_add_valid_points_secp) diff --git a/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm index 538a86dc..96e177ff 100644 --- a/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm +++ b/evm/src/cpu/kernel/asm/curve/secp256k1/ecrecover.asm @@ -1,6 +1,5 @@ // ecrecover precompile. global ecrecover: - JUMPDEST // stack: hash, v, r, s, retdest // Check if inputs are valid. @@ -47,7 +46,6 @@ global ecrecover: // let u2 = -hash * r_inv; // return u1*P + u2*GENERATOR; ecrecover_valid_input: - JUMPDEST // stack: hash, y, r, s, retdest // Compute u1 = s * r^(-1) @@ -83,7 +81,6 @@ ecrecover_valid_input: // ecrecover precompile. // Assumption: (X,Y) = u1 * P. Result is (X,Y) + u2*GENERATOR ecrecover_with_first_point: - JUMPDEST // stack: X, Y, hash, r^(-1), retdest %secp_scalar // stack: p, X, Y, hash, r^(-1), retdest @@ -132,7 +129,6 @@ ecrecover_with_first_point: // Take a public key (PKx, PKy) and return the associated address KECCAK256(PKx || PKy)[-20:]. pubkey_to_addr: - JUMPDEST // stack: PKx, PKy, retdest PUSH 0 // stack: 0, PKx, PKy, retdest diff --git a/evm/src/cpu/kernel/asm/memory/memcpy.asm b/evm/src/cpu/kernel/asm/memory/memcpy.asm index 0a390736..3feca35d 100644 --- a/evm/src/cpu/kernel/asm/memory/memcpy.asm +++ b/evm/src/cpu/kernel/asm/memory/memcpy.asm @@ -4,7 +4,6 @@ // DST = (dst_ctx, dst_segment, dst_addr). // These tuple definitions are used for brevity in the stack comments below. global memcpy: - JUMPDEST // stack: DST, SRC, count, retdest DUP7 // stack: count, DST, SRC, count, retdest @@ -44,7 +43,6 @@ global memcpy: %jump(memcpy) memcpy_finish: - JUMPDEST // stack: DST, SRC, count, retdest %pop7 // stack: retdest diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm index 0388276a..5749aee7 100644 --- a/evm/src/cpu/kernel/asm/rlp/decode.asm +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -12,7 +12,6 @@ // Pre stack: pos, retdest // Post stack: pos', len global decode_rlp_string_len: - JUMPDEST // stack: pos, retdest DUP1 %mload_current(@SEGMENT_RLP_RAW) @@ -32,7 +31,6 @@ global decode_rlp_string_len: JUMP decode_rlp_string_len_medium: - JUMPDEST // String is 0-55 bytes long. First byte contains the len. // stack: first_byte, pos, retdest %sub_const(0x80) @@ -44,7 +42,6 @@ decode_rlp_string_len_medium: JUMP decode_rlp_string_len_large: - JUMPDEST // String is >55 bytes long. First byte contains the len of the len. // stack: first_byte, pos, retdest %sub_const(0xb7) @@ -69,7 +66,6 @@ decode_rlp_string_len_large: // bytes, so that the result can be returned as a single word on the stack. // As per the spec, scalars must not have leading zeros. global decode_rlp_scalar: - JUMPDEST // stack: pos, retdest PUSH decode_int_given_len // stack: decode_int_given_len, pos, retdest @@ -91,7 +87,6 @@ global decode_rlp_scalar: // Pre stack: pos, retdest // Post stack: pos', len global decode_rlp_list_len: - JUMPDEST // stack: pos, retdest DUP1 %mload_current(@SEGMENT_RLP_RAW) @@ -116,7 +111,6 @@ global decode_rlp_list_len: JUMP decode_rlp_list_len_big: - JUMPDEST // The length of the length is first_byte - 0xf7. // stack: first_byte, pos', retdest %sub_const(0xf7) @@ -137,7 +131,6 @@ decode_rlp_list_len_big: // Pre stack: pos, len, retdest // Post stack: pos', int decode_int_given_len: - JUMPDEST %stack (pos, len, retdest) -> (pos, len, pos, retdest) ADD // stack: end_pos, pos, retdest @@ -147,7 +140,6 @@ decode_int_given_len: // stack: acc, pos, end_pos, retdest decode_int_given_len_loop: - JUMPDEST // stack: acc, pos, end_pos, retdest DUP3 DUP3 @@ -171,6 +163,5 @@ decode_int_given_len_loop: %jump(decode_int_given_len_loop) decode_int_given_len_finish: - JUMPDEST %stack (acc, pos, end_pos, retdest) -> (retdest, pos, acc) JUMP diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm index ae75e3d7..db474b9b 100644 --- a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -5,7 +5,6 @@ // Post stack: (empty) global read_rlp_to_memory: - JUMPDEST // stack: retdest PROVER_INPUT // Read the RLP blob length from the prover tape. // stack: len, retdest @@ -13,7 +12,6 @@ global read_rlp_to_memory: // stack: pos, len, retdest read_rlp_to_memory_loop: - JUMPDEST // stack: pos, len, retdest DUP2 DUP2 @@ -32,7 +30,6 @@ read_rlp_to_memory_loop: %jump(read_rlp_to_memory_loop) read_rlp_to_memory_finish: - JUMPDEST // stack: pos, len, retdest %pop2 // stack: retdest diff --git a/evm/src/cpu/kernel/asm/transactions/router.asm b/evm/src/cpu/kernel/asm/transactions/router.asm index 01a65fec..47a899c9 100644 --- a/evm/src/cpu/kernel/asm/transactions/router.asm +++ b/evm/src/cpu/kernel/asm/transactions/router.asm @@ -3,7 +3,6 @@ // jump to the appropriate transaction parsing method. global route_txn: - JUMPDEST // stack: (empty) // First load transaction data into memory, where it will be parsed. PUSH read_txn_from_memory @@ -11,7 +10,6 @@ global route_txn: // At this point, the raw txn data is in memory. read_txn_from_memory: - JUMPDEST // stack: (empty) // We will peak at the first byte to determine what type of transaction this is. diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm index 7c8488f7..3f258624 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -12,7 +12,6 @@ // keccak256(rlp([nonce, gas_price, gas_limit, to, value, data])) global process_type_0_txn: - JUMPDEST // stack: (empty) PUSH 0 // initial pos // stack: pos diff --git a/evm/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm index 5b9d2cdf..9d45c1e4 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_1.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_1.asm @@ -7,6 +7,5 @@ // data, access_list])) global process_type_1_txn: - JUMPDEST // stack: (empty) PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm index 9807f88f..b2a862c1 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -8,6 +8,5 @@ // access_list])) global process_type_2_txn: - JUMPDEST // stack: (empty) PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/asm/util/assertions.asm b/evm/src/cpu/kernel/asm/util/assertions.asm index 69193e5f..0051219c 100644 --- a/evm/src/cpu/kernel/asm/util/assertions.asm +++ b/evm/src/cpu/kernel/asm/util/assertions.asm @@ -1,7 +1,6 @@ // It is convenient to have a single panic routine, which we can jump to from // anywhere. global panic: - JUMPDEST PANIC // Consumes the top element and asserts that it is zero. From 1c2e94f9fccb8e1de2c5a8a3ac5ce46f43775795 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 4 Sep 2022 22:46:16 -0700 Subject: [PATCH 12/97] Compute answers to FRI queries in parallel It shaved off much less than a millisecond, so it's rather negligible, but the code came out simpler so might as well. --- plonky2/src/fri/prover.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/plonky2/src/fri/prover.rs b/plonky2/src/fri/prover.rs index 39e25869..71efe98a 100644 --- a/plonky2/src/fri/prover.rs +++ b/plonky2/src/fri/prover.rs @@ -149,15 +149,12 @@ fn fri_prover_query_rounds< n: usize, fri_params: &FriParams, ) -> Vec> { - (0..fri_params.config.num_query_rounds) - .map(|_| { - fri_prover_query_round::( - initial_merkle_trees, - trees, - challenger, - n, - fri_params, - ) + challenger + .get_n_challenges(fri_params.config.num_query_rounds) + .into_par_iter() + .map(|rand| { + let x_index = rand.to_canonical_u64() as usize % n; + fri_prover_query_round::(initial_merkle_trees, trees, x_index, fri_params) }) .collect() } @@ -169,13 +166,10 @@ fn fri_prover_query_round< >( initial_merkle_trees: &[&MerkleTree], trees: &[MerkleTree], - challenger: &mut Challenger, - n: usize, + mut x_index: usize, fri_params: &FriParams, ) -> FriQueryRound { let mut query_steps = Vec::new(); - let x = challenger.get_challenge(); - let mut x_index = x.to_canonical_u64() as usize % n; let initial_proof = initial_merkle_trees .iter() .map(|t| (t.get(x_index).to_vec(), t.prove(x_index))) From 9b259cb9172229ddd35d9a12f0c4d4dccf7303ed Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 5 Sep 2022 10:12:23 -0700 Subject: [PATCH 13/97] Feedback --- evm/src/cpu/kernel/asm/exp.asm | 3 --- evm/src/cpu/kernel/interpreter.rs | 6 +++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/evm/src/cpu/kernel/asm/exp.asm b/evm/src/cpu/kernel/asm/exp.asm index 3640b2f6..f025e312 100644 --- a/evm/src/cpu/kernel/asm/exp.asm +++ b/evm/src/cpu/kernel/asm/exp.asm @@ -10,7 +10,6 @@ /// Note that this correctly handles exp(0, 0) == 1. global exp: - jumpdest // stack: x, e, retdest dup2 // stack: e, x, e, retdest @@ -27,7 +26,6 @@ global exp: jump step_case: - jumpdest // stack: x, e, retdest push recursion_return // stack: recursion_return, x, e, retdest @@ -43,7 +41,6 @@ step_case: // stack: x * x, e / 2, recursion_return, x, e, retdest %jump(exp) recursion_return: - jumpdest // stack: exp(x * x, e / 2), x, e, retdest push 2 // stack: 2, exp(x * x, e / 2), x, e, retdest diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 17be0523..64d70529 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -250,7 +250,7 @@ impl<'a> Interpreter<'a> { 0x58 => todo!(), // "GETPC", 0x59 => todo!(), // "MSIZE", 0x5a => todo!(), // "GAS", - 0x5b => (), // "JUMPDEST", + 0x5b => self.run_jumpdest(), // "JUMPDEST", 0x5c => todo!(), // "GET_STATE_ROOT", 0x5d => todo!(), // "SET_STATE_ROOT", 0x5e => todo!(), // "GET_RECEIPT_ROOT", @@ -490,6 +490,10 @@ impl<'a> Interpreter<'a> { } } + fn run_jumpdest(&mut self) { + assert!(!self.kernel_mode, "JUMPDEST is not needed in kernel code"); + } + fn jump_to(&mut self, offset: usize) { // The JUMPDEST rule is not enforced in kernel mode. if !self.kernel_mode && self.jumpdests.binary_search(&offset).is_err() { From f496711b2142d2400bca00dd39015c8e90073504 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Tue, 6 Sep 2022 14:55:51 -0400 Subject: [PATCH 14/97] add flat_map_iter to maybe_rayon & feature-gate it --- maybe_rayon/src/lib.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/maybe_rayon/src/lib.rs b/maybe_rayon/src/lib.rs index 1a9bd823..b7dddc0e 100644 --- a/maybe_rayon/src/lib.rs +++ b/maybe_rayon/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "parallel"))] use std::{ - iter::{IntoIterator, Iterator}, + iter::{IntoIterator, Iterator, FlatMap}, slice::{Chunks, ChunksExact, ChunksExactMut, ChunksMut}, }; @@ -223,13 +223,22 @@ impl MaybeParChunksMut for [T] { } } +#[cfg(not(feature = "parallel"))] pub trait ParallelIteratorMock { type Item; fn find_any

(self, predicate: P) -> Option where P: Fn(&Self::Item) -> bool + Sync + Send; + + fn flat_map_iter(self, map_op: F) -> FlatMap + where + Self: Sized, + U: IntoIterator, + F: Fn(Self::Item) -> U; + } +#[cfg(not(feature = "parallel"))] impl ParallelIteratorMock for T { type Item = T::Item; @@ -239,6 +248,15 @@ impl ParallelIteratorMock for T { { self.find(predicate) } + + fn flat_map_iter(self, map_op: F) -> FlatMap + where + Self: Sized, + U: IntoIterator, + F: Fn(Self::Item) -> U + { + self.flat_map(map_op) + } } #[cfg(feature = "parallel")] From e72152eed80e06769cdb97d5c292e5f65cbf0942 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Tue, 6 Sep 2022 14:56:48 -0400 Subject: [PATCH 15/97] fix default features in starky & evm --- evm/Cargo.toml | 2 +- plonky2/src/hash/hash_types.rs | 2 +- starky/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index db774345..230cd5a8 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" edition = "2021" [dependencies] -plonky2 = { path = "../plonky2" } +plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "rand_chacha", "timing", "gate_testing"] } plonky2_util = { path = "../util" } anyhow = "1.0.40" env_logger = "0.9.0" diff --git a/plonky2/src/hash/hash_types.rs b/plonky2/src/hash/hash_types.rs index 14303ad3..f416732a 100644 --- a/plonky2/src/hash/hash_types.rs +++ b/plonky2/src/hash/hash_types.rs @@ -115,7 +115,7 @@ pub struct MerkleCapTarget(pub Vec); pub struct BytesHash(pub [u8; N]); impl BytesHash { - #[cfg(feature = "parallel")] + #[cfg(feature = "rand")] pub fn rand_from_rng(rng: &mut R) -> Self { let mut buf = [0; N]; rng.fill_bytes(&mut buf); diff --git a/starky/Cargo.toml b/starky/Cargo.toml index 80a26bfc..4bc12d31 100644 --- a/starky/Cargo.toml +++ b/starky/Cargo.toml @@ -9,7 +9,7 @@ default = ["parallel"] parallel = ["maybe_rayon/parallel"] [dependencies] -plonky2 = { path = "../plonky2" } +plonky2 = { path = "../plonky2", default-features = false } plonky2_util = { path = "../util" } anyhow = "1.0.40" env_logger = "0.9.0" From aaba931e4db788cf7a79aa224762761c183ab174 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Tue, 6 Sep 2022 14:58:42 -0400 Subject: [PATCH 16/97] fmt --- maybe_rayon/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/maybe_rayon/src/lib.rs b/maybe_rayon/src/lib.rs index b7dddc0e..d24ba2e5 100644 --- a/maybe_rayon/src/lib.rs +++ b/maybe_rayon/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "parallel"))] use std::{ - iter::{IntoIterator, Iterator, FlatMap}, + iter::{FlatMap, IntoIterator, Iterator}, slice::{Chunks, ChunksExact, ChunksExactMut, ChunksMut}, }; @@ -235,7 +235,6 @@ pub trait ParallelIteratorMock { Self: Sized, U: IntoIterator, F: Fn(Self::Item) -> U; - } #[cfg(not(feature = "parallel"))] @@ -253,7 +252,7 @@ impl ParallelIteratorMock for T { where Self: Sized, U: IntoIterator, - F: Fn(Self::Item) -> U + F: Fn(Self::Item) -> U, { self.flat_map(map_op) } From aa0f0f6e75359981ca98f4607475939804d8f447 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Tue, 6 Sep 2022 15:10:55 -0400 Subject: [PATCH 17/97] add other features back --- starky/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starky/Cargo.toml b/starky/Cargo.toml index 4bc12d31..700a3248 100644 --- a/starky/Cargo.toml +++ b/starky/Cargo.toml @@ -9,7 +9,7 @@ default = ["parallel"] parallel = ["maybe_rayon/parallel"] [dependencies] -plonky2 = { path = "../plonky2", default-features = false } +plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing", "rand_chacha"] } plonky2_util = { path = "../util" } anyhow = "1.0.40" env_logger = "0.9.0" From 6f98d6bc0316f843cfaa383f45f1eeb4b38ead54 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 7 Sep 2022 16:46:27 +0200 Subject: [PATCH 18/97] Comment batch opening --- plonky2/src/fri/oracle.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plonky2/src/fri/oracle.rs b/plonky2/src/fri/oracle.rs index 1f5b648f..efe34939 100644 --- a/plonky2/src/fri/oracle.rs +++ b/plonky2/src/fri/oracle.rs @@ -180,7 +180,13 @@ impl, C: GenericConfig, const D: usize> // Final low-degree polynomial that goes into FRI. let mut final_poly = PolynomialCoeffs::empty(); + // Each batch `i` consists of an opening point `z_i` and polynomials `{f_ij}_j` to be opened at that point. + // For each batch, we compute the composition polynomial `F_i = sum alpha^j f_ij`, + // where `alpha` is a random challenge in the extension field. + // The final polynomial is then computed as `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i)` + // where the `k_i`s are chosen such that each power of `alpha` appears only once in the final sum. for FriBatchInfo { point, polynomials } in &instance.batches { + // Collect the coefficients of all the polynomials in `polynomials`. let polys_coeff = polynomials.iter().map(|fri_poly| { &oracles[fri_poly.oracle_index].polynomials[fri_poly.polynomial_index] }); From 7fbbd301a7d8a3f9025a714d16cebf4f0bbe5f3f Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 7 Sep 2022 07:46:43 -0700 Subject: [PATCH 19/97] Update paper --- plonky2/plonky2.pdf | Bin 235098 -> 236419 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/plonky2/plonky2.pdf b/plonky2/plonky2.pdf index ad0cef0228082e7ce26f6c059dd66618c0e43235..8f0f9ece13a2e3997927736de6205d178a18a4dd 100644 GIT binary patch delta 59696 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

DRgc90bOT@M3*{@+bAF( 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%0D5j)tc+UHQ z?pm9{u4Q&8=1+@OTemggzNg|Lon=~U4Egj&o{nc^y!y`GI3##M6@sYN84=UnV=9pu zYI(4!cDXdhOzMR8R8Dv}(2;Qo=Md8|A+LUHj}W)!Sw3-g||v!5FC>h*RM(Z?ro zp6#GF)T3%uJPR;Exei7=`x75m_59(LT$d&A<~DeF9G|PNuHICgxo-h8@7Nz(f?whc zrpNRNX)6nsrd75cRS8wWQ$W6J?qIocc!LL@H$k8LAo|K*_S4g11g>1LZ!WC{@sWsN7Bz<+KGhi1{J6@-Yx z>0T>!-onWpriM$1*G!EY04cFKn*Wy%94~MvofxbA8>ivWs_)l!kH$Jv+*iFrXt?3=R2R}y#+X2xHB|krh#LQjTRGf|5SFy+ zF`!(y;J?=6Q`2l9x_ig1M52sl0r#xZa4MZ1B_P$Rc2;H}Lwk%t>i*p-uRaN^mW@PT z{BV9e*Bz3YTvz2boehv*WN#6!SUVXezH(YuST|>RGXY}7YSv3)WTNQ3No>; z;AGfD$hT$sk9;qL7$wYAMzxNIzN??k_VfTM3F)4EZ~3eRU($7_7KV$b*+@iojO3)X zdQs{)Bw;5yO&NiKhJmxVd*V6x^Qa`H+PXm=tYn;+w2sUNJa@iCt?tyiTqedGfUwi= z<-L~j`E%8F{9aBO`-ZDKO(6zq3Y>WZj_ltnPWW4$=Kno!Ms--C*9Up1_HnrR@xj&u z#{=OXh&d9TYUz@DM+xuN(Jh7`YOfyd`%<4qI30ml>ASpX{U>OH5@_H}X|rlb_qCJ> z!Y0-4J`{-^+@R2O~)z;9$x#Az9 zRt8XvB;jArQ#^y+@nwGw#HqsZtUB4tQu8j(I(}zO5!rvEVZCukfHpX_Um*6R<(h`_ zD3R&l6H!#SAV4v=72hBbf3iLN2h?e=e5l;0T65RP%a-FkZOs7_7^m($EBRZNw@wX{Lv9xa|(P)JUD+#Y53yAjIa7OQ65T z>;E*;^>q_KAkw?++i=fQE_&oPaJc#4c+th%aI=Yua!YWsuyC@oi?T~_vx>2DaEfqq@NlznO7I90|Noch zA^q>|fQO5V_`k4X6sfWf3d$H^kGy3y8dfdpC6&!pYI-LMr}J2Dbd{Fsr}Og>5yUW! zCZsTwI zxB8?2==Q+Y@Ny`1OmU8N`*~}nV2$9`Xjjy737wGEIMJ7v_ZJoBcrkKHy2gJ9IHLp<*S%YWCW`(B}d1RR-tr_UvOxP?q zzmyNK!(Nx1=g3F5;{q`qgX)pbiPvG~8E0eNVeJGef(e^R*b7C4t|alr7RW#;;OHjH zY86YdGj=dzp_LawnCd9`H$G}LSXvA=%k|7Ct%_PN%x_&xu3z}()Px9R)5~<0+)%W> z$F`HHzKXW4N>y%DLU+p2YM%jt{UVOAh=E4TkjTMgyVN+)WBpE!kl9dW{W#o6(*aF} z_}KvTUH*0m#vob4S(PO_Z7(#r1|jWB){HMryYS%lPXpr9UCNABwO3(;U)T0*mH=51 zfdm$8)y^eDpb~{sqzS?ffLg0wZB|Z%4VnR?S){?R;yKZOsiYbrgLjeJEiG4|;6Y); z4?;etkh8pP(MR^uT65CNG(k;L6UeT;?r#wP7q(f%aXhf1XlcHm=)JPZ=p&)x&{z)r z=G*6!3s1w){9m1(z0>DO<}lVKr%!XR4=?Q<`XTmbc|LfM=4jmwxt<97B~IfH)MqWoJX}4 zzxBf7qo&pfwHK+^Ys-DH#ae)ng|>&^1Y@+*II8{vjF#H$PHB6;$ye>xYwZH{Y62ls z0t!y#lr3y|k&fJSXV7NtM@-}O$=n#Xt%M93p!c6u6C&^Xf_ zeir4t=nH)P-)5mdv6S3*_xaeiP+9$0vnZ{BM*Y>dEou6Fi+MIj%pQJ#zksX(u*Uyv z95+%Jea~wRbVj5g{4~U9!D-=iAeZ8y)a6OsCUGXEd~TmML< Vv!nqC5ja@5IpHZNBorm#{{xy6phW-x delta 58255 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 T4M~W|%Eif!Ku#{MD1q=lZgBWM From 8fd4fc430446b38fcb3b21312c47985b58b90153 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 7 Sep 2022 16:49:23 +0200 Subject: [PATCH 20/97] Minor --- plonky2/src/fri/structure.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/fri/structure.rs b/plonky2/src/fri/structure.rs index 1a37a1b2..d5c2c81c 100644 --- a/plonky2/src/fri/structure.rs +++ b/plonky2/src/fri/structure.rs @@ -42,7 +42,7 @@ pub struct FriBatchInfoTarget { #[derive(Copy, Clone, Debug)] pub struct FriPolynomialInfo { - /// Index into `FriInstanceInfoTarget`'s `oracles` list. + /// Index into `FriInstanceInfo`'s `oracles` list. pub oracle_index: usize, /// Index of the polynomial within the oracle. pub polynomial_index: usize, From f1e21ffb5d4acdf22067829fd7eba1d3611689d1 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 7 Sep 2022 20:57:38 +0200 Subject: [PATCH 21/97] More comment --- plonky2/src/fri/oracle.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plonky2/src/fri/oracle.rs b/plonky2/src/fri/oracle.rs index efe34939..75f8847a 100644 --- a/plonky2/src/fri/oracle.rs +++ b/plonky2/src/fri/oracle.rs @@ -185,6 +185,8 @@ impl, C: GenericConfig, const D: usize> // where `alpha` is a random challenge in the extension field. // The final polynomial is then computed as `final_poly = sum_i alpha^(k_i) (F_i(X) - F_i(z_i))/(X-z_i)` // where the `k_i`s are chosen such that each power of `alpha` appears only once in the final sum. + // There are usually two batches for the openings at `zeta` and `g * zeta`. + // The oracles used in Plonky2 are given in `FRI_ORACLES` in `plonky2/src/plonk/plonk_common.rs`. for FriBatchInfo { point, polynomials } in &instance.batches { // Collect the coefficients of all the polynomials in `polynomials`. let polys_coeff = polynomials.iter().map(|fri_poly| { From 19162db596f248cba25f56954ee45fff51c61118 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 7 Sep 2022 15:09:56 -0700 Subject: [PATCH 22/97] Tweak features --- evm/Cargo.toml | 6 +++--- starky/Cargo.toml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 230cd5a8..1e8f3c56 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -5,8 +5,9 @@ version = "0.1.0" edition = "2021" [dependencies] -plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "rand_chacha", "timing", "gate_testing"] } +plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } +maybe_rayon = { path = "../maybe_rayon" } anyhow = "1.0.40" env_logger = "0.9.0" ethereum-types = "0.13.1" @@ -17,7 +18,6 @@ log = "0.4.14" once_cell = "1.13.0" pest = "2.1.3" pest_derive = "2.1.0" -maybe_rayon = { path = "../maybe_rayon" } rand = "0.8.5" rand_chacha = "0.3.1" rlp = "0.5.1" @@ -31,7 +31,7 @@ hex = "0.4.3" [features] default = ["parallel"] asmtools = ["hex"] -parallel = ["maybe_rayon/parallel"] +parallel = ["plonky2/parallel", "maybe_rayon/parallel"] [[bin]] name = "assemble" diff --git a/starky/Cargo.toml b/starky/Cargo.toml index 700a3248..43bea53e 100644 --- a/starky/Cargo.toml +++ b/starky/Cargo.toml @@ -6,13 +6,13 @@ edition = "2021" [features] default = ["parallel"] -parallel = ["maybe_rayon/parallel"] +parallel = ["plonky2/parallel", "maybe_rayon/parallel"] [dependencies] -plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing", "rand_chacha"] } +plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } +maybe_rayon = { path = "../maybe_rayon"} anyhow = "1.0.40" env_logger = "0.9.0" itertools = "0.10.0" log = "0.4.14" -maybe_rayon = { path = "../maybe_rayon"} From fdb6cafe18b5f2eaaecfe60c792b2d775d0691ed Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 15 Aug 2022 10:28:23 -0700 Subject: [PATCH 23/97] Fill in call_common routine --- evm/src/cpu/kernel/asm/core/call.asm | 85 +++++++++++++++++++----- evm/src/cpu/kernel/asm/core/transfer.asm | 12 ++++ 2 files changed, 80 insertions(+), 17 deletions(-) diff --git a/evm/src/cpu/kernel/asm/core/call.asm b/evm/src/cpu/kernel/asm/core/call.asm index 3cbbb441..1b8a535f 100644 --- a/evm/src/cpu/kernel/asm/core/call.asm +++ b/evm/src/cpu/kernel/asm/core/call.asm @@ -2,21 +2,21 @@ // Creates a new sub context and executes the code of the given account. global call: - // stack: gas, address, value, args_offset, args_size, ret_offset, ret_size + // stack: gas, address, value, args_offset, args_size, ret_offset, ret_size, retdest %address %stack (self, gas, address, value) - // These are (should_transfer_value, value, static, gas, sender, storage, code_addr) - -> (1, value, 0, gas, self, address, address) + // These are (static, should_transfer_value, value, sender, address, code_addr, gas) + -> (0, 1, value, self, address, address, gas) %jump(call_common) // Creates a new sub context as if calling itself, but with the code of the // given account. In particular the storage remains the same. global call_code: - // stack: gas, address, value, args_offset, args_size, ret_offset, ret_size + // stack: gas, address, value, args_offset, args_size, ret_offset, ret_size, retdest %address %stack (self, gas, address, value) - // These are (should_transfer_value, value, static, gas, sender, storage, code_addr) - -> (1, value, 0, gas, self, self, address) + // These are (static, should_transfer_value, value, sender, address, code_addr, gas) + -> (0, 1, value, self, self, address, gas) %jump(call_common) // Creates a new sub context and executes the code of the given account. @@ -25,35 +25,86 @@ global call_code: // are CREATE, CREATE2, LOG0, LOG1, LOG2, LOG3, LOG4, SSTORE, SELFDESTRUCT and // CALL if the value sent is not 0. global static_all: - // stack: gas, address, args_offset, args_size, ret_offset, ret_size + // stack: gas, address, args_offset, args_size, ret_offset, ret_size, retdest %address %stack (self, gas, address) - // These are (should_transfer_value, value, static, gas, sender, storage, code_addr) - -> (0, 0, 1, gas, self, address, address) + // These are (static, should_transfer_value, value, sender, address, code_addr, gas) + -> (1, 0, 0, self, address, address, gas) %jump(call_common) // Creates a new sub context as if calling itself, but with the code of the // given account. In particular the storage, the current sender and the current // value remain the same. global delegate_call: - // stack: gas, address, args_offset, args_size, ret_offset, ret_size + // stack: gas, address, args_offset, args_size, ret_offset, ret_size, retdest %address %sender %callvalue %stack (self, sender, value, gas, address) - // These are (should_transfer_value, value, static, gas, sender, storage, code_addr) - -> (0, value, 0, gas, sender, self, address) + // These are (static, should_transfer_value, value, sender, address, code_addr, gas) + -> (0, 0, value, sender, self, address, gas) %jump(call_common) call_common: - // stack: should_transfer_value, value, static, gas, sender, storage, code_addr, args_offset, args_size, ret_offset, ret_size - // TODO: Set all the appropriate metadata fields... + // stack: static, should_transfer_value, value, sender, address, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest %create_context - // stack: new_ctx, after_call + // Store the static flag in metadata. + %stack (new_ctx, static) -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_STATIC, static, new_ctx) + MSTORE_GENERAL + // stack: new_ctx, should_transfer_value, value, sender, address, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store the address in metadata. + %stack (new_ctx, should_transfer_value, value, sender, address) + -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_ADDRESS, address, + new_ctx, should_transfer_value, value, sender, address) + MSTORE_GENERAL + // stack: new_ctx, should_transfer_value, value, sender, address, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store the caller in metadata. + %stack (new_ctx, should_transfer_value, value, sender) + -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_CALLER, sender, + new_ctx, should_transfer_value, value, sender) + MSTORE_GENERAL + // stack: new_ctx, should_transfer_value, value, sender, address, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store the call value field in metadata. + %stack (new_ctx, should_transfer_value, value, sender, address) = + -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_CALL_VALUE, value, + should_transfer_value, sender, address, value, new_ctx) + MSTORE_GENERAL + // stack: should_transfer_value, sender, address, value, new_ctx, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + %maybe_transfer_eth + // stack: new_ctx, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store parent context in metadata. + GET_CONTEXT + PUSH @CTX_METADATA_PARENT_CONTEXT + PUSH @SEGMENT_CONTEXT_METADATA + DUP4 // new_ctx + MSTORE_GENERAL + // stack: new_ctx, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // Store parent PC = after_call. + %stack (new_ctx) -> (new_ctx, @SEGMENT_CONTEXT_METADATA, @CTX_METADATA_PARENT_PC, after_call, new_ctx) + MSTORE_GENERAL + // stack: new_ctx, code_addr, gas, args_offset, args_size, ret_offset, ret_size, retdest + + // TODO: Populate CALLDATA + // TODO: Save parent gas and set child gas + // TODO: Populate code + + // TODO: Temporary, remove after above steps are done. + %stack (new_ctx, code_addr, gas, args_offset, args_size) -> (new_ctx) + // stack: new_ctx, ret_offset, ret_size, retdest + // Now, switch to the new context and go to usermode with PC=0. + DUP1 // new_ctx SET_CONTEXT - PUSH 0 + PUSH 0 // jump dest EXIT_KERNEL after_call: - // TODO: Set RETURNDATA etc. + // stack: new_ctx, ret_offset, ret_size, retdest + // TODO: Set RETURNDATA. + // TODO: Return to caller w/ EXIT_KERNEL. diff --git a/evm/src/cpu/kernel/asm/core/transfer.asm b/evm/src/cpu/kernel/asm/core/transfer.asm index 41057aff..0ed48f4d 100644 --- a/evm/src/cpu/kernel/asm/core/transfer.asm +++ b/evm/src/cpu/kernel/asm/core/transfer.asm @@ -14,3 +14,15 @@ global transfer_eth: %jump(transfer_eth) %%after: %endmacro + +// Pre stack: should_transfer, from, to, amount +// Post stack: (empty) +%macro maybe_transfer_eth + %jumpi(%%transfer) + // We're skipping the transfer, so just pop the arguments and return. + %pop3 + %jump(%%after) +%%transfer: + %transfer_eth +%%after: +%endmacro From 0b9881c5e388dea4ddd7cdc7b48688ffb47a79b0 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 9 Sep 2022 12:05:58 -0700 Subject: [PATCH 24/97] blocks in stack manipulation --- evm/src/cpu/kernel/assembler.rs | 12 ++++++ evm/src/cpu/kernel/ast.rs | 10 ++++- evm/src/cpu/kernel/evm_asm.pest | 5 ++- evm/src/cpu/kernel/parser.rs | 23 +++++++--- .../cpu/kernel/stack/stack_manipulation.rs | 43 +++++++++++++++---- 5 files changed, 77 insertions(+), 16 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index f5175c41..f52ae29d 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -551,6 +551,7 @@ mod tests { let dup1 = get_opcode("DUP1"); let swap1 = get_opcode("SWAP1"); let swap2 = get_opcode("SWAP2"); + let swap3 = get_opcode("SWAP3"); let push_label = get_push_opcode(BYTES_PER_OFFSET); let kernel = parse_and_assemble(&["%stack (a) -> (a)"]); @@ -562,6 +563,17 @@ mod tests { let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); assert_eq!(kernel.code, vec![pop, swap1, pop]); + let kernel = parse_and_assemble(&["%stack (a, (b: 3), c) -> (c)"]); + assert_eq!(kernel.code, vec![pop, pop, pop, pop]); + + let kernel = parse_and_assemble(&["%stack ((a: 2), (b: 2)) -> (b, a)"]); + assert_eq!(kernel.code, vec![swap1, swap3, swap1, swap2]); + + let kernel1 = parse_and_assemble(&["%stack ((a: 3), (b: 3), c) -> (c, b, a)"]); + let kernel2 = + parse_and_assemble(&["%stack (a, b, c, d, e, f, g) -> (g, d, e, f, a, b, c)"]); + assert_eq!(kernel1.code, kernel2.code); + let mut consts = HashMap::new(); consts.insert("LIFE".into(), 42.into()); parse_and_assemble_ext(&["%stack (a, b) -> (b, @LIFE)"], consts, true); diff --git a/evm/src/cpu/kernel/ast.rs b/evm/src/cpu/kernel/ast.rs index 24cf01e1..bad60d03 100644 --- a/evm/src/cpu/kernel/ast.rs +++ b/evm/src/cpu/kernel/ast.rs @@ -19,7 +19,7 @@ pub(crate) enum Item { /// The first list gives names to items on the top of the stack. /// The second list specifies replacement items. /// Example: `(a, b, c) -> (c, 5, 0x20, @SOME_CONST, a)`. - StackManipulation(Vec, Vec), + StackManipulation(Vec, Vec), /// Declares a global label. GlobalLabelDeclaration(String), /// Declares a label that is local to the current file. @@ -36,6 +36,14 @@ pub(crate) enum Item { Bytes(Vec), } +/// The left hand side of a %stack stack-manipulation macro. +#[derive(Eq, PartialEq, Clone, Debug)] +pub(crate) enum StackPlaceholder { + Identifier(String), + Block(String, usize), +} + +/// The right hand side of a %stack stack-manipulation macro. #[derive(Eq, PartialEq, Clone, Debug)] pub(crate) enum StackReplacement { /// Can be either a named item or a label. diff --git a/evm/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest index 8ea7de4b..227e2466 100644 --- a/evm/src/cpu/kernel/evm_asm.pest +++ b/evm/src/cpu/kernel/evm_asm.pest @@ -21,7 +21,10 @@ macro_call = ${ "%" ~ !(^"macro" | ^"endmacro" | ^"rep" | ^"endrep" | ^"stack") repeat = { ^"%rep" ~ literal ~ item* ~ ^"%endrep" } paramlist = { "(" ~ identifier ~ ("," ~ identifier)* ~ ")" } macro_arglist = !{ "(" ~ push_target ~ ("," ~ push_target)* ~ ")" } -stack = { ^"%stack" ~ paramlist ~ "->" ~ stack_replacements } +stack = { ^"%stack" ~ stack_placeholders ~ "->" ~ stack_replacements } +stack_placeholders = { "(" ~ stack_placeholder ~ ("," ~ stack_placeholder)* ~ ")" } +stack_placeholder = { identifier | stack_block } +stack_block = { "(" ~ identifier ~ ":" ~ literal_decimal ~ ")" } stack_replacements = { "(" ~ stack_replacement ~ ("," ~ stack_replacement)* ~ ")" } stack_replacement = { literal | identifier | constant | macro_label | variable } global_label_decl = ${ ^"GLOBAL " ~ identifier ~ ":" } diff --git a/evm/src/cpu/kernel/parser.rs b/evm/src/cpu/kernel/parser.rs index 35bde4b6..89da016c 100644 --- a/evm/src/cpu/kernel/parser.rs +++ b/evm/src/cpu/kernel/parser.rs @@ -4,6 +4,7 @@ use ethereum_types::U256; use pest::iterators::Pair; use pest::Parser; +use super::ast::StackPlaceholder; use crate::cpu::kernel::ast::{File, Item, PushTarget, StackReplacement}; /// Parses EVM assembly code. @@ -99,14 +100,11 @@ fn parse_stack(item: Pair) -> Item { let mut inner = item.into_inner(); let params = inner.next().unwrap(); - assert_eq!(params.as_rule(), Rule::paramlist); + assert_eq!(params.as_rule(), Rule::stack_placeholders); let replacements = inner.next().unwrap(); assert_eq!(replacements.as_rule(), Rule::stack_replacements); - let params = params - .into_inner() - .map(|param| param.as_str().to_string()) - .collect(); + let params = params.into_inner().map(parse_stack_placeholder).collect(); let replacements = replacements .into_inner() .map(parse_stack_replacement) @@ -114,6 +112,21 @@ fn parse_stack(item: Pair) -> Item { Item::StackManipulation(params, replacements) } +fn parse_stack_placeholder(target: Pair) -> StackPlaceholder { + assert_eq!(target.as_rule(), Rule::stack_placeholder); + let inner = target.into_inner().next().unwrap(); + match inner.as_rule() { + Rule::identifier => StackPlaceholder::Identifier(inner.as_str().into()), + Rule::stack_block => { + let mut block = inner.into_inner(); + let identifier = block.next().unwrap().as_str(); + let length = block.next().unwrap().as_str().parse().unwrap(); + StackPlaceholder::Block(identifier.to_string(), length) + } + _ => panic!("Unexpected {:?}", inner.as_rule()), + } +} + fn parse_stack_replacement(target: Pair) -> StackReplacement { assert_eq!(target.as_rule(), Rule::stack_replacement); let inner = target.into_inner().next().unwrap(); diff --git a/evm/src/cpu/kernel/stack/stack_manipulation.rs b/evm/src/cpu/kernel/stack/stack_manipulation.rs index 9f685953..faec7e04 100644 --- a/evm/src/cpu/kernel/stack/stack_manipulation.rs +++ b/evm/src/cpu/kernel/stack/stack_manipulation.rs @@ -1,13 +1,13 @@ use std::cmp::Ordering; use std::collections::hash_map::Entry::{Occupied, Vacant}; -use std::collections::{BinaryHeap, HashMap}; +use std::collections::{BinaryHeap, HashMap, HashSet}; use std::hash::Hash; use itertools::Itertools; use crate::cpu::columns::NUM_CPU_COLUMNS; use crate::cpu::kernel::assembler::BYTES_PER_OFFSET; -use crate::cpu::kernel::ast::{Item, PushTarget, StackReplacement}; +use crate::cpu::kernel::ast::{Item, PushTarget, StackPlaceholder, StackReplacement}; use crate::cpu::kernel::stack::permutations::{get_stack_ops_for_perm, is_permutation}; use crate::cpu::kernel::stack::stack_manipulation::StackOp::Pop; use crate::cpu::kernel::utils::u256_to_trimmed_be_bytes; @@ -25,25 +25,50 @@ pub(crate) fn expand_stack_manipulation(body: Vec) -> Vec { expanded } -fn expand(names: Vec, replacements: Vec) -> Vec { +fn expand(names: Vec, replacements: Vec) -> Vec { + let mut stack_blocks = HashMap::new(); + let mut stack_names = HashSet::new(); + let mut src = names .iter() .cloned() - .map(StackItem::NamedItem) + .flat_map(|item| match item { + StackPlaceholder::Identifier(name) => { + stack_names.insert(name.clone()); + vec![StackItem::NamedItem(name)] + } + StackPlaceholder::Block(name, n) => { + stack_blocks.insert(name.clone(), n); + (0..n) + .map(|i| { + let literal_name = format!("block_{}_{}", name, i); + StackItem::NamedItem(literal_name) + }) + .collect_vec() + } + }) .collect_vec(); let mut dst = replacements .into_iter() - .map(|item| match item { + .flat_map(|item| match item { StackReplacement::Identifier(name) => { // May be either a named item or a label. Named items have precedence. - if names.contains(&name) { - StackItem::NamedItem(name) + if stack_blocks.contains_key(&name) { + let n = *stack_blocks.get(&name).unwrap(); + (0..n) + .map(|i| { + let literal_name = format!("block_{}_{}", name, i); + StackItem::NamedItem(literal_name) + }) + .collect_vec() + } else if stack_names.contains(&name) { + vec![StackItem::NamedItem(name)] } else { - StackItem::PushTarget(PushTarget::Label(name)) + vec![StackItem::PushTarget(PushTarget::Label(name))] } } - StackReplacement::Literal(n) => StackItem::PushTarget(PushTarget::Literal(n)), + StackReplacement::Literal(n) => vec![StackItem::PushTarget(PushTarget::Literal(n))], StackReplacement::MacroLabel(_) | StackReplacement::MacroVar(_) | StackReplacement::Constant(_) => { From cae5f4870cd3c57b777a7d8c6470e17f3151f246 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Sat, 10 Sep 2022 13:20:30 -0700 Subject: [PATCH 25/97] Stack pointer + underflow/overflow checks (#710) * Stack pointer + underflow/overflow checks * Daniel comments * Extra docs --- evm/src/cpu/columns/mod.rs | 7 ++ evm/src/cpu/control_flow.rs | 39 ++++++--- evm/src/cpu/cpu_stark.rs | 7 +- evm/src/cpu/mod.rs | 1 + evm/src/cpu/stack_bounds.rs | 157 ++++++++++++++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 evm/src/cpu/stack_bounds.rs diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 567c5a97..93e93ce6 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -38,6 +38,13 @@ pub struct CpuColumnsView { /// If CPU cycle: The program counter for the current instruction. pub program_counter: T, + /// If CPU cycle: The stack length. + pub stack_len: T, + + /// If CPU cycle: A prover-provided value needed to show that the instruction does not cause the + /// stack to underflow or overflow. + pub stack_len_bounds_aux: T, + /// If CPU cycle: We're in kernel (privileged) mode. pub is_kernel_mode: T, diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index e6ded598..5a43f7cf 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -68,14 +68,16 @@ pub fn eval_packed_generic( lv.is_cpu_cycle * is_native_instruction * (lv.is_kernel_mode - nv.is_kernel_mode), ); - // If a non-CPU cycle row is followed by a CPU cycle row, then the `program_counter` of the CPU - // cycle row is route_txn (the entry point of our kernel) and it is in kernel mode. + // If a non-CPU cycle row is followed by a CPU cycle row, then: + // - the `program_counter` of the CPU cycle row is `route_txn` (the entry point of our kernel), + // - execution is in kernel mode, and + // - the stack is empty. + let is_last_noncpu_cycle = (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle; let pc_diff = nv.program_counter - P::Scalar::from_canonical_usize(KERNEL.global_labels["route_txn"]); - yield_constr.constraint_transition((lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle * pc_diff); - yield_constr.constraint_transition( - (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle * (nv.is_kernel_mode - P::ONES), - ); + yield_constr.constraint_transition(is_last_noncpu_cycle * pc_diff); + yield_constr.constraint_transition(is_last_noncpu_cycle * (nv.is_kernel_mode - P::ONES)); + yield_constr.constraint_transition(is_last_noncpu_cycle * nv.stack_len); // The last row must be a CPU cycle row. yield_constr.constraint_last_row(lv.is_cpu_cycle - P::ONES); @@ -115,17 +117,32 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr.constraint_transition(builder, kernel_constr); } - // If a non-CPU cycle row is followed by a CPU cycle row, then the `program_counter` of the CPU - // cycle row is route_txn (the entry point of our kernel) and it is in kernel mode. + // If a non-CPU cycle row is followed by a CPU cycle row, then: + // - the `program_counter` of the CPU cycle row is `route_txn` (the entry point of our kernel), + // - execution is in kernel mode, and + // - the stack is empty. { - let filter = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle); + let is_last_noncpu_cycle = + builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle); + + // Start at `route_txn`. let route_txn = builder.constant_extension(F::Extension::from_canonical_usize( KERNEL.global_labels["route_txn"], )); let pc_diff = builder.sub_extension(nv.program_counter, route_txn); - let pc_constr = builder.mul_extension(filter, pc_diff); + let pc_constr = builder.mul_extension(is_last_noncpu_cycle, pc_diff); yield_constr.constraint_transition(builder, pc_constr); - let kernel_constr = builder.mul_sub_extension(filter, nv.is_kernel_mode, filter); + + // Start in kernel mode + let kernel_constr = builder.mul_sub_extension( + is_last_noncpu_cycle, + nv.is_kernel_mode, + is_last_noncpu_cycle, + ); + yield_constr.constraint_transition(builder, kernel_constr); + + // Start with empty stack + let kernel_constr = builder.mul_extension(is_last_noncpu_cycle, nv.stack_len); yield_constr.constraint_transition(builder, kernel_constr); } diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 9fd4792d..9949b044 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -9,7 +9,9 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; -use crate::cpu::{bootstrap_kernel, control_flow, decode, jumps, simple_logic, syscalls}; +use crate::cpu::{ + bootstrap_kernel, control_flow, decode, jumps, simple_logic, stack_bounds, syscalls, +}; use crate::cross_table_lookup::Column; use crate::memory::NUM_CHANNELS; use crate::stark::Stark; @@ -94,6 +96,7 @@ impl CpuStark { let local_values: &mut CpuColumnsView<_> = local_values.borrow_mut(); decode::generate(local_values); simple_logic::generate(local_values); + stack_bounds::generate(local_values); // Must come after `decode`. } } @@ -115,6 +118,7 @@ impl, const D: usize> Stark for CpuStark, const D: usize> Stark for CpuStark(lv: &mut CpuColumnsView) { + let cycle_filter = lv.is_cpu_cycle; + if cycle_filter == F::ZERO { + return; + } + + let check_underflow: F = DECREMENTING_FLAGS.map(|i| lv[i]).into_iter().sum(); + let check_overflow: F = INCREMENTING_FLAGS.map(|i| lv[i]).into_iter().sum(); + let no_check = F::ONE - (check_underflow + check_overflow); + + let disallowed_len = check_overflow * F::from_canonical_u64(MAX_USER_STACK_SIZE) - no_check; + let diff = lv.stack_len - disallowed_len; + + let user_mode = F::ONE - lv.is_kernel_mode; + let rhs = user_mode + check_underflow; + + lv.stack_len_bounds_aux = match diff.try_inverse() { + Some(diff_inv) => diff_inv * rhs, // `rhs` may be a value other than 1 or 0 + None => { + assert_eq!(rhs, F::ZERO); + F::ZERO + } + } +} + +pub fn eval_packed( + lv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + // `check_underflow`, `check_overflow`, and `no_check` are mutually exclusive. + let check_underflow: P = DECREMENTING_FLAGS.map(|i| lv[i]).into_iter().sum(); + let check_overflow: P = INCREMENTING_FLAGS.map(|i| lv[i]).into_iter().sum(); + let no_check = P::ONES - (check_underflow + check_overflow); + + // If `check_underflow`, then the instruction we are executing pops a value from the stack + // without reading it from memory, and the usual underflow checks do not work. We must show that + // `lv.stack_len` is not 0. We choose to perform this check whether or not we're in kernel mode. + // (The check in kernel mode is not necessary if the kernel is correct, but this is an easy + // sanity check. + // If `check_overflow`, then the instruction we are executing increases the stack length by 1. + // If we are in user mode, then we must show that the stack length is not currently + // `MAX_USER_STACK_SIZE`, as this is the maximum for the user stack. Note that this check must + // not run in kernel mode as the kernel's stack length is unrestricted. + // If `no_check`, then we don't need to check anything. The constraint is written to always + // test that `lv.stack_len` does not equal _something_ so we just show that it's not -1, which + // is always true. + + // 0 if `check_underflow`, `MAX_USER_STACK_SIZE` if `check_overflow`, and -1 if `no_check`. + let disallowed_len = + check_overflow * P::Scalar::from_canonical_u64(MAX_USER_STACK_SIZE) - no_check; + // This `lhs` must equal some `rhs`. If `rhs` is nonzero, then this shows that `lv.stack_len` is + // not `disallowed_len`. + let lhs = (lv.stack_len - disallowed_len) * lv.stack_len_bounds_aux; + + // We want this constraint to be active if we're in user mode OR the instruction might overflow. + // (In other words, we want to _skip_ overflow checks in kernel mode). + let user_mode = P::ONES - lv.is_kernel_mode; + // `rhs` is may be 0, 1, or 2. It's 0 if we're in kernel mode and we would be checking for + // overflow. + // Note: if `user_mode` and `check_underflow` then, `rhs` is 2. This is fine: we're still + // showing that `lv.stack_len - disallowed_len` is nonzero. + let rhs = user_mode + check_underflow; + + yield_constr.constraint(lv.is_cpu_cycle * (lhs - rhs)); +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + let one = builder.one_extension(); + let max_stack_size = + builder.constant_extension(F::from_canonical_u64(MAX_USER_STACK_SIZE).into()); + + // `check_underflow`, `check_overflow`, and `no_check` are mutually exclusive. + let check_underflow = builder.add_many_extension(DECREMENTING_FLAGS.map(|i| lv[i])); + let check_overflow = builder.add_many_extension(INCREMENTING_FLAGS.map(|i| lv[i])); + let no_check = { + let any_check = builder.add_extension(check_underflow, check_overflow); + builder.sub_extension(one, any_check) + }; + + // If `check_underflow`, then the instruction we are executing pops a value from the stack + // without reading it from memory, and the usual underflow checks do not work. We must show that + // `lv.stack_len` is not 0. We choose to perform this check whether or not we're in kernel mode. + // (The check in kernel mode is not necessary if the kernel is correct, but this is an easy + // sanity check. + // If `check_overflow`, then the instruction we are executing increases the stack length by 1. + // If we are in user mode, then we must show that the stack length is not currently + // `MAX_USER_STACK_SIZE`, as this is the maximum for the user stack. Note that this check must + // not run in kernel mode as the kernel's stack length is unrestricted. + // If `no_check`, then we don't need to check anything. The constraint is written to always + // test that `lv.stack_len` does not equal _something_ so we just show that it's not -1, which + // is always true. + + // 0 if `check_underflow`, `MAX_USER_STACK_SIZE` if `check_overflow`, and -1 if `no_check`. + let disallowed_len = builder.mul_sub_extension(check_overflow, max_stack_size, no_check); + // This `lhs` must equal some `rhs`. If `rhs` is nonzero, then this shows that `lv.stack_len` is + // not `disallowed_len`. + let lhs = { + let diff = builder.sub_extension(lv.stack_len, disallowed_len); + builder.mul_extension(diff, lv.stack_len_bounds_aux) + }; + + // We want this constraint to be active if we're in user mode OR the instruction might overflow. + // (In other words, we want to _skip_ overflow checks in kernel mode). + let user_mode = builder.sub_extension(one, lv.is_kernel_mode); + // `rhs` is may be 0, 1, or 2. It's 0 if we're in kernel mode and we would be checking for + // overflow. + // Note: if `user_mode` and `check_underflow` then, `rhs` is 2. This is fine: we're still + // showing that `lv.stack_len - disallowed_len` is nonzero. + let rhs = builder.add_extension(user_mode, check_underflow); + + let constr = { + let diff = builder.sub_extension(lhs, rhs); + builder.mul_extension(lv.is_cpu_cycle, diff) + }; + yield_constr.constraint(builder, constr); +} From 11666f02d2e7262716d43e0d9c13d409d67c19b5 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Sun, 11 Sep 2022 19:16:39 +0200 Subject: [PATCH 26/97] Clippy --- plonky2/src/util/serialization.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/util/serialization.rs b/plonky2/src/util/serialization.rs index da03213c..978134b6 100644 --- a/plonky2/src/util/serialization.rs +++ b/plonky2/src/util/serialization.rs @@ -282,7 +282,7 @@ impl Buffer { arity: usize, compressed: bool, ) -> Result> { - let evals = self.read_field_ext_vec::(arity - if compressed { 1 } else { 0 })?; + let evals = self.read_field_ext_vec::(arity - usize::from(compressed))?; let merkle_proof = self.read_merkle_proof()?; Ok(FriQueryStep { evals, From a930c1a8234e619e908315a09d6daf997b8b7a39 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 12 Sep 2022 08:09:17 +0200 Subject: [PATCH 27/97] s/l1/l0 --- evm/src/recursive_verifier.rs | 12 ++++++------ evm/src/verifier.rs | 18 +++++++++--------- field/src/zero_poly_coset.rs | 4 ++-- plonky2/src/plonk/plonk_common.rs | 12 ++++++------ plonky2/src/plonk/vanishing_poly.rs | 22 +++++++++++----------- starky/src/recursive_verifier.rs | 12 ++++++------ starky/src/verifier.rs | 18 +++++++++--------- 7 files changed, 49 insertions(+), 49 deletions(-) diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index 800ee461..000efce9 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -181,8 +181,8 @@ fn verify_stark_proof_with_challenges_circuit< let degree_bits = proof.recover_degree_bits(inner_config); let zeta_pow_deg = builder.exp_power_of_2_extension(challenges.stark_zeta, degree_bits); let z_h_zeta = builder.sub_extension(zeta_pow_deg, one); - let (l_1, l_last) = - eval_l_1_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); + let (l_0, l_last) = + eval_l_0_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); let last = builder.constant_extension(F::Extension::primitive_root_of_unity(degree_bits).inverse()); let z_last = builder.sub_extension(challenges.stark_zeta, last); @@ -191,7 +191,7 @@ fn verify_stark_proof_with_challenges_circuit< builder.zero_extension(), challenges.stark_alphas.clone(), z_last, - l_1, + l_0, l_last, ); @@ -254,7 +254,7 @@ fn verify_stark_proof_with_challenges_circuit< ); } -fn eval_l_1_and_l_last_circuit, const D: usize>( +fn eval_l_0_and_l_last_circuit, const D: usize>( builder: &mut CircuitBuilder, log_n: usize, x: ExtensionTarget, @@ -263,12 +263,12 @@ fn eval_l_1_and_l_last_circuit, const D: usize>( let n = builder.constant_extension(F::Extension::from_canonical_usize(1 << log_n)); let g = builder.constant_extension(F::Extension::primitive_root_of_unity(log_n)); let one = builder.one_extension(); - let l_1_deno = builder.mul_sub_extension(n, x, n); + let l_0_deno = builder.mul_sub_extension(n, x, n); let l_last_deno = builder.mul_sub_extension(g, x, one); let l_last_deno = builder.mul_extension(n, l_last_deno); ( - builder.div_extension(z_x, l_1_deno), + builder.div_extension(z_x, l_0_deno), builder.div_extension(z_x, l_last_deno), ) } diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index 9b56422d..3f5a5a88 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -133,7 +133,7 @@ where }; let degree_bits = proof.recover_degree_bits(config); - let (l_1, l_last) = eval_l_1_and_l_last(degree_bits, challenges.stark_zeta); + let (l_0, l_last) = eval_l_0_and_l_last(degree_bits, challenges.stark_zeta); let last = F::primitive_root_of_unity(degree_bits).inverse(); let z_last = challenges.stark_zeta - last.into(); let mut consumer = ConstraintConsumer::::new( @@ -143,7 +143,7 @@ where .map(|&alpha| F::Extension::from_basefield(alpha)) .collect::>(), z_last, - l_1, + l_0, l_last, ); let num_permutation_zs = stark.num_permutation_batches(config); @@ -204,10 +204,10 @@ where Ok(()) } -/// Evaluate the Lagrange polynomials `L_1` and `L_n` at a point `x`. -/// `L_1(x) = (x^n - 1)/(n * (x - 1))` -/// `L_n(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. -fn eval_l_1_and_l_last(log_n: usize, x: F) -> (F, F) { +/// Evaluate the Lagrange polynomials `L_0` and `L_(n-1)` at a point `x`. +/// `L_0(x) = (x^n - 1)/(n * (x - 1))` +/// `L_(n-1)(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. +fn eval_l_0_and_l_last(log_n: usize, x: F) -> (F, F) { let n = F::from_canonical_usize(1 << log_n); let g = F::primitive_root_of_unity(log_n); let z_x = x.exp_power_of_2(log_n) - F::ONE; @@ -222,10 +222,10 @@ mod tests { use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; - use crate::verifier::eval_l_1_and_l_last; + use crate::verifier::eval_l_0_and_l_last; #[test] - fn test_eval_l_1_and_l_last() { + fn test_eval_l_0_and_l_last() { type F = GoldilocksField; let log_n = 5; let n = 1 << log_n; @@ -234,7 +234,7 @@ mod tests { let expected_l_first_x = PolynomialValues::selector(n, 0).ifft().eval(x); let expected_l_last_x = PolynomialValues::selector(n, n - 1).ifft().eval(x); - let (l_first_x, l_last_x) = eval_l_1_and_l_last(log_n, x); + let (l_first_x, l_last_x) = eval_l_0_and_l_last(log_n, x); assert_eq!(l_first_x, expected_l_first_x); assert_eq!(l_last_x, expected_l_last_x); } diff --git a/field/src/zero_poly_coset.rs b/field/src/zero_poly_coset.rs index 18cc3238..8d63bc69 100644 --- a/field/src/zero_poly_coset.rs +++ b/field/src/zero_poly_coset.rs @@ -51,8 +51,8 @@ impl ZeroPolyOnCoset { packed } - /// Returns `L_1(x) = Z_H(x)/(n * (x - 1))` with `x = w^i`. - pub fn eval_l1(&self, i: usize, x: F) -> F { + /// Returns `L_0(x) = Z_H(x)/(n * (x - 1))` with `x = w^i`. + pub fn eval_l_0(&self, i: usize, x: F) -> F { // Could also precompute the inverses using Montgomery. self.eval(i) * (self.n * (x - F::ONE)).inverse() } diff --git a/plonky2/src/plonk/plonk_common.rs b/plonky2/src/plonk/plonk_common.rs index 4f92d732..e947353b 100644 --- a/plonky2/src/plonk/plonk_common.rs +++ b/plonky2/src/plonk/plonk_common.rs @@ -64,31 +64,31 @@ pub(crate) fn eval_zero_poly(n: usize, x: F) -> F { x.exp_u64(n as u64) - F::ONE } -/// Evaluate the Lagrange basis `L_1` with `L_1(1) = 1`, and `L_1(x) = 0` for other members of an +/// Evaluate the Lagrange basis `L_0` with `L_0(1) = 1`, and `L_0(x) = 0` for other members of the /// order `n` multiplicative subgroup. -pub(crate) fn eval_l_1(n: usize, x: F) -> F { +pub(crate) fn eval_l_0(n: usize, x: F) -> F { if x.is_one() { // The code below would divide by zero, since we have (x - 1) in both the numerator and // denominator. return F::ONE; } - // L_1(x) = (x^n - 1) / (n * (x - 1)) + // L_0(x) = (x^n - 1) / (n * (x - 1)) // = Z(x) / (n * (x - 1)) eval_zero_poly(n, x) / (F::from_canonical_usize(n) * (x - F::ONE)) } -/// Evaluates the Lagrange basis L_1(x), which has L_1(1) = 1 and vanishes at all other points in +/// Evaluates the Lagrange basis L_0(x), which has L_0(1) = 1 and vanishes at all other points in /// the order-`n` subgroup. /// /// Assumes `x != 1`; if `x` could be 1 then this is unsound. -pub(crate) fn eval_l_1_circuit, const D: usize>( +pub(crate) fn eval_l_0_circuit, const D: usize>( builder: &mut CircuitBuilder, n: usize, x: ExtensionTarget, x_pow_n: ExtensionTarget, ) -> ExtensionTarget { - // L_1(x) = (x^n - 1) / (n * (x - 1)) + // L_0(x) = (x^n - 1) / (n * (x - 1)) // = Z(x) / (n * (x - 1)) let one = builder.one_extension(); let neg_one = builder.neg_one(); diff --git a/plonky2/src/plonk/vanishing_poly.rs b/plonky2/src/plonk/vanishing_poly.rs index ab0ba53b..303f698b 100644 --- a/plonky2/src/plonk/vanishing_poly.rs +++ b/plonky2/src/plonk/vanishing_poly.rs @@ -10,7 +10,7 @@ use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::CommonCircuitData; use crate::plonk::config::GenericConfig; use crate::plonk::plonk_common; -use crate::plonk::plonk_common::eval_l_1_circuit; +use crate::plonk::plonk_common::eval_l_0_circuit; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBaseBatch}; use crate::util::partial_products::{check_partial_products, check_partial_products_circuit}; use crate::util::reducing::ReducingFactorTarget; @@ -41,17 +41,17 @@ pub(crate) fn eval_vanishing_poly< let constraint_terms = evaluate_gate_constraints(common_data, vars); - // The L_1(x) (Z(x) - 1) vanishing terms. + // The L_0(x) (Z(x) - 1) vanishing terms. let mut vanishing_z_1_terms = Vec::new(); // The terms checking the partial products. let mut vanishing_partial_products_terms = Vec::new(); - let l1_x = plonk_common::eval_l_1(common_data.degree(), x); + let l_0_x = plonk_common::eval_l_0(common_data.degree(), x); for i in 0..common_data.config.num_challenges { let z_x = local_zs[i]; let z_gx = next_zs[i]; - vanishing_z_1_terms.push(l1_x * (z_x - F::Extension::ONE)); + vanishing_z_1_terms.push(l_0_x * (z_x - F::Extension::ONE)); let numerator_values = (0..common_data.config.num_routed_wires) .map(|j| { @@ -135,7 +135,7 @@ pub(crate) fn eval_vanishing_poly_base_batch< let mut numerator_values = Vec::with_capacity(num_routed_wires); let mut denominator_values = Vec::with_capacity(num_routed_wires); - // The L_1(x) (Z(x) - 1) vanishing terms. + // The L_0(x) (Z(x) - 1) vanishing terms. let mut vanishing_z_1_terms = Vec::with_capacity(num_challenges); // The terms checking the partial products. let mut vanishing_partial_products_terms = Vec::new(); @@ -152,11 +152,11 @@ pub(crate) fn eval_vanishing_poly_base_batch< let constraint_terms = PackedStridedView::new(&constraint_terms_batch, n, k); - let l1_x = z_h_on_coset.eval_l1(index, x); + let l_0_x = z_h_on_coset.eval_l_0(index, x); for i in 0..num_challenges { let z_x = local_zs[i]; let z_gx = next_zs[i]; - vanishing_z_1_terms.push(l1_x * z_x.sub_one()); + vanishing_z_1_terms.push(l_0_x * z_x.sub_one()); numerator_values.extend((0..num_routed_wires).map(|j| { let wire_value = vars.local_wires[j]; @@ -332,12 +332,12 @@ pub(crate) fn eval_vanishing_poly_circuit< evaluate_gate_constraints_circuit(builder, common_data, vars,) ); - // The L_1(x) (Z(x) - 1) vanishing terms. + // The L_0(x) (Z(x) - 1) vanishing terms. let mut vanishing_z_1_terms = Vec::new(); // The terms checking the partial products. let mut vanishing_partial_products_terms = Vec::new(); - let l1_x = eval_l_1_circuit(builder, common_data.degree(), x, x_pow_deg); + let l_0_x = eval_l_0_circuit(builder, common_data.degree(), x, x_pow_deg); // Holds `k[i] * x`. let mut s_ids = Vec::new(); @@ -350,8 +350,8 @@ pub(crate) fn eval_vanishing_poly_circuit< let z_x = local_zs[i]; let z_gx = next_zs[i]; - // L_1(x) Z(x) = 0. - vanishing_z_1_terms.push(builder.mul_sub_extension(l1_x, z_x, l1_x)); + // L_0(x) (Z(x) - 1) = 0. + vanishing_z_1_terms.push(builder.mul_sub_extension(l_0_x, z_x, l_0_x)); let mut numerator_values = Vec::new(); let mut denominator_values = Vec::new(); diff --git a/starky/src/recursive_verifier.rs b/starky/src/recursive_verifier.rs index 7f20d89b..04858d55 100644 --- a/starky/src/recursive_verifier.rs +++ b/starky/src/recursive_verifier.rs @@ -102,8 +102,8 @@ fn verify_stark_proof_with_challenges_circuit< let zeta_pow_deg = builder.exp_power_of_2_extension(challenges.stark_zeta, degree_bits); let z_h_zeta = builder.sub_extension(zeta_pow_deg, one); - let (l_1, l_last) = - eval_l_1_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); + let (l_0, l_last) = + eval_l_0_and_l_last_circuit(builder, degree_bits, challenges.stark_zeta, z_h_zeta); let last = builder.constant_extension(F::Extension::primitive_root_of_unity(degree_bits).inverse()); let z_last = builder.sub_extension(challenges.stark_zeta, last); @@ -112,7 +112,7 @@ fn verify_stark_proof_with_challenges_circuit< builder.zero_extension(), challenges.stark_alphas, z_last, - l_1, + l_0, l_last, ); @@ -170,7 +170,7 @@ fn verify_stark_proof_with_challenges_circuit< ); } -fn eval_l_1_and_l_last_circuit, const D: usize>( +fn eval_l_0_and_l_last_circuit, const D: usize>( builder: &mut CircuitBuilder, log_n: usize, x: ExtensionTarget, @@ -179,12 +179,12 @@ fn eval_l_1_and_l_last_circuit, const D: usize>( let n = builder.constant_extension(F::Extension::from_canonical_usize(1 << log_n)); let g = builder.constant_extension(F::Extension::primitive_root_of_unity(log_n)); let one = builder.one_extension(); - let l_1_deno = builder.mul_sub_extension(n, x, n); + let l_0_deno = builder.mul_sub_extension(n, x, n); let l_last_deno = builder.mul_sub_extension(g, x, one); let l_last_deno = builder.mul_extension(n, l_last_deno); ( - builder.div_extension(z_x, l_1_deno), + builder.div_extension(z_x, l_0_deno), builder.div_extension(z_x, l_last_deno), ) } diff --git a/starky/src/verifier.rs b/starky/src/verifier.rs index 306d3d14..efb3d29c 100644 --- a/starky/src/verifier.rs +++ b/starky/src/verifier.rs @@ -78,7 +78,7 @@ where .unwrap(), }; - let (l_1, l_last) = eval_l_1_and_l_last(degree_bits, challenges.stark_zeta); + let (l_0, l_last) = eval_l_0_and_l_last(degree_bits, challenges.stark_zeta); let last = F::primitive_root_of_unity(degree_bits).inverse(); let z_last = challenges.stark_zeta - last.into(); let mut consumer = ConstraintConsumer::::new( @@ -88,7 +88,7 @@ where .map(|&alpha| F::Extension::from_basefield(alpha)) .collect::>(), z_last, - l_1, + l_0, l_last, ); let permutation_data = stark.uses_permutation_args().then(|| PermutationCheckVars { @@ -144,10 +144,10 @@ where Ok(()) } -/// Evaluate the Lagrange polynomials `L_1` and `L_n` at a point `x`. -/// `L_1(x) = (x^n - 1)/(n * (x - 1))` -/// `L_n(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. -fn eval_l_1_and_l_last(log_n: usize, x: F) -> (F, F) { +/// Evaluate the Lagrange polynomials `L_0` and `L_(n-1)` at a point `x`. +/// `L_0(x) = (x^n - 1)/(n * (x - 1))` +/// `L_(n-1)(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. +fn eval_l_0_and_l_last(log_n: usize, x: F) -> (F, F) { let n = F::from_canonical_usize(1 << log_n); let g = F::primitive_root_of_unity(log_n); let z_x = x.exp_power_of_2(log_n) - F::ONE; @@ -189,10 +189,10 @@ mod tests { use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; - use crate::verifier::eval_l_1_and_l_last; + use crate::verifier::eval_l_0_and_l_last; #[test] - fn test_eval_l_1_and_l_last() { + fn test_eval_l_0_and_l_last() { type F = GoldilocksField; let log_n = 5; let n = 1 << log_n; @@ -201,7 +201,7 @@ mod tests { let expected_l_first_x = PolynomialValues::selector(n, 0).ifft().eval(x); let expected_l_last_x = PolynomialValues::selector(n, n - 1).ifft().eval(x); - let (l_first_x, l_last_x) = eval_l_1_and_l_last(log_n, x); + let (l_first_x, l_last_x) = eval_l_0_and_l_last(log_n, x); assert_eq!(l_first_x, expected_l_first_x); assert_eq!(l_last_x, expected_l_last_x); } From e8eca780ae9bd1751b170fa40413bc1b5638c056 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 13 Sep 2022 13:06:47 +0200 Subject: [PATCH 28/97] Fix num_limbs in BaseSumGate --- plonky2/src/gates/base_sum.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/plonky2/src/gates/base_sum.rs b/plonky2/src/gates/base_sum.rs index a7f4fcb3..37f03414 100644 --- a/plonky2/src/gates/base_sum.rs +++ b/plonky2/src/gates/base_sum.rs @@ -32,7 +32,8 @@ impl BaseSumGate { } pub fn new_from_config(config: &CircuitConfig) -> Self { - let num_limbs = F::BITS.min(config.num_routed_wires - Self::START_LIMBS); + let num_limbs = + logarithm(F::ORDER as usize, B).min(config.num_routed_wires - Self::START_LIMBS); Self::new(num_limbs) } @@ -192,6 +193,23 @@ impl SimpleGenerator for BaseSplitGenerator } } +/// Returns the largest `i` such that `base**i < n`. +const fn logarithm(n: usize, base: usize) -> usize { + assert!(n > 0); + assert!(base > 1); + let mut i = 0; + let mut cur: usize = 1; + loop { + let (mul, overflow) = cur.overflowing_mul(base); + if overflow || mul >= n { + return i; + } else { + i += 1; + cur = mul; + } + } +} + #[cfg(test)] mod tests { use anyhow::Result; From 2a37aeca5db2852aa84955e1bb1ccbe94a2ba0fd Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 14 Sep 2022 05:48:37 +0200 Subject: [PATCH 29/97] Move to `util` and rename --- plonky2/src/gates/base_sum.rs | 20 ++------------------ util/src/lib.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/plonky2/src/gates/base_sum.rs b/plonky2/src/gates/base_sum.rs index 37f03414..43ae2fd2 100644 --- a/plonky2/src/gates/base_sum.rs +++ b/plonky2/src/gates/base_sum.rs @@ -3,6 +3,7 @@ use std::ops::Range; use plonky2_field::extension::Extendable; use plonky2_field::packed::PackedField; use plonky2_field::types::{Field, Field64}; +use plonky2_util::log_floor; use crate::gates::gate::Gate; use crate::gates::packed_util::PackedEvaluableBase; @@ -33,7 +34,7 @@ impl BaseSumGate { pub fn new_from_config(config: &CircuitConfig) -> Self { let num_limbs = - logarithm(F::ORDER as usize, B).min(config.num_routed_wires - Self::START_LIMBS); + log_floor(F::ORDER as usize, B).min(config.num_routed_wires - Self::START_LIMBS); Self::new(num_limbs) } @@ -193,23 +194,6 @@ impl SimpleGenerator for BaseSplitGenerator } } -/// Returns the largest `i` such that `base**i < n`. -const fn logarithm(n: usize, base: usize) -> usize { - assert!(n > 0); - assert!(base > 1); - let mut i = 0; - let mut cur: usize = 1; - loop { - let (mul, overflow) = cur.overflowing_mul(base); - if overflow || mul >= n { - return i; - } else { - i += 1; - cur = mul; - } - } -} - #[cfg(test)] mod tests { use anyhow::Result; diff --git a/util/src/lib.rs b/util/src/lib.rs index 61677ff0..33fe7ab4 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -38,6 +38,23 @@ pub fn log2_strict(n: usize) -> usize { res as usize } +/// Returns the largest `i` such that `base**i < n`. +pub const fn log_floor(n: usize, base: usize) -> usize { + assert!(n > 0); + assert!(base > 1); + let mut i = 0; + let mut cur: usize = 1; + loop { + let (mul, overflow) = cur.overflowing_mul(base); + if overflow || mul >= n { + return i; + } else { + i += 1; + cur = mul; + } + } +} + /// Permutes `arr` such that each index is mapped to its reverse in binary. pub fn reverse_index_bits(arr: &[T]) -> Vec { let n = arr.len(); From 0aecc2a3cf28a6a0733e1dd84b338db8ba4215c1 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 14 Sep 2022 06:19:06 +0200 Subject: [PATCH 30/97] Feedback --- plonky2/src/gates/base_sum.rs | 2 +- util/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plonky2/src/gates/base_sum.rs b/plonky2/src/gates/base_sum.rs index 43ae2fd2..93ac7e8d 100644 --- a/plonky2/src/gates/base_sum.rs +++ b/plonky2/src/gates/base_sum.rs @@ -34,7 +34,7 @@ impl BaseSumGate { pub fn new_from_config(config: &CircuitConfig) -> Self { let num_limbs = - log_floor(F::ORDER as usize, B).min(config.num_routed_wires - Self::START_LIMBS); + log_floor(F::ORDER as usize - 1, B).min(config.num_routed_wires - Self::START_LIMBS); Self::new(num_limbs) } diff --git a/util/src/lib.rs b/util/src/lib.rs index 33fe7ab4..3136a4b2 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -38,7 +38,7 @@ pub fn log2_strict(n: usize) -> usize { res as usize } -/// Returns the largest `i` such that `base**i < n`. +/// Returns the largest integer `i` such that `base**i <= n`. pub const fn log_floor(n: usize, base: usize) -> usize { assert!(n > 0); assert!(base > 1); @@ -46,7 +46,7 @@ pub const fn log_floor(n: usize, base: usize) -> usize { let mut cur: usize = 1; loop { let (mul, overflow) = cur.overflowing_mul(base); - if overflow || mul >= n { + if overflow || mul > n { return i; } else { i += 1; From b25986ce57e39782bf0451b72cf831e58931d95f Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Tue, 13 Sep 2022 22:03:08 -0700 Subject: [PATCH 31/97] parentheses change --- evm/src/cpu/kernel/assembler.rs | 8 ++++---- evm/src/cpu/kernel/evm_asm.pest | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index f52ae29d..da71d5ce 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -562,14 +562,14 @@ mod tests { let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); assert_eq!(kernel.code, vec![pop, swap1, pop]); - - let kernel = parse_and_assemble(&["%stack (a, (b: 3), c) -> (c)"]); + + let kernel = parse_and_assemble(&["%stack (a, b: 3, c) -> (c)"]); assert_eq!(kernel.code, vec![pop, pop, pop, pop]); - let kernel = parse_and_assemble(&["%stack ((a: 2), (b: 2)) -> (b, a)"]); + let kernel = parse_and_assemble(&["%stack (a: 2, b: 2) -> (b, a)"]); assert_eq!(kernel.code, vec![swap1, swap3, swap1, swap2]); - let kernel1 = parse_and_assemble(&["%stack ((a: 3), (b: 3), c) -> (c, b, a)"]); + let kernel1 = parse_and_assemble(&["%stack (a: 3, b: 3, c) -> (c, b, a)"]); let kernel2 = parse_and_assemble(&["%stack (a, b, c, d, e, f, g) -> (g, d, e, f, a, b, c)"]); assert_eq!(kernel1.code, kernel2.code); diff --git a/evm/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest index 227e2466..89d06e74 100644 --- a/evm/src/cpu/kernel/evm_asm.pest +++ b/evm/src/cpu/kernel/evm_asm.pest @@ -23,8 +23,8 @@ paramlist = { "(" ~ identifier ~ ("," ~ identifier)* ~ ")" } macro_arglist = !{ "(" ~ push_target ~ ("," ~ push_target)* ~ ")" } stack = { ^"%stack" ~ stack_placeholders ~ "->" ~ stack_replacements } stack_placeholders = { "(" ~ stack_placeholder ~ ("," ~ stack_placeholder)* ~ ")" } -stack_placeholder = { identifier | stack_block } -stack_block = { "(" ~ identifier ~ ":" ~ literal_decimal ~ ")" } +stack_placeholder = { stack_block | identifier } +stack_block = { identifier ~ ":" ~ literal_decimal } stack_replacements = { "(" ~ stack_replacement ~ ("," ~ stack_replacement)* ~ ")" } stack_replacement = { literal | identifier | constant | macro_label | variable } global_label_decl = ${ ^"GLOBAL " ~ identifier ~ ":" } From a5f34d9a2efa41a8946f1073ddf8be6119da7347 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Tue, 13 Sep 2022 22:03:19 -0700 Subject: [PATCH 32/97] fix --- evm/src/cpu/kernel/assembler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index da71d5ce..0471bf99 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -562,7 +562,7 @@ mod tests { let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); assert_eq!(kernel.code, vec![pop, swap1, pop]); - + let kernel = parse_and_assemble(&["%stack (a, b: 3, c) -> (c)"]); assert_eq!(kernel.code, vec![pop, pop, pop, pop]); From dc145501fdd8af104d81a7584c73f633644a4020 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 14 Sep 2022 10:10:08 +0200 Subject: [PATCH 33/97] Remove `num_virtual_targets` from `CommonCircuitData` --- plonky2/src/iop/generator.rs | 1 - plonky2/src/iop/witness.rs | 9 ++------- plonky2/src/plonk/circuit_builder.rs | 1 - plonky2/src/plonk/circuit_data.rs | 2 -- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/plonky2/src/iop/generator.rs b/plonky2/src/iop/generator.rs index 5bedf13d..3614b2e4 100644 --- a/plonky2/src/iop/generator.rs +++ b/plonky2/src/iop/generator.rs @@ -31,7 +31,6 @@ pub(crate) fn generate_partial_witness< let mut witness = PartitionWitness::new( config.num_wires, common_data.degree(), - common_data.num_virtual_targets, &prover_data.representative_map, ); diff --git a/plonky2/src/iop/witness.rs b/plonky2/src/iop/witness.rs index caa22c33..e7f21241 100644 --- a/plonky2/src/iop/witness.rs +++ b/plonky2/src/iop/witness.rs @@ -278,14 +278,9 @@ pub struct PartitionWitness<'a, F: Field> { } impl<'a, F: Field> PartitionWitness<'a, F> { - pub fn new( - num_wires: usize, - degree: usize, - num_virtual_targets: usize, - representative_map: &'a [usize], - ) -> Self { + pub fn new(num_wires: usize, degree: usize, representative_map: &'a [usize]) -> Self { Self { - values: vec![None; degree * num_wires + num_virtual_targets], + values: vec![None; representative_map.len()], representative_map, num_wires, degree, diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index ca68af9c..579a9017 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -814,7 +814,6 @@ impl, const D: usize> CircuitBuilder { quotient_degree_factor, num_gate_constraints, num_constants, - num_virtual_targets: self.virtual_target_index, num_public_inputs, k_is, num_partial_products, diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index fb839978..20697d36 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -265,8 +265,6 @@ pub struct CommonCircuitData< /// The number of constant wires. pub(crate) num_constants: usize, - pub(crate) num_virtual_targets: usize, - pub(crate) num_public_inputs: usize, /// The `{k_i}` valued used in `S_ID_i` in Plonk's permutation argument. From 7d9e81362d8bc849698668f4d42d182d786a6d25 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Thu, 15 Sep 2022 14:59:16 -0700 Subject: [PATCH 34/97] Python prototype of cache-oblivious FFT (#722) --- projects/cache-friendly-fft/__init__.py | 229 +++++++++++++++++++++++ projects/cache-friendly-fft/transpose.py | 61 ++++++ projects/cache-friendly-fft/util.py | 6 + 3 files changed, 296 insertions(+) create mode 100644 projects/cache-friendly-fft/__init__.py create mode 100644 projects/cache-friendly-fft/transpose.py create mode 100644 projects/cache-friendly-fft/util.py diff --git a/projects/cache-friendly-fft/__init__.py b/projects/cache-friendly-fft/__init__.py new file mode 100644 index 00000000..08f1acac --- /dev/null +++ b/projects/cache-friendly-fft/__init__.py @@ -0,0 +1,229 @@ +import numpy as np + +from transpose import transpose_square +from util import lb_exact + + +def _interleave(x, scratch): + """Interleave the elements in an array in-place. + + For example, if `x` is `array([1, 2, 3, 4, 5, 6, 7, 8])`, then its + contents will be rearranged to `array([1, 5, 2, 6, 3, 7, 4, 8])`. + + `scratch` is an externally-allocated buffer, whose `dtype` matches + `x` and whose length is at least half the length of `x`. + """ + assert len(x.shape) == len(scratch.shape) == 1 + + n, = x.shape + assert n % 2 == 0 + + half_n = n // 2 + assert scratch.shape[0] >= half_n + + assert x.dtype == scratch.dtype + scratch = scratch[:half_n] + + scratch[:] = x[:half_n] # Save the first half of `x`. + for i in range(half_n): + x[2 * i] = scratch[i] + x[2 * i + 1] = x[half_n + i] + + +def _deinterleave(x, scratch): + """Deinterleave the elements in an array in-place. + + For example, if `x` is `array([1, 2, 3, 4, 5, 6, 7, 8])`, then its + contents will be rearranged to `array([1, 3, 5, 7, 2, 4, 6, 8])`. + + `scratch` is an externally-allocated buffer, whose `dtype` matches + `x` and whose length is at least half the length of `x`. + """ + assert len(x.shape) == len(scratch.shape) == 1 + + n, = x.shape + assert n % 2 == 0 + + half_n = n // 2 + assert scratch.shape[0] >= half_n + + assert x.dtype == scratch.dtype + scratch = scratch[:half_n] + + for i in range(half_n): + x[i] = x[2 * i] + scratch[i] = x[2 * i + 1] + x[half_n:] = scratch + + +def _fft_inplace_evenpow(x, scratch): + """In-place FFT of length 2^even""" + # Reshape `x` to a square matrix in row-major order. + vec_len = x.shape[0] + n = 1 << (lb_exact(vec_len) >> 1) # Matrix dimension + x.shape = n, n, 1 + + # We want to recursively apply FFT to every column. Because `x` is + # in row-major order, we transpose it to make the columns contiguous + # in memory, then recurse, and finally transpose it back. While the + # row is in cache, we also multiply by the twiddle factors. + transpose_square(x) + for i, row in enumerate(x[..., 0]): + _fft_inplace(row, scratch) + # Multiply by the twiddle factors + for j in range(n): + row[j] *= np.exp(-2j * np.pi * (i * j) / vec_len) + transpose_square(x) + + # Now recursively apply FFT to the rows. + for row in x[..., 0]: + _fft_inplace(row, scratch) + + # Transpose again before returning. + transpose_square(x) + + +def _fft_inplace_oddpow(x, scratch): + """In-place FFT of length 2^odd""" + # This code is based on `_fft_inplace_evenpow`, but it has to + # account for some additional complications. + + vec_len = x.shape[0] + # `vec_len` is an odd power of 2, so we cannot reshape `x` to a + # matrix square. Instead, we'll (conceptually) reshape it to a + # matrix that's twice as wide as it is high. E.g., `[1 ... 8]` + # becomes `[1 2 3 4]` + # `[5 6 7 8]`. + col_len = 1 << (lb_exact(vec_len) >> 1) + row_len = col_len << 1 + + # We can only perform efficient, in-place transposes on square + # matrices, so we will actually treat this as a square matrix of + # 2-tuples, e.g. `[(1 2) (3 4)]` + # `[(5 6) (7 8)]`. + # Note that we can currently `.reshape` it to our intended wide + # matrix (although this is broken by transposition). + x.shape = col_len, col_len, 2 + + # We want to apply FFT to each column. We transpose our + # matrix-of-tuples and get something like `[(1 2) (5 6)]` + # `[(3 4) (7 8)]`. + # Note that each row of the transposed matrix represents two columns + # of the original matrix. We can deinterleave the values to recover + # the original columns. + transpose_square(x) + + for i, row_pair in enumerate(x): + # `row_pair` represents two columns of the original matrix. + # Their values must be deinterleaved to recover the columns. + row_pair.shape = row_len, + _deinterleave(row_pair, scratch) + # The below are rows of the transposed matrix(/cols of the + # original matrix. + row0 = row_pair[:col_len] + row1 = row_pair[col_len:] + + # Apply FFT and twiddle factors to each. + _fft_inplace(row0, scratch) + for j in range(col_len): + row0[j] *= np.exp(-2j * np.pi * ((2 * i) * j) / vec_len) + _fft_inplace(row1, scratch) + for j in range(col_len): + row1[j] *= np.exp(-2j * np.pi * ((2 * i + 1) * j) / vec_len) + + # Re-interleave them and transpose back. + _interleave(row_pair, scratch) + + transpose_square(x) + + # Recursively apply FFT to each row of the matrix. + for row in x: + # Turn vec of 2-tuples into vec of single elements. + row.shape = row_len, + _fft_inplace(row, scratch) + + # Transpose again before returning. This again involves + # deinterleaving. + transpose_square(x) + for row_pair in x: + row_pair.shape = row_len, + _deinterleave(row_pair, scratch) + + +def _fft_inplace(x, scratch): + """In-place FFT.""" + # Avoid modifying the shape of the original. + # This does not copy the buffer. + x = x.view() + assert x.flags['C_CONTIGUOUS'] + + n, = x.shape + if n == 1: + return + if n == 2: + x0, x1 = x + x[0] = x0 + x1 + x[1] = x0 - x1 + return + + lb_n = lb_exact(n) + is_odd = lb_n & 1 != 0 + if is_odd: + _fft_inplace_oddpow(x, scratch) + else: + _fft_inplace_evenpow(x, scratch) + + +def _scrach_length(lb_n): + """Find the amount of scratch space required to run the FFT. + + Layers where the input's length is an even power of two do not + require scratch space, but the layers where that power is odd do. + """ + if lb_n == 0: + # Length-1 input. + return 0 + # Repeatedly halve lb_n as long as it's even. This is the same as + # `n = sqrt(n)`, where the `sqrt` is exact. + while lb_n & 1 == 0: + lb_n >>= 1 + # `lb_n` is now odd, so `n` is not an even power of 2. + lb_res = (lb_n - 1) >> 1 + if lb_res == 0: + # Special case (n == 2 or n == 4): no scratch needed. + return 0 + return 1 << lb_res + + +def fft(x): + """Returns the FFT of `x`. + + This is a wrapper around an in-place routine, provided for user + convenience. + """ + n, = x.shape + lb_n = lb_exact(n) # Raises if not a power of 2. + # We have one scratch buffer for the whole algorithm. If we were to + # parallelize it, we'd need one thread-local buffer for each worker + # thread. + scratch_len = _scrach_length(lb_n) + if scratch_len == 0: + scratch = None + else: + scratch = np.empty_like(x, shape=scratch_len, order='C', subok=False) + + res = x.copy(order='C') + _fft_inplace(res, scratch) + + return res + + +if __name__ == "__main__": + LENGTH = 1 << 10 + v = np.random.normal(size=LENGTH).astype(complex) + print(v) + numpy_fft = np.fft.fft(v) + print(numpy_fft) + our_fft = fft(v) + print(our_fft) + print(np.isclose(numpy_fft, our_fft).all()) diff --git a/projects/cache-friendly-fft/transpose.py b/projects/cache-friendly-fft/transpose.py new file mode 100644 index 00000000..ea20bf6b --- /dev/null +++ b/projects/cache-friendly-fft/transpose.py @@ -0,0 +1,61 @@ +from util import lb_exact + + +def _swap_transpose_square(a, b): + """Transpose two square matrices in-place and swap them. + + The matrices must be a of shape `(n, n, m)`, where the `m` dimension + may be of arbitrary length and is not moved. + """ + assert len(a.shape) == len(b.shape) == 3 + n = a.shape[0] + m = a.shape[2] + assert n == a.shape[1] == b.shape[0] == b.shape[1] + assert m == b.shape[2] + + if n == 0: + return + if n == 1: + # Swap the two matrices (transposition is a no-op). + a = a[0, 0] + b = b[0, 0] + # Recall that each element of the matrix is an `m`-vector. Swap + # all `m` elements. + for i in range(m): + a[i], b[i] = b[i], a[i] + return + + half_n = n >> 1 + # Transpose and swap top-left of `a` with top-left of `b`. + _swap_transpose_square(a[:half_n, :half_n], b[:half_n, :half_n]) + # ...top-right of `a` with bottom-left of `b`. + _swap_transpose_square(a[:half_n, half_n:], b[half_n:, :half_n]) + # ...bottom-left of `a` with top-right of `b`. + _swap_transpose_square(a[half_n:, :half_n], b[:half_n, half_n:]) + # ...bottom-right of `a` with bottom-right of `b`. + _swap_transpose_square(a[half_n:, half_n:], b[half_n:, half_n:]) + + +def transpose_square(a): + """In-place transpose of a square matrix. + + The matrix must be a of shape `(n, n, m)`, where the `m` dimension + may be of arbitrary length and is not moved. + """ + if len(a.shape) != 3: + raise ValueError("a must be a matrix of batches") + n, n_, _ = a.shape + if n != n_: + raise ValueError("a must be square") + lb_exact(n) + + if n <= 1: + return # Base case: no-op + + half_n = n >> 1 + # Transpose top-left quarter in-place. + transpose_square(a[:half_n, :half_n]) + # Transpose top-right and bottom-left quarters and swap them. + _swap_transpose_square(a[:half_n, half_n:], a[half_n:, :half_n]) + # Transpose bottom-right quarter in-place. + transpose_square(a[half_n:, half_n:]) diff --git a/projects/cache-friendly-fft/util.py b/projects/cache-friendly-fft/util.py new file mode 100644 index 00000000..50118827 --- /dev/null +++ b/projects/cache-friendly-fft/util.py @@ -0,0 +1,6 @@ +def lb_exact(n): + """Returns `log2(n)`, raising if `n` is not a power of 2.""" + lb = n.bit_length() - 1 + if lb < 0 or n != 1 << lb: + raise ValueError(f"{n} is not a power of 2") + return lb From 9d1d179eb16c3db7e6fb04c7bf9fe5338c9b9120 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Sat, 17 Sep 2022 10:47:55 -0700 Subject: [PATCH 35/97] Verify that comparison output is zero or one (#715) --- evm/src/arithmetic/compare.rs | 56 ++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/evm/src/arithmetic/compare.rs b/evm/src/arithmetic/compare.rs index 8410cade..a6566db5 100644 --- a/evm/src/arithmetic/compare.rs +++ b/evm/src/arithmetic/compare.rs @@ -45,6 +45,14 @@ pub(crate) fn generate(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) lv[CMP_OUTPUT] = F::from_canonical_u64(br); } +fn eval_packed_generic_check_is_one_bit( + yield_constr: &mut ConstraintConsumer

, + filter: P, + x: P, +) { + yield_constr.constraint(filter * x * (x - P::ONES)); +} + pub(crate) fn eval_packed_generic_lt( yield_constr: &mut ConstraintConsumer

, is_op: P, @@ -69,15 +77,31 @@ pub fn eval_packed_generic( range_check_error!(CMP_INPUT_0, 16); range_check_error!(CMP_INPUT_1, 16); range_check_error!(CMP_AUX_INPUT, 16); - range_check_error!([CMP_OUTPUT], 1); + + let is_lt = lv[IS_LT]; + let is_gt = lv[IS_GT]; let input0 = CMP_INPUT_0.map(|c| lv[c]); let input1 = CMP_INPUT_1.map(|c| lv[c]); let aux = CMP_AUX_INPUT.map(|c| lv[c]); let output = lv[CMP_OUTPUT]; - eval_packed_generic_lt(yield_constr, lv[IS_LT], input0, input1, aux, output); - eval_packed_generic_lt(yield_constr, lv[IS_GT], input1, input0, aux, output); + let is_cmp = is_lt + is_gt; + eval_packed_generic_check_is_one_bit(yield_constr, is_cmp, output); + + eval_packed_generic_lt(yield_constr, is_lt, input0, input1, aux, output); + eval_packed_generic_lt(yield_constr, is_gt, input1, input0, aux, output); +} + +fn eval_ext_circuit_check_is_one_bit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + yield_constr: &mut RecursiveConstraintConsumer, + filter: ExtensionTarget, + x: ExtensionTarget, +) { + let constr = builder.mul_sub_extension(x, x, x); + let filtered_constr = builder.mul_extension(filter, constr); + yield_constr.constraint(builder, filtered_constr); } #[allow(clippy::needless_collect)] @@ -117,29 +141,19 @@ pub fn eval_ext_circuit, const D: usize>( lv: &[ExtensionTarget; NUM_ARITH_COLUMNS], yield_constr: &mut RecursiveConstraintConsumer, ) { + let is_lt = lv[IS_LT]; + let is_gt = lv[IS_GT]; + let input0 = CMP_INPUT_0.map(|c| lv[c]); let input1 = CMP_INPUT_1.map(|c| lv[c]); let aux = CMP_AUX_INPUT.map(|c| lv[c]); let output = lv[CMP_OUTPUT]; - eval_ext_circuit_lt( - builder, - yield_constr, - lv[IS_LT], - input0, - input1, - aux, - output, - ); - eval_ext_circuit_lt( - builder, - yield_constr, - lv[IS_GT], - input1, - input0, - aux, - output, - ); + let is_cmp = builder.add_extension(is_lt, is_gt); + eval_ext_circuit_check_is_one_bit(builder, yield_constr, is_cmp, output); + + eval_ext_circuit_lt(builder, yield_constr, is_lt, input0, input1, aux, output); + eval_ext_circuit_lt(builder, yield_constr, is_gt, input1, input0, aux, output); } #[cfg(test)] From 3007b5e779dde133218bcefaec8773781688b66b Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 19 Sep 2022 11:25:21 +0200 Subject: [PATCH 36/97] Fix DTH_ROOT for degree 1 extension --- field/src/extension/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/field/src/extension/mod.rs b/field/src/extension/mod.rs index f54d669c..ed596764 100644 --- a/field/src/extension/mod.rs +++ b/field/src/extension/mod.rs @@ -22,8 +22,8 @@ pub trait OEF: FieldExtension { } impl OEF<1> for F { - const W: Self::BaseField = F::ZERO; - const DTH_ROOT: Self::BaseField = F::ZERO; + const W: Self::BaseField = F::ONE; + const DTH_ROOT: Self::BaseField = F::ONE; } pub trait Frobenius: OEF { @@ -80,8 +80,8 @@ pub trait Extendable: Field + Sized { impl + FieldExtension<1, BaseField = F>> Extendable<1> for F { type Extension = F; - const W: Self = F::ZERO; - const DTH_ROOT: Self = F::ZERO; + const W: Self = F::ONE; + const DTH_ROOT: Self = F::ONE; const EXT_MULTIPLICATIVE_GROUP_GENERATOR: [Self; 1] = [F::MULTIPLICATIVE_GROUP_GENERATOR]; const EXT_POWER_OF_TWO_GENERATOR: [Self; 1] = [F::POWER_OF_TWO_GENERATOR]; } From d7d8803d0a5101bd0dbeb9ebf1dea23b154629e1 Mon Sep 17 00:00:00 2001 From: BGluth Date: Mon, 19 Sep 2022 11:05:48 -0600 Subject: [PATCH 37/97] Replaced `PartialTrie` definitions with `eth-trie-utils` crate - There were enough dependencies that it made sense to move `PartialTrie` logic to its own crate. --- evm/Cargo.toml | 2 ++ evm/src/generation/mod.rs | 5 +++-- evm/src/generation/partial_trie.rs | 34 ------------------------------ evm/src/proof.rs | 3 ++- evm/tests/transfer_to_new_addr.rs | 2 +- 5 files changed, 8 insertions(+), 38 deletions(-) delete mode 100644 evm/src/generation/partial_trie.rs diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 1e8f3c56..9d1bfa02 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } +eth-trie-utils = { git = "https://github.com/mir-protocol/eth-trie-utils.git", rev = "3ca443fd18e3f6d209dd96cbad851e05ae058b34" } maybe_rayon = { path = "../maybe_rayon" } anyhow = "1.0.40" env_logger = "0.9.0" @@ -21,6 +22,7 @@ pest_derive = "2.1.0" rand = "0.8.5" rand_chacha = "0.3.1" rlp = "0.5.1" +serde = { version = "1.0.144", features = ["derive"] } keccak-hash = "0.9.0" tiny-keccak = "2.0.2" diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 5b0b3c8f..baf2ec32 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -1,15 +1,16 @@ +use eth_trie_utils::partial_trie::PartialTrie; use ethereum_types::Address; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; +use serde::{Deserialize, Serialize}; use crate::all_stark::{AllStark, NUM_TABLES}; use crate::config::StarkConfig; use crate::cpu::bootstrap_kernel::generate_bootstrap_kernel; use crate::cpu::columns::NUM_CPU_COLUMNS; use crate::cpu::kernel::global_metadata::GlobalMetadata; -use crate::generation::partial_trie::PartialTrie; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; use crate::memory::NUM_CHANNELS; @@ -17,9 +18,9 @@ use crate::proof::{BlockMetadata, PublicValues, TrieRoots}; use crate::util::trace_rows_to_poly_values; pub(crate) mod memory; -pub mod partial_trie; pub(crate) mod state; +#[derive(Clone, Debug, Deserialize, Serialize)] /// Inputs needed for trace generation. pub struct GenerationInputs { pub signed_txns: Vec>, diff --git a/evm/src/generation/partial_trie.rs b/evm/src/generation/partial_trie.rs deleted file mode 100644 index 5e52e1e0..00000000 --- a/evm/src/generation/partial_trie.rs +++ /dev/null @@ -1,34 +0,0 @@ -use ethereum_types::U256; - -#[derive(Clone, Debug)] -/// A partial trie, or a sub-trie thereof. This mimics the structure of an Ethereum trie, except -/// with an additional `Hash` node type, representing a node whose data is not needed to process -/// our transaction. -pub enum PartialTrie { - /// An empty trie. - Empty, - /// The digest of trie whose data does not need to be stored. - Hash(U256), - /// A branch node, which consists of 16 children and an optional value. - Branch { - children: [Box; 16], - value: Option, - }, - /// An extension node, which consists of a list of nibbles and a single child. - Extension { - nibbles: Nibbles, - child: Box, - }, - /// A leaf node, which consists of a list of nibbles and a value. - Leaf { nibbles: Nibbles, value: Vec }, -} - -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -/// A sequence of nibbles. -pub struct Nibbles { - /// The number of nibbles in this sequence. - pub count: usize, - /// A packed encoding of these nibbles. Only the first (least significant) `4 * count` bits are - /// used. The rest are unused and should be zero. - pub packed: U256, -} diff --git a/evm/src/proof.rs b/evm/src/proof.rs index a5bc61a7..81614e67 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -12,6 +12,7 @@ use plonky2::hash::merkle_tree::MerkleCap; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::iop::target::Target; use plonky2::plonk::config::GenericConfig; +use serde::{Deserialize, Serialize}; use crate::all_stark::NUM_TABLES; use crate::config::StarkConfig; @@ -58,7 +59,7 @@ pub struct TrieRoots { pub receipts_root: U256, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct BlockMetadata { pub block_beneficiary: Address, pub block_timestamp: U256, diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs index ecb71076..1cd79194 100644 --- a/evm/tests/transfer_to_new_addr.rs +++ b/evm/tests/transfer_to_new_addr.rs @@ -1,10 +1,10 @@ +use eth_trie_utils::partial_trie::PartialTrie; use hex_literal::hex; use plonky2::field::goldilocks_field::GoldilocksField; use plonky2::plonk::config::PoseidonGoldilocksConfig; use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; -use plonky2_evm::generation::partial_trie::PartialTrie; use plonky2_evm::generation::GenerationInputs; use plonky2_evm::proof::BlockMetadata; use plonky2_evm::prover::prove; From 4d873cdaf5b1c2c929582e2929302c1990e72375 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 13:38:02 -0700 Subject: [PATCH 38/97] zkEVM spec --- evm/spec/.gitignore | 7 ++++ evm/spec/Makefile | 20 ++++++++++ evm/spec/bibliography.bib | 20 ++++++++++ evm/spec/framework.tex | 39 +++++++++++++++++++ evm/spec/instructions.tex | 8 ++++ evm/spec/introduction.tex | 3 ++ evm/spec/tables.tex | 9 +++++ evm/spec/tables/arithmetic.tex | 4 ++ evm/spec/tables/cpu.tex | 4 ++ evm/spec/tables/keccak-f.tex | 4 ++ evm/spec/tables/keccak-sponge.tex | 4 ++ evm/spec/tables/logic.tex | 4 ++ evm/spec/tables/memory.tex | 61 ++++++++++++++++++++++++++++++ evm/spec/tries.tex | 4 ++ evm/spec/zkevm.pdf | Bin 0 -> 146723 bytes evm/spec/zkevm.tex | 59 +++++++++++++++++++++++++++++ 16 files changed, 250 insertions(+) create mode 100644 evm/spec/.gitignore create mode 100644 evm/spec/Makefile create mode 100644 evm/spec/bibliography.bib create mode 100644 evm/spec/framework.tex create mode 100644 evm/spec/instructions.tex create mode 100644 evm/spec/introduction.tex create mode 100644 evm/spec/tables.tex create mode 100644 evm/spec/tables/arithmetic.tex create mode 100644 evm/spec/tables/cpu.tex create mode 100644 evm/spec/tables/keccak-f.tex create mode 100644 evm/spec/tables/keccak-sponge.tex create mode 100644 evm/spec/tables/logic.tex create mode 100644 evm/spec/tables/memory.tex create mode 100644 evm/spec/tries.tex create mode 100644 evm/spec/zkevm.pdf create mode 100644 evm/spec/zkevm.tex diff --git a/evm/spec/.gitignore b/evm/spec/.gitignore new file mode 100644 index 00000000..ba6d4007 --- /dev/null +++ b/evm/spec/.gitignore @@ -0,0 +1,7 @@ +## Files generated by pdflatex, bibtex, etc. +*.aux +*.log +*.out +*.toc +*.bbl +*.blg diff --git a/evm/spec/Makefile b/evm/spec/Makefile new file mode 100644 index 00000000..97954528 --- /dev/null +++ b/evm/spec/Makefile @@ -0,0 +1,20 @@ +DOCNAME=zkevm + +all: pdf + +.PHONY: clean + +quick: + pdflatex $(DOCNAME).tex + +pdf: + pdflatex $(DOCNAME).tex + bibtex $(DOCNAME).aux + pdflatex $(DOCNAME).tex + pdflatex $(DOCNAME).tex + +view: pdf + open $(DOCNAME).pdf + +clean: + rm -f *.blg *.bbl *.aux *.log diff --git a/evm/spec/bibliography.bib b/evm/spec/bibliography.bib new file mode 100644 index 00000000..41fa56b8 --- /dev/null +++ b/evm/spec/bibliography.bib @@ -0,0 +1,20 @@ +@misc{stark, + author = {Eli Ben-Sasson and + Iddo Bentov and + Yinon Horesh and + Michael Riabzev}, + title = {Scalable, transparent, and post-quantum secure computational integrity}, + howpublished = {Cryptology ePrint Archive, Report 2018/046}, + year = {2018}, + note = {\url{https://ia.cr/2018/046}}, +} + +@misc{plonk, + author = {Ariel Gabizon and + Zachary J. Williamson and + Oana Ciobotaru}, + title = {PLONK: Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge}, + howpublished = {Cryptology ePrint Archive, Report 2019/953}, + year = {2019}, + note = {\url{https://ia.cr/2019/953}}, +} diff --git a/evm/spec/framework.tex b/evm/spec/framework.tex new file mode 100644 index 00000000..122e1c75 --- /dev/null +++ b/evm/spec/framework.tex @@ -0,0 +1,39 @@ +\section{STARK framework} +\label{framework} + + +\subsection{Cost model} + +Our zkEVM is designed for efficient verification by STARKs \cite{stark}, particularly by an AIR with degree 3 constraints. In this model, the prover bottleneck is typically constructing Merkle trees, particularly constructing the tree containing low-degree extensions of witness polynomials. + +More specifically, we target a constraint system of degree 3. + + +\subsection{Field selection} +\label{field} +Our zkEVM is designed to have its execution traces encoded in a particular prime field $\mathbb{F}_p$, with $p = 2^{64} - 2^{32} + 1$. A nice property of this field is that it can represent the results of many common \texttt{u32} operations. For example, (widening) \texttt{u32} multiplication has a maximum value of $(2^{32} - 1)^2$, which is less than $p$. In fact a \texttt{u32} multiply-add has a maximum value of $p - 1$, so the result can be represented with a single field element, although if we were to add a carry in bit, this no longer holds. + +This field also enables a very efficient reduction method. Observe that +$$ +2^{64} \equiv 2^{32} - 1 \pmod p +$$ +and consequently +\begin{align*} + 2^{96} &\equiv 2^{32} (2^{32} - 1) \pmod p \\ + &\equiv 2^{64} - 2^{32} \pmod p \\ + &\equiv -1 \pmod p. +\end{align*} +To reduce a 128-bit number $n$, we first rewrite $n$ as $n_0 + 2^{64} n_1 + 2^{96} n_2$, where $n_0$ is 64 bits and $n_1, n_2$ are 32 bits each. Then +\begin{align*} + n &\equiv n_0 + 2^{64} n_1 + 2^{96} n_2 \pmod p \\ + &\equiv n_0 + (2^{32} - 1) n_1 - n_2 \pmod p +\end{align*} +After computing $(2^{32} - 1) n_1$, which can be done with a shift and subtraction, we add the first two terms, subtracting $p$ if overflow occurs. We then subtract $n_2$, adding $p$ if underflow occurs. + +At this point we have reduced $n$ to a \texttt{u64}. This partial reduction is adequate for most purposes, but if we needed the result in canonical form, we would perform a final conditional subtraction. + + +\subsection{Cross-table lookups} +\label{ctl} + +TODO diff --git a/evm/spec/instructions.tex b/evm/spec/instructions.tex new file mode 100644 index 00000000..ea096982 --- /dev/null +++ b/evm/spec/instructions.tex @@ -0,0 +1,8 @@ +\section{Privileged instructions} +\label{privileged-instructions} + +\begin{enumerate} + \item[0xFB.] \texttt{MLOAD\_GENERAL}. Returns + \item[0xFC.] \texttt{MSTORE\_GENERAL}. Returns + \item[TODO.] \texttt{STACK\_SIZE}. Returns +\end{enumerate} diff --git a/evm/spec/introduction.tex b/evm/spec/introduction.tex new file mode 100644 index 00000000..cb969a16 --- /dev/null +++ b/evm/spec/introduction.tex @@ -0,0 +1,3 @@ +\section{Introduction} + +TODO diff --git a/evm/spec/tables.tex b/evm/spec/tables.tex new file mode 100644 index 00000000..92ee1d2a --- /dev/null +++ b/evm/spec/tables.tex @@ -0,0 +1,9 @@ +\section{Tables} +\label{tables} + +\input{tables/cpu} +\input{tables/arithmetic} +\input{tables/logic} +\input{tables/memory} +\input{tables/keccak-f} +\input{tables/keccak-sponge} diff --git a/evm/spec/tables/arithmetic.tex b/evm/spec/tables/arithmetic.tex new file mode 100644 index 00000000..eafed3ba --- /dev/null +++ b/evm/spec/tables/arithmetic.tex @@ -0,0 +1,4 @@ +\subsection{Arithmetic} +\label{arithmetic} + +TODO diff --git a/evm/spec/tables/cpu.tex b/evm/spec/tables/cpu.tex new file mode 100644 index 00000000..76c8be07 --- /dev/null +++ b/evm/spec/tables/cpu.tex @@ -0,0 +1,4 @@ +\subsection{CPU} +\label{cpu} + +TODO diff --git a/evm/spec/tables/keccak-f.tex b/evm/spec/tables/keccak-f.tex new file mode 100644 index 00000000..76e9e9f4 --- /dev/null +++ b/evm/spec/tables/keccak-f.tex @@ -0,0 +1,4 @@ +\subsection{Keccak-f} +\label{keccak-f} + +This table computes the Keccak-f[1600] permutation. diff --git a/evm/spec/tables/keccak-sponge.tex b/evm/spec/tables/keccak-sponge.tex new file mode 100644 index 00000000..29f71ba1 --- /dev/null +++ b/evm/spec/tables/keccak-sponge.tex @@ -0,0 +1,4 @@ +\subsection{Keccak sponge} +\label{keccak-sponge} + +This table computes the Keccak256 hash, a sponge-based hash built on top of the Keccak-f[1600] permutation. diff --git a/evm/spec/tables/logic.tex b/evm/spec/tables/logic.tex new file mode 100644 index 00000000..b430c95d --- /dev/null +++ b/evm/spec/tables/logic.tex @@ -0,0 +1,4 @@ +\subsection{Logic} +\label{logic} + +TODO diff --git a/evm/spec/tables/memory.tex b/evm/spec/tables/memory.tex new file mode 100644 index 00000000..9653f391 --- /dev/null +++ b/evm/spec/tables/memory.tex @@ -0,0 +1,61 @@ +\subsection{Memory} +\label{memory} + +For simplicity, let's treat addresses and values as individual field elements. The generalization to multi-element addresses and values is straightforward. + +Each row of the memory table corresponds to a single memory operation (a read or a write), and contains the following columns: + +\begin{enumerate} + \item $a$, the target address + \item $r$, an ``is read'' flag, which should be 1 for a read or 0 for a write + \item $v$, the value being read or written + \item $\tau$, the timestamp of the operation +\end{enumerate} +The memory table should be ordered by $(a, \tau)$. Note that the correctness memory could be checked as follows: +\begin{enumerate} + \item Verify the ordering by checking that $(a_i, \tau_i) < (a_{i+1}, \tau_{i+1})$ for each consecutive pair. + \item Enumerate the purportedly-ordered log while tracking a ``current'' value $c$, which is initially zero.\footnote{EVM memory is zero-initialized.} + \begin{enumerate} + \item Upon observing an address which doesn't match that of the previous row, set $c \leftarrow 0$. + \item Upon observing a write, set $c \leftarrow v$. + \item Upon observing a read, check that $v = c$. + \end{enumerate} +\end{enumerate} + +The ordering check is slightly involved since we are comparing multiple columns. To facilitate this, we add an additional column $e$, where the prover can indicate whether two consecutive addresses are equal. An honest prover will set +$$ +e_i \leftarrow \begin{cases} + 1 & \text{if } a_i = a_{i + 1}, \\ + 0 & \text{otherwise}. +\end{cases} +$$ +We then impose the following transition constraints: +\begin{enumerate} + \item $e_i (e_i - 1) = 0$, + \item $e_i (a_i - a_{i + 1}) = 0$, + \item $e_i (\tau_{i + 1} - \tau_i) + (1 - e_i) (a_{i + 1} - a_i - 1) < 2^{32}$. +\end{enumerate} +The last constraint emulates a comparison between two addresses or timestamps by bounding their difference; this assumes that all addresses and timestamps fit in 32 bits and that the field is larger than that. + +Finally, the iterative checks can be arithmetized by introducing a trace column for the current value $c$. We add a boundary constraint $c_0 = 0$, and the following transition constraints: +\todo{This is out of date, we don't actually need a $c$ column.} +\begin{enumerate} + \item $v_{\text{from},i} = c_i$, + \item $c_{i + 1} = e_i v_{\text{to},i}$. +\end{enumerate} + + +\subsubsection{Virtual memory} + +In the EVM, each contract call has its own address space. Within that address space, there are separate segments for code, main memory, stack memory, calldata, and returndata. Thus each address actually has three compoments: +\begin{enumerate} + \item an execution context, representing a contract call, + \item a segment ID, used to separate code, main memory, and so forth, and so on + \item a virtual address. +\end{enumerate} +The comparisons now involve several columns, which requires some minor adaptations to the technique described above; we will leave these as an exercise to the reader. + + +\subsubsection{Timestamps} + +TODO: Explain $\tau = \texttt{NUM\_CHANNELS} \times \texttt{cycle} + \texttt{channel}$. diff --git a/evm/spec/tries.tex b/evm/spec/tries.tex new file mode 100644 index 00000000..d8fc2674 --- /dev/null +++ b/evm/spec/tries.tex @@ -0,0 +1,4 @@ +\section{Merkle Patricia tries} +\label{tries} + +TODO diff --git a/evm/spec/zkevm.pdf b/evm/spec/zkevm.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8501cfbb9e6ce463f65f0f2369c78cd08d445096 GIT binary patch literal 146723 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�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 Date: Fri, 16 Sep 2022 15:18:44 -0700 Subject: [PATCH 39/97] Validate the shape of each proof --- evm/src/stark.rs | 74 +++++++++++++++++----------- plonky2/src/fri/mod.rs | 1 + plonky2/src/fri/structure.rs | 1 + plonky2/src/fri/validate_shape.rs | 66 +++++++++++++++++++++++++ plonky2/src/fri/verifier.rs | 6 +-- plonky2/src/hash/merkle_proofs.rs | 6 +++ plonky2/src/hash/merkle_tree.rs | 4 ++ plonky2/src/plonk/circuit_data.rs | 30 ++++++++++-- plonky2/src/plonk/mod.rs | 1 + plonky2/src/plonk/plonk_common.rs | 14 ------ plonky2/src/plonk/validate_shape.rs | 67 +++++++++++++++++++++++++ plonky2/src/plonk/verifier.rs | 7 ++- starky/src/stark.rs | 76 +++++++++++++++++------------ 13 files changed, 267 insertions(+), 86 deletions(-) create mode 100644 plonky2/src/fri/validate_shape.rs create mode 100644 plonky2/src/plonk/validate_shape.rs diff --git a/evm/src/stark.rs b/evm/src/stark.rs index a205547a..1af8a5e2 100644 --- a/evm/src/stark.rs +++ b/evm/src/stark.rs @@ -16,6 +16,10 @@ use crate::permutation::PermutationPair; use crate::vars::StarkEvaluationTargets; use crate::vars::StarkEvaluationVars; +const TRACE_ORACLE_INDEX: usize = 0; +const PERMUTATION_CTL_ORACLE_INDEX: usize = 1; +const QUOTIENT_ORACLE_INDEX: usize = 2; + /// Represents a STARK system. pub trait Stark, const D: usize>: Sync { /// The total number of columns in the trace. @@ -81,28 +85,35 @@ pub trait Stark, const D: usize>: Sync { num_ctl_zs: usize, config: &StarkConfig, ) -> FriInstanceInfo { - let no_blinding_oracle = FriOracleInfo { blinding: false }; - let mut oracle_indices = 0..; - - let trace_info = - FriPolynomialInfo::from_range(oracle_indices.next().unwrap(), 0..Self::COLUMNS); + let trace_oracle = FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }; + let trace_info = FriPolynomialInfo::from_range(TRACE_ORACLE_INDEX, 0..Self::COLUMNS); let num_permutation_batches = self.num_permutation_batches(config); - let permutation_ctl_index = oracle_indices.next().unwrap(); + let num_perutation_ctl_polys = num_permutation_batches + num_ctl_zs; + let permutation_ctl_oracle = FriOracleInfo { + num_polys: num_perutation_ctl_polys, + blinding: false, + }; let permutation_ctl_zs_info = FriPolynomialInfo::from_range( - permutation_ctl_index, - 0..num_permutation_batches + num_ctl_zs, + PERMUTATION_CTL_ORACLE_INDEX, + 0..num_perutation_ctl_polys, ); let ctl_zs_info = FriPolynomialInfo::from_range( - permutation_ctl_index, + PERMUTATION_CTL_ORACLE_INDEX, num_permutation_batches..num_permutation_batches + num_ctl_zs, ); - let quotient_info = FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.quotient_degree_factor() * config.num_challenges, - ); + let num_quotient_polys = self.quotient_degree_factor() * config.num_challenges; + let quotient_oracle = FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }; + let quotient_info = + FriPolynomialInfo::from_range(QUOTIENT_ORACLE_INDEX, 0..num_quotient_polys); let zeta_batch = FriBatchInfo { point: zeta, @@ -122,7 +133,7 @@ pub trait Stark, const D: usize>: Sync { polynomials: ctl_zs_info, }; FriInstanceInfo { - oracles: vec![no_blinding_oracle; oracle_indices.next().unwrap()], + oracles: vec![trace_oracle, permutation_ctl_oracle, quotient_oracle], batches: vec![zeta_batch, zeta_next_batch, ctl_last_batch], } } @@ -137,28 +148,35 @@ pub trait Stark, const D: usize>: Sync { num_ctl_zs: usize, inner_config: &StarkConfig, ) -> FriInstanceInfoTarget { - let no_blinding_oracle = FriOracleInfo { blinding: false }; - let mut oracle_indices = 0..; - - let trace_info = - FriPolynomialInfo::from_range(oracle_indices.next().unwrap(), 0..Self::COLUMNS); + let trace_oracle = FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }; + let trace_info = FriPolynomialInfo::from_range(TRACE_ORACLE_INDEX, 0..Self::COLUMNS); let num_permutation_batches = self.num_permutation_batches(inner_config); - let permutation_ctl_index = oracle_indices.next().unwrap(); + let num_perutation_ctl_polys = num_permutation_batches + num_ctl_zs; + let permutation_ctl_oracle = FriOracleInfo { + num_polys: num_perutation_ctl_polys, + blinding: false, + }; let permutation_ctl_zs_info = FriPolynomialInfo::from_range( - permutation_ctl_index, - 0..num_permutation_batches + num_ctl_zs, + PERMUTATION_CTL_ORACLE_INDEX, + 0..num_perutation_ctl_polys, ); let ctl_zs_info = FriPolynomialInfo::from_range( - permutation_ctl_index, + PERMUTATION_CTL_ORACLE_INDEX, num_permutation_batches..num_permutation_batches + num_ctl_zs, ); - let quotient_info = FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.quotient_degree_factor() * inner_config.num_challenges, - ); + let num_quotient_polys = self.quotient_degree_factor() * inner_config.num_challenges; + let quotient_oracle = FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }; + let quotient_info = + FriPolynomialInfo::from_range(QUOTIENT_ORACLE_INDEX, 0..num_quotient_polys); let zeta_batch = FriBatchInfoTarget { point: zeta, @@ -180,7 +198,7 @@ pub trait Stark, const D: usize>: Sync { polynomials: ctl_zs_info, }; FriInstanceInfoTarget { - oracles: vec![no_blinding_oracle; oracle_indices.next().unwrap()], + oracles: vec![trace_oracle, permutation_ctl_oracle, quotient_oracle], batches: vec![zeta_batch, zeta_next_batch, ctl_last_batch], } } diff --git a/plonky2/src/fri/mod.rs b/plonky2/src/fri/mod.rs index 4ed2ea3b..5eaf012e 100644 --- a/plonky2/src/fri/mod.rs +++ b/plonky2/src/fri/mod.rs @@ -7,6 +7,7 @@ pub mod prover; pub mod recursive_verifier; pub mod reduction_strategies; pub mod structure; +mod validate_shape; pub mod verifier; pub mod witness_util; diff --git a/plonky2/src/fri/structure.rs b/plonky2/src/fri/structure.rs index d5c2c81c..0d64ae20 100644 --- a/plonky2/src/fri/structure.rs +++ b/plonky2/src/fri/structure.rs @@ -25,6 +25,7 @@ pub struct FriInstanceInfoTarget { #[derive(Copy, Clone)] pub struct FriOracleInfo { + pub num_polys: usize, pub blinding: bool, } diff --git a/plonky2/src/fri/validate_shape.rs b/plonky2/src/fri/validate_shape.rs new file mode 100644 index 00000000..e8a70a08 --- /dev/null +++ b/plonky2/src/fri/validate_shape.rs @@ -0,0 +1,66 @@ +use anyhow::ensure; +use plonky2_field::extension::Extendable; + +use crate::fri::proof::{FriProof, FriQueryRound, FriQueryStep}; +use crate::fri::structure::FriInstanceInfo; +use crate::fri::FriParams; +use crate::hash::hash_types::RichField; +use crate::plonk::config::GenericConfig; + +pub(crate) fn validate_fri_proof_shape( + proof: &FriProof, + instance: &FriInstanceInfo, + params: &FriParams, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, +{ + let FriProof { + commit_phase_merkle_caps, + query_round_proofs, + final_poly, + pow_witness: _pow_witness, + } = proof; + + let cap_height = params.config.cap_height; + for cap in commit_phase_merkle_caps { + ensure!(cap.height() == cap_height); + } + + for query_round in query_round_proofs { + let FriQueryRound { + initial_trees_proof, + steps, + } = query_round; + + ensure!(initial_trees_proof.evals_proofs.len() == instance.oracles.len()); + for ((leaf, merkle_proof), oracle) in initial_trees_proof + .evals_proofs + .iter() + .zip(&instance.oracles) + { + ensure!(leaf.len() == oracle.num_polys); // TODO: Account for blinding if ZK? + ensure!(merkle_proof.len() + cap_height == params.lde_bits()); + } + + ensure!(steps.len() == params.reduction_arity_bits.len()); + let mut codeword_len_bits = params.lde_bits(); + for (step, arity_bits) in steps.iter().zip(¶ms.reduction_arity_bits) { + let FriQueryStep { + evals, + merkle_proof, + } = step; + + let arity = 1 << arity_bits; + codeword_len_bits -= arity_bits; + + ensure!(evals.len() == arity); + ensure!(merkle_proof.len() + cap_height == codeword_len_bits); + } + } + + ensure!(final_poly.len() == params.final_poly_len()); + + Ok(()) +} diff --git a/plonky2/src/fri/verifier.rs b/plonky2/src/fri/verifier.rs index ed44f0c4..02816000 100644 --- a/plonky2/src/fri/verifier.rs +++ b/plonky2/src/fri/verifier.rs @@ -6,6 +6,7 @@ use plonky2_util::{log2_strict, reverse_index_bits_in_place}; use crate::fri::proof::{FriChallenges, FriInitialTreeProof, FriProof, FriQueryRound}; use crate::fri::structure::{FriBatchInfo, FriInstanceInfo, FriOpenings}; +use crate::fri::validate_shape::validate_fri_proof_shape; use crate::fri::{FriConfig, FriParams}; use crate::hash::hash_types::RichField; use crate::hash::merkle_proofs::verify_merkle_proof_to_cap; @@ -67,10 +68,7 @@ pub fn verify_fri_proof, C: GenericConfig where [(); C::Hasher::HASH_SIZE]:, { - ensure!( - params.final_poly_len() == proof.final_poly.len(), - "Final polynomial has wrong degree." - ); + validate_fri_proof_shape::(proof, instance, params)?; // Size of the LDE domain. let n = params.lde_size(); diff --git a/plonky2/src/hash/merkle_proofs.rs b/plonky2/src/hash/merkle_proofs.rs index 90d55ce1..f54793d9 100644 --- a/plonky2/src/hash/merkle_proofs.rs +++ b/plonky2/src/hash/merkle_proofs.rs @@ -17,6 +17,12 @@ pub struct MerkleProof> { pub siblings: Vec, } +impl> MerkleProof { + pub fn len(&self) -> usize { + self.siblings.len() + } +} + #[derive(Clone, Debug)] pub struct MerkleProofTarget { /// The Merkle digest of each sibling subtree, staying from the bottommost layer. diff --git a/plonky2/src/hash/merkle_tree.rs b/plonky2/src/hash/merkle_tree.rs index 1da66bff..703a353e 100644 --- a/plonky2/src/hash/merkle_tree.rs +++ b/plonky2/src/hash/merkle_tree.rs @@ -21,6 +21,10 @@ impl> MerkleCap { self.0.len() } + pub fn height(&self) -> usize { + log2_strict(self.len()) + } + pub fn flatten(&self) -> Vec { self.0.iter().flat_map(|&h| h.to_vec()).collect() } diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index 20697d36..f2363f41 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -9,7 +9,8 @@ use crate::field::types::Field; use crate::fri::oracle::PolynomialBatch; use crate::fri::reduction_strategies::FriReductionStrategy; use crate::fri::structure::{ - FriBatchInfo, FriBatchInfoTarget, FriInstanceInfo, FriInstanceInfoTarget, FriPolynomialInfo, + FriBatchInfo, FriBatchInfoTarget, FriInstanceInfo, FriInstanceInfoTarget, FriOracleInfo, + FriPolynomialInfo, }; use crate::fri::{FriConfig, FriParams}; use crate::gates::gate::GateRef; @@ -22,7 +23,7 @@ use crate::iop::target::Target; use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::config::{GenericConfig, Hasher}; -use crate::plonk::plonk_common::{PlonkOracle, FRI_ORACLES}; +use crate::plonk::plonk_common::PlonkOracle; use crate::plonk::proof::{CompressedProofWithPublicInputs, ProofWithPublicInputs}; use crate::plonk::prover::prove; use crate::plonk::verifier::verify; @@ -342,7 +343,7 @@ impl, C: GenericConfig, const D: usize> let openings = vec![zeta_batch, zeta_next_batch]; FriInstanceInfo { - oracles: FRI_ORACLES.to_vec(), + oracles: self.fri_oracles(), batches: openings, } } @@ -368,11 +369,32 @@ impl, C: GenericConfig, const D: usize> let openings = vec![zeta_batch, zeta_next_batch]; FriInstanceInfoTarget { - oracles: FRI_ORACLES.to_vec(), + oracles: self.fri_oracles(), batches: openings, } } + fn fri_oracles(&self) -> Vec { + vec![ + FriOracleInfo { + num_polys: self.num_preprocessed_polys(), + blinding: PlonkOracle::CONSTANTS_SIGMAS.blinding, + }, + FriOracleInfo { + num_polys: self.config.num_wires, + blinding: PlonkOracle::WIRES.blinding, + }, + FriOracleInfo { + num_polys: self.num_zs_partial_products_polys(), + blinding: PlonkOracle::ZS_PARTIAL_PRODUCTS.blinding, + }, + FriOracleInfo { + num_polys: self.num_quotient_polys(), + blinding: PlonkOracle::QUOTIENT.blinding, + }, + ] + } + fn fri_preprocessed_polys(&self) -> Vec { FriPolynomialInfo::from_range( PlonkOracle::CONSTANTS_SIGMAS.index, diff --git a/plonky2/src/plonk/mod.rs b/plonky2/src/plonk/mod.rs index 4f2fa4e1..73e6c96e 100644 --- a/plonky2/src/plonk/mod.rs +++ b/plonky2/src/plonk/mod.rs @@ -8,6 +8,7 @@ pub mod plonk_common; pub mod proof; pub mod prover; pub mod recursive_verifier; +mod validate_shape; pub(crate) mod vanishing_poly; pub mod vars; pub mod verifier; diff --git a/plonky2/src/plonk/plonk_common.rs b/plonky2/src/plonk/plonk_common.rs index e947353b..24a94bb3 100644 --- a/plonky2/src/plonk/plonk_common.rs +++ b/plonky2/src/plonk/plonk_common.rs @@ -3,7 +3,6 @@ use plonky2_field::packed::PackedField; use plonky2_field::types::Field; use crate::fri::oracle::SALT_SIZE; -use crate::fri::structure::FriOracleInfo; use crate::gates::arithmetic_base::ArithmeticGate; use crate::hash::hash_types::RichField; use crate::iop::ext_target::ExtensionTarget; @@ -11,13 +10,6 @@ use crate::iop::target::Target; use crate::plonk::circuit_builder::CircuitBuilder; use crate::util::reducing::ReducingFactorTarget; -pub(crate) const FRI_ORACLES: [FriOracleInfo; 4] = [ - PlonkOracle::CONSTANTS_SIGMAS.as_fri_oracle(), - PlonkOracle::WIRES.as_fri_oracle(), - PlonkOracle::ZS_PARTIAL_PRODUCTS.as_fri_oracle(), - PlonkOracle::QUOTIENT.as_fri_oracle(), -]; - /// Holds the Merkle tree index and blinding flag of a set of polynomials used in FRI. #[derive(Debug, Copy, Clone)] pub struct PlonkOracle { @@ -42,12 +34,6 @@ impl PlonkOracle { index: 3, blinding: true, }; - - pub(crate) const fn as_fri_oracle(&self) -> FriOracleInfo { - FriOracleInfo { - blinding: self.blinding, - } - } } pub fn salt_size(salted: bool) -> usize { diff --git a/plonky2/src/plonk/validate_shape.rs b/plonky2/src/plonk/validate_shape.rs new file mode 100644 index 00000000..9d36110b --- /dev/null +++ b/plonky2/src/plonk/validate_shape.rs @@ -0,0 +1,67 @@ +use anyhow::ensure; +use plonky2_field::extension::Extendable; + +use crate::hash::hash_types::RichField; +use crate::plonk::circuit_data::CommonCircuitData; +use crate::plonk::config::{GenericConfig, Hasher}; +use crate::plonk::proof::{Proof, ProofWithPublicInputs}; + +pub(crate) fn validate_proof_with_pis_shape( + proof_with_pis: &ProofWithPublicInputs, + common_data: &CommonCircuitData, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + [(); C::Hasher::HASH_SIZE]:, +{ + let ProofWithPublicInputs { + proof, + public_inputs, + } = proof_with_pis; + + validate_proof_shape(proof, common_data)?; + + ensure!( + public_inputs.len() == common_data.num_public_inputs, + "Number of public inputs doesn't match circuit data." + ); + + Ok(()) +} + +fn validate_proof_shape( + proof: &Proof, + common_data: &CommonCircuitData, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + [(); C::Hasher::HASH_SIZE]:, +{ + let config = &common_data.config; + let Proof { + wires_cap, + plonk_zs_partial_products_cap, + quotient_polys_cap, + openings, + // The shape of the opening proof will be checked in the FRI verifier (see + // validate_fri_proof_shape), so we ignore it here. + opening_proof: _, + } = proof; + + let cap_height = common_data.fri_params.config.cap_height; + ensure!(wires_cap.height() == cap_height); + ensure!(plonk_zs_partial_products_cap.height() == cap_height); + ensure!(quotient_polys_cap.height() == cap_height); + + ensure!(openings.constants.len() == common_data.num_constants); + ensure!(openings.plonk_sigmas.len() == config.num_routed_wires); + ensure!(openings.wires.len() == config.num_wires); + ensure!(openings.plonk_zs.len() == config.num_challenges); + ensure!(openings.plonk_zs_next.len() == config.num_challenges); + ensure!(openings.partial_products.len() == common_data.num_partial_products); + ensure!(openings.quotient_polys.len() == common_data.num_quotient_polys()); + + Ok(()) +} diff --git a/plonky2/src/plonk/verifier.rs b/plonky2/src/plonk/verifier.rs index 13821ff3..6a4f3790 100644 --- a/plonky2/src/plonk/verifier.rs +++ b/plonky2/src/plonk/verifier.rs @@ -8,6 +8,7 @@ use crate::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData}; use crate::plonk::config::{GenericConfig, Hasher}; use crate::plonk::plonk_common::reduce_with_powers; use crate::plonk::proof::{Proof, ProofChallenges, ProofWithPublicInputs}; +use crate::plonk::validate_shape::validate_proof_with_pis_shape; use crate::plonk::vanishing_poly::eval_vanishing_poly; use crate::plonk::vars::EvaluationVars; @@ -19,10 +20,8 @@ pub(crate) fn verify, C: GenericConfig, c where [(); C::Hasher::HASH_SIZE]:, { - ensure!( - proof_with_pis.public_inputs.len() == common_data.num_public_inputs, - "Number of public inputs doesn't match circuit data." - ); + validate_proof_with_pis_shape(&proof_with_pis, common_data)?; + let public_inputs_hash = proof_with_pis.get_public_inputs_hash(); let challenges = proof_with_pis.get_challenges(public_inputs_hash, common_data)?; diff --git a/starky/src/stark.rs b/starky/src/stark.rs index df549572..7f0df197 100644 --- a/starky/src/stark.rs +++ b/starky/src/stark.rs @@ -85,25 +85,32 @@ pub trait Stark, const D: usize>: Sync { g: F, config: &StarkConfig, ) -> FriInstanceInfo { - let no_blinding_oracle = FriOracleInfo { blinding: false }; - let mut oracle_indices = 0..; + let mut oracles = vec![]; - let trace_info = - FriPolynomialInfo::from_range(oracle_indices.next().unwrap(), 0..Self::COLUMNS); + let trace_info = FriPolynomialInfo::from_range(oracles.len(), 0..Self::COLUMNS); + oracles.push(FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }); let permutation_zs_info = if self.uses_permutation_args() { - FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.num_permutation_batches(config), - ) + let num_z_polys = self.num_permutation_batches(config); + let polys = FriPolynomialInfo::from_range(oracles.len(), 0..num_z_polys); + oracles.push(FriOracleInfo { + num_polys: num_z_polys, + blinding: false, + }); + polys } else { vec![] }; - let quotient_info = FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.quotient_degree_factor() * config.num_challenges, - ); + let num_quotient_polys = self.quotient_degree_factor() * config.num_challenges; + let quotient_info = FriPolynomialInfo::from_range(oracles.len(), 0..num_quotient_polys); + oracles.push(FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }); let zeta_batch = FriBatchInfo { point: zeta, @@ -118,10 +125,9 @@ pub trait Stark, const D: usize>: Sync { point: zeta.scalar_mul(g), polynomials: [trace_info, permutation_zs_info].concat(), }; - FriInstanceInfo { - oracles: vec![no_blinding_oracle; oracle_indices.next().unwrap()], - batches: vec![zeta_batch, zeta_next_batch], - } + let batches = vec![zeta_batch, zeta_next_batch]; + + FriInstanceInfo { oracles, batches } } /// Computes the FRI instance used to prove this Stark. @@ -132,25 +138,32 @@ pub trait Stark, const D: usize>: Sync { g: F, config: &StarkConfig, ) -> FriInstanceInfoTarget { - let no_blinding_oracle = FriOracleInfo { blinding: false }; - let mut oracle_indices = 0..; + let mut oracles = vec![]; - let trace_info = - FriPolynomialInfo::from_range(oracle_indices.next().unwrap(), 0..Self::COLUMNS); + let trace_info = FriPolynomialInfo::from_range(oracles.len(), 0..Self::COLUMNS); + oracles.push(FriOracleInfo { + num_polys: Self::COLUMNS, + blinding: false, + }); let permutation_zs_info = if self.uses_permutation_args() { - FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.num_permutation_batches(config), - ) + let num_z_polys = self.num_permutation_batches(config); + let polys = FriPolynomialInfo::from_range(oracles.len(), 0..num_z_polys); + oracles.push(FriOracleInfo { + num_polys: num_z_polys, + blinding: false, + }); + polys } else { vec![] }; - let quotient_info = FriPolynomialInfo::from_range( - oracle_indices.next().unwrap(), - 0..self.quotient_degree_factor() * config.num_challenges, - ); + let num_quotient_polys = self.quotient_degree_factor() * config.num_challenges; + let quotient_info = FriPolynomialInfo::from_range(oracles.len(), 0..num_quotient_polys); + oracles.push(FriOracleInfo { + num_polys: num_quotient_polys, + blinding: false, + }); let zeta_batch = FriBatchInfoTarget { point: zeta, @@ -166,10 +179,9 @@ pub trait Stark, const D: usize>: Sync { point: zeta_next, polynomials: [trace_info, permutation_zs_info].concat(), }; - FriInstanceInfoTarget { - oracles: vec![no_blinding_oracle; oracle_indices.next().unwrap()], - batches: vec![zeta_batch, zeta_next_batch], - } + let batches = vec![zeta_batch, zeta_next_batch]; + + FriInstanceInfoTarget { oracles, batches } } /// Pairs of lists of columns that should be permutations of one another. A permutation argument From 786826487cc7b1d350929c2e6960a52b16ef0227 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 17:33:18 -0700 Subject: [PATCH 40/97] Update plonky2/src/fri/validate_shape.rs Co-authored-by: wborgeaud --- plonky2/src/fri/validate_shape.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plonky2/src/fri/validate_shape.rs b/plonky2/src/fri/validate_shape.rs index e8a70a08..597adeda 100644 --- a/plonky2/src/fri/validate_shape.rs +++ b/plonky2/src/fri/validate_shape.rs @@ -40,7 +40,15 @@ where .iter() .zip(&instance.oracles) { - ensure!(leaf.len() == oracle.num_polys); // TODO: Account for blinding if ZK? + ensure!( + leaf.len() + == oracle.num_polys + + if oracle.blinding && params.hiding { + SALT_SIZE + } else { + 0 + } + ); ensure!(merkle_proof.len() + cap_height == params.lde_bits()); } From 74ab741057bdf971009fe25a7294502a7aac7a10 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 17:35:23 -0700 Subject: [PATCH 41/97] Update plonky2/src/plonk/validate_shape.rs Co-authored-by: wborgeaud --- plonky2/src/plonk/validate_shape.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plonky2/src/plonk/validate_shape.rs b/plonky2/src/plonk/validate_shape.rs index 9d36110b..3e0082fc 100644 --- a/plonky2/src/plonk/validate_shape.rs +++ b/plonky2/src/plonk/validate_shape.rs @@ -60,7 +60,9 @@ where ensure!(openings.wires.len() == config.num_wires); ensure!(openings.plonk_zs.len() == config.num_challenges); ensure!(openings.plonk_zs_next.len() == config.num_challenges); - ensure!(openings.partial_products.len() == common_data.num_partial_products); + ensure!( + openings.partial_products.len() == config.num_challenges * common_data.num_partial_products + ); ensure!(openings.quotient_polys.len() == common_data.num_quotient_polys()); Ok(()) From e20b76f1040f2b4439765f21d98a6597627ef7e7 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 17:45:59 -0700 Subject: [PATCH 42/97] Use salt_size --- plonky2/src/fri/validate_shape.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/plonky2/src/fri/validate_shape.rs b/plonky2/src/fri/validate_shape.rs index 597adeda..0ef85c4c 100644 --- a/plonky2/src/fri/validate_shape.rs +++ b/plonky2/src/fri/validate_shape.rs @@ -6,6 +6,7 @@ use crate::fri::structure::FriInstanceInfo; use crate::fri::FriParams; use crate::hash::hash_types::RichField; use crate::plonk::config::GenericConfig; +use crate::plonk::plonk_common::salt_size; pub(crate) fn validate_fri_proof_shape( proof: &FriProof, @@ -40,15 +41,7 @@ where .iter() .zip(&instance.oracles) { - ensure!( - leaf.len() - == oracle.num_polys - + if oracle.blinding && params.hiding { - SALT_SIZE - } else { - 0 - } - ); + ensure!(leaf.len() == oracle.num_polys + salt_size(oracle.blinding && params.hiding)); ensure!(merkle_proof.len() + cap_height == params.lde_bits()); } From 616a6b39190ea9d9410deea7bf0f2a86c49299de Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 20:54:45 -0700 Subject: [PATCH 43/97] Validate EVM proof shape --- evm/src/stark.rs | 4 ++++ evm/src/verifier.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/evm/src/stark.rs b/evm/src/stark.rs index 1af8a5e2..00818bbe 100644 --- a/evm/src/stark.rs +++ b/evm/src/stark.rs @@ -76,6 +76,10 @@ pub trait Stark, const D: usize>: Sync { 1.max(self.constraint_degree() - 1) } + fn num_quotient_polys(&self, config: &StarkConfig) -> usize { + self.quotient_degree_factor() * config.num_challenges + } + /// Computes the FRI instance used to prove this Stark. fn fri_instance( &self, diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index 3f5a5a88..a9e4d30b 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -119,6 +119,7 @@ where [(); S::COLUMNS]:, [(); C::Hasher::HASH_SIZE]:, { + validate_proof_shape(&stark, proof, config, ctl_vars.len())?; let StarkOpeningSet { local_values, next_values, @@ -204,6 +205,49 @@ where Ok(()) } +fn validate_proof_shape( + stark: &S, + proof: &StarkProof, + config: &StarkConfig, + num_ctls: usize, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + [(); S::COLUMNS]:, + [(); C::Hasher::HASH_SIZE]:, +{ + let StarkProof { + trace_cap, + permutation_ctl_zs_cap, + quotient_polys_cap, + openings, + // The shape of the opening proof will be checked in the FRI verifier (see + // validate_fri_proof_shape), so we ignore it here. + opening_proof: _, + } = proof; + + let degree_bits = proof.recover_degree_bits(config); + let fri_params = config.fri_params(degree_bits); + let cap_height = fri_params.config.cap_height; + ensure!(trace_cap.height() == cap_height); + ensure!(permutation_ctl_zs_cap.height() == cap_height); + ensure!(quotient_polys_cap.height() == cap_height); + + ensure!(openings.local_values.len() == S::COLUMNS); + ensure!(openings.next_values.len() == S::COLUMNS); + let num_ctl_zs = num_ctls * config.num_challenges; + let num_zs = num_ctl_zs + stark.num_permutation_batches(config); + ensure!(openings.permutation_ctl_zs.len() == num_zs); + ensure!(openings.permutation_ctl_zs_next.len() == num_zs); + ensure!(openings.ctl_zs_last.len() == num_ctl_zs); + let num_quotient_polys = stark.num_quotient_polys(config); + ensure!(openings.quotient_polys.len() == num_quotient_polys); + + Ok(()) +} + /// Evaluate the Lagrange polynomials `L_0` and `L_(n-1)` at a point `x`. /// `L_0(x) = (x^n - 1)/(n * (x - 1))` /// `L_(n-1)(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. From f8e0b6f6a3be437675f2278a0c53b298b46c3c96 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 21:30:14 -0700 Subject: [PATCH 44/97] fix --- evm/src/verifier.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index a9e4d30b..ad919f72 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -209,7 +209,7 @@ fn validate_proof_shape( stark: &S, proof: &StarkProof, config: &StarkConfig, - num_ctls: usize, + num_ctl_zs: usize, ) -> anyhow::Result<()> where F: RichField + Extendable, @@ -237,7 +237,6 @@ where ensure!(openings.local_values.len() == S::COLUMNS); ensure!(openings.next_values.len() == S::COLUMNS); - let num_ctl_zs = num_ctls * config.num_challenges; let num_zs = num_ctl_zs + stark.num_permutation_batches(config); ensure!(openings.permutation_ctl_zs.len() == num_zs); ensure!(openings.permutation_ctl_zs_next.len() == num_zs); From 5d4d81c29f752dfea04b8c52b832d651298543e8 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 21:41:24 -0700 Subject: [PATCH 45/97] Shape check in starky --- evm/src/stark.rs | 4 +-- evm/src/verifier.rs | 3 +- starky/src/stark.rs | 4 +++ starky/src/verifier.rs | 62 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/evm/src/stark.rs b/evm/src/stark.rs index 00818bbe..49c5b70b 100644 --- a/evm/src/stark.rs +++ b/evm/src/stark.rs @@ -111,7 +111,7 @@ pub trait Stark, const D: usize>: Sync { num_permutation_batches..num_permutation_batches + num_ctl_zs, ); - let num_quotient_polys = self.quotient_degree_factor() * config.num_challenges; + let num_quotient_polys = self.num_quotient_polys(config); let quotient_oracle = FriOracleInfo { num_polys: num_quotient_polys, blinding: false, @@ -174,7 +174,7 @@ pub trait Stark, const D: usize>: Sync { num_permutation_batches..num_permutation_batches + num_ctl_zs, ); - let num_quotient_polys = self.quotient_degree_factor() * inner_config.num_challenges; + let num_quotient_polys = self.num_quotient_polys(inner_config); let quotient_oracle = FriOracleInfo { num_polys: num_quotient_polys, blinding: false, diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index ad919f72..8c238e93 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -241,8 +241,7 @@ where ensure!(openings.permutation_ctl_zs.len() == num_zs); ensure!(openings.permutation_ctl_zs_next.len() == num_zs); ensure!(openings.ctl_zs_last.len() == num_ctl_zs); - let num_quotient_polys = stark.num_quotient_polys(config); - ensure!(openings.quotient_polys.len() == num_quotient_polys); + ensure!(openings.quotient_polys.len() == stark.num_quotient_polys(config)); Ok(()) } diff --git a/starky/src/stark.rs b/starky/src/stark.rs index 7f0df197..8ebca87c 100644 --- a/starky/src/stark.rs +++ b/starky/src/stark.rs @@ -78,6 +78,10 @@ pub trait Stark, const D: usize>: Sync { 1.max(self.constraint_degree() - 1) } + fn num_quotient_polys(&self, config: &StarkConfig) -> usize { + self.quotient_degree_factor() * config.num_challenges + } + /// Computes the FRI instance used to prove this Stark. fn fri_instance( &self, diff --git a/starky/src/verifier.rs b/starky/src/verifier.rs index efb3d29c..d4847859 100644 --- a/starky/src/verifier.rs +++ b/starky/src/verifier.rs @@ -1,6 +1,6 @@ use std::iter::once; -use anyhow::{ensure, Result}; +use anyhow::{anyhow, ensure, Result}; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::types::Field; @@ -12,7 +12,7 @@ use plonky2::plonk::plonk_common::reduce_with_powers; use crate::config::StarkConfig; use crate::constraint_consumer::ConstraintConsumer; use crate::permutation::PermutationCheckVars; -use crate::proof::{StarkOpeningSet, StarkProofChallenges, StarkProofWithPublicInputs}; +use crate::proof::{StarkOpeningSet, StarkProof, StarkProofChallenges, StarkProofWithPublicInputs}; use crate::stark::Stark; use crate::vanishing_poly::eval_vanishing_poly; use crate::vars::StarkEvaluationVars; @@ -144,6 +144,64 @@ where Ok(()) } +fn validate_proof_shape( + stark: &S, + proof_with_pis: StarkProofWithPublicInputs, + config: &StarkConfig, +) -> anyhow::Result<()> +where + F: RichField + Extendable, + C: GenericConfig, + S: Stark, + [(); S::COLUMNS]:, + [(); C::Hasher::HASH_SIZE]:, +{ + let degree_bits = proof_with_pis.proof.recover_degree_bits(config); + + let StarkProofWithPublicInputs { + proof: + StarkProof { + trace_cap, + permutation_zs_cap, + quotient_polys_cap, + openings, + // The shape of the opening proof will be checked in the FRI verifier (see + // validate_fri_proof_shape), so we ignore it here. + opening_proof: _, + }, + public_inputs, + } = proof_with_pis; + + ensure!(public_inputs.len() == S::PUBLIC_INPUTS); + + let fri_params = config.fri_params(degree_bits); + let cap_height = fri_params.config.cap_height; + let num_zs = stark.num_permutation_batches(config); + + ensure!(trace_cap.height() == cap_height); + ensure!(quotient_polys_cap.height() == cap_height); + + ensure!(openings.local_values.len() == S::COLUMNS); + ensure!(openings.next_values.len() == S::COLUMNS); + ensure!(openings.quotient_polys.len() == stark.num_quotient_polys(config)); + + if stark.uses_permutation_args() { + let permutation_zs_cap = permutation_zs_cap.ok_or_else(|| anyhow!("Missing Zs cap"))?; + let permutation_zs = openings + .permutation_zs + .ok_or_else(|| anyhow!("Missing permutation_zs"))?; + let permutation_zs_next = openings + .permutation_zs_next + .ok_or_else(|| anyhow!("Missing permutation_zs_next"))?; + + ensure!(permutation_zs_cap.height() == cap_height); + ensure!(permutation_zs.len() == num_zs); + ensure!(permutation_zs_next.len() == num_zs); + } + + Ok(()) +} + /// Evaluate the Lagrange polynomials `L_0` and `L_(n-1)` at a point `x`. /// `L_0(x) = (x^n - 1)/(n * (x - 1))` /// `L_(n-1)(x) = (x^n - 1)/(n * (g * x - 1))`, with `g` the first element of the subgroup. From d7d50e9d5ad7a8311c0d1aa6a166aad67efc82a7 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 19 Sep 2022 23:04:53 -0700 Subject: [PATCH 46/97] Minor --- evm/src/verifier.rs | 24 ++++++++---- plonky2/src/plonk/validate_shape.rs | 28 +++++++++----- starky/src/verifier.rs | 57 ++++++++++++++++++----------- 3 files changed, 71 insertions(+), 38 deletions(-) diff --git a/evm/src/verifier.rs b/evm/src/verifier.rs index 8c238e93..53ac3c7c 100644 --- a/evm/src/verifier.rs +++ b/evm/src/verifier.rs @@ -228,20 +228,30 @@ where opening_proof: _, } = proof; + let StarkOpeningSet { + local_values, + next_values, + permutation_ctl_zs, + permutation_ctl_zs_next, + ctl_zs_last, + quotient_polys, + } = openings; + let degree_bits = proof.recover_degree_bits(config); let fri_params = config.fri_params(degree_bits); let cap_height = fri_params.config.cap_height; + let num_zs = num_ctl_zs + stark.num_permutation_batches(config); + ensure!(trace_cap.height() == cap_height); ensure!(permutation_ctl_zs_cap.height() == cap_height); ensure!(quotient_polys_cap.height() == cap_height); - ensure!(openings.local_values.len() == S::COLUMNS); - ensure!(openings.next_values.len() == S::COLUMNS); - let num_zs = num_ctl_zs + stark.num_permutation_batches(config); - ensure!(openings.permutation_ctl_zs.len() == num_zs); - ensure!(openings.permutation_ctl_zs_next.len() == num_zs); - ensure!(openings.ctl_zs_last.len() == num_ctl_zs); - ensure!(openings.quotient_polys.len() == stark.num_quotient_polys(config)); + ensure!(local_values.len() == S::COLUMNS); + ensure!(next_values.len() == S::COLUMNS); + ensure!(permutation_ctl_zs.len() == num_zs); + ensure!(permutation_ctl_zs_next.len() == num_zs); + ensure!(ctl_zs_last.len() == num_ctl_zs); + ensure!(quotient_polys.len() == stark.num_quotient_polys(config)); Ok(()) } diff --git a/plonky2/src/plonk/validate_shape.rs b/plonky2/src/plonk/validate_shape.rs index 3e0082fc..f7ec1b6e 100644 --- a/plonky2/src/plonk/validate_shape.rs +++ b/plonky2/src/plonk/validate_shape.rs @@ -4,7 +4,7 @@ use plonky2_field::extension::Extendable; use crate::hash::hash_types::RichField; use crate::plonk::circuit_data::CommonCircuitData; use crate::plonk::config::{GenericConfig, Hasher}; -use crate::plonk::proof::{Proof, ProofWithPublicInputs}; +use crate::plonk::proof::{OpeningSet, Proof, ProofWithPublicInputs}; pub(crate) fn validate_proof_with_pis_shape( proof_with_pis: &ProofWithPublicInputs, @@ -50,20 +50,28 @@ where opening_proof: _, } = proof; + let OpeningSet { + constants, + plonk_sigmas, + wires, + plonk_zs, + plonk_zs_next, + partial_products, + quotient_polys, + } = openings; + let cap_height = common_data.fri_params.config.cap_height; ensure!(wires_cap.height() == cap_height); ensure!(plonk_zs_partial_products_cap.height() == cap_height); ensure!(quotient_polys_cap.height() == cap_height); - ensure!(openings.constants.len() == common_data.num_constants); - ensure!(openings.plonk_sigmas.len() == config.num_routed_wires); - ensure!(openings.wires.len() == config.num_wires); - ensure!(openings.plonk_zs.len() == config.num_challenges); - ensure!(openings.plonk_zs_next.len() == config.num_challenges); - ensure!( - openings.partial_products.len() == config.num_challenges * common_data.num_partial_products - ); - ensure!(openings.quotient_polys.len() == common_data.num_quotient_polys()); + ensure!(constants.len() == common_data.num_constants); + ensure!(plonk_sigmas.len() == config.num_routed_wires); + ensure!(wires.len() == config.num_wires); + ensure!(plonk_zs.len() == config.num_challenges); + ensure!(plonk_zs_next.len() == config.num_challenges); + ensure!(partial_products.len() == config.num_challenges * common_data.num_partial_products); + ensure!(quotient_polys.len() == common_data.num_quotient_polys()); Ok(()) } diff --git a/starky/src/verifier.rs b/starky/src/verifier.rs index d4847859..18ae9a27 100644 --- a/starky/src/verifier.rs +++ b/starky/src/verifier.rs @@ -55,6 +55,7 @@ where [(); S::PUBLIC_INPUTS]:, [(); C::Hasher::HASH_SIZE]:, { + validate_proof_shape(&stark, &proof_with_pis, config)?; check_permutation_options(&stark, &proof_with_pis, &challenges)?; let StarkProofWithPublicInputs { proof, @@ -146,7 +147,7 @@ where fn validate_proof_shape( stark: &S, - proof_with_pis: StarkProofWithPublicInputs, + proof_with_pis: &StarkProofWithPublicInputs, config: &StarkConfig, ) -> anyhow::Result<()> where @@ -156,21 +157,29 @@ where [(); S::COLUMNS]:, [(); C::Hasher::HASH_SIZE]:, { - let degree_bits = proof_with_pis.proof.recover_degree_bits(config); - let StarkProofWithPublicInputs { - proof: - StarkProof { - trace_cap, - permutation_zs_cap, - quotient_polys_cap, - openings, - // The shape of the opening proof will be checked in the FRI verifier (see - // validate_fri_proof_shape), so we ignore it here. - opening_proof: _, - }, + proof, public_inputs, } = proof_with_pis; + let degree_bits = proof.recover_degree_bits(config); + + let StarkProof { + trace_cap, + permutation_zs_cap, + quotient_polys_cap, + openings, + // The shape of the opening proof will be checked in the FRI verifier (see + // validate_fri_proof_shape), so we ignore it here. + opening_proof: _, + } = proof; + + let StarkOpeningSet { + local_values, + next_values, + permutation_zs, + permutation_zs_next, + quotient_polys, + } = openings; ensure!(public_inputs.len() == S::PUBLIC_INPUTS); @@ -181,22 +190,28 @@ where ensure!(trace_cap.height() == cap_height); ensure!(quotient_polys_cap.height() == cap_height); - ensure!(openings.local_values.len() == S::COLUMNS); - ensure!(openings.next_values.len() == S::COLUMNS); - ensure!(openings.quotient_polys.len() == stark.num_quotient_polys(config)); + ensure!(local_values.len() == S::COLUMNS); + ensure!(next_values.len() == S::COLUMNS); + ensure!(quotient_polys.len() == stark.num_quotient_polys(config)); if stark.uses_permutation_args() { - let permutation_zs_cap = permutation_zs_cap.ok_or_else(|| anyhow!("Missing Zs cap"))?; - let permutation_zs = openings - .permutation_zs + let permutation_zs_cap = permutation_zs_cap + .as_ref() + .ok_or_else(|| anyhow!("Missing Zs cap"))?; + let permutation_zs = permutation_zs + .as_ref() .ok_or_else(|| anyhow!("Missing permutation_zs"))?; - let permutation_zs_next = openings - .permutation_zs_next + let permutation_zs_next = permutation_zs_next + .as_ref() .ok_or_else(|| anyhow!("Missing permutation_zs_next"))?; ensure!(permutation_zs_cap.height() == cap_height); ensure!(permutation_zs.len() == num_zs); ensure!(permutation_zs_next.len() == num_zs); + } else { + ensure!(permutation_zs_cap.is_none()); + ensure!(permutation_zs.is_none()); + ensure!(permutation_zs_next.is_none()); } Ok(()) From f876a8ab024b2d943d4b5be45962b98f050d8664 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 21 Sep 2022 08:42:56 -0700 Subject: [PATCH 47/97] Fix macro vars in %stack directive --- evm/src/cpu/kernel/assembler.rs | 40 ++++++++++++++++--- evm/src/cpu/kernel/ast.rs | 15 ++++++- .../cpu/kernel/stack/stack_manipulation.rs | 3 +- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 0471bf99..4319612d 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -195,12 +195,12 @@ fn expand_macro_call( Item::StackManipulation(before, after) => { let after = after .iter() - .map(|replacement| { - if let StackReplacement::MacroLabel(label) = replacement { + .map(|replacement| match replacement { + StackReplacement::MacroLabel(label) => { StackReplacement::Identifier(get_actual_label(label)) - } else { - replacement.clone() } + StackReplacement::MacroVar(var) => get_arg(var).into(), + _ => replacement.clone(), }) .collect(); Item::StackManipulation(before.clone(), after) @@ -508,8 +508,8 @@ mod tests { "%macro bar(y) PUSH $y %endmacro", "%foo(42)", ]); - let push = get_push_opcode(1); - assert_eq!(kernel.code, vec![push, 42, push, 42]); + let push1 = get_push_opcode(1); + assert_eq!(kernel.code, vec![push1, 42, push1, 42]); } #[test] @@ -587,6 +587,34 @@ mod tests { assert_eq!(kernel.code, vec![dup1]); } + #[test] + fn stack_manipulation_in_macro() { + let pop = get_opcode("POP"); + let push1 = get_push_opcode(1); + + let kernel = parse_and_assemble(&[ + "%macro set_top(x) %stack (a) -> ($x) %endmacro", + "%set_top(42)", + ]); + assert_eq!(kernel.code, vec![pop, push1, 42]); + } + + #[test] + fn stack_manipulation_in_macro_with_name_collision() { + let pop = get_opcode("POP"); + let push_label = get_push_opcode(BYTES_PER_OFFSET); + + // In the stack directive, there's a named item `foo`. + // But when we invoke `%foo(foo)`, the argument refers to the `foo` label. + // Thus the expanded macro is `%stack (foo) -> (label foo)` (not real syntax). + let kernel = parse_and_assemble(&[ + "global foo:", + "%macro foo(x) %stack (foo) -> ($x) %endmacro", + "%foo(foo)", + ]); + assert_eq!(kernel.code, vec![pop, push_label, 0, 0, 0]); + } + fn parse_and_assemble(files: &[&str]) -> Kernel { parse_and_assemble_ext(files, HashMap::new(), true) } diff --git a/evm/src/cpu/kernel/ast.rs b/evm/src/cpu/kernel/ast.rs index bad60d03..19188ad9 100644 --- a/evm/src/cpu/kernel/ast.rs +++ b/evm/src/cpu/kernel/ast.rs @@ -46,14 +46,27 @@ pub(crate) enum StackPlaceholder { /// The right hand side of a %stack stack-manipulation macro. #[derive(Eq, PartialEq, Clone, Debug)] pub(crate) enum StackReplacement { + Literal(U256), /// Can be either a named item or a label. Identifier(String), - Literal(U256), + Label(String), MacroLabel(String), MacroVar(String), Constant(String), } +impl From for StackReplacement { + fn from(target: PushTarget) -> Self { + match target { + PushTarget::Literal(x) => Self::Literal(x), + PushTarget::Label(l) => Self::Label(l), + PushTarget::MacroLabel(l) => Self::MacroLabel(l), + PushTarget::MacroVar(v) => Self::MacroVar(v), + PushTarget::Constant(c) => Self::Constant(c), + } + } +} + /// The target of a `PUSH` operation. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub(crate) enum PushTarget { diff --git a/evm/src/cpu/kernel/stack/stack_manipulation.rs b/evm/src/cpu/kernel/stack/stack_manipulation.rs index faec7e04..ebc54af1 100644 --- a/evm/src/cpu/kernel/stack/stack_manipulation.rs +++ b/evm/src/cpu/kernel/stack/stack_manipulation.rs @@ -52,6 +52,7 @@ fn expand(names: Vec, replacements: Vec) -> let mut dst = replacements .into_iter() .flat_map(|item| match item { + StackReplacement::Literal(n) => vec![StackItem::PushTarget(PushTarget::Literal(n))], StackReplacement::Identifier(name) => { // May be either a named item or a label. Named items have precedence. if stack_blocks.contains_key(&name) { @@ -68,7 +69,7 @@ fn expand(names: Vec, replacements: Vec) -> vec![StackItem::PushTarget(PushTarget::Label(name))] } } - StackReplacement::Literal(n) => vec![StackItem::PushTarget(PushTarget::Literal(n))], + StackReplacement::Label(name) => vec![StackItem::PushTarget(PushTarget::Label(name))], StackReplacement::MacroLabel(_) | StackReplacement::MacroVar(_) | StackReplacement::Constant(_) => { From 218f689422f0d90041d5a023ce32bc13e7e92af3 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 21 Sep 2022 12:53:47 -0700 Subject: [PATCH 48/97] Fix prohibited macro names --- evm/src/cpu/kernel/assembler.rs | 6 ++++++ evm/src/cpu/kernel/evm_asm.pest | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 4319612d..5364e8a5 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -512,6 +512,12 @@ mod tests { assert_eq!(kernel.code, vec![push1, 42, push1, 42]); } + #[test] + fn macro_with_reserved_prefix() { + // The name `repeat` should be allowed, even though `rep` is reserved. + parse_and_assemble(&["%macro repeat %endmacro", "%repeat"]); + } + #[test] #[should_panic] fn macro_with_wrong_vars() { diff --git a/evm/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest index 89d06e74..5d670c6c 100644 --- a/evm/src/cpu/kernel/evm_asm.pest +++ b/evm/src/cpu/kernel/evm_asm.pest @@ -17,7 +17,7 @@ constant = ${ "@" ~ identifier } item = { macro_def | macro_call | repeat | stack | global_label_decl | local_label_decl | macro_label_decl | bytes_item | push_instruction | prover_input_instruction | nullary_instruction } macro_def = { ^"%macro" ~ identifier ~ paramlist? ~ item* ~ ^"%endmacro" } -macro_call = ${ "%" ~ !(^"macro" | ^"endmacro" | ^"rep" | ^"endrep" | ^"stack") ~ identifier ~ macro_arglist? } +macro_call = ${ "%" ~ !((^"macro" | ^"endmacro" | ^"rep" | ^"endrep" | ^"stack") ~ !identifier_char) ~ identifier ~ macro_arglist? } repeat = { ^"%rep" ~ literal ~ item* ~ ^"%endrep" } paramlist = { "(" ~ identifier ~ ("," ~ identifier)* ~ ")" } macro_arglist = !{ "(" ~ push_target ~ ("," ~ push_target)* ~ ")" } From 8fb1e4e760089401a5f992f3450cb4da5ea4fc54 Mon Sep 17 00:00:00 2001 From: BGluth Date: Wed, 21 Sep 2022 15:40:11 -0600 Subject: [PATCH 49/97] Added a mapping between code hashes and contract byte code Added a mapping between an account's `codehash` field and the actual contract byte code in `GenerationInputs`. --- evm/src/generation/mod.rs | 8 +++++++- evm/tests/transfer_to_new_addr.rs | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index baf2ec32..1d2fbf30 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -1,5 +1,7 @@ +use std::collections::HashMap; + use eth_trie_utils::partial_trie::PartialTrie; -use ethereum_types::Address; +use ethereum_types::{Address, H256}; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; @@ -41,6 +43,10 @@ pub struct GenerationInputs { /// storage tries, and nodes therein, that will be accessed by these transactions. pub storage_tries: Vec<(Address, PartialTrie)>, + /// Mapping between smart contract code hashes and the contract byte code. + /// All account smart contracts that are invoked will have an entry present. + pub contract_code: HashMap>, + pub block_metadata: BlockMetadata, } diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs index 1cd79194..82f4938b 100644 --- a/evm/tests/transfer_to_new_addr.rs +++ b/evm/tests/transfer_to_new_addr.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use eth_trie_utils::partial_trie::PartialTrie; use hex_literal::hex; use plonky2::field::goldilocks_field::GoldilocksField; @@ -31,6 +33,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { transactions_trie: PartialTrie::Empty, receipts_trie: PartialTrie::Empty, storage_tries: vec![], + contract_code: HashMap::new(), block_metadata, }; From 37d92b55acf3a1bccee7021e8af8e7b659df9f7b Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 18 Sep 2022 09:45:31 -0700 Subject: [PATCH 50/97] Basic MPT logic For now this contains most of the basic framework/structure. Logic for things like insertions will come later. --- evm/Cargo.toml | 2 +- evm/src/cpu/kernel/aggregator.rs | 8 +- evm/src/cpu/kernel/asm/mpt/hash.asm | 2 + evm/src/cpu/kernel/asm/mpt/load.asm | 12 +++ evm/src/cpu/kernel/asm/mpt/read.asm | 81 +++++++++++++++++++ .../read.asm => mpt/storage_read.asm} | 0 .../write.asm => mpt/storage_write.asm} | 0 evm/src/cpu/kernel/asm/mpt/util.asm | 11 +++ evm/src/cpu/kernel/asm/mpt/write.asm | 2 + evm/src/cpu/kernel/assembler.rs | 2 +- evm/src/cpu/kernel/ast.rs | 2 +- .../kernel/{constants.rs => constants/mod.rs} | 6 ++ evm/src/cpu/kernel/constants/trie_type.rs | 44 ++++++++++ evm/src/cpu/kernel/interpreter.rs | 15 ++-- evm/src/cpu/kernel/mod.rs | 3 +- evm/src/generation/mod.rs | 6 +- evm/src/generation/mpt.rs | 61 ++++++++++++++ .../kernel => generation}/prover_input.rs | 55 +++++++------ evm/src/generation/state.rs | 40 +++++---- 19 files changed, 299 insertions(+), 53 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/mpt/hash.asm create mode 100644 evm/src/cpu/kernel/asm/mpt/load.asm create mode 100644 evm/src/cpu/kernel/asm/mpt/read.asm rename evm/src/cpu/kernel/asm/{storage/read.asm => mpt/storage_read.asm} (100%) rename evm/src/cpu/kernel/asm/{storage/write.asm => mpt/storage_write.asm} (100%) create mode 100644 evm/src/cpu/kernel/asm/mpt/util.asm create mode 100644 evm/src/cpu/kernel/asm/mpt/write.asm rename evm/src/cpu/kernel/{constants.rs => constants/mod.rs} (92%) create mode 100644 evm/src/cpu/kernel/constants/trie_type.rs create mode 100644 evm/src/generation/mpt.rs rename evm/src/{cpu/kernel => generation}/prover_input.rs (66%) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 9d1bfa02..0c1e651e 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } -eth-trie-utils = { git = "https://github.com/mir-protocol/eth-trie-utils.git", rev = "3ca443fd18e3f6d209dd96cbad851e05ae058b34" } +eth-trie-utils = { git = "https://github.com/mir-protocol/eth-trie-utils.git", rev = "c52a04c9f349ac812b886f383a7306b27c8b96dc" } maybe_rayon = { path = "../maybe_rayon" } anyhow = "1.0.40" env_logger = "0.9.0" diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index dda006e6..08a1bb24 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -41,8 +41,12 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/rlp/encode.asm"), include_str!("asm/rlp/decode.asm"), include_str!("asm/rlp/read_to_memory.asm"), - include_str!("asm/storage/read.asm"), - include_str!("asm/storage/write.asm"), + include_str!("asm/mpt/hash.asm"), + include_str!("asm/mpt/read.asm"), + include_str!("asm/mpt/storage_read.asm"), + include_str!("asm/mpt/storage_write.asm"), + include_str!("asm/mpt/util.asm"), + include_str!("asm/mpt/write.asm"), include_str!("asm/transactions/router.asm"), include_str!("asm/transactions/type_0.asm"), include_str!("asm/transactions/type_1.asm"), diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm new file mode 100644 index 00000000..41795d5d --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -0,0 +1,2 @@ +global mpt_hash: + // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm new file mode 100644 index 00000000..e58e32dc --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -0,0 +1,12 @@ +// Load all partial trie data from prover inputs. +global mpt_load_all: + // First set GLOBAL_METADATA_TRIE_DATA_SIZE = 1. + // We don't want it to start at 0, as we use 0 as a null pointer. + PUSH 1 + %mstore(@GLOBAL_METADATA_TRIE_DATA_SIZE) + + TODO + +mpt_load_state: + PROVER_INPUT(mpt::state) + TODO diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm new file mode 100644 index 00000000..1dc497c8 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -0,0 +1,81 @@ +// Read a value from a MPT. +// +// Arguments: +// - the virtual address of the trie to search in +// - the key, as a U256 +// - the number of nibbles in the key +// +// This function returns a pointer to the leaf, or 0 if the key is not found. + +global mpt_read: + // stack: node_ptr, key, nibbles, retdest + DUP1 + %mload_trie_data + // stack: node_type, node_ptr, key, nibbles, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %add_const(1) SWAP1 + // stack: node_type, node_payload_ptr, key, nibbles, retdest + + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_read_empty) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_read_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_read_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_read_leaf) + + // There's still the MPT_NODE_DIGEST case, but if we hit a digest node, + // it means the prover failed to provide necessary Merkle data, so panic. + PANIC + +mpt_read_empty: + // Return 0 to indicate that the value was not found. + %stack (node_type, node_payload_ptr, key, nibbles, retdest) + -> (retdest, 0) + JUMP + +mpt_read_branch: + // stack: node_type, node_payload_ptr, key, nibbles, retdest + POP + // stack: node_payload_ptr, key, nibbles, retdest + DUP3 // nibbles + ISZERO + // stack: nibbles == 0, node_payload_ptr, key, nibbles, retdest + %jumpi(mpt_read_branch_end_of_key) + + // stack: node_payload_ptr, key, nibbles, retdest + // We have not reached the end of the key, so we descend to one of our children. + // Decrement nibbles, then compute current_nibble = (key >> (nibbles * 4)) & 0xF. + SWAP2 + %sub_const(1) + // stack: nibbles, key, node_payload_ptr, retdest + DUP2 DUP2 + // stack: nibbles, key, nibbles, key, node_payload_ptr, retdest + %mul_const(4) + // stack: nibbles * 4, key, nibbles, key, node_payload_ptr, retdest + SHR + // stack: key >> (nibbles * 4), nibbles, key, node_payload_ptr, retdest + %and_const(0xF) + // stack: current_nibble, nibbles, key, node_payload_ptr, retdest + %stack (current_nibble, nibbles, key, node_payload_ptr, retdest) + -> (current_nibble, node_payload_ptr, key, nibbles, retdest) + // child_ptr = load(node_payload_ptr + current_nibble) + ADD + %mload_trie_data + // stack: child_ptr, key, nibbles, retdest + %jump(mpt_read) // recurse + +mpt_read_branch_end_of_key: + %stack (node_payload_ptr, key, nibbles, retdest) -> (node_payload_ptr, retdest) + // stack: node_payload_ptr, retdest + %add_const(16) // skip over the 16 child nodes + // stack: leaf_ptr, retdest + SWAP1 + JUMP + +mpt_read_extension: + // stack: node_type, node_payload_ptr, key, nibbles, retdest + POP + // stack: node_payload_ptr, key, nibbles, retdest + +mpt_read_leaf: + // stack: node_type, node_payload_ptr, key, nibbles, retdest + POP + // stack: node_payload_ptr, key, nibbles, retdest diff --git a/evm/src/cpu/kernel/asm/storage/read.asm b/evm/src/cpu/kernel/asm/mpt/storage_read.asm similarity index 100% rename from evm/src/cpu/kernel/asm/storage/read.asm rename to evm/src/cpu/kernel/asm/mpt/storage_read.asm diff --git a/evm/src/cpu/kernel/asm/storage/write.asm b/evm/src/cpu/kernel/asm/mpt/storage_write.asm similarity index 100% rename from evm/src/cpu/kernel/asm/storage/write.asm rename to evm/src/cpu/kernel/asm/mpt/storage_write.asm diff --git a/evm/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm new file mode 100644 index 00000000..96c0ed9e --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/util.asm @@ -0,0 +1,11 @@ +%macro mload_trie_data + // stack: virtual + %mload_kernel(@SEGMENT_TRIE_DATA) + // stack: value +%endmacro + +%macro mstore_trie_data + // stack: virtual, value + %mstore_kernel(@SEGMENT_TRIE_DATA) + // stack: (empty) +%endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/write.asm b/evm/src/cpu/kernel/asm/mpt/write.asm new file mode 100644 index 00000000..be593a97 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/write.asm @@ -0,0 +1,2 @@ +global mpt_write: + // TODO diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 4319612d..7cc336ec 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -9,13 +9,13 @@ use crate::cpu::kernel::ast::Item::LocalLabelDeclaration; use crate::cpu::kernel::ast::StackReplacement; use crate::cpu::kernel::keccak_util::hash_kernel; use crate::cpu::kernel::optimizer::optimize_asm; -use crate::cpu::kernel::prover_input::ProverInputFn; use crate::cpu::kernel::stack::stack_manipulation::expand_stack_manipulation; use crate::cpu::kernel::utils::u256_to_trimmed_be_bytes; use crate::cpu::kernel::{ ast::{File, Item}, opcodes::{get_opcode, get_push_opcode}, }; +use crate::generation::prover_input::ProverInputFn; /// The number of bytes to push when pushing an offset within the code (i.e. when assembling jumps). /// Ideally we would automatically use the minimal number of bytes required, but that would be diff --git a/evm/src/cpu/kernel/ast.rs b/evm/src/cpu/kernel/ast.rs index 19188ad9..3728aa35 100644 --- a/evm/src/cpu/kernel/ast.rs +++ b/evm/src/cpu/kernel/ast.rs @@ -1,6 +1,6 @@ use ethereum_types::U256; -use crate::cpu::kernel::prover_input::ProverInputFn; +use crate::generation::prover_input::ProverInputFn; #[derive(Debug)] pub(crate) struct File { diff --git a/evm/src/cpu/kernel/constants.rs b/evm/src/cpu/kernel/constants/mod.rs similarity index 92% rename from evm/src/cpu/kernel/constants.rs rename to evm/src/cpu/kernel/constants/mod.rs index 98fe57c6..0db347cf 100644 --- a/evm/src/cpu/kernel/constants.rs +++ b/evm/src/cpu/kernel/constants/mod.rs @@ -4,11 +4,14 @@ use ethereum_types::U256; use hex_literal::hex; use crate::cpu::decode::invalid_opcodes_user; +use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::context_metadata::ContextMetadata; use crate::cpu::kernel::global_metadata::GlobalMetadata; use crate::cpu::kernel::txn_fields::NormalizedTxnField; use crate::memory::segments::Segment; +pub(crate) mod trie_type; + /// Constants that are accessible to our kernel assembly code. pub fn evm_constants() -> HashMap { let mut c = HashMap::new(); @@ -30,6 +33,9 @@ pub fn evm_constants() -> HashMap { for txn_field in ContextMetadata::all() { c.insert(txn_field.var_name().into(), (txn_field as u32).into()); } + for trie_type in PartialTrieType::all() { + c.insert(trie_type.var_name().into(), (trie_type as u32).into()); + } c.insert( "INVALID_OPCODES_USER".into(), U256::from_little_endian(&invalid_opcodes_user()), diff --git a/evm/src/cpu/kernel/constants/trie_type.rs b/evm/src/cpu/kernel/constants/trie_type.rs new file mode 100644 index 00000000..08fd8748 --- /dev/null +++ b/evm/src/cpu/kernel/constants/trie_type.rs @@ -0,0 +1,44 @@ +use eth_trie_utils::partial_trie::PartialTrie; + +pub(crate) enum PartialTrieType { + Empty = 0, + Hash = 1, + Branch = 2, + Extension = 3, + Leaf = 4, +} + +impl PartialTrieType { + pub(crate) const COUNT: usize = 5; + + pub(crate) fn of(trie: &PartialTrie) -> Self { + match trie { + PartialTrie::Empty => Self::Empty, + PartialTrie::Hash(_) => Self::Hash, + PartialTrie::Branch { .. } => Self::Branch, + PartialTrie::Extension { .. } => Self::Extension, + PartialTrie::Leaf { .. } => Self::Leaf, + } + } + + pub(crate) fn all() -> [Self; Self::COUNT] { + [ + Self::Empty, + Self::Hash, + Self::Branch, + Self::Extension, + Self::Leaf, + ] + } + + /// The variable name that gets passed into kernel assembly code. + pub(crate) fn var_name(&self) -> &'static str { + match self { + Self::Empty => "MPT_NODE_EMPTY", + Self::Hash => "MPT_NODE_HASH", + Self::Branch => "MPT_NODE_BRANCH", + Self::Extension => "MPT_NODE_EXTENSION", + Self::Leaf => "MPT_NODE_LEAF", + } + } +} diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 64d70529..6c2d1e6c 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -3,14 +3,19 @@ use std::collections::HashMap; use anyhow::{anyhow, bail}; use ethereum_types::{BigEndianHash, U256, U512}; use keccak_hash::keccak; +use plonky2::field::goldilocks_field::GoldilocksField; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::assembler::Kernel; -use crate::cpu::kernel::prover_input::ProverInputFn; use crate::cpu::kernel::txn_fields::NormalizedTxnField; use crate::generation::memory::{MemoryContextState, MemorySegmentState}; +use crate::generation::prover_input::ProverInputFn; +use crate::generation::state::GenerationState; +use crate::generation::GenerationInputs; use crate::memory::segments::Segment; +type F = GoldilocksField; + /// Halt interpreter execution whenever a jump to this offset is done. const DEFAULT_HALT_OFFSET: usize = 0xdeadbeef; @@ -55,8 +60,8 @@ pub struct Interpreter<'a> { offset: usize, context: usize, pub(crate) memory: InterpreterMemory, + generation_state: GenerationState, prover_inputs_map: &'a HashMap, - prover_inputs: Vec, pub(crate) halt_offsets: Vec, running: bool, } @@ -107,8 +112,8 @@ impl<'a> Interpreter<'a> { jumpdests: find_jumpdests(code), offset: initial_offset, memory: InterpreterMemory::with_code_and_stack(code, initial_stack), + generation_state: GenerationState::new(GenerationInputs::default()), prover_inputs_map: prover_inputs, - prover_inputs: Vec::new(), context: 0, halt_offsets: vec![DEFAULT_HALT_OFFSET], running: true, @@ -431,9 +436,9 @@ impl<'a> Interpreter<'a> { .prover_inputs_map .get(&(self.offset - 1)) .ok_or_else(|| anyhow!("Offset not in prover inputs."))?; - let output = prover_input_fn.run(self.stack()); + let stack = self.stack().to_vec(); + let output = self.generation_state.prover_input(&stack, prover_input_fn); self.push(output); - self.prover_inputs.push(output); Ok(()) } diff --git a/evm/src/cpu/kernel/mod.rs b/evm/src/cpu/kernel/mod.rs index ef5a9ba0..e14a6cd6 100644 --- a/evm/src/cpu/kernel/mod.rs +++ b/evm/src/cpu/kernel/mod.rs @@ -1,7 +1,7 @@ pub mod aggregator; pub mod assembler; mod ast; -mod constants; +pub(crate) mod constants; pub(crate) mod context_metadata; mod cost_estimator; pub(crate) mod global_metadata; @@ -9,7 +9,6 @@ pub(crate) mod keccak_util; mod opcodes; mod optimizer; mod parser; -pub mod prover_input; pub mod stack; mod txn_fields; mod utils; diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index baf2ec32..c17bba2c 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -18,9 +18,11 @@ use crate::proof::{BlockMetadata, PublicValues, TrieRoots}; use crate::util::trace_rows_to_poly_values; pub(crate) mod memory; +pub(crate) mod mpt; +pub(crate) mod prover_input; pub(crate) mod state; -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Default)] /// Inputs needed for trace generation. pub struct GenerationInputs { pub signed_txns: Vec>, @@ -49,7 +51,7 @@ pub(crate) fn generate_traces, const D: usize>( inputs: GenerationInputs, config: &StarkConfig, ) -> ([Vec>; NUM_TABLES], PublicValues) { - let mut state = GenerationState::::default(); + let mut state = GenerationState::::new(inputs.clone()); generate_bootstrap_kernel::(&mut state); diff --git a/evm/src/generation/mpt.rs b/evm/src/generation/mpt.rs new file mode 100644 index 00000000..eba53781 --- /dev/null +++ b/evm/src/generation/mpt.rs @@ -0,0 +1,61 @@ +use eth_trie_utils::partial_trie::PartialTrie; +use ethereum_types::U256; + +use crate::cpu::kernel::constants::trie_type::PartialTrieType; +use crate::generation::GenerationInputs; + +pub(crate) fn all_mpt_prover_inputs(inputs: &GenerationInputs) -> Vec { + let mut prover_inputs = vec![]; + + mpt_prover_inputs(&inputs.state_trie, &mut prover_inputs, &|_rlp| vec![]); // TODO + + mpt_prover_inputs( + &inputs.transactions_trie, + &mut prover_inputs, + &|_rlp| vec![], + ); // TODO + + mpt_prover_inputs(&inputs.receipts_trie, &mut prover_inputs, &|_rlp| vec![]); // TODO + + for (_addr, storage_trie) in &inputs.storage_tries { + mpt_prover_inputs(storage_trie, &mut prover_inputs, &|leaf_be| { + vec![U256::from_big_endian(leaf_be)] + }); + } + + prover_inputs +} + +/// Given a trie, generate the prover input data for that trie. In essence, this serializes a trie +/// into a `U256` array, in a simple format which the kernel understands. For example, a leaf node +/// is serialized as `(TYPE_LEAF, key, value)`, where key is a `(nibbles, depth)` pair and `value` +/// is a variable-length structure which depends on which trie we're dealing with. +pub(crate) fn mpt_prover_inputs( + trie: &PartialTrie, + prover_inputs: &mut Vec, + parse_leaf: &F, +) where + F: Fn(&[u8]) -> Vec, +{ + prover_inputs.push((PartialTrieType::of(trie) as u32).into()); + match trie { + PartialTrie::Empty => {} + PartialTrie::Hash(h) => prover_inputs.push(*h), + PartialTrie::Branch { children, value } => { + for child in children { + mpt_prover_inputs(child, prover_inputs, parse_leaf); + } + prover_inputs.extend(parse_leaf(value)); + } + PartialTrie::Extension { nibbles, child } => { + prover_inputs.push(nibbles.count.into()); + prover_inputs.push(nibbles.packed); + mpt_prover_inputs(child, prover_inputs, parse_leaf); + } + PartialTrie::Leaf { nibbles, value } => { + prover_inputs.push(nibbles.count.into()); + prover_inputs.push(nibbles.packed); + prover_inputs.extend(parse_leaf(value)); + } + } +} diff --git a/evm/src/cpu/kernel/prover_input.rs b/evm/src/generation/prover_input.rs similarity index 66% rename from evm/src/cpu/kernel/prover_input.rs rename to evm/src/generation/prover_input.rs index 38e1914e..7b49807c 100644 --- a/evm/src/cpu/kernel/prover_input.rs +++ b/evm/src/generation/prover_input.rs @@ -1,11 +1,13 @@ use std::str::FromStr; use ethereum_types::U256; +use plonky2::field::types::Field; -use crate::cpu::kernel::prover_input::Field::{ +use crate::generation::prover_input::EvmField::{ Bn254Base, Bn254Scalar, Secp256k1Base, Secp256k1Scalar, }; -use crate::cpu::kernel::prover_input::FieldOp::{Inverse, Sqrt}; +use crate::generation::prover_input::FieldOp::{Inverse, Sqrt}; +use crate::generation::state::GenerationState; /// Prover input function represented as a scoped function name. /// Example: `PROVER_INPUT(ff::bn254_base::inverse)` is represented as `ProverInputFn([ff, bn254_base, inverse])`. @@ -18,32 +20,39 @@ impl From> for ProverInputFn { } } -impl ProverInputFn { - /// Run the function on the stack. - pub fn run(&self, stack: &[U256]) -> U256 { - match self.0[0].as_str() { - "ff" => self.run_ff(stack), - "mpt" => todo!(), +impl GenerationState { + #[allow(unused)] // TODO: Should be used soon. + pub(crate) fn prover_input(&mut self, stack: &[U256], input_fn: &ProverInputFn) -> U256 { + match input_fn.0[0].as_str() { + "ff" => self.run_ff(stack, input_fn), + "mpt" => self.run_mpt(input_fn), _ => panic!("Unrecognized prover input function."), } } - // Finite field operations. - fn run_ff(&self, stack: &[U256]) -> U256 { - let field = Field::from_str(self.0[1].as_str()).unwrap(); - let op = FieldOp::from_str(self.0[2].as_str()).unwrap(); + /// Finite field operations. + fn run_ff(&self, stack: &[U256], input_fn: &ProverInputFn) -> U256 { + let field = EvmField::from_str(input_fn.0[1].as_str()).unwrap(); + let op = FieldOp::from_str(input_fn.0[2].as_str()).unwrap(); let x = *stack.last().expect("Empty stack"); field.op(op, x) } - // MPT operations. - #[allow(dead_code)] - fn run_mpt(&self, _stack: Vec) -> U256 { - todo!() + /// MPT data. + fn run_mpt(&mut self, input_fn: &ProverInputFn) -> U256 { + let operation = input_fn.0[1].as_str(); + match operation { + "trie_data" => self + .mpt_prover_inputs + .pop() + .unwrap_or_else(|| panic!("Out of MPT data")), + "num_storage_tries" => self.inputs.storage_tries.len().into(), + _ => panic!("Unrecognized MPT operation."), + } } } -enum Field { +enum EvmField { Bn254Base, Bn254Scalar, Secp256k1Base, @@ -55,7 +64,7 @@ enum FieldOp { Sqrt, } -impl FromStr for Field { +impl FromStr for EvmField { type Err = (); fn from_str(s: &str) -> Result { @@ -81,19 +90,19 @@ impl FromStr for FieldOp { } } -impl Field { +impl EvmField { fn order(&self) -> U256 { match self { - Field::Bn254Base => { + EvmField::Bn254Base => { U256::from_str("0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47") .unwrap() } - Field::Bn254Scalar => todo!(), - Field::Secp256k1Base => { + EvmField::Bn254Scalar => todo!(), + EvmField::Secp256k1Base => { U256::from_str("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f") .unwrap() } - Field::Secp256k1Scalar => { + EvmField::Secp256k1Scalar => { U256::from_str("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") .unwrap() } diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index 4cbe61c8..3319a604 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -6,6 +6,8 @@ use tiny_keccak::keccakf; use crate::cpu::columns::{CpuColumnsView, NUM_CPU_COLUMNS}; use crate::generation::memory::MemoryState; +use crate::generation::mpt::all_mpt_prover_inputs; +use crate::generation::GenerationInputs; use crate::keccak_memory::keccak_memory_stark::KeccakMemoryOp; use crate::memory::memory_stark::MemoryOp; use crate::memory::segments::Segment; @@ -15,6 +17,7 @@ use crate::{keccak, logic}; #[derive(Debug)] pub(crate) struct GenerationState { + pub(crate) inputs: GenerationInputs, pub(crate) cpu_rows: Vec<[F; NUM_CPU_COLUMNS]>, pub(crate) current_cpu_row: CpuColumnsView, @@ -24,9 +27,30 @@ pub(crate) struct GenerationState { pub(crate) keccak_inputs: Vec<[u64; keccak::keccak_stark::NUM_INPUTS]>, pub(crate) keccak_memory_inputs: Vec, pub(crate) logic_ops: Vec, + + /// Prover inputs containing MPT data, in reverse order so that the next input can be obtained + /// via `pop()`. + pub(crate) mpt_prover_inputs: Vec, } impl GenerationState { + pub(crate) fn new(inputs: GenerationInputs) -> Self { + let mut mpt_prover_inputs = all_mpt_prover_inputs(&inputs); + mpt_prover_inputs.reverse(); + + Self { + inputs, + cpu_rows: vec![], + current_cpu_row: [F::ZERO; NUM_CPU_COLUMNS].into(), + current_context: 0, + memory: MemoryState::default(), + keccak_inputs: vec![], + keccak_memory_inputs: vec![], + logic_ops: vec![], + mpt_prover_inputs, + } + } + /// Compute logical AND, and record the operation to be added in the logic table later. #[allow(unused)] // TODO: Should be used soon. pub(crate) fn and(&mut self, input0: U256, input1: U256) -> U256 { @@ -217,19 +241,3 @@ impl GenerationState { self.cpu_rows.push(swapped_row.into()); } } - -// `GenerationState` can't `derive(Default)` because `Default` is only implemented for arrays up to -// length 32 :-\. -impl Default for GenerationState { - fn default() -> Self { - Self { - cpu_rows: vec![], - current_cpu_row: [F::ZERO; NUM_CPU_COLUMNS].into(), - current_context: 0, - memory: MemoryState::default(), - keccak_inputs: vec![], - keccak_memory_inputs: vec![], - logic_ops: vec![], - } - } -} From 084700a7f4ad45efc9f14fe42a641cc1a3342108 Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Thu, 22 Sep 2022 18:09:23 -0700 Subject: [PATCH 51/97] Memory channel for program counter (#717) --- evm/src/all_stark.rs | 21 ++++++++--- evm/src/cpu/columns/mod.rs | 10 +++++- evm/src/cpu/cpu_stark.rs | 74 +++++++++++++++++++++++++++----------- evm/src/cpu/membus.rs | 70 ++++++++++++++++++++++++++++++++++++ evm/src/cpu/mod.rs | 1 + evm/src/memory/mod.rs | 2 +- 6 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 evm/src/cpu/membus.rs diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 5fd262ac..1775eef7 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -1,3 +1,5 @@ +use std::iter; + use plonky2::field::extension::Extendable; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; @@ -5,6 +7,7 @@ use plonky2::hash::hash_types::RichField; use crate::config::StarkConfig; use crate::cpu::cpu_stark; use crate::cpu::cpu_stark::CpuStark; +use crate::cpu::membus::NUM_GP_CHANNELS; use crate::cross_table_lookup::{CrossTableLookup, TableWithColumns}; use crate::keccak::keccak_stark; use crate::keccak::keccak_stark::KeccakStark; @@ -13,8 +16,8 @@ use crate::keccak_memory::keccak_memory_stark; use crate::keccak_memory::keccak_memory_stark::KeccakMemoryStark; use crate::logic; use crate::logic::LogicStark; +use crate::memory::memory_stark; use crate::memory::memory_stark::MemoryStark; -use crate::memory::{memory_stark, NUM_CHANNELS}; use crate::stark::Stark; #[derive(Clone)] @@ -129,11 +132,16 @@ fn ctl_logic() -> CrossTableLookup { } fn ctl_memory() -> CrossTableLookup { - let cpu_memory_ops = (0..NUM_CHANNELS).map(|channel| { + let cpu_memory_code_read = TableWithColumns::new( + Table::Cpu, + cpu_stark::ctl_data_code_memory(), + Some(cpu_stark::ctl_filter_code_memory()), + ); + let cpu_memory_gp_ops = (0..NUM_GP_CHANNELS).map(|channel| { TableWithColumns::new( Table::Cpu, - cpu_stark::ctl_data_memory(channel), - Some(cpu_stark::ctl_filter_memory(channel)), + cpu_stark::ctl_data_gp_memory(channel), + Some(cpu_stark::ctl_filter_gp_memory(channel)), ) }); let keccak_memory_reads = (0..KECCAK_WIDTH_BYTES).map(|i| { @@ -150,7 +158,8 @@ fn ctl_memory() -> CrossTableLookup { Some(keccak_memory_stark::ctl_filter()), ) }); - let all_lookers = cpu_memory_ops + let all_lookers = iter::once(cpu_memory_code_read) + .chain(cpu_memory_gp_ops) .chain(keccak_memory_reads) .chain(keccak_memory_writes) .collect(); @@ -725,6 +734,7 @@ mod tests { } #[test] + #[ignore] // Ignoring but not deleting so the test can serve as an API usage example fn test_all_stark() -> Result<()> { let config = StarkConfig::standard_fast_config(); let (all_stark, proof) = get_proof(&config)?; @@ -732,6 +742,7 @@ mod tests { } #[test] + #[ignore] // Ignoring but not deleting so the test can serve as an API usage example fn test_all_stark_recursive_verifier() -> Result<()> { init_logger(); diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 93e93ce6..5204122e 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -7,6 +7,7 @@ use std::mem::{size_of, transmute}; use std::ops::{Index, IndexMut}; use crate::cpu::columns::general::CpuGeneralColumnsView; +use crate::cpu::membus::NUM_GP_CHANNELS; use crate::memory; use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; @@ -35,6 +36,13 @@ pub struct CpuColumnsView { /// Lets us re-use columns in non-cycle rows. pub is_cpu_cycle: T, + /// If CPU cycle: Current context. + // TODO: this is currently unconstrained + pub context: T, + + /// If CPU cycle: Context for code memory channel. + pub code_context: T, + /// If CPU cycle: The program counter for the current instruction. pub program_counter: T, @@ -159,7 +167,7 @@ pub struct CpuColumnsView { pub(crate) general: CpuGeneralColumnsView, pub(crate) clock: T, - pub mem_channels: [MemoryChannelView; memory::NUM_CHANNELS], + pub mem_channels: [MemoryChannelView; NUM_GP_CHANNELS], } // `u8` is guaranteed to have a `size_of` of 1. diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 9949b044..5f702317 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -1,4 +1,5 @@ use std::borrow::{Borrow, BorrowMut}; +use std::iter::repeat; use std::marker::PhantomData; use itertools::Itertools; @@ -10,10 +11,11 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; use crate::cpu::{ - bootstrap_kernel, control_flow, decode, jumps, simple_logic, stack_bounds, syscalls, + bootstrap_kernel, control_flow, decode, jumps, membus, simple_logic, stack_bounds, syscalls, }; use crate::cross_table_lookup::Column; -use crate::memory::NUM_CHANNELS; +use crate::memory::segments::Segment; +use crate::memory::{NUM_CHANNELS, VALUE_LIMBS}; use crate::stark::Stark; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; @@ -25,14 +27,13 @@ pub fn ctl_data_keccak() -> Vec> { } pub fn ctl_data_keccak_memory() -> Vec> { - // When executing KECCAK_GENERAL, the memory channels are used as follows: - // channel 0: instruction - // channel 1: stack[-1] = context - // channel 2: stack[-2] = segment - // channel 3: stack[-3] = virtual - let context = Column::single(COL_MAP.mem_channels[1].value[0]); - let segment = Column::single(COL_MAP.mem_channels[2].value[0]); - let virt = Column::single(COL_MAP.mem_channels[3].value[0]); + // When executing KECCAK_GENERAL, the GP memory channels are used as follows: + // GP channel 0: stack[-1] = context + // GP channel 1: stack[-2] = segment + // GP channel 2: stack[-3] = virtual + let context = Column::single(COL_MAP.mem_channels[0].value[0]); + let segment = Column::single(COL_MAP.mem_channels[1].value[0]); + let virt = Column::single(COL_MAP.mem_channels[2].value[0]); let num_channels = F::from_canonical_usize(NUM_CHANNELS); let clock = Column::linear_combination([(COL_MAP.clock, num_channels)]); @@ -60,29 +61,57 @@ pub fn ctl_filter_logic() -> Column { Column::sum([COL_MAP.is_and, COL_MAP.is_or, COL_MAP.is_xor]) } -pub fn ctl_data_memory(channel: usize) -> Vec> { - debug_assert!(channel < NUM_CHANNELS); +pub const MEM_CODE_CHANNEL_IDX: usize = 0; +pub const MEM_GP_CHANNELS_IDX_START: usize = MEM_CODE_CHANNEL_IDX + 1; + +/// Make the time/channel column for memory lookups. +fn mem_time_and_channel(channel: usize) -> Column { + let scalar = F::from_canonical_usize(NUM_CHANNELS); + let addend = F::from_canonical_usize(channel); + Column::linear_combination_with_constant([(COL_MAP.clock, scalar)], addend) +} + +pub fn ctl_data_code_memory() -> Vec> { + let mut cols = vec![ + Column::constant(F::ONE), // is_read + Column::single(COL_MAP.code_context), // addr_context + Column::constant(F::from_canonical_u64(Segment::Code as u64)), // addr_segment + Column::single(COL_MAP.program_counter), // addr_virtual + ]; + + // Low limb of the value matches the opcode bits + cols.push(Column::le_bits(COL_MAP.opcode_bits)); + + // High limbs of the value are all zero. + cols.extend(repeat(Column::constant(F::ZERO)).take(VALUE_LIMBS - 1)); + + cols.push(mem_time_and_channel(MEM_CODE_CHANNEL_IDX)); + + cols +} + +pub fn ctl_data_gp_memory(channel: usize) -> Vec> { let channel_map = COL_MAP.mem_channels[channel]; - let mut cols: Vec> = Column::singles([ + let mut cols: Vec<_> = Column::singles([ channel_map.is_read, channel_map.addr_context, channel_map.addr_segment, channel_map.addr_virtual, ]) - .collect_vec(); + .collect(); + cols.extend(Column::singles(channel_map.value)); - let scalar = F::from_canonical_usize(NUM_CHANNELS); - let addend = F::from_canonical_usize(channel); - cols.push(Column::linear_combination_with_constant( - [(COL_MAP.clock, scalar)], - addend, - )); + cols.push(mem_time_and_channel(MEM_GP_CHANNELS_IDX_START + channel)); cols } -pub fn ctl_filter_memory(channel: usize) -> Column { +pub fn ctl_filter_code_memory() -> Column { + Column::single(COL_MAP.is_cpu_cycle) +} + +pub fn ctl_filter_gp_memory(channel: usize) -> Column { Column::single(COL_MAP.mem_channels[channel].used) } @@ -95,6 +124,7 @@ impl CpuStark { pub fn generate(&self, local_values: &mut [F; NUM_CPU_COLUMNS]) { let local_values: &mut CpuColumnsView<_> = local_values.borrow_mut(); decode::generate(local_values); + membus::generate(local_values); simple_logic::generate(local_values); stack_bounds::generate(local_values); // Must come after `decode`. } @@ -117,6 +147,7 @@ impl, const D: usize> Stark for CpuStark, const D: usize> Stark for CpuStark = CODE + 1..(CODE + 1) + super::NUM_GP_CHANNELS; +} + +/// Total memory channels used by the CPU table. This includes all the `GP_MEM_CHANNELS` as well as +/// all special-purpose memory channels. +/// +/// Currently, there is one special-purpose memory channel, which reads the opcode from memory. Its +/// limitations are: +/// - it is enabled by `is_cpu_cycle`, +/// - it always reads and cannot write, +/// - the context is derived from the current context and the `is_kernel_mode` flag, +/// - the segment is hard-wired to the code segment, +/// - the address is `program_counter`, +/// - the value must fit in one byte (in the least-significant position) and its eight bits are +/// found in `opcode_bits`. +/// These limitations save us numerous columns in the CPU table. +pub const NUM_CHANNELS: usize = channel_indices::GP.end; + +/// Calculates `lv.stack_len_bounds_aux`. Note that this must be run after decode. +pub fn generate(lv: &mut CpuColumnsView) { + let cycle_filter = lv.is_cpu_cycle; + if cycle_filter == F::ZERO { + return; + } + + assert!(lv.is_kernel_mode.to_canonical_u64() <= 1); + + // Set `lv.code_context` to 0 if in kernel mode and to `lv.context` if in user mode. + lv.code_context = (F::ONE - lv.is_kernel_mode) * lv.context; +} + +pub fn eval_packed( + lv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + // Validate `lv.code_context`. It should be 0 if in kernel mode and `lv.context` if in user + // mode. + yield_constr.constraint( + lv.is_cpu_cycle * (lv.code_context - (P::ONES - lv.is_kernel_mode) * lv.context), + ); +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + // Validate `lv.code_context`. It should be 0 if in kernel mode and `lv.context` if in user + // mode. + let diff = builder.sub_extension(lv.context, lv.code_context); + let constr = builder.mul_sub_extension(lv.is_kernel_mode, lv.context, diff); + let filtered_constr = builder.mul_extension(lv.is_cpu_cycle, constr); + yield_constr.constraint(builder, filtered_constr); +} diff --git a/evm/src/cpu/mod.rs b/evm/src/cpu/mod.rs index bda044b7..7b1e4756 100644 --- a/evm/src/cpu/mod.rs +++ b/evm/src/cpu/mod.rs @@ -5,6 +5,7 @@ pub mod cpu_stark; pub(crate) mod decode; mod jumps; pub mod kernel; +pub(crate) mod membus; mod simple_logic; mod stack_bounds; mod syscalls; diff --git a/evm/src/memory/mod.rs b/evm/src/memory/mod.rs index dd82ad04..4cdfd1be 100644 --- a/evm/src/memory/mod.rs +++ b/evm/src/memory/mod.rs @@ -3,5 +3,5 @@ pub mod memory_stark; pub mod segments; // TODO: Move to CPU module, now that channels have been removed from the memory table. -pub(crate) const NUM_CHANNELS: usize = 4; +pub(crate) const NUM_CHANNELS: usize = crate::cpu::membus::NUM_CHANNELS; pub(crate) const VALUE_LIMBS: usize = 8; From dbb0503d3e108a82178f4c177076d4c5865052bb Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 22 Sep 2022 20:22:43 -0700 Subject: [PATCH 52/97] Support macro overloading --- evm/src/cpu/kernel/assembler.rs | 55 +++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 2cacf90b..811c2f0e 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -52,6 +52,12 @@ impl Kernel { } } +#[derive(Eq, PartialEq, Hash, Clone, Debug)] +struct MacroSignature { + name: String, + num_params: usize, +} + struct Macro { params: Vec, items: Vec, @@ -105,17 +111,21 @@ pub(crate) fn assemble( Kernel::new(code, global_labels, prover_inputs) } -fn find_macros(files: &[File]) -> HashMap { +fn find_macros(files: &[File]) -> HashMap { let mut macros = HashMap::new(); for file in files { for item in &file.body { if let Item::MacroDef(name, params, items) = item { - let _macro = Macro { + let signature = MacroSignature { + name: name.clone(), + num_params: params.len(), + }; + let macro_ = Macro { params: params.clone(), items: items.clone(), }; - let old = macros.insert(name.clone(), _macro); - assert!(old.is_none(), "Duplicate macro: {name}"); + let old = macros.insert(signature.clone(), macro_); + assert!(old.is_none(), "Duplicate macro signature: {:?}", signature); } } } @@ -124,7 +134,7 @@ fn find_macros(files: &[File]) -> HashMap { fn expand_macros( body: Vec, - macros: &HashMap, + macros: &HashMap, macro_counter: &mut u32, ) -> Vec { let mut expanded = vec![]; @@ -147,30 +157,25 @@ fn expand_macros( fn expand_macro_call( name: String, args: Vec, - macros: &HashMap, + macros: &HashMap, macro_counter: &mut u32, ) -> Vec { - let _macro = macros - .get(&name) - .unwrap_or_else(|| panic!("No such macro: {}", name)); - - assert_eq!( - args.len(), - _macro.params.len(), - "Macro `{}`: expected {} arguments, got {}", + let signature = MacroSignature { name, - _macro.params.len(), - args.len() - ); + num_params: args.len(), + }; + let macro_ = macros + .get(&signature) + .unwrap_or_else(|| panic!("No such macro: {:?}", signature)); let get_actual_label = |macro_label| format!("@{}.{}", macro_counter, macro_label); let get_arg = |var| { - let param_index = _macro.get_param_index(var); + let param_index = macro_.get_param_index(var); args[param_index].clone() }; - let expanded_item = _macro + let expanded_item = macro_ .items .iter() .map(|item| match item { @@ -518,6 +523,18 @@ mod tests { parse_and_assemble(&["%macro repeat %endmacro", "%repeat"]); } + #[test] + fn overloaded_macros() { + let kernel = parse_and_assemble(&[ + "%macro push(x) PUSH $x %endmacro", + "%macro push(x, y) PUSH $x PUSH $y %endmacro", + "%push(5)", + "%push(6, 7)", + ]); + let push1 = get_push_opcode(1); + assert_eq!(kernel.code, vec![push1, 5, push1, 6, push1, 7]); + } + #[test] #[should_panic] fn macro_with_wrong_vars() { From b6d71a7008b966d6bc5461ae1dfb9310a43d4006 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 23 Sep 2022 10:54:17 -0700 Subject: [PATCH 53/97] Keccak benchmark And reworking things a bit to include the timing data we want. --- evm/src/all_stark.rs | 12 ++- evm/src/config.rs | 4 +- evm/src/generation/mod.rs | 16 ++-- evm/src/keccak/keccak_stark.rs | 98 ++++++++++++++++++-- evm/src/keccak_memory/keccak_memory_stark.rs | 8 +- evm/src/keccak_sponge/keccak_sponge_stark.rs | 8 +- evm/src/logic.rs | 25 ++++- evm/src/memory/memory_stark.rs | 17 ++-- evm/src/prover.rs | 76 +++++++++------ starky/src/config.rs | 4 +- 10 files changed, 195 insertions(+), 73 deletions(-) diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 1775eef7..2786c36a 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -223,14 +223,18 @@ mod tests { let keccak_inputs = (0..num_keccak_perms) .map(|_| [0u64; NUM_INPUTS].map(|_| rng.gen())) .collect_vec(); - keccak_stark.generate_trace(keccak_inputs) + keccak_stark.generate_trace(keccak_inputs, &mut TimingTree::default()) } fn make_keccak_memory_trace( keccak_memory_stark: &KeccakMemoryStark, config: &StarkConfig, ) -> Vec> { - keccak_memory_stark.generate_trace(vec![], 1 << config.fri_config.cap_height) + keccak_memory_stark.generate_trace( + vec![], + 1 << config.fri_config.cap_height, + &mut TimingTree::default(), + ) } fn make_logic_trace( @@ -247,7 +251,7 @@ mod tests { Operation::new(op, input0, input1) }) .collect(); - logic_stark.generate_trace(ops) + logic_stark.generate_trace(ops, &mut TimingTree::default()) } fn make_memory_trace( @@ -256,7 +260,7 @@ mod tests { rng: &mut R, ) -> (Vec>, usize) { let memory_ops = generate_random_memory_ops(num_memory_ops, rng); - let trace = memory_stark.generate_trace(memory_ops); + let trace = memory_stark.generate_trace(memory_ops, &mut TimingTree::default()); let num_ops = trace[0].values.len(); (trace, num_ops) } diff --git a/evm/src/config.rs b/evm/src/config.rs index 500cd957..a593c827 100644 --- a/evm/src/config.rs +++ b/evm/src/config.rs @@ -21,9 +21,9 @@ impl StarkConfig { fri_config: FriConfig { rate_bits: 1, cap_height: 4, - proof_of_work_bits: 10, + proof_of_work_bits: 16, reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), - num_query_rounds: 90, + num_query_rounds: 84, }, } } diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 12a531c3..f91b70d2 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -6,6 +6,7 @@ use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; +use plonky2::util::timing::TimingTree; use serde::{Deserialize, Serialize}; use crate::all_stark::{AllStark, NUM_TABLES}; @@ -56,6 +57,7 @@ pub(crate) fn generate_traces, const D: usize>( all_stark: &AllStark, inputs: GenerationInputs, config: &StarkConfig, + timing: &mut TimingTree, ) -> ([Vec>; NUM_TABLES], PublicValues) { let mut state = GenerationState::::new(inputs.clone()); @@ -101,12 +103,14 @@ pub(crate) fn generate_traces, const D: usize>( assert_eq!(current_cpu_row, [F::ZERO; NUM_CPU_COLUMNS].into()); let cpu_trace = trace_rows_to_poly_values(cpu_rows); - let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs); - let keccak_memory_trace = all_stark - .keccak_memory_stark - .generate_trace(keccak_memory_inputs, 1 << config.fri_config.cap_height); - let logic_trace = all_stark.logic_stark.generate_trace(logic_ops); - let memory_trace = all_stark.memory_stark.generate_trace(memory.log); + let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs, timing); + let keccak_memory_trace = all_stark.keccak_memory_stark.generate_trace( + keccak_memory_inputs, + 1 << config.fri_config.cap_height, + timing, + ); + let logic_trace = all_stark.logic_stark.generate_trace(logic_ops, timing); + let memory_trace = all_stark.memory_stark.generate_trace(memory.log, timing); let traces = [ cpu_trace, keccak_trace, diff --git a/evm/src/keccak/keccak_stark.rs b/evm/src/keccak/keccak_stark.rs index 23ffe0e9..87a61ae7 100644 --- a/evm/src/keccak/keccak_stark.rs +++ b/evm/src/keccak/keccak_stark.rs @@ -201,23 +201,22 @@ impl, const D: usize> KeccakStark { row[out_reg_hi] = F::from_canonical_u64(row[in_reg_hi].to_canonical_u64() ^ rc_hi); } - pub fn generate_trace(&self, inputs: Vec<[u64; NUM_INPUTS]>) -> Vec> { - let mut timing = TimingTree::new("generate trace", log::Level::Debug); - + pub fn generate_trace( + &self, + inputs: Vec<[u64; NUM_INPUTS]>, + timing: &mut TimingTree, + ) -> Vec> { // Generate the witness, except for permuted columns in the lookup argument. let trace_rows = timed!( - &mut timing, + timing, "generate trace rows", self.generate_trace_rows(inputs) ); - let trace_polys = timed!( - &mut timing, + timing, "convert to PolynomialValues", trace_rows_to_poly_values(trace_rows) ); - - timing.print(); trace_polys } } @@ -542,12 +541,22 @@ impl, const D: usize> Stark for KeccakStark Result<()> { + const NUM_PERMS: usize = 85; + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = KeccakStark; + let stark = S::default(); + let config = StarkConfig::standard_fast_config(); + + init_logger(); + + let input: Vec<[u64; NUM_INPUTS]> = (0..NUM_PERMS).map(|_| rand::random()).collect(); + + let mut timing = TimingTree::new("prove", log::Level::Debug); + let trace_poly_values = timed!( + timing, + "generate trace", + stark.generate_trace(input.try_into().unwrap(), &mut timing) + ); + + // TODO: Cloning this isn't great; consider having `from_values` accept a reference, + // or having `compute_permutation_z_polys` read trace values from the `PolynomialBatch`. + let cloned_trace_poly_values = timed!(timing, "clone", trace_poly_values.clone()); + + let trace_commitments = timed!( + timing, + "compute trace commitment", + PolynomialBatch::::from_values( + cloned_trace_poly_values, + config.fri_config.rate_bits, + false, + config.fri_config.cap_height, + &mut timing, + None, + ) + ); + let degree = 1 << trace_commitments.degree_log; + + // Fake CTL data. + let ctl_z_data = CtlZData { + z: PolynomialValues::zero(degree), + challenge: GrandProductChallenge { + beta: F::ZERO, + gamma: F::ZERO, + }, + columns: vec![], + filter_column: None, + }; + let ctl_data = CtlData { + zs_columns: vec![ctl_z_data; config.num_challenges], + }; + + prove_single_table( + &stark, + &config, + &trace_poly_values, + &trace_commitments, + &ctl_data, + &mut Challenger::new(), + &mut timing, + )?; + + timing.print(); + Ok(()) + } + + fn init_logger() { + let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "debug")); + } } diff --git a/evm/src/keccak_memory/keccak_memory_stark.rs b/evm/src/keccak_memory/keccak_memory_stark.rs index cf8955b3..1bbea168 100644 --- a/evm/src/keccak_memory/keccak_memory_stark.rs +++ b/evm/src/keccak_memory/keccak_memory_stark.rs @@ -93,23 +93,21 @@ impl, const D: usize> KeccakMemoryStark { &self, operations: Vec, min_rows: usize, + timing: &mut TimingTree, ) -> Vec> { - let mut timing = TimingTree::new("generate trace", log::Level::Debug); - // Generate the witness row-wise. let trace_rows = timed!( - &mut timing, + timing, "generate trace rows", self.generate_trace_rows(operations, min_rows) ); let trace_polys = timed!( - &mut timing, + timing, "convert to PolynomialValues", trace_rows_to_poly_values(trace_rows) ); - timing.print(); trace_polys } diff --git a/evm/src/keccak_sponge/keccak_sponge_stark.rs b/evm/src/keccak_sponge/keccak_sponge_stark.rs index afde02c2..219c0c21 100644 --- a/evm/src/keccak_sponge/keccak_sponge_stark.rs +++ b/evm/src/keccak_sponge/keccak_sponge_stark.rs @@ -171,23 +171,21 @@ impl, const D: usize> KeccakSpongeStark { &self, operations: Vec, min_rows: usize, + timing: &mut TimingTree, ) -> Vec> { - let mut timing = TimingTree::new("generate trace", log::Level::Debug); - // Generate the witness row-wise. let trace_rows = timed!( - &mut timing, + timing, "generate trace rows", self.generate_trace_rows(operations, min_rows) ); let trace_polys = timed!( - &mut timing, + timing, "convert to PolynomialValues", trace_rows_to_poly_values(trace_rows) ); - timing.print(); trace_polys } diff --git a/evm/src/logic.rs b/evm/src/logic.rs index 2fa9c810..dc6fc777 100644 --- a/evm/src/logic.rs +++ b/evm/src/logic.rs @@ -7,10 +7,13 @@ use plonky2::field::packed::PackedField; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; +use plonky2::timed; +use plonky2::util::timing::TimingTree; use plonky2_util::ceil_div_usize; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cross_table_lookup::Column; +use crate::logic::columns::NUM_COLUMNS; use crate::stark::Stark; use crate::util::{limb_from_bits_le, limb_from_bits_le_recursive, trace_rows_to_poly_values}; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; @@ -101,7 +104,25 @@ impl Operation { } impl LogicStark { - pub(crate) fn generate_trace(&self, operations: Vec) -> Vec> { + pub(crate) fn generate_trace( + &self, + operations: Vec, + timing: &mut TimingTree, + ) -> Vec> { + let trace_rows = timed!( + timing, + "generate trace rows", + self.generate_trace_rows(operations) + ); + let trace_polys = timed!( + timing, + "convert to PolynomialValues", + trace_rows_to_poly_values(trace_rows) + ); + trace_polys + } + + fn generate_trace_rows(&self, operations: Vec) -> Vec<[F; NUM_COLUMNS]> { let len = operations.len(); let padded_len = len.next_power_of_two(); @@ -115,7 +136,7 @@ impl LogicStark { rows.push([F::ZERO; columns::NUM_COLUMNS]); } - trace_rows_to_poly_values(rows) + rows } fn generate_row(operation: Operation) -> [F; columns::NUM_COLUMNS] { diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index 1ec0c11c..f5455a53 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -187,12 +187,14 @@ impl, const D: usize> MemoryStark { } } - pub(crate) fn generate_trace(&self, memory_ops: Vec) -> Vec> { - let mut timing = TimingTree::new("generate trace", log::Level::Debug); - + pub(crate) fn generate_trace( + &self, + memory_ops: Vec, + timing: &mut TimingTree, + ) -> Vec> { // Generate most of the trace in row-major form. let trace_rows = timed!( - &mut timing, + timing, "generate trace rows", self.generate_trace_row_major(memory_ops) ); @@ -204,13 +206,10 @@ impl, const D: usize> MemoryStark { // A few final generation steps, which work better in column-major form. Self::generate_trace_col_major(&mut trace_col_vecs); - let trace_polys = trace_col_vecs + trace_col_vecs .into_iter() .map(|column| PolynomialValues::new(column)) - .collect(); - - timing.print(); - trace_polys + .collect() } } diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 31e76a1c..7fe57631 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -53,7 +53,7 @@ where [(); LogicStark::::COLUMNS]:, [(); MemoryStark::::COLUMNS]:, { - let (traces, public_values) = generate_traces(all_stark, inputs, config); + let (traces, public_values) = generate_traces(all_stark, inputs, config, timing); prove_with_traces(all_stark, config, traces, public_values, timing) } @@ -175,7 +175,7 @@ where } /// Compute proof for a single STARK table. -fn prove_single_table( +pub(crate) fn prove_single_table( stark: &S, config: &StarkConfig, trace_poly_values: &[PolynomialValues], @@ -210,7 +210,11 @@ where ) }); let permutation_zs = permutation_challenges.as_ref().map(|challenges| { - compute_permutation_z_polys::(stark, config, trace_poly_values, challenges) + timed!( + timing, + "compute permutation Z(x) polys", + compute_permutation_z_polys::(stark, config, trace_poly_values, challenges) + ) }); let num_permutation_zs = permutation_zs.as_ref().map(|v| v.len()).unwrap_or(0); @@ -223,13 +227,17 @@ where }; assert!(!z_polys.is_empty(), "No CTL?"); - let permutation_ctl_zs_commitment = PolynomialBatch::from_values( - z_polys, - rate_bits, - false, - config.fri_config.cap_height, + let permutation_ctl_zs_commitment = timed!( timing, - None, + "compute Zs commitment", + PolynomialBatch::from_values( + z_polys, + rate_bits, + false, + config.fri_config.cap_height, + timing, + None, + ) ); let permutation_ctl_zs_cap = permutation_ctl_zs_commitment.merkle_tree.cap.clone(); @@ -249,27 +257,37 @@ where config, ); } - let quotient_polys = compute_quotient_polys::::Packing, C, S, D>( - stark, - trace_commitment, - &permutation_ctl_zs_commitment, - permutation_challenges.as_ref(), - ctl_data, - alphas, - degree_bits, - num_permutation_zs, - config, + let quotient_polys = timed!( + timing, + "compute quotient polys", + compute_quotient_polys::::Packing, C, S, D>( + stark, + trace_commitment, + &permutation_ctl_zs_commitment, + permutation_challenges.as_ref(), + ctl_data, + alphas, + degree_bits, + num_permutation_zs, + config, + ) + ); + let all_quotient_chunks = timed!( + timing, + "split quotient polys", + quotient_polys + .into_par_iter() + .flat_map(|mut quotient_poly| { + quotient_poly + .trim_to_len(degree * stark.quotient_degree_factor()) + .expect( + "Quotient has failed, the vanishing polynomial is not divisible by Z_H", + ); + // Split quotient into degree-n chunks. + quotient_poly.chunks(degree) + }) + .collect() ); - let all_quotient_chunks = quotient_polys - .into_par_iter() - .flat_map(|mut quotient_poly| { - quotient_poly - .trim_to_len(degree * stark.quotient_degree_factor()) - .expect("Quotient has failed, the vanishing polynomial is not divisible by Z_H"); - // Split quotient into degree-n chunks. - quotient_poly.chunks(degree) - }) - .collect(); let quotient_commitment = timed!( timing, "compute quotient commitment", diff --git a/starky/src/config.rs b/starky/src/config.rs index 500cd957..a593c827 100644 --- a/starky/src/config.rs +++ b/starky/src/config.rs @@ -21,9 +21,9 @@ impl StarkConfig { fri_config: FriConfig { rate_bits: 1, cap_height: 4, - proof_of_work_bits: 10, + proof_of_work_bits: 16, reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), - num_query_rounds: 90, + num_query_rounds: 84, }, } } From 2b298e39eb1a9787ed3512c1b13de513b9f2af5d Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 23 Sep 2022 11:49:13 -0700 Subject: [PATCH 54/97] stack manipulation: allow empty LHS --- evm/src/cpu/kernel/assembler.rs | 7 +++++++ evm/src/cpu/kernel/evm_asm.pest | 5 ++++- evm/src/cpu/kernel/parser.rs | 8 ++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 811c2f0e..5120575d 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -575,8 +575,12 @@ mod tests { let swap1 = get_opcode("SWAP1"); let swap2 = get_opcode("SWAP2"); let swap3 = get_opcode("SWAP3"); + let push_one_byte = get_push_opcode(1); let push_label = get_push_opcode(BYTES_PER_OFFSET); + let kernel = parse_and_assemble(&["%stack () -> (1, 2, 3)"]); + assert_eq!(kernel.code, vec![push_one_byte, 3, push_one_byte, 2, push_one_byte, 1]); + let kernel = parse_and_assemble(&["%stack (a) -> (a)"]); assert_eq!(kernel.code, vec![]); @@ -586,6 +590,9 @@ mod tests { let kernel = parse_and_assemble(&["%stack (a, b, c) -> (b)"]); assert_eq!(kernel.code, vec![pop, swap1, pop]); + let kernel = parse_and_assemble(&["%stack (a, b, c) -> (7, b)"]); + assert_eq!(kernel.code, vec![pop, swap1, pop, push_one_byte, 7]); + let kernel = parse_and_assemble(&["%stack (a, b: 3, c) -> (c)"]); assert_eq!(kernel.code, vec![pop, pop, pop, pop]); diff --git a/evm/src/cpu/kernel/evm_asm.pest b/evm/src/cpu/kernel/evm_asm.pest index 5d670c6c..9b8721f4 100644 --- a/evm/src/cpu/kernel/evm_asm.pest +++ b/evm/src/cpu/kernel/evm_asm.pest @@ -21,16 +21,19 @@ macro_call = ${ "%" ~ !((^"macro" | ^"endmacro" | ^"rep" | ^"endrep" | ^"stack") repeat = { ^"%rep" ~ literal ~ item* ~ ^"%endrep" } paramlist = { "(" ~ identifier ~ ("," ~ identifier)* ~ ")" } macro_arglist = !{ "(" ~ push_target ~ ("," ~ push_target)* ~ ")" } + stack = { ^"%stack" ~ stack_placeholders ~ "->" ~ stack_replacements } -stack_placeholders = { "(" ~ stack_placeholder ~ ("," ~ stack_placeholder)* ~ ")" } +stack_placeholders = { "(" ~ (stack_placeholder ~ ("," ~ stack_placeholder)*)? ~ ")" } stack_placeholder = { stack_block | identifier } stack_block = { identifier ~ ":" ~ literal_decimal } stack_replacements = { "(" ~ stack_replacement ~ ("," ~ stack_replacement)* ~ ")" } stack_replacement = { literal | identifier | constant | macro_label | variable } + global_label_decl = ${ ^"GLOBAL " ~ identifier ~ ":" } local_label_decl = ${ identifier ~ ":" } macro_label_decl = ${ "%%" ~ identifier ~ ":" } macro_label = ${ "%%" ~ identifier } + bytes_item = { ^"BYTES " ~ literal ~ ("," ~ literal)* } push_instruction = { ^"PUSH " ~ push_target } push_target = { literal | identifier | macro_label | variable | constant } diff --git a/evm/src/cpu/kernel/parser.rs b/evm/src/cpu/kernel/parser.rs index 89da016c..ca08cf0d 100644 --- a/evm/src/cpu/kernel/parser.rs +++ b/evm/src/cpu/kernel/parser.rs @@ -99,17 +99,17 @@ fn parse_stack(item: Pair) -> Item { assert_eq!(item.as_rule(), Rule::stack); let mut inner = item.into_inner(); - let params = inner.next().unwrap(); - assert_eq!(params.as_rule(), Rule::stack_placeholders); + let placeholders = inner.next().unwrap(); + assert_eq!(placeholders.as_rule(), Rule::stack_placeholders); let replacements = inner.next().unwrap(); assert_eq!(replacements.as_rule(), Rule::stack_replacements); - let params = params.into_inner().map(parse_stack_placeholder).collect(); + let placeholders = placeholders.into_inner().map(parse_stack_placeholder).collect(); let replacements = replacements .into_inner() .map(parse_stack_replacement) .collect(); - Item::StackManipulation(params, replacements) + Item::StackManipulation(placeholders, replacements) } fn parse_stack_placeholder(target: Pair) -> StackPlaceholder { From 26fcd9eed46263e60cee3c55739b82ee608b8bf3 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 23 Sep 2022 11:49:30 -0700 Subject: [PATCH 55/97] fmt --- evm/src/cpu/kernel/assembler.rs | 5 ++++- evm/src/cpu/kernel/parser.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 5120575d..8937aa2c 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -579,7 +579,10 @@ mod tests { let push_label = get_push_opcode(BYTES_PER_OFFSET); let kernel = parse_and_assemble(&["%stack () -> (1, 2, 3)"]); - assert_eq!(kernel.code, vec![push_one_byte, 3, push_one_byte, 2, push_one_byte, 1]); + assert_eq!( + kernel.code, + vec![push_one_byte, 3, push_one_byte, 2, push_one_byte, 1] + ); let kernel = parse_and_assemble(&["%stack (a) -> (a)"]); assert_eq!(kernel.code, vec![]); diff --git a/evm/src/cpu/kernel/parser.rs b/evm/src/cpu/kernel/parser.rs index ca08cf0d..fd762eae 100644 --- a/evm/src/cpu/kernel/parser.rs +++ b/evm/src/cpu/kernel/parser.rs @@ -104,7 +104,10 @@ fn parse_stack(item: Pair) -> Item { let replacements = inner.next().unwrap(); assert_eq!(replacements.as_rule(), Rule::stack_replacements); - let placeholders = placeholders.into_inner().map(parse_stack_placeholder).collect(); + let placeholders = placeholders + .into_inner() + .map(parse_stack_placeholder) + .collect(); let replacements = replacements .into_inner() .map(parse_stack_replacement) From c7b03cfe9a5d02b0fcf5db237fd8ced65d5cb429 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 22 Sep 2022 20:09:48 -0700 Subject: [PATCH 56/97] More MPT logic --- evm/spec/framework.tex | 2 - evm/spec/tries.tex | 14 +- evm/spec/zkevm.pdf | Bin 146723 -> 150705 bytes evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/core/intrinsic_gas.asm | 3 - evm/src/cpu/kernel/asm/core/process_txn.asm | 2 + evm/src/cpu/kernel/asm/memory/metadata.asm | 10 +- evm/src/cpu/kernel/asm/mpt/load.asm | 178 ++++++++++++++- evm/src/cpu/kernel/asm/mpt/read.asm | 8 +- evm/src/cpu/kernel/asm/mpt/util.asm | 26 +++ evm/src/cpu/kernel/assembler.rs | 52 ++--- evm/src/cpu/kernel/interpreter.rs | 203 +++++++++--------- evm/src/cpu/kernel/tests/mod.rs | 1 + evm/src/cpu/kernel/tests/mpt/load.rs | 67 ++++++ evm/src/cpu/kernel/tests/mpt/mod.rs | 1 + evm/src/generation/mod.rs | 17 +- evm/src/generation/mpt.rs | 40 ++-- evm/src/generation/prover_input.rs | 16 +- evm/src/generation/state.rs | 6 +- evm/tests/transfer_to_new_addr.rs | 12 +- 20 files changed, 475 insertions(+), 184 deletions(-) create mode 100644 evm/src/cpu/kernel/tests/mpt/load.rs create mode 100644 evm/src/cpu/kernel/tests/mpt/mod.rs diff --git a/evm/spec/framework.tex b/evm/spec/framework.tex index 122e1c75..d99a31bb 100644 --- a/evm/spec/framework.tex +++ b/evm/spec/framework.tex @@ -6,8 +6,6 @@ Our zkEVM is designed for efficient verification by STARKs \cite{stark}, particularly by an AIR with degree 3 constraints. In this model, the prover bottleneck is typically constructing Merkle trees, particularly constructing the tree containing low-degree extensions of witness polynomials. -More specifically, we target a constraint system of degree 3. - \subsection{Field selection} \label{field} diff --git a/evm/spec/tries.tex b/evm/spec/tries.tex index d8fc2674..fed78f40 100644 --- a/evm/spec/tries.tex +++ b/evm/spec/tries.tex @@ -1,4 +1,16 @@ \section{Merkle Patricia tries} \label{tries} -TODO +\subsection{Internal memory format} + +Withour our zkEVM's kernel memory, +\begin{enumerate} + \item An empty node is encoded as $(\texttt{MPT\_NODE\_EMPTY})$. + \item A branch node is encoded as $(\texttt{MPT\_NODE\_BRANCH}, c_1, \dots, c_{16}, v)$, where each $c_i$ is a pointer to a child node, and $v$ is a leaf payload. + \item An extension node is encoded as $(\texttt{MPT\_NODE\_EXTENSION}, k, c)$, $k$ is a 2-tuple $(\texttt{packed\_nibbles}, \texttt{num\_nibbles})$, and $c$ is a pointer to a child node. + \item A leaf node is encoded as $(\texttt{MPT\_NODE\_LEAF}, k, v)$, where $k$ is a 2-tuple as above, and $v$ is a leaf payload. + \item A digest node is encoded as $(\texttt{MPT\_NODE\_DIGEST}, d)$, where $d$ is a Keccak256 digest. +\end{enumerate} + + +\subsection{Prover input format} diff --git a/evm/spec/zkevm.pdf b/evm/spec/zkevm.pdf index 8501cfbb9e6ce463f65f0f2369c78cd08d445096..aff46edac15c99d96ac36fb631373c0924c9b040 100644 GIT binary patch delta 69180 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 delta 65360 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

d+WYL;rr?gr_%>G8C%&8cR*U#7^m4%d)5PX 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 Kernel { include_str!("asm/rlp/decode.asm"), include_str!("asm/rlp/read_to_memory.asm"), include_str!("asm/mpt/hash.asm"), + include_str!("asm/mpt/load.asm"), include_str!("asm/mpt/read.asm"), include_str!("asm/mpt/storage_read.asm"), include_str!("asm/mpt/storage_write.asm"), diff --git a/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm b/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm index 3c6f47b6..931a6a7b 100644 --- a/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm +++ b/evm/src/cpu/kernel/asm/core/intrinsic_gas.asm @@ -1,6 +1,3 @@ -// After the transaction data has been parsed into a normalized set of fields -// (see NormalizedTxnField), this routine processes the transaction. - global intrinsic_gas: // stack: retdest // Calculate the number of zero and nonzero bytes in the txn data. diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index b3d92131..ac52c53d 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -1,6 +1,8 @@ // After the transaction data has been parsed into a normalized set of fields // (see NormalizedTxnField), this routine processes the transaction. +// TODO: Save checkpoints in @CTX_METADATA_STATE_TRIE_CHECKPOINT_PTR and @SEGMENT_STORAGE_TRIE_CHECKPOINT_PTRS. + global process_normalized_txn: // stack: (empty) PUSH validate diff --git a/evm/src/cpu/kernel/asm/memory/metadata.asm b/evm/src/cpu/kernel/asm/memory/metadata.asm index 23c45d13..644699e0 100644 --- a/evm/src/cpu/kernel/asm/memory/metadata.asm +++ b/evm/src/cpu/kernel/asm/memory/metadata.asm @@ -12,7 +12,7 @@ // stack: value PUSH $field // stack: offset, value - %mload_kernel(@SEGMENT_GLOBAL_METADATA) + %mstore_kernel(@SEGMENT_GLOBAL_METADATA) // stack: (empty) %endmacro @@ -30,18 +30,18 @@ // stack: value PUSH $field // stack: offset, value - %mload_current(@SEGMENT_CONTEXT_METADATA) + %mstore_current(@SEGMENT_CONTEXT_METADATA) // stack: (empty) %endmacro %macro address - %mload_context_metadata(0) // TODO: Read proper field. + %mload_context_metadata(@CTX_METADATA_ADDRESS) %endmacro %macro sender - %mload_context_metadata(0) // TODO: Read proper field. + %mload_context_metadata(@CTX_METADATA_CALLER) %endmacro %macro callvalue - %mload_context_metadata(0) // TODO: Read proper field. + %mload_context_metadata(@CTX_METADATA_CALL_VALUE) %endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index e58e32dc..208b7221 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -1,12 +1,176 @@ +// TODO: Receipt trie leaves are variable-length, so we need to be careful not +// to permit buffer over-reads. + // Load all partial trie data from prover inputs. -global mpt_load_all: - // First set GLOBAL_METADATA_TRIE_DATA_SIZE = 1. +global load_all_mpts: + // stack: retdest + // First set @GLOBAL_METADATA_TRIE_DATA_SIZE = 1. // We don't want it to start at 0, as we use 0 as a null pointer. PUSH 1 - %mstore(@GLOBAL_METADATA_TRIE_DATA_SIZE) + %set_trie_data_size - TODO + %load_mpt_and_return_root_ptr + %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) + %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) -mpt_load_state: - PROVER_INPUT(mpt::state) - TODO + PROVER_INPUT(mpt) + // stack: num_storage_tries, retdest + DUP1 %mstore_global_metadata(@GLOBAL_METADATA_NUM_STORAGE_TRIES) + // stack: num_storage_tries, retdest + PUSH 0 // i = 0 + // stack: i, num_storage_tries, retdest +storage_trie_loop: + DUP2 DUP2 EQ + // stack: i == num_storage_tries, i, num_storage_tries, retdest + %jumpi(storage_trie_loop_end) + // stack: i, num_storage_tries, retdest + PROVER_INPUT(mpt) + // stack: storage_trie_addr, i, num_storage_tries, retdest + DUP2 + // stack: i, storage_trie_addr, i, num_storage_tries, retdest + %mstore_kernel(@SEGMENT_STORAGE_TRIE_ADDRS) + // stack: i, num_storage_tries, retdest + %load_mpt_and_return_root_ptr + // stack: root_ptr, i, num_storage_tries, retdest + DUP2 + // stack: i, root_ptr, i, num_storage_tries, retdest + %mstore_kernel(@SEGMENT_STORAGE_TRIE_PTRS) + // stack: i, num_storage_tries, retdest + %jump(storage_trie_loop) +storage_trie_loop_end: + // TODO: Hash tries and set @GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE, etc. + // stack: i, num_storage_tries, retdest + %pop2 + // stack: retdest + JUMP + +// Load an MPT from prover inputs. +// Pre stack: retdest +// Post stack: (empty) +load_mpt: + // stack: retdest + PROVER_INPUT(mpt) + // stack: node_type, retdest + DUP1 %append_to_trie_data + // stack: node_type, retdest + + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(load_mpt_empty) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(load_mpt_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(load_mpt_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(load_mpt_leaf) + DUP1 %eq_const(@MPT_NODE_HASH) %jumpi(load_mpt_digest) + PANIC // Invalid node type + +load_mpt_empty: + // stack: node_type, retdest + POP + // stack: retdest + JUMP + +load_mpt_branch: + // stack: node_type, retdest + POP + // stack: retdest + %get_trie_data_size + // stack: ptr_children, retdest + DUP1 %add_const(16) + // stack: ptr_leaf, ptr_children, retdest + %set_trie_data_size + // stack: ptr_children, retdest + %load_leaf_value + + // Save the current trie_data_size (which now points to the end of the leaf) + // for later, then have it point to the start of our 16 child pointers. + %get_trie_data_size + // stack: ptr_end_of_leaf, ptr_children, retdest + SWAP1 + %set_trie_data_size + // stack: ptr_end_of_leaf, retdest + + // Load the 16 children. + %rep 16 + %load_mpt_and_return_root_ptr + // stack: child_ptr, ptr_end_of_leaf, retdest + %append_to_trie_data + // stack: ptr_end_of_leaf, retdest + %endrep + + %set_trie_data_size + // stack: retdest + JUMP + +load_mpt_extension: + // stack: node_type, retdest + POP + // stack: retdest + PROVER_INPUT(mpt) // read num_nibbles + %append_to_trie_data + PROVER_INPUT(mpt) // read packed_nibbles + %append_to_trie_data + // stack: retdest + + %load_mpt_and_return_root_ptr + // stack: child_ptr, retdest + %append_to_trie_data + // stack: retdest + JUMP + +load_mpt_leaf: + // stack: node_type, retdest + POP + // stack: retdest + PROVER_INPUT(mpt) // read num_nibbles + %append_to_trie_data + PROVER_INPUT(mpt) // read packed_nibbles + %append_to_trie_data + // stack: retdest + %load_leaf_value + // stack: retdest + JUMP + +load_mpt_digest: + // stack: node_type, retdest + POP + // stack: retdest + PROVER_INPUT(mpt) // read digest + %append_to_trie_data + // stack: retdest + JUMP + +// Convenience macro to call load_mpt and return where we left off. +%macro load_mpt + PUSH %%after + %jump(load_mpt) +%%after: +%endmacro + +%macro load_mpt_and_return_root_ptr + // stack: (empty) + %get_trie_data_size + // stack: ptr + %load_mpt + // stack: ptr +%endmacro + +// Load a leaf from prover input, and append it to trie data. +%macro load_leaf_value + // stack: (empty) + PROVER_INPUT(mpt) + // stack: leaf_len +%%loop: + DUP1 ISZERO + // stack: leaf_len == 0, leaf_len + %jumpi(%%finish) + // stack: leaf_len + PROVER_INPUT(mpt) + // stack: leaf_part, leaf_len + %append_to_trie_data + // stack: leaf_len + %sub_const(1) + // stack: leaf_len' + %jump(%%loop) +%%finish: + POP + // stack: (empty) +%endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index 1dc497c8..fb61df06 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -16,12 +16,12 @@ global mpt_read: SWAP1 %add_const(1) SWAP1 // stack: node_type, node_payload_ptr, key, nibbles, retdest - DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_read_empty) - DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_read_branch) + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_read_empty) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_read_branch) DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_read_extension) - DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_read_leaf) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_read_leaf) - // There's still the MPT_NODE_DIGEST case, but if we hit a digest node, + // There's still the MPT_NODE_HASH case, but if we hit a digest node, // it means the prover failed to provide necessary Merkle data, so panic. PANIC diff --git a/evm/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm index 96c0ed9e..86208866 100644 --- a/evm/src/cpu/kernel/asm/mpt/util.asm +++ b/evm/src/cpu/kernel/asm/mpt/util.asm @@ -9,3 +9,29 @@ %mstore_kernel(@SEGMENT_TRIE_DATA) // stack: (empty) %endmacro + +%macro get_trie_data_size + // stack: (empty) + %mload_global_metadata(@GLOBAL_METADATA_TRIE_DATA_SIZE) + // stack: trie_data_size +%endmacro + +%macro set_trie_data_size + // stack: trie_data_size + %mstore_global_metadata(@GLOBAL_METADATA_TRIE_DATA_SIZE) + // stack: (empty) +%endmacro + +// Equivalent to: trie_data[trie_data_size++] = value +%macro append_to_trie_data + // stack: value + %get_trie_data_size + // stack: trie_data_size, value + DUP1 + %add_const(1) + // stack: trie_data_size', trie_data_size, value + %set_trie_data_size + // stack: trie_data_size, value + %mstore_trie_data + // stack: (empty) +%endmacro diff --git a/evm/src/cpu/kernel/assembler.rs b/evm/src/cpu/kernel/assembler.rs index 8937aa2c..ede60a29 100644 --- a/evm/src/cpu/kernel/assembler.rs +++ b/evm/src/cpu/kernel/assembler.rs @@ -85,20 +85,20 @@ pub(crate) fn assemble( let mut local_labels = Vec::with_capacity(files.len()); let mut macro_counter = 0; for file in files { - let expanded_file = expand_macros(file.body, ¯os, &mut macro_counter); - let expanded_file = expand_repeats(expanded_file); - let expanded_file = inline_constants(expanded_file, &constants); - let mut expanded_file = expand_stack_manipulation(expanded_file); + let mut file = file.body; + file = expand_macros(file, ¯os, &mut macro_counter); + file = inline_constants(file, &constants); + file = expand_stack_manipulation(file); if optimize { - optimize_asm(&mut expanded_file); + optimize_asm(&mut file); } local_labels.push(find_labels( - &expanded_file, + &file, &mut offset, &mut global_labels, &mut prover_inputs, )); - expanded_files.push(expanded_file); + expanded_files.push(file); } let mut code = vec![]; for (file, locals) in izip!(expanded_files, local_labels) { @@ -146,6 +146,11 @@ fn expand_macros( Item::MacroCall(m, args) => { expanded.extend(expand_macro_call(m, args, macros, macro_counter)); } + Item::Repeat(count, body) => { + for _ in 0..count.as_usize() { + expanded.extend(expand_macros(body.clone(), macros, macro_counter)); + } + } item => { expanded.push(item); } @@ -187,12 +192,10 @@ fn expand_macro_call( Item::MacroCall(name, args) => { let expanded_args = args .iter() - .map(|arg| { - if let PushTarget::MacroVar(var) = arg { - get_arg(var) - } else { - arg.clone() - } + .map(|arg| match arg { + PushTarget::MacroVar(var) => get_arg(var), + PushTarget::MacroLabel(l) => PushTarget::Label(get_actual_label(l)), + _ => arg.clone(), }) .collect(); Item::MacroCall(name.clone(), expanded_args) @@ -220,21 +223,6 @@ fn expand_macro_call( expand_macros(expanded_item, macros, macro_counter) } -fn expand_repeats(body: Vec) -> Vec { - let mut expanded = vec![]; - for item in body { - if let Item::Repeat(count, block) = item { - let reps = count.as_usize(); - for _ in 0..reps { - expanded.extend(block.clone()); - } - } else { - expanded.push(item); - } - } - expanded -} - fn inline_constants(body: Vec, constants: &HashMap) -> Vec { let resolve_const = |c| { *constants @@ -494,7 +482,8 @@ mod tests { #[test] fn macro_with_label() { let files = &[ - "%macro spin %%start: PUSH %%start JUMP %endmacro", + "%macro jump(x) PUSH $x JUMP %endmacro", + "%macro spin %%start: %jump(%%start) %endmacro", "%spin %spin", ]; let kernel = parse_and_assemble_ext(files, HashMap::new(), false); @@ -535,6 +524,11 @@ mod tests { assert_eq!(kernel.code, vec![push1, 5, push1, 6, push1, 7]); } + #[test] + fn pop2_macro() { + parse_and_assemble(&["%macro pop2 %rep 2 pop %endrep %endmacro", "%pop2"]); + } + #[test] #[should_panic] fn macro_with_wrong_vars() { diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 6c2d1e6c..4a1f588b 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, ensure}; use ethereum_types::{BigEndianHash, U256, U512}; use keccak_hash::keccak; use plonky2::field::goldilocks_field::GoldilocksField; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::assembler::Kernel; +use crate::cpu::kernel::global_metadata::GlobalMetadata; use crate::cpu::kernel::txn_fields::NormalizedTxnField; use crate::generation::memory::{MemoryContextState, MemorySegmentState}; use crate::generation::prover_input::ProverInputFn; @@ -60,7 +61,7 @@ pub struct Interpreter<'a> { offset: usize, context: usize, pub(crate) memory: InterpreterMemory, - generation_state: GenerationState, + pub(crate) generation_state: GenerationState, prover_inputs_map: &'a HashMap, pub(crate) halt_offsets: Vec, running: bool, @@ -151,6 +152,14 @@ impl<'a> Interpreter<'a> { &self.memory.context_memory[0].segments[Segment::TxnData as usize].content } + pub(crate) fn get_global_metadata_field(&self, field: GlobalMetadata) -> U256 { + self.memory.context_memory[0].segments[Segment::GlobalMetadata as usize].get(field as usize) + } + + pub(crate) fn get_trie_data(&self) -> &[U256] { + &self.memory.context_memory[0].segments[Segment::TrieData as usize].content + } + pub(crate) fn get_rlp_memory(&self) -> Vec { self.memory.context_memory[self.context].segments[Segment::RlpRaw as usize] .content @@ -192,99 +201,99 @@ impl<'a> Interpreter<'a> { let opcode = self.code().get(self.offset).byte(0); self.incr(1); match opcode { - 0x00 => self.run_stop(), // "STOP", - 0x01 => self.run_add(), // "ADD", - 0x02 => self.run_mul(), // "MUL", - 0x03 => self.run_sub(), // "SUB", - 0x04 => self.run_div(), // "DIV", - 0x05 => todo!(), // "SDIV", - 0x06 => self.run_mod(), // "MOD", - 0x07 => todo!(), // "SMOD", - 0x08 => self.run_addmod(), // "ADDMOD", - 0x09 => self.run_mulmod(), // "MULMOD", - 0x0a => self.run_exp(), // "EXP", - 0x0b => todo!(), // "SIGNEXTEND", - 0x10 => self.run_lt(), // "LT", - 0x11 => self.run_gt(), // "GT", - 0x12 => todo!(), // "SLT", - 0x13 => todo!(), // "SGT", - 0x14 => self.run_eq(), // "EQ", - 0x15 => self.run_iszero(), // "ISZERO", - 0x16 => self.run_and(), // "AND", - 0x17 => self.run_or(), // "OR", - 0x18 => self.run_xor(), // "XOR", - 0x19 => self.run_not(), // "NOT", - 0x1a => self.run_byte(), // "BYTE", - 0x1b => self.run_shl(), // "SHL", - 0x1c => todo!(), // "SHR", - 0x1d => todo!(), // "SAR", - 0x20 => self.run_keccak256(), // "KECCAK256", - 0x30 => todo!(), // "ADDRESS", - 0x31 => todo!(), // "BALANCE", - 0x32 => todo!(), // "ORIGIN", - 0x33 => todo!(), // "CALLER", - 0x34 => todo!(), // "CALLVALUE", - 0x35 => todo!(), // "CALLDATALOAD", - 0x36 => todo!(), // "CALLDATASIZE", - 0x37 => todo!(), // "CALLDATACOPY", - 0x38 => todo!(), // "CODESIZE", - 0x39 => todo!(), // "CODECOPY", - 0x3a => todo!(), // "GASPRICE", - 0x3b => todo!(), // "EXTCODESIZE", - 0x3c => todo!(), // "EXTCODECOPY", - 0x3d => todo!(), // "RETURNDATASIZE", - 0x3e => todo!(), // "RETURNDATACOPY", - 0x3f => todo!(), // "EXTCODEHASH", - 0x40 => todo!(), // "BLOCKHASH", - 0x41 => todo!(), // "COINBASE", - 0x42 => todo!(), // "TIMESTAMP", - 0x43 => todo!(), // "NUMBER", - 0x44 => todo!(), // "DIFFICULTY", - 0x45 => todo!(), // "GASLIMIT", - 0x46 => todo!(), // "CHAINID", - 0x48 => todo!(), // "BASEFEE", - 0x49 => self.run_prover_input()?, // "PROVER_INPUT", - 0x50 => self.run_pop(), // "POP", - 0x51 => self.run_mload(), // "MLOAD", - 0x52 => self.run_mstore(), // "MSTORE", - 0x53 => self.run_mstore8(), // "MSTORE8", - 0x54 => todo!(), // "SLOAD", - 0x55 => todo!(), // "SSTORE", - 0x56 => self.run_jump(), // "JUMP", - 0x57 => self.run_jumpi(), // "JUMPI", - 0x58 => todo!(), // "GETPC", - 0x59 => todo!(), // "MSIZE", - 0x5a => todo!(), // "GAS", - 0x5b => self.run_jumpdest(), // "JUMPDEST", - 0x5c => todo!(), // "GET_STATE_ROOT", - 0x5d => todo!(), // "SET_STATE_ROOT", - 0x5e => todo!(), // "GET_RECEIPT_ROOT", - 0x5f => todo!(), // "SET_RECEIPT_ROOT", - x if (0x60..0x80).contains(&x) => self.run_push(x - 0x5f), // "PUSH" - x if (0x80..0x90).contains(&x) => self.run_dup(x - 0x7f), // "DUP" - x if (0x90..0xa0).contains(&x) => self.run_swap(x - 0x8f), // "SWAP" - 0xa0 => todo!(), // "LOG0", - 0xa1 => todo!(), // "LOG1", - 0xa2 => todo!(), // "LOG2", - 0xa3 => todo!(), // "LOG3", - 0xa4 => todo!(), // "LOG4", - 0xa5 => bail!("Executed PANIC"), // "PANIC", - 0xf0 => todo!(), // "CREATE", - 0xf1 => todo!(), // "CALL", - 0xf2 => todo!(), // "CALLCODE", - 0xf3 => todo!(), // "RETURN", - 0xf4 => todo!(), // "DELEGATECALL", - 0xf5 => todo!(), // "CREATE2", - 0xf6 => self.run_get_context(), // "GET_CONTEXT", - 0xf7 => self.run_set_context(), // "SET_CONTEXT", - 0xf8 => todo!(), // "CONSUME_GAS", - 0xf9 => todo!(), // "EXIT_KERNEL", - 0xfa => todo!(), // "STATICCALL", - 0xfb => self.run_mload_general(), // "MLOAD_GENERAL", - 0xfc => self.run_mstore_general(), // "MSTORE_GENERAL", - 0xfd => todo!(), // "REVERT", - 0xfe => bail!("Executed INVALID"), // "INVALID", - 0xff => todo!(), // "SELFDESTRUCT", + 0x00 => self.run_stop(), // "STOP", + 0x01 => self.run_add(), // "ADD", + 0x02 => self.run_mul(), // "MUL", + 0x03 => self.run_sub(), // "SUB", + 0x04 => self.run_div(), // "DIV", + 0x05 => todo!(), // "SDIV", + 0x06 => self.run_mod(), // "MOD", + 0x07 => todo!(), // "SMOD", + 0x08 => self.run_addmod(), // "ADDMOD", + 0x09 => self.run_mulmod(), // "MULMOD", + 0x0a => self.run_exp(), // "EXP", + 0x0b => todo!(), // "SIGNEXTEND", + 0x10 => self.run_lt(), // "LT", + 0x11 => self.run_gt(), // "GT", + 0x12 => todo!(), // "SLT", + 0x13 => todo!(), // "SGT", + 0x14 => self.run_eq(), // "EQ", + 0x15 => self.run_iszero(), // "ISZERO", + 0x16 => self.run_and(), // "AND", + 0x17 => self.run_or(), // "OR", + 0x18 => self.run_xor(), // "XOR", + 0x19 => self.run_not(), // "NOT", + 0x1a => self.run_byte(), // "BYTE", + 0x1b => self.run_shl(), // "SHL", + 0x1c => todo!(), // "SHR", + 0x1d => todo!(), // "SAR", + 0x20 => self.run_keccak256(), // "KECCAK256", + 0x30 => todo!(), // "ADDRESS", + 0x31 => todo!(), // "BALANCE", + 0x32 => todo!(), // "ORIGIN", + 0x33 => todo!(), // "CALLER", + 0x34 => todo!(), // "CALLVALUE", + 0x35 => todo!(), // "CALLDATALOAD", + 0x36 => todo!(), // "CALLDATASIZE", + 0x37 => todo!(), // "CALLDATACOPY", + 0x38 => todo!(), // "CODESIZE", + 0x39 => todo!(), // "CODECOPY", + 0x3a => todo!(), // "GASPRICE", + 0x3b => todo!(), // "EXTCODESIZE", + 0x3c => todo!(), // "EXTCODECOPY", + 0x3d => todo!(), // "RETURNDATASIZE", + 0x3e => todo!(), // "RETURNDATACOPY", + 0x3f => todo!(), // "EXTCODEHASH", + 0x40 => todo!(), // "BLOCKHASH", + 0x41 => todo!(), // "COINBASE", + 0x42 => todo!(), // "TIMESTAMP", + 0x43 => todo!(), // "NUMBER", + 0x44 => todo!(), // "DIFFICULTY", + 0x45 => todo!(), // "GASLIMIT", + 0x46 => todo!(), // "CHAINID", + 0x48 => todo!(), // "BASEFEE", + 0x49 => self.run_prover_input()?, // "PROVER_INPUT", + 0x50 => self.run_pop(), // "POP", + 0x51 => self.run_mload(), // "MLOAD", + 0x52 => self.run_mstore(), // "MSTORE", + 0x53 => self.run_mstore8(), // "MSTORE8", + 0x54 => todo!(), // "SLOAD", + 0x55 => todo!(), // "SSTORE", + 0x56 => self.run_jump(), // "JUMP", + 0x57 => self.run_jumpi(), // "JUMPI", + 0x58 => todo!(), // "GETPC", + 0x59 => todo!(), // "MSIZE", + 0x5a => todo!(), // "GAS", + 0x5b => self.run_jumpdest(), // "JUMPDEST", + 0x5c => todo!(), // "GET_STATE_ROOT", + 0x5d => todo!(), // "SET_STATE_ROOT", + 0x5e => todo!(), // "GET_RECEIPT_ROOT", + 0x5f => todo!(), // "SET_RECEIPT_ROOT", + x if (0x60..0x80).contains(&x) => self.run_push(x - 0x5f), // "PUSH" + x if (0x80..0x90).contains(&x) => self.run_dup(x - 0x7f), // "DUP" + x if (0x90..0xa0).contains(&x) => self.run_swap(x - 0x8f)?, // "SWAP" + 0xa0 => todo!(), // "LOG0", + 0xa1 => todo!(), // "LOG1", + 0xa2 => todo!(), // "LOG2", + 0xa3 => todo!(), // "LOG3", + 0xa4 => todo!(), // "LOG4", + 0xa5 => bail!("Executed PANIC"), // "PANIC", + 0xf0 => todo!(), // "CREATE", + 0xf1 => todo!(), // "CALL", + 0xf2 => todo!(), // "CALLCODE", + 0xf3 => todo!(), // "RETURN", + 0xf4 => todo!(), // "DELEGATECALL", + 0xf5 => todo!(), // "CREATE2", + 0xf6 => self.run_get_context(), // "GET_CONTEXT", + 0xf7 => self.run_set_context(), // "SET_CONTEXT", + 0xf8 => todo!(), // "CONSUME_GAS", + 0xf9 => todo!(), // "EXIT_KERNEL", + 0xfa => todo!(), // "STATICCALL", + 0xfb => self.run_mload_general(), // "MLOAD_GENERAL", + 0xfc => self.run_mstore_general(), // "MSTORE_GENERAL", + 0xfd => todo!(), // "REVERT", + 0xfe => bail!("Executed INVALID"), // "INVALID", + 0xff => todo!(), // "SELFDESTRUCT", _ => bail!("Unrecognized opcode {}.", opcode), }; Ok(()) @@ -522,9 +531,11 @@ impl<'a> Interpreter<'a> { self.push(self.stack()[self.stack().len() - n as usize]); } - fn run_swap(&mut self, n: u8) { + fn run_swap(&mut self, n: u8) -> anyhow::Result<()> { let len = self.stack().len(); + ensure!(len > n as usize); self.stack_mut().swap(len - 1, len - n as usize - 1); + Ok(()) } fn run_get_context(&mut self) { diff --git a/evm/src/cpu/kernel/tests/mod.rs b/evm/src/cpu/kernel/tests/mod.rs index 925db56f..a9c8c08c 100644 --- a/evm/src/cpu/kernel/tests/mod.rs +++ b/evm/src/cpu/kernel/tests/mod.rs @@ -2,6 +2,7 @@ mod core; mod curve_ops; mod ecrecover; mod exp; +mod mpt; mod packing; mod rlp; mod transaction_parsing; diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs new file mode 100644 index 00000000..871a8682 --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/load.rs @@ -0,0 +1,67 @@ +use anyhow::Result; +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use ethereum_types::U256; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::trie_type::PartialTrieType; +use crate::cpu::kernel::global_metadata::GlobalMetadata; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; +use crate::generation::TrieInputs; + +#[test] +fn load_all_mpts() -> Result<()> { + let nonce = U256::from(1111); + let balance = U256::from(2222); + let storage_root = U256::from(3333); + let code_hash = U256::from(4444); + + let value_rlp = rlp::encode_list(&[nonce, balance, storage_root, code_hash]); + + let trie_inputs = TrieInputs { + state_trie: PartialTrie::Leaf { + nibbles: Nibbles { + count: 2, + packed: 123.into(), + }, + value: value_rlp.to_vec(), + }, + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + + // Contract creation transaction. + let initial_stack = vec![0xdeadbeefu32.into()]; + let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack); + interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![]); + + let type_empty = U256::from(PartialTrieType::Empty as u32); + let type_leaf = U256::from(PartialTrieType::Leaf as u32); + assert_eq!( + interpreter.get_trie_data(), + vec![ + 0.into(), // First address is unused, so 0 can be treated as a null pointer. + type_leaf, + 2.into(), + 123.into(), + nonce, + balance, + storage_root, + code_hash, + type_empty, + type_empty, + ] + ); + + assert_eq!( + interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries), + trie_inputs.storage_tries.len().into() + ); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs new file mode 100644 index 00000000..4295c3bf --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -0,0 +1 @@ +mod load; diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index f91b70d2..511aa009 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -30,6 +30,17 @@ pub(crate) mod state; pub struct GenerationInputs { pub signed_txns: Vec>, + pub tries: TrieInputs, + + /// Mapping between smart contract code hashes and the contract byte code. + /// All account smart contracts that are invoked will have an entry present. + pub contract_code: HashMap>, + + pub block_metadata: BlockMetadata, +} + +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +pub struct TrieInputs { /// A partial version of the state trie prior to these transactions. It should include all nodes /// that will be accessed by these transactions. pub state_trie: PartialTrie, @@ -45,12 +56,6 @@ pub struct GenerationInputs { /// A partial version of each storage trie prior to these transactions. It should include all /// storage tries, and nodes therein, that will be accessed by these transactions. pub storage_tries: Vec<(Address, PartialTrie)>, - - /// Mapping between smart contract code hashes and the contract byte code. - /// All account smart contracts that are invoked will have an entry present. - pub contract_code: HashMap>, - - pub block_metadata: BlockMetadata, } pub(crate) fn generate_traces, const D: usize>( diff --git a/evm/src/generation/mpt.rs b/evm/src/generation/mpt.rs index eba53781..f689b613 100644 --- a/evm/src/generation/mpt.rs +++ b/evm/src/generation/mpt.rs @@ -2,22 +2,34 @@ use eth_trie_utils::partial_trie::PartialTrie; use ethereum_types::U256; use crate::cpu::kernel::constants::trie_type::PartialTrieType; -use crate::generation::GenerationInputs; +use crate::generation::TrieInputs; -pub(crate) fn all_mpt_prover_inputs(inputs: &GenerationInputs) -> Vec { +pub(crate) fn all_mpt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec { + let mut inputs = all_mpt_prover_inputs(trie_inputs); + inputs.reverse(); + inputs +} + +/// Generate prover inputs for the initial MPT data, in the format expected by `mpt/load.asm`. +pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec { let mut prover_inputs = vec![]; - mpt_prover_inputs(&inputs.state_trie, &mut prover_inputs, &|_rlp| vec![]); // TODO + mpt_prover_inputs(&trie_inputs.state_trie, &mut prover_inputs, &|rlp| { + rlp::decode_list(rlp) + }); - mpt_prover_inputs( - &inputs.transactions_trie, - &mut prover_inputs, - &|_rlp| vec![], - ); // TODO + mpt_prover_inputs(&trie_inputs.transactions_trie, &mut prover_inputs, &|rlp| { + rlp::decode_list(rlp) + }); - mpt_prover_inputs(&inputs.receipts_trie, &mut prover_inputs, &|_rlp| vec![]); // TODO + mpt_prover_inputs(&trie_inputs.receipts_trie, &mut prover_inputs, &|_rlp| { + // TODO: Decode receipt RLP. + vec![] + }); - for (_addr, storage_trie) in &inputs.storage_tries { + prover_inputs.push(trie_inputs.storage_tries.len().into()); + for (addr, storage_trie) in &trie_inputs.storage_tries { + prover_inputs.push(addr.0.as_ref().into()); mpt_prover_inputs(storage_trie, &mut prover_inputs, &|leaf_be| { vec![U256::from_big_endian(leaf_be)] }); @@ -45,7 +57,9 @@ pub(crate) fn mpt_prover_inputs( for child in children { mpt_prover_inputs(child, prover_inputs, parse_leaf); } - prover_inputs.extend(parse_leaf(value)); + let leaf = parse_leaf(value); + prover_inputs.push(leaf.len().into()); + prover_inputs.extend(leaf); } PartialTrie::Extension { nibbles, child } => { prover_inputs.push(nibbles.count.into()); @@ -55,7 +69,9 @@ pub(crate) fn mpt_prover_inputs( PartialTrie::Leaf { nibbles, value } => { prover_inputs.push(nibbles.count.into()); prover_inputs.push(nibbles.packed); - prover_inputs.extend(parse_leaf(value)); + let leaf = parse_leaf(value); + prover_inputs.push(leaf.len().into()); + prover_inputs.extend(leaf); } } } diff --git a/evm/src/generation/prover_input.rs b/evm/src/generation/prover_input.rs index 7b49807c..c6051a4d 100644 --- a/evm/src/generation/prover_input.rs +++ b/evm/src/generation/prover_input.rs @@ -25,7 +25,7 @@ impl GenerationState { pub(crate) fn prover_input(&mut self, stack: &[U256], input_fn: &ProverInputFn) -> U256 { match input_fn.0[0].as_str() { "ff" => self.run_ff(stack, input_fn), - "mpt" => self.run_mpt(input_fn), + "mpt" => self.run_mpt(), _ => panic!("Unrecognized prover input function."), } } @@ -39,16 +39,10 @@ impl GenerationState { } /// MPT data. - fn run_mpt(&mut self, input_fn: &ProverInputFn) -> U256 { - let operation = input_fn.0[1].as_str(); - match operation { - "trie_data" => self - .mpt_prover_inputs - .pop() - .unwrap_or_else(|| panic!("Out of MPT data")), - "num_storage_tries" => self.inputs.storage_tries.len().into(), - _ => panic!("Unrecognized MPT operation."), - } + fn run_mpt(&mut self) -> U256 { + self.mpt_prover_inputs + .pop() + .unwrap_or_else(|| panic!("Out of MPT data")) } } diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index 3319a604..b8ae9735 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -6,7 +6,7 @@ use tiny_keccak::keccakf; use crate::cpu::columns::{CpuColumnsView, NUM_CPU_COLUMNS}; use crate::generation::memory::MemoryState; -use crate::generation::mpt::all_mpt_prover_inputs; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; use crate::generation::GenerationInputs; use crate::keccak_memory::keccak_memory_stark::KeccakMemoryOp; use crate::memory::memory_stark::MemoryOp; @@ -17,6 +17,7 @@ use crate::{keccak, logic}; #[derive(Debug)] pub(crate) struct GenerationState { + #[allow(unused)] // TODO: Should be used soon. pub(crate) inputs: GenerationInputs, pub(crate) cpu_rows: Vec<[F; NUM_CPU_COLUMNS]>, pub(crate) current_cpu_row: CpuColumnsView, @@ -35,8 +36,7 @@ pub(crate) struct GenerationState { impl GenerationState { pub(crate) fn new(inputs: GenerationInputs) -> Self { - let mut mpt_prover_inputs = all_mpt_prover_inputs(&inputs); - mpt_prover_inputs.reverse(); + let mpt_prover_inputs = all_mpt_prover_inputs_reversed(&inputs.tries); Self { inputs, diff --git a/evm/tests/transfer_to_new_addr.rs b/evm/tests/transfer_to_new_addr.rs index 82f4938b..1c74366e 100644 --- a/evm/tests/transfer_to_new_addr.rs +++ b/evm/tests/transfer_to_new_addr.rs @@ -7,7 +7,7 @@ use plonky2::plonk::config::PoseidonGoldilocksConfig; use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; -use plonky2_evm::generation::GenerationInputs; +use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::BlockMetadata; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; @@ -29,10 +29,12 @@ fn test_simple_transfer() -> anyhow::Result<()> { let inputs = GenerationInputs { signed_txns: vec![txn.to_vec()], - state_trie: PartialTrie::Empty, - transactions_trie: PartialTrie::Empty, - receipts_trie: PartialTrie::Empty, - storage_tries: vec![], + tries: TrieInputs { + state_trie: PartialTrie::Empty, + transactions_trie: PartialTrie::Empty, + receipts_trie: PartialTrie::Empty, + storage_tries: vec![], + }, contract_code: HashMap::new(), block_metadata, }; From 2e6480a97fb11e29b25f5e808e3d674a10682ed3 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 21 Sep 2022 15:41:14 -0700 Subject: [PATCH 57/97] Fibonacci example --- plonky2/examples/fibonacci.rs | 39 +++++++++++++++++++++++++++++++++++ plonky2/src/plonk/verifier.rs | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 plonky2/examples/fibonacci.rs diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs new file mode 100644 index 00000000..4f968178 --- /dev/null +++ b/plonky2/examples/fibonacci.rs @@ -0,0 +1,39 @@ +use anyhow::Result; +use plonky2::iop::witness::PartialWitness; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::CircuitConfig; +use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; +use plonky2::field::types::Field; +use plonky2::plonk::verifier::verify; + +fn main() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let zero_target = builder.zero(); + let one_target = builder.one(); + let mut prev_target = zero_target; + let mut cur_target = one_target; + for _ in 0..99 { + let temp = builder.add(prev_target, cur_target); + prev_target = cur_target; + cur_target = temp; + } + + let fib_100 = F::from_canonical_u64(3736710860384812976); + let fib_100_target = builder.constant(fib_100); + + builder.connect(fib_100_target, cur_target); + + let data = builder.build::(); + + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) +} diff --git a/plonky2/src/plonk/verifier.rs b/plonky2/src/plonk/verifier.rs index 6a4f3790..5a3f6a94 100644 --- a/plonky2/src/plonk/verifier.rs +++ b/plonky2/src/plonk/verifier.rs @@ -12,7 +12,7 @@ use crate::plonk::validate_shape::validate_proof_with_pis_shape; use crate::plonk::vanishing_poly::eval_vanishing_poly; use crate::plonk::vars::EvaluationVars; -pub(crate) fn verify, C: GenericConfig, const D: usize>( +pub fn verify, C: GenericConfig, const D: usize>( proof_with_pis: ProofWithPublicInputs, verifier_data: &VerifierOnlyCircuitData, common_data: &CommonCircuitData, From 849a89105abe4e52c6b3b1a622c06c8c4fbd5d42 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 21 Sep 2022 15:41:21 -0700 Subject: [PATCH 58/97] fmt --- plonky2/examples/fibonacci.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index 4f968178..90e23295 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -1,9 +1,9 @@ use anyhow::Result; +use plonky2::field::types::Field; use plonky2::iop::witness::PartialWitness; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; -use plonky2::field::types::Field; use plonky2::plonk::verifier::verify; fn main() -> Result<()> { From 556507a9cd0c42e4f641df153f88aba42b75ac3e Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 21 Sep 2022 16:44:35 -0700 Subject: [PATCH 59/97] public input --- plonky2/examples/fibonacci.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index 90e23295..ed4610ea 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -28,6 +28,7 @@ fn main() -> Result<()> { let fib_100 = F::from_canonical_u64(3736710860384812976); let fib_100_target = builder.constant(fib_100); + builder.register_public_input(fib_100_target); builder.connect(fib_100_target, cur_target); @@ -35,5 +36,7 @@ fn main() -> Result<()> { let proof = data.prove(pw)?; + println!("Public inputs: {:?}", proof.clone().public_inputs); + verify(proof, &data.verifier_only, &data.common) } From 9756e06db249b7dac2cc19114c619a623f91210a Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 21 Sep 2022 16:50:14 -0700 Subject: [PATCH 60/97] reformat --- plonky2/examples/fibonacci.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index ed4610ea..27399d66 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -36,7 +36,7 @@ fn main() -> Result<()> { let proof = data.prove(pw)?; - println!("Public inputs: {:?}", proof.clone().public_inputs); + println!("100th Fibonacci number is: {:?}", proof.public_inputs[0]); verify(proof, &data.verifier_only, &data.common) } From 38d6f98f876be5e63ae0e1db8a5c7613e8392145 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Wed, 21 Sep 2022 19:00:01 -0700 Subject: [PATCH 61/97] fixes, and new examples (fibonacci and square root) --- plonky2/examples/fibonacci.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index 27399d66..9a090fc2 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -16,10 +16,8 @@ fn main() -> Result<()> { let pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); - let zero_target = builder.zero(); - let one_target = builder.one(); - let mut prev_target = zero_target; - let mut cur_target = one_target; + let mut prev_target = builder.zero(); + let mut cur_target = builder.one(); for _ in 0..99 { let temp = builder.add(prev_target, cur_target); prev_target = cur_target; @@ -36,7 +34,10 @@ fn main() -> Result<()> { let proof = data.prove(pw)?; - println!("100th Fibonacci number is: {:?}", proof.public_inputs[0]); + println!( + "100th Fibonacci number (mod |F|) is: {}", + proof.public_inputs[0] + ); verify(proof, &data.verifier_only, &data.common) } From 6d81968bbb0b5b2eb66aab96df467461940af3d2 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 22 Sep 2022 06:50:00 -0700 Subject: [PATCH 62/97] use data.verify --- plonky2/examples/fibonacci.rs | 4 +--- plonky2/src/plonk/verifier.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index 9a090fc2..aabed559 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -4,7 +4,6 @@ use plonky2::iop::witness::PartialWitness; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; -use plonky2::plonk::verifier::verify; fn main() -> Result<()> { const D: usize = 2; @@ -31,7 +30,6 @@ fn main() -> Result<()> { builder.connect(fib_100_target, cur_target); let data = builder.build::(); - let proof = data.prove(pw)?; println!( @@ -39,5 +37,5 @@ fn main() -> Result<()> { proof.public_inputs[0] ); - verify(proof, &data.verifier_only, &data.common) + data.verify(proof) } diff --git a/plonky2/src/plonk/verifier.rs b/plonky2/src/plonk/verifier.rs index 5a3f6a94..6a4f3790 100644 --- a/plonky2/src/plonk/verifier.rs +++ b/plonky2/src/plonk/verifier.rs @@ -12,7 +12,7 @@ use crate::plonk::validate_shape::validate_proof_with_pis_shape; use crate::plonk::vanishing_poly::eval_vanishing_poly; use crate::plonk::vars::EvaluationVars; -pub fn verify, C: GenericConfig, const D: usize>( +pub(crate) fn verify, C: GenericConfig, const D: usize>( proof_with_pis: ProofWithPublicInputs, verifier_data: &VerifierOnlyCircuitData, common_data: &CommonCircuitData, From 8bd5f43c45f272aeab3e4a54e72a5f5b03c57451 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 22 Sep 2022 06:50:50 -0700 Subject: [PATCH 63/97] oops, included other examples --- plonky2/examples/factorial.rs | 36 +++++++++++++++++++++++++++++++++ plonky2/examples/square_root.rs | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 plonky2/examples/factorial.rs create mode 100644 plonky2/examples/square_root.rs diff --git a/plonky2/examples/factorial.rs b/plonky2/examples/factorial.rs new file mode 100644 index 00000000..6a2771f2 --- /dev/null +++ b/plonky2/examples/factorial.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use plonky2::field::types::Field; +use plonky2::iop::witness::PartialWitness; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::CircuitConfig; +use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + +fn main() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let mut cur_target = builder.one(); + for i in 2..101 { + let i_target = builder.constant(F::from_canonical_u32(i)); + cur_target = builder.mul(cur_target, i_target); + } + + let fact_100 = F::from_canonical_u64(3822706312645553057); + let fact_100_target = builder.constant(fact_100); + builder.register_public_input(fact_100_target); + + builder.connect(fact_100_target, cur_target); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + println!("100 factorial (mod |F|) is: {}", proof.public_inputs[0]); + + data.verify(proof) +} diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs new file mode 100644 index 00000000..3fce9030 --- /dev/null +++ b/plonky2/examples/square_root.rs @@ -0,0 +1,35 @@ +use anyhow::Result; +use plonky2::field::types::Field; +use plonky2::iop::witness::PartialWitness; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::plonk::circuit_data::CircuitConfig; +use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + +fn main() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let x = F::rand(); + let x_squared = x * x; + let x_target = builder.constant(x); + let x_squared_target = builder.constant(x_squared); + + let x_squared_computed = builder.mul(x_target, x_target); + builder.connect(x_squared_target, x_squared_computed); + + builder.register_public_input(x_target); + + let data = builder.build::(); + let proof = data.prove(pw)?; + + println!("Random field element: {}", x_squared); + println!("Its square root: {}", proof.public_inputs[0]); + + data.verify(proof) +} From 0381641b5c6f85c205441d5590152d9d861f1a15 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 22 Sep 2022 07:03:24 -0700 Subject: [PATCH 64/97] addressed comments --- plonky2/examples/factorial.rs | 3 +-- plonky2/examples/fibonacci.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plonky2/examples/factorial.rs b/plonky2/examples/factorial.rs index 6a2771f2..bc30c7c2 100644 --- a/plonky2/examples/factorial.rs +++ b/plonky2/examples/factorial.rs @@ -20,11 +20,10 @@ fn main() -> Result<()> { let i_target = builder.constant(F::from_canonical_u32(i)); cur_target = builder.mul(cur_target, i_target); } + builder.register_public_input(cur_target); let fact_100 = F::from_canonical_u64(3822706312645553057); let fact_100_target = builder.constant(fact_100); - builder.register_public_input(fact_100_target); - builder.connect(fact_100_target, cur_target); let data = builder.build::(); diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index aabed559..2f8842e4 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -22,11 +22,10 @@ fn main() -> Result<()> { prev_target = cur_target; cur_target = temp; } + builder.register_public_input(cur_target); let fib_100 = F::from_canonical_u64(3736710860384812976); let fib_100_target = builder.constant(fib_100); - builder.register_public_input(fib_100_target); - builder.connect(fib_100_target, cur_target); let data = builder.build::(); From 44a1f4c3288a21b0d8d60d457b2794b2894ce024 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 22 Sep 2022 09:33:42 -0700 Subject: [PATCH 65/97] no need to hard-code! --- plonky2/examples/factorial.rs | 4 ---- plonky2/examples/fibonacci.rs | 5 ----- 2 files changed, 9 deletions(-) diff --git a/plonky2/examples/factorial.rs b/plonky2/examples/factorial.rs index bc30c7c2..c4de7f65 100644 --- a/plonky2/examples/factorial.rs +++ b/plonky2/examples/factorial.rs @@ -22,10 +22,6 @@ fn main() -> Result<()> { } builder.register_public_input(cur_target); - let fact_100 = F::from_canonical_u64(3822706312645553057); - let fact_100_target = builder.constant(fact_100); - builder.connect(fact_100_target, cur_target); - let data = builder.build::(); let proof = data.prove(pw)?; diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index 2f8842e4..bd7e70a0 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use plonky2::field::types::Field; use plonky2::iop::witness::PartialWitness; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; @@ -24,10 +23,6 @@ fn main() -> Result<()> { } builder.register_public_input(cur_target); - let fib_100 = F::from_canonical_u64(3736710860384812976); - let fib_100_target = builder.constant(fib_100); - builder.connect(fib_100_target, cur_target); - let data = builder.build::(); let proof = data.prove(pw)?; From ecdac53960f67ae10e2b464ffd3e675468bb2a5b Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 23 Sep 2022 09:33:14 -0700 Subject: [PATCH 66/97] fixes to fibonacci and factorial --- plonky2/examples/factorial.rs | 12 ++++++++---- plonky2/examples/fibonacci.rs | 18 +++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/plonky2/examples/factorial.rs b/plonky2/examples/factorial.rs index c4de7f65..9f292372 100644 --- a/plonky2/examples/factorial.rs +++ b/plonky2/examples/factorial.rs @@ -1,6 +1,6 @@ use anyhow::Result; use plonky2::field::types::Field; -use plonky2::iop::witness::PartialWitness; +use plonky2::iop::witness::{PartialWitness, Witness}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; @@ -12,20 +12,24 @@ fn main() -> Result<()> { let config = CircuitConfig::standard_recursion_config(); - let pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); - let mut cur_target = builder.one(); + let initial = builder.add_virtual_target(); + let mut cur_target = initial; for i in 2..101 { let i_target = builder.constant(F::from_canonical_u32(i)); cur_target = builder.mul(cur_target, i_target); } + builder.register_public_input(initial); builder.register_public_input(cur_target); + let mut pw = PartialWitness::new(); + pw.set_target(initial, F::ONE); + let data = builder.build::(); let proof = data.prove(pw)?; - println!("100 factorial (mod |F|) is: {}", proof.public_inputs[0]); + println!("100 factorial (mod |F|) is: {}", proof.public_inputs[1]); data.verify(proof) } diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index bd7e70a0..d7a47c02 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -1,5 +1,6 @@ use anyhow::Result; -use plonky2::iop::witness::PartialWitness; +use plonky2::field::types::Field; +use plonky2::iop::witness::{PartialWitness, Witness}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; @@ -11,24 +12,31 @@ fn main() -> Result<()> { let config = CircuitConfig::standard_recursion_config(); - let pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); - let mut prev_target = builder.zero(); - let mut cur_target = builder.one(); + let initial_a = builder.add_virtual_target(); + let initial_b = builder.add_virtual_target(); + let mut prev_target = initial_a; + let mut cur_target = initial_b; for _ in 0..99 { let temp = builder.add(prev_target, cur_target); prev_target = cur_target; cur_target = temp; } + builder.register_public_input(initial_a); + builder.register_public_input(initial_b); builder.register_public_input(cur_target); + let mut pw = PartialWitness::new(); + pw.set_target(initial_a, F::ZERO); + pw.set_target(initial_b, F::ONE); + let data = builder.build::(); let proof = data.prove(pw)?; println!( "100th Fibonacci number (mod |F|) is: {}", - proof.public_inputs[0] + proof.public_inputs[2] ); data.verify(proof) From b271a71a74dd675d9cf42ad7c1b5add331ff6eb9 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 23 Sep 2022 11:00:22 -0700 Subject: [PATCH 67/97] square root example: use generator --- plonky2/examples/square_root.rs | 53 ++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 3fce9030..07f1d230 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -1,9 +1,36 @@ +use std::marker::PhantomData; + use anyhow::Result; use plonky2::field::types::Field; -use plonky2::iop::witness::PartialWitness; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::generator::{SimpleGenerator, GeneratedValues}; +use plonky2::iop::target::Target; +use plonky2::iop::witness::{PartialWitness, PartitionWitness, Witness}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; +use plonky2_field::extension::Extendable; + +#[derive(Debug)] +struct SquareGenerator, const D: usize> { + x: Target, + x_squared: Target, + _phantom: PhantomData, +} + +impl, const D: usize> SimpleGenerator for SquareGenerator +{ + fn dependencies(&self) -> Vec { + vec![self.x] + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let x = witness.get_target(self.x); + let x_squared = x * x; + + out_buffer.set_target(self.x_squared, x_squared); + } +} fn main() -> Result<()> { const D: usize = 2; @@ -12,23 +39,29 @@ fn main() -> Result<()> { let config = CircuitConfig::standard_recursion_config(); - let pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); - let x = F::rand(); - let x_squared = x * x; - let x_target = builder.constant(x); - let x_squared_target = builder.constant(x_squared); + let x = builder.add_virtual_target(); + let x_squared = builder.mul(x, x); - let x_squared_computed = builder.mul(x_target, x_target); - builder.connect(x_squared_target, x_squared_computed); + builder.register_public_input(x); + builder.register_public_input(x_squared); - builder.register_public_input(x_target); + builder.add_simple_generator(SquareGenerator:: { + x, + x_squared, + _phantom: PhantomData, + }); + + let x_value = F::rand(); + + let mut pw = PartialWitness::new(); + pw.set_target(x, x_value); let data = builder.build::(); let proof = data.prove(pw)?; - println!("Random field element: {}", x_squared); + println!("Random field element: {}", proof.public_inputs[1]); println!("Its square root: {}", proof.public_inputs[0]); data.verify(proof) From b21883c32152c844472e53d6412d3989c89a31db Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 23 Sep 2022 11:05:09 -0700 Subject: [PATCH 68/97] fmt --- plonky2/examples/square_root.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 07f1d230..86d53271 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use anyhow::Result; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; -use plonky2::iop::generator::{SimpleGenerator, GeneratedValues}; +use plonky2::iop::generator::{GeneratedValues, SimpleGenerator}; use plonky2::iop::target::Target; use plonky2::iop::witness::{PartialWitness, PartitionWitness, Witness}; use plonky2::plonk::circuit_builder::CircuitBuilder; @@ -18,8 +18,7 @@ struct SquareGenerator, const D: usize> { _phantom: PhantomData, } -impl, const D: usize> SimpleGenerator for SquareGenerator -{ +impl, const D: usize> SimpleGenerator for SquareGenerator { fn dependencies(&self) -> Vec { vec![self.x] } From 843baf1aa0c2618c7048413c30867f82675549c5 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 23 Sep 2022 15:09:56 -0700 Subject: [PATCH 69/97] documentation --- plonky2/examples/factorial.rs | 7 ++++++- plonky2/examples/fibonacci.rs | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/plonky2/examples/factorial.rs b/plonky2/examples/factorial.rs index 9f292372..518532c3 100644 --- a/plonky2/examples/factorial.rs +++ b/plonky2/examples/factorial.rs @@ -5,21 +5,26 @@ use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; +/// An example of using Plonky2 to prove a statement of the form +/// "I know n * (n + 1) * ... * (n + 99)". +/// When n == 1, this is proving knowledge of 100!. fn main() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = >::F; let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::new(config); + // The arithmetic circuit. let initial = builder.add_virtual_target(); let mut cur_target = initial; for i in 2..101 { let i_target = builder.constant(F::from_canonical_u32(i)); cur_target = builder.mul(cur_target, i_target); } + + // Public inputs are the initial value (provided below) and the result (which is generated). builder.register_public_input(initial); builder.register_public_input(cur_target); diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index d7a47c02..80bf8d8f 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -5,15 +5,18 @@ use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; +/// An example of using Plonky2 to prove a statement of the form +/// "I know the 100th element of the Fibonacci sequence, starting with constants a and b." +/// When a == 0 and b == 1, this is proving knowledge of the 100th (standard) Fibonacci number. fn main() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = >::F; let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::new(config); + // The arithmetic circuit. let initial_a = builder.add_virtual_target(); let initial_b = builder.add_virtual_target(); let mut prev_target = initial_a; @@ -23,10 +26,13 @@ fn main() -> Result<()> { prev_target = cur_target; cur_target = temp; } + + // Public inputs are the two initial values (provided below) and the result (which is generated). builder.register_public_input(initial_a); builder.register_public_input(initial_b); builder.register_public_input(cur_target); + // Provide initial values. let mut pw = PartialWitness::new(); pw.set_target(initial_a, F::ZERO); pw.set_target(initial_b, F::ONE); From bda96e84ee599355b239170341ff5c1e97d6f002 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 23 Sep 2022 15:10:14 -0700 Subject: [PATCH 70/97] working on SquareRootGenerator instead of SquareGenerator --- plonky2/examples/square_root.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 86d53271..0aab8283 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -12,25 +12,27 @@ use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use plonky2_field::extension::Extendable; #[derive(Debug)] -struct SquareGenerator, const D: usize> { +struct SquareRootGenerator, const D: usize> { x: Target, x_squared: Target, _phantom: PhantomData, } -impl, const D: usize> SimpleGenerator for SquareGenerator { +impl, const D: usize> SimpleGenerator for SquareRootGenerator { fn dependencies(&self) -> Vec { - vec![self.x] + vec![self.x_squared] } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let x = witness.get_target(self.x); - let x_squared = x * x; + let x_squared = witness.get_target(self.x); + let s = F::from_canonical_u32(4294967295); + let x = - out_buffer.set_target(self.x_squared, x_squared); + out_buffer.set_target(self.x, x); } } +/// An example of using Plonky2 to prove a statement of the form fn main() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; @@ -46,11 +48,11 @@ fn main() -> Result<()> { builder.register_public_input(x); builder.register_public_input(x_squared); - builder.add_simple_generator(SquareGenerator:: { - x, - x_squared, - _phantom: PhantomData, - }); + // builder.add_simple_generator(SquareGenerator:: { + // x, + // x_squared, + // _phantom: PhantomData, + // }); let x_value = F::rand(); From 880bc87bb13d1efa19506566b88438b7ce3e677b Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 10:43:18 -0700 Subject: [PATCH 71/97] sqrt --- field/src/goldilocks_field.rs | 55 +++++++++++++++++++++++++++++++++ plonky2/examples/square_root.rs | 30 +++++++++++------- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs index c1bb60b0..21235e63 100644 --- a/field/src/goldilocks_field.rs +++ b/field/src/goldilocks_field.rs @@ -280,6 +280,61 @@ impl DivAssign for GoldilocksField { } } +impl GoldilocksField { + pub fn is_quadratic_residue(&self) -> bool { + if self.is_zero() { + return true; + } + // This is based on Euler's criterion. + let power = Self::NEG_ONE.to_canonical_biguint() / 2u8; + let exp = self.exp_biguint(&power); + if exp == Self::ONE { + return true; + } + if exp == Self::NEG_ONE { + return false; + } + panic!("Unreachable") + } + + pub fn sqrt(&self) -> Option { + if self.is_zero() { + Some(*self) + } else if self.is_quadratic_residue() { + let t = (Self::order() - BigUint::from(1u32)) / (BigUint::from(2u32).pow(Self::TWO_ADICITY as u32)); + let mut z = Self::POWER_OF_TWO_GENERATOR.exp_biguint(&t); + let mut w = self.exp_biguint(&((t - BigUint::from(1u32)) / BigUint::from(2u32))); + let mut x = w * *self; + let mut b = x * w; + + let mut v = Self::TWO_ADICITY as usize; + + while !b.is_one() { + let mut k = 0usize; + let mut b2k = b; + while !b2k.is_one() { + b2k = b2k * b2k; + k += 1; + } + let j = v - k - 1; + w = z; + for _ in 0..j { + w = w * w; + } + + z = w * w; + b = b * z; + x = x * w; + v = k; + } + Some(x) + } else { + None + } + } + +} + /// Fast addition modulo ORDER for x86-64. /// This function is marked unsafe for the following reasons: /// - It is only correct if x + y < 2**64 + ORDER = 0x1ffffffff00000001. diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 0aab8283..5cbdf02e 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -10,6 +10,7 @@ use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use plonky2_field::extension::Extendable; +use plonky2_field::goldilocks_field::GoldilocksField; #[derive(Debug)] struct SquareRootGenerator, const D: usize> { @@ -18,15 +19,14 @@ struct SquareRootGenerator, const D: usize> { _phantom: PhantomData, } -impl, const D: usize> SimpleGenerator for SquareRootGenerator { +impl SimpleGenerator for SquareRootGenerator { fn dependencies(&self) -> Vec { vec![self.x_squared] } - fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { - let x_squared = witness.get_target(self.x); - let s = F::from_canonical_u32(4294967295); - let x = + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let x_squared = witness.get_target(self.x_squared); + let x = x_squared.sqrt().unwrap(); out_buffer.set_target(self.x, x); } @@ -48,16 +48,22 @@ fn main() -> Result<()> { builder.register_public_input(x); builder.register_public_input(x_squared); - // builder.add_simple_generator(SquareGenerator:: { - // x, - // x_squared, - // _phantom: PhantomData, - // }); + builder.add_simple_generator(SquareRootGenerator:: { + x, + x_squared, + _phantom: PhantomData, + }); - let x_value = F::rand(); + let x_squared_value = { + let mut val = F::rand(); + while !val.is_quadratic_residue() { + val = F::rand(); + } + val + }; let mut pw = PartialWitness::new(); - pw.set_target(x, x_value); + pw.set_target(x_squared, x_squared_value); let data = builder.build::(); let proof = data.prove(pw)?; From d239d3ffb56418f5dafecd901dbd75f2e9afb75c Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 10:44:28 -0700 Subject: [PATCH 72/97] fix --- plonky2/examples/square_root.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 5cbdf02e..a19130fa 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -68,8 +68,12 @@ fn main() -> Result<()> { let data = builder.build::(); let proof = data.prove(pw)?; - println!("Random field element: {}", proof.public_inputs[1]); - println!("Its square root: {}", proof.public_inputs[0]); + let x_actual = proof.public_inputs[0]; + let x_squared_actual = proof.public_inputs[1]; + println!("Random field element: {}", x_squared_actual); + println!("Its square root: {}", x_actual); + + assert!(x_actual * x_actual == x_squared_actual); data.verify(proof) } From 59acd9436cd5981fb324b0c26171fc38d46569dd Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 10:44:51 -0700 Subject: [PATCH 73/97] fmt --- field/src/goldilocks_field.rs | 4 ++-- plonky2/examples/square_root.rs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs index 21235e63..ef9e2c2b 100644 --- a/field/src/goldilocks_field.rs +++ b/field/src/goldilocks_field.rs @@ -301,7 +301,8 @@ impl GoldilocksField { if self.is_zero() { Some(*self) } else if self.is_quadratic_residue() { - let t = (Self::order() - BigUint::from(1u32)) / (BigUint::from(2u32).pow(Self::TWO_ADICITY as u32)); + let t = (Self::order() - BigUint::from(1u32)) + / (BigUint::from(2u32).pow(Self::TWO_ADICITY as u32)); let mut z = Self::POWER_OF_TWO_GENERATOR.exp_biguint(&t); let mut w = self.exp_biguint(&((t - BigUint::from(1u32)) / BigUint::from(2u32))); let mut x = w * *self; @@ -332,7 +333,6 @@ impl GoldilocksField { None } } - } /// Fast addition modulo ORDER for x86-64. diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index a19130fa..4e1a37d7 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -24,7 +24,11 @@ impl SimpleGenerator for SquareRootGenerator, out_buffer: &mut GeneratedValues) { + fn run_once( + &self, + witness: &PartitionWitness, + out_buffer: &mut GeneratedValues, + ) { let x_squared = witness.get_target(self.x_squared); let x = x_squared.sqrt().unwrap(); From 4668e8c5f8dac8bd6a4d5f2594cb9ccdc3252a62 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 10:45:20 -0700 Subject: [PATCH 74/97] clippy --- field/src/goldilocks_field.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs index ef9e2c2b..65315a01 100644 --- a/field/src/goldilocks_field.rs +++ b/field/src/goldilocks_field.rs @@ -324,8 +324,8 @@ impl GoldilocksField { } z = w * w; - b = b * z; - x = x * w; + b *= z; + x *= w; v = k; } Some(x) From 3bc1e65a7abfa2dd3c54c4048698b562c7e4746f Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 11:11:41 -0700 Subject: [PATCH 75/97] fix --- field/src/goldilocks_field.rs | 2 +- plonky2/examples/square_root.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs index 65315a01..537b1f11 100644 --- a/field/src/goldilocks_field.rs +++ b/field/src/goldilocks_field.rs @@ -303,7 +303,7 @@ impl GoldilocksField { } else if self.is_quadratic_residue() { let t = (Self::order() - BigUint::from(1u32)) / (BigUint::from(2u32).pow(Self::TWO_ADICITY as u32)); - let mut z = Self::POWER_OF_TWO_GENERATOR.exp_biguint(&t); + let mut z = Self::MULTIPLICATIVE_GROUP_GENERATOR.exp_biguint(&t); let mut w = self.exp_biguint(&((t - BigUint::from(1u32)) / BigUint::from(2u32))); let mut x = w * *self; let mut b = x * w; diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 4e1a37d7..b20bf8e6 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -30,7 +30,9 @@ impl SimpleGenerator for SquareRootGenerator, ) { let x_squared = witness.get_target(self.x_squared); + dbg!(x_squared); let x = x_squared.sqrt().unwrap(); + dbg!(x); out_buffer.set_target(self.x, x); } From a053372176cc83c804fa9af6b44cc189808a7ecd Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 11:19:09 -0700 Subject: [PATCH 76/97] cleanup and documentation --- field/src/goldilocks_field.rs | 2 +- plonky2/examples/square_root.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs index 537b1f11..28d17a05 100644 --- a/field/src/goldilocks_field.rs +++ b/field/src/goldilocks_field.rs @@ -303,7 +303,7 @@ impl GoldilocksField { } else if self.is_quadratic_residue() { let t = (Self::order() - BigUint::from(1u32)) / (BigUint::from(2u32).pow(Self::TWO_ADICITY as u32)); - let mut z = Self::MULTIPLICATIVE_GROUP_GENERATOR.exp_biguint(&t); + let mut z = Self::POWER_OF_TWO_GENERATOR; let mut w = self.exp_biguint(&((t - BigUint::from(1u32)) / BigUint::from(2u32))); let mut x = w * *self; let mut b = x * w; diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index b20bf8e6..230b33aa 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -19,6 +19,8 @@ struct SquareRootGenerator, const D: usize> { _phantom: PhantomData, } +// We implement specifically for the Goldilocks field because it's currently the only field with +// the sqrt() function written. impl SimpleGenerator for SquareRootGenerator { fn dependencies(&self) -> Vec { vec![self.x_squared] @@ -30,15 +32,14 @@ impl SimpleGenerator for SquareRootGenerator, ) { let x_squared = witness.get_target(self.x_squared); - dbg!(x_squared); let x = x_squared.sqrt().unwrap(); - dbg!(x); out_buffer.set_target(self.x, x); } } /// An example of using Plonky2 to prove a statement of the form +/// "I know the square root of this field element." fn main() -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; From 33d97eff1cac1e6286662a7983c7f77be96e532c Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 11:31:11 -0700 Subject: [PATCH 77/97] moved sqrt to PrimeField --- field/src/goldilocks_field.rs | 55 --------------------------------- field/src/types.rs | 53 +++++++++++++++++++++++++++++++ plonky2/examples/square_root.rs | 15 +++------ 3 files changed, 58 insertions(+), 65 deletions(-) diff --git a/field/src/goldilocks_field.rs b/field/src/goldilocks_field.rs index 28d17a05..c1bb60b0 100644 --- a/field/src/goldilocks_field.rs +++ b/field/src/goldilocks_field.rs @@ -280,61 +280,6 @@ impl DivAssign for GoldilocksField { } } -impl GoldilocksField { - pub fn is_quadratic_residue(&self) -> bool { - if self.is_zero() { - return true; - } - // This is based on Euler's criterion. - let power = Self::NEG_ONE.to_canonical_biguint() / 2u8; - let exp = self.exp_biguint(&power); - if exp == Self::ONE { - return true; - } - if exp == Self::NEG_ONE { - return false; - } - panic!("Unreachable") - } - - pub fn sqrt(&self) -> Option { - if self.is_zero() { - Some(*self) - } else if self.is_quadratic_residue() { - let t = (Self::order() - BigUint::from(1u32)) - / (BigUint::from(2u32).pow(Self::TWO_ADICITY as u32)); - let mut z = Self::POWER_OF_TWO_GENERATOR; - let mut w = self.exp_biguint(&((t - BigUint::from(1u32)) / BigUint::from(2u32))); - let mut x = w * *self; - let mut b = x * w; - - let mut v = Self::TWO_ADICITY as usize; - - while !b.is_one() { - let mut k = 0usize; - let mut b2k = b; - while !b2k.is_one() { - b2k = b2k * b2k; - k += 1; - } - let j = v - k - 1; - w = z; - for _ in 0..j { - w = w * w; - } - - z = w * w; - b *= z; - x *= w; - v = k; - } - Some(x) - } else { - None - } - } -} - /// Fast addition modulo ORDER for x86-64. /// This function is marked unsafe for the following reasons: /// - It is only correct if x + y < 2**64 + ORDER = 0x1ffffffff00000001. diff --git a/field/src/types.rs b/field/src/types.rs index ac94bcfa..7130b7f5 100644 --- a/field/src/types.rs +++ b/field/src/types.rs @@ -427,6 +427,59 @@ pub trait Field: pub trait PrimeField: Field { fn to_canonical_biguint(&self) -> BigUint; + + fn is_quadratic_residue(&self) -> bool { + if self.is_zero() { + return true; + } + // This is based on Euler's criterion. + let power = Self::NEG_ONE.to_canonical_biguint() / 2u8; + let exp = self.exp_biguint(&power); + if exp == Self::ONE { + return true; + } + if exp == Self::NEG_ONE { + return false; + } + panic!("Unreachable") + } + + fn sqrt(&self) -> Option { + if self.is_zero() { + Some(*self) + } else if self.is_quadratic_residue() { + let t = (Self::order() - BigUint::from(1u32)) + / (BigUint::from(2u32).pow(Self::TWO_ADICITY as u32)); + let mut z = Self::POWER_OF_TWO_GENERATOR; + let mut w = self.exp_biguint(&((t - BigUint::from(1u32)) / BigUint::from(2u32))); + let mut x = w * *self; + let mut b = x * w; + + let mut v = Self::TWO_ADICITY as usize; + + while !b.is_one() { + let mut k = 0usize; + let mut b2k = b; + while !b2k.is_one() { + b2k = b2k * b2k; + k += 1; + } + let j = v - k - 1; + w = z; + for _ in 0..j { + w = w * w; + } + + z = w * w; + b *= z; + x *= w; + v = k; + } + Some(x) + } else { + None + } + } } /// A finite field of order less than 2^64. diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 230b33aa..d8b3bd69 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use anyhow::Result; -use plonky2::field::types::Field; +use plonky2::field::types::{Field, PrimeField}; use plonky2::hash::hash_types::RichField; use plonky2::iop::generator::{GeneratedValues, SimpleGenerator}; use plonky2::iop::target::Target; @@ -10,7 +10,6 @@ use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use plonky2_field::extension::Extendable; -use plonky2_field::goldilocks_field::GoldilocksField; #[derive(Debug)] struct SquareRootGenerator, const D: usize> { @@ -19,18 +18,14 @@ struct SquareRootGenerator, const D: usize> { _phantom: PhantomData, } -// We implement specifically for the Goldilocks field because it's currently the only field with -// the sqrt() function written. -impl SimpleGenerator for SquareRootGenerator { +impl, const D: usize> SimpleGenerator + for SquareRootGenerator +{ fn dependencies(&self) -> Vec { vec![self.x_squared] } - fn run_once( - &self, - witness: &PartitionWitness, - out_buffer: &mut GeneratedValues, - ) { + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { let x_squared = witness.get_target(self.x_squared); let x = x_squared.sqrt().unwrap(); From 20053ac4c70bf52f926f3628958b6144219fc3f0 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 15:58:28 -0700 Subject: [PATCH 78/97] documentation --- plonky2/examples/square_root.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index d8b3bd69..98ddc4e9 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -11,6 +11,8 @@ use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use plonky2_field::extension::Extendable; +/// A generator used by the prover to calculate the square root (`x`) of a given value +/// (`x_squared`), outside of the circuit, in order to supply it as an additional public input. #[derive(Debug)] struct SquareRootGenerator, const D: usize> { x: Target, @@ -56,6 +58,7 @@ fn main() -> Result<()> { _phantom: PhantomData, }); + // Randomly generate the value of x^2: any quadratic residue in the field works. let x_squared_value = { let mut val = F::rand(); while !val.is_quadratic_residue() { From 7d7269b26dae9736fd3a69b1719e69e9dc3b0973 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 16:17:04 -0700 Subject: [PATCH 79/97] nit --- plonky2/examples/square_root.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 98ddc4e9..81216905 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -47,7 +47,7 @@ fn main() -> Result<()> { let mut builder = CircuitBuilder::::new(config); let x = builder.add_virtual_target(); - let x_squared = builder.mul(x, x); + let x_squared = builder.square(x); builder.register_public_input(x); builder.register_public_input(x_squared); From 0e48d581472d720fef9c53fbf4243634330e23b9 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 24 Sep 2022 22:23:24 -0700 Subject: [PATCH 80/97] Finish MPT read logic --- evm/src/cpu/kernel/asm/mpt/read.asm | 79 ++++++++++++++++------------ evm/src/cpu/kernel/asm/mpt/util.asm | 37 +++++++++++++ evm/src/cpu/kernel/interpreter.rs | 7 +-- evm/src/cpu/kernel/tests/mpt/load.rs | 5 +- evm/src/cpu/kernel/tests/mpt/mod.rs | 1 + evm/src/cpu/kernel/tests/mpt/read.rs | 57 ++++++++++++++++++++ 6 files changed, 147 insertions(+), 39 deletions(-) create mode 100644 evm/src/cpu/kernel/tests/mpt/read.rs diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index fb61df06..aaf39fcc 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -3,18 +3,18 @@ // Arguments: // - the virtual address of the trie to search in // - the key, as a U256 -// - the number of nibbles in the key +// - the number of nibbles in the key (should start at 64) // // This function returns a pointer to the leaf, or 0 if the key is not found. global mpt_read: - // stack: node_ptr, key, nibbles, retdest + // stack: node_ptr, num_nibbles, key, retdest DUP1 %mload_trie_data - // stack: node_type, node_ptr, key, nibbles, retdest + // stack: node_type, node_ptr, num_nibbles, key, retdest // Increment node_ptr, so it points to the node payload instead of its type. SWAP1 %add_const(1) SWAP1 - // stack: node_type, node_payload_ptr, key, nibbles, retdest + // stack: node_type, node_payload_ptr, num_nibbles, key, retdest DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_read_empty) DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_read_branch) @@ -27,43 +27,34 @@ global mpt_read: mpt_read_empty: // Return 0 to indicate that the value was not found. - %stack (node_type, node_payload_ptr, key, nibbles, retdest) + %stack (node_type, node_payload_ptr, num_nibbles, key, retdest) -> (retdest, 0) JUMP mpt_read_branch: - // stack: node_type, node_payload_ptr, key, nibbles, retdest + // stack: node_type, node_payload_ptr, num_nibbles, key, retdest POP - // stack: node_payload_ptr, key, nibbles, retdest - DUP3 // nibbles + // stack: node_payload_ptr, num_nibbles, key, retdest + DUP2 // num_nibbles ISZERO - // stack: nibbles == 0, node_payload_ptr, key, nibbles, retdest + // stack: num_nibbles == 0, node_payload_ptr, num_nibbles, key, retdest %jumpi(mpt_read_branch_end_of_key) - // stack: node_payload_ptr, key, nibbles, retdest // We have not reached the end of the key, so we descend to one of our children. - // Decrement nibbles, then compute current_nibble = (key >> (nibbles * 4)) & 0xF. - SWAP2 - %sub_const(1) - // stack: nibbles, key, node_payload_ptr, retdest - DUP2 DUP2 - // stack: nibbles, key, nibbles, key, node_payload_ptr, retdest - %mul_const(4) - // stack: nibbles * 4, key, nibbles, key, node_payload_ptr, retdest - SHR - // stack: key >> (nibbles * 4), nibbles, key, node_payload_ptr, retdest - %and_const(0xF) - // stack: current_nibble, nibbles, key, node_payload_ptr, retdest - %stack (current_nibble, nibbles, key, node_payload_ptr, retdest) - -> (current_nibble, node_payload_ptr, key, nibbles, retdest) - // child_ptr = load(node_payload_ptr + current_nibble) - ADD - %mload_trie_data - // stack: child_ptr, key, nibbles, retdest + // stack: node_payload_ptr, num_nibbles, key, retdest + %stack (node_payload_ptr, num_nibbles, key) + -> (num_nibbles, key, node_payload_ptr) + // stack: num_nibbles, key, node_payload_ptr, retdest + %split_first_nibble + %stack (first_nibble, num_nibbles, key, node_payload_ptr) + -> (node_payload_ptr, first_nibble, num_nibbles, key) + // child_ptr = load(node_payload_ptr + first_nibble) + ADD %mload_trie_data + // stack: child_ptr, num_nibbles, key, retdest %jump(mpt_read) // recurse mpt_read_branch_end_of_key: - %stack (node_payload_ptr, key, nibbles, retdest) -> (node_payload_ptr, retdest) + %stack (node_payload_ptr, num_nibbles, key, retdest) -> (node_payload_ptr, retdest) // stack: node_payload_ptr, retdest %add_const(16) // skip over the 16 child nodes // stack: leaf_ptr, retdest @@ -71,11 +62,33 @@ mpt_read_branch_end_of_key: JUMP mpt_read_extension: - // stack: node_type, node_payload_ptr, key, nibbles, retdest + // stack: node_type, node_payload_ptr, num_nibbles, key, retdest POP - // stack: node_payload_ptr, key, nibbles, retdest + // stack: node_payload_ptr, num_nibbles, key, retdest + // TODO mpt_read_leaf: - // stack: node_type, node_payload_ptr, key, nibbles, retdest + // stack: node_type, node_payload_ptr, key, num_nibbles, retdest POP - // stack: node_payload_ptr, key, nibbles, retdest + // stack: node_payload_ptr, key, num_nibbles, retdest + DUP1 %mload_trie_data + // stack: node_num_nibbles, node_payload_ptr, key, num_nibbles, retdest + DUP2 %add_const(1) %mload_trie_data + // stack: node_key, node_num_nibbles, node_payload_ptr, key, num_nibbles, retdest + SWAP4 + // stack: num_nibbles, node_num_nibbles, node_payload_ptr, key, node_key, retdest + EQ + %stack (num_nibbles_match, node_payload_ptr, key, node_key) + -> (key, node_key, num_nibbles_match, node_payload_ptr) + EQ + AND + // stack: keys_match && num_nibbles_match, node_payload_ptr, retdest + %jumpi(mpt_read_leaf_found) + %stack (node_payload_ptr, retdest) -> (retdest, 0) + JUMP +mpt_read_leaf_found: + // stack: node_payload_ptr, retdest + %add_const(2) // The leaf data is located after num_nibbles and the key. + // stack: value_ptr, retdest + SWAP1 + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm index 86208866..0e0006d3 100644 --- a/evm/src/cpu/kernel/asm/mpt/util.asm +++ b/evm/src/cpu/kernel/asm/mpt/util.asm @@ -35,3 +35,40 @@ %mstore_trie_data // stack: (empty) %endmacro + +// Split off the first nibble from a key part. Roughly equivalent to +// def split_first_nibble(num_nibbles, key): +// num_nibbles -= 1 +// num_nibbles_x4 = num_nibbles * 4 +// first_nibble = (key >> num_nibbles_x4) & 0xF +// key -= (first_nibble << num_nibbles_x4) +// return (first_nibble, num_nibbles, key) +%macro split_first_nibble + // stack: num_nibbles, key + %sub_const(1) // num_nibbles -= 1 + // stack: num_nibbles, key + DUP2 + // stack: key, num_nibbles, key + DUP2 %mul_const(4) + // stack: num_nibbles_x4, key, num_nibbles, key + SHR + // stack: key >> num_nibbles_x4, num_nibbles, key + %and_const(0xF) + // stack: first_nibble, num_nibbles, key + DUP1 + // stack: first_nibble, first_nibble, num_nibbles, key + DUP3 %mul_const(4) + // stack: num_nibbles_x4, first_nibble, first_nibble, num_nibbles, key + SHL + // stack: first_nibble << num_nibbles_x4, first_nibble, num_nibbles, key + DUP1 + // stack: junk, first_nibble << num_nibbles_x4, first_nibble, num_nibbles, key + SWAP4 + // stack: key, first_nibble << num_nibbles_x4, first_nibble, num_nibbles, junk + SUB + // stack: key, first_nibble, num_nibbles, junk + SWAP3 + // stack: junk, first_nibble, num_nibbles, key + POP + // stack: first_nibble, num_nibbles, key +%endmacro diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 4a1f588b..20d0d1f4 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -58,7 +58,7 @@ impl InterpreterMemory { pub struct Interpreter<'a> { kernel_mode: bool, jumpdests: Vec, - offset: usize, + pub(crate) offset: usize, context: usize, pub(crate) memory: InterpreterMemory, pub(crate) generation_state: GenerationState, @@ -117,11 +117,12 @@ impl<'a> Interpreter<'a> { prover_inputs_map: prover_inputs, context: 0, halt_offsets: vec![DEFAULT_HALT_OFFSET], - running: true, + running: false, } } pub(crate) fn run(&mut self) -> anyhow::Result<()> { + self.running = true; while self.running { self.run_opcode()?; } @@ -185,7 +186,7 @@ impl<'a> Interpreter<'a> { &mut self.memory.context_memory[self.context].segments[Segment::Stack as usize].content } - fn push(&mut self, x: U256) { + pub(crate) fn push(&mut self, x: U256) { self.stack_mut().push(x); } diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs index 871a8682..01bd35a8 100644 --- a/evm/src/cpu/kernel/tests/mpt/load.rs +++ b/evm/src/cpu/kernel/tests/mpt/load.rs @@ -16,7 +16,7 @@ fn load_all_mpts() -> Result<()> { let storage_root = U256::from(3333); let code_hash = U256::from(4444); - let value_rlp = rlp::encode_list(&[nonce, balance, storage_root, code_hash]); + let account_rlp = rlp::encode_list(&[nonce, balance, storage_root, code_hash]); let trie_inputs = TrieInputs { state_trie: PartialTrie::Leaf { @@ -24,7 +24,7 @@ fn load_all_mpts() -> Result<()> { count: 2, packed: 123.into(), }, - value: value_rlp.to_vec(), + value: account_rlp.to_vec(), }, transactions_trie: Default::default(), receipts_trie: Default::default(), @@ -33,7 +33,6 @@ fn load_all_mpts() -> Result<()> { let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; - // Contract creation transaction. let initial_stack = vec![0xdeadbeefu32.into()]; let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack); interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs); diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs index 4295c3bf..febfb73f 100644 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -1 +1,2 @@ mod load; +mod read; diff --git a/evm/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs new file mode 100644 index 00000000..3a716e33 --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/read.rs @@ -0,0 +1,57 @@ +use anyhow::Result; +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use ethereum_types::U256; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::global_metadata::GlobalMetadata; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; +use crate::generation::TrieInputs; + +#[test] +fn mpt_read() -> Result<()> { + let nonce = U256::from(1111); + let balance = U256::from(2222); + let storage_root = U256::from(3333); + let code_hash = U256::from(4444); + + let account = &[nonce, balance, storage_root, code_hash]; + let account_rlp = rlp::encode_list(account); + + let trie_inputs = TrieInputs { + state_trie: PartialTrie::Leaf { + nibbles: Nibbles { + count: 2, + packed: 123.into(), + }, + value: account_rlp.to_vec(), + }, + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + let mpt_read = KERNEL.global_labels["mpt_read"]; + + let initial_stack = vec![0xdeadbeefu32.into()]; + let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack); + interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![]); + + // Now, execute mpt_read on the state trie. + interpreter.offset = mpt_read; + interpreter.push(0xdeadbeefu32.into()); + interpreter.push(2.into()); + interpreter.push(123.into()); + interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); + interpreter.run()?; + + assert_eq!(interpreter.stack().len(), 1); + let result_ptr = interpreter.stack()[0].as_usize(); + let result = &interpreter.get_trie_data()[result_ptr..result_ptr + 4]; + assert_eq!(result, account); + + Ok(()) +} From e2811550e13852bbf4c37a8c7dc01b253f405ffa Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Mon, 26 Sep 2022 20:34:17 -0700 Subject: [PATCH 81/97] addressed comments --- plonky2/examples/factorial.rs | 5 ++++- plonky2/examples/fibonacci.rs | 4 ++-- plonky2/examples/square_root.rs | 13 +++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plonky2/examples/factorial.rs b/plonky2/examples/factorial.rs index 518532c3..bcdb35dc 100644 --- a/plonky2/examples/factorial.rs +++ b/plonky2/examples/factorial.rs @@ -34,7 +34,10 @@ fn main() -> Result<()> { let data = builder.build::(); let proof = data.prove(pw)?; - println!("100 factorial (mod |F|) is: {}", proof.public_inputs[1]); + println!( + "Factorial starting at {} is {}!", + proof.public_inputs[0], proof.public_inputs[1] + ); data.verify(proof) } diff --git a/plonky2/examples/fibonacci.rs b/plonky2/examples/fibonacci.rs index 80bf8d8f..6609fc1d 100644 --- a/plonky2/examples/fibonacci.rs +++ b/plonky2/examples/fibonacci.rs @@ -41,8 +41,8 @@ fn main() -> Result<()> { let proof = data.prove(pw)?; println!( - "100th Fibonacci number (mod |F|) is: {}", - proof.public_inputs[2] + "100th Fibonacci number mod |F| (starting with {}, {}) is: {}", + proof.public_inputs[0], proof.public_inputs[1], proof.public_inputs[2] ); data.verify(proof) diff --git a/plonky2/examples/square_root.rs b/plonky2/examples/square_root.rs index 81216905..0bc89f47 100644 --- a/plonky2/examples/square_root.rs +++ b/plonky2/examples/square_root.rs @@ -31,6 +31,8 @@ impl, const D: usize> SimpleGenerator let x_squared = witness.get_target(self.x_squared); let x = x_squared.sqrt().unwrap(); + println!("Square root: {}", x); + out_buffer.set_target(self.x, x); } } @@ -49,7 +51,6 @@ fn main() -> Result<()> { let x = builder.add_virtual_target(); let x_squared = builder.square(x); - builder.register_public_input(x); builder.register_public_input(x_squared); builder.add_simple_generator(SquareRootGenerator:: { @@ -71,14 +72,10 @@ fn main() -> Result<()> { pw.set_target(x_squared, x_squared_value); let data = builder.build::(); - let proof = data.prove(pw)?; + let proof = data.prove(pw.clone())?; - let x_actual = proof.public_inputs[0]; - let x_squared_actual = proof.public_inputs[1]; - println!("Random field element: {}", x_squared_actual); - println!("Its square root: {}", x_actual); - - assert!(x_actual * x_actual == x_squared_actual); + let x_squared_actual = proof.public_inputs[0]; + println!("Field element (square): {}", x_squared_actual); data.verify(proof) } From 5555085c4cfbdaf69052e8568e2f66b4ec6a983d Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 27 Sep 2022 10:49:13 -0700 Subject: [PATCH 82/97] MPT read for extension nodes --- evm/src/cpu/kernel/asm/mpt/load.asm | 10 ++++-- evm/src/cpu/kernel/asm/mpt/read.asm | 52 ++++++++++++++++++++++------ evm/src/cpu/kernel/interpreter.rs | 8 ++++- evm/src/cpu/kernel/tests/mpt/load.rs | 21 ++++++----- evm/src/cpu/kernel/tests/mpt/mod.rs | 19 ++++++++++ evm/src/cpu/kernel/tests/mpt/read.rs | 18 ++++------ 6 files changed, 93 insertions(+), 35 deletions(-) diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index 208b7221..57c84ddb 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -110,10 +110,16 @@ load_mpt_extension: %append_to_trie_data // stack: retdest - %load_mpt_and_return_root_ptr - // stack: child_ptr, retdest + // Let i be the current trie data size. We still need to expand this node by + // one element, appending our child pointer. Thus our child node will start + // at i + 1. So we will set our child pointer to i + 1. + %get_trie_data_size + %add_const(1) %append_to_trie_data // stack: retdest + + %load_mpt + // stack: retdest JUMP load_mpt_leaf: diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index aaf39fcc..934b6bbf 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -63,27 +63,59 @@ mpt_read_branch_end_of_key: mpt_read_extension: // stack: node_type, node_payload_ptr, num_nibbles, key, retdest - POP - // stack: node_payload_ptr, num_nibbles, key, retdest - // TODO + %stack (node_type, node_payload_ptr, num_nibbles, key) + -> (num_nibbles, key, node_payload_ptr) + // stack: num_nibbles, key, node_payload_ptr, retdest + DUP3 %mload_trie_data + // stack: node_num_nibbles, num_nibbles, key, node_payload_ptr, retdest + SWAP1 + SUB + // stack: future_nibbles, key, node_payload_ptr, retdest + DUP2 DUP2 + // stack: future_nibbles, key, future_nibbles, key, node_payload_ptr, retdest + %mul_const(4) SHR // key_part = key >> (future_nibbles * 4) + DUP1 + // stack: key_part, key_part, future_nibbles, key, node_payload_ptr, retdest + DUP5 %add_const(1) %mload_trie_data + // stack: node_key, key_part, key_part, future_nibbles, key, node_payload_ptr, retdest + EQ // does the first part of our key match the node's key? + %jumpi(mpt_read_extension_found) + // Not found; return 0. + %stack (key_part, future_nibbles, node_payload_ptr, retdest) -> (retdest, 0) + JUMP +mpt_read_extension_found: + // stack: key_part, future_nibbles, key, node_payload_ptr, retdest + DUP2 %mul_const(4) SHL // key_part_shifted = (key_part << (future_nibbles * 4)) + // stack: key_part_shifted, future_nibbles, key, node_payload_ptr, retdest + %stack (key_part_shifted, future_nibbles, key) + -> (key, key_part_shifted, future_nibbles) + SUB // key -= key_part_shifted + // stack: key, future_nibbles, node_payload_ptr, retdest + SWAP2 + // stack: node_payload_ptr, future_nibbles, key, retdest + %add_const(2) // child pointer is third field of extension node + %mload_trie_data + // stack: child_ptr, future_nibbles, key, retdest + %jump(mpt_read) // recurse mpt_read_leaf: - // stack: node_type, node_payload_ptr, key, num_nibbles, retdest + // stack: node_type, node_payload_ptr, num_nibbles, key, retdest POP - // stack: node_payload_ptr, key, num_nibbles, retdest + // stack: node_payload_ptr, num_nibbles, key, retdest DUP1 %mload_trie_data - // stack: node_num_nibbles, node_payload_ptr, key, num_nibbles, retdest + // stack: node_num_nibbles, node_payload_ptr, num_nibbles, key, retdest DUP2 %add_const(1) %mload_trie_data - // stack: node_key, node_num_nibbles, node_payload_ptr, key, num_nibbles, retdest - SWAP4 - // stack: num_nibbles, node_num_nibbles, node_payload_ptr, key, node_key, retdest + // stack: node_key, node_num_nibbles, node_payload_ptr, num_nibbles, key, retdest + SWAP3 + // stack: num_nibbles, node_num_nibbles, node_payload_ptr, node_key, key, retdest EQ - %stack (num_nibbles_match, node_payload_ptr, key, node_key) + %stack (num_nibbles_match, node_payload_ptr, node_key, key) -> (key, node_key, num_nibbles_match, node_payload_ptr) EQ AND // stack: keys_match && num_nibbles_match, node_payload_ptr, retdest %jumpi(mpt_read_leaf_found) + // Not found; return 0. %stack (node_payload_ptr, retdest) -> (retdest, 0) JUMP mpt_read_leaf_found: diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 20d0d1f4..7577b974 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -226,7 +226,7 @@ impl<'a> Interpreter<'a> { 0x19 => self.run_not(), // "NOT", 0x1a => self.run_byte(), // "BYTE", 0x1b => self.run_shl(), // "SHL", - 0x1c => todo!(), // "SHR", + 0x1c => self.run_shr(), // "SHR", 0x1d => todo!(), // "SAR", 0x20 => self.run_keccak256(), // "KECCAK256", 0x30 => todo!(), // "ADDRESS", @@ -427,6 +427,12 @@ impl<'a> Interpreter<'a> { self.push(x << shift); } + fn run_shr(&mut self) { + let shift = self.pop(); + let x = self.pop(); + self.push(x >> shift); + } + fn run_keccak256(&mut self) { let offset = self.pop().as_usize(); let size = self.pop().as_usize(); diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs index 01bd35a8..fbbc690a 100644 --- a/evm/src/cpu/kernel/tests/mpt/load.rs +++ b/evm/src/cpu/kernel/tests/mpt/load.rs @@ -1,11 +1,11 @@ use anyhow::Result; -use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; use ethereum_types::U256; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; +use crate::cpu::kernel::tests::mpt::state_trie_ext_to_account_leaf; use crate::generation::mpt::all_mpt_prover_inputs_reversed; use crate::generation::TrieInputs; @@ -19,13 +19,7 @@ fn load_all_mpts() -> Result<()> { let account_rlp = rlp::encode_list(&[nonce, balance, storage_root, code_hash]); let trie_inputs = TrieInputs { - state_trie: PartialTrie::Leaf { - nibbles: Nibbles { - count: 2, - packed: 123.into(), - }, - value: account_rlp.to_vec(), - }, + state_trie: state_trie_ext_to_account_leaf(account_rlp.to_vec()), transactions_trie: Default::default(), receipts_trie: Default::default(), storage_tries: vec![], @@ -40,14 +34,19 @@ fn load_all_mpts() -> Result<()> { assert_eq!(interpreter.stack(), vec![]); let type_empty = U256::from(PartialTrieType::Empty as u32); + let type_extension = U256::from(PartialTrieType::Extension as u32); let type_leaf = U256::from(PartialTrieType::Leaf as u32); assert_eq!( interpreter.get_trie_data(), vec![ - 0.into(), // First address is unused, so 0 can be treated as a null pointer. + 0.into(), // First address is unused, so that 0 can be treated as a null pointer. + type_extension, + 3.into(), // 3 nibbles + 0xABC.into(), // key part + 5.into(), // Pointer to the leaf node immediately below. type_leaf, - 2.into(), - 123.into(), + 3.into(), // 3 nibbles + 0xDEF.into(), // key part nonce, balance, storage_root, diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs index febfb73f..2d14c89a 100644 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -1,2 +1,21 @@ +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; + mod load; mod read; + +/// A `PartialTrie` where an extension node leads to a leaf node containing an account. +pub(crate) fn state_trie_ext_to_account_leaf(value: Vec) -> PartialTrie { + PartialTrie::Extension { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + child: Box::new(PartialTrie::Leaf { + nibbles: Nibbles { + count: 3, + packed: 0xDEF.into(), + }, + value, + }), + } +} diff --git a/evm/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs index 3a716e33..e26ee4ab 100644 --- a/evm/src/cpu/kernel/tests/mpt/read.rs +++ b/evm/src/cpu/kernel/tests/mpt/read.rs @@ -1,10 +1,10 @@ use anyhow::Result; -use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; use ethereum_types::U256; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; +use crate::cpu::kernel::tests::mpt::state_trie_ext_to_account_leaf; use crate::generation::mpt::all_mpt_prover_inputs_reversed; use crate::generation::TrieInputs; @@ -19,13 +19,7 @@ fn mpt_read() -> Result<()> { let account_rlp = rlp::encode_list(account); let trie_inputs = TrieInputs { - state_trie: PartialTrie::Leaf { - nibbles: Nibbles { - count: 2, - packed: 123.into(), - }, - value: account_rlp.to_vec(), - }, + state_trie: state_trie_ext_to_account_leaf(account_rlp.to_vec()), transactions_trie: Default::default(), receipts_trie: Default::default(), storage_tries: vec![], @@ -43,14 +37,16 @@ fn mpt_read() -> Result<()> { // Now, execute mpt_read on the state trie. interpreter.offset = mpt_read; interpreter.push(0xdeadbeefu32.into()); - interpreter.push(2.into()); - interpreter.push(123.into()); + interpreter.push(0xABCDEFu64.into()); + interpreter.push(6.into()); interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); interpreter.run()?; + dbg!(interpreter.stack()); assert_eq!(interpreter.stack().len(), 1); let result_ptr = interpreter.stack()[0].as_usize(); - let result = &interpreter.get_trie_data()[result_ptr..result_ptr + 4]; + dbg!(result_ptr); + let result = &interpreter.get_trie_data()[result_ptr..][..account.len()]; assert_eq!(result, account); Ok(()) From e978425b26ea21b357f405f1b42f2b8bd813ef7d Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Wed, 28 Sep 2022 15:18:56 -0700 Subject: [PATCH 83/97] Connect stack to memory (#735) * Connect stack to memory * Daniel PR comment --- evm/src/cpu/columns/mod.rs | 107 +-------- evm/src/cpu/columns/ops.rs | 153 +++++++++++++ evm/src/cpu/control_flow.rs | 50 ++--- evm/src/cpu/cpu_stark.rs | 9 +- evm/src/cpu/decode.rs | 198 ++++++++--------- evm/src/cpu/jumps.rs | 18 +- evm/src/cpu/mod.rs | 1 + evm/src/cpu/simple_logic/eq_iszero.rs | 12 +- evm/src/cpu/simple_logic/not.rs | 6 +- evm/src/cpu/stack.rs | 307 ++++++++++++++++++++++++++ evm/src/cpu/stack_bounds.rs | 4 +- evm/src/cpu/syscalls.rs | 6 +- 12 files changed, 622 insertions(+), 249 deletions(-) create mode 100644 evm/src/cpu/columns/ops.rs create mode 100644 evm/src/cpu/stack.rs diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 5204122e..d0ef3f28 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -7,11 +7,15 @@ use std::mem::{size_of, transmute}; use std::ops::{Index, IndexMut}; use crate::cpu::columns::general::CpuGeneralColumnsView; +use crate::cpu::columns::ops::OpsColumnsView; use crate::cpu::membus::NUM_GP_CHANNELS; use crate::memory; use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; mod general; +pub(crate) mod ops; + +pub type MemValue = [T; memory::VALUE_LIMBS]; #[repr(C)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -23,7 +27,7 @@ pub struct MemoryChannelView { pub addr_context: T, pub addr_segment: T, pub addr_virtual: T, - pub value: [T; memory::VALUE_LIMBS], + pub value: MemValue, } #[repr(C)] @@ -56,104 +60,9 @@ pub struct CpuColumnsView { /// If CPU cycle: We're in kernel (privileged) mode. pub is_kernel_mode: T, - // If CPU cycle: flags for EVM instructions. PUSHn, DUPn, and SWAPn only get one flag each. - // Invalid opcodes are split between a number of flags for practical reasons. Exactly one of - // these flags must be 1. - pub is_stop: T, - pub is_add: T, - pub is_mul: T, - pub is_sub: T, - pub is_div: T, - pub is_sdiv: T, - pub is_mod: T, - pub is_smod: T, - pub is_addmod: T, - pub is_mulmod: T, - pub is_exp: T, - pub is_signextend: T, - pub is_lt: T, - pub is_gt: T, - pub is_slt: T, - pub is_sgt: T, - pub is_eq: T, // Note: This column must be 0 when is_cpu_cycle = 0. - pub is_iszero: T, // Note: This column must be 0 when is_cpu_cycle = 0. - pub is_and: T, - pub is_or: T, - pub is_xor: T, - pub is_not: T, - pub is_byte: T, - pub is_shl: T, - pub is_shr: T, - pub is_sar: T, - pub is_keccak256: T, - pub is_address: T, - pub is_balance: T, - pub is_origin: T, - pub is_caller: T, - pub is_callvalue: T, - pub is_calldataload: T, - pub is_calldatasize: T, - pub is_calldatacopy: T, - pub is_codesize: T, - pub is_codecopy: T, - pub is_gasprice: T, - pub is_extcodesize: T, - pub is_extcodecopy: T, - pub is_returndatasize: T, - pub is_returndatacopy: T, - pub is_extcodehash: T, - pub is_blockhash: T, - pub is_coinbase: T, - pub is_timestamp: T, - pub is_number: T, - pub is_difficulty: T, - pub is_gaslimit: T, - pub is_chainid: T, - pub is_selfbalance: T, - pub is_basefee: T, - pub is_prover_input: T, - pub is_pop: T, - pub is_mload: T, - pub is_mstore: T, - pub is_mstore8: T, - pub is_sload: T, - pub is_sstore: T, - pub is_jump: T, // Note: This column must be 0 when is_cpu_cycle = 0. - pub is_jumpi: T, // Note: This column must be 0 when is_cpu_cycle = 0. - pub is_pc: T, - pub is_msize: T, - pub is_gas: T, - pub is_jumpdest: T, - pub is_get_state_root: T, - pub is_set_state_root: T, - pub is_get_receipt_root: T, - pub is_set_receipt_root: T, - pub is_push: T, - pub is_dup: T, - pub is_swap: T, - pub is_log0: T, - pub is_log1: T, - pub is_log2: T, - pub is_log3: T, - pub is_log4: T, - // PANIC does not get a flag; it fails at the decode stage. - pub is_create: T, - pub is_call: T, - pub is_callcode: T, - pub is_return: T, - pub is_delegatecall: T, - pub is_create2: T, - pub is_get_context: T, - pub is_set_context: T, - pub is_consume_gas: T, - pub is_exit_kernel: T, - pub is_staticcall: T, - pub is_mload_general: T, - pub is_mstore_general: T, - pub is_revert: T, - pub is_selfdestruct: T, - - pub is_invalid: T, + /// If CPU cycle: flags for EVM instructions (a few cannot be shared; see the comments in + /// `OpsColumnsView`). + pub op: OpsColumnsView, /// If CPU cycle: the opcode, broken up into bits in little-endian order. pub opcode_bits: [T; 8], diff --git a/evm/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs new file mode 100644 index 00000000..28087b9e --- /dev/null +++ b/evm/src/cpu/columns/ops.rs @@ -0,0 +1,153 @@ +use std::borrow::{Borrow, BorrowMut}; +use std::mem::{size_of, transmute}; +use std::ops::{Deref, DerefMut}; + +use crate::util::{indices_arr, transmute_no_compile_time_size_checks}; + +#[repr(C)] +#[derive(Eq, PartialEq, Debug)] +pub struct OpsColumnsView { + pub stop: T, + pub add: T, + pub mul: T, + pub sub: T, + pub div: T, + pub sdiv: T, + pub mod_: T, + pub smod: T, + pub addmod: T, + pub mulmod: T, + pub exp: T, + pub signextend: T, + pub lt: T, + pub gt: T, + pub slt: T, + pub sgt: T, + pub eq: T, // Note: This column must be 0 when is_cpu_cycle = 0. + pub iszero: T, // Note: This column must be 0 when is_cpu_cycle = 0. + pub and: T, + pub or: T, + pub xor: T, + pub not: T, + pub byte: T, + pub shl: T, + pub shr: T, + pub sar: T, + pub keccak256: T, + pub address: T, + pub balance: T, + pub origin: T, + pub caller: T, + pub callvalue: T, + pub calldataload: T, + pub calldatasize: T, + pub calldatacopy: T, + pub codesize: T, + pub codecopy: T, + pub gasprice: T, + pub extcodesize: T, + pub extcodecopy: T, + pub returndatasize: T, + pub returndatacopy: T, + pub extcodehash: T, + pub blockhash: T, + pub coinbase: T, + pub timestamp: T, + pub number: T, + pub difficulty: T, + pub gaslimit: T, + pub chainid: T, + pub selfbalance: T, + pub basefee: T, + pub prover_input: T, + pub pop: T, + pub mload: T, + pub mstore: T, + pub mstore8: T, + pub sload: T, + pub sstore: T, + pub jump: T, // Note: This column must be 0 when is_cpu_cycle = 0. + pub jumpi: T, // Note: This column must be 0 when is_cpu_cycle = 0. + pub pc: T, + pub msize: T, + pub gas: T, + pub jumpdest: T, + pub get_state_root: T, + pub set_state_root: T, + pub get_receipt_root: T, + pub set_receipt_root: T, + pub push: T, + pub dup: T, + pub swap: T, + pub log0: T, + pub log1: T, + pub log2: T, + pub log3: T, + pub log4: T, + // PANIC does not get a flag; it fails at the decode stage. + pub create: T, + pub call: T, + pub callcode: T, + pub return_: T, + pub delegatecall: T, + pub create2: T, + pub get_context: T, + pub set_context: T, + pub consume_gas: T, + pub exit_kernel: T, + pub staticcall: T, + pub mload_general: T, + pub mstore_general: T, + pub revert: T, + pub selfdestruct: T, + + // TODO: this doesn't actually need its own flag. We can just do `1 - sum(all other flags)`. + pub invalid: T, +} + +// `u8` is guaranteed to have a `size_of` of 1. +pub const NUM_OPS_COLUMNS: usize = size_of::>(); + +impl From<[T; NUM_OPS_COLUMNS]> for OpsColumnsView { + fn from(value: [T; NUM_OPS_COLUMNS]) -> Self { + unsafe { transmute_no_compile_time_size_checks(value) } + } +} + +impl From> for [T; NUM_OPS_COLUMNS] { + fn from(value: OpsColumnsView) -> Self { + unsafe { transmute_no_compile_time_size_checks(value) } + } +} + +impl Borrow> for [T; NUM_OPS_COLUMNS] { + fn borrow(&self) -> &OpsColumnsView { + unsafe { transmute(self) } + } +} + +impl BorrowMut> for [T; NUM_OPS_COLUMNS] { + fn borrow_mut(&mut self) -> &mut OpsColumnsView { + unsafe { transmute(self) } + } +} + +impl Deref for OpsColumnsView { + type Target = [T; NUM_OPS_COLUMNS]; + fn deref(&self) -> &Self::Target { + unsafe { transmute(self) } + } +} + +impl DerefMut for OpsColumnsView { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { transmute(self) } + } +} + +const fn make_col_map() -> OpsColumnsView { + let indices_arr = indices_arr::(); + unsafe { transmute::<[usize; NUM_OPS_COLUMNS], OpsColumnsView>(indices_arr) } +} + +pub const COL_MAP: OpsColumnsView = make_col_map(); diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index 5a43f7cf..4ab9b91a 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -10,31 +10,31 @@ use crate::cpu::kernel::aggregator::KERNEL; // TODO: This list is incomplete. const NATIVE_INSTRUCTIONS: [usize; 25] = [ - COL_MAP.is_add, - COL_MAP.is_mul, - COL_MAP.is_sub, - COL_MAP.is_div, - COL_MAP.is_sdiv, - COL_MAP.is_mod, - COL_MAP.is_smod, - COL_MAP.is_addmod, - COL_MAP.is_mulmod, - COL_MAP.is_signextend, - COL_MAP.is_lt, - COL_MAP.is_gt, - COL_MAP.is_slt, - COL_MAP.is_sgt, - COL_MAP.is_eq, - COL_MAP.is_iszero, - COL_MAP.is_and, - COL_MAP.is_or, - COL_MAP.is_xor, - COL_MAP.is_not, - COL_MAP.is_byte, - COL_MAP.is_shl, - COL_MAP.is_shr, - COL_MAP.is_sar, - COL_MAP.is_pop, + COL_MAP.op.add, + COL_MAP.op.mul, + COL_MAP.op.sub, + COL_MAP.op.div, + COL_MAP.op.sdiv, + COL_MAP.op.mod_, + COL_MAP.op.smod, + COL_MAP.op.addmod, + COL_MAP.op.mulmod, + COL_MAP.op.signextend, + COL_MAP.op.lt, + COL_MAP.op.gt, + COL_MAP.op.slt, + COL_MAP.op.sgt, + COL_MAP.op.eq, + COL_MAP.op.iszero, + COL_MAP.op.and, + COL_MAP.op.or, + COL_MAP.op.xor, + COL_MAP.op.not, + COL_MAP.op.byte, + COL_MAP.op.shl, + COL_MAP.op.shr, + COL_MAP.op.sar, + COL_MAP.op.pop, ]; fn get_halt_pcs() -> (F, F) { diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index 5f702317..7ee204ca 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -11,7 +11,8 @@ use plonky2::hash::hash_types::RichField; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; use crate::cpu::{ - bootstrap_kernel, control_flow, decode, jumps, membus, simple_logic, stack_bounds, syscalls, + bootstrap_kernel, control_flow, decode, jumps, membus, simple_logic, stack, stack_bounds, + syscalls, }; use crate::cross_table_lookup::Column; use crate::memory::segments::Segment; @@ -50,7 +51,7 @@ pub fn ctl_filter_keccak_memory() -> Column { } pub fn ctl_data_logic() -> Vec> { - let mut res = Column::singles([COL_MAP.is_and, COL_MAP.is_or, COL_MAP.is_xor]).collect_vec(); + let mut res = Column::singles([COL_MAP.op.and, COL_MAP.op.or, COL_MAP.op.xor]).collect_vec(); res.extend(Column::singles(COL_MAP.mem_channels[0].value)); res.extend(Column::singles(COL_MAP.mem_channels[1].value)); res.extend(Column::singles(COL_MAP.mem_channels[2].value)); @@ -58,7 +59,7 @@ pub fn ctl_data_logic() -> Vec> { } pub fn ctl_filter_logic() -> Column { - Column::sum([COL_MAP.is_and, COL_MAP.is_or, COL_MAP.is_xor]) + Column::sum([COL_MAP.op.and, COL_MAP.op.or, COL_MAP.op.xor]) } pub const MEM_CODE_CHANNEL_IDX: usize = 0; @@ -149,6 +150,7 @@ impl, const D: usize> Stark for CpuStark, const D: usize> Stark for CpuStark(lv: &mut CpuColumnsView) { let cycle_filter = lv.is_cpu_cycle; if cycle_filter == F::ZERO { // These columns cannot be shared. - lv.is_eq = F::ZERO; - lv.is_iszero = F::ZERO; + lv.op.eq = F::ZERO; + lv.op.iszero = F::ZERO; return; } // This assert is not _strictly_ necessary, but I include it as a sanity check. @@ -196,7 +196,7 @@ pub fn generate(lv: &mut CpuColumnsView) { any_flag_set = any_flag_set || flag; } // is_invalid is a catch-all for opcodes we can't decode. - lv.is_invalid = F::from_bool(!any_flag_set); + lv.op.invalid = F::from_bool(!any_flag_set); } /// Break up an opcode (which is 8 bits long) into its eight bits. @@ -234,13 +234,13 @@ pub fn eval_packed_generic( let flag = lv[flag_col]; yield_constr.constraint(cycle_filter * flag * (flag - P::ONES)); } - yield_constr.constraint(cycle_filter * lv.is_invalid * (lv.is_invalid - P::ONES)); + yield_constr.constraint(cycle_filter * lv.op.invalid * (lv.op.invalid - P::ONES)); // Now check that exactly one is 1. let flag_sum: P = OPCODES .into_iter() .map(|(_, _, _, flag_col)| lv[flag_col]) .sum::

() - + lv.is_invalid; + + lv.op.invalid; yield_constr.constraint(cycle_filter * (P::ONES - flag_sum)); // Finally, classify all opcodes, together with the kernel flag, into blocks @@ -305,7 +305,7 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr.constraint(builder, constr); } { - let constr = builder.mul_sub_extension(lv.is_invalid, lv.is_invalid, lv.is_invalid); + let constr = builder.mul_sub_extension(lv.op.invalid, lv.op.invalid, lv.op.invalid); let constr = builder.mul_extension(cycle_filter, constr); yield_constr.constraint(builder, constr); } @@ -316,7 +316,7 @@ pub fn eval_ext_circuit, const D: usize>( let flag = lv[flag_col]; constr = builder.sub_extension(constr, flag); } - constr = builder.sub_extension(constr, lv.is_invalid); + constr = builder.sub_extension(constr, lv.op.invalid); constr = builder.mul_extension(cycle_filter, constr); yield_constr.constraint(builder, constr); } diff --git a/evm/src/cpu/jumps.rs b/evm/src/cpu/jumps.rs index 219b39dd..fb13f83b 100644 --- a/evm/src/cpu/jumps.rs +++ b/evm/src/cpu/jumps.rs @@ -23,10 +23,10 @@ pub fn eval_packed_exit_kernel( // flag. The top 6 (32-bit) limbs are ignored (this is not part of the spec, but we trust the // kernel to set them to zero). yield_constr.constraint_transition( - lv.is_cpu_cycle * lv.is_exit_kernel * (input[0] - nv.program_counter), + lv.is_cpu_cycle * lv.op.exit_kernel * (input[0] - nv.program_counter), ); yield_constr.constraint_transition( - lv.is_cpu_cycle * lv.is_exit_kernel * (input[1] - nv.is_kernel_mode), + lv.is_cpu_cycle * lv.op.exit_kernel * (input[1] - nv.is_kernel_mode), ); } @@ -37,7 +37,7 @@ pub fn eval_ext_circuit_exit_kernel, const D: usize yield_constr: &mut RecursiveConstraintConsumer, ) { let input = lv.mem_channels[0].value; - let filter = builder.mul_extension(lv.is_cpu_cycle, lv.is_exit_kernel); + let filter = builder.mul_extension(lv.is_cpu_cycle, lv.op.exit_kernel); // If we are executing `EXIT_KERNEL` then we simply restore the program counter and kernel mode // flag. The top 6 (32-bit) limbs are ignored (this is not part of the spec, but we trust the @@ -60,16 +60,16 @@ pub fn eval_packed_jump_jumpi( let jumps_lv = lv.general.jumps(); let input0 = lv.mem_channels[0].value; let input1 = lv.mem_channels[1].value; - let filter = lv.is_jump + lv.is_jumpi; // `JUMP` or `JUMPI` + let filter = lv.op.jump + lv.op.jumpi; // `JUMP` or `JUMPI` // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the predicate) to be 1. // In other words, we implement `JUMP(addr)` as `JUMPI(addr, cond=1)`. - yield_constr.constraint(lv.is_jump * (input1[0] - P::ONES)); + yield_constr.constraint(lv.op.jump * (input1[0] - P::ONES)); for &limb in &input1[1..] { // Set all limbs (other than the least-significant limb) to 0. // NB: Technically, they don't have to be 0, as long as the sum // `input1[0] + ... + input1[7]` cannot overflow. - yield_constr.constraint(lv.is_jump * limb); + yield_constr.constraint(lv.op.jump * limb); } // Check `input0_upper_zero` @@ -162,19 +162,19 @@ pub fn eval_ext_circuit_jump_jumpi, const D: usize> let jumps_lv = lv.general.jumps(); let input0 = lv.mem_channels[0].value; let input1 = lv.mem_channels[1].value; - let filter = builder.add_extension(lv.is_jump, lv.is_jumpi); // `JUMP` or `JUMPI` + let filter = builder.add_extension(lv.op.jump, lv.op.jumpi); // `JUMP` or `JUMPI` // If `JUMP`, re-use the `JUMPI` logic, but setting the second input (the predicate) to be 1. // In other words, we implement `JUMP(addr)` as `JUMPI(addr, cond=1)`. { - let constr = builder.mul_sub_extension(lv.is_jump, input1[0], lv.is_jump); + let constr = builder.mul_sub_extension(lv.op.jump, input1[0], lv.op.jump); yield_constr.constraint(builder, constr); } for &limb in &input1[1..] { // Set all limbs (other than the least-significant limb) to 0. // NB: Technically, they don't have to be 0, as long as the sum // `input1[0] + ... + input1[7]` cannot overflow. - let constr = builder.mul_extension(lv.is_jump, limb); + let constr = builder.mul_extension(lv.op.jump, limb); yield_constr.constraint(builder, constr); } diff --git a/evm/src/cpu/mod.rs b/evm/src/cpu/mod.rs index 7b1e4756..c5b7dd32 100644 --- a/evm/src/cpu/mod.rs +++ b/evm/src/cpu/mod.rs @@ -7,5 +7,6 @@ mod jumps; pub mod kernel; pub(crate) mod membus; mod simple_logic; +mod stack; mod stack_bounds; mod syscalls; diff --git a/evm/src/cpu/simple_logic/eq_iszero.rs b/evm/src/cpu/simple_logic/eq_iszero.rs index 6b7294a8..37e06248 100644 --- a/evm/src/cpu/simple_logic/eq_iszero.rs +++ b/evm/src/cpu/simple_logic/eq_iszero.rs @@ -10,8 +10,8 @@ use crate::cpu::columns::CpuColumnsView; pub fn generate(lv: &mut CpuColumnsView) { let input0 = lv.mem_channels[0].value; - let eq_filter = lv.is_eq.to_canonical_u64(); - let iszero_filter = lv.is_iszero.to_canonical_u64(); + let eq_filter = lv.op.eq.to_canonical_u64(); + let iszero_filter = lv.op.iszero.to_canonical_u64(); assert!(eq_filter <= 1); assert!(iszero_filter <= 1); assert!(eq_filter + iszero_filter <= 1); @@ -62,8 +62,8 @@ pub fn eval_packed( let input1 = lv.mem_channels[1].value; let output = lv.mem_channels[2].value; - let eq_filter = lv.is_eq; - let iszero_filter = lv.is_iszero; + let eq_filter = lv.op.eq; + let iszero_filter = lv.op.iszero; let eq_or_iszero_filter = eq_filter + iszero_filter; let equal = output[0]; @@ -110,8 +110,8 @@ pub fn eval_ext_circuit, const D: usize>( let input1 = lv.mem_channels[1].value; let output = lv.mem_channels[2].value; - let eq_filter = lv.is_eq; - let iszero_filter = lv.is_iszero; + let eq_filter = lv.op.eq; + let iszero_filter = lv.op.iszero; let eq_or_iszero_filter = builder.add_extension(eq_filter, iszero_filter); let equal = output[0]; diff --git a/evm/src/cpu/simple_logic/not.rs b/evm/src/cpu/simple_logic/not.rs index 83d43276..3b8a888f 100644 --- a/evm/src/cpu/simple_logic/not.rs +++ b/evm/src/cpu/simple_logic/not.rs @@ -11,7 +11,7 @@ const LIMB_SIZE: usize = 32; const ALL_1_LIMB: u64 = (1 << LIMB_SIZE) - 1; pub fn generate(lv: &mut CpuColumnsView) { - let is_not_filter = lv.is_not.to_canonical_u64(); + let is_not_filter = lv.op.not.to_canonical_u64(); if is_not_filter == 0 { return; } @@ -35,7 +35,7 @@ pub fn eval_packed( let input = lv.mem_channels[0].value; let output = lv.mem_channels[1].value; let cycle_filter = lv.is_cpu_cycle; - let is_not_filter = lv.is_not; + let is_not_filter = lv.op.not; let filter = cycle_filter * is_not_filter; for (input_limb, output_limb) in input.into_iter().zip(output) { yield_constr.constraint( @@ -52,7 +52,7 @@ pub fn eval_ext_circuit, const D: usize>( let input = lv.mem_channels[0].value; let output = lv.mem_channels[1].value; let cycle_filter = lv.is_cpu_cycle; - let is_not_filter = lv.is_not; + let is_not_filter = lv.op.not; let filter = builder.mul_extension(cycle_filter, is_not_filter); for (input_limb, output_limb) in input.into_iter().zip(output) { let constr = builder.add_extension(output_limb, input_limb); diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs new file mode 100644 index 00000000..d478571a --- /dev/null +++ b/evm/src/cpu/stack.rs @@ -0,0 +1,307 @@ +use itertools::izip; +use plonky2::field::extension::Extendable; +use plonky2::field::packed::PackedField; +use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; +use plonky2::iop::ext_target::ExtensionTarget; + +use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use crate::cpu::columns::ops::OpsColumnsView; +use crate::cpu::columns::CpuColumnsView; +use crate::cpu::membus::NUM_GP_CHANNELS; +use crate::memory::segments::Segment; + +#[derive(Clone, Copy)] +struct StackBehavior { + num_pops: usize, + pushes: bool, + disable_other_channels: bool, +} + +const BASIC_UNARY_OP: Option = Some(StackBehavior { + num_pops: 1, + pushes: true, + disable_other_channels: true, +}); +const BASIC_BINARY_OP: Option = Some(StackBehavior { + num_pops: 2, + pushes: true, + disable_other_channels: true, +}); +const BASIC_TERNARY_OP: Option = Some(StackBehavior { + num_pops: 2, + pushes: true, + disable_other_channels: true, +}); + +const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { + stop: None, // TODO + add: BASIC_BINARY_OP, + mul: BASIC_BINARY_OP, + sub: BASIC_BINARY_OP, + div: BASIC_BINARY_OP, + sdiv: BASIC_BINARY_OP, + mod_: BASIC_BINARY_OP, + smod: BASIC_BINARY_OP, + addmod: BASIC_TERNARY_OP, + mulmod: BASIC_TERNARY_OP, + exp: None, // TODO + signextend: BASIC_BINARY_OP, + lt: BASIC_BINARY_OP, + gt: BASIC_BINARY_OP, + slt: BASIC_BINARY_OP, + sgt: BASIC_BINARY_OP, + eq: BASIC_BINARY_OP, + iszero: BASIC_UNARY_OP, + and: BASIC_BINARY_OP, + or: BASIC_BINARY_OP, + xor: BASIC_BINARY_OP, + not: BASIC_TERNARY_OP, + byte: BASIC_BINARY_OP, + shl: BASIC_BINARY_OP, + shr: BASIC_BINARY_OP, + sar: BASIC_BINARY_OP, + keccak256: None, // TODO + address: None, // TODO + balance: None, // TODO + origin: None, // TODO + caller: None, // TODO + callvalue: None, // TODO + calldataload: None, // TODO + calldatasize: None, // TODO + calldatacopy: None, // TODO + codesize: None, // TODO + codecopy: None, // TODO + gasprice: None, // TODO + extcodesize: None, // TODO + extcodecopy: None, // TODO + returndatasize: None, // TODO + returndatacopy: None, // TODO + extcodehash: None, // TODO + blockhash: None, // TODO + coinbase: None, // TODO + timestamp: None, // TODO + number: None, // TODO + difficulty: None, // TODO + gaslimit: None, // TODO + chainid: None, // TODO + selfbalance: None, // TODO + basefee: None, // TODO + prover_input: None, // TODO + pop: None, // TODO + mload: None, // TODO + mstore: None, // TODO + mstore8: None, // TODO + sload: None, // TODO + sstore: None, // TODO + jump: None, // TODO + jumpi: None, // TODO + pc: None, // TODO + msize: None, // TODO + gas: None, // TODO + jumpdest: None, // TODO + get_state_root: None, // TODO + set_state_root: None, // TODO + get_receipt_root: None, // TODO + set_receipt_root: None, // TODO + push: None, // TODO + dup: None, // TODO + swap: None, // TODO + log0: None, // TODO + log1: None, // TODO + log2: None, // TODO + log3: None, // TODO + log4: None, // TODO + create: None, // TODO + call: None, // TODO + callcode: None, // TODO + return_: None, // TODO + delegatecall: None, // TODO + create2: None, // TODO + get_context: None, // TODO + set_context: None, // TODO + consume_gas: None, // TODO + exit_kernel: None, // TODO + staticcall: None, // TODO + mload_general: None, // TODO + mstore_general: None, // TODO + revert: None, // TODO + selfdestruct: None, // TODO + invalid: None, // TODO +}; + +fn eval_packed_one( + lv: &CpuColumnsView

, + filter: P, + stack_behavior: StackBehavior, + yield_constr: &mut ConstraintConsumer

, +) { + let num_operands = stack_behavior.num_pops + (stack_behavior.pushes as usize); + assert!(num_operands <= NUM_GP_CHANNELS); + + // Pops + for i in 0..stack_behavior.num_pops { + let channel = lv.mem_channels[i]; + + yield_constr.constraint(filter * (channel.used - P::ONES)); + yield_constr.constraint(filter * (channel.is_read - P::ONES)); + + yield_constr.constraint(filter * (channel.addr_context - lv.context)); + yield_constr.constraint( + filter * (channel.addr_segment - P::Scalar::from_canonical_u64(Segment::Stack as u64)), + ); + // E.g. if `stack_len == 1` and `i == 0`, we want `add_virtual == 0`. + let addr_virtual = lv.stack_len - P::Scalar::from_canonical_usize(i + 1); + yield_constr.constraint(filter * (channel.addr_virtual - addr_virtual)); + } + + // Pushes + if stack_behavior.pushes { + let channel = lv.mem_channels[NUM_GP_CHANNELS - 1]; + + yield_constr.constraint(filter * (channel.used - P::ONES)); + yield_constr.constraint(filter * channel.is_read); + + yield_constr.constraint(filter * (channel.addr_context - lv.context)); + yield_constr.constraint( + filter * (channel.addr_segment - P::Scalar::from_canonical_u64(Segment::Stack as u64)), + ); + let addr_virtual = lv.stack_len - P::Scalar::from_canonical_usize(stack_behavior.num_pops); + yield_constr.constraint(filter * (channel.addr_virtual - addr_virtual)); + } + + // Unused channels + if stack_behavior.disable_other_channels { + for i in stack_behavior.num_pops..NUM_GP_CHANNELS - (stack_behavior.pushes as usize) { + let channel = lv.mem_channels[i]; + yield_constr.constraint(filter * channel.used); + } + } +} + +pub fn eval_packed( + lv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + for (op, stack_behavior) in izip!(lv.op.into_iter(), STACK_BEHAVIORS.into_iter()) { + if let Some(stack_behavior) = stack_behavior { + let filter = lv.is_cpu_cycle * op; + eval_packed_one(lv, filter, stack_behavior, yield_constr); + } + } +} + +fn eval_ext_circuit_one, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + filter: ExtensionTarget, + stack_behavior: StackBehavior, + yield_constr: &mut RecursiveConstraintConsumer, +) { + let num_operands = stack_behavior.num_pops + (stack_behavior.pushes as usize); + assert!(num_operands <= NUM_GP_CHANNELS); + + // Pops + for i in 0..stack_behavior.num_pops { + let channel = lv.mem_channels[i]; + + { + let constr = builder.mul_sub_extension(filter, channel.used, filter); + yield_constr.constraint(builder, constr); + } + { + let constr = builder.mul_sub_extension(filter, channel.is_read, filter); + yield_constr.constraint(builder, constr); + } + + { + let diff = builder.sub_extension(channel.addr_context, lv.context); + let constr = builder.mul_extension(filter, diff); + yield_constr.constraint(builder, constr); + } + { + let constr = builder.arithmetic_extension( + F::ONE, + -F::from_canonical_u64(Segment::Stack as u64), + filter, + channel.addr_segment, + filter, + ); + yield_constr.constraint(builder, constr); + } + { + let diff = builder.sub_extension(channel.addr_virtual, lv.stack_len); + let constr = builder.arithmetic_extension( + F::ONE, + F::from_canonical_usize(i + 1), + filter, + diff, + filter, + ); + yield_constr.constraint(builder, constr); + } + } + + // Pushes + if stack_behavior.pushes { + let channel = lv.mem_channels[NUM_GP_CHANNELS - 1]; + + { + let constr = builder.mul_sub_extension(filter, channel.used, filter); + yield_constr.constraint(builder, constr); + } + { + let constr = builder.mul_extension(filter, channel.is_read); + yield_constr.constraint(builder, constr); + } + + { + let diff = builder.sub_extension(channel.addr_context, lv.context); + let constr = builder.mul_extension(filter, diff); + yield_constr.constraint(builder, constr); + } + { + let constr = builder.arithmetic_extension( + F::ONE, + -F::from_canonical_u64(Segment::Stack as u64), + filter, + channel.addr_segment, + filter, + ); + yield_constr.constraint(builder, constr); + } + { + let diff = builder.sub_extension(channel.addr_virtual, lv.stack_len); + let constr = builder.arithmetic_extension( + F::ONE, + F::from_canonical_usize(stack_behavior.num_pops), + filter, + diff, + filter, + ); + yield_constr.constraint(builder, constr); + } + } + + // Unused channels + if stack_behavior.disable_other_channels { + for i in stack_behavior.num_pops..NUM_GP_CHANNELS - (stack_behavior.pushes as usize) { + let channel = lv.mem_channels[i]; + let constr = builder.mul_extension(filter, channel.used); + yield_constr.constraint(builder, constr); + } + } +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + for (op, stack_behavior) in izip!(lv.op.into_iter(), STACK_BEHAVIORS.into_iter()) { + if let Some(stack_behavior) = stack_behavior { + let filter = builder.mul_extension(lv.is_cpu_cycle, op); + eval_ext_circuit_one(builder, lv, filter, stack_behavior, yield_constr); + } + } +} diff --git a/evm/src/cpu/stack_bounds.rs b/evm/src/cpu/stack_bounds.rs index 2c9c46eb..99734433 100644 --- a/evm/src/cpu/stack_bounds.rs +++ b/evm/src/cpu/stack_bounds.rs @@ -26,13 +26,13 @@ const MAX_USER_STACK_SIZE: u64 = 1024; // Other operations that have a minimum stack size (e.g. `MULMOD`, which has three inputs) read // all their inputs from memory. On underflow, the cross-table lookup fails, as -1, ..., -17 are // invalid memory addresses. -const DECREMENTING_FLAGS: [usize; 1] = [COL_MAP.is_pop]; +const DECREMENTING_FLAGS: [usize; 1] = [COL_MAP.op.pop]; // Operations that increase the stack length by 1, but excluding: // - privileged (kernel-only) operations (superfluous; doesn't affect correctness), // - operations that from userspace to the kernel (required for correctness). // TODO: This list is incomplete. -const INCREMENTING_FLAGS: [usize; 2] = [COL_MAP.is_pc, COL_MAP.is_dup]; +const INCREMENTING_FLAGS: [usize; 2] = [COL_MAP.op.pc, COL_MAP.op.dup]; /// Calculates `lv.stack_len_bounds_aux`. Note that this must be run after decode. pub fn generate(lv: &mut CpuColumnsView) { diff --git a/evm/src/cpu/syscalls.rs b/evm/src/cpu/syscalls.rs index b0b63be8..0ac31ef6 100644 --- a/evm/src/cpu/syscalls.rs +++ b/evm/src/cpu/syscalls.rs @@ -18,9 +18,9 @@ const NUM_SYSCALLS: usize = 3; fn make_syscall_list() -> [(usize, usize); NUM_SYSCALLS] { let kernel = Lazy::force(&KERNEL); [ - (COL_MAP.is_stop, "sys_stop"), - (COL_MAP.is_exp, "sys_exp"), - (COL_MAP.is_invalid, "handle_invalid"), + (COL_MAP.op.stop, "sys_stop"), + (COL_MAP.op.exp, "sys_exp"), + (COL_MAP.op.invalid, "handle_invalid"), ] .map(|(col_index, handler_name)| (col_index, kernel.global_labels[handler_name])) } From ee54b295d9c478ab4e0c1175c788fefbaea237a3 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 29 Sep 2022 15:24:43 +0200 Subject: [PATCH 84/97] Use u64 instead of usize --- plonky2/src/gates/base_sum.rs | 2 +- util/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plonky2/src/gates/base_sum.rs b/plonky2/src/gates/base_sum.rs index 93ac7e8d..5be54eeb 100644 --- a/plonky2/src/gates/base_sum.rs +++ b/plonky2/src/gates/base_sum.rs @@ -34,7 +34,7 @@ impl BaseSumGate { pub fn new_from_config(config: &CircuitConfig) -> Self { let num_limbs = - log_floor(F::ORDER as usize - 1, B).min(config.num_routed_wires - Self::START_LIMBS); + log_floor(F::ORDER - 1, B as u64).min(config.num_routed_wires - Self::START_LIMBS); Self::new(num_limbs) } diff --git a/util/src/lib.rs b/util/src/lib.rs index 3136a4b2..bbc2af98 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -39,11 +39,11 @@ pub fn log2_strict(n: usize) -> usize { } /// Returns the largest integer `i` such that `base**i <= n`. -pub const fn log_floor(n: usize, base: usize) -> usize { +pub const fn log_floor(n: u64, base: u64) -> usize { assert!(n > 0); assert!(base > 1); let mut i = 0; - let mut cur: usize = 1; + let mut cur: u64 = 1; loop { let (mul, overflow) = cur.overflowing_mul(base); if overflow || mul > n { From 8e08b218d22af980887c230e6df1e1727365c0eb Mon Sep 17 00:00:00 2001 From: BGluth Date: Thu, 29 Sep 2022 13:45:46 -0600 Subject: [PATCH 85/97] Trie roots now use `H256` instead of `U256` - `H256` preserves any leading `0`s, which could be critical in some situations. Also just a slightly more appropriate type for hashes. --- evm/src/generation/mod.rs | 20 +++++++++++++------- evm/src/proof.rs | 8 ++++---- evm/src/recursive_verifier.rs | 13 ++++++++----- evm/src/util.rs | 13 ++++++++++++- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 511aa009..fb46e0fa 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use eth_trie_utils::partial_trie::PartialTrie; -use ethereum_types::{Address, H256}; +use ethereum_types::{Address, BigEndianHash, H256}; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; use plonky2::field::types::Field; @@ -86,14 +86,20 @@ pub(crate) fn generate_traces, const D: usize>( }; let trie_roots_before = TrieRoots { - state_root: read_metadata(GlobalMetadata::StateTrieRootDigestBefore), - transactions_root: read_metadata(GlobalMetadata::TransactionsTrieRootDigestBefore), - receipts_root: read_metadata(GlobalMetadata::ReceiptsTrieRootDigestBefore), + state_root: H256::from_uint(&read_metadata(GlobalMetadata::StateTrieRootDigestBefore)), + transactions_root: H256::from_uint(&read_metadata( + GlobalMetadata::TransactionsTrieRootDigestBefore, + )), + receipts_root: H256::from_uint(&read_metadata( + GlobalMetadata::ReceiptsTrieRootDigestBefore, + )), }; let trie_roots_after = TrieRoots { - state_root: read_metadata(GlobalMetadata::StateTrieRootDigestAfter), - transactions_root: read_metadata(GlobalMetadata::TransactionsTrieRootDigestAfter), - receipts_root: read_metadata(GlobalMetadata::ReceiptsTrieRootDigestAfter), + state_root: H256::from_uint(&read_metadata(GlobalMetadata::StateTrieRootDigestAfter)), + transactions_root: H256::from_uint(&read_metadata( + GlobalMetadata::TransactionsTrieRootDigestAfter, + )), + receipts_root: H256::from_uint(&read_metadata(GlobalMetadata::ReceiptsTrieRootDigestAfter)), }; let GenerationState { diff --git a/evm/src/proof.rs b/evm/src/proof.rs index 81614e67..de00abfc 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -1,4 +1,4 @@ -use ethereum_types::{Address, U256}; +use ethereum_types::{Address, H256, U256}; use itertools::Itertools; use maybe_rayon::*; use plonky2::field::extension::{Extendable, FieldExtension}; @@ -54,9 +54,9 @@ pub struct PublicValues { #[derive(Debug, Clone, Default)] pub struct TrieRoots { - pub state_root: U256, - pub transactions_root: U256, - pub receipts_root: U256, + pub state_root: H256, + pub transactions_root: H256, + pub receipts_root: H256, } #[derive(Debug, Clone, Default, Deserialize, Serialize)] diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index 000efce9..35041a48 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -11,7 +11,6 @@ use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; use plonky2::util::reducing::ReducingFactorTarget; use plonky2::with_context; -use crate::all_stark::{AllStark, Table}; use crate::config::StarkConfig; use crate::constraint_consumer::RecursiveConstraintConsumer; use crate::cpu::cpu_stark::CpuStark; @@ -27,9 +26,13 @@ use crate::proof::{ StarkProofChallengesTarget, StarkProofTarget, TrieRoots, TrieRootsTarget, }; use crate::stark::Stark; -use crate::util::{h160_limbs, u256_limbs}; +use crate::util::h160_limbs; use crate::vanishing_poly::eval_vanishing_poly_circuit; use crate::vars::StarkEvaluationTargets; +use crate::{ + all_stark::{AllStark, Table}, + util::h256_limbs, +}; pub fn verify_proof_circuit< F: RichField + Extendable, @@ -504,15 +507,15 @@ pub fn set_trie_roots_target( { witness.set_target_arr( trie_roots_target.state_root, - u256_limbs(trie_roots.state_root), + h256_limbs(trie_roots.state_root), ); witness.set_target_arr( trie_roots_target.transactions_root, - u256_limbs(trie_roots.transactions_root), + h256_limbs(trie_roots.transactions_root), ); witness.set_target_arr( trie_roots_target.receipts_root, - u256_limbs(trie_roots.receipts_root), + h256_limbs(trie_roots.receipts_root), ); } diff --git a/evm/src/util.rs b/evm/src/util.rs index 12aead46..7f958fd2 100644 --- a/evm/src/util.rs +++ b/evm/src/util.rs @@ -1,6 +1,6 @@ use std::mem::{size_of, transmute_copy, ManuallyDrop}; -use ethereum_types::{H160, U256}; +use ethereum_types::{H160, H256, U256}; use itertools::Itertools; use plonky2::field::extension::Extendable; use plonky2::field::packed::PackedField; @@ -59,6 +59,17 @@ pub(crate) fn u256_limbs(u256: U256) -> [F; 8] { .unwrap() } +/// Returns the 32-bit little-endian limbs of a `H256`. +pub(crate) fn h256_limbs(h256: H256) -> [F; 8] { + h256.0 + .chunks(4) + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) + .map(F::from_canonical_u32) + .collect_vec() + .try_into() + .unwrap() +} + /// Returns the 32-bit limbs of a `U160`. pub(crate) fn h160_limbs(h160: H160) -> [F; 5] { h160.0 From 7e6844963aec56123452d2c052ccc93bfa953bad Mon Sep 17 00:00:00 2001 From: BGluth Date: Thu, 29 Sep 2022 17:24:23 -0600 Subject: [PATCH 86/97] Few small changes related to switching to `H256` --- evm/Cargo.toml | 4 ++-- evm/src/cpu/kernel/interpreter.rs | 4 ++-- evm/src/generation/mpt.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/evm/Cargo.toml b/evm/Cargo.toml index 0c1e651e..6db81902 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -7,11 +7,11 @@ edition = "2021" [dependencies] plonky2 = { path = "../plonky2", default-features = false, features = ["rand", "timing"] } plonky2_util = { path = "../util" } -eth-trie-utils = { git = "https://github.com/mir-protocol/eth-trie-utils.git", rev = "c52a04c9f349ac812b886f383a7306b27c8b96dc" } +eth-trie-utils = { git = "https://github.com/mir-protocol/eth-trie-utils.git", rev = "dd3595b4ba7923f8d465450d210f17a2b4e20f96" } maybe_rayon = { path = "../maybe_rayon" } anyhow = "1.0.40" env_logger = "0.9.0" -ethereum-types = "0.13.1" +ethereum-types = "0.14.0" hex = { version = "0.4.3", optional = true } hex-literal = "0.3.4" itertools = "0.10.3" diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 7577b974..877e24ab 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use anyhow::{anyhow, bail, ensure}; -use ethereum_types::{BigEndianHash, U256, U512}; +use ethereum_types::{U256, U512}; use keccak_hash::keccak; use plonky2::field::goldilocks_field::GoldilocksField; @@ -444,7 +444,7 @@ impl<'a> Interpreter<'a> { }) .collect::>(); let hash = keccak(bytes); - self.push(hash.into_uint()); + self.push(U256::from_big_endian(hash.as_bytes())); } fn run_prover_input(&mut self) -> anyhow::Result<()> { diff --git a/evm/src/generation/mpt.rs b/evm/src/generation/mpt.rs index f689b613..0bdfede4 100644 --- a/evm/src/generation/mpt.rs +++ b/evm/src/generation/mpt.rs @@ -52,7 +52,7 @@ pub(crate) fn mpt_prover_inputs( prover_inputs.push((PartialTrieType::of(trie) as u32).into()); match trie { PartialTrie::Empty => {} - PartialTrie::Hash(h) => prover_inputs.push(*h), + PartialTrie::Hash(h) => prover_inputs.push(U256::from_big_endian(h.as_bytes())), PartialTrie::Branch { children, value } => { for child in children { mpt_prover_inputs(child, prover_inputs, parse_leaf); From c721155e23a66c0c141019d64aaf19cffc5c6450 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 29 Sep 2022 23:09:32 -0700 Subject: [PATCH 87/97] Main function, txn processing loop --- evm/src/all_stark.rs | 6 +-- evm/src/cpu/control_flow.rs | 14 ++--- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/keccak.asm | 8 --- evm/src/cpu/kernel/asm/main.asm | 8 +++ evm/src/cpu/kernel/asm/rlp/read_to_memory.asm | 4 +- .../cpu/kernel/asm/transactions/router.asm | 14 ++--- .../cpu/kernel/asm/transactions/type_0.asm | 52 +++++++++---------- .../cpu/kernel/asm/transactions/type_1.asm | 2 +- .../cpu/kernel/asm/transactions/type_2.asm | 2 +- .../transaction_parsing/parse_type_0_txn.rs | 3 +- evm/src/generation/mod.rs | 1 + evm/src/generation/prover_input.rs | 19 +++++++ evm/src/generation/rlp.rs | 18 +++++++ evm/src/generation/state.rs | 9 ++++ 15 files changed, 105 insertions(+), 56 deletions(-) delete mode 100644 evm/src/cpu/kernel/asm/keccak.asm create mode 100644 evm/src/cpu/kernel/asm/main.asm create mode 100644 evm/src/generation/rlp.rs diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 2786c36a..0c2516e5 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -338,7 +338,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x5b); row.is_cpu_cycle = F::ONE; row.is_kernel_mode = F::ONE; - row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"]); + row.program_counter = F::from_canonical_usize(KERNEL.global_labels["main"]); cpu_stark.generate(row.borrow_mut()); cpu_trace_rows.push(row.into()); } @@ -377,8 +377,8 @@ mod tests { row.is_cpu_cycle = F::ONE; row.is_kernel_mode = F::ONE; - // Since these are the first cycle rows, we must start with PC=route_txn then increment. - row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"] + i); + // Since these are the first cycle rows, we must start with PC=main then increment. + row.program_counter = F::from_canonical_usize(KERNEL.global_labels["main"] + i); row.opcode_bits = bits_from_opcode( if logic_trace[logic::columns::IS_AND].values[i] != F::ZERO { 0x16 diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index 4ab9b91a..3856726c 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -69,12 +69,12 @@ pub fn eval_packed_generic( ); // If a non-CPU cycle row is followed by a CPU cycle row, then: - // - the `program_counter` of the CPU cycle row is `route_txn` (the entry point of our kernel), + // - the `program_counter` of the CPU cycle row is `main` (the entry point of our kernel), // - execution is in kernel mode, and // - the stack is empty. let is_last_noncpu_cycle = (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle; let pc_diff = - nv.program_counter - P::Scalar::from_canonical_usize(KERNEL.global_labels["route_txn"]); + nv.program_counter - P::Scalar::from_canonical_usize(KERNEL.global_labels["main"]); yield_constr.constraint_transition(is_last_noncpu_cycle * pc_diff); yield_constr.constraint_transition(is_last_noncpu_cycle * (nv.is_kernel_mode - P::ONES)); yield_constr.constraint_transition(is_last_noncpu_cycle * nv.stack_len); @@ -118,18 +118,18 @@ pub fn eval_ext_circuit, const D: usize>( } // If a non-CPU cycle row is followed by a CPU cycle row, then: - // - the `program_counter` of the CPU cycle row is `route_txn` (the entry point of our kernel), + // - the `program_counter` of the CPU cycle row is `main` (the entry point of our kernel), // - execution is in kernel mode, and // - the stack is empty. { let is_last_noncpu_cycle = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle); - // Start at `route_txn`. - let route_txn = builder.constant_extension(F::Extension::from_canonical_usize( - KERNEL.global_labels["route_txn"], + // Start at `main`. + let main = builder.constant_extension(F::Extension::from_canonical_usize( + KERNEL.global_labels["main"], )); - let pc_diff = builder.sub_extension(nv.program_counter, route_txn); + let pc_diff = builder.sub_extension(nv.program_counter, main); let pc_constr = builder.mul_extension(is_last_noncpu_cycle, pc_diff); yield_constr.constraint_transition(builder, pc_constr); diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 5d025ac2..efaa621d 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -33,6 +33,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/curve/secp256k1/moddiv.asm"), include_str!("asm/exp.asm"), include_str!("asm/halt.asm"), + include_str!("asm/main.asm"), include_str!("asm/memory/core.asm"), include_str!("asm/memory/memcpy.asm"), include_str!("asm/memory/metadata.asm"), diff --git a/evm/src/cpu/kernel/asm/keccak.asm b/evm/src/cpu/kernel/asm/keccak.asm deleted file mode 100644 index d464bb6a..00000000 --- a/evm/src/cpu/kernel/asm/keccak.asm +++ /dev/null @@ -1,8 +0,0 @@ -// Computes the Keccak256 hash of some arbitrary bytes in memory. -// The given memory values should be in the range of a byte. -// -// Pre stack: ADDR, len, retdest -// Post stack: hash -global keccak_general: - // stack: ADDR, len - // TODO diff --git a/evm/src/cpu/kernel/asm/main.asm b/evm/src/cpu/kernel/asm/main.asm new file mode 100644 index 00000000..d0402f17 --- /dev/null +++ b/evm/src/cpu/kernel/asm/main.asm @@ -0,0 +1,8 @@ +global main: + // If the prover has no more txns for us to process, halt. + PROVER_INPUT(end_of_txns) + %jumpi(halt) + + // Call route_txn, returning to main to continue the loop. + PUSH main + %jump(route_txn) diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm index db474b9b..189edd1d 100644 --- a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -6,7 +6,7 @@ global read_rlp_to_memory: // stack: retdest - PROVER_INPUT // Read the RLP blob length from the prover tape. + PROVER_INPUT(rlp) // Read the RLP blob length from the prover tape. // stack: len, retdest PUSH 0 // initial position // stack: pos, len, retdest @@ -19,7 +19,7 @@ read_rlp_to_memory_loop: // stack: pos == len, pos, len, retdest %jumpi(read_rlp_to_memory_finish) // stack: pos, len, retdest - PROVER_INPUT + PROVER_INPUT(rlp) // stack: byte, pos, len, retdest DUP2 // stack: pos, byte, pos, len, retdest diff --git a/evm/src/cpu/kernel/asm/transactions/router.asm b/evm/src/cpu/kernel/asm/transactions/router.asm index 47a899c9..974fed99 100644 --- a/evm/src/cpu/kernel/asm/transactions/router.asm +++ b/evm/src/cpu/kernel/asm/transactions/router.asm @@ -3,14 +3,14 @@ // jump to the appropriate transaction parsing method. global route_txn: - // stack: (empty) + // stack: retdest // First load transaction data into memory, where it will be parsed. PUSH read_txn_from_memory %jump(read_rlp_to_memory) // At this point, the raw txn data is in memory. read_txn_from_memory: - // stack: (empty) + // stack: retdest // We will peak at the first byte to determine what type of transaction this is. // Note that type 1 and 2 transactions have a first byte of 1 and 2, respectively. @@ -20,17 +20,17 @@ read_txn_from_memory: PUSH 0 %mload_current(@SEGMENT_RLP_RAW) %eq_const(1) - // stack: first_byte == 1 + // stack: first_byte == 1, retdest %jumpi(process_type_1_txn) - // stack: (empty) + // stack: retdest PUSH 0 %mload_current(@SEGMENT_RLP_RAW) %eq_const(2) - // stack: first_byte == 2 + // stack: first_byte == 2, retdest %jumpi(process_type_2_txn) - // stack: (empty) + // stack: retdest // At this point, since it's not a type 1 or 2 transaction, // it must be a legacy (aka type 0) transaction. - %jump(process_type_2_txn) + %jump(process_type_0_txn) diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm index 3f258624..7bc7a399 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -12,15 +12,15 @@ // keccak256(rlp([nonce, gas_price, gas_limit, to, value, data])) global process_type_0_txn: - // stack: (empty) + // stack: retdest PUSH 0 // initial pos - // stack: pos + // stack: pos, retdest %decode_rlp_list_len // We don't actually need the length. %stack (pos, len) -> (pos) // Decode the nonce and store it. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, nonce) -> (nonce, pos) %mstore_txn_field(@TXN_FIELD_NONCE) @@ -29,38 +29,38 @@ global process_type_0_txn: // For legacy transactions, we set both the // TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS and TXN_FIELD_MAX_FEE_PER_GAS // fields to gas_price. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, gas_price) -> (gas_price, gas_price, pos) %mstore_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) %mstore_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) // Decode the gas limit and store it. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, gas_limit) -> (gas_limit, pos) %mstore_txn_field(@TXN_FIELD_GAS_LIMIT) // Decode the "to" field and store it. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, to) -> (to, pos) %mstore_txn_field(@TXN_FIELD_TO) // Decode the value field and store it. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, value) -> (value, pos) %mstore_txn_field(@TXN_FIELD_VALUE) // Decode the data length, store it, and compute new_pos after any data. - // stack: pos + // stack: pos, retdest %decode_rlp_string_len %stack (pos, data_len) -> (data_len, pos, data_len, pos, data_len) %mstore_txn_field(@TXN_FIELD_DATA_LEN) - // stack: pos, data_len, pos, data_len + // stack: pos, data_len, pos, data_len, retdest ADD - // stack: new_pos, pos, data_len + // stack: new_pos, pos, data_len, retdest // Memcpy the txn data from @SEGMENT_RLP_RAW to @SEGMENT_TXN_DATA. PUSH parse_v @@ -70,62 +70,62 @@ global process_type_0_txn: PUSH 0 PUSH @SEGMENT_TXN_DATA GET_CONTEXT - // stack: DST, SRC, data_len, parse_v, new_pos + // stack: DST, SRC, data_len, parse_v, new_pos, retdest %jump(memcpy) parse_v: - // stack: pos + // stack: pos, retdest %decode_rlp_scalar - // stack: pos, v + // stack: pos, v, retdest SWAP1 - // stack: v, pos + // stack: v, pos, retdest DUP1 %gt_const(28) - // stack: v > 28, v, pos + // stack: v > 28, v, pos, retdest %jumpi(process_v_new_style) // We have an old style v, so y_parity = v - 27. // No chain ID is present, so we can leave TXN_FIELD_CHAIN_ID_PRESENT and // TXN_FIELD_CHAIN_ID with their default values of zero. - // stack: v, pos + // stack: v, pos, retdest %sub_const(27) %stack (y_parity, pos) -> (y_parity, pos) %mstore_txn_field(@TXN_FIELD_Y_PARITY) - // stack: pos + // stack: pos, retdest %jump(parse_r) process_v_new_style: - // stack: v, pos + // stack: v, pos, retdest // We have a new style v, so chain_id_present = 1, // chain_id = (v - 35) / 2, and y_parity = (v - 35) % 2. %stack (v, pos) -> (1, v, pos) %mstore_txn_field(@TXN_FIELD_CHAIN_ID_PRESENT) - // stack: v, pos + // stack: v, pos, retdest %sub_const(35) DUP1 - // stack: v - 35, v - 35, pos + // stack: v - 35, v - 35, pos, retdest %div_const(2) - // stack: chain_id, v - 35, pos + // stack: chain_id, v - 35, pos, retdest %mstore_txn_field(@TXN_FIELD_CHAIN_ID) - // stack: v - 35, pos + // stack: v - 35, pos, retdest %mod_const(2) - // stack: y_parity, pos + // stack: y_parity, pos, retdest %mstore_txn_field(@TXN_FIELD_Y_PARITY) parse_r: - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, r) -> (r, pos) %mstore_txn_field(@TXN_FIELD_R) - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, s) -> (s) %mstore_txn_field(@TXN_FIELD_S) - // stack: (empty) + // stack: retdest // TODO: Write the signed txn data to memory, where it can be hashed and // checked against the signature. diff --git a/evm/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm index 9d45c1e4..8c7fcaae 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_1.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_1.asm @@ -7,5 +7,5 @@ // data, access_list])) global process_type_1_txn: - // stack: (empty) + // stack: retdest PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm index b2a862c1..f1ff18d8 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -8,5 +8,5 @@ // access_list])) global process_type_2_txn: - // stack: (empty) + // stack: retdest PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs b/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs index c01474ce..53a3d282 100644 --- a/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs +++ b/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs @@ -12,7 +12,8 @@ fn process_type_0_txn() -> Result<()> { let process_type_0_txn = KERNEL.global_labels["process_type_0_txn"]; let process_normalized_txn = KERNEL.global_labels["process_normalized_txn"]; - let mut interpreter = Interpreter::new_with_kernel(process_type_0_txn, vec![]); + let retaddr = 0xDEADBEEFu32.into(); + let mut interpreter = Interpreter::new_with_kernel(process_type_0_txn, vec![retaddr]); // When we reach process_normalized_txn, we're done with parsing and normalizing. // Processing normalized transactions is outside the scope of this test. diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 511aa009..2d0a9f7c 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -23,6 +23,7 @@ use crate::util::trace_rows_to_poly_values; pub(crate) mod memory; pub(crate) mod mpt; pub(crate) mod prover_input; +pub(crate) mod rlp; pub(crate) mod state; #[derive(Clone, Debug, Deserialize, Serialize, Default)] diff --git a/evm/src/generation/prover_input.rs b/evm/src/generation/prover_input.rs index c6051a4d..d5d7df7c 100644 --- a/evm/src/generation/prover_input.rs +++ b/evm/src/generation/prover_input.rs @@ -24,12 +24,24 @@ impl GenerationState { #[allow(unused)] // TODO: Should be used soon. pub(crate) fn prover_input(&mut self, stack: &[U256], input_fn: &ProverInputFn) -> U256 { match input_fn.0[0].as_str() { + "end_of_txns" => self.run_end_of_txns(), "ff" => self.run_ff(stack, input_fn), "mpt" => self.run_mpt(), + "rlp" => self.run_rlp(), _ => panic!("Unrecognized prover input function."), } } + fn run_end_of_txns(&mut self) -> U256 { + let end = self.next_txn_index == self.inputs.signed_txns.len(); + if end { + U256::one() + } else { + self.next_txn_index += 1; + U256::zero() + } + } + /// Finite field operations. fn run_ff(&self, stack: &[U256], input_fn: &ProverInputFn) -> U256 { let field = EvmField::from_str(input_fn.0[1].as_str()).unwrap(); @@ -44,6 +56,13 @@ impl GenerationState { .pop() .unwrap_or_else(|| panic!("Out of MPT data")) } + + /// RLP data. + fn run_rlp(&mut self) -> U256 { + self.rlp_prover_inputs + .pop() + .unwrap_or_else(|| panic!("Out of RLP data")) + } } enum EvmField { diff --git a/evm/src/generation/rlp.rs b/evm/src/generation/rlp.rs new file mode 100644 index 00000000..f28272a2 --- /dev/null +++ b/evm/src/generation/rlp.rs @@ -0,0 +1,18 @@ +use ethereum_types::U256; + +pub(crate) fn all_rlp_prover_inputs_reversed(signed_txns: &[Vec]) -> Vec { + let mut inputs = all_rlp_prover_inputs(signed_txns); + inputs.reverse(); + inputs +} + +fn all_rlp_prover_inputs(signed_txns: &[Vec]) -> Vec { + let mut prover_inputs = vec![]; + for txn in signed_txns { + prover_inputs.push(txn.len().into()); + for &byte in txn { + prover_inputs.push(byte.into()); + } + } + prover_inputs +} diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index b8ae9735..17d63018 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -7,6 +7,7 @@ use tiny_keccak::keccakf; use crate::cpu::columns::{CpuColumnsView, NUM_CPU_COLUMNS}; use crate::generation::memory::MemoryState; use crate::generation::mpt::all_mpt_prover_inputs_reversed; +use crate::generation::rlp::all_rlp_prover_inputs_reversed; use crate::generation::GenerationInputs; use crate::keccak_memory::keccak_memory_stark::KeccakMemoryOp; use crate::memory::memory_stark::MemoryOp; @@ -19,6 +20,7 @@ use crate::{keccak, logic}; pub(crate) struct GenerationState { #[allow(unused)] // TODO: Should be used soon. pub(crate) inputs: GenerationInputs, + pub(crate) next_txn_index: usize, pub(crate) cpu_rows: Vec<[F; NUM_CPU_COLUMNS]>, pub(crate) current_cpu_row: CpuColumnsView, @@ -32,14 +34,20 @@ pub(crate) struct GenerationState { /// Prover inputs containing MPT data, in reverse order so that the next input can be obtained /// via `pop()`. pub(crate) mpt_prover_inputs: Vec, + + /// Prover inputs containing RLP data, in reverse order so that the next input can be obtained + /// via `pop()`. + pub(crate) rlp_prover_inputs: Vec, } impl GenerationState { pub(crate) fn new(inputs: GenerationInputs) -> Self { let mpt_prover_inputs = all_mpt_prover_inputs_reversed(&inputs.tries); + let rlp_prover_inputs = all_rlp_prover_inputs_reversed(&inputs.signed_txns); Self { inputs, + next_txn_index: 0, cpu_rows: vec![], current_cpu_row: [F::ZERO; NUM_CPU_COLUMNS].into(), current_context: 0, @@ -48,6 +56,7 @@ impl GenerationState { keccak_memory_inputs: vec![], logic_ops: vec![], mpt_prover_inputs, + rlp_prover_inputs, } } From ea135341e8e82d4550beebbbd253769550c92e73 Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Thu, 29 Sep 2022 23:35:02 -0700 Subject: [PATCH 88/97] MSIZE --- evm/src/cpu/kernel/interpreter.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 7577b974..7075b4d0 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -263,7 +263,7 @@ impl<'a> Interpreter<'a> { 0x56 => self.run_jump(), // "JUMP", 0x57 => self.run_jumpi(), // "JUMPI", 0x58 => todo!(), // "GETPC", - 0x59 => todo!(), // "MSIZE", + 0x59 => self.run_msize(), // "MSIZE", 0x5a => todo!(), // "GAS", 0x5b => self.run_jumpdest(), // "JUMPDEST", 0x5c => todo!(), // "GET_STATE_ROOT", @@ -511,6 +511,14 @@ impl<'a> Interpreter<'a> { } } + fn run_msize(&mut self) { + let num_u256s = self.memory.context_memory[0].segments[Segment::MainMemory as usize] + .content + .len(); + let num_bytes = num_u256s * 32; + self.push(U256::from(num_bytes)); + } + fn run_jumpdest(&mut self) { assert!(!self.kernel_mode, "JUMPDEST is not needed in kernel code"); } From f6ff07840f1d2c90348ffdf0412150e07c0e7cce Mon Sep 17 00:00:00 2001 From: Nicholas Ward Date: Fri, 30 Sep 2022 09:05:18 -0700 Subject: [PATCH 89/97] fixes --- evm/src/cpu/kernel/interpreter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 7075b4d0..fef15a4a 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -512,10 +512,10 @@ impl<'a> Interpreter<'a> { } fn run_msize(&mut self) { - let num_u256s = self.memory.context_memory[0].segments[Segment::MainMemory as usize] + let num_bytes = self.memory.context_memory[self.context].segments + [Segment::MainMemory as usize] .content .len(); - let num_bytes = num_u256s * 32; self.push(U256::from(num_bytes)); } From 1dbd96ba2052f30ba87aee177698890299f97545 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 30 Sep 2022 12:48:42 -0700 Subject: [PATCH 90/97] Empty txn list test (disabled for now) --- evm/tests/empty_txn_list.rs | 81 +++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 evm/tests/empty_txn_list.rs diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs new file mode 100644 index 00000000..a9c71026 --- /dev/null +++ b/evm/tests/empty_txn_list.rs @@ -0,0 +1,81 @@ +use std::collections::HashMap; + +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::plonk::config::PoseidonGoldilocksConfig; +use plonky2::util::timing::TimingTree; +use plonky2_evm::all_stark::AllStark; +use plonky2_evm::config::StarkConfig; +use plonky2_evm::generation::{GenerationInputs, TrieInputs}; +use plonky2_evm::proof::BlockMetadata; +use plonky2_evm::prover::prove; +use plonky2_evm::verifier::verify_proof; + +type F = GoldilocksField; +const D: usize = 2; +type C = PoseidonGoldilocksConfig; + +/// Execute the empty list of transactions, i.e. a no-op. +#[test] +#[ignore] // TODO: Won't work until storage, etc. are implemented. +fn test_empty_txn_list() -> anyhow::Result<()> { + let all_stark = AllStark::::default(); + let config = StarkConfig::standard_fast_config(); + + let block_metadata = BlockMetadata::default(); + + let state_trie = PartialTrie::Leaf { + nibbles: Nibbles { + count: 5, + packed: 0xABCDE.into(), + }, + value: vec![1, 2, 3], + }; + let transactions_trie = PartialTrie::Empty; + let receipts_trie = PartialTrie::Empty; + let storage_tries = vec![]; + + let state_trie_root = state_trie.calc_hash(); + let txns_trie_root = transactions_trie.calc_hash(); + let receipts_trie_root = receipts_trie.calc_hash(); + + let inputs = GenerationInputs { + signed_txns: vec![], + tries: TrieInputs { + state_trie, + transactions_trie, + receipts_trie, + storage_tries, + }, + contract_code: HashMap::new(), + block_metadata, + }; + + let proof = prove::(&all_stark, &config, inputs, &mut TimingTree::default())?; + assert_eq!( + proof.public_values.trie_roots_before.state_root, + state_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_after.state_root, + state_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_before.transactions_root, + txns_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_after.transactions_root, + txns_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_before.receipts_root, + receipts_trie_root + ); + assert_eq!( + proof.public_values.trie_roots_after.receipts_root, + receipts_trie_root + ); + + verify_proof(all_stark, proof, &config) +} From 8b58725fa8608fbe73fc17a6d806c24787899752 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 30 Sep 2022 10:22:46 -0700 Subject: [PATCH 91/97] Fix bootstrap channel indices --- evm/src/cpu/bootstrap_kernel.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/evm/src/cpu/bootstrap_kernel.rs b/evm/src/cpu/bootstrap_kernel.rs index 533589af..dd52f166 100644 --- a/evm/src/cpu/bootstrap_kernel.rs +++ b/evm/src/cpu/bootstrap_kernel.rs @@ -17,7 +17,6 @@ use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::keccak_util::keccakf_u32s; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; -use crate::memory::NUM_CHANNELS; use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars}; /// The Keccak rate (1088 bits), measured in bytes. @@ -47,8 +46,7 @@ pub(crate) fn generate_bootstrap_kernel(state: &mut GenerationState // Write this chunk to memory, while simultaneously packing its bytes into a u32 word. let mut packed_bytes: u32 = 0; - for (addr, byte) in chunk { - let channel = addr % NUM_CHANNELS; + for (channel, (addr, byte)) in chunk.enumerate() { state.set_mem_cpu_current(channel, Segment::Code, addr, byte.into()); packed_bytes = (packed_bytes << 8) | byte as u32; From 12247047ae88540365752e2343c7f66b090182d3 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 30 Sep 2022 13:04:16 -0700 Subject: [PATCH 92/97] MPT hashing logic, part 1 --- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/main.asm | 18 ++++++-- evm/src/cpu/kernel/asm/memory/core.asm | 7 +++ evm/src/cpu/kernel/asm/mpt/hash.asm | 48 +++++++++++++++++++- evm/src/cpu/kernel/asm/mpt/hex_prefix.asm | 51 ++++++++++++++++++++++ evm/src/cpu/kernel/asm/mpt/load.asm | 1 - evm/src/cpu/kernel/constants/mod.rs | 11 +++++ evm/src/cpu/kernel/interpreter.rs | 4 ++ evm/src/cpu/kernel/tests/mpt/hex_prefix.rs | 50 +++++++++++++++++++++ evm/src/cpu/kernel/tests/mpt/mod.rs | 1 + 10 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/mpt/hex_prefix.asm create mode 100644 evm/src/cpu/kernel/tests/mpt/hex_prefix.rs diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index efaa621d..76091a2e 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -43,6 +43,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/rlp/decode.asm"), include_str!("asm/rlp/read_to_memory.asm"), include_str!("asm/mpt/hash.asm"), + include_str!("asm/mpt/hex_prefix.asm"), include_str!("asm/mpt/load.asm"), include_str!("asm/mpt/read.asm"), include_str!("asm/mpt/storage_read.asm"), diff --git a/evm/src/cpu/kernel/asm/main.asm b/evm/src/cpu/kernel/asm/main.asm index d0402f17..a27a1527 100644 --- a/evm/src/cpu/kernel/asm/main.asm +++ b/evm/src/cpu/kernel/asm/main.asm @@ -1,8 +1,20 @@ global main: + // First, load all MPT data from the prover. + PUSH txn_loop + %jump(load_all_mpts) + +hash_initial_tries: + // TODO: Hash each trie and set @GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE, etc. + +txn_loop: // If the prover has no more txns for us to process, halt. PROVER_INPUT(end_of_txns) - %jumpi(halt) + %jumpi(hash_final_tries) - // Call route_txn, returning to main to continue the loop. - PUSH main + // Call route_txn. When we return, continue the txn loop. + PUSH txn_loop %jump(route_txn) + +hash_final_tries: + // TODO: Hash each trie and set @GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER, etc. + %jump(halt) diff --git a/evm/src/cpu/kernel/asm/memory/core.asm b/evm/src/cpu/kernel/asm/memory/core.asm index 73bafbee..f18a620a 100644 --- a/evm/src/cpu/kernel/asm/memory/core.asm +++ b/evm/src/cpu/kernel/asm/memory/core.asm @@ -98,3 +98,10 @@ %mstore_kernel(@SEGMENT_CODE) // stack: (empty) %endmacro + +// Store a single value to @SEGMENT_KERNEL_GENERAL. +%macro mstore_kernel_general + // stack: offset, value + %mstore_kernel(@SEGMENT_KERNEL_GENERAL) + // stack: (empty) +%endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index 41795d5d..b41e3dbf 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -1,2 +1,48 @@ global mpt_hash: - // TODO + // stack: node_ptr, retdest + DUP1 + %mload_trie_data + // stack: node_type, node_ptr, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %add_const(1) SWAP1 + // stack: node_type, node_payload_ptr, retdest + + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_hash_empty) + DUP1 %eq_const(@MPT_NODE_HASH) %jumpi(mpt_hash_hash) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_hash_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_hash_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_hash_leaf) + PANIC // Invalid node type? Shouldn't get here. + +mpt_hash_empty: + %stack (node_type, node_payload_ptr, retdest) -> (retdest, @EMPTY_NODE_HASH) + JUMP + +mpt_hash_hash: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + %mload_trie_data + // stack: hash, retdest + SWAP1 + JUMP + +mpt_hash_branch: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + PANIC // TODO + +mpt_hash_extension: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + PANIC // TODO + +mpt_hash_leaf: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + DUP1 %mload_trie_data + // stack: node_nibbles, node_payload_ptr, retdest + PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm new file mode 100644 index 00000000..a1319748 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm @@ -0,0 +1,51 @@ +// Computes the hex-prefix encoding of the given nibble list and termination +// flag. Writes the result to the @KERNEL_GENERAL segment of memory, and returns +// its length on the stack. +global hex_prefix: + // stack: num_nibbles, packed_nibbles, terminated, retdest + // We will iterate backwards, from i = num_nibbles/2 to i=0, so that we can + // take nibbles from the least-significant end of packed_nibbles. + PUSH 2 DUP2 DIV // i = num_nibbles / 2 + // stack: i, num_nibbles, packed_nibbles, terminated, retdest + +loop: + // If i == 0, break to first_byte. + DUP1 ISZERO %jumpi(first_byte) + + // stack: i, num_nibbles, packed_nibbles, terminated, retdest + DUP3 // packed_nibbles + %and_const(0xFF) + // stack: byte_i, i, num_nibbles, packed_nibbles, terminated, retdest + DUP2 // i + %mstore_kernel_general + // stack: i, num_nibbles, packed_nibbles, terminated, retdest + %sub_const(1) + SWAP2 %shr_const(8) SWAP2 // packed_nibbles >>= 8 + // stack: i, num_nibbles, packed_nibbles, terminated, retdest + %jump(loop) + +first_byte: + // stack: 0, num_nibbles, first_nibble_or_zero, terminated, retdest + POP + DUP1 + // stack: num_nibbles, num_nibbles, first_nibble_or_zero, terminated, retdest + %div_const(2) + %add_const(1) + // stack: result_len, num_nibbles, first_nibble_or_zero, terminated, retdest + SWAP3 + // stack: terminated, num_nibbles, first_nibble_or_zero, result_len, retdest + %mul_const(2) + SWAP1 + // stack: num_nibbles, terminated * 2, first_nibble_or_zero, result_len, retdest + %mod_const(2) + ADD + // stack: parity + terminated * 2, first_nibble_or_zero, result_len, retdest + %mul_const(16) + ADD + // stack: 16 * (parity + terminated * 2) + first_nibble_or_zero, result_len, retdest + PUSH 0 + %mstore_kernel_general + + // stack: result_len, retdest + SWAP1 + JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index 57c84ddb..94d05dde 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -39,7 +39,6 @@ storage_trie_loop: // stack: i, num_storage_tries, retdest %jump(storage_trie_loop) storage_trie_loop_end: - // TODO: Hash tries and set @GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE, etc. // stack: i, num_storage_tries, retdest %pop2 // stack: retdest diff --git a/evm/src/cpu/kernel/constants/mod.rs b/evm/src/cpu/kernel/constants/mod.rs index 0db347cf..2694b82a 100644 --- a/evm/src/cpu/kernel/constants/mod.rs +++ b/evm/src/cpu/kernel/constants/mod.rs @@ -18,6 +18,9 @@ pub fn evm_constants() -> HashMap { for (name, value) in EC_CONSTANTS { c.insert(name.into(), U256::from_big_endian(&value)); } + for (name, value) in HASH_CONSTANTS { + c.insert(name.into(), U256::from_big_endian(&value)); + } for (name, value) in GAS_CONSTANTS { c.insert(name.into(), U256::from(value)); } @@ -43,6 +46,14 @@ pub fn evm_constants() -> HashMap { c } +const HASH_CONSTANTS: [(&str, [u8; 32]); 1] = [ + // Hash of an empty node: keccak(rlp.encode(b'')).hex() + ( + "EMPTY_NODE_HASH", + hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + ), +]; + const EC_CONSTANTS: [(&str, [u8; 32]); 3] = [ ( "BN_BASE", diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 8a898cb3..04fc5ced 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -161,6 +161,10 @@ impl<'a> Interpreter<'a> { &self.memory.context_memory[0].segments[Segment::TrieData as usize].content } + pub(crate) fn get_kernel_general_data(&self) -> &[U256] { + &self.memory.context_memory[0].segments[Segment::KernelGeneral as usize].content + } + pub(crate) fn get_rlp_memory(&self) -> Vec { self.memory.context_memory[self.context].segments[Segment::RlpRaw as usize] .content diff --git a/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs new file mode 100644 index 00000000..4318b560 --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs @@ -0,0 +1,50 @@ +use anyhow::Result; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::interpreter::Interpreter; + +#[test] +fn hex_prefix_even_nonterminated() -> Result<()> { + let hex_prefix = KERNEL.global_labels["hex_prefix"]; + + let retdest = 0xDEADBEEFu32.into(); + let terminated = 0.into(); + let packed_nibbles = 0xABCDEF.into(); + let num_nibbles = 6.into(); + let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles]; + let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![4.into()]); + + assert_eq!( + interpreter.get_kernel_general_data(), + vec![0.into(), 0xAB.into(), 0xCD.into(), 0xEF.into(),] + ); + + Ok(()) +} + +#[test] +fn hex_prefix_odd_terminated() -> Result<()> { + let hex_prefix = KERNEL.global_labels["hex_prefix"]; + + let retdest = 0xDEADBEEFu32.into(); + let terminated = 1.into(); + let packed_nibbles = 0xABCDE.into(); + let num_nibbles = 5.into(); + let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles]; + let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![3.into()]); + + assert_eq!( + interpreter.get_kernel_general_data(), + vec![ + (terminated * 2 + 1u32) * 16 + 0xAu32, + 0xBC.into(), + 0xDE.into(), + ] + ); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs index 2d14c89a..3f7ef252 100644 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -1,5 +1,6 @@ use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +mod hex_prefix; mod load; mod read; From f2f05952abb06d41a7f796fecbc9560edf8d9fd4 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 1 Oct 2022 10:33:05 -0700 Subject: [PATCH 93/97] MPT hashing logic, part 2 --- evm/src/cpu/kernel/asm/memory/core.asm | 6 +- evm/src/cpu/kernel/asm/mpt/hash.asm | 82 ++++++++---- evm/src/cpu/kernel/asm/mpt/hex_prefix.asm | 121 +++++++++++++----- evm/src/cpu/kernel/asm/rlp/encode.asm | 67 ++++++++++ evm/src/cpu/kernel/interpreter.rs | 4 - evm/src/cpu/kernel/tests/mpt/hex_prefix.rs | 59 +++++++-- .../kernel/tests/{rlp.rs => rlp/decode.rs} | 78 ----------- evm/src/cpu/kernel/tests/rlp/encode.rs | 110 ++++++++++++++++ evm/src/cpu/kernel/tests/rlp/mod.rs | 2 + 9 files changed, 374 insertions(+), 155 deletions(-) rename evm/src/cpu/kernel/tests/{rlp.rs => rlp/decode.rs} (59%) create mode 100644 evm/src/cpu/kernel/tests/rlp/encode.rs create mode 100644 evm/src/cpu/kernel/tests/rlp/mod.rs diff --git a/evm/src/cpu/kernel/asm/memory/core.asm b/evm/src/cpu/kernel/asm/memory/core.asm index f18a620a..6722b0ca 100644 --- a/evm/src/cpu/kernel/asm/memory/core.asm +++ b/evm/src/cpu/kernel/asm/memory/core.asm @@ -99,9 +99,9 @@ // stack: (empty) %endmacro -// Store a single value to @SEGMENT_KERNEL_GENERAL. -%macro mstore_kernel_general +// Store a single byte to @SEGMENT_RLP_RAW. +%macro mstore_rlp // stack: offset, value - %mstore_kernel(@SEGMENT_KERNEL_GENERAL) + %mstore_kernel(@SEGMENT_RLP_RAW) // stack: (empty) %endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index b41e3dbf..4018a036 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -1,4 +1,12 @@ -global mpt_hash: +// Computes the Merkle root of the given trie node. +// +// The encode_value function should take as input +// - the position withing @SEGMENT_RLP_RAW to write to, +// - the offset of a value within @SEGMENT_TRIE_DATA, and +// - a return address. +// It should serialize the value, write it to @SEGMENT_RLP_RAW starting at the +// given position, and return an updated position (the next unused offset). +%macro mpt_hash(encode_value) // stack: node_ptr, retdest DUP1 %mload_trie_data @@ -9,11 +17,57 @@ global mpt_hash: DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(mpt_hash_empty) DUP1 %eq_const(@MPT_NODE_HASH) %jumpi(mpt_hash_hash) - DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_hash_branch) - DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_hash_extension) - DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_hash_leaf) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(%%mpt_hash_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(%%mpt_hash_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(%%mpt_hash_leaf) PANIC // Invalid node type? Shouldn't get here. +%%mpt_hash_branch: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + PANIC // TODO + +%%mpt_hash_extension: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + PANIC // TODO + +%%mpt_hash_leaf: + // stack: node_type, node_payload_ptr, retdest + POP + // stack: node_payload_ptr, retdest + PUSH %%mpt_hash_leaf_after_hex_prefix // retdest + PUSH 1 // terminated + // stack: terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest + DUP3 %add_const(1) %mload_trie_data // Load the packed_nibbles field, which is at index 1. + // stack: packed_nibbles, terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest + DUP4 %mload_trie_data // Load the num_nibbles field, which is at index 0. + // stack: num_nibbles, packed_nibbles, terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest + PUSH 9 // We start at 9 to leave room to prepend the largest possible RLP list header. + // stack: rlp_start, num_nibbles, packed_nibbles, terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest + %jump(hex_prefix) +%%mpt_hash_leaf_after_hex_prefix: + // stack: rlp_pos, node_payload_ptr, retdest + SWAP1 + %add_const(2) // The value starts at index 2. + %stack (value_ptr, rlp_pos, retdest) + -> (rlp_pos, value_ptr, %%mpt_hash_leaf_after_encode_value, retdest) + %jump($encode_value) +%%mpt_hash_leaf_after_encode_value: + // stack: rlp_end_pos, retdest + %prepend_rlp_list_prefix + // stack: rlp_start_pos, rlp_len, retdest + PUSH $SEGMENT_RLP + PUSH 0 // kernel context + // stack: rlp_start_addr: 3, rlp_len, retdest + KECCAK_GENERAL + // stack: hash, retdest + SWAP + JUMP +%endmacro + mpt_hash_empty: %stack (node_type, node_payload_ptr, retdest) -> (retdest, @EMPTY_NODE_HASH) JUMP @@ -26,23 +80,3 @@ mpt_hash_hash: // stack: hash, retdest SWAP1 JUMP - -mpt_hash_branch: - // stack: node_type, node_payload_ptr, retdest - POP - // stack: node_payload_ptr, retdest - PANIC // TODO - -mpt_hash_extension: - // stack: node_type, node_payload_ptr, retdest - POP - // stack: node_payload_ptr, retdest - PANIC // TODO - -mpt_hash_leaf: - // stack: node_type, node_payload_ptr, retdest - POP - // stack: node_payload_ptr, retdest - DUP1 %mload_trie_data - // stack: node_nibbles, node_payload_ptr, retdest - PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm index a1319748..043974a4 100644 --- a/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm +++ b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm @@ -1,51 +1,104 @@ -// Computes the hex-prefix encoding of the given nibble list and termination -// flag. Writes the result to the @KERNEL_GENERAL segment of memory, and returns -// its length on the stack. -global hex_prefix: - // stack: num_nibbles, packed_nibbles, terminated, retdest - // We will iterate backwards, from i = num_nibbles/2 to i=0, so that we can - // take nibbles from the least-significant end of packed_nibbles. - PUSH 2 DUP2 DIV // i = num_nibbles / 2 - // stack: i, num_nibbles, packed_nibbles, terminated, retdest +// Computes the RLP encoding of the hex-prefix encoding of the given nibble list +// and termination flag. Writes the result to @SEGMENT_RLP_RAW starting at the +// given position, and returns the updated position, i.e. a pointer to the next +// unused offset. +// +// Pre stack: rlp_start_pos, num_nibbles, packed_nibbles, terminated, retdest +// Post stack: rlp_end_pos + +global hex_prefix_rlp: + // stack: rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + // We will iterate backwards, from i = num_nibbles / 2 to i = 0, so that we + // can take nibbles from the least-significant end of packed_nibbles. + PUSH 2 DUP3 DIV // i = num_nibbles / 2 + // stack: i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + + // Compute the length of the hex-prefix string, in bytes: + // hp_len = num_nibbles / 2 + 1 = i + 1 + DUP1 %add_const(1) + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + + // Write the RLP header. + DUP1 %gt_const(55) %jumpi(rlp_header_large) + DUP1 %gt_const(1) %jumpi(rlp_header_medium) + + // The hex-prefix is a single byte. It must be <= 127, since its first + // nibble only has two bits. So this is the "small" RLP string case, where + // the byte is its own RLP encoding. + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + %jump(start_loop) + +rlp_header_medium: + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + DUP1 // value = hp_len + DUP4 // offset = rlp_pos + %mstore_rlp + + // rlp_pos += 1 + SWAP2 %add_const(1) SWAP2 + + %jump(start_loop) + +rlp_header_large: + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + // In practice hex-prefix length will never exceed 256, so the length of the + // length will always be 1 byte in this case. + + PUSH 1 // value = len_of_len = 1 + DUP4 // offset = rlp_pos + %mstore_rlp + + DUP1 // value = hp_len + DUP4 %add_const(1) // offset = rlp_pos + 1 + %mstore_rlp + + // rlp_pos += 2 + SWAP2 %add_const(2) SWAP2 + +start_loop: + // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + SWAP1 loop: + // stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest // If i == 0, break to first_byte. DUP1 ISZERO %jumpi(first_byte) - // stack: i, num_nibbles, packed_nibbles, terminated, retdest - DUP3 // packed_nibbles + // stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + DUP5 // packed_nibbles %and_const(0xFF) - // stack: byte_i, i, num_nibbles, packed_nibbles, terminated, retdest - DUP2 // i - %mstore_kernel_general - // stack: i, num_nibbles, packed_nibbles, terminated, retdest + // stack: byte_i, i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest + DUP4 // rlp_pos + DUP3 // i + ADD // We'll write to offset rlp_pos + i + %mstore_rlp + + // stack: i, hp_len, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest %sub_const(1) - SWAP2 %shr_const(8) SWAP2 // packed_nibbles >>= 8 - // stack: i, num_nibbles, packed_nibbles, terminated, retdest + SWAP4 %shr_const(8) SWAP4 // packed_nibbles >>= 8 %jump(loop) first_byte: - // stack: 0, num_nibbles, first_nibble_or_zero, terminated, retdest + // stack: 0, hp_len, rlp_pos, num_nibbles, first_nibble_or_zero, terminated, retdest POP - DUP1 - // stack: num_nibbles, num_nibbles, first_nibble_or_zero, terminated, retdest - %div_const(2) - %add_const(1) - // stack: result_len, num_nibbles, first_nibble_or_zero, terminated, retdest - SWAP3 - // stack: terminated, num_nibbles, first_nibble_or_zero, result_len, retdest + // stack: hp_len, rlp_pos, num_nibbles, first_nibble_or_zero, terminated, retdest + DUP2 ADD + // stack: rlp_end_pos, rlp_pos, num_nibbles, first_nibble_or_zero, terminated, retdest + SWAP4 + // stack: terminated, rlp_pos, num_nibbles, first_nibble_or_zero, rlp_end_pos, retdest %mul_const(2) - SWAP1 - // stack: num_nibbles, terminated * 2, first_nibble_or_zero, result_len, retdest - %mod_const(2) + // stack: terminated * 2, rlp_pos, num_nibbles, first_nibble_or_zero, rlp_end_pos, retdest + %stack (terminated_x2, rlp_pos, num_nibbles, first_nibble_or_zero) + -> (num_nibbles, terminated_x2, first_nibble_or_zero, rlp_pos) + // stack: num_nibbles, terminated * 2, first_nibble_or_zero, rlp_pos, rlp_end_pos, retdest + %mod_const(2) // parity ADD - // stack: parity + terminated * 2, first_nibble_or_zero, result_len, retdest + // stack: parity + terminated * 2, first_nibble_or_zero, rlp_pos, rlp_end_pos, retdest %mul_const(16) ADD - // stack: 16 * (parity + terminated * 2) + first_nibble_or_zero, result_len, retdest - PUSH 0 - %mstore_kernel_general - - // stack: result_len, retdest + // stack: first_byte, rlp_pos, rlp_end_pos, retdest + SWAP1 + %mstore_rlp + // stack: rlp_end_pos, retdest SWAP1 JUMP diff --git a/evm/src/cpu/kernel/asm/rlp/encode.asm b/evm/src/cpu/kernel/asm/rlp/encode.asm index 7e296f9d..cd931037 100644 --- a/evm/src/cpu/kernel/asm/rlp/encode.asm +++ b/evm/src/cpu/kernel/asm/rlp/encode.asm @@ -90,6 +90,73 @@ encode_rlp_fixed_finish: SWAP1 JUMP +// Given an RLP list payload which starts at position 9 and ends at the given +// position, prepend the appropriate RLP list prefix. Returns the updated start +// position, as well as the length of the RLP data (including the newly-added +// prefix). +// +// (We sometimes start list payloads at position 9 because 9 is the length of +// the longest possible RLP list prefix.) +// +// Pre stack: end_pos, retdest +// Post stack: start_pos, rlp_len +global prepend_rlp_list_prefix: + // stack: end_pos, retdest + // Since the list payload starts at position 9, payload_len = end_pos - 9. + PUSH 9 DUP2 SUB + // stack: payload_len, end_pos, retdest + DUP1 %gt_const(55) + %jumpi(prepend_rlp_list_prefix_big) + + // If we got here, we have a small list, so we prepend 0xc0 + len at position 8. + // stack: payload_len, end_pos, retdest + %add_const(0xc0) + // stack: prefix_byte, end_pos, retdest + PUSH 8 // offset + %mstore_rlp + // stack: end_pos, retdest + %sub_const(8) + // stack: rlp_len, retdest + PUSH 8 // start_pos + %stack (start_pos, rlp_len, retdest) -> (retdest, start_pos, rlp_len) + JUMP + +prepend_rlp_list_prefix_big: + // We have a large list, so we prepend 0xf7 + len_of_len at position + // 8 - len_of_len, followed by the length itself. + // stack: payload_len, end_pos, retdest + DUP1 %num_bytes + // stack: len_of_len, payload_len, end_pos, retdest + DUP1 + PUSH 8 + SUB + // stack: start_pos, len_of_len, payload_len, end_pos, retdest + DUP2 DUP2 %mstore_rlp // rlp[start_pos] = len_of_len + DUP1 %add_const(1) // start_len_pos = start_pos + 1 + %stack (start_len_pos, start_pos, len_of_len, payload_len, end_pos, retdest) + -> (len_of_len, start_len_pos, payload_len, + prepend_rlp_list_prefix_big_done_writing_len, + start_pos, end_pos, retdest) + %jump(encode_rlp_fixed) +prepend_rlp_list_prefix_big_done_writing_len: + // stack: start_payload_pos, start_pos, end_pos, retdest + POP + // stack: start_pos, end_pos, retdest + DUP1 + SWAP2 + // stack: end_pos, start_pos, start_pos, retdest + SUB + // stack: rlp_len, start_pos, retdest + %stack (rlp_len, start_pos, retdest) -> (retdest, start_pos, rlp_len) + JUMP + +// Convenience macro to call prepend_rlp_list_prefix and return where we left off. +%macro prepend_rlp_list_prefix + %stack (start_pos) -> (start_pos, %%after) + %jump(prepend_rlp_list_prefix) +%%after: +%endmacro + // Get the number of bytes required to represent the given scalar. // The scalar is assumed to be non-zero, as small scalars like zero should // have already been handled with the small-scalar encoding. diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 04fc5ced..8a898cb3 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -161,10 +161,6 @@ impl<'a> Interpreter<'a> { &self.memory.context_memory[0].segments[Segment::TrieData as usize].content } - pub(crate) fn get_kernel_general_data(&self) -> &[U256] { - &self.memory.context_memory[0].segments[Segment::KernelGeneral as usize].content - } - pub(crate) fn get_rlp_memory(&self) -> Vec { self.memory.context_memory[self.context].segments[Segment::RlpRaw as usize] .content diff --git a/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs index 4318b560..76fe8014 100644 --- a/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs +++ b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs @@ -5,20 +5,25 @@ use crate::cpu::kernel::interpreter::Interpreter; #[test] fn hex_prefix_even_nonterminated() -> Result<()> { - let hex_prefix = KERNEL.global_labels["hex_prefix"]; + let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; let retdest = 0xDEADBEEFu32.into(); let terminated = 0.into(); let packed_nibbles = 0xABCDEF.into(); let num_nibbles = 6.into(); - let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles]; + let rlp_pos = 0.into(); + let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); interpreter.run()?; - assert_eq!(interpreter.stack(), vec![4.into()]); + assert_eq!(interpreter.stack(), vec![5.into()]); assert_eq!( - interpreter.get_kernel_general_data(), - vec![0.into(), 0xAB.into(), 0xCD.into(), 0xEF.into(),] + interpreter.get_rlp_memory(), + vec![ + 4, // length + 0, // neither flag is set + 0xAB, 0xCD, 0xEF + ] ); Ok(()) @@ -26,23 +31,53 @@ fn hex_prefix_even_nonterminated() -> Result<()> { #[test] fn hex_prefix_odd_terminated() -> Result<()> { - let hex_prefix = KERNEL.global_labels["hex_prefix"]; + let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; let retdest = 0xDEADBEEFu32.into(); let terminated = 1.into(); let packed_nibbles = 0xABCDE.into(); let num_nibbles = 5.into(); - let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles]; + let rlp_pos = 0.into(); + let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); interpreter.run()?; - assert_eq!(interpreter.stack(), vec![3.into()]); + assert_eq!(interpreter.stack(), vec![4.into()]); assert_eq!( - interpreter.get_kernel_general_data(), + interpreter.get_rlp_memory(), vec![ - (terminated * 2 + 1u32) * 16 + 0xAu32, - 0xBC.into(), - 0xDE.into(), + 3, // length + (2 + 1) * 16 + 0xA, + 0xBC, + 0xDE, + ] + ); + + Ok(()) +} + +#[test] +fn hex_prefix_odd_terminated_tiny() -> Result<()> { + let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; + + let retdest = 0xDEADBEEFu32.into(); + let terminated = 1.into(); + let packed_nibbles = 0xA.into(); + let num_nibbles = 1.into(); + let rlp_pos = 2.into(); + let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; + let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![3.into()]); + + assert_eq!( + interpreter.get_rlp_memory(), + vec![ + // Since rlp_pos = 2, we skipped over the first two bytes. + 0, + 0, + // No length prefix; this tiny string is its own RLP encoding. + (2 + 1) * 16 + 0xA, ] ); diff --git a/evm/src/cpu/kernel/tests/rlp.rs b/evm/src/cpu/kernel/tests/rlp/decode.rs similarity index 59% rename from evm/src/cpu/kernel/tests/rlp.rs rename to evm/src/cpu/kernel/tests/rlp/decode.rs index 37949e13..a1ca3609 100644 --- a/evm/src/cpu/kernel/tests/rlp.rs +++ b/evm/src/cpu/kernel/tests/rlp/decode.rs @@ -3,84 +3,6 @@ use anyhow::Result; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::interpreter::Interpreter; -#[test] -fn test_encode_rlp_scalar_small() -> Result<()> { - let encode_rlp_scalar = KERNEL.global_labels["encode_rlp_scalar"]; - - let retdest = 0xDEADBEEFu32.into(); - let scalar = 42.into(); - let pos = 2.into(); - let initial_stack = vec![retdest, scalar, pos]; - let mut interpreter = Interpreter::new_with_kernel(encode_rlp_scalar, initial_stack); - - interpreter.run()?; - let expected_stack = vec![3.into()]; // pos' = pos + rlp_len = 2 + 1 - let expected_rlp = vec![0, 0, 42]; - assert_eq!(interpreter.stack(), expected_stack); - assert_eq!(interpreter.get_rlp_memory(), expected_rlp); - - Ok(()) -} - -#[test] -fn test_encode_rlp_scalar_medium() -> Result<()> { - let encode_rlp_scalar = KERNEL.global_labels["encode_rlp_scalar"]; - - let retdest = 0xDEADBEEFu32.into(); - let scalar = 0x12345.into(); - let pos = 2.into(); - let initial_stack = vec![retdest, scalar, pos]; - let mut interpreter = Interpreter::new_with_kernel(encode_rlp_scalar, initial_stack); - - interpreter.run()?; - let expected_stack = vec![6.into()]; // pos' = pos + rlp_len = 2 + 4 - let expected_rlp = vec![0, 0, 0x80 + 3, 0x01, 0x23, 0x45]; - assert_eq!(interpreter.stack(), expected_stack); - assert_eq!(interpreter.get_rlp_memory(), expected_rlp); - - Ok(()) -} - -#[test] -fn test_encode_rlp_160() -> Result<()> { - let encode_rlp_160 = KERNEL.global_labels["encode_rlp_160"]; - - let retdest = 0xDEADBEEFu32.into(); - let string = 0x12345.into(); - let pos = 0.into(); - let initial_stack = vec![retdest, string, pos]; - let mut interpreter = Interpreter::new_with_kernel(encode_rlp_160, initial_stack); - - interpreter.run()?; - let expected_stack = vec![(1 + 20).into()]; // pos' - #[rustfmt::skip] - let expected_rlp = vec![0x80 + 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x23, 0x45]; - assert_eq!(interpreter.stack(), expected_stack); - assert_eq!(interpreter.get_rlp_memory(), expected_rlp); - - Ok(()) -} - -#[test] -fn test_encode_rlp_256() -> Result<()> { - let encode_rlp_256 = KERNEL.global_labels["encode_rlp_256"]; - - let retdest = 0xDEADBEEFu32.into(); - let string = 0x12345.into(); - let pos = 0.into(); - let initial_stack = vec![retdest, string, pos]; - let mut interpreter = Interpreter::new_with_kernel(encode_rlp_256, initial_stack); - - interpreter.run()?; - let expected_stack = vec![(1 + 32).into()]; // pos' - #[rustfmt::skip] - let expected_rlp = vec![0x80 + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x23, 0x45]; - assert_eq!(interpreter.stack(), expected_stack); - assert_eq!(interpreter.get_rlp_memory(), expected_rlp); - - Ok(()) -} - #[test] fn test_decode_rlp_string_len_short() -> Result<()> { let decode_rlp_string_len = KERNEL.global_labels["decode_rlp_string_len"]; diff --git a/evm/src/cpu/kernel/tests/rlp/encode.rs b/evm/src/cpu/kernel/tests/rlp/encode.rs new file mode 100644 index 00000000..bacf32fb --- /dev/null +++ b/evm/src/cpu/kernel/tests/rlp/encode.rs @@ -0,0 +1,110 @@ +use anyhow::Result; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::interpreter::Interpreter; + +#[test] +fn test_encode_rlp_scalar_small() -> Result<()> { + let encode_rlp_scalar = KERNEL.global_labels["encode_rlp_scalar"]; + + let retdest = 0xDEADBEEFu32.into(); + let scalar = 42.into(); + let pos = 2.into(); + let initial_stack = vec![retdest, scalar, pos]; + let mut interpreter = Interpreter::new_with_kernel(encode_rlp_scalar, initial_stack); + + interpreter.run()?; + let expected_stack = vec![3.into()]; // pos' = pos + rlp_len = 2 + 1 + let expected_rlp = vec![0, 0, 42]; + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_encode_rlp_scalar_medium() -> Result<()> { + let encode_rlp_scalar = KERNEL.global_labels["encode_rlp_scalar"]; + + let retdest = 0xDEADBEEFu32.into(); + let scalar = 0x12345.into(); + let pos = 2.into(); + let initial_stack = vec![retdest, scalar, pos]; + let mut interpreter = Interpreter::new_with_kernel(encode_rlp_scalar, initial_stack); + + interpreter.run()?; + let expected_stack = vec![6.into()]; // pos' = pos + rlp_len = 2 + 4 + let expected_rlp = vec![0, 0, 0x80 + 3, 0x01, 0x23, 0x45]; + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_encode_rlp_160() -> Result<()> { + let encode_rlp_160 = KERNEL.global_labels["encode_rlp_160"]; + + let retdest = 0xDEADBEEFu32.into(); + let string = 0x12345.into(); + let pos = 0.into(); + let initial_stack = vec![retdest, string, pos]; + let mut interpreter = Interpreter::new_with_kernel(encode_rlp_160, initial_stack); + + interpreter.run()?; + let expected_stack = vec![(1 + 20).into()]; // pos' + #[rustfmt::skip] + let expected_rlp = vec![0x80 + 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x23, 0x45]; + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_encode_rlp_256() -> Result<()> { + let encode_rlp_256 = KERNEL.global_labels["encode_rlp_256"]; + + let retdest = 0xDEADBEEFu32.into(); + let string = 0x12345.into(); + let pos = 0.into(); + let initial_stack = vec![retdest, string, pos]; + let mut interpreter = Interpreter::new_with_kernel(encode_rlp_256, initial_stack); + + interpreter.run()?; + let expected_stack = vec![(1 + 32).into()]; // pos' + #[rustfmt::skip] + let expected_rlp = vec![0x80 + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x23, 0x45]; + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} + +#[test] +fn test_prepend_rlp_list_prefix_small() -> Result<()> { + let prepend_rlp_list_prefix = KERNEL.global_labels["prepend_rlp_list_prefix"]; + + let retdest = 0xDEADBEEFu32.into(); + let end_pos = (9 + 5).into(); + let initial_stack = vec![retdest, end_pos]; + let mut interpreter = Interpreter::new_with_kernel(prepend_rlp_list_prefix, initial_stack); + interpreter.set_rlp_memory(vec![ + // Nine 0s to leave room for the longest possible RLP list prefix. + 0, 0, 0, 0, 0, 0, 0, 0, 0, + // The actual RLP list payload, consisting of 5 tiny strings. + 1, 2, 3, 4, 5, + ]); + + interpreter.run()?; + + let expected_rlp_len = 6.into(); + let expected_start_pos = 8.into(); + let expected_stack = vec![expected_rlp_len, expected_start_pos]; + let expected_rlp = vec![0, 0, 0, 0, 0, 0, 0, 0, 0xc0 + 5, 1, 2, 3, 4, 5]; + + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/rlp/mod.rs b/evm/src/cpu/kernel/tests/rlp/mod.rs new file mode 100644 index 00000000..bc9bde59 --- /dev/null +++ b/evm/src/cpu/kernel/tests/rlp/mod.rs @@ -0,0 +1,2 @@ +mod decode; +mod encode; From 9e483528d32d5fe3b2b397b42c0dd80e4d84b652 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 1 Oct 2022 21:55:47 -0700 Subject: [PATCH 94/97] MPT hashing logic, part 3 --- evm/src/cpu/columns/ops.rs | 1 + evm/src/cpu/decode.rs | 3 +- evm/src/cpu/kernel/asm/mpt/hash.asm | 49 ++++++++++++- evm/src/cpu/kernel/asm/mpt/hex_prefix.asm | 4 +- evm/src/cpu/kernel/asm/mpt/load.asm | 3 +- evm/src/cpu/kernel/asm/rlp/encode.asm | 80 ++++++++++++++++++++-- evm/src/cpu/kernel/interpreter.rs | 32 +++++++-- evm/src/cpu/kernel/opcodes.rs | 1 + evm/src/cpu/kernel/tests/mpt/hash.rs | 62 +++++++++++++++++ evm/src/cpu/kernel/tests/mpt/hex_prefix.rs | 10 +-- evm/src/cpu/kernel/tests/mpt/mod.rs | 1 + evm/src/cpu/kernel/tests/mpt/read.rs | 2 - evm/src/cpu/kernel/tests/rlp/encode.rs | 45 ++++++++++++ evm/src/cpu/stack.rs | 1 + 14 files changed, 268 insertions(+), 26 deletions(-) create mode 100644 evm/src/cpu/kernel/tests/mpt/hash.rs diff --git a/evm/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs index 28087b9e..e0cb2952 100644 --- a/evm/src/cpu/columns/ops.rs +++ b/evm/src/cpu/columns/ops.rs @@ -34,6 +34,7 @@ pub struct OpsColumnsView { pub shr: T, pub sar: T, pub keccak256: T, + pub keccak_general: T, pub address: T, pub balance: T, pub origin: T, diff --git a/evm/src/cpu/decode.rs b/evm/src/cpu/decode.rs index c086aff5..6fcbae1c 100644 --- a/evm/src/cpu/decode.rs +++ b/evm/src/cpu/decode.rs @@ -22,7 +22,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; /// behavior. /// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to /// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid. -const OPCODES: [(u8, usize, bool, usize); 92] = [ +const OPCODES: [(u8, usize, bool, usize); 93] = [ // (start index of block, number of top bits to check (log2), kernel-only, flag column) (0x00, 0, false, COL_MAP.op.stop), (0x01, 0, false, COL_MAP.op.add), @@ -51,6 +51,7 @@ const OPCODES: [(u8, usize, bool, usize); 92] = [ (0x1c, 0, false, COL_MAP.op.shr), (0x1d, 0, false, COL_MAP.op.sar), (0x20, 0, false, COL_MAP.op.keccak256), + (0x21, 0, true, COL_MAP.op.keccak_general), (0x30, 0, false, COL_MAP.op.address), (0x31, 0, false, COL_MAP.op.balance), (0x32, 0, false, COL_MAP.op.origin), diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index 4018a036..eb896208 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -1,3 +1,46 @@ +global mpt_hash_state_trie: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + // stack: node_ptr, retdest + %mpt_hash(encode_account) + +encode_account: + // stack: rlp_pos, value_ptr, retdest + // First, we compute the length of the RLP data we're about to write. + // The nonce and balance fields are variable-length, so we need to load them + // to determine their contribution, while the other two fields are fixed + // 32-bytes integers. + DUP2 %mload_trie_data // nonce = value[0] + %scalar_rlp_len + // stack: nonce_rlp_len, rlp_pos, value_ptr, retdest + DUP3 %add_const(1) %mload_trie_data // balance = value[1] + %scalar_rlp_len + // stack: balance_rlp_lenm, nonce_rlp_len, rlp_pos, value_ptr, retdest + PUSH 66 // storage_root and code_hash fields each take 1 + 32 bytes + ADD ADD + // stack: payload_len, rlp_pos, value_ptr, retdest + SWAP1 + %encode_rlp_list_prefix + // stack: rlp_pos', value_ptr, retdest + DUP2 %mload_trie_data // nonce = value[0] + // stack: nonce, rlp_pos', value_ptr, retdest + SWAP1 %encode_rlp_scalar + // stack: rlp_pos'', value_ptr, retdest + DUP2 %add_const(1) %mload_trie_data // balance = value[1] + // stack: balance, rlp_pos'', value_ptr, retdest + SWAP1 %encode_rlp_scalar + // stack: rlp_pos''', value_ptr, retdest + DUP2 %add_const(2) %mload_trie_data // storage_root = value[2] + // stack: storage_root, rlp_pos''', value_ptr, retdest + SWAP1 %encode_rlp_256 + // stack: rlp_pos'''', value_ptr, retdest + SWAP1 %add_const(3) %mload_trie_data // code_hash = value[3] + // stack: code_hash, rlp_pos'''', retdest + SWAP1 %encode_rlp_256 + // stack: rlp_pos''''', retdest + SWAP1 + JUMP + // Computes the Merkle root of the given trie node. // // The encode_value function should take as input @@ -47,7 +90,7 @@ // stack: num_nibbles, packed_nibbles, terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest PUSH 9 // We start at 9 to leave room to prepend the largest possible RLP list header. // stack: rlp_start, num_nibbles, packed_nibbles, terminated, %%mpt_hash_leaf_after_hex_prefix, node_payload_ptr, retdest - %jump(hex_prefix) + %jump(hex_prefix_rlp) %%mpt_hash_leaf_after_hex_prefix: // stack: rlp_pos, node_payload_ptr, retdest SWAP1 @@ -59,12 +102,12 @@ // stack: rlp_end_pos, retdest %prepend_rlp_list_prefix // stack: rlp_start_pos, rlp_len, retdest - PUSH $SEGMENT_RLP + PUSH @SEGMENT_RLP_RAW PUSH 0 // kernel context // stack: rlp_start_addr: 3, rlp_len, retdest KECCAK_GENERAL // stack: hash, retdest - SWAP + SWAP1 JUMP %endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm index 043974a4..72ac18cc 100644 --- a/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm +++ b/evm/src/cpu/kernel/asm/mpt/hex_prefix.asm @@ -30,7 +30,7 @@ global hex_prefix_rlp: rlp_header_medium: // stack: hp_len, i, rlp_pos, num_nibbles, packed_nibbles, terminated, retdest - DUP1 // value = hp_len + DUP1 %add_const(0x80) // value = 0x80 + hp_len DUP4 // offset = rlp_pos %mstore_rlp @@ -44,7 +44,7 @@ rlp_header_large: // In practice hex-prefix length will never exceed 256, so the length of the // length will always be 1 byte in this case. - PUSH 1 // value = len_of_len = 1 + PUSH 0xb8 // value = 0xb7 + len_of_len = 0xb8 DUP4 // offset = rlp_pos %mstore_rlp diff --git a/evm/src/cpu/kernel/asm/mpt/load.asm b/evm/src/cpu/kernel/asm/mpt/load.asm index 94d05dde..2f1bd624 100644 --- a/evm/src/cpu/kernel/asm/mpt/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load.asm @@ -9,8 +9,7 @@ global load_all_mpts: PUSH 1 %set_trie_data_size - %load_mpt_and_return_root_ptr - %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) %load_mpt_and_return_root_ptr %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) diff --git a/evm/src/cpu/kernel/asm/rlp/encode.asm b/evm/src/cpu/kernel/asm/rlp/encode.asm index cd931037..4f9344d1 100644 --- a/evm/src/cpu/kernel/asm/rlp/encode.asm +++ b/evm/src/cpu/kernel/asm/rlp/encode.asm @@ -79,7 +79,7 @@ encode_rlp_fixed: %add_const(1) // increment pos // stack: pos, len, string, retdest %stack (pos, len, string) -> (@SEGMENT_RLP_RAW, pos, string, len, encode_rlp_fixed_finish, pos, len) - GET_CONTEXT + PUSH 0 // context // stack: context, segment, pos, string, len, encode_rlp_fixed, pos, retdest %jump(mstore_unpacking) @@ -90,6 +90,54 @@ encode_rlp_fixed_finish: SWAP1 JUMP +// Pre stack: pos, payload_len, retdest +// Post stack: pos' +global encode_rlp_list_prefix: + // stack: pos, payload_len, retdest + DUP2 %gt_const(55) + %jumpi(encode_rlp_list_prefix_large) + // Small case: prefix is just 0xc0 + length. + // stack: pos, payload_len, retdest + SWAP1 + %add_const(0xc0) + // stack: prefix, pos, retdest + DUP2 + // stack: pos, prefix, pos, retdest + %mstore_rlp + // stack: pos, retdest + %add_const(1) + SWAP1 + JUMP +encode_rlp_list_prefix_large: + // Write 0xf7 + len_of_len. + // stack: pos, payload_len, retdest + DUP2 %num_bytes + // stack: len_of_len, pos, payload_len, retdest + DUP1 %add_const(0xf7) + // stack: first_byte, len_of_len, pos, payload_len, retdest + DUP3 // pos + %mstore_rlp + // stack: len_of_len, pos, payload_len, retdest + SWAP1 %add_const(1) + // stack: pos', len_of_len, payload_len, retdest + %stack (pos, len_of_len, payload_len, retdest) + -> (0, @SEGMENT_RLP_RAW, pos, payload_len, len_of_len, + encode_rlp_list_prefix_large_done_writing_len, + pos, len_of_len, retdest) + %jump(mstore_unpacking) +encode_rlp_list_prefix_large_done_writing_len: + // stack: pos', len_of_len, retdest + ADD + // stack: pos'', retdest + SWAP1 + JUMP + +%macro encode_rlp_list_prefix + %stack (pos, payload_len) -> (pos, payload_len, %%after) + %jump(encode_rlp_list_prefix) +%%after: +%endmacro + // Given an RLP list payload which starts at position 9 and ends at the given // position, prepend the appropriate RLP list prefix. Returns the updated start // position, as well as the length of the RLP data (including the newly-added @@ -131,16 +179,15 @@ prepend_rlp_list_prefix_big: PUSH 8 SUB // stack: start_pos, len_of_len, payload_len, end_pos, retdest - DUP2 DUP2 %mstore_rlp // rlp[start_pos] = len_of_len + DUP2 %add_const(0xf7) DUP2 %mstore_rlp // rlp[start_pos] = 0xf7 + len_of_len DUP1 %add_const(1) // start_len_pos = start_pos + 1 %stack (start_len_pos, start_pos, len_of_len, payload_len, end_pos, retdest) - -> (len_of_len, start_len_pos, payload_len, + -> (0, @SEGMENT_RLP_RAW, start_len_pos, // context, segment, offset + payload_len, len_of_len, prepend_rlp_list_prefix_big_done_writing_len, start_pos, end_pos, retdest) - %jump(encode_rlp_fixed) + %jump(mstore_unpacking) prepend_rlp_list_prefix_big_done_writing_len: - // stack: start_payload_pos, start_pos, end_pos, retdest - POP // stack: start_pos, end_pos, retdest DUP1 SWAP2 @@ -160,7 +207,7 @@ prepend_rlp_list_prefix_big_done_writing_len: // Get the number of bytes required to represent the given scalar. // The scalar is assumed to be non-zero, as small scalars like zero should // have already been handled with the small-scalar encoding. -num_bytes: +global num_bytes: // stack: x, retdest PUSH 0 // i // stack: i, x, retdest @@ -192,3 +239,22 @@ num_bytes_finish: %jump(num_bytes) %%after: %endmacro + +// Given some scalar, compute the number of bytes used in its RLP encoding, +// including any length prefix. +%macro scalar_rlp_len + // stack: scalar + // Since the scalar fits in a word, we can't hit the large (>55 byte) + // case, so we just check for small vs medium. + DUP1 %gt_const(0x7f) + // stack: is_medium, scalar + %jumpi(%%medium) + // Small case; result is 1. + %stack (scalar) -> (1) +%%medium: + // stack: scalar + %num_bytes + // stack: scalar_bytes + %add_const(1) // Account for the length prefix. + // stack: rlp_len +%endmacro diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 8a898cb3..45211848 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -47,10 +47,21 @@ impl InterpreterMemory { impl InterpreterMemory { fn mload_general(&self, context: usize, segment: Segment, offset: usize) -> U256 { - self.context_memory[context].segments[segment as usize].get(offset) + let value = self.context_memory[context].segments[segment as usize].get(offset); + assert!( + value.bits() <= segment.bit_range(), + "Value read from memory exceeds expected range of {:?} segment", + segment + ); + value } fn mstore_general(&mut self, context: usize, segment: Segment, offset: usize, value: U256) { + assert!( + value.bits() <= segment.bit_range(), + "Value written to memory exceeds expected range of {:?} segment", + segment + ); self.context_memory[context].segments[segment as usize].set(offset, value) } } @@ -162,7 +173,7 @@ impl<'a> Interpreter<'a> { } pub(crate) fn get_rlp_memory(&self) -> Vec { - self.memory.context_memory[self.context].segments[Segment::RlpRaw as usize] + self.memory.context_memory[0].segments[Segment::RlpRaw as usize] .content .iter() .map(|x| x.as_u32() as u8) @@ -170,7 +181,7 @@ impl<'a> Interpreter<'a> { } pub(crate) fn set_rlp_memory(&mut self, rlp: Vec) { - self.memory.context_memory[self.context].segments[Segment::RlpRaw as usize].content = + self.memory.context_memory[0].segments[Segment::RlpRaw as usize].content = rlp.into_iter().map(U256::from).collect(); } @@ -229,6 +240,7 @@ impl<'a> Interpreter<'a> { 0x1c => self.run_shr(), // "SHR", 0x1d => todo!(), // "SAR", 0x20 => self.run_keccak256(), // "KECCAK256", + 0x21 => self.run_keccak_general(), // "KECCAK_GENERAL", 0x30 => todo!(), // "ADDRESS", 0x31 => todo!(), // "BALANCE", 0x32 => todo!(), // "ORIGIN", @@ -447,6 +459,18 @@ impl<'a> Interpreter<'a> { self.push(U256::from_big_endian(hash.as_bytes())); } + fn run_keccak_general(&mut self) { + let context = self.pop().as_usize(); + let segment = Segment::all()[self.pop().as_usize()]; + let offset = self.pop().as_usize(); + let size = self.pop().as_usize(); + let bytes = (offset..offset + size) + .map(|i| self.memory.mload_general(context, segment, i).byte(0)) + .collect::>(); + let hash = keccak(bytes); + self.push(U256::from_big_endian(hash.as_bytes())); + } + fn run_prover_input(&mut self) -> anyhow::Result<()> { let prover_input_fn = self .prover_inputs_map @@ -567,7 +591,6 @@ impl<'a> Interpreter<'a> { let segment = Segment::all()[self.pop().as_usize()]; let offset = self.pop().as_usize(); let value = self.memory.mload_general(context, segment, offset); - assert!(value.bits() <= segment.bit_range()); self.push(value); } @@ -576,7 +599,6 @@ impl<'a> Interpreter<'a> { let segment = Segment::all()[self.pop().as_usize()]; let offset = self.pop().as_usize(); let value = self.pop(); - assert!(value.bits() <= segment.bit_range()); self.memory.mstore_general(context, segment, offset, value); } } diff --git a/evm/src/cpu/kernel/opcodes.rs b/evm/src/cpu/kernel/opcodes.rs index 69ee13fe..2325c53a 100644 --- a/evm/src/cpu/kernel/opcodes.rs +++ b/evm/src/cpu/kernel/opcodes.rs @@ -35,6 +35,7 @@ pub(crate) fn get_opcode(mnemonic: &str) -> u8 { "SHR" => 0x1c, "SAR" => 0x1d, "KECCAK256" => 0x20, + "KECCAK_GENERAL" => 0x21, "ADDRESS" => 0x30, "BALANCE" => 0x31, "ORIGIN" => 0x32, diff --git a/evm/src/cpu/kernel/tests/mpt/hash.rs b/evm/src/cpu/kernel/tests/mpt/hash.rs new file mode 100644 index 00000000..5f212e3c --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/hash.rs @@ -0,0 +1,62 @@ +use anyhow::Result; +use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +use ethereum_types::{BigEndianHash, H256, U256}; +use hex_literal::hex; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; +use crate::generation::TrieInputs; + +#[test] +fn mpt_hash() -> Result<()> { + let nonce = U256::from(1111); + let balance = U256::from(2222); + let storage_root = U256::from(3333); + let code_hash = U256::from(4444); + + let account = &[nonce, balance, storage_root, code_hash]; + let account_rlp = rlp::encode_list(account); + + // TODO: Try this more "advanced" trie. + // let state_trie = state_trie_ext_to_account_leaf(account_rlp.to_vec()); + let state_trie = PartialTrie::Leaf { + nibbles: Nibbles { + count: 3, + packed: 0xABC.into(), + }, + value: account_rlp.to_vec(), + }; + // TODO: It seems like calc_hash isn't giving the expected hash yet, so for now, I'm using a + // hardcoded hash obtained from py-evm. + // let state_trie_hash = state_trie.calc_hash(); + let state_trie_hash = + hex!("e38d6053838fe057c865ec0c74a8f0de21865d74fac222a2d3241fe57c9c3a0f").into(); + + let trie_inputs = TrieInputs { + state_trie, + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; + + let initial_stack = vec![0xdeadbeefu32.into()]; + let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack); + interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![]); + + // Now, execute mpt_hash_state_trie. + interpreter.offset = mpt_hash_state_trie; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.run()?; + + assert_eq!(interpreter.stack().len(), 1); + let hash = H256::from_uint(&interpreter.stack()[0]); + assert_eq!(hash, state_trie_hash); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs index 76fe8014..c13b8122 100644 --- a/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs +++ b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs @@ -20,9 +20,11 @@ fn hex_prefix_even_nonterminated() -> Result<()> { assert_eq!( interpreter.get_rlp_memory(), vec![ - 4, // length - 0, // neither flag is set - 0xAB, 0xCD, 0xEF + 0x80 + 4, // prefix + 0, // neither flag is set + 0xAB, + 0xCD, + 0xEF ] ); @@ -46,7 +48,7 @@ fn hex_prefix_odd_terminated() -> Result<()> { assert_eq!( interpreter.get_rlp_memory(), vec![ - 3, // length + 0x80 + 3, // prefix (2 + 1) * 16 + 0xA, 0xBC, 0xDE, diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs index 3f7ef252..8308962a 100644 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -1,5 +1,6 @@ use eth_trie_utils::partial_trie::{Nibbles, PartialTrie}; +mod hash; mod hex_prefix; mod load; mod read; diff --git a/evm/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs index e26ee4ab..e20aa0fe 100644 --- a/evm/src/cpu/kernel/tests/mpt/read.rs +++ b/evm/src/cpu/kernel/tests/mpt/read.rs @@ -42,10 +42,8 @@ fn mpt_read() -> Result<()> { interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); interpreter.run()?; - dbg!(interpreter.stack()); assert_eq!(interpreter.stack().len(), 1); let result_ptr = interpreter.stack()[0].as_usize(); - dbg!(result_ptr); let result = &interpreter.get_trie_data()[result_ptr..][..account.len()]; assert_eq!(result, account); diff --git a/evm/src/cpu/kernel/tests/rlp/encode.rs b/evm/src/cpu/kernel/tests/rlp/encode.rs index bacf32fb..4e04b248 100644 --- a/evm/src/cpu/kernel/tests/rlp/encode.rs +++ b/evm/src/cpu/kernel/tests/rlp/encode.rs @@ -108,3 +108,48 @@ fn test_prepend_rlp_list_prefix_small() -> Result<()> { Ok(()) } + +#[test] +fn test_prepend_rlp_list_prefix_large() -> Result<()> { + let prepend_rlp_list_prefix = KERNEL.global_labels["prepend_rlp_list_prefix"]; + + let retdest = 0xDEADBEEFu32.into(); + let end_pos = (9 + 60).into(); + let initial_stack = vec![retdest, end_pos]; + let mut interpreter = Interpreter::new_with_kernel(prepend_rlp_list_prefix, initial_stack); + + #[rustfmt::skip] + interpreter.set_rlp_memory(vec![ + // Nine 0s to leave room for the longest possible RLP list prefix. + 0, 0, 0, 0, 0, 0, 0, 0, 0, + // The actual RLP list payload, consisting of 60 tiny strings. + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + ]); + + interpreter.run()?; + + let expected_rlp_len = 62.into(); + let expected_start_pos = 7.into(); + let expected_stack = vec![expected_rlp_len, expected_start_pos]; + + #[rustfmt::skip] + let expected_rlp = vec![ + 0, 0, 0, 0, 0, 0, 0, 0xf7 + 1, 60, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + ]; + + assert_eq!(interpreter.stack(), expected_stack); + assert_eq!(interpreter.get_rlp_memory(), expected_rlp); + + Ok(()) +} diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index d478571a..3186b5ae 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -62,6 +62,7 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { shr: BASIC_BINARY_OP, sar: BASIC_BINARY_OP, keccak256: None, // TODO + keccak_general: None, // TODO address: None, // TODO balance: None, // TODO origin: None, // TODO From 9d22e376d0643f5ce092a1e87ed59f5f510ed895 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 2 Oct 2022 09:27:25 -0700 Subject: [PATCH 95/97] Add TODO --- evm/src/cpu/kernel/asm/rlp/encode.asm | 1 + 1 file changed, 1 insertion(+) diff --git a/evm/src/cpu/kernel/asm/rlp/encode.asm b/evm/src/cpu/kernel/asm/rlp/encode.asm index 4f9344d1..f92a8fda 100644 --- a/evm/src/cpu/kernel/asm/rlp/encode.asm +++ b/evm/src/cpu/kernel/asm/rlp/encode.asm @@ -207,6 +207,7 @@ prepend_rlp_list_prefix_big_done_writing_len: // Get the number of bytes required to represent the given scalar. // The scalar is assumed to be non-zero, as small scalars like zero should // have already been handled with the small-scalar encoding. +// TODO: Should probably unroll the loop global num_bytes: // stack: x, retdest PUSH 0 // i From 9f9143d6f6ff0e36d32dd907dcff11453388a56d Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 2 Oct 2022 11:14:07 -0700 Subject: [PATCH 96/97] Finish some misc storage logic --- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/core/transfer.asm | 33 +++++++- evm/src/cpu/kernel/asm/main.asm | 8 +- evm/src/cpu/kernel/asm/mpt/hash.asm | 47 +---------- .../cpu/kernel/asm/mpt/hash_trie_specific.asm | 80 +++++++++++++++++++ evm/src/cpu/kernel/asm/mpt/read.asm | 20 ++++- evm/src/cpu/kernel/asm/mpt/write.asm | 1 + evm/src/cpu/kernel/global_metadata.rs | 32 ++++---- evm/src/generation/mod.rs | 10 +-- evm/tests/empty_txn_list.rs | 2 +- 10 files changed, 160 insertions(+), 74 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 76091a2e..002a84fb 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -43,6 +43,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/rlp/decode.asm"), include_str!("asm/rlp/read_to_memory.asm"), include_str!("asm/mpt/hash.asm"), + include_str!("asm/mpt/hash_trie_specific.asm"), include_str!("asm/mpt/hex_prefix.asm"), include_str!("asm/mpt/load.asm"), include_str!("asm/mpt/read.asm"), diff --git a/evm/src/cpu/kernel/asm/core/transfer.asm b/evm/src/cpu/kernel/asm/core/transfer.asm index 0ed48f4d..b12bc9de 100644 --- a/evm/src/cpu/kernel/asm/core/transfer.asm +++ b/evm/src/cpu/kernel/asm/core/transfer.asm @@ -1,11 +1,15 @@ // Transfers some ETH from one address to another. The amount is given in wei. // Pre stack: from, to, amount, retdest // Post stack: (empty) - global transfer_eth: // stack: from, to, amount, retdest - // TODO: Replace with actual implementation. - %pop3 + %stack (from, to, amount, retdest) + -> (from, amount, to, amount) + %deduct_eth + // TODO: Handle exception from %deduct_eth? + // stack: to, amount, retdest + %add_eth + // stack: retdest JUMP // Convenience macro to call transfer_eth and return where we left off. @@ -26,3 +30,26 @@ global transfer_eth: %transfer_eth %%after: %endmacro + +global deduct_eth: + // stack: addr, amount, retdest + %jump(mpt_read_state_trie) +deduct_eth_after_read: + PANIC // TODO + +// Convenience macro to call deduct_eth and return where we left off. +%macro deduct_eth + %stack (addr, amount) -> (addr, amount, %%after) + %jump(deduct_eth) +%%after: +%endmacro + +global add_eth: + PANIC // TODO + +// Convenience macro to call add_eth and return where we left off. +%macro add_eth + %stack (addr, amount) -> (addr, amount, %%after) + %jump(add_eth) +%%after: +%endmacro diff --git a/evm/src/cpu/kernel/asm/main.asm b/evm/src/cpu/kernel/asm/main.asm index a27a1527..e8c8e3e4 100644 --- a/evm/src/cpu/kernel/asm/main.asm +++ b/evm/src/cpu/kernel/asm/main.asm @@ -4,7 +4,9 @@ global main: %jump(load_all_mpts) hash_initial_tries: - // TODO: Hash each trie and set @GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE, etc. + %mpt_hash_state_trie %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) + %mpt_hash_txn_trie %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE) + %mpt_hash_receipt_trie %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_BEFORE) txn_loop: // If the prover has no more txns for us to process, halt. @@ -16,5 +18,7 @@ txn_loop: %jump(route_txn) hash_final_tries: - // TODO: Hash each trie and set @GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER, etc. + %mpt_hash_state_trie %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER) + %mpt_hash_txn_trie %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_AFTER) + %mpt_hash_receipt_trie %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER) %jump(halt) diff --git a/evm/src/cpu/kernel/asm/mpt/hash.asm b/evm/src/cpu/kernel/asm/mpt/hash.asm index eb896208..053f357c 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash.asm @@ -1,46 +1,3 @@ -global mpt_hash_state_trie: - // stack: retdest - %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) - // stack: node_ptr, retdest - %mpt_hash(encode_account) - -encode_account: - // stack: rlp_pos, value_ptr, retdest - // First, we compute the length of the RLP data we're about to write. - // The nonce and balance fields are variable-length, so we need to load them - // to determine their contribution, while the other two fields are fixed - // 32-bytes integers. - DUP2 %mload_trie_data // nonce = value[0] - %scalar_rlp_len - // stack: nonce_rlp_len, rlp_pos, value_ptr, retdest - DUP3 %add_const(1) %mload_trie_data // balance = value[1] - %scalar_rlp_len - // stack: balance_rlp_lenm, nonce_rlp_len, rlp_pos, value_ptr, retdest - PUSH 66 // storage_root and code_hash fields each take 1 + 32 bytes - ADD ADD - // stack: payload_len, rlp_pos, value_ptr, retdest - SWAP1 - %encode_rlp_list_prefix - // stack: rlp_pos', value_ptr, retdest - DUP2 %mload_trie_data // nonce = value[0] - // stack: nonce, rlp_pos', value_ptr, retdest - SWAP1 %encode_rlp_scalar - // stack: rlp_pos'', value_ptr, retdest - DUP2 %add_const(1) %mload_trie_data // balance = value[1] - // stack: balance, rlp_pos'', value_ptr, retdest - SWAP1 %encode_rlp_scalar - // stack: rlp_pos''', value_ptr, retdest - DUP2 %add_const(2) %mload_trie_data // storage_root = value[2] - // stack: storage_root, rlp_pos''', value_ptr, retdest - SWAP1 %encode_rlp_256 - // stack: rlp_pos'''', value_ptr, retdest - SWAP1 %add_const(3) %mload_trie_data // code_hash = value[3] - // stack: code_hash, rlp_pos'''', retdest - SWAP1 %encode_rlp_256 - // stack: rlp_pos''''', retdest - SWAP1 - JUMP - // Computes the Merkle root of the given trie node. // // The encode_value function should take as input @@ -111,11 +68,11 @@ encode_account: JUMP %endmacro -mpt_hash_empty: +global mpt_hash_empty: %stack (node_type, node_payload_ptr, retdest) -> (retdest, @EMPTY_NODE_HASH) JUMP -mpt_hash_hash: +global mpt_hash_hash: // stack: node_type, node_payload_ptr, retdest POP // stack: node_payload_ptr, retdest diff --git a/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm new file mode 100644 index 00000000..30ea730f --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/hash_trie_specific.asm @@ -0,0 +1,80 @@ +// Hashing logic specific to a particular trie. + +global mpt_hash_state_trie: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + // stack: node_ptr, retdest + %mpt_hash(encode_account) + +%macro mpt_hash_state_trie + PUSH %%after + %jump(mpt_hash_state_trie) +%%after: +%endmacro + +global mpt_hash_txn_trie: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) + // stack: node_ptr, retdest + %mpt_hash(encode_txn) + +%macro mpt_hash_txn_trie + PUSH %%after + %jump(mpt_hash_txn_trie) +%%after: +%endmacro + +global mpt_hash_receipt_trie: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) + // stack: node_ptr, retdest + %mpt_hash(encode_receipt) + +%macro mpt_hash_receipt_trie + PUSH %%after + %jump(mpt_hash_receipt_trie) +%%after: +%endmacro + +encode_account: + // stack: rlp_pos, value_ptr, retdest + // First, we compute the length of the RLP data we're about to write. + // The nonce and balance fields are variable-length, so we need to load them + // to determine their contribution, while the other two fields are fixed + // 32-bytes integers. + DUP2 %mload_trie_data // nonce = value[0] + %scalar_rlp_len + // stack: nonce_rlp_len, rlp_pos, value_ptr, retdest + DUP3 %add_const(1) %mload_trie_data // balance = value[1] + %scalar_rlp_len + // stack: balance_rlp_lenm, nonce_rlp_len, rlp_pos, value_ptr, retdest + PUSH 66 // storage_root and code_hash fields each take 1 + 32 bytes + ADD ADD + // stack: payload_len, rlp_pos, value_ptr, retdest + SWAP1 + %encode_rlp_list_prefix + // stack: rlp_pos', value_ptr, retdest + DUP2 %mload_trie_data // nonce = value[0] + // stack: nonce, rlp_pos', value_ptr, retdest + SWAP1 %encode_rlp_scalar + // stack: rlp_pos'', value_ptr, retdest + DUP2 %add_const(1) %mload_trie_data // balance = value[1] + // stack: balance, rlp_pos'', value_ptr, retdest + SWAP1 %encode_rlp_scalar + // stack: rlp_pos''', value_ptr, retdest + DUP2 %add_const(2) %mload_trie_data // storage_root = value[2] + // stack: storage_root, rlp_pos''', value_ptr, retdest + SWAP1 %encode_rlp_256 + // stack: rlp_pos'''', value_ptr, retdest + SWAP1 %add_const(3) %mload_trie_data // code_hash = value[3] + // stack: code_hash, rlp_pos'''', retdest + SWAP1 %encode_rlp_256 + // stack: rlp_pos''''', retdest + SWAP1 + JUMP + +encode_txn: + PANIC // TODO + +encode_receipt: + PANIC // TODO diff --git a/evm/src/cpu/kernel/asm/mpt/read.asm b/evm/src/cpu/kernel/asm/mpt/read.asm index 934b6bbf..aec0c776 100644 --- a/evm/src/cpu/kernel/asm/mpt/read.asm +++ b/evm/src/cpu/kernel/asm/mpt/read.asm @@ -1,3 +1,22 @@ +// Given an address, return a pointer to the associated account data, which +// consists of four words (nonce, balance, storage_root, code_hash), in the +// state trie. Returns 0 if the address is not found. +global mpt_read_state_trie: + // stack: addr, retdest + // The key is the hash of the address. Since KECCAK_GENERAL takes input from + // memory, we will write addr bytes to SEGMENT_KERNEL_GENERAL[0..20] first. + %stack (addr) -> (0, @SEGMENT_KERNEL_GENERAL, 0, addr, 20, mpt_read_state_trie_after_mstore) + %jump(mstore_unpacking) +mpt_read_state_trie_after_mstore: + // stack: retdest + %stack () -> (0, @SEGMENT_KERNEL_GENERAL, 0, 20) // context, segment, offset, len + KECCAK_GENERAL + // stack: key, retdest + PUSH 64 // num_nibbles + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) // node_ptr + // stack: node_ptr, num_nibbles, key, retdest + %jump(mpt_read) + // Read a value from a MPT. // // Arguments: @@ -6,7 +25,6 @@ // - the number of nibbles in the key (should start at 64) // // This function returns a pointer to the leaf, or 0 if the key is not found. - global mpt_read: // stack: node_ptr, num_nibbles, key, retdest DUP1 diff --git a/evm/src/cpu/kernel/asm/mpt/write.asm b/evm/src/cpu/kernel/asm/mpt/write.asm index be593a97..5b59d016 100644 --- a/evm/src/cpu/kernel/asm/mpt/write.asm +++ b/evm/src/cpu/kernel/asm/mpt/write.asm @@ -1,2 +1,3 @@ global mpt_write: + // stack: node_ptr, num_nibbles, key, retdest // TODO diff --git a/evm/src/cpu/kernel/global_metadata.rs b/evm/src/cpu/kernel/global_metadata.rs index ddc3c839..f3f34e7a 100644 --- a/evm/src/cpu/kernel/global_metadata.rs +++ b/evm/src/cpu/kernel/global_metadata.rs @@ -24,13 +24,13 @@ pub(crate) enum GlobalMetadata { // The root digests of each Merkle trie before these transactions. StateTrieRootDigestBefore = 8, - TransactionsTrieRootDigestBefore = 9, - ReceiptsTrieRootDigestBefore = 10, + TransactionTrieRootDigestBefore = 9, + ReceiptTrieRootDigestBefore = 10, // The root digests of each Merkle trie after these transactions. StateTrieRootDigestAfter = 11, - TransactionsTrieRootDigestAfter = 12, - ReceiptsTrieRootDigestAfter = 13, + TransactionTrieRootDigestAfter = 12, + ReceiptTrieRootDigestAfter = 13, } impl GlobalMetadata { @@ -47,11 +47,11 @@ impl GlobalMetadata { Self::ReceiptTrieRoot, Self::NumStorageTries, Self::StateTrieRootDigestBefore, - Self::TransactionsTrieRootDigestBefore, - Self::ReceiptsTrieRootDigestBefore, + Self::TransactionTrieRootDigestBefore, + Self::ReceiptTrieRootDigestBefore, Self::StateTrieRootDigestAfter, - Self::TransactionsTrieRootDigestAfter, - Self::ReceiptsTrieRootDigestAfter, + Self::TransactionTrieRootDigestAfter, + Self::ReceiptTrieRootDigestAfter, ] } @@ -67,18 +67,18 @@ impl GlobalMetadata { GlobalMetadata::ReceiptTrieRoot => "GLOBAL_METADATA_RECEIPT_TRIE_ROOT", GlobalMetadata::NumStorageTries => "GLOBAL_METADATA_NUM_STORAGE_TRIES", GlobalMetadata::StateTrieRootDigestBefore => "GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE", - GlobalMetadata::TransactionsTrieRootDigestBefore => { - "GLOBAL_METADATA_TXNS_TRIE_DIGEST_BEFORE" + GlobalMetadata::TransactionTrieRootDigestBefore => { + "GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE" } - GlobalMetadata::ReceiptsTrieRootDigestBefore => { - "GLOBAL_METADATA_RECEIPTS_TRIE_DIGEST_BEFORE" + GlobalMetadata::ReceiptTrieRootDigestBefore => { + "GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_BEFORE" } GlobalMetadata::StateTrieRootDigestAfter => "GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER", - GlobalMetadata::TransactionsTrieRootDigestAfter => { - "GLOBAL_METADATA_TXNS_TRIE_DIGEST_AFTER" + GlobalMetadata::TransactionTrieRootDigestAfter => { + "GLOBAL_METADATA_TXN_TRIE_DIGEST_AFTER" } - GlobalMetadata::ReceiptsTrieRootDigestAfter => { - "GLOBAL_METADATA_RECEIPTS_TRIE_DIGEST_AFTER" + GlobalMetadata::ReceiptTrieRootDigestAfter => { + "GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER" } } } diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index a24f44d5..11fbd879 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -89,18 +89,16 @@ pub(crate) fn generate_traces, const D: usize>( let trie_roots_before = TrieRoots { state_root: H256::from_uint(&read_metadata(GlobalMetadata::StateTrieRootDigestBefore)), transactions_root: H256::from_uint(&read_metadata( - GlobalMetadata::TransactionsTrieRootDigestBefore, - )), - receipts_root: H256::from_uint(&read_metadata( - GlobalMetadata::ReceiptsTrieRootDigestBefore, + GlobalMetadata::TransactionTrieRootDigestBefore, )), + receipts_root: H256::from_uint(&read_metadata(GlobalMetadata::ReceiptTrieRootDigestBefore)), }; let trie_roots_after = TrieRoots { state_root: H256::from_uint(&read_metadata(GlobalMetadata::StateTrieRootDigestAfter)), transactions_root: H256::from_uint(&read_metadata( - GlobalMetadata::TransactionsTrieRootDigestAfter, + GlobalMetadata::TransactionTrieRootDigestAfter, )), - receipts_root: H256::from_uint(&read_metadata(GlobalMetadata::ReceiptsTrieRootDigestAfter)), + receipts_root: H256::from_uint(&read_metadata(GlobalMetadata::ReceiptTrieRootDigestAfter)), }; let GenerationState { diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs index a9c71026..6e16fa47 100644 --- a/evm/tests/empty_txn_list.rs +++ b/evm/tests/empty_txn_list.rs @@ -17,7 +17,7 @@ type C = PoseidonGoldilocksConfig; /// Execute the empty list of transactions, i.e. a no-op. #[test] -#[ignore] // TODO: Won't work until storage, etc. are implemented. +#[ignore] // TODO: Won't work until witness generation logic is finished. fn test_empty_txn_list() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); From 0de392b3358ae0bb70a3256240c9ecb406600d02 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 2 Oct 2022 11:41:44 -0700 Subject: [PATCH 97/97] Fix optimization --- evm/src/cpu/kernel/optimizer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/src/cpu/kernel/optimizer.rs b/evm/src/cpu/kernel/optimizer.rs index e23bf520..e2504203 100644 --- a/evm/src/cpu/kernel/optimizer.rs +++ b/evm/src/cpu/kernel/optimizer.rs @@ -80,9 +80,9 @@ fn no_op_jumps(code: &mut Vec) { replace_windows(code, |window| { if let [Push(Label(l)), StandardOp(jump), decl] = window && &jump == "JUMP" - && (decl == LocalLabelDeclaration(l.clone()) || decl == GlobalLabelDeclaration(l.clone())) + && (decl == LocalLabelDeclaration(l.clone()) || decl == GlobalLabelDeclaration(l)) { - Some(vec![LocalLabelDeclaration(l)]) + Some(vec![decl]) } else { None }