use std::{marker::PhantomData, ops::Rem};
use midnight_proofs::{
circuit::{Chip, Layouter, Value},
plonk::{Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector},
poly::Rotation,
};
use num_bigint::{BigInt as BI, ToBigInt};
use num_traits::One;
use crate::{
ecc::curves::CircuitCurve,
field::foreign::{
params::FieldEmulationParams,
util::{
compute_u, compute_vj, get_advice_vec, get_identity_auxiliary_bounds, pair_wise_prod,
sum_bigints, sum_exprs, urem,
},
FieldChip, FieldChipConfig,
},
instructions::NativeInstructions,
types::AssignedField,
utils::util::bigint_to_fe,
CircuitField,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AdditionConfig<C: CircuitCurve> {
q: Selector,
u_bounds: (BI, BI),
vs_bounds: Vec<(BI, BI)>,
sign_col: Column<Fixed>,
_marker: PhantomData<C>,
}
impl<C: CircuitCurve> AdditionConfig<C> {
pub fn bounds<F, P>(
nb_parallel_range_checks: usize,
max_bit_len: u32,
) -> ((BI, BI), Vec<(BI, BI)>)
where
F: CircuitField,
P: FieldEmulationParams<F, C::Base>,
{
let base = BI::from(2).pow(P::LOG2_BASE);
let nb_limbs = P::NB_LIMBS;
let moduli = P::moduli();
let bs = P::base_powers();
let bs_sqrd = P::double_base_powers();
let limbs_max = vec![&base - BI::one(); nb_limbs as usize];
let limbs_max_sqrd_val = (&base - BI::one()).pow(2);
let limbs_max_sqrd = vec![limbs_max_sqrd_val.clone(); (nb_limbs * nb_limbs) as usize];
let max_sum = sum_bigints(&bs, &limbs_max);
let max_sum_sqrd = sum_bigints(&bs_sqrd, &limbs_max_sqrd);
let expr_min = -(BI::from(2) * &max_sum + &max_sum_sqrd + BI::from(2));
let expr_max = BI::from(3) * &max_sum + &max_sum_sqrd;
let expr_bounds = (expr_min, expr_max);
let expr_mj_bounds: Vec<_> = moduli
.iter()
.map(|mj| {
let bs_mj = bs.iter().map(|b| b.rem(mj)).collect::<Vec<_>>();
let bs_sqrd_mj = bs_sqrd.iter().map(|b| b.rem(mj)).collect::<Vec<_>>();
let max_sum_mj = sum_bigints(&bs_mj, &limbs_max);
let max_sum_sqrd_mj = sum_bigints(&bs_sqrd_mj, &limbs_max_sqrd);
let expr_min_mj = -(BI::from(2) * &max_sum_mj + &max_sum_sqrd_mj + BI::from(2));
let expr_max_mj = BI::from(3) * &max_sum_mj + &max_sum_sqrd_mj;
(expr_min_mj, expr_max_mj)
})
.collect();
get_identity_auxiliary_bounds::<F, C::Base>(
"addition",
&moduli,
expr_bounds,
&expr_mj_bounds,
nb_parallel_range_checks,
max_bit_len,
)
}
pub fn configure<F, P>(
meta: &mut ConstraintSystem<F>,
field_chip_config: &FieldChipConfig,
sign_col: Column<Fixed>,
nb_parallel_range_checks: usize,
max_bit_len: u32,
) -> AdditionConfig<C>
where
F: CircuitField,
P: FieldEmulationParams<F, C::Base>,
{
let m = &C::Base::modulus().to_bigint().unwrap();
let moduli = P::moduli();
let bs = P::base_powers();
let bs_sqrd = P::double_base_powers();
let ((k_min, u_max), vs_bounds) =
Self::bounds::<F, P>(nb_parallel_range_checks, max_bit_len);
let q = meta.selector();
meta.create_gate("Foreign-Edwards addition", |meta| {
let x_limbs = get_advice_vec(meta, &field_chip_config.x_cols, Rotation::prev());
let y_limbs = get_advice_vec(meta, &field_chip_config.x_cols, Rotation::cur());
let z_limbs = get_advice_vec(meta, &field_chip_config.x_cols, Rotation::next());
let w_limbs = get_advice_vec(meta, &field_chip_config.z_cols, Rotation::prev());
let u = meta.query_advice(field_chip_config.u_col, Rotation::next());
let vs = get_advice_vec(meta, &field_chip_config.v_cols, Rotation::next());
let s = meta.query_fixed(sign_col, Rotation::cur());
let xw_limbs = pair_wise_prod(&x_limbs, &w_limbs);
let native_id = (Expression::from(1) + s.clone()) * sum_exprs::<F>(&bs, &x_limbs)
+ s.clone() * (sum_exprs::<F>(&bs, &w_limbs) + sum_exprs::<F>(&bs_sqrd, &xw_limbs))
- sum_exprs::<F>(&bs, &y_limbs)
- sum_exprs::<F>(&bs, &z_limbs)
+ s.clone()
- Expression::from(1)
- (&u + Expression::Constant(bigint_to_fe::<F>(&k_min)))
* Expression::Constant(bigint_to_fe::<F>(m));
let mut moduli_ids = moduli
.iter()
.zip(vs)
.zip(vs_bounds.iter())
.map(|((mj, vj), vj_bounds)| {
let bs_mj = bs.iter().map(|b| b.rem(mj)).collect::<Vec<_>>();
let bs_sqrd_mj = bs_sqrd.iter().map(|b| b.rem(mj)).collect::<Vec<_>>();
let (lj_min, _) = vj_bounds;
let xw_limbs_mj = pair_wise_prod(&x_limbs, &w_limbs);
(Expression::from(1) + s.clone()) * sum_exprs::<F>(&bs_mj, &x_limbs)
+ s.clone()
* (sum_exprs::<F>(&bs_mj, &w_limbs)
+ sum_exprs::<F>(&bs_sqrd_mj, &xw_limbs_mj))
- sum_exprs::<F>(&bs_mj, &y_limbs)
- sum_exprs::<F>(&bs_mj, &z_limbs)
+ s.clone()
- Expression::from(1)
- &u * Expression::Constant(bigint_to_fe::<F>(&urem(m, mj)))
- Expression::Constant(bigint_to_fe::<F>(&urem(&(&k_min * m), mj)))
- (vj + Expression::Constant(bigint_to_fe::<F>(lj_min)))
* Expression::Constant(bigint_to_fe::<F>(mj))
})
.collect::<Vec<_>>();
moduli_ids.push(native_id);
Constraints::with_selector(q, moduli_ids)
});
AdditionConfig {
q,
sign_col,
u_bounds: (k_min, u_max),
vs_bounds,
_marker: PhantomData,
}
}
}
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub fn assert_addition_coordinate<F, C, P, N>(
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, C::Base, P>,
y: &AssignedField<F, C::Base, P>,
z: &AssignedField<F, C::Base, P>,
w: &AssignedField<F, C::Base, P>,
negate_w: bool,
base_chip: &FieldChip<F, C::Base, P, N>,
addition_config: &AdditionConfig<C>,
) -> Result<(), Error>
where
F: CircuitField,
C: CircuitCurve,
P: FieldEmulationParams<F, C::Base>,
N: NativeInstructions<F>,
{
let m = &C::Base::modulus().to_bigint().unwrap();
let moduli = P::moduli();
let bs = P::base_powers();
let bs_sqrd = P::double_base_powers();
let field_chip_config = base_chip.config();
let x_norm = &base_chip.normalize(layouter, x)?;
let y_norm = &base_chip.normalize(layouter, y)?;
let z_norm = &base_chip.normalize(layouter, z)?;
let w_norm = &base_chip.normalize(layouter, w)?;
let sign: BI = if negate_w { -BI::one() } else { BI::one() };
let sx_coeff = BI::one() + &sign; let const_offset = &sign - BI::one();
let range_checks = layouter.assign_region(
|| "Foreign-Edwards addition",
|mut region| {
let xs_val = x_norm.bigint_limbs();
let ys_val = y_norm.bigint_limbs();
let zs_val = z_norm.bigint_limbs();
let ws_val = w_norm.bigint_limbs();
let xw_val = xs_val.clone().zip(ws_val.clone()).map(|(x, w)| pair_wise_prod(&x, &w));
let (k_min, u_max) = addition_config.u_bounds.clone();
let w_term = ws_val.clone().map(|v| sum_bigints(&bs, &v))
+ xw_val.clone().map(|v| sum_bigints(&bs_sqrd, &v));
let expr = xs_val.clone().map(|v| &sx_coeff * sum_bigints(&bs, &v))
+ w_term.map(|v| &sign * v)
- ys_val.clone().map(|v| sum_bigints(&bs, &v))
- zs_val.clone().map(|v| sum_bigints(&bs, &v))
+ Value::known(const_offset.clone());
let u = expr.map(|e| compute_u(m, &e, (&k_min, &u_max), Value::unknown()));
let vs_values =
moduli.iter().zip(addition_config.vs_bounds.iter()).map(|(mj, vj_bounds)| {
let bs_mj = bs.iter().map(|b| b.rem(mj)).collect::<Vec<_>>();
let bs_sqrd_mj = bs_sqrd.iter().map(|b| b.rem(mj)).collect::<Vec<_>>();
let (lj_min, vj_max) = vj_bounds.clone();
let w_term_mj = ws_val.clone().map(|v| sum_bigints(&bs_mj, &v))
+ xw_val.clone().map(|v| sum_bigints(&bs_sqrd_mj, &v));
let expr_mj = xs_val.clone().map(|v| &sx_coeff * sum_bigints(&bs_mj, &v))
+ w_term_mj.map(|v| &sign * v)
- ys_val.clone().map(|v| sum_bigints(&bs_mj, &v))
- zs_val.clone().map(|v| sum_bigints(&bs_mj, &v))
+ Value::known(const_offset.clone());
expr_mj.zip(u.clone()).map(|(e, u)| {
compute_vj(m, mj, &e, &u, &k_min, (&lj_min, &vj_max), Value::unknown())
})
});
let x_limbs = x_norm.limb_values();
let y_limbs = y_norm.limb_values();
let z_limbs = z_norm.limb_values();
let w_limbs = w_norm.limb_values();
let mut offset = 0;
x_limbs
.iter()
.zip(field_chip_config.x_cols.iter())
.map(|(cell, &col)| {
cell.copy_advice(|| "Edwards.addition x", &mut region, col, offset)
})
.collect::<Result<Vec<_>, _>>()?;
w_limbs
.iter()
.zip(field_chip_config.z_cols.iter())
.map(|(cell, &col)| {
cell.copy_advice(|| "Edwards.addition w", &mut region, col, offset)
})
.collect::<Result<Vec<_>, _>>()?;
offset += 1;
addition_config.q.enable(&mut region, offset)?;
let sign_fe = if negate_w { -F::ONE } else { F::ONE };
region.assign_fixed(
|| "Edwards.addition sign",
addition_config.sign_col,
offset,
|| Value::known(sign_fe),
)?;
y_limbs
.iter()
.zip(field_chip_config.x_cols.iter())
.map(|(cell, &col)| {
cell.copy_advice(|| "Edwards.addition y", &mut region, col, offset)
})
.collect::<Result<Vec<_>, _>>()?;
offset += 1;
z_limbs
.iter()
.zip(field_chip_config.x_cols.iter())
.map(|(cell, &col)| {
cell.copy_advice(|| "Edwards.addition z", &mut region, col, offset)
})
.collect::<Result<Vec<_>, _>>()?;
let u_value = u.clone().map(|u| bigint_to_fe::<F>(&u));
let u_cell = region.assign_advice(
|| "Edwards.addition u",
field_chip_config.u_col,
offset,
|| u_value,
)?;
let vs_cells = vs_values
.zip(field_chip_config.v_cols.iter())
.map(|(vj, &col)| {
let vj_value = vj.map(|vj| bigint_to_fe::<F>(&vj));
region.assign_advice(|| "Edwards.addition vj", col, offset, || vj_value)
})
.collect::<Result<Vec<_>, _>>()?;
let u_range_check = (u_cell, u_max);
let vs_max = addition_config.vs_bounds.iter().map(|(_, vj_max)| vj_max.clone());
let vs_range_checks = vs_cells.into_iter().zip(vs_max);
Ok([u_range_check].into_iter().chain(vs_range_checks).collect::<Vec<_>>())
},
)?;
range_checks.iter().try_for_each(|(cell, ubound)| {
base_chip
.native_gadget
.assert_lower_than_fixed(layouter, cell, ubound.magnitude())
})
}