use std::{
cell::RefCell,
cmp::min,
collections::HashMap,
hash::{Hash, Hasher},
ops::Neg,
rc::Rc,
};
use midnight_proofs::{
circuit::{Chip, Layouter, Region, Value},
plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Fixed, Instance, Selector},
poly::Rotation,
};
use num_bigint::BigUint;
use num_traits::Zero;
#[cfg(any(test, feature = "testing"))]
use crate::testing_utils::FromScratch;
use crate::{
instructions::{
public_input::CommittedInstanceInstructions, ArithInstructions, AssertionInstructions,
AssignmentInstructions, BinaryInstructions, CanonicityInstructions,
ControlFlowInstructions, ConversionInstructions, EqualityInstructions, FieldInstructions,
PublicInputInstructions, UnsafeConversionInstructions, ZeroInstructions,
},
types::{AssignedNative, InnerValue, Instantiable},
utils::ComposableChip,
CircuitField,
};
pub const NB_ARITH_COLS: usize = 5;
pub const NB_ARITH_FIXED_COLS: usize = NB_ARITH_COLS + 4;
const NB_PARALLEL_ADD_COLS: usize = 3;
#[derive(Clone, Debug)]
pub struct NativeConfig {
q_arith: Selector,
q_12_minus_34: Selector,
q_par_add: Selector,
pub(crate) value_cols: [Column<Advice>; NB_ARITH_COLS],
coeff_cols: [Column<Fixed>; NB_ARITH_COLS],
q_next_col: Column<Fixed>,
mul_ab_col: Column<Fixed>,
mul_ac_col: Column<Fixed>,
constant_col: Column<Fixed>,
fixed_values_col: Column<Fixed>,
committed_instance_col: Column<Instance>,
instance_col: Column<Instance>,
}
#[derive(Clone, Debug)]
pub struct NativeChip<F: CircuitField> {
config: NativeConfig,
cached_fixed: Rc<RefCell<HashMap<BigUint, AssignedNative<F>>>>,
committed_instance_offset: Rc<RefCell<usize>>,
instance_offset: Rc<RefCell<usize>>,
}
impl<F: CircuitField> Chip<F> for NativeChip<F> {
type Config = NativeConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<F: CircuitField> ComposableChip<F> for NativeChip<F> {
type SharedResources = (
[Column<Advice>; NB_ARITH_COLS],
[Column<Fixed>; NB_ARITH_FIXED_COLS],
[Column<Instance>; 2], );
type InstructionDeps = ();
fn new(config: &NativeConfig, _sub_chips: &()) -> Self {
Self {
config: config.clone(),
cached_fixed: Default::default(),
instance_offset: Rc::new(RefCell::new(0)),
committed_instance_offset: Rc::new(RefCell::new(0)),
}
}
fn configure(
meta: &mut ConstraintSystem<F>,
shared_res: &Self::SharedResources,
) -> NativeConfig {
let value_columns = &shared_res.0;
let fixed_columns = &shared_res.1;
let committed_instance_col = shared_res.2[0];
let instance_col = shared_res.2[1];
let q_arith = meta.selector();
let q_12_minus_34 = meta.selector();
let q_par_add = meta.selector();
let coeff_cols: [Column<Fixed>; NB_ARITH_COLS] = fixed_columns[4..].try_into().unwrap();
let q_next_col = fixed_columns[0];
let mul_ab_col = fixed_columns[1];
let mul_ac_col = fixed_columns[2];
let constant_col = fixed_columns[3];
let fixed_values_col = meta.fixed_column();
meta.enable_equality(fixed_values_col);
for col in value_columns.iter() {
meta.enable_equality(*col);
}
meta.enable_equality(committed_instance_col);
meta.enable_equality(instance_col);
meta.create_gate("arith_gate", |meta| {
let values = value_columns
.iter()
.map(|col| meta.query_advice(*col, Rotation::cur()))
.collect::<Vec<_>>();
let next_value = meta.query_advice(value_columns[0], Rotation::next());
let coeffs = coeff_cols
.iter()
.map(|col| meta.query_fixed(*col, Rotation::cur()))
.collect::<Vec<_>>();
let q_next_coeff = meta.query_fixed(q_next_col, Rotation::cur());
let mul_ab_coeff = meta.query_fixed(mul_ab_col, Rotation::cur());
let mul_ac_coeff = meta.query_fixed(mul_ac_col, Rotation::cur());
let constant = meta.query_fixed(constant_col, Rotation::cur());
let id = values
.iter()
.zip(coeffs.iter())
.fold(constant, |acc, (value, coeff)| acc + coeff * value)
+ q_next_coeff * next_value
+ mul_ab_coeff * &values[0] * &values[1]
+ mul_ac_coeff * &values[0] * &values[2];
Constraints::with_selector(q_arith, vec![id])
});
meta.create_gate("12_minus_34", |meta| {
let v1 = meta.query_advice(value_columns[1], Rotation::cur());
let v2 = meta.query_advice(value_columns[2], Rotation::cur());
let v3 = meta.query_advice(value_columns[3], Rotation::cur());
let v4 = meta.query_advice(value_columns[4], Rotation::cur());
Constraints::with_selector(q_12_minus_34, vec![v1 + v2 - v3 - v4])
});
meta.create_gate("parallel_add_gate", |meta| {
let ids = (value_columns[0..NB_PARALLEL_ADD_COLS].iter())
.zip(coeff_cols[0..NB_PARALLEL_ADD_COLS].iter())
.map(|(val_col, const_col)| {
let val = meta.query_advice(*val_col, Rotation::cur());
let res = meta.query_advice(*val_col, Rotation::next());
let c = meta.query_fixed(*const_col, Rotation::cur());
val + c - res
})
.collect::<Vec<_>>();
Constraints::with_selector(q_par_add, ids)
});
NativeConfig {
q_arith,
q_12_minus_34,
q_par_add,
value_cols: *value_columns,
coeff_cols,
q_next_col,
mul_ab_col,
mul_ac_col,
constant_col,
fixed_values_col,
committed_instance_col,
instance_col,
}
}
fn load(&self, _layouter: &mut impl Layouter<F>) -> Result<(), Error> {
Ok(())
}
}
impl<F: CircuitField> NativeChip<F> {
fn custom(
&self,
region: &mut Region<'_, F>,
coeffs: &[F; NB_ARITH_COLS],
q_next_coeff: F,
mul_coeffs: (F, F),
constant: F,
offset: usize,
) -> Result<(), Error> {
self.config.q_arith.enable(region, offset)?;
for (i, coeff) in coeffs.iter().enumerate() {
region.assign_fixed(
|| "arith coeff",
self.config.coeff_cols[i],
offset,
|| Value::known(*coeff),
)?;
}
region.assign_fixed(
|| "arith q_next",
self.config.q_next_col,
offset,
|| Value::known(q_next_coeff),
)?;
region.assign_fixed(
|| "arith mul_ab",
self.config.mul_ab_col,
offset,
|| Value::known(mul_coeffs.0),
)?;
region.assign_fixed(
|| "arith mul_ac",
self.config.mul_ac_col,
offset,
|| Value::known(mul_coeffs.1),
)?;
region.assign_fixed(
|| "arith const",
self.config.constant_col,
offset,
|| Value::known(constant),
)?;
Ok(())
}
fn copy_in_row(
&self,
region: &mut Region<'_, F>,
x: &AssignedNative<F>,
column: &Column<Advice>,
offset: usize,
) -> Result<(), Error> {
let y = region.assign_advice(
|| "arith copy_in_row",
*column,
offset,
|| x.value().copied(),
)?;
region.constrain_equal(x.cell(), y.cell())?;
Ok(())
}
fn add_and_double_mul(
&self,
layouter: &mut impl Layouter<F>,
(a, x): (F, &AssignedNative<F>),
(b, y): (F, &AssignedNative<F>),
(c, z): (F, &AssignedNative<F>),
k: F,
(m1, m2): (F, F),
) -> Result<AssignedNative<F>, Error> {
let res_value = x
.value()
.zip(y.value())
.zip(z.value())
.map(|((x, y), z)| a * x + b * y + c * z + k + m1 * x * y + m2 * x * z);
layouter.assign_region(
|| "add and double mul",
|mut region| {
self.copy_in_row(&mut region, x, &self.config.value_cols[0], 0)?;
self.copy_in_row(&mut region, y, &self.config.value_cols[1], 0)?;
self.copy_in_row(&mut region, z, &self.config.value_cols[2], 0)?;
let res =
region.assign_advice(|| "res", self.config.value_cols[4], 0, || res_value)?;
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
coeffs[0] = a; coeffs[1] = b; coeffs[2] = c; coeffs[4] = -F::ONE; self.custom(&mut region, &coeffs, F::ZERO, (m1, m2), k, 0)?;
Ok(res)
},
)
}
fn assign_with_shifted_inverse(
&self,
layouter: &mut impl Layouter<F>,
value: Value<F>,
shift: F,
) -> Result<(AssignedNative<F>, AssignedNative<F>), Error> {
layouter.assign_region(
|| "assign with shifted inverse",
|mut region| {
let r_value = value.map(|x| (x - shift).invert().unwrap_or(F::ZERO));
let x = region.assign_advice(|| "x", self.config.value_cols[0], 0, || value)?;
let r = region.assign_advice(|| "r", self.config.value_cols[1], 0, || r_value)?;
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
coeffs[1] = -shift; self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), -F::ONE, 0)?;
Ok((x, r))
},
)
}
pub(crate) fn assign_linear_combination_aux(
&self,
region: &mut Region<'_, F>,
terms: &[(F, Value<F>)],
constant: F,
result: &Value<F>,
cols_used: usize,
offset: &mut usize,
) -> Result<(Vec<AssignedNative<F>>, AssignedNative<F>), Error> {
assert!(cols_used < NB_ARITH_COLS);
let chunk_len = min(terms.len(), cols_used);
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
let assigned_result = region.assign_advice(
|| "assign linear combination term",
self.config.value_cols[0],
*offset,
|| *result,
)?;
coeffs[0] = -F::ONE;
let mut assigned_limbs = terms[0..chunk_len]
.iter()
.enumerate()
.map(|(i, term)| {
coeffs[i + 1] = term.0;
region.assign_advice(
|| "assign linear combination term",
self.config.value_cols[i + 1],
*offset,
|| term.1,
)
})
.collect::<Result<Vec<_>, _>>()?;
if terms.len() <= cols_used {
self.custom(
region,
&coeffs,
F::ZERO,
(F::ZERO, F::ZERO),
constant,
*offset,
)?;
Ok((assigned_limbs, assigned_result))
}
else {
let chunk_result =
terms[..chunk_len].iter().fold(Value::known(constant), |acc, (coeff, x)| {
acc.zip(*x).map(|(acc, val)| acc + *coeff * val)
});
let next_result = *result - chunk_result;
self.custom(
region,
&coeffs,
F::ONE,
(F::ZERO, F::ZERO),
constant,
*offset,
)?;
*offset += 1;
let (new_assigned_limbs, _) = self.assign_linear_combination_aux(
region,
&terms[chunk_len..],
F::ZERO,
&next_result,
cols_used,
offset,
)?;
assigned_limbs.extend_from_slice(&new_assigned_limbs);
Ok((assigned_limbs, assigned_result))
}
}
pub fn nb_public_inputs(&self) -> usize {
*self.instance_offset.borrow()
}
}
impl<F: CircuitField> NativeChip<F> {
pub(crate) fn add_constants_in_region(
&self,
region: &mut Region<'_, F>,
variables: &[AssignedNative<F>; NB_PARALLEL_ADD_COLS],
constants: &[F; NB_PARALLEL_ADD_COLS],
offset: &mut usize,
) -> Result<Vec<AssignedNative<F>>, Error> {
self.config.q_par_add.enable(region, *offset)?;
(variables.iter())
.zip(self.config.value_cols)
.try_for_each(|(x, col)| self.copy_in_row(region, x, &col, *offset))?;
constants.iter().zip(self.config.coeff_cols).try_for_each(|(c, col)| {
region.assign_fixed(|| "add_consts", col, *offset, || Value::known(*c))?;
Ok::<(), Error>(())
})?;
*offset += 1;
let res_values = variables.iter().zip(constants).map(|(x, c)| x.value().map(|x| *x + *c));
res_values
.zip(self.config.value_cols)
.map(|(val, col)| region.assign_advice(|| "add_consts", col, *offset, || val))
.collect::<Result<Vec<_>, Error>>()
}
}
impl<F> AssignmentInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn assign(
&self,
layouter: &mut impl Layouter<F>,
value: Value<F>,
) -> Result<AssignedNative<F>, Error> {
layouter.assign_region(
|| "Assign native value",
|mut region| {
region.assign_advice(|| "assign element", self.config.value_cols[0], 0, || value)
},
)
}
fn assign_fixed(
&self,
layouter: &mut impl Layouter<F>,
constant: F,
) -> Result<AssignedNative<F>, Error> {
let constant_big = constant.to_biguint();
if let Some(assigned) = self.cached_fixed.borrow().get(&constant_big) {
return Ok(assigned.clone());
};
let x = layouter.assign_region(
|| "Assign fixed",
|mut region| {
let mut x = region.assign_fixed(
|| "fixed",
self.config.fixed_values_col,
0,
|| Value::known(constant),
)?;
x.update_value(constant)?;
Ok(x)
},
)?;
self.cached_fixed.borrow_mut().insert(constant_big.clone(), x.clone());
Ok(x)
}
fn assign_many(
&self,
layouter: &mut impl Layouter<F>,
values: &[Value<F>],
) -> Result<Vec<AssignedNative<F>>, Error> {
layouter.assign_region(
|| "assign_many (native)",
|mut region| {
let mut assigned = vec![];
for (i, chunk_values) in values.chunks(NB_ARITH_COLS).enumerate() {
for (value, col) in chunk_values.iter().zip(self.config.value_cols.iter()) {
let cell = region.assign_advice(|| "assign", *col, i, || *value)?;
assigned.push(cell);
}
}
Ok(assigned)
},
)
}
}
impl<F> PublicInputInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn as_public_input(
&self,
_layouter: &mut impl Layouter<F>,
assigned: &AssignedNative<F>,
) -> Result<Vec<AssignedNative<F>>, Error> {
Ok(vec![assigned.clone()])
}
fn constrain_as_public_input(
&self,
layouter: &mut impl Layouter<F>,
assigned: &AssignedNative<F>,
) -> Result<(), Error> {
let mut offset = self.instance_offset.borrow_mut();
layouter.constrain_instance(assigned.cell(), self.config.instance_col, *offset)?;
*offset += 1;
Ok(())
}
fn assign_as_public_input(
&self,
layouter: &mut impl Layouter<F>,
value: Value<F>,
) -> Result<AssignedNative<F>, Error> {
let assigned = self.assign(layouter, value)?;
self.constrain_as_public_input(layouter, &assigned)?;
Ok(assigned)
}
}
impl<F> CommittedInstanceInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn constrain_as_committed_public_input(
&self,
layouter: &mut impl Layouter<F>,
assigned: &AssignedNative<F>,
) -> Result<(), Error> {
let mut offset = self.committed_instance_offset.borrow_mut();
layouter.constrain_instance(
assigned.cell(),
self.config.committed_instance_col,
*offset,
)?;
*offset += 1;
Ok(())
}
}
impl<F> AssignmentInstructions<F, AssignedBit<F>> for NativeChip<F>
where
F: CircuitField,
{
fn assign(
&self,
layouter: &mut impl Layouter<F>,
value: Value<bool>,
) -> Result<AssignedBit<F>, Error> {
layouter.assign_region(
|| "Assign big",
|mut region| {
let b_value = value.map(|b| if b { F::ONE } else { F::ZERO });
let b = region.assign_advice(|| "b", self.config.value_cols[0], 0, || b_value)?;
self.copy_in_row(&mut region, &b, &self.config.value_cols[1], 0)?;
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
coeffs[0] = -F::ONE; self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), F::ZERO, 0)?;
Ok(AssignedBit(b))
},
)
}
fn assign_fixed(
&self,
layouter: &mut impl Layouter<F>,
bit: bool,
) -> Result<AssignedBit<F>, Error> {
let constant = if bit { F::ONE } else { F::ZERO };
let x = self.assign_fixed(layouter, constant)?;
Ok(AssignedBit(x))
}
}
impl<F> PublicInputInstructions<F, AssignedBit<F>> for NativeChip<F>
where
F: CircuitField,
{
fn as_public_input(
&self,
_layouter: &mut impl Layouter<F>,
assigned: &AssignedBit<F>,
) -> Result<Vec<AssignedNative<F>>, Error> {
Ok(vec![assigned.clone().into()])
}
fn constrain_as_public_input(
&self,
layouter: &mut impl Layouter<F>,
assigned: &AssignedBit<F>,
) -> Result<(), Error> {
let assigned_as_native: AssignedNative<F> = assigned.clone().into();
self.constrain_as_public_input(layouter, &assigned_as_native)
}
fn assign_as_public_input(
&self,
layouter: &mut impl Layouter<F>,
value: Value<bool>,
) -> Result<AssignedBit<F>, Error> {
let assigned = self.assign(layouter, value)?;
self.constrain_as_public_input(layouter, &assigned)?;
Ok(assigned)
}
}
impl<F> AssertionInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn assert_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
) -> Result<(), Error> {
layouter.assign_region(
|| "Assert equal",
|mut region| region.constrain_equal(x.cell(), y.cell()),
)
}
fn assert_not_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
) -> Result<(), Error> {
layouter.assign_region(
|| "Assert not equal",
|mut region| {
let r_value = (x.value().copied() - y.value().copied())
.map(|v| v.invert().unwrap_or(F::ZERO));
region.assign_advice(|| "r", self.config.value_cols[0], 0, || r_value)?;
self.copy_in_row(&mut region, x, &self.config.value_cols[1], 0)?;
self.copy_in_row(&mut region, y, &self.config.value_cols[2], 0)?;
let coeffs = [F::ZERO; NB_ARITH_COLS];
self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), -F::ONE, 0)?;
Ok(())
},
)
}
fn assert_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
constant: F,
) -> Result<(), Error> {
let c = self.assign_fixed(layouter, constant)?;
self.assert_equal(layouter, x, &c)
}
fn assert_not_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
constant: F,
) -> Result<(), Error> {
let (y, _) = self.assign_with_shifted_inverse(layouter, x.value().copied(), constant)?;
self.assert_equal(layouter, x, &y)
}
}
impl<F> AssertionInstructions<F, AssignedBit<F>> for NativeChip<F>
where
F: CircuitField,
{
fn assert_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
y: &AssignedBit<F>,
) -> Result<(), Error> {
self.assert_equal(layouter, &x.0, &y.0)
}
fn assert_not_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
y: &AssignedBit<F>,
) -> Result<(), Error> {
let diff = self.sub(layouter, &x.0, &y.0)?;
self.assert_non_zero(layouter, &diff)
}
fn assert_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
b: bool,
) -> Result<(), Error> {
let constant = if b { F::ONE } else { F::ZERO };
self.assert_equal_to_fixed(layouter, &x.0, constant)
}
fn assert_not_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
constant: bool,
) -> Result<(), Error> {
self.assert_equal_to_fixed(layouter, x, !constant)
}
}
impl<F> ZeroInstructions<F, AssignedNative<F>> for NativeChip<F> where F: CircuitField {}
impl<F> ArithInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn linear_combination(
&self,
layouter: &mut impl Layouter<F>,
terms: &[(F, AssignedNative<F>)],
constant: F,
) -> Result<AssignedNative<F>, Error> {
let zero: AssignedNative<F> = self.assign_fixed(layouter, F::ZERO)?;
let terms: Vec<_> = terms
.iter()
.filter(|(c, x)| !F::is_zero_vartime(c) && *x != zero)
.cloned()
.collect();
if terms.is_empty() {
return self.assign_fixed(layouter, constant);
}
if let [(coeff, var)] = terms.as_slice() {
if *coeff == F::ONE && F::is_zero_vartime(&constant) {
return Ok(var.clone());
}
}
let term_values = terms
.iter()
.map(|(c, assigned_t)| (*c, assigned_t.value().copied()))
.collect::<Vec<_>>();
let result = terms.iter().fold(Value::known(constant), |acc, (coeff, x)| {
acc.zip(x.value()).map(|(acc, val)| acc + *coeff * val)
});
layouter.assign_region(
|| "Linear combination",
|mut region| {
let mut offset = 0;
let (assigned_limbs, assigned_result) = self.assign_linear_combination_aux(
&mut region,
term_values.as_slice(),
constant,
&result,
NB_ARITH_COLS - 1,
&mut offset,
)?;
assert_eq!(assigned_limbs.len(), terms.len());
assigned_limbs.iter().zip(terms.iter()).try_for_each(|(new_av, (_, av))| {
region.constrain_equal(new_av.cell(), av.cell())
})?;
Ok(assigned_result)
},
)
}
fn mul(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
multiplying_constant: Option<F>,
) -> Result<AssignedNative<F>, Error> {
if multiplying_constant == Some(F::ZERO) {
return self.assign_fixed(layouter, F::ZERO);
}
let m = multiplying_constant.unwrap_or(F::ONE);
let one: AssignedNative<F> = self.assign_fixed(layouter, F::ONE)?;
if m == F::ONE && x.clone() == one {
return Ok(y.clone());
}
if m == F::ONE && y.clone() == one {
return Ok(x.clone());
}
self.add_and_mul(
layouter,
(F::ZERO, x),
(F::ZERO, y),
(F::ZERO, x),
F::ZERO,
m,
)
}
fn div(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
) -> Result<AssignedNative<F>, Error> {
let y_inv = self.inv(layouter, y)?;
self.mul(layouter, x, &y_inv, None)
}
fn inv(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
) -> Result<AssignedNative<F>, Error> {
let (y, inv) = self.assign_with_shifted_inverse(layouter, x.value().copied(), F::ZERO)?;
self.assert_equal(layouter, x, &y)?;
Ok(inv)
}
fn inv0(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
) -> Result<AssignedNative<F>, Error> {
let is_zero = self.is_zero(layouter, x)?;
let zero = self.assign_fixed(layouter, F::ZERO)?;
let one = self.assign_fixed(layouter, F::ONE)?;
let invertible = self.select(layouter, &is_zero, &one, x)?;
let inverse = self.inv(layouter, &invertible)?;
self.select(layouter, &is_zero, &zero, &inverse)
}
fn add_and_mul(
&self,
layouter: &mut impl Layouter<F>,
(a, x): (F, &AssignedNative<F>),
(b, y): (F, &AssignedNative<F>),
(c, z): (F, &AssignedNative<F>),
k: F,
m: F,
) -> Result<AssignedNative<F>, Error> {
self.add_and_double_mul(layouter, (a, x), (b, y), (c, z), k, (m, F::ZERO))
}
fn add_constants(
&self,
layouter: &mut impl Layouter<F>,
xs: &[AssignedNative<F>],
constants: &[F],
) -> Result<Vec<AssignedNative<F>>, Error> {
assert_eq!(xs.len(), constants.len());
let pairs = (xs.iter().zip(constants.iter()))
.filter(|&(_, &c)| c != F::ZERO)
.collect::<Vec<_>>();
let mut non_trivial_outputs = Vec::with_capacity(pairs.len());
let mut chunks = pairs.chunks_exact(NB_PARALLEL_ADD_COLS);
for chunk in chunks.by_ref() {
let outputs = layouter.assign_region(
|| "add_constants",
|mut region| {
let values = chunk.iter().map(|(x, _)| (*x).clone()).collect::<Vec<_>>();
let consts = chunk.iter().map(|(_, c)| **c).collect::<Vec<_>>();
self.add_constants_in_region(
&mut region,
&values.try_into().unwrap(),
&consts.try_into().unwrap(),
&mut 0,
)
},
)?;
non_trivial_outputs.extend(outputs);
}
for (x, c) in chunks.remainder() {
non_trivial_outputs.push(self.add_constant(layouter, x, **c)?);
}
let mut outputs = Vec::with_capacity(xs.len());
let mut j = 0;
for i in 0..xs.len() {
if constants[i] != F::ZERO {
outputs.push(non_trivial_outputs[j].clone());
j += 1;
} else {
outputs.push(xs[i].clone())
}
}
Ok(outputs)
}
}
impl<F: CircuitField> Instantiable<F> for AssignedBit<F> {
fn as_public_input(element: &bool) -> Vec<F> {
vec![if *element { F::ONE } else { F::ZERO }]
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use]
pub struct AssignedBit<F: CircuitField>(pub(crate) AssignedNative<F>);
impl<F: CircuitField> Hash for AssignedBit<F> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl<F: CircuitField> InnerValue for AssignedBit<F> {
type Element = bool;
fn value(&self) -> Value<bool> {
self.0.value().map(|b| !F::is_zero_vartime(b))
}
}
impl<F: CircuitField> From<AssignedBit<F>> for AssignedNative<F> {
fn from(bit: AssignedBit<F>) -> Self {
bit.0
}
}
impl<F> ConversionInstructions<F, AssignedNative<F>, AssignedBit<F>> for NativeChip<F>
where
F: CircuitField,
{
fn convert_value(&self, x: &F) -> Option<bool> {
let is_zero: bool = F::is_zero(x).into();
Some(!is_zero)
}
fn convert(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
) -> Result<AssignedBit<F>, Error> {
let b_value = x.value().map(|v| !F::is_zero_vartime(v));
let b: AssignedBit<F> = self.assign(layouter, b_value)?;
self.assert_equal(layouter, x, &b.0)?;
Ok(b)
}
}
impl<F> ConversionInstructions<F, AssignedBit<F>, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn convert_value(&self, x: &bool) -> Option<F> {
Some(if *x { F::from(1) } else { F::from(0) })
}
fn convert(
&self,
_layouter: &mut impl Layouter<F>,
bit: &AssignedBit<F>,
) -> Result<AssignedNative<F>, Error> {
Ok(bit.clone().0)
}
}
impl<F> UnsafeConversionInstructions<F, AssignedNative<F>, AssignedBit<F>> for NativeChip<F>
where
F: CircuitField,
{
fn convert_unsafe(
&self,
_layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
) -> Result<AssignedBit<F>, Error> {
#[cfg(not(test))]
x.value().map(|&x| {
assert!(
x == F::ZERO || x == F::ONE,
"Trying to convert {:?} to an AssignedBit!",
x
);
});
Ok(AssignedBit(x.clone()))
}
}
#[cfg(test)]
impl<F> UnsafeConversionInstructions<F, AssignedBit<F>, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn convert_unsafe(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedBit<F>,
) -> Result<AssignedNative<F>, Error> {
self.convert(layouter, x)
}
}
impl<F> BinaryInstructions<F> for NativeChip<F>
where
F: CircuitField,
{
fn and(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
) -> Result<AssignedBit<F>, Error> {
let mut acc = bits.first().unwrap().0.clone();
for b in bits.iter().skip(1) {
acc = self.mul(layouter, &acc, &b.0, None)?;
}
Ok(AssignedBit(acc))
}
fn or(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
) -> Result<AssignedBit<F>, Error> {
let mut acc = bits.first().unwrap().0.clone();
for b in bits.iter().skip(1) {
acc = self.add_and_mul(
layouter,
(F::ONE, &acc),
(F::ONE, &b.0),
(F::ZERO, &acc),
F::ZERO,
-F::ONE,
)?;
}
Ok(AssignedBit(acc))
}
fn xor(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
) -> Result<AssignedBit<F>, Error> {
let mut acc = bits.first().unwrap().0.clone();
for b in bits.iter().skip(1) {
acc = self.add_and_mul(
layouter,
(F::ONE, &acc),
(F::ONE, &b.0),
(F::ZERO, &acc),
F::ZERO,
-F::from(2),
)?;
}
Ok(AssignedBit(acc))
}
fn not(
&self,
layouter: &mut impl Layouter<F>,
bit: &AssignedBit<F>,
) -> Result<AssignedBit<F>, Error> {
let neg_bit = self.linear_combination(layouter, &[(-F::ONE, bit.0.clone())], F::ONE)?;
Ok(AssignedBit(neg_bit))
}
}
impl<F> EqualityInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField + From<u64> + Neg<Output = F>,
{
fn is_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
) -> Result<AssignedBit<F>, Error> {
let value_cols = &self.config.value_cols;
let res_val = x.value().zip(y.value()).map(|(x, y)| F::from((x == y) as u64));
let aux_val = x.value().zip(y.value()).map(|(x, y)| (*x - *y).invert().unwrap_or(F::ONE));
let res = layouter.assign_region(
|| "is_equal (i)",
|mut region| {
region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
self.copy_in_row(&mut region, y, &value_cols[2], 0)?;
let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
coeffs[4] = F::ONE; self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), -F::ONE, 0)?;
Ok(res)
},
)?;
let must_be_zero = self.add_and_double_mul(
layouter,
(F::ZERO, &res),
(F::ZERO, x),
(F::ZERO, y),
F::ZERO,
(F::ONE, -F::ONE),
)?;
self.assert_zero(layouter, &must_be_zero)?;
Ok(AssignedBit(res))
}
fn is_not_equal(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
) -> Result<AssignedBit<F>, Error> {
let value_cols = &self.config.value_cols;
let res_val = x.value().zip(y.value()).map(|(x, y)| F::from((x != y) as u64));
let aux_val = x.value().zip(y.value()).map(|(x, y)| (*x - *y).invert().unwrap_or(F::ONE));
let res = layouter.assign_region(
|| "is_not_equal (i)",
|mut region| {
region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
self.copy_in_row(&mut region, y, &value_cols[2], 0)?;
let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
coeffs[4] = -F::ONE; self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), F::ZERO, 0)?;
Ok(res)
},
)?;
let must_be_zero = self.add_and_double_mul(
layouter,
(F::ZERO, &res),
(F::ONE, x),
(-F::ONE, y),
F::ZERO,
(-F::ONE, F::ONE),
)?;
self.assert_zero(layouter, &must_be_zero)?;
Ok(AssignedBit(res))
}
fn is_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
c: F,
) -> Result<AssignedBit<F>, Error> {
let value_cols = &self.config.value_cols;
let res_val = x.value().map(|x| F::from((*x == c) as u64));
let aux_val = x.value().map(|x| (*x - c).invert().unwrap_or(F::ONE));
let res = layouter.assign_region(
|| "is_equal (i)",
|mut region| {
region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
coeffs[0] = -c; coeffs[4] = F::ONE; self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), -F::ONE, 0)?;
Ok(res)
},
)?;
let must_be_zero = self.add_and_mul(
layouter,
(-c, &res),
(F::ZERO, x),
(F::ZERO, x),
F::ZERO,
F::ONE,
)?;
self.assert_zero(layouter, &must_be_zero)?;
Ok(AssignedBit(res))
}
fn is_not_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
x: &AssignedNative<F>,
c: F,
) -> Result<AssignedBit<F>, Error> {
let value_cols = &self.config.value_cols;
let res_val = x.value().map(|x| F::from((*x != c) as u64));
let aux_val = x.value().map(|x| (*x - c).invert().unwrap_or(F::ONE));
let res = layouter.assign_region(
|| "is_not_equal (i)",
|mut region| {
region.assign_advice(|| "aux", value_cols[0], 0, || aux_val)?;
self.copy_in_row(&mut region, x, &value_cols[1], 0)?;
let res = region.assign_advice(|| "res", value_cols[4], 0, || res_val)?;
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
coeffs[0] = -c; coeffs[4] = -F::ONE; self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, F::ZERO), F::ZERO, 0)?;
Ok(res)
},
)?;
let must_be_zero =
self.add_and_mul(layouter, (c, &res), (F::ONE, x), (F::ZERO, x), -c, -F::ONE)?;
self.assert_zero(layouter, &must_be_zero)?;
Ok(AssignedBit(res))
}
}
impl<F> EqualityInstructions<F, AssignedBit<F>> for NativeChip<F>
where
F: CircuitField + From<u64> + Neg<Output = F>,
{
fn is_equal(
&self,
layouter: &mut impl Layouter<F>,
a: &AssignedBit<F>,
b: &AssignedBit<F>,
) -> Result<AssignedBit<F>, Error> {
self.add_and_mul(
layouter,
(-F::ONE, &a.0),
(-F::ONE, &b.0),
(F::ZERO, &a.0),
F::ONE,
F::from(2),
)
.map(AssignedBit)
}
fn is_not_equal(
&self,
layouter: &mut impl Layouter<F>,
a: &AssignedBit<F>,
b: &AssignedBit<F>,
) -> Result<AssignedBit<F>, Error> {
self.add_and_mul(
layouter,
(F::ONE, &a.0),
(F::ONE, &b.0),
(F::ZERO, &a.0),
F::ZERO,
-F::from(2),
)
.map(AssignedBit)
}
fn is_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
b: &AssignedBit<F>,
constant: bool,
) -> Result<AssignedBit<F>, Error> {
let assigned_constant = self.assign_fixed(layouter, constant)?;
self.is_equal(layouter, b, &assigned_constant)
}
fn is_not_equal_to_fixed(
&self,
layouter: &mut impl Layouter<F>,
b: &AssignedBit<F>,
constant: bool,
) -> Result<AssignedBit<F>, Error> {
let assigned_constant = self.assign_fixed(layouter, constant)?;
self.is_not_equal(layouter, b, &assigned_constant)
}
}
impl<F> ControlFlowInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField + From<u64> + Neg<Output = F>,
{
fn select(
&self,
layouter: &mut impl Layouter<F>,
cond: &AssignedBit<F>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
) -> Result<AssignedNative<F>, Error> {
self.add_and_double_mul(
layouter,
(F::ZERO, &cond.0),
(F::ZERO, x),
(F::ONE, y),
F::ZERO,
(F::ONE, -F::ONE),
)
}
fn cond_swap(
&self,
layouter: &mut impl Layouter<F>,
cond: &AssignedBit<F>,
x: &AssignedNative<F>,
y: &AssignedNative<F>,
) -> Result<(AssignedNative<F>, AssignedNative<F>), Error> {
let val2 = (x.value().zip(y.value()).zip(cond.value()))
.map(|((x, y), b)| if b { x } else { y })
.copied();
let val1 = x.value().copied() + y.value().copied() - val2;
layouter.assign_region(
|| "cond swap",
|mut region| {
self.copy_in_row(&mut region, &cond.0, &self.config.value_cols[0], 0)?;
self.copy_in_row(&mut region, x, &self.config.value_cols[1], 0)?;
self.copy_in_row(&mut region, y, &self.config.value_cols[2], 0)?;
let fst = region.assign_advice(|| "fst", self.config.value_cols[3], 0, || val1)?;
let snd = region.assign_advice(|| "snd", self.config.value_cols[4], 0, || val2)?;
let mut coeffs = [F::ZERO; NB_ARITH_COLS];
coeffs[2] = F::ONE; coeffs[4] = -F::ONE; self.custom(&mut region, &coeffs, F::ZERO, (F::ONE, -F::ONE), F::ZERO, 0)?;
self.config.q_12_minus_34.enable(&mut region, 0)?;
Ok((fst, snd))
},
)
}
}
impl<F> ControlFlowInstructions<F, AssignedBit<F>> for NativeChip<F>
where
F: CircuitField,
{
fn select(
&self,
layouter: &mut impl Layouter<F>,
cond: &AssignedBit<F>,
x: &AssignedBit<F>,
y: &AssignedBit<F>,
) -> Result<AssignedBit<F>, Error> {
self.select(layouter, cond, &x.0, &y.0).map(AssignedBit)
}
fn cond_swap(
&self,
layouter: &mut impl Layouter<F>,
cond: &AssignedBit<F>,
x: &AssignedBit<F>,
y: &AssignedBit<F>,
) -> Result<(AssignedBit<F>, AssignedBit<F>), Error> {
self.cond_swap(layouter, cond, &x.0, &y.0)
.map(|(fst, snd)| (AssignedBit(fst), AssignedBit(snd)))
}
}
impl<F> FieldInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn order(&self) -> BigUint {
F::modulus()
}
}
impl<F> CanonicityInstructions<F, AssignedNative<F>> for NativeChip<F>
where
F: CircuitField,
{
fn le_bits_lower_than(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
bound: BigUint,
) -> Result<AssignedBit<F>, Error> {
let geq = self.le_bits_geq_than(layouter, bits, bound)?;
self.not(layouter, &geq)
}
fn le_bits_geq_than(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
bound: BigUint,
) -> Result<AssignedBit<F>, Error> {
if bound.is_zero() {
return self.assign_fixed(layouter, true);
}
if bits.len() < bound.bits() as usize {
return self.assign_fixed(layouter, false);
}
if bits.len() == 1 {
return Ok(bits[0].clone());
}
let msb_pos = bits.len() - 1;
let rest_is_geq = {
let mut rest_bound = bound.clone();
rest_bound.set_bit(msb_pos as u64, false);
self.le_bits_geq_than(layouter, &bits[0..msb_pos], rest_bound)?
};
if bound.bit(msb_pos as u64) {
self.and(layouter, &[bits[msb_pos].clone(), rest_is_geq])
} else {
self.or(layouter, &[bits[msb_pos].clone(), rest_is_geq])
}
}
}
#[cfg(any(test, feature = "testing"))]
impl<F: CircuitField> FromScratch<F> for NativeChip<F> {
type Config = NativeConfig;
fn new_from_scratch(config: &Self::Config) -> Self {
NativeChip::new(config, &())
}
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],
) -> Self::Config {
while advice_columns.len() < NB_ARITH_COLS {
advice_columns.push(meta.advice_column());
}
while fixed_columns.len() < NB_ARITH_FIXED_COLS {
fixed_columns.push(meta.fixed_column());
}
let advice_cols: [_; NB_ARITH_COLS] = advice_columns[..NB_ARITH_COLS].try_into().unwrap();
let fixed_cols: [_; NB_ARITH_FIXED_COLS] =
fixed_columns[..NB_ARITH_FIXED_COLS].try_into().unwrap();
NativeChip::configure(meta, &(advice_cols, fixed_cols, *instance_columns))
}
fn load_from_scratch(
&self,
_layouter: &mut impl midnight_proofs::circuit::Layouter<F>,
) -> Result<(), Error> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use ff::FromUniformBytes;
use midnight_curves::Fq as BlsScalar;
use super::*;
use crate::instructions::{
arithmetic, assertions, binary, canonicity, control_flow,
conversions::{
self,
tests::Operation::{Convert, UnsafeConvert},
},
equality, public_input, zero,
};
macro_rules! test {
($mod:ident, $op:ident) => {
#[test]
fn $op() {
$mod::tests::$op::<BlsScalar, AssignedNative<BlsScalar>, NativeChip<BlsScalar>>(
"native_chip",
);
}
};
}
test!(assertions, test_assertions);
test!(public_input, test_public_inputs);
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);
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!(canonicity, test_canonical);
test!(canonicity, test_le_bits_lower_and_geq);
macro_rules! test {
($mod:ident, $op:ident) => {
#[test]
fn $op() {
$mod::tests::$op::<BlsScalar, NativeChip<BlsScalar>>("native_chip");
}
};
}
test!(binary, test_and);
test!(binary, test_or);
test!(binary, test_xor);
test!(binary, test_not);
fn test_generic_conversion_to_bit<F>(name: &str)
where
F: CircuitField + FromUniformBytes<64> + Ord,
{
[
(
F::ZERO,
Some(false),
Convert,
true,
true,
name,
"convert_to_bit",
),
(F::ZERO, Some(true), Convert, false, false, "", ""),
(F::ONE, Some(true), Convert, true, false, "", ""),
(F::ONE, Some(false), Convert, false, false, "", ""),
(F::from(2), None, Convert, false, false, "", ""),
(
F::from(2),
None,
UnsafeConvert,
true,
true,
name,
"unsafe_convert_to_bit",
),
]
.into_iter()
.for_each(
|(x, expected, operation, must_pass, cost_model, chip_name, op_name)| {
conversions::tests::run::<F, AssignedNative<F>, AssignedBit<F>, NativeChip<F>>(
x, expected, operation, must_pass, cost_model, chip_name, op_name,
)
},
);
}
#[test]
fn test_conversion_to_bit() {
test_generic_conversion_to_bit::<BlsScalar>("native_chip")
}
fn test_generic_conversion_from_bit<F>(name: &str)
where
F: CircuitField + FromUniformBytes<64> + Ord,
{
[
(
false,
Some(F::ZERO),
Convert,
true,
true,
name,
"convert_from_bit",
),
(false, Some(F::ONE), Convert, false, false, "", ""),
(true, Some(F::ONE), Convert, true, false, "", ""),
(true, Some(F::ZERO), Convert, false, false, "", ""),
(
false,
None,
UnsafeConvert,
true,
true,
name,
"unsafe_convert_from_bit",
),
(true, None, UnsafeConvert, true, false, "", ""),
]
.into_iter()
.for_each(
|(x, expected, operation, must_pass, cost_model, chip_name, op_name)| {
conversions::tests::run::<F, AssignedBit<F>, AssignedNative<F>, NativeChip<F>>(
x, expected, operation, must_pass, cost_model, chip_name, op_name,
)
},
);
}
#[test]
fn test_conversion_from_bit() {
test_generic_conversion_from_bit::<BlsScalar>("native_chip");
}
}