Merge pull request #89 from zcash/utils-chip

Chip for general utils
This commit is contained in:
str4d 2021-06-07 19:40:56 +01:00 committed by GitHub
commit 2be13bfa80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 950 additions and 0 deletions

View File

@ -1,2 +1,3 @@
pub(crate) mod ecc;
pub(crate) mod poseidon;
pub(crate) mod utilities;

View File

@ -0,0 +1,85 @@
use halo2::{
circuit::{Cell, Chip, Layouter, Region},
plonk::{Advice, Column, Error, Permutation},
};
use pasta_curves::arithmetic::FieldExt;
mod cond_swap;
mod enable_flag;
mod plonk;
/// A variable representing a number.
#[derive(Copy, Clone, Debug)]
pub struct CellValue<F: FieldExt> {
cell: Cell,
value: Option<F>,
}
pub trait Var<F: FieldExt> {
fn new(cell: Cell, value: Option<F>) -> Self;
fn cell(&self) -> Cell;
fn value(&self) -> Option<F>;
}
impl<F: FieldExt> Var<F> for CellValue<F> {
fn new(cell: Cell, value: Option<F>) -> Self {
Self { cell, value }
}
fn cell(&self) -> Cell {
self.cell
}
fn value(&self) -> Option<F> {
self.value
}
}
pub trait UtilitiesInstructions<F: FieldExt>: Chip<F> {
type Var: Var<F>;
fn load_private(
&self,
mut layouter: impl Layouter<F>,
column: Column<Advice>,
value: Option<F>,
) -> Result<Self::Var, Error> {
layouter.assign_region(
|| "load private",
|mut region| {
let cell = region.assign_advice(
|| "load private",
column,
0,
|| value.ok_or(Error::SynthesisError),
)?;
Ok(Var::new(cell, value))
},
)
}
}
/// Assigns a cell at a specific offset within the given region, constraining it
/// to the same value as another cell (which may be in any region).
///
/// Returns an error if either `column` or `copy` is not within `perm`.
pub fn copy<A, AR, F: FieldExt>(
region: &mut Region<'_, F>,
annotation: A,
column: Column<Advice>,
offset: usize,
copy: &CellValue<F>,
perm: &Permutation,
) -> Result<CellValue<F>, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
let cell = region.assign_advice(annotation, column, offset, || {
copy.value.ok_or(Error::SynthesisError)
})?;
region.constrain_equal(perm, cell, copy.cell)?;
Ok(CellValue::new(cell, copy.value))
}

View File

