Extract a `DeriveNullifier` gadget from the circuit

This introduces an `AddChip` implementing field element addition on a
single row, precisely matching what the nullifier integrity constraints
were relying on.
This commit is contained in:
Jack Grigg 2022-04-29 17:54:28 +00:00
parent 70b6eb3623
commit dafb357dc0
3 changed files with 193 additions and 76 deletions

View File

@ -16,12 +16,15 @@ use memuse::DynamicUsage;
use pasta_curves::{arithmetic::CurveAffine, pallas, vesta};
use rand::RngCore;
use self::commit_ivk::CommitIvkConfig;
use self::note_commit::NoteCommitConfig;
use self::{
commit_ivk::CommitIvkConfig,
gadget::add_chip::{AddChip, AddConfig},
note_commit::NoteCommitConfig,
};
use crate::{
constants::{
NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull,
OrchardHashDomains, ValueCommitV, MERKLE_DEPTH_ORCHARD,
OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains,
ValueCommitV, MERKLE_DEPTH_ORCHARD,
},
keys::{
CommitIvkRandomness, DiversifiedTransmissionKey, NullifierDerivingKey, SpendValidatingKey,
@ -39,10 +42,10 @@ use crate::{
use halo2_gadgets::{
ecc::{
chip::{EccChip, EccConfig},
FixedPoint, FixedPointBaseField, FixedPointShort, NonIdentityPoint, Point,
FixedPoint, FixedPointShort, NonIdentityPoint, Point,
},
poseidon::{Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
primitives::poseidon::{self, ConstantLength},
poseidon::{Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
primitives::poseidon,
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
merkle::{
@ -76,9 +79,8 @@ 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],
add_config: AddConfig,
ecc_config: EccConfig<OrchardFixedBases>,
poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
merkle_config_1: MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
@ -182,16 +184,8 @@ impl plonk::Circuit<pallas::Base> for Circuit {
)
});
// 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[6], Rotation::cur());
let hash_old = meta.query_advice(advices[7], Rotation::cur());
let psi_old = meta.query_advice(advices[8], Rotation::cur());
Constraints::with_selector(q_add, Some(hash_old + psi_old - sum))
});
// Addition of two field elements.
let add_config = AddChip::configure(meta, advices[7], advices[8], advices[6]);
// Fixed columns for the Sinsemilla generator lookup table
let table_idx = meta.lookup_table_column();
@ -306,8 +300,8 @@ impl plonk::Circuit<pallas::Base> for Circuit {
Config {
primary,
q_orchard,
q_add,
advices,
add_config,
ecc_config,
poseidon_config,
merkle_config_1,
@ -471,61 +465,17 @@ impl plonk::Circuit<pallas::Base> for Circuit {
// Nullifier integrity
let nf_old = {
// hash_old = poseidon_hash(nk, rho_old)
let hash_old = {
let poseidon_message = [nk.clone(), rho_old.clone()];
let poseidon_hasher =
PoseidonHash::<_, _, poseidon::P128Pow5T3, ConstantLength<2>, 3, 2>::init(
config.poseidon_chip(),
layouter.namespace(|| "Poseidon init"),
)?;
poseidon_hasher.hash(
layouter.namespace(|| "Poseidon hash (nk, rho_old)"),
poseidon_message,
)?
};
// Add hash output to psi.
// `scalar` = poseidon_hash(nk, rho_old) + psi_old.
//
let scalar = layouter.assign_region(
|| " `scalar` = poseidon_hash(nk, rho_old) + psi_old",
|mut region| {
config.q_add.enable(&mut region, 0)?;
hash_old.copy_advice(|| "copy hash_old", &mut region, config.advices[7], 0)?;
psi_old.copy_advice(|| "copy psi_old", &mut region, config.advices[8], 0)?;
let scalar_val = hash_old
.value()
.zip(psi_old.value())
.map(|(hash_old, psi_old)| hash_old + psi_old);
region.assign_advice(
|| "poseidon_hash(nk, rho_old) + psi_old",
config.advices[6],
0,
|| scalar_val.ok_or(plonk::Error::Synthesis),
)
},
let nf_old = gadget::derive_nullifier(
layouter.namespace(|| "nf_old = DeriveNullifier_nk(rho_old, psi_old, cm_old)"),
config.poseidon_chip(),
config.add_chip(),
ecc_chip.clone(),
rho_old.clone(),
&psi_old,
&cm_old,
nk.clone(),
)?;
// Multiply scalar by NullifierK
// `product` = [poseidon_hash(nk, rho_old) + psi_old] NullifierK.
//
let product = {
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip.clone(), NullifierK);
nullifier_k.mul(
layouter.namespace(|| "[poseidon_output + psi_old] NullifierK"),
scalar,
)?
};
// Add cm_old to multiplied fixed base to get nf_old
// cm_old + [poseidon_output + psi_old] NullifierK
let nf_old = cm_old
.add(layouter.namespace(|| "nf_old"), &product)?
.extract_p();
// Constrain nf_old to equal public input
layouter.constrain_instance(nf_old.inner().cell(), config.primary, NF_OLD)?;

View File

@ -2,14 +2,26 @@
use pasta_curves::pallas;
use crate::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains};
use crate::constants::{NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains};
use halo2_gadgets::{
ecc::chip::EccChip,
poseidon::Pow5Chip as PoseidonChip,
ecc::{chip::EccChip, EccInstructions, FixedPointBaseField, Point, X},
poseidon::{Hash as PoseidonHash, PoseidonSpongeInstructions, Pow5Chip as PoseidonChip},
primitives::poseidon::{self, ConstantLength},
sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip},
};
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{AssignedCell, Chip, Layouter},
plonk,
};
pub(in crate::circuit) mod add_chip;
impl super::Config {
pub(super) fn add_chip(&self) -> add_chip::AddChip {
add_chip::AddChip::construct(self.add_config.clone())
}
pub(super) fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {
EccChip::construct(self.ecc_config.clone())
}
@ -42,3 +54,72 @@ impl super::Config {
PoseidonChip::construct(self.poseidon_config.clone())
}
}
/// An instruction set for adding two circuit words (field elements).
pub(in crate::circuit) trait AddInstruction<F: FieldExt>: Chip<F> {
/// Constraints `a + b` and returns the sum.
fn add(
&self,
layouter: impl Layouter<F>,
a: &AssignedCell<F, F>,
b: &AssignedCell<F, F>,
) -> Result<AssignedCell<F, F>, plonk::Error>;
}
/// `DeriveNullifier` from [Section 4.16: Note Commitments and Nullifiers].
///
/// [Section 4.16: Note Commitments and Nullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers
#[allow(clippy::too_many_arguments)]
pub(in crate::circuit) fn derive_nullifier<
PoseidonChip: PoseidonSpongeInstructions<pallas::Base, poseidon::P128Pow5T3, ConstantLength<2>, 3, 2>,
AddChip: AddInstruction<pallas::Base>,
EccChip: EccInstructions<
pallas::Affine,
FixedPoints = OrchardFixedBases,
Var = AssignedCell<pallas::Base, pallas::Base>,
>,
>(
mut layouter: impl Layouter<pallas::Base>,
poseidon_chip: PoseidonChip,
add_chip: AddChip,
ecc_chip: EccChip,
rho: AssignedCell<pallas::Base, pallas::Base>,
psi: &AssignedCell<pallas::Base, pallas::Base>,
cm: &Point<pallas::Affine, EccChip>,
nk: AssignedCell<pallas::Base, pallas::Base>,
) -> Result<X<pallas::Affine, EccChip>, plonk::Error> {
// hash = poseidon_hash(nk, rho)
let hash = {
let poseidon_message = [nk, rho];
let poseidon_hasher =
PoseidonHash::init(poseidon_chip, layouter.namespace(|| "Poseidon init"))?;
poseidon_hasher.hash(
layouter.namespace(|| "Poseidon hash (nk, rho)"),
poseidon_message,
)?
};
// Add hash output to psi.
// `scalar` = poseidon_hash(nk, rho) + psi.
let scalar = add_chip.add(
layouter.namespace(|| "scalar = poseidon_hash(nk, rho) + psi"),
&hash,
psi,
)?;
// Multiply scalar by NullifierK
// `product` = [poseidon_hash(nk, rho) + psi] NullifierK.
//
let product = {
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip, NullifierK);
nullifier_k.mul(
layouter.namespace(|| "[poseidon_output + psi] NullifierK"),
scalar,
)?
};
// Add cm to multiplied fixed base to get nf
// cm + [poseidon_output + psi] NullifierK
cm.add(layouter.namespace(|| "nf"), &product)
.map(|res| res.extract_p())
}

View File

@ -0,0 +1,86 @@
use halo2_proofs::{
circuit::{AssignedCell, Chip, Layouter},
plonk::{self, Advice, Column, ConstraintSystem, Constraints, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
use super::AddInstruction;
#[derive(Clone, Debug)]
pub(in crate::circuit) struct AddConfig {
a: Column<Advice>,
b: Column<Advice>,
c: Column<Advice>,
q_add: Selector,
}
/// A chip implementing a single addition constraint `c = a + b` on a single row.
pub(in crate::circuit) struct AddChip {
config: AddConfig,
}
impl Chip<pallas::Base> for AddChip {
type Config = AddConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl AddChip {
pub(in crate::circuit) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
a: Column<Advice>,
b: Column<Advice>,
c: Column<Advice>,
) -> AddConfig {
let q_add = meta.selector();
meta.create_gate("Field element addition: c = a + b", |meta| {
let q_add = meta.query_selector(q_add);
let a = meta.query_advice(a, Rotation::cur());
let b = meta.query_advice(b, Rotation::cur());
let c = meta.query_advice(c, Rotation::cur());
Constraints::with_selector(q_add, Some(a + b - c))
});
AddConfig { a, b, c, q_add }
}
pub(in crate::circuit) fn construct(config: AddConfig) -> Self {
Self { config }
}
}
impl AddInstruction<pallas::Base> for AddChip {
fn add(
&self,
mut layouter: impl Layouter<pallas::Base>,
a: &AssignedCell<pallas::Base, pallas::Base>,
b: &AssignedCell<pallas::Base, pallas::Base>,
) -> Result<AssignedCell<pallas::Base, pallas::Base>, plonk::Error> {
layouter.assign_region(
|| "c = a + b",
|mut region| {
self.config.q_add.enable(&mut region, 0)?;
a.copy_advice(|| "copy a", &mut region, self.config.a, 0)?;
b.copy_advice(|| "copy b", &mut region, self.config.b, 0)?;
let scalar_val = a.value().zip(b.value()).map(|(a, b)| a + b);
region.assign_advice(
|| "c",
self.config.c,
0,
|| scalar_val.ok_or(plonk::Error::Synthesis),
)
},
)
}
}