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.
This commit is contained in:
Hamish Ivey-Law 2022-10-12 02:39:13 +11:00 committed by GitHub
parent 0d0067554e
commit 68a5428500
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 192 additions and 181 deletions

View File

@ -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<F: RichField>(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<P: PackedField>(
@ -114,15 +112,20 @@ pub fn eval_packed_generic<P: PackedField>(
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<F: RichField + Extendable<D>, const D: usize>(
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
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::<Vec<ExtensionTarget<D>>>();
eval_ext_circuit_are_equal(
@ -150,7 +154,7 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, 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());
}

View File

@ -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<usize> = START_SHARED_COLS..START_SHARED_COLS + N_LIMBS;
const GENERAL_INPUT_1: Range<usize> = GENERAL_INPUT_0.end..GENERAL_INPUT_0.end + N_LIMBS;
const GENERAL_INPUT_2: Range<usize> = GENERAL_INPUT_1.end..GENERAL_INPUT_1.end + N_LIMBS;
const GENERAL_INPUT_3: Range<usize> = GENERAL_INPUT_2.end..GENERAL_INPUT_2.end + N_LIMBS;
const AUX_INPUT_0: Range<usize> = GENERAL_INPUT_3.end..GENERAL_INPUT_3.end + 2 * N_LIMBS;
const AUX_INPUT_1: Range<usize> = AUX_INPUT_0.end..AUX_INPUT_0.end + 2 * N_LIMBS;
const AUX_INPUT_2: Range<usize> = AUX_INPUT_1.end..AUX_INPUT_1.end + N_LIMBS;
const fn gen_input_cols<const N: usize>(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<usize> = GENERAL_INPUT_0;
pub(crate) const ADD_INPUT_1: Range<usize> = GENERAL_INPUT_1;
pub(crate) const ADD_OUTPUT: Range<usize> = GENERAL_INPUT_2;
const GENERAL_INPUT_0: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(0);
const GENERAL_INPUT_1: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(N_LIMBS);
const GENERAL_INPUT_2: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(2 * N_LIMBS);
const GENERAL_INPUT_3: [usize; N_LIMBS] = gen_input_cols::<N_LIMBS>(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::<N_LIMBS>(8 * N_LIMBS);
pub(crate) const SUB_INPUT_0: Range<usize> = GENERAL_INPUT_0;
pub(crate) const SUB_INPUT_1: Range<usize> = GENERAL_INPUT_1;
pub(crate) const SUB_OUTPUT: Range<usize> = 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<usize> = GENERAL_INPUT_0;
pub(crate) const MUL_INPUT_1: Range<usize> = GENERAL_INPUT_1;
pub(crate) const MUL_OUTPUT: Range<usize> = GENERAL_INPUT_2;
pub(crate) const MUL_AUX_INPUT: Range<usize> = 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<usize> = GENERAL_INPUT_0;
pub(crate) const CMP_INPUT_1: Range<usize> = GENERAL_INPUT_1;
pub(crate) const CMP_OUTPUT: usize = GENERAL_INPUT_2.start;
pub(crate) const CMP_AUX_INPUT: Range<usize> = 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<usize> = GENERAL_INPUT_0;
pub(crate) const MODULAR_INPUT_1: Range<usize> = GENERAL_INPUT_1;
pub(crate) const MODULAR_MODULUS: Range<usize> = GENERAL_INPUT_2;
pub(crate) const MODULAR_OUTPUT: Range<usize> = GENERAL_INPUT_3;
pub(crate) const MODULAR_QUO_INPUT: Range<usize> = 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<usize> = AUX_INPUT_1;
pub(crate) const MODULAR_MOD_IS_ZERO: usize = AUX_INPUT_1.end - 1;
pub(crate) const MODULAR_OUT_AUX_RED: Range<usize> = AUX_INPUT_2;
pub const NUM_ARITH_COLUMNS: usize = START_SHARED_COLS + NUM_SHARED_COLS;

View File

@ -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<F: RichField>(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<F: RichField>(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<P: PackedField>(
pub(crate) fn eval_packed_generic_lt<P: PackedField>(
yield_constr: &mut ConstraintConsumer<P>,
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<P: PackedField>(
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<F: RichField + Extendable<D>, const D: usize>(
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
is_op: ExtensionTarget<D>,
input0: [ExtensionTarget<D>; N_LIMBS],
input1: [ExtensionTarget<D>; N_LIMBS],
aux: [ExtensionTarget<D>; N_LIMBS],
input0: &[ExtensionTarget<D>],
input1: &[ExtensionTarget<D>],
aux: &[ExtensionTarget<D>],
output: ExtensionTarget<D>,
) {
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<F: RichField + Extendable<D>, 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::<Vec<ExtensionTarget<D>>>();
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<F: RichField + Extendable<D>, 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());
}

View File

@ -160,9 +160,9 @@ fn generate_modular_op<F: RichField>(
) {
// 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<F: RichField>(
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<F: RichField>(
// the result of removing that root.
let aux_limbs = pol_remove_root_2exp::<LIMB_BITS, _, { 2 * N_LIMBS }>(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(&quot_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<P: PackedField>(
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::<N_LIMBS, _>(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<P: PackedField>(
// 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<P: PackedField>(
// 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<P: PackedField>(
// 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<F: RichField + Extendable<D>, const D: usize>
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
filter: ExtensionTarget<D>,
) -> [ExtensionTarget<D>; 2 * N_LIMBS] {
let mut modulus = MODULAR_MODULUS.map(|c| lv[c]);
let mut modulus = read_value::<N_LIMBS, _>(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<F: RichField + Extendable<D>, 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<F: RichField + Extendable<D>, 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<F: RichField + Extendable<D>, 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::<usize>() % (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::<usize>() % N_LIMBS];
let random_oi = MODULAR_OUTPUT.start + rng.gen::<usize>() % N_LIMBS;
lv[random_oi] = F::from_canonical_u16(rng.gen_range(1..u16::MAX));
eval_packed_generic(&lv, &mut constraint_consumer);

View File

@ -67,8 +67,8 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer
use crate::range_check_error;
pub fn generate<F: RichField>(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<F: RichField>(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<F: RichField>(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::<LIMB_BITS, _, N_LIMBS>(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<P: PackedField>(
@ -111,10 +109,10 @@ pub fn eval_packed_generic<P: PackedField>(
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::<N_LIMBS, _>(lv, MUL_INPUT_0);
let input1_limbs = read_value::<N_LIMBS, _>(lv, MUL_INPUT_1);
let output_limbs = read_value::<N_LIMBS, _>(lv, MUL_OUTPUT);
let aux_limbs = read_value::<N_LIMBS, _>(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<P: PackedField>(
// 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<F: RichField + Extendable<D>, const D: usize>(
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
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::<N_LIMBS, _>(lv, MUL_INPUT_0);
let input1_limbs = read_value::<N_LIMBS, _>(lv, MUL_INPUT_1);
let output_limbs = read_value::<N_LIMBS, _>(lv, MUL_OUTPUT);
let aux_limbs = read_value::<N_LIMBS, _>(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());
}

View File

@ -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<F: RichField>(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<P: PackedField>(
@ -47,13 +46,18 @@ pub fn eval_packed_generic<P: PackedField>(
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<F: RichField + Extendable<D>, const D: usize>(
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
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::<Vec<ExtensionTarget<D>>>();
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());
}

View File

@ -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<const RC_BITS: u32>(
file: &str,
line: u32,
cols: &[usize],
cols: Range<usize>,
signedness: &str,
) {
error!(
@ -23,8 +23,8 @@ pub(crate) fn _range_check_error<const RC_BITS: u32>(
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<const N: usize, T: Copy>(
lv: &[T; NUM_ARITH_COLUMNS],
value_idxs: Range<usize>,
) -> [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<const N: usize, F: RichField>(
lv: &[F; NUM_ARITH_COLUMNS],
value_idxs: Range<usize>,
) -> [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<const N: usize, F: RichField>(
lv: &[F; NUM_ARITH_COLUMNS],
value_idxs: Range<usize>,
) -> [i64; N] {
let limbs: [_; N] = lv[value_idxs].try_into().unwrap();
limbs.map(|c| F::to_canonical_u64(&c) as i64)
}