use midnight_proofs::{circuit::Layouter, plonk::Error};
use crate::{types::AssignedBit, CircuitField};
pub trait BinaryInstructions<F: CircuitField> {
fn and(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
) -> Result<AssignedBit<F>, Error>;
fn or(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
) -> Result<AssignedBit<F>, Error>;
fn xor(
&self,
layouter: &mut impl Layouter<F>,
bits: &[AssignedBit<F>],
) -> Result<AssignedBit<F>, Error>;
fn not(
&self,
layouter: &mut impl Layouter<F>,
bit: &AssignedBit<F>,
) -> Result<AssignedBit<F>, Error>;
}
#[cfg(test)]
pub(crate) mod tests {
use std::{cmp::min, marker::PhantomData};
use ff::FromUniformBytes;
use midnight_proofs::{
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
plonk::{Circuit, ConstraintSystem},
};
use super::*;
use crate::{
instructions::{AssertionInstructions, AssignmentInstructions},
testing_utils::FromScratch,
utils::circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
};
#[derive(Clone, Copy, Debug)]
enum Operation {
And,
Or,
Xor,
Not,
}
#[derive(Clone, Debug)]
struct TestCircuit<F, BinaryChip> {
inputs: Vec<bool>,
expected: bool,
operation: Operation,
_marker: PhantomData<(F, BinaryChip)>,
}
impl<F, BinaryChip> Circuit<F> for TestCircuit<F, BinaryChip>
where
F: CircuitField,
BinaryChip: BinaryInstructions<F>
+ AssignmentInstructions<F, AssignedBit<F>>
+ AssertionInstructions<F, AssignedBit<F>>
+ FromScratch<F>,
{
type Config = <BinaryChip 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();
BinaryChip::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 = BinaryChip::new_from_scratch(&config);
let b2_idx = min(self.inputs.len() - 1, 1);
let b1 = chip.assign(&mut layouter, Value::known(self.inputs[0]))?;
let b2 = chip.assign(&mut layouter, Value::known(self.inputs[b2_idx]))?;
cost_measure_start(&mut layouter);
let res = match self.operation {
Operation::And => chip.and(&mut layouter, &[b1, b2]),
Operation::Or => chip.or(&mut layouter, &[b1, b2]),
Operation::Xor => chip.xor(&mut layouter, &[b1, b2]),
Operation::Not => chip.not(&mut layouter, &b1),
}?;
cost_measure_end(&mut layouter);
chip.assert_equal_to_fixed(&mut layouter, &res, self.expected)?;
chip.load_from_scratch(&mut layouter)
}
}
fn run<F, BinaryChip>(
inputs: &[bool],
expected: bool,
operation: Operation,
must_pass: bool,
cost_model: bool,
chip_name: &str,
op_name: &str,
) where
F: CircuitField + FromUniformBytes<64> + Ord,
BinaryChip: BinaryInstructions<F>
+ AssignmentInstructions<F, AssignedBit<F>>
+ AssertionInstructions<F, AssignedBit<F>>
+ FromScratch<F>,
{
let circuit = TestCircuit::<F, BinaryChip> {
inputs: inputs.to_vec(),
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 test_binary_op<F, BinaryChip>(
inputs: &[u8],
expected: u8,
operation: Operation,
cost_model: bool,
chip_name: &str,
op_name: &str,
) where
F: CircuitField + FromUniformBytes<64> + Ord,
BinaryChip: BinaryInstructions<F>
+ AssignmentInstructions<F, AssignedBit<F>>
+ AssertionInstructions<F, AssignedBit<F>>
+ FromScratch<F>,
{
let inputs = inputs.iter().map(|b| *b == 1).collect::<Vec<_>>();
let expected = expected == 1;
run::<F, BinaryChip>(
&inputs, expected, operation, true, cost_model, chip_name, op_name,
);
run::<F, BinaryChip>(
&inputs, !expected, operation, false, false, chip_name, op_name,
);
}
pub fn test_and<F, BinaryChip>(name: &str)
where
F: CircuitField + FromUniformBytes<64> + Ord,
BinaryChip: BinaryInstructions<F>
+ AssignmentInstructions<F, AssignedBit<F>>
+ AssertionInstructions<F, AssignedBit<F>>
+ FromScratch<F>,
{
test_binary_op::<F, BinaryChip>(&[0, 0], 0, Operation::And, true, name, "and");
test_binary_op::<F, BinaryChip>(&[0, 1], 0, Operation::And, false, "", "");
test_binary_op::<F, BinaryChip>(&[1, 0], 0, Operation::And, false, "", "");
test_binary_op::<F, BinaryChip>(&[1, 1], 1, Operation::And, false, "", "");
}
pub fn test_or<F, BinaryChip>(name: &str)
where
F: CircuitField + FromUniformBytes<64> + Ord,
BinaryChip: BinaryInstructions<F>
+ AssignmentInstructions<F, AssignedBit<F>>
+ AssertionInstructions<F, AssignedBit<F>>
+ FromScratch<F>,
{
test_binary_op::<F, BinaryChip>(&[0, 0], 0, Operation::Or, true, name, "or");
test_binary_op::<F, BinaryChip>(&[0, 1], 1, Operation::Or, false, "", "");
test_binary_op::<F, BinaryChip>(&[1, 0], 1, Operation::Or, false, "", "");
test_binary_op::<F, BinaryChip>(&[1, 1], 1, Operation::Or, false, "", "");
}
pub fn test_xor<F, BinaryChip>(name: &str)
where
F: CircuitField + FromUniformBytes<64> + Ord,
BinaryChip: BinaryInstructions<F>
+ AssignmentInstructions<F, AssignedBit<F>>
+ AssertionInstructions<F, AssignedBit<F>>
+ FromScratch<F>,
{
test_binary_op::<F, BinaryChip>(&[0, 0], 0, Operation::Xor, true, name, "xor");
test_binary_op::<F, BinaryChip>(&[0, 1], 1, Operation::Xor, false, "", "");
test_binary_op::<F, BinaryChip>(&[1, 0], 1, Operation::Xor, false, "", "");
test_binary_op::<F, BinaryChip>(&[1, 1], 0, Operation::Xor, false, "", "");
}
pub fn test_not<F, BinaryChip>(name: &str)
where
F: CircuitField + FromUniformBytes<64> + Ord,
BinaryChip: BinaryInstructions<F>
+ AssignmentInstructions<F, AssignedBit<F>>
+ AssertionInstructions<F, AssignedBit<F>>
+ FromScratch<F>,
{
test_binary_op::<F, BinaryChip>(&[0], 1, Operation::Not, true, name, "not");
test_binary_op::<F, BinaryChip>(&[1], 0, Operation::Not, false, "", "");
}
}