use std::{
cmp::{max, min},
fmt::Debug,
hash::{Hash, Hasher},
marker::PhantomData,
};
use midnight_proofs::{
circuit::{Chip, Layouter, Value},
plonk::{Advice, Column, ConstraintSystem, Error},
};
use num_bigint::{BigInt as BI, BigUint, ToBigInt};
use num_integer::Integer;
use num_traits::{One, Signed, Zero};
#[cfg(any(test, feature = "testing"))]
use {
crate::testing_utils::{FromScratch, Sampleable},
midnight_proofs::plonk::{Fixed, Instance},
rand::RngCore,
};
use super::gates::{
mul::{self, MulConfig},
norm::{self, NormConfig},
};
use crate::{
field::foreign::{
params::{check_params, FieldEmulationParams},
util::{bi_from_limbs, bi_to_limbs},
},
instructions::{
ArithInstructions, AssertionInstructions, AssignmentInstructions, CanonicityInstructions,
ControlFlowInstructions, ConversionInstructions, DecompositionInstructions,
EqualityInstructions, FieldInstructions, NativeInstructions, PublicInputInstructions,
ScalarFieldInstructions, ZeroInstructions,
},
types::{AssignedBit, AssignedByte, AssignedNative, InnerConstants, InnerValue, Instantiable},
utils::util::bigint_to_fe,
CircuitField,
};
#[derive(Clone, Debug)]
#[must_use]
pub struct AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
limb_values: Vec<AssignedNative<F>>,
limb_bounds: Vec<(BI, BI)>,
_marker: PhantomData<(K, P)>,
}
impl<F, K, P> PartialEq for AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
fn eq(&self, other: &Self) -> bool {
self.limb_values
.iter()
.zip(other.limb_values.iter())
.all(|(s, o)| s.cell() == o.cell())
}
}
impl<F: CircuitField, K: CircuitField, P: FieldEmulationParams<F, K>> Eq
for AssignedField<F, K, P>
{
}
impl<F, K, P> Hash for AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.limb_values.iter().for_each(|elem| elem.hash(state));
}
}
impl<F, K, P> Instantiable<F> for AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
fn as_public_input(element: &K) -> Vec<F> {
let element_as_bi = (*element - K::ONE).to_biguint().into();
let base = BI::from(2).pow(P::LOG2_BASE);
let nb_limbs_per_batch = (F::CAPACITY / P::LOG2_BASE) as usize;
bi_to_limbs(P::NB_LIMBS, &base, &element_as_bi)
.chunks(nb_limbs_per_batch)
.map(|chunk| bigint_to_fe::<F>(&bi_from_limbs(&base, chunk)))
.collect()
}
}
impl<F, K, P> InnerValue for AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
type Element = K;
fn value(&self) -> Value<K> {
let bi_limbs = self
.limb_values
.iter()
.zip(self.limb_bounds.iter())
.map(|(xi, (lower_bound, _))| {
let shift = BI::abs(lower_bound);
let fe_shift = bigint_to_fe::<F>(&shift);
xi.value().map(|xv| {
let bi: BI = (*xv + fe_shift).to_biguint().into();
bi - &shift
})
})
.collect::<Vec<_>>();
let bi_limbs: Value<Vec<BI>> = Value::from_iter(bi_limbs);
let base = BI::from(2).pow(P::LOG2_BASE);
bi_limbs.map(|limbs| bigint_to_fe::<K>(&(BI::one() + bi_from_limbs(&base, &limbs))))
}
}
impl<F: CircuitField, K: CircuitField, P> InnerConstants for AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
fn inner_zero() -> K {
K::ZERO
}
fn inner_one() -> K {
K::ONE
}
}
impl<F, K, P> AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
pub(crate) fn from_limbs_unsafe(limb_values: Vec<AssignedNative<F>>) -> Self {
debug_assert!(limb_values.len() as u32 == P::NB_LIMBS);
Self {
limb_values,
limb_bounds: well_formed_bounds::<F, K, P>(),
_marker: PhantomData,
}
}
}
#[cfg(any(test, feature = "testing"))]
impl<F, K, P> Sampleable for AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
fn sample_inner(rng: impl RngCore) -> K {
K::random(rng)
}
}
pub fn nb_field_chip_columns<F, K, P>() -> usize
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
P::NB_LIMBS as usize + max(P::NB_LIMBS as usize, 1 + P::moduli().len())
}
pub fn well_formed_log2_bounds<F, K, P>() -> Vec<u32>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
let m = &K::modulus().to_bigint().unwrap();
let log2_msl_bound = m.bits() as u32 - (P::NB_LIMBS - 1) * P::LOG2_BASE;
let mut bounds = vec![log2_msl_bound];
bounds.resize(P::NB_LIMBS as usize, P::LOG2_BASE);
bounds.into_iter().rev().collect::<Vec<_>>()
}
#[derive(Clone, Debug)]
pub struct FieldChipConfig {
mul_config: mul::MulConfig,
norm_config: norm::NormConfig,
pub x_cols: Vec<Column<Advice>>,
pub y_cols: Vec<Column<Advice>>,
pub z_cols: Vec<Column<Advice>>,
pub u_col: Column<Advice>,
pub v_cols: Vec<Column<Advice>>,
}
#[derive(Clone, Debug)]
pub struct FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
config: FieldChipConfig,
pub(crate) native_gadget: N,
_marker: PhantomData<(F, K, P, N)>,
}
impl<F, K, P> AssignedField<F, K, P>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
pub fn modulus(&self) -> BI {
K::modulus().to_bigint().unwrap().clone()
}
pub fn is_well_formed(&self) -> bool {
self.limb_bounds.iter().zip(well_formed_log2_bounds::<F, K, P>()).all(
|((lower, upper), expected_upper)| {
assert!(lower <= upper);
!BI::is_negative(lower) && upper.bits() <= expected_upper as u64
},
)
}
pub fn limb_values(&self) -> Vec<AssignedNative<F>> {
self.limb_values.clone()
}
pub fn bigint_limbs(&self) -> Value<Vec<BI>> {
let limbs = self
.limb_values
.iter()
.zip(self.limb_bounds.iter())
.map(|(xi, (lbound, _))| {
let shift = BI::abs(lbound);
let fe_shift = bigint_to_fe::<F>(&shift);
xi.value().map(|xv| {
let bi: BI = (*xv + fe_shift).to_biguint().into();
bi - &shift
})
})
.collect::<Vec<_>>();
Value::from_iter(limbs)
}
}
fn well_formed_bounds<F, K, P>() -> Vec<(BI, BI)>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
well_formed_log2_bounds::<F, K, P>()
.into_iter()
.map(|log2_base| (BI::zero(), BI::from(2).pow(log2_base) - BI::one()))
.collect()
}
fn limbs_of_zero<F, K, P>() -> Vec<BI>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
bi_to_limbs(
P::NB_LIMBS,
&BI::from(2).pow(P::LOG2_BASE),
&(K::modulus().to_bigint().unwrap() - BI::one()),
)
}
impl<F, K, P, N> Chip<F> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
type Config = FieldChipConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<F, K, P, N> AssignmentInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn assign(
&self,
layouter: &mut impl Layouter<F>,
x: Value<K>,
) -> Result<AssignedField<F, K, P>, Error> {
let base = BI::from(2).pow(P::LOG2_BASE);
let x = x.map(|v| {
let bi = (v - K::ONE).to_biguint().into();
bi_to_limbs(P::NB_LIMBS, &base, &bi)
});
let x_cells = (0..P::NB_LIMBS)
.map(|i| x.clone().map(|limbs| bigint_to_fe::<F>(&limbs[i as usize])))
.zip(well_formed_log2_bounds::<F, K, P>().iter())
.map(|(xi_value, log2_bound)| {
self.native_gadget.assign_lower_than_fixed(
layouter,
xi_value,
&(BigUint::one() << *log2_bound),
)
})
.collect::<Result<Vec<_>, Error>>()?;
Ok(AssignedField::<F, K, P> {
limb_values: x_cells,
limb_bounds: well_formed_bounds::<F, K, P>(),
_marker: PhantomData,
})
}
fn assign_fixed(
&self,
layouter: &mut impl Layouter<F>,
constant: K,
) -> Result<AssignedField<F, K, P>, Error> {
let base = BI::from(2).pow(P::LOG2_BASE);
let constant = (constant - K::ONE).to_biguint().into();
let constant_limbs = bi_to_limbs(P::NB_LIMBS, &base, &constant);
let constant_cells = constant_limbs
.iter()
.map(|x| self.native_gadget.assign_fixed(layouter, bigint_to_fe::<F>(x)))
.collect::<Result<Vec<_>, _>>()?;
Ok(AssignedField::<F, K, P> {
limb_values: constant_cells,
limb_bounds: well_formed_bounds::<F, K, P>(),
_marker: PhantomData,
})
}
}
impl<F, K, P> From<AssignedField<F, K, P>> for Vec<AssignedNative<F>>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
{
fn from(x: AssignedField<F, K, P>) -> Self {
x.limb_values()
}
}
impl<F, K, P, N> PublicInputInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn as_public_input(
&self,
layouter: &mut impl Layouter<F>,
assigned: &AssignedField<F, K, P>,
) -> Result<Vec<AssignedNative<F>>, Error> {
let assigned = self.normalize(layouter, assigned)?;
let nb_limbs_per_batch = (F::CAPACITY / P::LOG2_BASE) as usize;
let base = BI::from(2).pow(P::LOG2_BASE);
assigned
.limb_values
.chunks(nb_limbs_per_batch)
.map(|chunk| {
let terms: Vec<(F, AssignedNative<F>)> = chunk
.iter()
.enumerate()
.map(|(i, limb)| (bigint_to_fe::<F>(&base.pow(i as u32)), limb.clone()))
.collect();
self.native_gadget.linear_combination(layouter, &terms, F::ZERO)
})
.collect()
}
fn constrain_as_public_input(
&self,
layouter: &mut impl Layouter<F>,
assigned: &AssignedField<F, K, P>,
) -> Result<(), Error> {
self.as_public_input(layouter, assigned)?
.iter()
.try_for_each(|c| self.native_gadget.constrain_as_public_input(layouter, c))
}
fn assign_as_public_input(
&self,
layouter: &mut impl Layouter<F>,
value: Value<K>,
) -> Result<AssignedField<F, K, P>, Error> {
let assigned = self.assign(layouter, value)?;
self.constrain_as_public_input(layouter, &assigned)?;
Ok(assigned)
}
}
impl<F, K, P, N> AssertionInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn assert_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
) -> Result<(), Error> {
let x = self.normalize(layouter, x)?;
let y = self.normalize(layouter, y)?;
x.limb_values
.iter()
.zip(y.limb_values.iter())
.map(|(xi, yi)| self.native_gadget.assert_equal(layouter, xi, yi))
.collect::<Result<Vec<_>, _>>()?;
Ok(())
}
fn assert_not_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
) -> Result<(), Error> {
let diff = self.sub(layouter, x, y)?;
self.assert_non_zero(layouter, &diff)
}
fn assert_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
constant: K,
) -> Result<(), Error> {
let x = self.normalize(layouter, x)?;
let constant_limbs = {
let constant = (constant - K::ONE).to_biguint().into();
let base = BI::from(2).pow(P::LOG2_BASE);
bi_to_limbs(P::NB_LIMBS, &base, &constant)
};
x.limb_values
.iter()
.zip(constant_limbs.iter())
.map(|(xi, ki)| {
self.native_gadget.assert_equal_to_fixed(layouter, xi, bigint_to_fe::<F>(ki))
})
.collect::<Result<Vec<_>, _>>()?;
Ok(())
}
fn assert_not_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
constant: K,
) -> Result<(), Error> {
let diff = self.add_constant(layouter, x, -constant)?;
self.assert_non_zero(layouter, &diff)
}
}
impl<F, K, P, N> EqualityInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn is_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
) -> Result<AssignedBit<F>, Error> {
let diff = self.sub(layouter, x, y)?;
self.is_zero(layouter, &diff)
}
fn is_not_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
) -> Result<AssignedBit<F>, Error> {
let b = self.is_equal(layouter, x, y)?;
self.native_gadget.not(layouter, &b)
}
fn is_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
constant: <AssignedField<F, K, P> as InnerValue>::Element,
) -> Result<AssignedBit<F>, Error> {
let diff = self.add_constant(layouter, x, -constant)?;
self.is_zero(layouter, &diff)
}
fn is_not_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
constant: <AssignedField<F, K, P> as InnerValue>::Element,
) -> Result<AssignedBit<F>, Error> {
let b = self.is_equal_to_fixed(layouter, x, constant)?;
self.native_gadget.not(layouter, &b)
}
}
impl<F, K, P, N> ZeroInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn assert_non_zero(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
) -> Result<(), Error> {
let b = self.is_zero(layouter, x)?;
self.native_gadget.assert_equal_to_fixed(layouter, &b, false)
}
fn is_zero(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
) -> Result<AssignedBit<F>, Error> {
let x = self.normalize(layouter, x)?;
let bs = x
.limb_values
.iter()
.zip(limbs_of_zero::<F, K, P>().iter())
.map(|(xi, ci)| {
self.native_gadget.is_equal_to_fixed(layouter, xi, bigint_to_fe::<F>(ci))
})
.collect::<Result<Vec<_>, _>>()?;
self.native_gadget.and(layouter, &bs)
}
}
impl<F, K, P, N> ControlFlowInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn select(
&self,
layouter: &mut impl Layouter<F>,
cond: &AssignedBit<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let z_limb_values = x
.limb_values
.iter()
.zip(y.limb_values.iter())
.map(|(xi, yi)| self.native_gadget.select(layouter, cond, xi, yi))
.collect::<Result<Vec<_>, _>>()?;
let z_limb_bounds = x
.limb_bounds
.iter()
.zip(y.limb_bounds.iter())
.map(|(xi_bounds, yi_bounds)| {
(
min(&xi_bounds.0, &yi_bounds.0).clone(),
max(&xi_bounds.1, &yi_bounds.1).clone(),
)
})
.collect::<Vec<_>>();
Ok(AssignedField::<F, K, P> {
limb_values: z_limb_values,
limb_bounds: z_limb_bounds,
_marker: PhantomData,
})
}
}
impl<F, K, P, N> ArithInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn linear_combination(
&self,
layouter: &mut impl Layouter<F>,
terms: &[(K, AssignedField<F, K, P>)],
constant: K,
) -> Result<AssignedField<F, K, P>, Error> {
if terms.is_empty() {
return self.assign_fixed(layouter, constant);
}
if terms.iter().all(|(c, _)| *c == K::ONE || *c == -K::ONE) {
let (pos, neg): (Vec<_>, Vec<_>) = terms.iter().partition(|(c, _)| *c == K::ONE);
let pos: Vec<_> = pos.into_iter().map(|(_, x)| x.clone()).collect();
let neg: Vec<_> = neg.into_iter().map(|(_, x)| x.clone()).collect();
return self.sum(layouter, &pos, &neg, constant);
}
let init: AssignedField<F, K, P> = self.assign_fixed(layouter, constant)?;
let res = terms.iter().try_fold(init, |acc, (c, x)| {
let prod = self.mul_by_constant(layouter, x, *c)?;
self.add(layouter, &acc, &prod)
})?;
self.normalize_if_approaching_limit(layouter, &res)
}
fn add(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let zero: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ZERO)?;
if x == &zero {
return Ok(y.clone());
}
if y == &zero {
return Ok(x.clone());
}
let mut constants = vec![BI::one()];
constants.resize(P::NB_LIMBS as usize, BI::zero());
let z_limb_values = x
.limb_values
.iter()
.zip(y.limb_values.iter())
.zip(constants.iter().map(|ci| bigint_to_fe::<F>(ci)))
.map(|((xi, yi), ci)| {
self.native_gadget.linear_combination(
layouter,
&[(F::ONE, xi.clone()), (F::ONE, yi.clone())],
ci,
)
})
.collect::<Result<Vec<_>, _>>()?;
let z = AssignedField::<F, K, P> {
limb_values: z_limb_values,
limb_bounds: x
.limb_bounds
.iter()
.zip(y.limb_bounds.iter())
.zip(constants.iter())
.map(|((xi_bounds, yi_bounds), ci)| {
(
&xi_bounds.0 + &yi_bounds.0 + ci,
&xi_bounds.1 + &yi_bounds.1 + ci,
)
})
.collect(),
_marker: PhantomData,
};
self.normalize_if_approaching_limit(layouter, &z)
}
fn sub(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let zero: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ZERO)?;
if y == &zero {
return Ok(x.clone());
}
let mut constants = vec![BI::from(-1)];
constants.resize(P::NB_LIMBS as usize, BI::zero());
let z_limb_values = x
.limb_values
.iter()
.zip(y.limb_values.iter())
.zip(constants.iter().map(|ci| bigint_to_fe::<F>(ci)))
.map(|((xi, yi), ci)| {
self.native_gadget.linear_combination(
layouter,
&[(F::ONE, xi.clone()), (-F::ONE, yi.clone())],
ci,
)
})
.collect::<Result<Vec<_>, _>>()?;
let z = AssignedField::<F, K, P> {
limb_values: z_limb_values,
limb_bounds: x
.limb_bounds
.iter()
.zip(y.limb_bounds.iter())
.zip(constants.iter())
.map(|((xi_bounds, yi_bounds), ci)| {
(
&xi_bounds.0 - &yi_bounds.1 + ci,
&xi_bounds.1 - &yi_bounds.0 + ci,
)
})
.collect(),
_marker: PhantomData,
};
self.normalize_if_approaching_limit(layouter, &z)
}
fn mul(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
multiplying_constant: Option<K>,
) -> Result<AssignedField<F, K, P>, Error> {
let zero: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ZERO)?;
let one: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ONE)?;
if x == &zero || y == &zero {
return Ok(zero);
}
if x == &one {
return Ok(y.clone());
}
if y == &one {
return Ok(x.clone());
}
let y = match multiplying_constant {
None => y.clone(),
Some(k) => self.mul_by_constant(layouter, y, k)?,
};
self.assign_mul(layouter, x, &y, false)
}
fn div(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let one: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ONE)?;
if y == &one {
return Ok(x.clone());
}
let y = self.normalize(layouter, y)?;
self.assert_non_zero(layouter, &y)?;
self.assign_mul(layouter, x, &y, true)
}
fn neg(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let zero: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ZERO)?;
if x == &zero {
return Ok(zero);
}
let mut constants = vec![BI::from(-2)];
constants.resize(P::NB_LIMBS as usize, BI::zero());
let z_limb_values = x
.limb_values
.iter()
.zip(constants.iter().map(|ci| bigint_to_fe::<F>(ci)))
.map(|(xi, ci)| {
self.native_gadget.linear_combination(layouter, &[(-F::ONE, xi.clone())], ci)
})
.collect::<Result<Vec<_>, _>>()?;
let z = AssignedField::<F, K, P> {
limb_values: z_limb_values,
limb_bounds: x
.limb_bounds
.iter()
.zip(constants.iter())
.map(|(xi_bounds, ci)| (-&xi_bounds.1 + ci, -&xi_bounds.0 + ci))
.collect(),
_marker: PhantomData,
};
self.normalize_if_approaching_limit(layouter, &z)
}
fn inv(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let one: AssignedField<F, K, P> = self.assign_fixed(layouter, K::ONE)?;
if x == &one {
return Ok(one);
}
self.assign_mul(layouter, &one, x, true)
}
fn inv0(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let is_zero = self.is_zero(layouter, x)?;
let zero = self.assign_fixed(layouter, K::ZERO)?;
let one = self.assign_fixed(layouter, K::ONE)?;
let invertible = self.select(layouter, &is_zero, &one, x)?;
let inverse = self.assign_mul(layouter, &one, &invertible, true)?;
self.select(layouter, &is_zero, &zero, &inverse)
}
fn add_constant(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
k: K,
) -> Result<AssignedField<F, K, P>, Error> {
if k.is_zero().into() {
return Ok(x.clone());
}
let base = BI::from(2).pow(P::LOG2_BASE);
let k_limbs = bi_to_limbs(P::NB_LIMBS, &base, &k.to_biguint().into());
let z_limb_values = {
self.native_gadget.add_constants(
layouter,
&x.limb_values,
&k_limbs.iter().map(bigint_to_fe::<F>).collect::<Vec<_>>(),
)?
};
let z = AssignedField::<F, K, P> {
limb_values: z_limb_values,
limb_bounds: x
.limb_bounds
.iter()
.zip(k_limbs.iter())
.map(|(xi_bound, ki)| (&xi_bound.0 + ki, &xi_bound.1 + ki))
.collect(),
_marker: PhantomData,
};
self.normalize_if_approaching_limit(layouter, &z)
}
fn mul_by_constant(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
k: K,
) -> Result<AssignedField<F, K, P>, Error> {
if k.is_zero().into() {
return self.assign_fixed(layouter, K::ZERO);
}
if k == K::ONE {
return Ok(x.clone());
}
if k == -K::ONE {
return self.neg(layouter, x);
}
let threshold =
P::max_limb_bound().div_floor(&(BI::from(1000) * BI::from(2).pow(P::LOG2_BASE)));
if BI::from(k.to_biguint()) > threshold {
let assigned_k = self.assign_fixed(layouter, k)?;
return self.assign_mul(layouter, x, &assigned_k, false);
}
let k_as_bigint: BI = k.to_biguint().into();
let kv = bigint_to_fe::<F>(&k_as_bigint);
let mut constants = vec![k_as_bigint.clone() - BI::one()];
constants.resize(P::NB_LIMBS as usize, BI::zero());
let z_limb_values = x
.limb_values
.iter()
.zip(constants.iter().map(|ci| bigint_to_fe::<F>(ci)))
.map(|(xi, ci)| {
self.native_gadget.linear_combination(layouter, &[(kv, xi.clone())], ci)
})
.collect::<Result<Vec<_>, _>>()?;
let limb_bounds = x
.limb_bounds
.iter()
.zip(constants.iter())
.map(|(xi_bounds, ci)| {
(
&xi_bounds.0 * k_as_bigint.clone() + ci,
&xi_bounds.1 * k_as_bigint.clone() + ci,
)
})
.collect();
let z = AssignedField::<F, K, P> {
limb_values: z_limb_values,
limb_bounds,
_marker: PhantomData,
};
self.normalize_if_approaching_limit(layouter, &z)
}
}
impl<F, K, P, N> FieldInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn order(&self) -> BigUint {
K::modulus()
}
}
impl<F, K, P, N> ScalarFieldInstructions<F> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
type Scalar = AssignedField<F, K, P>;
}
impl<F, K, P, N> DecompositionInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn assigned_to_le_bits(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
nb_bits: Option<usize>,
enforce_canonical: bool,
) -> Result<Vec<AssignedBit<F>>, Error> {
let mut x = self.add_constant(layouter, x, K::ONE)?;
if enforce_canonical {
x = self.make_canonical(layouter, &x)?;
};
let mut bits = vec![];
x.limb_values
.iter()
.zip(well_formed_log2_bounds::<F, K, P>().iter())
.map(|(cell, log2_bound)| {
self.native_gadget.assigned_to_le_bits(
layouter,
cell,
Some(*log2_bound as usize),
true,
)
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.for_each(|new_bits| bits.extend(new_bits));
let nb_bits = min(
K::NUM_BITS as usize,
nb_bits.unwrap_or(K::NUM_BITS as usize),
);
bits[nb_bits..]
.iter()
.try_for_each(|byte| self.native_gadget.assert_equal_to_fixed(layouter, byte, false))?;
bits = bits[0..nb_bits].to_vec();
if enforce_canonical && nb_bits == K::NUM_BITS as usize {
let canonical = self.is_canonical(layouter, &bits)?;
self.assert_equal_to_fixed(layouter, &canonical, true)?;
}
Ok(bits)
}
fn assigned_to_le_bytes(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
nb_bytes: Option<usize>,
) -> Result<Vec<AssignedByte<F>>, Error> {
let nb_bytes = nb_bytes.unwrap_or(K::NUM_BITS.div_ceil(8) as usize);
let bits = self.assigned_to_le_bits(layouter, x, Some(nb_bytes * 8), true)?;
let bytes = bits
.chunks(8)
.map(|chunk| {
let terms = chunk
.iter()
.enumerate()
.map(|(i, bit)| (F::from(1 << i), bit.clone().into()))
.collect::<Vec<_>>();
let byte = self.native_gadget.linear_combination(layouter, &terms, F::ZERO)?;
self.native_gadget.convert_unsafe(layouter, &byte)
})
.collect::<Result<Vec<AssignedByte<F>>, Error>>()?;
bytes[nb_bytes..]
.iter()
.try_for_each(|byte| self.native_gadget.assert_equal_to_fixed(layouter, byte, 0u8))?;
Ok(bytes[0..nb_bytes].to_vec())
}
fn assigned_from_le_bits(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
) -> Result<AssignedField<F, K, P>, Error> {
let mut coeff = K::ONE;
let mut terms = vec![];
for chunk in bits.chunks(P::LOG2_BASE as usize) {
let mut native_coeff = F::ONE;
let mut native_terms = vec![];
for b in chunk.iter() {
let bit: AssignedNative<F> = b.clone().into();
native_terms.push((native_coeff, bit));
native_coeff = native_coeff + native_coeff;
}
let term = {
let limb =
self.native_gadget.linear_combination(layouter, &native_terms, F::ZERO)?;
self.assigned_field_from_limb(layouter, &limb)?
};
terms.push((coeff, term));
coeff = bigint_to_fe::<K>(&BI::from(2).pow(P::LOG2_BASE)) * coeff;
}
let x = self.linear_combination(layouter, &terms, K::ZERO)?;
self.normalize(layouter, &x)
}
fn assigned_from_le_bytes(
&self,
layouter: &mut impl Layouter<F>,
bytes: &[AssignedByte<F>],
) -> Result<AssignedField<F, K, P>, Error> {
let mut coeff = K::ONE;
let mut terms = vec![];
let nb_bytes_per_chunk = P::LOG2_BASE / 8;
for chunk in bytes.chunks(nb_bytes_per_chunk as usize) {
let mut native_coeff = F::ONE;
let mut native_terms = vec![];
for b in chunk.iter() {
let byte: AssignedNative<F> = b.clone().into();
native_terms.push((native_coeff, byte));
native_coeff = F::from(256) * native_coeff;
}
let term = {
let limb =
self.native_gadget.linear_combination(layouter, &native_terms, F::ZERO)?;
self.assigned_field_from_limb(layouter, &limb)?
};
terms.push((coeff, term));
coeff = bigint_to_fe::<K>(&BI::from(2).pow(8 * nb_bytes_per_chunk)) * coeff;
}
let x = self.linear_combination(layouter, &terms, K::ZERO)?;
self.normalize(layouter, &x)
}
fn assigned_to_le_chunks(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
nb_bits_per_chunk: usize,
nb_chunks: Option<usize>,
) -> Result<Vec<AssignedNative<F>>, Error> {
assert!(nb_bits_per_chunk < F::NUM_BITS as usize);
if P::LOG2_BASE % (nb_bits_per_chunk as u32) == 0 {
let nb_chunks_per_limb = (P::LOG2_BASE / (nb_bits_per_chunk as u32)) as usize;
let mut nb_missing_chunks =
nb_chunks.unwrap_or(nb_chunks_per_limb * P::NB_LIMBS as usize);
let x = self.add_constant(layouter, x, K::ONE)?;
let x = self.normalize(layouter, &x)?;
let chunks = x
.limb_values
.iter()
.map(|limb| {
let nb_chunks_on_this_limb = min(nb_missing_chunks, nb_chunks_per_limb);
nb_missing_chunks -= nb_chunks_on_this_limb;
self.native_gadget.assigned_to_le_chunks(
layouter,
limb,
nb_bits_per_chunk,
Some(nb_chunks_on_this_limb),
)
})
.collect::<Result<Vec<_>, Error>>()?
.concat();
assert_eq!(nb_missing_chunks, 0);
Ok(chunks)
}
else {
let bits = self.assigned_to_le_bits(layouter, x, None, false)?;
bits.chunks(nb_bits_per_chunk)
.map(|bits_of_chunk| {
self.native_gadget.assigned_from_le_bits(layouter, bits_of_chunk)
})
.collect::<Result<Vec<_>, Error>>()
}
}
}
impl<F, K, P, N> FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
pub fn new(config: &FieldChipConfig, native_gadget: &N) -> Self {
Self {
config: config.clone(),
native_gadget: native_gadget.clone(),
_marker: PhantomData,
}
}
pub fn configure(
meta: &mut ConstraintSystem<F>,
advice_columns: &[Column<Advice>],
nb_parallel_range_checks: usize,
max_bit_len: u32,
) -> FieldChipConfig {
check_params::<F, K, P>();
let nb_limbs = P::NB_LIMBS;
let x_cols = advice_columns[..(nb_limbs as usize)].to_vec();
let y_cols = x_cols.clone();
let z_cols = advice_columns[(nb_limbs as usize)..(2 * nb_limbs as usize)].to_vec();
x_cols.iter().chain(z_cols.iter()).for_each(|&col| meta.enable_equality(col));
let u_col = advice_columns[nb_limbs as usize];
let v_cols = advice_columns
[(nb_limbs as usize + 1)..(nb_limbs as usize + 1 + P::moduli().len())]
.to_vec();
let mul_config = MulConfig::configure::<F, K, P>(
meta,
&x_cols,
&z_cols,
nb_parallel_range_checks,
max_bit_len,
);
let norm_config = NormConfig::configure::<F, K, P>(
meta,
&x_cols,
&z_cols,
nb_parallel_range_checks,
max_bit_len,
);
FieldChipConfig {
mul_config,
norm_config,
x_cols,
y_cols,
z_cols,
u_col,
v_cols,
}
}
fn assign_mul(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
y: &AssignedField<F, K, P>,
division: bool,
) -> Result<AssignedField<F, K, P>, Error> {
let base = BI::from(2).pow(P::LOG2_BASE);
let nb_limbs = P::NB_LIMBS;
let x = self.normalize(layouter, x)?;
let y = self.normalize(layouter, y)?;
y.value().error_if_known_and(|yv| division && K::is_zero(yv).into())?;
let zv = x
.value()
.zip(y.value())
.map(|(xv, yv)| {
if division {
xv * yv.invert().unwrap()
} else {
xv * yv
}
})
.map(|z| bi_to_limbs(nb_limbs, &base, &(z - K::ONE).to_biguint().into()));
let z_values = (0..nb_limbs)
.map(|i| zv.clone().map(|zs| bigint_to_fe::<F>(&zs[i as usize])))
.collect::<Vec<_>>();
let z_limbs = z_values
.iter()
.zip(well_formed_log2_bounds::<F, K, P>().iter())
.map(|(&z_value, &log2_bound)| {
self.native_gadget.assign_lower_than_fixed(
layouter,
z_value,
&(BigUint::one() << log2_bound),
)
})
.collect::<Result<Vec<_>, Error>>()?;
let z = AssignedField::<F, K, P> {
limb_values: z_limbs,
limb_bounds: well_formed_bounds::<F, K, P>(),
_marker: PhantomData,
};
let (l, r) = if !division { (&x, &z) } else { (&z, &x) };
mul::assert_mul::<F, K, P, N>(
layouter,
l,
&y,
r,
&self.config.mul_config,
&self.native_gadget,
)?;
Ok(z)
}
}
impl<F, K, P, N> FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
pub fn sum(
&self,
layouter: &mut impl Layouter<F>,
positives: &[AssignedField<F, K, P>],
negatives: &[AssignedField<F, K, P>],
constant: K,
) -> Result<AssignedField<F, K, P>, Error> {
if positives.is_empty() && negatives.is_empty() {
return self.assign_fixed(layouter, constant);
}
let base = BI::from(2).pow(P::LOG2_BASE);
let p = positives.len();
let n = negatives.len();
let constant_repr: BI = (constant - K::ONE).to_biguint().into();
let constant_limbs = bi_to_limbs(P::NB_LIMBS, &base, &constant_repr);
let mut offsets = constant_limbs;
offsets[0] += BI::from(p) - BI::from(n);
let z_limb_values = (0..P::NB_LIMBS as usize)
.map(|i| {
let pos_terms: Vec<(F, AssignedNative<F>)> =
positives.iter().map(|x| (F::ONE, x.limb_values[i].clone())).collect();
let neg_terms: Vec<(F, AssignedNative<F>)> =
negatives.iter().map(|x| (-F::ONE, x.limb_values[i].clone())).collect();
let native_terms: Vec<_> = pos_terms.into_iter().chain(neg_terms).collect();
self.native_gadget.linear_combination(
layouter,
&native_terms,
bigint_to_fe::<F>(&offsets[i]),
)
})
.collect::<Result<Vec<_>, _>>()?;
let all_terms = positives
.iter()
.zip(std::iter::repeat(false))
.chain(negatives.iter().zip(std::iter::repeat(true)));
let init_bounds: Vec<(BI, BI)> = (0..P::NB_LIMBS as usize)
.map(|i| (offsets[i].clone(), offsets[i].clone()))
.collect();
let z_bounds = all_terms.fold(init_bounds, |acc, (x, negated)| {
acc.into_iter()
.zip(x.limb_bounds.iter())
.map(|((lo, hi), b)| {
if negated {
(lo - &b.1, hi - &b.0)
} else {
(lo + &b.0, hi + &b.1)
}
})
.collect()
});
let z = AssignedField::<F, K, P> {
limb_values: z_limb_values,
limb_bounds: z_bounds,
_marker: PhantomData,
};
self.normalize_if_approaching_limit(layouter, &z)
}
pub(crate) fn normalize(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
if x.is_well_formed() {
Ok(x.clone())
} else {
self.make_canonical(layouter, x)
}
}
pub(crate) fn normalize_if_approaching_limit(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let threshold: BI = P::max_limb_bound() / 10;
let dangerous_lower_bounds = x.limb_bounds.iter().any(|b| b.0 < -threshold.clone());
let dangerous_upper_bounds = x.limb_bounds.iter().any(|b| b.1 > threshold);
if dangerous_lower_bounds || dangerous_upper_bounds {
self.make_canonical(layouter, x)
} else {
Ok(x.clone())
}
}
fn make_canonical(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedField<F, K, P>,
) -> Result<AssignedField<F, K, P>, Error> {
let max_limb_bound = P::max_limb_bound();
x.limb_bounds.iter().for_each(|(lower, upper)| {
if lower < &(-&max_limb_bound) || upper > &max_limb_bound {
panic!(
"make_canonical: the limb bounds of the input: [{}, {}] exceed the
maximum limb bound value {}; consider applying a normalization
earlier, when the bounds are still within the permited range;
increasing the [max_limb_bound] of your FieldEmulationParams could also
help, if possible.",
lower, upper, max_limb_bound
);
}
});
let z_limbs = norm::normalize::<F, K, P, N>(
layouter,
x,
&self.config.norm_config,
&self.native_gadget,
)?;
let z = AssignedField::<F, K, P> {
limb_values: z_limbs,
limb_bounds: well_formed_bounds::<F, K, P>(),
_marker: PhantomData,
};
Ok(z)
}
fn assigned_field_from_limb(
&self,
layouter: &mut impl Layouter<F>,
limb: &AssignedNative<F>,
) -> Result<AssignedField<F, K, P>, Error> {
let least_significant_limb = self.native_gadget.add_constant(layouter, limb, -F::ONE)?;
let mut limb_values = vec![least_significant_limb];
let mut limb_bounds = well_formed_bounds::<F, K, P>();
let zero = self.native_gadget.assign_fixed(layouter, F::ZERO)?;
limb_values.resize(P::NB_LIMBS as usize, zero);
limb_bounds[0] = (limb_bounds[0].clone().0 - 1, limb_bounds[0].clone().1 - 1);
Ok(AssignedField::<F, K, P> {
limb_values,
limb_bounds,
_marker: PhantomData,
})
}
}
impl<F, K, P, N> AssignmentInstructions<F, AssignedBit<F>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn assign(
&self,
layouter: &mut impl Layouter<F>,
value: Value<bool>,
) -> Result<AssignedBit<F>, Error> {
self.native_gadget.assign(layouter, value)
}
fn assign_fixed(
&self,
layouter: &mut impl Layouter<F>,
constant: bool,
) -> Result<AssignedBit<F>, Error> {
self.native_gadget.assign_fixed(layouter, constant)
}
}
impl<F, K, P, N> AssertionInstructions<F, AssignedBit<F>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn assert_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
y: &AssignedBit<F>,
) -> Result<(), Error> {
self.native_gadget.assert_equal(layouter, x, y)
}
fn assert_not_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
y: &AssignedBit<F>,
) -> Result<(), Error> {
self.native_gadget.assert_not_equal(layouter, x, y)
}
fn assert_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
constant: bool,
) -> Result<(), Error> {
self.native_gadget.assert_equal_to_fixed(layouter, x, constant)
}
fn assert_not_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
constant: bool,
) -> Result<(), Error> {
self.native_gadget.assert_not_equal_to_fixed(layouter, x, constant)
}
}
impl<F, K, P, N> ConversionInstructions<F, AssignedBit<F>, AssignedField<F, K, P>>
for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn convert_value(&self, x: &bool) -> Option<K> {
Some(if *x { K::ONE } else { K::ZERO })
}
fn convert(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
) -> Result<AssignedField<F, K, P>, Error> {
let x: AssignedNative<F> = x.clone().into();
self.assigned_field_from_limb(layouter, &x)
}
}
impl<F, K, P, N> ConversionInstructions<F, AssignedByte<F>, AssignedField<F, K, P>>
for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn convert_value(&self, x: &u8) -> Option<K> {
Some(K::from(*x as u64))
}
fn convert(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedByte<F>,
) -> Result<AssignedField<F, K, P>, Error> {
let x: AssignedNative<F> = x.clone().into();
self.assigned_field_from_limb(layouter, &x)
}
}
impl<F, K, P, N> CanonicityInstructions<F, AssignedField<F, K, P>> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F>,
{
fn le_bits_lower_than(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
bound: BigUint,
) -> Result<AssignedBit<F>, Error> {
self.native_gadget.le_bits_lower_than(layouter, bits, bound)
}
fn le_bits_geq_than(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
bound: BigUint,
) -> Result<AssignedBit<F>, Error> {
self.native_gadget.le_bits_geq_than(layouter, bits, bound)
}
}
#[derive(Clone, Debug)]
#[cfg(any(test, feature = "testing"))]
pub struct FieldChipConfigForTests<F, N>
where
F: CircuitField,
N: NativeInstructions<F> + FromScratch<F>,
{
native_gadget_config: <N as FromScratch<F>>::Config,
field_chip_config: FieldChipConfig,
}
#[cfg(any(test, feature = "testing"))]
impl<F, K, P, N> FromScratch<F> for FieldChip<F, K, P, N>
where
F: CircuitField,
K: CircuitField,
P: FieldEmulationParams<F, K>,
N: NativeInstructions<F> + FromScratch<F>,
{
type Config = FieldChipConfigForTests<F, N>;
fn new_from_scratch(config: &FieldChipConfigForTests<F, N>) -> Self {
let native_gadget = <N as FromScratch<F>>::new_from_scratch(&config.native_gadget_config);
FieldChip::new(&config.field_chip_config, &native_gadget)
}
fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
self.native_gadget.load_from_scratch(layouter)
}
fn configure_from_scratch(
meta: &mut ConstraintSystem<F>,
advice_columns: &mut Vec<Column<Advice>>,
fixed_columns: &mut Vec<Column<Fixed>>,
instance_columns: &[Column<Instance>; 2],
) -> FieldChipConfigForTests<F, N> {
let native_gadget_config = <N as FromScratch<F>>::configure_from_scratch(
meta,
advice_columns,
fixed_columns,
instance_columns,
);
let nb_parallel_range_checks = 4;
let max_bit_len = 8;
let field_chip_config = {
let nb_fc_cols = nb_field_chip_columns::<F, K, P>();
while advice_columns.len() < nb_fc_cols {
advice_columns.push(meta.advice_column());
}
FieldChip::<F, K, P, N>::configure(
meta,
&advice_columns[..nb_fc_cols],
nb_parallel_range_checks,
max_bit_len,
)
};
FieldChipConfigForTests {
native_gadget_config,
field_chip_config,
}
}
}
#[cfg(test)]
mod tests {
use midnight_curves::{
k256::{Fp as K256Base, Fq as K256Scalar},
p256::{Fp as P256Base, Fq as P256Scalar},
Fq as BlsScalar,
};
use super::*;
use crate::{
field::{
decomposition::chip::P2RDecompositionChip, foreign::params::MultiEmulationParams,
NativeChip, NativeGadget,
},
instructions::{
arithmetic, assertions, control_flow, decomposition, equality, public_input, zero,
},
};
macro_rules! test_generic {
($mod:ident, $op:ident, $native:ident, $emulated:ident, $name:expr) => {
$mod::tests::$op::<
$native,
AssignedField<$native, $emulated, MultiEmulationParams>,
FieldChip<
$native,
$emulated,
MultiEmulationParams,
NativeGadget<$native, P2RDecompositionChip<$native>, NativeChip<$native>>,
>,
>($name);
};
}
macro_rules! test {
($mod:ident, $op:ident) => {
#[test]
fn $op() {
test_generic!($mod, $op, BlsScalar, K256Base, "field_chip_k256_base");
test_generic!($mod, $op, BlsScalar, K256Scalar, "field_chip_k256_scalar");
test_generic!($mod, $op, BlsScalar, P256Base, "field_chip_p256_base");
test_generic!($mod, $op, BlsScalar, P256Scalar, "field_chip_p256_scalar");
}
};
}
test!(assertions, test_assertions);
test!(public_input, test_public_inputs);
test!(equality, test_is_equal);
test!(zero, test_zero_assertions);
test!(zero, test_is_zero);
test!(control_flow, test_select);
test!(control_flow, test_cond_assert_equal);
test!(control_flow, test_cond_swap);
test!(arithmetic, test_add);
test!(arithmetic, test_sub);
test!(arithmetic, test_mul);
test!(arithmetic, test_div);
test!(arithmetic, test_neg);
test!(arithmetic, test_inv);
test!(arithmetic, test_pow);
test!(arithmetic, test_linear_combination);
test!(arithmetic, test_add_and_mul);
macro_rules! test_generic {
($mod:ident, $op:ident, $native:ident, $emulated:ident, $name:expr) => {
$mod::tests::$op::<
$native,
AssignedField<$native, $emulated, MultiEmulationParams>,
FieldChip<
$native,
$emulated,
MultiEmulationParams,
NativeGadget<$native, P2RDecompositionChip<$native>, NativeChip<$native>>,
>,
NativeGadget<$native, P2RDecompositionChip<$native>, NativeChip<$native>>,
>($name);
};
}
macro_rules! test {
($mod:ident, $op:ident) => {
#[test]
fn $op() {
test_generic!($mod, $op, BlsScalar, K256Base, "field_chip_k256_base");
test_generic!($mod, $op, BlsScalar, K256Scalar, "field_chip_k256_scalar");
test_generic!($mod, $op, BlsScalar, P256Base, "field_chip_p256_base");
test_generic!($mod, $op, BlsScalar, P256Scalar, "field_chip_p256_scalar");
}
};
}
test!(decomposition, test_bit_decomposition);
test!(decomposition, test_byte_decomposition);
test!(decomposition, test_sgn0);
}