Remove standard PLONK helper.

The Action circuit only used standard PLONK in one place. Since it
used non-binary selectors, it cannot be optimised by the halo2
selector optimisations. We now replace it with a custom gate which
uses a binary selector.
This commit is contained in:
therealyingtong 2021-07-23 17:58:37 +08:00
parent 8cf7a6872c
commit cba0d8672b
4 changed files with 47 additions and 377 deletions

View File

@ -53,11 +53,7 @@ use gadget::{
},
note_commit::NoteCommitConfig,
},
utilities::{
copy,
plonk::{PLONKChip, PLONKConfig, PLONKInstructions},
CellValue, UtilitiesInstructions, Var,
},
utilities::{copy, CellValue, UtilitiesInstructions, Var},
};
use std::convert::TryInto;
@ -85,10 +81,11 @@ const ENABLE_OUTPUT: usize = 8;
pub struct Config {
primary: Column<InstanceColumn>,
q_orchard: Selector,
// Selector for the field addition gate poseidon_hash(nk, rho_old) + psi_old.
q_add: Selector,
advices: [Column<Advice>; 10],
ecc_config: EccConfig,
poseidon_config: PoseidonConfig<pallas::Base>,
plonk_config: PLONKConfig,
merkle_config_1: MerkleConfig,
merkle_config_2: MerkleConfig,
sinsemilla_config_1: SinsemillaConfig,
@ -186,6 +183,17 @@ impl plonk::Circuit<pallas::Base> for Circuit {
.map(move |(name, poly)| (name, q_orchard.clone() * poly))
});
// Addition of two field elements poseidon_hash(nk, rho_old) + psi_old.
let q_add = meta.selector();
meta.create_gate("poseidon_hash(nk, rho_old) + psi_old", |meta| {
let q_add = meta.query_selector(q_add);
let sum = meta.query_advice(advices[0], Rotation::cur());
let nk_rho_old = meta.query_advice(advices[1], Rotation::cur());
let psi_old = meta.query_advice(advices[2], Rotation::cur());
vec![q_add * (nk_rho_old + psi_old - sum)]
});
// Fixed columns for the Sinsemilla generator lookup table
let table_idx = meta.fixed_column();
let lookup = (table_idx, meta.fixed_column(), meta.fixed_column());
@ -239,9 +247,6 @@ impl plonk::Circuit<pallas::Base> for Circuit {
rc_b,
);
// Configuration for standard PLONK (addition and multiplication).
let plonk_config = PLONKChip::configure(meta, [advices[0], advices[1], advices[2]]);
// Configuration for a Sinsemilla hash instantiation and a
// Merkle hash instantiation using this Sinsemilla instance.
// Since the Sinsemilla config uses only 5 advice columns,
@ -294,10 +299,10 @@ impl plonk::Circuit<pallas::Base> for Circuit {
Config {
primary,
q_orchard,
q_add,
advices,
ecc_config,
poseidon_config,
plonk_config,
merkle_config_1,
merkle_config_2,
sinsemilla_config_1,
@ -504,32 +509,42 @@ impl plonk::Circuit<pallas::Base> for Circuit {
poseidon_output
};
// Add hash output to psi using standard PLONK
// Add hash output to psi.
// `scalar` = poseidon_hash(nk, rho_old) + psi_old.
//
let scalar = {
let scalar_val = nk_rho_old
.value()
.zip(psi_old.value())
.map(|(nk_rho_old, psi_old)| nk_rho_old + psi_old);
let scalar = self.load_private(
layouter.namespace(|| "poseidon_hash(nk, rho_old) + psi_old"),
config.advices[0],
scalar_val,
)?;
let scalar = layouter.assign_region(
|| " `scalar` = poseidon_hash(nk, rho_old) + psi_old",
|mut region| {
config.q_add.enable(&mut region, 0)?;
config.plonk_chip().add(
layouter.namespace(|| "poseidon_hash(nk, rho_old) + psi_old"),
nk_rho_old,
psi_old,
scalar,
Some(pallas::Base::one()),
Some(pallas::Base::one()),
Some(pallas::Base::one()),
)?;
copy(
&mut region,
|| "copy nk_rho_old",
config.advices[1],
0,
&nk_rho_old,
)?;
copy(
&mut region,
|| "copy psi_old",
config.advices[2],
0,
&psi_old,
)?;
scalar
};
let scalar_val = nk_rho_old
.value()
.zip(psi_old.value())
.map(|(nk_rho_old, psi_old)| nk_rho_old + psi_old);
let cell = region.assign_advice(
|| "poseidon_hash(nk, rho_old) + psi_old",
config.advices[0],
0,
|| scalar_val.ok_or(plonk::Error::SynthesisError),
)?;
Ok(CellValue::new(cell, scalar_val))
},
)?;
// Multiply scalar by NullifierK
// `product` = [poseidon_hash(nk, rho_old) + psi_old] NullifierK.

View File

@ -3,7 +3,6 @@ use pasta_curves::pallas;
use ecc::chip::EccChip;
use poseidon::Pow5T3Chip as PoseidonChip;
use sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip};
use utilities::plonk::PLONKChip;
pub(crate) mod ecc;
pub(crate) mod poseidon;
@ -11,10 +10,6 @@ pub(crate) mod sinsemilla;
pub(crate) mod utilities;
impl super::Config {
pub(super) fn plonk_chip(&self) -> PLONKChip<pallas::Base> {
PLONKChip::construct(self.plonk_config.clone())
}
pub(super) fn ecc_chip(&self) -> EccChip {
EccChip::construct(self.ecc_config.clone())
}

View File

@ -9,7 +9,6 @@ use std::{array, convert::TryInto, ops::Range};
pub(crate) mod cond_swap;
pub(crate) mod decompose_running_sum;
pub(crate) mod lookup_range_check;
pub(crate) mod plonk;
/// A variable representing a field element.
#[derive(Copy, Clone, Debug)]

View File

@ -1,339 +0,0 @@
use super::{copy, CellValue, UtilitiesInstructions};
use halo2::{
circuit::{Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Fixed},
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>,
}
#[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)?;
// Copy in `b`
copy(&mut region, || "copy b", config.b, 0, &b)?;
// Copy in `c`
copy(&mut region, || "copy c", config.c, 0, &c)?;
// 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)?;
// Copy in `b`
copy(&mut region, || "copy b", config.b, 0, &b)?;
// Copy in `c`
copy(&mut region, || "copy c", config.c, 0, &c)?;
// 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.
///
/// # Side-effects
///
/// All columns in `advices` will be equality-enabled.
pub fn configure(meta: &mut ConstraintSystem<F>, advices: [Column<Advice>; 3]) -> PLONKConfig {
let a = advices[0];
let b = advices[1];
let c = advices[2];
meta.enable_equality(a.into());
meta.enable_equality(b.into());
meta.enable_equality(c.into());
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,
}
}
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, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas::Base};
#[test]
fn plonk_util() {
#[derive(Default)]
struct MyCircuit<F: FieldExt> {
a: Option<F>,
b: Option<F>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
type Config = PLONKConfig;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advices = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
PLONKChip::<F>::configure(meta, advices)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
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(4, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
}