@ -0,0 +1,291 @@
use super::{copy, CellValue, UtilitiesInstructions};
use halo2::{
circuit::{Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector},
poly::Rotation,
};
use pasta_curves::arithmetic::FieldExt;
use std::{array, marker::PhantomData};
pub trait CondSwapInstructions<F: FieldExt>: UtilitiesInstructions<F> {
#[allow(clippy::type_complexity)]
/// Given an input pair (a,b) and a `swap` boolean flag, returns
/// (b,a) if `swap` is set, else (a,b) if `swap` is not set.
fn swap(
&self,
layouter: impl Layouter<F>,
pair: (Self::Var, Self::Var),
swap: Option<bool>,
) -> Result<(Self::Var, Self::Var), Error>;
}
/// A chip implementing a conditional swap.
#[derive(Clone, Debug)]
pub struct CondSwapChip<F> {
config: CondSwapConfig,
_marker: PhantomData<F>,
}
impl<F: FieldExt> Chip<F> for CondSwapChip<F> {
type Config = CondSwapConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
#[derive(Clone, Debug)]
pub struct CondSwapConfig {
pub q_swap: Selector,
pub a: Column<Advice>,
pub b: Column<Advice>,
pub a_swapped: Column<Advice>,
pub b_swapped: Column<Advice>,
pub swap: Column<Advice>,
pub perm: Permutation,
}
impl<F: FieldExt> UtilitiesInstructions<F> for CondSwapChip<F> {
type Var = CellValue<F>;
}
impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
#[allow(clippy::type_complexity)]
fn swap(
&self,
mut layouter: impl Layouter<F>,
pair: (Self::Var, Self::Var),
swap: Option<bool>,
) -> Result<(Self::Var, Self::Var), Error> {
let config = self.config();
layouter.assign_region(
|| "swap",
|mut region| {
// Enable `q_swap` selector
config.q_swap.enable(&mut region, 0)?;
// Copy in `a` value
let a = copy(&mut region, || "copy a", config.a, 0, &pair.0, &config.perm)?;
// Copy in `b` value
let b = copy(&mut region, || "copy b", config.b, 0, &pair.1, &config.perm)?;
// Witness `swap` value
let swap_val = swap.map(|swap| F::from_u64(swap as u64));
region.assign_advice(
|| "swap",
config.swap,
0,
|| swap_val.ok_or(Error::SynthesisError),
)?;
// Conditionally swap a
let a_swapped = {
let a_swapped = a
.value
.zip(b.value)
.zip(swap)
.map(|((a, b), swap)| if swap { b } else { a });
let a_swapped_cell = region.assign_advice(
|| "a_swapped",
config.a_swapped,
0,
|| a_swapped.ok_or(Error::SynthesisError),
)?;
CellValue {
cell: a_swapped_cell,
value: a_swapped,
}
};
// Conditionally swap b
let b_swapped = {
let b_swapped = a
.value
.zip(b.value)
.zip(swap)
.map(|((a, b), swap)| if swap { a } else { b });
let b_swapped_cell = region.assign_advice(
|| "b_swapped",
config.b_swapped,
0,
|| b_swapped.ok_or(Error::SynthesisError),
)?;
CellValue {
cell: b_swapped_cell,
value: b_swapped,
}
};
// Return swapped pair
Ok((a_swapped, b_swapped))
},
)
}
}
impl<F: FieldExt> CondSwapChip<F> {
/// Configures this chip for use in a circuit.
///
/// `perm` must cover `advices[0..2]`, as well as any columns that will
/// be passed to this chip.
pub fn configure(
meta: &mut ConstraintSystem<F>,
advices: [Column<Advice>; 5],
perm: Permutation,
) -> CondSwapConfig {
let q_swap = meta.selector();
let config = CondSwapConfig {
q_swap,
a: advices[0],
b: advices[1],
a_swapped: advices[2],
b_swapped: advices[3],
swap: advices[4],
perm,
};
// TODO: optimise shape of gate for Merkle path validation
meta.create_gate("a' = b ⋅ swap + a ⋅ (1-swap)", |meta| {
let q_swap = meta.query_selector(q_swap, Rotation::cur());
let a = meta.query_advice(config.a, Rotation::cur());
let b = meta.query_advice(config.b, Rotation::cur());
let a_swapped = meta.query_advice(config.a_swapped, Rotation::cur());
let b_swapped = meta.query_advice(config.b_swapped, Rotation::cur());
let swap = meta.query_advice(config.swap, Rotation::cur());
let one = Expression::Constant(F::one());
// a_swapped - b ⋅ swap - a ⋅ (1-swap) = 0
// This checks that `a_swapped` is equal to `y` when `swap` is set,
// but remains as `a` when `swap` is not set.
let a_check =
a_swapped - b.clone() * swap.clone() - a.clone() * (one.clone() - swap.clone());
// b_swapped - a ⋅ swap - b ⋅ (1-swap) = 0
// This checks that `b_swapped` is equal to `a` when `swap` is set,
// but remains as `b` when `swap` is not set.
let b_check = b_swapped - a * swap.clone() - b * (one.clone() - swap.clone());
// Check `swap` is boolean.
let bool_check = swap.clone() * (one - swap);
array::IntoIter::new([a_check, b_check, bool_check])
.map(|poly| q_swap.clone() * poly)
.collect()
});
config
}
pub fn construct(config: CondSwapConfig) -> Self {
CondSwapChip {
config,
_marker: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::super::UtilitiesInstructions;
use super::{CondSwapChip, CondSwapConfig, CondSwapInstructions};
use halo2::{
circuit::{layouter::SingleChipLayouter, Layouter},
dev::MockProver,
plonk::{Any, Assignment, Circuit, Column, ConstraintSystem, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas::Base};
#[test]
fn cond_swap() {
struct MyCircuit<F: FieldExt> {
a: Option<F>,
b: Option<F>,
swap: Option<bool>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
type Config = CondSwapConfig;
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advices = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let perm = meta.permutation(
&advices
.iter()
.map(|advice| (*advice).into())
.collect::<Vec<Column<Any>>>(),
);
CondSwapChip::<F>::configure(meta, advices, perm)
}
fn synthesize(
&self,
cs: &mut impl Assignment<F>,
config: Self::Config,
) -> Result<(), Error> {
let mut layouter = SingleChipLayouter::new(cs)?;
let chip = CondSwapChip::<F>::construct(config.clone());
// Load the pair and the swap flag into the circuit.
let a = chip.load_private(layouter.namespace(|| "a"), config.a, self.a)?;
let b = chip.load_private(layouter.namespace(|| "b"), config.b, self.b)?;
// Return the swapped pair.
let swapped_pair = chip.swap(layouter.namespace(|| "swap"), (a, b), self.swap)?;
if let Some(swap) = self.swap {
if swap {
// Check that `a` and `b` have been swapped
assert_eq!(swapped_pair.0.value.unwrap(), b.value.unwrap());
assert_eq!(swapped_pair.1.value.unwrap(), a.value.unwrap());
} else {
// Check that `a` and `b` have not been swapped
assert_eq!(swapped_pair.0.value.unwrap(), a.value.unwrap());
assert_eq!(swapped_pair.1.value.unwrap(), b.value.unwrap());
}
}
Ok(())
}
}
// Test swap case
{
let circuit: MyCircuit<Base> = MyCircuit {
a: Some(Base::rand()),
b: Some(Base::rand()),
swap: Some(true),
};
let prover = MockProver::<Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Test non-swap case
{
let circuit: MyCircuit<Base> = MyCircuit {
a: Some(Base::rand()),
b: Some(Base::rand()),
swap: Some(false),
};
let prover = MockProver::<Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
}
}

View File

@ -0,0 +1,229 @@
use super::{copy, CellValue, UtilitiesInstructions};
use halo2::{
circuit::{Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector},
poly::Rotation,
};
use pasta_curves::arithmetic::FieldExt;
use std::marker::PhantomData;
pub trait EnableFlagInstructions<F: FieldExt>: UtilitiesInstructions<F> {
/// Enforces that `value` be zero or, if non-zero, that `enable_flag` must be 1.
fn enable_flag(
&self,
layouter: impl Layouter<F>,
value: Self::Var,
enable_flag: Option<bool>,
) -> Result<(), Error>;
}
#[derive(Clone, Debug)]
pub struct EnableFlagConfig {
q_enable: Selector,
value: Column<Advice>,
enable_flag: Column<Advice>,
perm: Permutation,
}
/// A chip implementing an enable flag.
#[derive(Clone, Debug)]
pub struct EnableFlagChip<F> {
config: EnableFlagConfig,
_marker: PhantomData<F>,
}
impl<F: FieldExt> Chip<F> for EnableFlagChip<F> {
type Config = EnableFlagConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<F: FieldExt> UtilitiesInstructions<F> for EnableFlagChip<F> {
type Var = CellValue<F>;
}
impl<F: FieldExt> EnableFlagInstructions<F> for EnableFlagChip<F> {
fn enable_flag(
&self,
mut layouter: impl Layouter<F>,
value: Self::Var,
enable_flag: Option<bool>,
) -> Result<(), Error> {
let config = self.config().clone();
layouter.assign_region(
|| "enable flag",
|mut region| {
// Enable `q_enable` selector
config.q_enable.enable(&mut region, 0)?;
// Witness `enable_flag` value
let enable_flag_val = enable_flag.map(|flag| F::from_u64(flag as u64));
region.assign_advice(
|| "enable_flag",
config.enable_flag,
0,
|| enable_flag_val.ok_or(Error::SynthesisError),
)?;
// Copy `value`
copy(
&mut region,
|| "copy value",
config.value,
0,
&value,
&config.perm,
)?;
Ok(())
},
)
}
}
impl<F: FieldExt> EnableFlagChip<F> {
/// Configures this chip for use in a circuit.
///
/// `perm` must cover `advices[0]`, as well as any columns that will be
/// passed to this chip.
pub fn configure(
meta: &mut ConstraintSystem<F>,
advices: [Column<Advice>; 2],
perm: Permutation,
) -> EnableFlagConfig {
let q_enable = meta.selector();
let config = EnableFlagConfig {
q_enable,
value: advices[0],
enable_flag: advices[1],
perm,
};
meta.create_gate("Enable flag", |meta| {
let q_enable = meta.query_selector(config.q_enable, Rotation::cur());
let value = meta.query_advice(config.value, Rotation::cur());
let enable_flag = meta.query_advice(config.enable_flag, Rotation::cur());
vec![q_enable * (Expression::Constant(F::one()) - enable_flag) * value]
});
config
}
pub fn construct(config: EnableFlagConfig) -> Self {
EnableFlagChip {
config,
_marker: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::super::UtilitiesInstructions;
use super::{EnableFlagChip, EnableFlagConfig, EnableFlagInstructions};
use halo2::{
circuit::{layouter::SingleChipLayouter, Layouter},
dev::{MockProver, VerifyFailure},
plonk::{Any, Assignment, Circuit, Column, ConstraintSystem, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas::Base};
#[test]
fn enable_flag() {
struct MyCircuit<F: FieldExt> {
value: Option<F>,
enable_flag: Option<bool>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
type Config = EnableFlagConfig;
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advices = [meta.advice_column(), meta.advice_column()];
let perm = meta.permutation(
&advices
.iter()
.map(|advice| (*advice).into())
.collect::<Vec<Column<Any>>>(),
);
EnableFlagChip::<F>::configure(meta, advices, perm)
}
fn synthesize(
&self,
cs: &mut impl Assignment<F>,
config: Self::Config,
) -> Result<(), Error> {
let mut layouter = SingleChipLayouter::new(cs)?;
let chip = EnableFlagChip::<F>::construct(config.clone());
// Load the value and the enable flag into the circuit.
let value =
chip.load_private(layouter.namespace(|| "value"), config.value, self.value)?;
// Run the enable flag logic.
chip.enable_flag(layouter.namespace(|| "swap"), value, self.enable_flag)?;
Ok(())
}
}
// Test value = 1, flag = 1 case (success)
{
let circuit: MyCircuit<Base> = MyCircuit {
value: Some(Base::one()),
enable_flag: Some(true),
};
let prover = MockProver::<Base>::run(1, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Test value = 0, flag = 0 case (success)
{
let circuit: MyCircuit<Base> = MyCircuit {
value: Some(Base::zero()),
enable_flag: Some(false),
};
let prover = MockProver::<Base>::run(1, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Test value = 0, flag = 1 case (success)
{
let circuit: MyCircuit<Base> = MyCircuit {
value: Some(Base::zero()),
enable_flag: Some(true),
};
let prover = MockProver::<Base>::run(1, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Test value = 1, flag = 0 case (error)
{
let circuit: MyCircuit<Base> = MyCircuit {
value: Some(Base::one()),
enable_flag: Some(false),
};
let prover = MockProver::<Base>::run(1, &circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![VerifyFailure::Gate {
gate_index: 0,
gate_name: "Enable flag",
row: 1,
}])
);
}
}
}

View File

@ -0,0 +1,344 @@
use super::{copy, CellValue, UtilitiesInstructions};
use halo2::{
circuit::{Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation},
poly::Rotation,
};
use pasta_curves::arithmetic::FieldExt;
use std::marker::PhantomData;
#[allow(clippy::upper_case_acronyms)]
#[allow(clippy::too_many_arguments)]
pub trait PLONKInstructions<F: FieldExt>: UtilitiesInstructions<F> {
// Checks that a * sm * b = c * sc
fn mul(
&self,
layouter: impl Layouter<F>,
a: Self::Var,
b: Self::Var,
c: Self::Var,
sc: Option<F>,
sm: Option<F>,
) -> Result<(), Error>;
// Checks that a * sa + b * sb = c * sc
fn add(
&self,
layouter: impl Layouter<F>,
a: Self::Var,
b: Self::Var,
c: Self::Var,
sa: Option<F>,
sb: Option<F>,
sc: Option<F>,
) -> Result<(), Error>;
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug)]
pub struct PLONKConfig {
a: Column<Advice>,
b: Column<Advice>,
c: Column<Advice>,
sa: Column<Fixed>,
sb: Column<Fixed>,
sc: Column<Fixed>,
sm: Column<Fixed>,
perm: Permutation,
}
#[allow(clippy::upper_case_acronyms)]
pub struct PLONKChip<F: FieldExt> {
config: PLONKConfig,
_marker: PhantomData<F>,
}
#[allow(clippy::upper_case_acronyms)]
impl<F: FieldExt> Chip<F> for PLONKChip<F> {
type Config = PLONKConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
#[allow(clippy::upper_case_acronyms)]
impl<F: FieldExt> UtilitiesInstructions<F> for PLONKChip<F> {
type Var = CellValue<F>;
}
#[allow(clippy::upper_case_acronyms)]
impl<F: FieldExt> PLONKInstructions<F> for PLONKChip<F> {
fn mul(
&self,
mut layouter: impl Layouter<F>,
a: Self::Var,
b: Self::Var,
c: Self::Var,
sc: Option<F>,
sm: Option<F>,
) -> Result<(), Error> {
layouter.assign_region(
|| "mul",
|mut region| {
let config = self.config().clone();
// Copy in `a`
copy(&mut region, || "copy a", config.a, 0, &a, &config.perm)?;
// Copy in `b`
copy(&mut region, || "copy b", config.b, 0, &b, &config.perm)?;
// Copy in `c`
copy(&mut region, || "copy c", config.c, 0, &c, &config.perm)?;
// Assign fixed columns
region.assign_fixed(|| "sc", config.sc, 0, || sc.ok_or(Error::SynthesisError))?;
region.assign_fixed(
|| "a * (sm) * b",
config.sm,
0,
|| sm.ok_or(Error::SynthesisError),
)?;
#[cfg(test)]
// Checks that a * sm * b = c * sc
{
if let (Some(a), Some(b), Some(c), Some(sm), Some(sc)) =
(a.value, b.value, c.value, sm, sc)
{
assert_eq!(a * sm * b, c * sc);
}
}
Ok(())
},
)
}
fn add(
&self,
mut layouter: impl Layouter<F>,
a: Self::Var,
b: Self::Var,
c: Self::Var,
sa: Option<F>,
sb: Option<F>,
sc: Option<F>,
) -> Result<(), Error> {
let config = self.config().clone();
layouter.assign_region(
|| "add",
|mut region| {
// Copy in `a`
copy(&mut region, || "copy a", config.a, 0, &a, &config.perm)?;
// Copy in `b`
copy(&mut region, || "copy b", config.b, 0, &b, &config.perm)?;
// Copy in `c`
copy(&mut region, || "copy c", config.c, 0, &c, &config.perm)?;
// Assign fixed columns
region.assign_fixed(|| "a", config.sa, 0, || sa.ok_or(Error::SynthesisError))?;
region.assign_fixed(|| "b", config.sb, 0, || sb.ok_or(Error::SynthesisError))?;
region.assign_fixed(|| "c", config.sc, 0, || sc.ok_or(Error::SynthesisError))?;
#[cfg(test)]
// Checks that a * sa + b * sb = c * sc
{
if let (Some(a), Some(b), Some(c), Some(sa), Some(sb), Some(sc)) =
(a.value, b.value, c.value, sa, sb, sc)
{
assert_eq!(a * sa + b * sb, c * sc);
}
}
Ok(())
},
)
}
}
#[allow(clippy::upper_case_acronyms)]
impl<F: FieldExt> PLONKChip<F> {
/// Configures this chip for use in a circuit.
///
/// `perm` must cover `advices`, as well as any columns that will be passed
/// to this chip.
pub fn configure(
meta: &mut ConstraintSystem<F>,
advices: [Column<Advice>; 3],
perm: Permutation,
) -> PLONKConfig {
let a = advices[0];
let b = advices[1];
let c = advices[2];
let sa = meta.fixed_column();
let sb = meta.fixed_column();
let sc = meta.fixed_column();
let sm = meta.fixed_column();
meta.create_gate("Combined add-mult", |meta| {
let a = meta.query_advice(a, Rotation::cur());
let b = meta.query_advice(b, Rotation::cur());
let c = meta.query_advice(c, Rotation::cur());
let sa = meta.query_fixed(sa, Rotation::cur());
let sb = meta.query_fixed(sb, Rotation::cur());
let sc = meta.query_fixed(sc, Rotation::cur());
let sm = meta.query_fixed(sm, Rotation::cur());
vec![a.clone() * sa + b.clone() * sb + a * b * sm + (c * sc * (-F::one()))]
});
PLONKConfig {
a,
b,
c,
sa,
sb,
sc,
sm,
perm,
}
}
pub fn construct(config: PLONKConfig) -> Self {
PLONKChip {
config,
_marker: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::super::UtilitiesInstructions;
use super::{PLONKChip, PLONKConfig, PLONKInstructions};
use halo2::{
circuit::{layouter::SingleChipLayouter, Layouter},
dev::MockProver,
plonk::{Any, Assignment, Circuit, Column, ConstraintSystem, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas::Base};
#[test]
fn plonk_util() {
struct MyCircuit<F: FieldExt> {
a: Option<F>,
b: Option<F>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
type Config = PLONKConfig;
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advices = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let perm = meta.permutation(
&advices
.iter()
.map(|advice| (*advice).into())
.collect::<Vec<Column<Any>>>(),
);
PLONKChip::<F>::configure(meta, advices, perm)
}
fn synthesize(
&self,
cs: &mut impl Assignment<F>,
config: Self::Config,
) -> Result<(), Error> {
let mut layouter = SingleChipLayouter::new(cs)?;
let chip = PLONKChip::<F>::construct(config.clone());
let a = chip.load_private(layouter.namespace(|| "a"), config.a, self.a)?;
let b = chip.load_private(layouter.namespace(|| "b"), config.b, self.b)?;
// a + b = c
{
let c = self.a.zip(self.b).map(|(a, b)| a + b);
let c = chip.load_private(layouter.namespace(|| "c"), config.c, c)?;
chip.add(
layouter.namespace(|| "a + b = c"),
a,
b,
c,
Some(F::one()),
Some(F::one()),
Some(F::one()),
)?;
}
// a * b = c
{
let c = self.a.zip(self.b).map(|(a, b)| a * b);
let c = chip.load_private(layouter.namespace(|| "c"), config.c, c)?;
chip.mul(
layouter.namespace(|| "a * b = c"),
a,
b,
c,
Some(F::one()),
Some(F::one()),
)?;
}
// 2a + 3b = c
{
let c = self
.a
.zip(self.b)
.map(|(a, b)| a * F::from_u64(2) + b * F::from_u64(3));
let c = chip.load_private(layouter.namespace(|| "c"), config.c, c)?;
chip.add(
layouter.namespace(|| "2a + 3b = c"),
a,
b,
c,
Some(F::from_u64(2)),
Some(F::from_u64(3)),
Some(F::one()),
)?;
}
// 4 * a * b = 2 * c => c = 2ab
{
let c = self.a.zip(self.b).map(|(a, b)| a * b * F::from_u64(2));
let c = chip.load_private(layouter.namespace(|| "c"), config.c, c)?;
chip.mul(
layouter.namespace(|| "4 * a * b = 2 * c"),
a,
b,
c,
Some(F::from_u64(2)),
Some(F::from_u64(4)),
)?;
}
Ok(())
}
}
let circuit: MyCircuit<Base> = MyCircuit {
a: Some(Base::rand()),
b: Some(Base::rand()),
};
let prover = MockProver::<Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
}