use std::marker::PhantomData;
use midnight_proofs::{
circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value},
plonk::{Advice, Circuit, Column, ConstraintSystem, Constraints, Error, Instance, Selector},
poly::Rotation,
utils::arithmetic::Field,
};
trait NumericInstructions<F: Field>: Chip<F> {
type Num;
fn load_private(
&self,
layouter: impl Layouter<F>,
a: &[Value<F>],
) -> Result<Vec<Self::Num>, Error>;
fn mul(
&self,
layouter: impl Layouter<F>,
a: &[Self::Num],
b: &[Self::Num],
) -> Result<Vec<Self::Num>, Error>;
fn expose_public(
&self,
layouter: impl Layouter<F>,
num: &Self::Num,
row: usize,
) -> Result<(), Error>;
}
struct FieldChip<F: Field> {
config: FieldConfig,
_marker: PhantomData<F>,
}
#[derive(Clone, Debug)]
struct FieldConfig {
advice: [Column<Advice>; 3],
instance: Column<Instance>,
s_mul: Selector,
}
impl<F: Field> FieldChip<F> {
fn construct(config: <Self as Chip<F>>::Config) -> Self {
Self {
config,
_marker: PhantomData,
}
}
fn configure(
meta: &mut ConstraintSystem<F>,
advice: [Column<Advice>; 3],
instance: Column<Instance>,
) -> <Self as Chip<F>>::Config {
meta.enable_equality(instance);
for column in &advice {
meta.enable_equality(*column);
}
let s_mul = meta.selector();
meta.create_gate("mul", |meta| {
let lhs = meta.query_advice(advice[0], Rotation::cur());
let rhs = meta.query_advice(advice[1], Rotation::cur());
let out = meta.query_advice(advice[2], Rotation::cur());
Constraints::with_selector(s_mul, vec![lhs * rhs - out])
});
FieldConfig {
advice,
instance,
s_mul,
}
}
}
impl<F: Field> Chip<F> for FieldChip<F> {
type Config = FieldConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
#[derive(Clone, Debug)]
struct Number<F: Field>(AssignedCell<F, F>);
impl<F: Field> NumericInstructions<F> for FieldChip<F> {
type Num = Number<F>;
fn load_private(
&self,
mut layouter: impl Layouter<F>,
values: &[Value<F>],
) -> Result<Vec<Self::Num>, Error> {
let config = self.config();
layouter.assign_region(
|| "load private",
|mut region| {
values
.iter()
.enumerate()
.map(|(i, value)| {
region
.assign_advice(|| "private input", config.advice[0], i, || *value)
.map(Number)
})
.collect()
},
)
}
fn mul(
&self,
mut layouter: impl Layouter<F>,
a: &[Self::Num],
b: &[Self::Num],
) -> Result<Vec<Self::Num>, Error> {
let config = self.config();
assert_eq!(a.len(), b.len());
layouter.assign_region(
|| "mul",
|mut region: Region<'_, F>| {
a.iter()
.zip(b.iter())
.enumerate()
.map(|(i, (a, b))| {
config.s_mul.enable(&mut region, i)?;
a.0.copy_advice(|| "lhs", &mut region, config.advice[0], i)?;
b.0.copy_advice(|| "rhs", &mut region, config.advice[1], i)?;
let value = a.0.value().copied() * b.0.value();
region
.assign_advice(|| "lhs * rhs", config.advice[2], i, || value)
.map(Number)
})
.collect()
},
)
}
fn expose_public(
&self,
mut layouter: impl Layouter<F>,
num: &Self::Num,
row: usize,
) -> Result<(), Error> {
let config = self.config();
layouter.constrain_instance(num.0.cell(), config.instance, row)
}
}
#[derive(Default)]
struct MyCircuit<F: Field> {
a: Vec<Value<F>>,
b: Vec<Value<F>>,
}
impl<F: Field> Circuit<F> for MyCircuit<F> {
type Config = FieldConfig;
type FloorPlanner = SimpleFloorPlanner;
#[cfg(feature = "circuit-params")]
type Params = ();
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advice = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let instance = meta.instance_column();
FieldChip::configure(meta, advice, instance)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let field_chip = FieldChip::<F>::construct(config);
let a = field_chip.load_private(layouter.namespace(|| "load a"), &self.a)?;
let b = field_chip.load_private(layouter.namespace(|| "load b"), &self.b)?;
let ab = field_chip.mul(layouter.namespace(|| "a * b"), &a, &b)?;
for (i, c) in ab.iter().enumerate() {
field_chip.expose_public(layouter.namespace(|| "expose c"), c, i)?;
}
Ok(())
}
}
fn main() {
use ff::Field;
use midnight_curves::Fq as Scalar;
use midnight_proofs::dev::MockProver;
const N: usize = 20000;
let k = 16;
let a = [Scalar::from(2); N];
let b = [Scalar::from(3); N];
let c: Vec<Scalar> = a.iter().zip(b).map(|(&a, b)| a * b).collect();
let circuit = MyCircuit {
a: a.iter().map(|&x| Value::known(x)).collect(),
b: b.iter().map(|&x| Value::known(x)).collect(),
};
let mut public_inputs = c;
let start = std::time::Instant::now();
let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap();
assert_eq!(prover.verify(), Ok(()));
println!("positive test took {:?}", start.elapsed());
let start = std::time::Instant::now();
public_inputs[0] += Scalar::ONE;
let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap();
assert!(prover.verify().is_err());
println!("negative test took {:?}", start.elapsed());
}