From 68a5428500966679b746a096b61442b57e790be0 Mon Sep 17 00:00:00 2001 From: Hamish Ivey-Law <426294+unzvfu@users.noreply.github.com> Date: Wed, 12 Oct 2022 02:39:13 +1100 Subject: [PATCH] Represent input columns as ranges rather than arrays (#776) * Use std::ops::Range of columns rather than arrays of column indices. * Refactor reading from the local values table. * The inevitable post-push fmt/clippy commit. --- evm/src/arithmetic/add.rs | 40 +++++++++-------- evm/src/arithmetic/columns.rs | 79 ++++++++++++++------------------- evm/src/arithmetic/compare.rs | 47 ++++++++++---------- evm/src/arithmetic/modular.rs | 83 ++++++++++++++--------------------- evm/src/arithmetic/mul.rs | 40 ++++++++--------- evm/src/arithmetic/sub.rs | 39 +++++++++------- evm/src/arithmetic/utils.rs | 45 ++++++++++++++++--- 7 files changed, 192 insertions(+), 181 deletions(-) diff --git a/evm/src/arithmetic/add.rs b/evm/src/arithmetic/add.rs index d2520fb9..1bf798cc 100644 --- a/evm/src/arithmetic/add.rs +++ b/evm/src/arithmetic/add.rs @@ -6,6 +6,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use crate::arithmetic::columns::*; +use crate::arithmetic::utils::read_value_u64_limbs; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; @@ -94,15 +95,12 @@ where } pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { - let input0_limbs = ADD_INPUT_0.map(|c| lv[c].to_canonical_u64()); - let input1_limbs = ADD_INPUT_1.map(|c| lv[c].to_canonical_u64()); + let input0 = read_value_u64_limbs(lv, ADD_INPUT_0); + let input1 = read_value_u64_limbs(lv, ADD_INPUT_1); // Input and output have 16-bit limbs - let (output_limbs, _) = u256_add_cc(input0_limbs, input1_limbs); - - for (&c, output_limb) in ADD_OUTPUT.iter().zip(output_limbs) { - lv[c] = F::from_canonical_u64(output_limb); - } + let (output_limbs, _) = u256_add_cc(input0, input1); + lv[ADD_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_u64(c))); } pub fn eval_packed_generic( @@ -114,15 +112,20 @@ pub fn eval_packed_generic( range_check_error!(ADD_OUTPUT, 16); let is_add = lv[IS_ADD]; - let input0_limbs = ADD_INPUT_0.iter().map(|&c| lv[c]); - let input1_limbs = ADD_INPUT_1.iter().map(|&c| lv[c]); - let output_limbs = ADD_OUTPUT.iter().map(|&c| lv[c]); + let input0_limbs = &lv[ADD_INPUT_0]; + let input1_limbs = &lv[ADD_INPUT_1]; + let output_limbs = &lv[ADD_OUTPUT]; // This computed output is not yet reduced; i.e. some limbs may be // more than 16 bits. - let output_computed = input0_limbs.zip(input1_limbs).map(|(a, b)| a + b); + let output_computed = input0_limbs.iter().zip(input1_limbs).map(|(&a, &b)| a + b); - eval_packed_generic_are_equal(yield_constr, is_add, output_computed, output_limbs); + eval_packed_generic_are_equal( + yield_constr, + is_add, + output_computed, + output_limbs.iter().copied(), + ); } #[allow(clippy::needless_collect)] @@ -132,17 +135,18 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr: &mut RecursiveConstraintConsumer, ) { let is_add = lv[IS_ADD]; - let input0_limbs = ADD_INPUT_0.iter().map(|&c| lv[c]); - let input1_limbs = ADD_INPUT_1.iter().map(|&c| lv[c]); - let output_limbs = ADD_OUTPUT.iter().map(|&c| lv[c]); + let input0_limbs = &lv[ADD_INPUT_0]; + let input1_limbs = &lv[ADD_INPUT_1]; + let output_limbs = &lv[ADD_OUTPUT]; // Since `map` is lazy and the closure passed to it borrows // `builder`, we can't then borrow builder again below in the call // to `eval_ext_circuit_are_equal`. The solution is to force // evaluation with `collect`. let output_computed = input0_limbs + .iter() .zip(input1_limbs) - .map(|(a, b)| builder.add_extension(a, b)) + .map(|(&a, &b)| builder.add_extension(a, b)) .collect::>>(); eval_ext_circuit_are_equal( @@ -150,7 +154,7 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr, is_add, output_computed.into_iter(), - output_limbs, + output_limbs.iter().copied(), ); } @@ -203,7 +207,7 @@ mod tests { for _ in 0..N_RND_TESTS { // set inputs to random values - for (&ai, bi) in ADD_INPUT_0.iter().zip(ADD_INPUT_1) { + for (ai, bi) in ADD_INPUT_0.zip(ADD_INPUT_1) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); } diff --git a/evm/src/arithmetic/columns.rs b/evm/src/arithmetic/columns.rs index ca8ba549..ee73f223 100644 --- a/evm/src/arithmetic/columns.rs +++ b/evm/src/arithmetic/columns.rs @@ -1,5 +1,7 @@ //! Arithmetic unit +use std::ops::Range; + pub const LIMB_BITS: usize = 16; const EVM_REGISTER_BITS: usize = 256; @@ -44,57 +46,42 @@ pub(crate) const ALL_OPERATIONS: [usize; 16] = [ /// used by any arithmetic circuit, depending on which one is active /// this cycle. Can be increased as needed as other operations are /// implemented. -const NUM_SHARED_COLS: usize = 144; // only need 64 for add, sub, and mul +const NUM_SHARED_COLS: usize = 9 * N_LIMBS; // only need 64 for add, sub, and mul -const fn shared_col(i: usize) -> usize { - assert!(i < NUM_SHARED_COLS); - START_SHARED_COLS + i -} +const GENERAL_INPUT_0: Range = START_SHARED_COLS..START_SHARED_COLS + N_LIMBS; +const GENERAL_INPUT_1: Range = GENERAL_INPUT_0.end..GENERAL_INPUT_0.end + N_LIMBS; +const GENERAL_INPUT_2: Range = GENERAL_INPUT_1.end..GENERAL_INPUT_1.end + N_LIMBS; +const GENERAL_INPUT_3: Range = GENERAL_INPUT_2.end..GENERAL_INPUT_2.end + N_LIMBS; +const AUX_INPUT_0: Range = GENERAL_INPUT_3.end..GENERAL_INPUT_3.end + 2 * N_LIMBS; +const AUX_INPUT_1: Range = AUX_INPUT_0.end..AUX_INPUT_0.end + 2 * N_LIMBS; +const AUX_INPUT_2: Range = AUX_INPUT_1.end..AUX_INPUT_1.end + N_LIMBS; -const fn gen_input_cols(start: usize) -> [usize; N] { - let mut cols = [0usize; N]; - let mut i = 0; - while i < N { - cols[i] = shared_col(start + i); - i += 1; - } - cols -} +pub(crate) const ADD_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const ADD_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const ADD_OUTPUT: Range = GENERAL_INPUT_2; -const GENERAL_INPUT_0: [usize; N_LIMBS] = gen_input_cols::(0); -const GENERAL_INPUT_1: [usize; N_LIMBS] = gen_input_cols::(N_LIMBS); -const GENERAL_INPUT_2: [usize; N_LIMBS] = gen_input_cols::(2 * N_LIMBS); -const GENERAL_INPUT_3: [usize; N_LIMBS] = gen_input_cols::(3 * N_LIMBS); -const AUX_INPUT_0: [usize; 2 * N_LIMBS] = gen_input_cols::<{ 2 * N_LIMBS }>(4 * N_LIMBS); -const AUX_INPUT_1: [usize; 2 * N_LIMBS] = gen_input_cols::<{ 2 * N_LIMBS }>(6 * N_LIMBS); -const AUX_INPUT_2: [usize; N_LIMBS] = gen_input_cols::(8 * N_LIMBS); +pub(crate) const SUB_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const SUB_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const SUB_OUTPUT: Range = GENERAL_INPUT_2; -pub(crate) const ADD_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const ADD_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const ADD_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; +pub(crate) const MUL_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const MUL_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const MUL_OUTPUT: Range = GENERAL_INPUT_2; +pub(crate) const MUL_AUX_INPUT: Range = GENERAL_INPUT_3; -pub(crate) const SUB_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const SUB_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const SUB_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; +pub(crate) const CMP_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const CMP_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const CMP_OUTPUT: usize = GENERAL_INPUT_2.start; +pub(crate) const CMP_AUX_INPUT: Range = GENERAL_INPUT_3; -pub(crate) const MUL_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const MUL_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const MUL_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_2; -pub(crate) const MUL_AUX_INPUT: [usize; N_LIMBS] = GENERAL_INPUT_3; - -pub(crate) const CMP_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const CMP_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const CMP_OUTPUT: usize = GENERAL_INPUT_2[0]; -pub(crate) const CMP_AUX_INPUT: [usize; N_LIMBS] = GENERAL_INPUT_3; - -pub(crate) const MODULAR_INPUT_0: [usize; N_LIMBS] = GENERAL_INPUT_0; -pub(crate) const MODULAR_INPUT_1: [usize; N_LIMBS] = GENERAL_INPUT_1; -pub(crate) const MODULAR_MODULUS: [usize; N_LIMBS] = GENERAL_INPUT_2; -pub(crate) const MODULAR_OUTPUT: [usize; N_LIMBS] = GENERAL_INPUT_3; -pub(crate) const MODULAR_QUO_INPUT: [usize; 2 * N_LIMBS] = AUX_INPUT_0; -// NB: Last value is not used in AUX, it is used in IS_ZERO -pub(crate) const MODULAR_AUX_INPUT: [usize; 2 * N_LIMBS] = AUX_INPUT_1; -pub(crate) const MODULAR_MOD_IS_ZERO: usize = AUX_INPUT_1[2 * N_LIMBS - 1]; -pub(crate) const MODULAR_OUT_AUX_RED: [usize; N_LIMBS] = AUX_INPUT_2; +pub(crate) const MODULAR_INPUT_0: Range = GENERAL_INPUT_0; +pub(crate) const MODULAR_INPUT_1: Range = GENERAL_INPUT_1; +pub(crate) const MODULAR_MODULUS: Range = GENERAL_INPUT_2; +pub(crate) const MODULAR_OUTPUT: Range = GENERAL_INPUT_3; +pub(crate) const MODULAR_QUO_INPUT: Range = AUX_INPUT_0; +// NB: Last value is not used in AUX, it is used in MOD_IS_ZERO +pub(crate) const MODULAR_AUX_INPUT: Range = AUX_INPUT_1; +pub(crate) const MODULAR_MOD_IS_ZERO: usize = AUX_INPUT_1.end - 1; +pub(crate) const MODULAR_OUT_AUX_RED: Range = AUX_INPUT_2; pub const NUM_ARITH_COLUMNS: usize = START_SHARED_COLS + NUM_SHARED_COLS; diff --git a/evm/src/arithmetic/compare.rs b/evm/src/arithmetic/compare.rs index a6566db5..55dc5764 100644 --- a/evm/src/arithmetic/compare.rs +++ b/evm/src/arithmetic/compare.rs @@ -22,12 +22,13 @@ use plonky2::iop::ext_target::ExtensionTarget; use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal}; use crate::arithmetic::columns::*; use crate::arithmetic::sub::u256_sub_br; +use crate::arithmetic::utils::read_value_u64_limbs; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; pub(crate) fn generate(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) { - let input0 = CMP_INPUT_0.map(|c| lv[c].to_canonical_u64()); - let input1 = CMP_INPUT_1.map(|c| lv[c].to_canonical_u64()); + let input0 = read_value_u64_limbs(lv, CMP_INPUT_0); + let input1 = read_value_u64_limbs(lv, CMP_INPUT_1); let (diff, br) = match op { // input0 - input1 == diff + br*2^256 @@ -39,9 +40,7 @@ pub(crate) fn generate(lv: &mut [F; NUM_ARITH_COLUMNS], op: usize) _ => panic!("op code not a comparison"), }; - for (&c, diff_limb) in CMP_AUX_INPUT.iter().zip(diff) { - lv[c] = F::from_canonical_u64(diff_limb); - } + lv[CMP_AUX_INPUT].copy_from_slice(&diff.map(|c| F::from_canonical_u64(c))); lv[CMP_OUTPUT] = F::from_canonical_u64(br); } @@ -56,15 +55,17 @@ fn eval_packed_generic_check_is_one_bit( pub(crate) fn eval_packed_generic_lt( yield_constr: &mut ConstraintConsumer

, is_op: P, - input0: [P; N_LIMBS], - input1: [P; N_LIMBS], - aux: [P; N_LIMBS], + input0: &[P], + input1: &[P], + aux: &[P], output: P, ) { + debug_assert!(input0.len() == N_LIMBS && input1.len() == N_LIMBS && aux.len() == N_LIMBS); + // Verify (input0 < input1) == output by providing aux such that // input0 - input1 == aux + output*2^256. - let lhs_limbs = input0.iter().zip(input1).map(|(&a, b)| a - b); - let cy = eval_packed_generic_are_equal(yield_constr, is_op, aux.into_iter(), lhs_limbs); + let lhs_limbs = input0.iter().zip(input1).map(|(&a, &b)| a - b); + let cy = eval_packed_generic_are_equal(yield_constr, is_op, aux.iter().copied(), lhs_limbs); // We don't need to check that cy is 0 or 1, since output has // already been checked to be 0 or 1. yield_constr.constraint(is_op * (cy - output)); @@ -81,9 +82,9 @@ pub fn eval_packed_generic( 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 input0 = &lv[CMP_INPUT_0]; + let input1 = &lv[CMP_INPUT_1]; + let aux = &lv[CMP_AUX_INPUT]; let output = lv[CMP_OUTPUT]; let is_cmp = is_lt + is_gt; @@ -109,11 +110,13 @@ pub(crate) fn eval_ext_circuit_lt, const D: usize>( builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, yield_constr: &mut RecursiveConstraintConsumer, is_op: ExtensionTarget, - input0: [ExtensionTarget; N_LIMBS], - input1: [ExtensionTarget; N_LIMBS], - aux: [ExtensionTarget; N_LIMBS], + input0: &[ExtensionTarget], + input1: &[ExtensionTarget], + aux: &[ExtensionTarget], output: ExtensionTarget, ) { + debug_assert!(input0.len() == N_LIMBS && input1.len() == N_LIMBS && aux.len() == N_LIMBS); + // Since `map` is lazy and the closure passed to it borrows // `builder`, we can't then borrow builder again below in the call // to `eval_ext_circuit_are_equal`. The solution is to force @@ -121,14 +124,14 @@ pub(crate) fn eval_ext_circuit_lt, const D: usize>( let lhs_limbs = input0 .iter() .zip(input1) - .map(|(&a, b)| builder.sub_extension(a, b)) + .map(|(&a, &b)| builder.sub_extension(a, b)) .collect::>>(); let cy = eval_ext_circuit_are_equal( builder, yield_constr, is_op, - aux.into_iter(), + aux.iter().copied(), lhs_limbs.into_iter(), ); let good_output = builder.sub_extension(cy, output); @@ -144,9 +147,9 @@ pub fn eval_ext_circuit, const D: usize>( 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 input0 = &lv[CMP_INPUT_0]; + let input1 = &lv[CMP_INPUT_1]; + let aux = &lv[CMP_AUX_INPUT]; let output = lv[CMP_OUTPUT]; let is_cmp = builder.add_extension(is_lt, is_gt); @@ -210,7 +213,7 @@ mod tests { lv[other_op] = F::ZERO; // set inputs to random values - for (&ai, bi) in CMP_INPUT_0.iter().zip(CMP_INPUT_1) { + for (ai, bi) in CMP_INPUT_0.zip(CMP_INPUT_1) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); } diff --git a/evm/src/arithmetic/modular.rs b/evm/src/arithmetic/modular.rs index fd2a2e28..53051cda 100644 --- a/evm/src/arithmetic/modular.rs +++ b/evm/src/arithmetic/modular.rs @@ -160,9 +160,9 @@ fn generate_modular_op( ) { // Inputs are all range-checked in [0, 2^16), so the "as i64" // conversion is safe. - let input0_limbs = MODULAR_INPUT_0.map(|c| F::to_canonical_u64(&lv[c]) as i64); - let input1_limbs = MODULAR_INPUT_1.map(|c| F::to_canonical_u64(&lv[c]) as i64); - let mut modulus_limbs = MODULAR_MODULUS.map(|c| F::to_canonical_u64(&lv[c]) as i64); + let input0_limbs = read_value_i64_limbs(lv, MODULAR_INPUT_0); + let input1_limbs = read_value_i64_limbs(lv, MODULAR_INPUT_1); + let mut modulus_limbs = read_value_i64_limbs(lv, MODULAR_MODULUS); // The use of BigUints is just to avoid having to implement // modular reduction. @@ -175,12 +175,11 @@ fn generate_modular_op( let mut constr_poly = [0i64; 2 * N_LIMBS]; constr_poly[..2 * N_LIMBS - 1].copy_from_slice(&operation(input0_limbs, input1_limbs)); + let mut mod_is_zero = F::ZERO; if modulus.is_zero() { modulus += 1u32; modulus_limbs[0] += 1i64; - lv[MODULAR_MOD_IS_ZERO] = F::ONE; - } else { - lv[MODULAR_MOD_IS_ZERO] = F::ZERO; + mod_is_zero = F::ONE; } let input = columns_to_biguint(&constr_poly); @@ -214,19 +213,11 @@ fn generate_modular_op( // the result of removing that root. let aux_limbs = pol_remove_root_2exp::(constr_poly); - for deg in 0..N_LIMBS { - lv[MODULAR_OUTPUT[deg]] = F::from_canonical_i64(output_limbs[deg]); - lv[MODULAR_OUT_AUX_RED[deg]] = F::from_canonical_i64(out_aux_red[deg]); - lv[MODULAR_QUO_INPUT[deg]] = F::from_canonical_i64(quot_limbs[deg]); - lv[MODULAR_QUO_INPUT[deg + N_LIMBS]] = F::from_canonical_i64(quot_limbs[deg + N_LIMBS]); - lv[MODULAR_AUX_INPUT[deg]] = F::from_noncanonical_i64(aux_limbs[deg]); - // Don't overwrite MODULAR_MOD_IS_ZERO, which is at the last - // index of MODULAR_AUX_INPUT - if deg < N_LIMBS - 1 { - lv[MODULAR_AUX_INPUT[deg + N_LIMBS]] = - F::from_noncanonical_i64(aux_limbs[deg + N_LIMBS]); - } - } + lv[MODULAR_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_i64(c))); + lv[MODULAR_OUT_AUX_RED].copy_from_slice(&out_aux_red.map(|c| F::from_canonical_i64(c))); + lv[MODULAR_QUO_INPUT].copy_from_slice("_limbs.map(|c| F::from_canonical_i64(c))); + lv[MODULAR_AUX_INPUT].copy_from_slice(&aux_limbs.map(|c| F::from_noncanonical_i64(c))); + lv[MODULAR_MOD_IS_ZERO] = mod_is_zero; } /// Generate the output and auxiliary values for modular operations. @@ -262,7 +253,7 @@ fn modular_constr_poly( range_check_error!(MODULAR_AUX_INPUT, 20, signed); range_check_error!(MODULAR_OUTPUT, 16); - let mut modulus = MODULAR_MODULUS.map(|c| lv[c]); + let mut modulus = read_value::(lv, MODULAR_MODULUS); let mod_is_zero = lv[MODULAR_MOD_IS_ZERO]; // Check that mod_is_zero is zero or one @@ -277,22 +268,22 @@ fn modular_constr_poly( // modulus = 0. modulus[0] += mod_is_zero; - let output = MODULAR_OUTPUT.map(|c| lv[c]); + let output = &lv[MODULAR_OUTPUT]; // Verify that the output is reduced, i.e. output < modulus. - let out_aux_red = MODULAR_OUT_AUX_RED.map(|c| lv[c]); + let out_aux_red = &lv[MODULAR_OUT_AUX_RED]; let is_less_than = P::ONES; eval_packed_generic_lt( yield_constr, filter, output, - modulus, + &modulus, out_aux_red, is_less_than, ); // prod = q(x) * m(x) - let quot = MODULAR_QUO_INPUT.map(|c| lv[c]); + let quot = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_QUO_INPUT); let prod = pol_mul_wide2(quot, modulus); // higher order terms must be zero for &x in prod[2 * N_LIMBS..].iter() { @@ -301,10 +292,10 @@ fn modular_constr_poly( // constr_poly = c(x) + q(x) * m(x) let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap(); - pol_add_assign(&mut constr_poly, &output); + pol_add_assign(&mut constr_poly, output); // constr_poly = c(x) + q(x) * m(x) + (x - β) * s(x) - let mut aux = MODULAR_AUX_INPUT.map(|c| lv[c]); + let mut aux = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_AUX_INPUT); aux[2 * N_LIMBS - 1] = P::ZEROS; // zero out the MOD_IS_ZERO flag let base = P::Scalar::from_canonical_u64(1 << LIMB_BITS); pol_add_assign(&mut constr_poly, &pol_adjoin_root(aux, base)); @@ -324,8 +315,8 @@ pub(crate) fn eval_packed_generic( // constr_poly has 2*N_LIMBS limbs let constr_poly = modular_constr_poly(lv, yield_constr, filter); - let input0 = MODULAR_INPUT_0.map(|c| lv[c]); - let input1 = MODULAR_INPUT_1.map(|c| lv[c]); + let input0 = read_value(lv, MODULAR_INPUT_0); + let input1 = read_value(lv, MODULAR_INPUT_1); let add_input = pol_add(input0, input1); let mul_input = pol_mul_wide(input0, input1); @@ -362,7 +353,7 @@ fn modular_constr_poly_ext_circuit, const D: usize> yield_constr: &mut RecursiveConstraintConsumer, filter: ExtensionTarget, ) -> [ExtensionTarget; 2 * N_LIMBS] { - let mut modulus = MODULAR_MODULUS.map(|c| lv[c]); + let mut modulus = read_value::(lv, MODULAR_MODULUS); let mod_is_zero = lv[MODULAR_MOD_IS_ZERO]; let t = builder.mul_sub_extension(mod_is_zero, mod_is_zero, mod_is_zero); @@ -376,20 +367,20 @@ fn modular_constr_poly_ext_circuit, const D: usize> modulus[0] = builder.add_extension(modulus[0], mod_is_zero); - let output = MODULAR_OUTPUT.map(|c| lv[c]); - let out_aux_red = MODULAR_OUT_AUX_RED.map(|c| lv[c]); + let output = &lv[MODULAR_OUTPUT]; + let out_aux_red = &lv[MODULAR_OUT_AUX_RED]; let is_less_than = builder.one_extension(); eval_ext_circuit_lt( builder, yield_constr, filter, output, - modulus, + &modulus, out_aux_red, is_less_than, ); - let quot = MODULAR_QUO_INPUT.map(|c| lv[c]); + let quot = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_QUO_INPUT); let prod = pol_mul_wide2_ext_circuit(builder, quot, modulus); for &x in prod[2 * N_LIMBS..].iter() { let t = builder.mul_extension(filter, x); @@ -397,9 +388,9 @@ fn modular_constr_poly_ext_circuit, const D: usize> } let mut constr_poly: [_; 2 * N_LIMBS] = prod[0..2 * N_LIMBS].try_into().unwrap(); - pol_add_assign_ext_circuit(builder, &mut constr_poly, &output); + pol_add_assign_ext_circuit(builder, &mut constr_poly, output); - let mut aux = MODULAR_AUX_INPUT.map(|c| lv[c]); + let mut aux = read_value::<{ 2 * N_LIMBS }, _>(lv, MODULAR_AUX_INPUT); aux[2 * N_LIMBS - 1] = builder.zero_extension(); let base = builder.constant_extension(F::Extension::from_canonical_u64(1u64 << LIMB_BITS)); let t = pol_adjoin_root_ext_circuit(builder, aux, base); @@ -421,8 +412,8 @@ pub(crate) fn eval_ext_circuit, const D: usize>( let constr_poly = modular_constr_poly_ext_circuit(lv, builder, yield_constr, filter); - let input0 = MODULAR_INPUT_0.map(|c| lv[c]); - let input1 = MODULAR_INPUT_1.map(|c| lv[c]); + let input0 = read_value(lv, MODULAR_INPUT_0); + let input1 = read_value(lv, MODULAR_INPUT_1); let add_input = pol_add_ext_circuit(builder, input0, input1); let mul_input = pol_mul_wide_ext_circuit(builder, input0, input1); @@ -498,11 +489,7 @@ mod tests { for i in 0..N_RND_TESTS { // set inputs to random values - for (&ai, &bi, &mi) in izip!( - MODULAR_INPUT_0.iter(), - MODULAR_INPUT_1.iter(), - MODULAR_MODULUS.iter() - ) { + for (ai, bi, mi) in izip!(MODULAR_INPUT_0, MODULAR_INPUT_1, MODULAR_MODULUS) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); lv[mi] = F::from_canonical_u16(rng.gen()); @@ -514,7 +501,7 @@ mod tests { if i > N_RND_TESTS / 2 { // 1 <= start < N_LIMBS let start = (rng.gen::() % (N_LIMBS - 1)) + 1; - for &mi in &MODULAR_MODULUS[start..N_LIMBS] { + for mi in MODULAR_MODULUS.skip(start) { lv[mi] = F::ZERO; } } @@ -552,11 +539,7 @@ mod tests { for _i in 0..N_RND_TESTS { // set inputs to random values and the modulus to zero; // the output is defined to be zero when modulus is zero. - for (&ai, &bi, &mi) in izip!( - MODULAR_INPUT_0.iter(), - MODULAR_INPUT_1.iter(), - MODULAR_MODULUS.iter() - ) { + for (ai, bi, mi) in izip!(MODULAR_INPUT_0, MODULAR_INPUT_1, MODULAR_MODULUS) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); lv[mi] = F::ZERO; @@ -565,7 +548,7 @@ mod tests { generate(&mut lv, op_filter); // check that the correct output was generated - assert!(MODULAR_OUTPUT.iter().all(|&oi| lv[oi] == F::ZERO)); + assert!(lv[MODULAR_OUTPUT].iter().all(|&c| c == F::ZERO)); let mut constraint_consumer = ConstraintConsumer::new( vec![GoldilocksField(2), GoldilocksField(3), GoldilocksField(5)], @@ -580,7 +563,7 @@ mod tests { .all(|&acc| acc == F::ZERO)); // Corrupt one output limb by setting it to a non-zero value - let random_oi = MODULAR_OUTPUT[rng.gen::() % N_LIMBS]; + let random_oi = MODULAR_OUTPUT.start + rng.gen::() % N_LIMBS; lv[random_oi] = F::from_canonical_u16(rng.gen_range(1..u16::MAX)); eval_packed_generic(&lv, &mut constraint_consumer); diff --git a/evm/src/arithmetic/mul.rs b/evm/src/arithmetic/mul.rs index c98b9af8..7dda18e2 100644 --- a/evm/src/arithmetic/mul.rs +++ b/evm/src/arithmetic/mul.rs @@ -67,8 +67,8 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::range_check_error; pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { - let input0_limbs = MUL_INPUT_0.map(|c| lv[c].to_canonical_u64() as i64); - let input1_limbs = MUL_INPUT_1.map(|c| lv[c].to_canonical_u64() as i64); + let input0 = read_value_i64_limbs(lv, MUL_INPUT_0); + let input1 = read_value_i64_limbs(lv, MUL_INPUT_1); const MASK: i64 = (1i64 << LIMB_BITS) - 1i64; @@ -79,7 +79,7 @@ pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { // First calculate the coefficients of a(x)*b(x) (in unreduced_prod), // then do carry propagation to obtain C = c(β) = a(β)*b(β). let mut cy = 0i64; - let mut unreduced_prod = pol_mul_lo(input0_limbs, input1_limbs); + let mut unreduced_prod = pol_mul_lo(input0, input1); for col in 0..N_LIMBS { let t = unreduced_prod[col] + cy; cy = t >> LIMB_BITS; @@ -90,15 +90,13 @@ pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { // aux_limbs to handle the fact that unreduced_prod will // inevitably contain one digit's worth that is > 2^256. + lv[MUL_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_i64(c))); pol_sub_assign(&mut unreduced_prod, &output_limbs); let mut aux_limbs = pol_remove_root_2exp::(unreduced_prod); aux_limbs[N_LIMBS - 1] = -cy; - for deg in 0..N_LIMBS { - lv[MUL_OUTPUT[deg]] = F::from_canonical_i64(output_limbs[deg]); - lv[MUL_AUX_INPUT[deg]] = F::from_noncanonical_i64(aux_limbs[deg]); - } + lv[MUL_AUX_INPUT].copy_from_slice(&aux_limbs.map(|c| F::from_noncanonical_i64(c))); } pub fn eval_packed_generic( @@ -111,10 +109,10 @@ pub fn eval_packed_generic( range_check_error!(MUL_AUX_INPUT, 20); let is_mul = lv[IS_MUL]; - let input0_limbs = MUL_INPUT_0.map(|c| lv[c]); - let input1_limbs = MUL_INPUT_1.map(|c| lv[c]); - let output_limbs = MUL_OUTPUT.map(|c| lv[c]); - let aux_limbs = MUL_AUX_INPUT.map(|c| lv[c]); + let input0_limbs = read_value::(lv, MUL_INPUT_0); + let input1_limbs = read_value::(lv, MUL_INPUT_1); + let output_limbs = read_value::(lv, MUL_OUTPUT); + let aux_limbs = read_value::(lv, MUL_AUX_INPUT); // Constraint poly holds the coefficients of the polynomial that // must be identically zero for this multiplication to be @@ -123,13 +121,13 @@ pub fn eval_packed_generic( // These two lines set constr_poly to the polynomial a(x)b(x) - c(x), // where a, b and c are the polynomials // - // a(x) = \sum_i input0_limbs[i] * β^i - // b(x) = \sum_i input1_limbs[i] * β^i - // c(x) = \sum_i output_limbs[i] * β^i + // a(x) = \sum_i input0_limbs[i] * x^i + // b(x) = \sum_i input1_limbs[i] * x^i + // c(x) = \sum_i output_limbs[i] * x^i // - // This polynomial should equal where s is + // This polynomial should equal (x - β)*s(x) where s is // - // s(x) = \sum_i aux_limbs[i] * β^i + // s(x) = \sum_i aux_limbs[i] * x^i // let mut constr_poly = pol_mul_lo(input0_limbs, input1_limbs); pol_sub_assign(&mut constr_poly, &output_limbs); @@ -153,10 +151,10 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr: &mut RecursiveConstraintConsumer, ) { let is_mul = lv[IS_MUL]; - let input0_limbs = MUL_INPUT_0.map(|c| lv[c]); - let input1_limbs = MUL_INPUT_1.map(|c| lv[c]); - let output_limbs = MUL_OUTPUT.map(|c| lv[c]); - let aux_limbs = MUL_AUX_INPUT.map(|c| lv[c]); + let input0_limbs = read_value::(lv, MUL_INPUT_0); + let input1_limbs = read_value::(lv, MUL_INPUT_1); + let output_limbs = read_value::(lv, MUL_OUTPUT); + let aux_limbs = read_value::(lv, MUL_AUX_INPUT); let mut constr_poly = pol_mul_lo_ext_circuit(builder, input0_limbs, input1_limbs); pol_sub_assign_ext_circuit(builder, &mut constr_poly, &output_limbs); @@ -220,7 +218,7 @@ mod tests { for _i in 0..N_RND_TESTS { // set inputs to random values - for (&ai, bi) in MUL_INPUT_0.iter().zip(MUL_INPUT_1) { + for (ai, bi) in MUL_INPUT_0.zip(MUL_INPUT_1) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); } diff --git a/evm/src/arithmetic/sub.rs b/evm/src/arithmetic/sub.rs index 25834406..f8377651 100644 --- a/evm/src/arithmetic/sub.rs +++ b/evm/src/arithmetic/sub.rs @@ -6,6 +6,7 @@ use plonky2::iop::ext_target::ExtensionTarget; use crate::arithmetic::add::{eval_ext_circuit_are_equal, eval_packed_generic_are_equal}; use crate::arithmetic::columns::*; +use crate::arithmetic::utils::read_value_u64_limbs; use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; use crate::range_check_error; @@ -28,14 +29,12 @@ pub(crate) fn u256_sub_br(input0: [u64; N_LIMBS], input1: [u64; N_LIMBS]) -> ([u } pub fn generate(lv: &mut [F; NUM_ARITH_COLUMNS]) { - let input0_limbs = SUB_INPUT_0.map(|c| lv[c].to_canonical_u64()); - let input1_limbs = SUB_INPUT_1.map(|c| lv[c].to_canonical_u64()); + let input0 = read_value_u64_limbs(lv, SUB_INPUT_0); + let input1 = read_value_u64_limbs(lv, SUB_INPUT_1); - let (output_limbs, _) = u256_sub_br(input0_limbs, input1_limbs); + let (output_limbs, _) = u256_sub_br(input0, input1); - for (&c, output_limb) in SUB_OUTPUT.iter().zip(output_limbs) { - lv[c] = F::from_canonical_u64(output_limb); - } + lv[SUB_OUTPUT].copy_from_slice(&output_limbs.map(|c| F::from_canonical_u64(c))); } pub fn eval_packed_generic( @@ -47,13 +46,18 @@ pub fn eval_packed_generic( range_check_error!(SUB_OUTPUT, 16); let is_sub = lv[IS_SUB]; - let input0_limbs = SUB_INPUT_0.iter().map(|&c| lv[c]); - let input1_limbs = SUB_INPUT_1.iter().map(|&c| lv[c]); - let output_limbs = SUB_OUTPUT.iter().map(|&c| lv[c]); + let input0_limbs = &lv[SUB_INPUT_0]; + let input1_limbs = &lv[SUB_INPUT_1]; + let output_limbs = &lv[SUB_OUTPUT]; - let output_computed = input0_limbs.zip(input1_limbs).map(|(a, b)| a - b); + let output_computed = input0_limbs.iter().zip(input1_limbs).map(|(&a, &b)| a - b); - eval_packed_generic_are_equal(yield_constr, is_sub, output_limbs, output_computed); + eval_packed_generic_are_equal( + yield_constr, + is_sub, + output_limbs.iter().copied(), + output_computed, + ); } #[allow(clippy::needless_collect)] @@ -63,24 +67,25 @@ pub fn eval_ext_circuit, const D: usize>( yield_constr: &mut RecursiveConstraintConsumer, ) { let is_sub = lv[IS_SUB]; - let input0_limbs = SUB_INPUT_0.iter().map(|&c| lv[c]); - let input1_limbs = SUB_INPUT_1.iter().map(|&c| lv[c]); - let output_limbs = SUB_OUTPUT.iter().map(|&c| lv[c]); + let input0_limbs = &lv[SUB_INPUT_0]; + let input1_limbs = &lv[SUB_INPUT_1]; + let output_limbs = &lv[SUB_OUTPUT]; // Since `map` is lazy and the closure passed to it borrows // `builder`, we can't then borrow builder again below in the call // to `eval_ext_circuit_are_equal`. The solution is to force // evaluation with `collect`. let output_computed = input0_limbs + .iter() .zip(input1_limbs) - .map(|(a, b)| builder.sub_extension(a, b)) + .map(|(&a, &b)| builder.sub_extension(a, b)) .collect::>>(); eval_ext_circuit_are_equal( builder, yield_constr, is_sub, - output_limbs, + output_limbs.iter().copied(), output_computed.into_iter(), ); } @@ -134,7 +139,7 @@ mod tests { for _ in 0..N_RND_TESTS { // set inputs to random values - for (&ai, bi) in SUB_INPUT_0.iter().zip(SUB_INPUT_1) { + for (ai, bi) in SUB_INPUT_0.zip(SUB_INPUT_1) { lv[ai] = F::from_canonical_u16(rng.gen()); lv[bi] = F::from_canonical_u16(rng.gen()); } diff --git a/evm/src/arithmetic/utils.rs b/evm/src/arithmetic/utils.rs index ccb8bc0a..871a9646 100644 --- a/evm/src/arithmetic/utils.rs +++ b/evm/src/arithmetic/utils.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, AddAssign, Mul, Neg, Shr, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Mul, Neg, Range, Shr, Sub, SubAssign}; use log::error; use plonky2::field::extension::Extendable; @@ -6,7 +6,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::iop::ext_target::ExtensionTarget; use plonky2::plonk::circuit_builder::CircuitBuilder; -use crate::arithmetic::columns::N_LIMBS; +use crate::arithmetic::columns::{NUM_ARITH_COLUMNS, N_LIMBS}; /// Emit an error message regarding unchecked range assumptions. /// Assumes the values in `cols` are `[cols[0], cols[0] + 1, ..., @@ -14,7 +14,7 @@ use crate::arithmetic::columns::N_LIMBS; pub(crate) fn _range_check_error( file: &str, line: u32, - cols: &[usize], + cols: Range, signedness: &str, ) { error!( @@ -23,8 +23,8 @@ pub(crate) fn _range_check_error( file, RC_BITS, signedness, - cols[0], - cols[0] + cols.len() - 1 + cols.start, + cols.end - 1, ); } @@ -34,7 +34,7 @@ macro_rules! range_check_error { $crate::arithmetic::utils::_range_check_error::<$rc_bits>( file!(), line!(), - &$cols, + $cols, "unsigned", ); }; @@ -42,7 +42,7 @@ macro_rules! range_check_error { $crate::arithmetic::utils::_range_check_error::<$rc_bits>( file!(), line!(), - &$cols, + $cols, "signed", ); }; @@ -337,3 +337,34 @@ where } q } + +/// Read the range `value_idxs` of values from `lv` into an array of +/// length `N`. Panics if the length of the range is not `N`. +pub(crate) fn read_value( + lv: &[T; NUM_ARITH_COLUMNS], + value_idxs: Range, +) -> [T; N] { + lv[value_idxs].try_into().unwrap() +} + +/// Read the range `value_idxs` of values from `lv` into an array of +/// length `N`, interpreting the values as `u64`s. Panics if the +/// length of the range is not `N`. +pub(crate) fn read_value_u64_limbs( + lv: &[F; NUM_ARITH_COLUMNS], + value_idxs: Range, +) -> [u64; N] { + let limbs: [_; N] = lv[value_idxs].try_into().unwrap(); + limbs.map(|c| F::to_canonical_u64(&c)) +} + +/// Read the range `value_idxs` of values from `lv` into an array of +/// length `N`, interpreting the values as `i64`s. Panics if the +/// length of the range is not `N`. +pub(crate) fn read_value_i64_limbs( + lv: &[F; NUM_ARITH_COLUMNS], + value_idxs: Range, +) -> [i64; N] { + let limbs: [_; N] = lv[value_idxs].try_into().unwrap(); + limbs.map(|c| F::to_canonical_u64(&c) as i64) +}