use midnight_proofs::{circuit::Layouter, plonk::Error};
use num_bigint::BigUint;
use crate::{
instructions::{AssignmentInstructions, FieldInstructions},
types::{AssignedBit, InnerConstants, Instantiable},
CircuitField,
};
pub trait CanonicityInstructions<F, Assigned>:
FieldInstructions<F, Assigned> + AssignmentInstructions<F, AssignedBit<F>>
where
F: CircuitField,
Assigned::Element: CircuitField,
Assigned: Instantiable<F> + InnerConstants + Clone,
{
fn is_canonical(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
) -> Result<AssignedBit<F>, Error> {
let order = self.order();
if bits.len() > order.bits() as usize {
self.assign_fixed(layouter, false)
} else {
self.le_bits_lower_than(layouter, bits, order)
}
}
fn le_bits_lower_than(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
bound: BigUint,
) -> Result<AssignedBit<F>, Error>;
fn le_bits_geq_than(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
bound: BigUint,
) -> Result<AssignedBit<F>, Error>;
}
#[cfg(test)]
pub(crate) mod tests {
use std::marker::PhantomData;
use ff::{FromUniformBytes, PrimeField};
use midnight_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem},
};
use num_traits::{One, Zero};
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaCha8Rng;
use super::*;
use crate::{
instructions::{AssertionInstructions, AssignmentInstructions},
types::InnerValue,
utils::{
circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
util::FromScratch,
},
};
#[derive(Clone, Debug)]
enum Operation {
Canonical,
Lower,
Geq,
}
#[derive(Clone, Debug)]
struct TestCircuit<F, Assigned, CanonicityChip>
where
Assigned: InnerValue,
{
bits: Vec<bool>,
bound: BigUint,
expected: bool,
operation: Operation,
_marker: PhantomData<(F, Assigned, CanonicityChip)>,
}
impl<F, Assigned, CanonicityChip> Circuit<F> for TestCircuit<F, Assigned, CanonicityChip>
where
F: CircuitField,
Assigned::Element: CircuitField,
Assigned: Instantiable<F> + InnerConstants + Clone,
CanonicityChip: CanonicityInstructions<F, Assigned>
+ AssertionInstructions<F, Assigned>
+ AssertionInstructions<F, AssignedBit<F>>
+ AssignmentInstructions<F, Assigned>
+ FromScratch<F>,
{
type Config = <CanonicityChip as FromScratch<F>>::Config;
type FloorPlanner = SimpleFloorPlanner;
type Params = ();
fn without_witnesses(&self) -> Self {
unreachable!()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let committed_instance_column = meta.instance_column();
let instance_column = meta.instance_column();
CanonicityChip::configure_from_scratch(
meta,
&mut vec![],
&mut vec![],
&[committed_instance_column, instance_column],
)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let chip = CanonicityChip::new_from_scratch(&config);
let bits = self
.bits
.iter()
.map(|b| chip.assign_fixed(&mut layouter, *b))
.collect::<Result<Vec<_>, Error>>()?;
let bound = self.bound.clone();
cost_measure_start(&mut layouter);
let res = match self.operation {
Operation::Canonical => chip.is_canonical(&mut layouter, &bits),
Operation::Lower => chip.le_bits_lower_than(&mut layouter, &bits, bound),
Operation::Geq => chip.le_bits_geq_than(&mut layouter, &bits, bound),
}?;
cost_measure_end(&mut layouter);
chip.assert_equal_to_fixed(&mut layouter, &res, self.expected)?;
chip.load_from_scratch(&mut layouter)
}
}
#[allow(clippy::too_many_arguments)]
fn run<F, Assigned, CanonicityChip>(
bits: &[u8],
bound: Option<&BigUint>,
expected: bool,
operation: Operation,
must_pass: bool,
cost_model: bool,
chip_name: &str,
op_name: &str,
) where
F: CircuitField + FromUniformBytes<64> + Ord,
Assigned::Element: CircuitField,
Assigned: Instantiable<F> + InnerConstants + Clone,
CanonicityChip: CanonicityInstructions<F, Assigned>
+ AssertionInstructions<F, Assigned>
+ AssertionInstructions<F, AssignedBit<F>>
+ AssignmentInstructions<F, Assigned>
+ FromScratch<F>,
{
let circuit = TestCircuit::<F, Assigned, CanonicityChip> {
bits: bits.iter().map(|b| *b != 0).collect::<Vec<_>>(),
bound: bound.unwrap_or(&BigUint::default()).clone(),
expected,
operation,
_marker: PhantomData,
};
let public_inputs = vec![vec![], vec![]];
match MockProver::run(&circuit, public_inputs) {
Ok(prover) => match prover.verify() {
Ok(()) => assert!(must_pass),
Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
},
Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
}
if cost_model {
circuit_to_json(chip_name, op_name, circuit);
}
}
fn decompose_biguint(n: &BigUint) -> Vec<u8> {
(0..(n.bits() as usize)).map(|i| if n.bit(i as u64) { 1 } else { 0 }).collect()
}
pub fn test_canonical<F, Assigned, CanonicityChip>(name: &str)
where
F: CircuitField + FromUniformBytes<64> + Ord,
Assigned::Element: CircuitField,
Assigned: Instantiable<F> + InnerConstants + Clone,
CanonicityChip: CanonicityInstructions<F, Assigned>
+ AssertionInstructions<F, Assigned>
+ AssertionInstructions<F, AssignedBit<F>>
+ AssignmentInstructions<F, Assigned>
+ FromScratch<F>,
{
let m = Assigned::Element::modulus();
let mut cost_model = true;
[
(vec![0], true),
(vec![1], true),
(vec![1, 0, 1], true),
(decompose_biguint(&m), false),
(decompose_biguint(&(m - BigUint::one())), true),
(vec![0; Assigned::Element::NUM_BITS as usize], true),
(vec![1; Assigned::Element::NUM_BITS as usize], false),
(vec![0; 1 + Assigned::Element::NUM_BITS as usize], false),
]
.iter()
.for_each(|(bits, expected)| {
run::<F, Assigned, CanonicityChip>(
bits,
None,
*expected,
Operation::Canonical,
true,
cost_model,
name,
"canonical",
);
cost_model = false;
run::<F, Assigned, CanonicityChip>(
bits,
None,
!expected,
Operation::Canonical,
false,
false,
"",
"",
);
});
}
pub fn test_le_bits_lower_and_geq<F, Assigned, CanonChip>(name: &str)
where
F: CircuitField + FromUniformBytes<64> + Ord,
Assigned::Element: CircuitField,
Assigned: Instantiable<F> + InnerConstants + Clone,
CanonChip: CanonicityInstructions<F, Assigned>
+ AssertionInstructions<F, Assigned>
+ AssertionInstructions<F, AssignedBit<F>>
+ AssignmentInstructions<F, Assigned>
+ FromScratch<F>,
{
let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
let r: BigUint = rng.next_u64().into();
let m = Assigned::Element::modulus();
let mut cost_model = true;
[
(decompose_biguint(&r), r.clone() - BigUint::one(), true),
(decompose_biguint(&r), r.clone(), true),
(decompose_biguint(&r), r + BigUint::one(), false),
(decompose_biguint(&m), m.clone(), true),
(decompose_biguint(&m), m + BigUint::one(), false),
(vec![0], BigUint::zero(), true),
(vec![1], BigUint::zero(), true),
(vec![1, 0, 1], BigUint::from(5u64), true),
(vec![1, 0, 1], BigUint::from(6u64), false),
(vec![1, 1, 1, 0, 0, 0], BigUint::from(7u64), true),
(vec![1, 1, 1, 0, 0, 0], BigUint::from(8u64), false),
]
.iter()
.for_each(|(bits, bound, geq)| {
run::<_, _, CanonChip>(
bits,
Some(bound),
!geq,
Operation::Lower,
true,
cost_model,
name,
"lt",
);
run::<_, _, CanonChip>(
bits,
Some(bound),
*geq,
Operation::Geq,
true,
cost_model,
name,
"geq",
);
cost_model = false;
run::<_, _, CanonChip>(
bits,
Some(bound),
*geq,
Operation::Lower,
false,
false,
"",
"",
);
run::<_, _, CanonChip>(
bits,
Some(bound),
!geq,
Operation::Geq,
false,
false,
"",
"",
);
});
}
}