From b63c8685914824035e0285f460d141f3d950f53c Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 3 Nov 2021 14:12:24 +0100 Subject: [PATCH 1/4] poseidon: Make Spec trait methods not take (&self) parameter. --- benches/primitives.rs | 2 +- src/circuit.rs | 3 +- src/circuit/gadget/poseidon/pow5.rs | 15 +++++----- src/primitives/poseidon.rs | 21 +++++-------- src/primitives/poseidon/p128pow5t3.rs | 43 +++++++++++---------------- src/spec.rs | 3 +- 6 files changed, 38 insertions(+), 49 deletions(-) diff --git a/benches/primitives.rs b/benches/primitives.rs index df6747b5..4d141c69 100644 --- a/benches/primitives.rs +++ b/benches/primitives.rs @@ -21,7 +21,7 @@ fn bench_primitives(c: &mut Criterion) { let message = [pallas::Base::random(rng), pallas::Base::random(rng)]; group.bench_function("2-to-1", |b| { - b.iter(|| poseidon::Hash::init(P128Pow5T3, ConstantLength).hash(message)) + b.iter(|| poseidon::Hash::<_, P128Pow5T3, _, 3, 2>::init(ConstantLength).hash(message)) }); } diff --git a/src/circuit.rs b/src/circuit.rs index f1b741dd..001a6d34 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -239,9 +239,8 @@ impl plonk::Circuit for Circuit { let ecc_config = EccChip::configure(meta, advices, lagrange_coeffs, range_check.clone()); // Configuration for the Poseidon hash. - let poseidon_config = PoseidonChip::configure( + let poseidon_config = PoseidonChip::configure::( meta, - poseidon::P128Pow5T3, // We place the state columns after the partial_sbox column so that the // pad-and-add region can be layed out more efficiently. advices[6..9].try_into().unwrap(), diff --git a/src/circuit/gadget/poseidon/pow5.rs b/src/circuit/gadget/poseidon/pow5.rs index d84fb392..f9f812ce 100644 --- a/src/circuit/gadget/poseidon/pow5.rs +++ b/src/circuit/gadget/poseidon/pow5.rs @@ -52,7 +52,6 @@ impl Pow5Chip>( meta: &mut ConstraintSystem, - spec: S, state: [Column; WIDTH], partial_sbox: Column, rc_a: [Column; WIDTH], @@ -65,7 +64,7 @@ impl Pow5Chip(meta, state, partial_sbox, rc_a, rc_b) } fn synthesize( @@ -685,7 +684,7 @@ mod tests { // For the purpose of this test, compute the real final state inline. let mut expected_final_state = [Fp::zero(), Fp::one(), Fp::from_u64(2)]; - let (round_constants, mds, _) = OrchardNullifier.constants(); + let (round_constants, mds, _) = OrchardNullifier::constants(); poseidon::permute::<_, OrchardNullifier, WIDTH, RATE>( &mut expected_final_state, &mds, @@ -758,7 +757,7 @@ mod tests { meta.enable_constant(rc_b[0]); - Pow5Chip::configure(meta, OrchardNullifier, state, partial_sbox, rc_a, rc_b) + Pow5Chip::configure::(meta, state, partial_sbox, rc_a, rc_b) } fn synthesize( @@ -811,7 +810,8 @@ mod tests { #[test] fn poseidon_hash() { let message = [Fp::rand(), Fp::rand()]; - let output = poseidon::Hash::init(OrchardNullifier, ConstantLength::<2>).hash(message); + let output = + poseidon::Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength::<2>).hash(message); let k = 6; let circuit = HashCircuit { @@ -829,7 +829,8 @@ mod tests { pallas::Base::from_repr(tv.input[0]).unwrap(), pallas::Base::from_repr(tv.input[1]).unwrap(), ]; - let output = poseidon::Hash::init(OrchardNullifier, ConstantLength).hash(message); + let output = + poseidon::Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength).hash(message); let k = 6; let circuit = HashCircuit { diff --git a/src/primitives/poseidon.rs b/src/primitives/poseidon.rs index f2892d1b..281614b0 100644 --- a/src/primitives/poseidon.rs +++ b/src/primitives/poseidon.rs @@ -47,10 +47,10 @@ pub trait Spec { /// /// This is used by the default implementation of [`Spec::constants`]. If you are /// hard-coding the constants, you may leave this unimplemented. - fn secure_mds(&self) -> usize; + fn secure_mds() -> usize; /// Generates `(round_constants, mds, mds^-1)` corresponding to this specification. - fn constants(&self) -> (Vec<[F; T]>, Mds, Mds) { + fn constants() -> (Vec<[F; T]>, Mds, Mds) { let r_f = Self::full_rounds(); let r_p = Self::partial_rounds(); @@ -69,7 +69,7 @@ pub trait Spec { }) .collect(); - let (mds, mds_inv) = mds::generate_mds::(&mut grain, self.secure_mds()); + let (mds, mds_inv) = mds::generate_mds::(&mut grain, Self::secure_mds()); (round_constants, mds, mds_inv) } @@ -167,11 +167,10 @@ pub(crate) struct Duplex, const T: usize, const impl, const T: usize, const RATE: usize> Duplex { /// Constructs a new duplex sponge for the given Poseidon specification. pub(crate) fn new( - spec: S, initial_capacity_element: F, pad_and_add: Box, &SpongeState)>, ) -> Self { - let (round_constants, mds_matrix, _) = spec.constants(); + let (round_constants, mds_matrix, _) = S::constants(); let input = [None; RATE]; let mut state = [F::zero(); T]; @@ -336,13 +335,9 @@ impl< > Hash { /// Initializes a new hasher. - pub fn init(spec: S, domain: D) -> Self { + pub fn init(domain: D) -> Self { Hash { - duplex: Duplex::new( - spec, - domain.initial_capacity_element(), - domain.pad_and_add(), - ), + duplex: Duplex::new(domain.initial_capacity_element(), domain.pad_and_add()), domain, } } @@ -371,9 +366,9 @@ mod tests { fn orchard_spec_equivalence() { let message = [pallas::Base::from_u64(6), pallas::Base::from_u64(42)]; - let (round_constants, mds, _) = OrchardNullifier.constants(); + let (round_constants, mds, _) = OrchardNullifier::constants(); - let hasher = Hash::init(OrchardNullifier, ConstantLength); + let hasher = Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength); let result = hasher.hash(message); // The result should be equivalent to just directly applying the permutation and diff --git a/src/primitives/poseidon/p128pow5t3.rs b/src/primitives/poseidon/p128pow5t3.rs index 31c7217c..c33a9ca6 100644 --- a/src/primitives/poseidon/p128pow5t3.rs +++ b/src/primitives/poseidon/p128pow5t3.rs @@ -25,11 +25,11 @@ impl Spec for P128Pow5T3 { val.pow_vartime(&[5]) } - fn secure_mds(&self) -> usize { + fn secure_mds() -> usize { unimplemented!() } - fn constants(&self) -> (Vec<[Fp; 3]>, Mds, Mds) { + fn constants() -> (Vec<[Fp; 3]>, Mds, Mds) { ( super::fp::ROUND_CONSTANTS[..].to_vec(), super::fp::MDS, @@ -51,11 +51,11 @@ impl Spec for P128Pow5T3 { val.pow_vartime(&[5]) } - fn secure_mds(&self) -> usize { + fn secure_mds() -> usize { unimplemented!() } - fn constants(&self) -> (Vec<[Fq; 3]>, Mds, Mds) { + fn constants() -> (Vec<[Fq; 3]>, Mds, Mds) { ( super::fq::ROUND_CONSTANTS[..].to_vec(), super::fq::MDS, @@ -80,21 +80,15 @@ mod tests { /// The same Poseidon specification as poseidon::P128Pow5T3, but constructed /// such that its constants will be generated at runtime. #[derive(Debug)] - pub struct P128Pow5T3Gen { - secure_mds: usize, - _field: PhantomData, - } + pub struct P128Pow5T3Gen(PhantomData); - impl P128Pow5T3Gen { - pub fn new(secure_mds: usize) -> Self { - P128Pow5T3Gen { - secure_mds, - _field: PhantomData::default(), - } + impl P128Pow5T3Gen { + pub fn new() -> Self { + P128Pow5T3Gen(PhantomData::default()) } } - impl Spec for P128Pow5T3Gen { + impl Spec for P128Pow5T3Gen { fn full_rounds() -> usize { 8 } @@ -107,8 +101,8 @@ mod tests { val.pow_vartime(&[5]) } - fn secure_mds(&self) -> usize { - self.secure_mds + fn secure_mds() -> usize { + SECURE_MDS } } @@ -119,8 +113,7 @@ mod tests { expected_mds: [[F; 3]; 3], expected_mds_inv: [[F; 3]; 3], ) { - let poseidon = P128Pow5T3Gen::::new(0); - let (round_constants, mds, mds_inv) = poseidon.constants(); + let (round_constants, mds, mds_inv) = P128Pow5T3Gen::::constants(); for (actual, expected) in round_constants .iter() @@ -196,7 +189,7 @@ mod tests { ]), ]; - permute::, 3, 2>(&mut input, &fp::MDS, &fp::ROUND_CONSTANTS); + permute::, 3, 2>(&mut input, &fp::MDS, &fp::ROUND_CONSTANTS); assert_eq!(input, expected_output); } @@ -247,7 +240,7 @@ mod tests { ]), ]; - permute::, 3, 2>(&mut input, &fq::MDS, &fq::ROUND_CONSTANTS); + permute::, 3, 2>(&mut input, &fq::MDS, &fq::ROUND_CONSTANTS); assert_eq!(input, expected_output); } } @@ -255,7 +248,7 @@ mod tests { #[test] fn permute_test_vectors() { { - let (round_constants, mds, _) = super::P128Pow5T3.constants(); + let (round_constants, mds, _) = super::P128Pow5T3::constants(); for tv in crate::primitives::poseidon::test_vectors::fp::permute() { let mut state = [ @@ -273,7 +266,7 @@ mod tests { } { - let (round_constants, mds, _) = super::P128Pow5T3.constants(); + let (round_constants, mds, _) = super::P128Pow5T3::constants(); for tv in crate::primitives::poseidon::test_vectors::fq::permute() { let mut state = [ @@ -299,7 +292,7 @@ mod tests { Fp::from_repr(tv.input[1]).unwrap(), ]; - let result = Hash::init(super::P128Pow5T3, ConstantLength).hash(message); + let result = Hash::<_, super::P128Pow5T3, _, 3, 2>::init(ConstantLength).hash(message); assert_eq!(result.to_repr(), tv.output); } @@ -310,7 +303,7 @@ mod tests { Fq::from_repr(tv.input[1]).unwrap(), ]; - let result = Hash::init(super::P128Pow5T3, ConstantLength).hash(message); + let result = Hash::<_, super::P128Pow5T3, _, 3, 2>::init(ConstantLength).hash(message); assert_eq!(result.to_repr(), tv.output); } diff --git a/src/spec.rs b/src/spec.rs index ddd394c6..7d1cd698 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -212,7 +212,8 @@ pub(crate) fn diversify_hash(d: &[u8; 11]) -> NonIdentityPallasPoint { /// /// [concreteprfs]: https://zips.z.cash/protocol/nu5.pdf#concreteprfs pub(crate) fn prf_nf(nk: pallas::Base, rho: pallas::Base) -> pallas::Base { - poseidon::Hash::init(poseidon::P128Pow5T3, poseidon::ConstantLength).hash([nk, rho]) + poseidon::Hash::<_, poseidon::P128Pow5T3, _, 3, 2>::init(poseidon::ConstantLength) + .hash([nk, rho]) } /// Defined in [Zcash Protocol Spec ยง 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement]. From 9b765565038b8661b62cb7d72d797eb1ff420121 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 3 Nov 2021 14:37:02 +0100 Subject: [PATCH 2/4] poseidon: Make gadget tests generic over WIDTH, RATE --- src/circuit/gadget/poseidon/pow5.rs | 128 +++++++++++++++------------- src/primitives/poseidon.rs | 2 +- 2 files changed, 69 insertions(+), 61 deletions(-) diff --git a/src/circuit/gadget/poseidon/pow5.rs b/src/circuit/gadget/poseidon/pow5.rs index f9f812ce..3c1525a9 100644 --- a/src/circuit/gadget/poseidon/pow5.rs +++ b/src/circuit/gadget/poseidon/pow5.rs @@ -615,40 +615,37 @@ mod tests { }, primitives::poseidon::{self, ConstantLength, P128Pow5T3 as OrchardNullifier, Spec}, }; + use std::convert::TryInto; + use std::marker::PhantomData; - const WIDTH: usize = 3; - const RATE: usize = 2; + struct PermuteCircuit, const WIDTH: usize, const RATE: usize>( + PhantomData, + ); - struct PermuteCircuit {} - - impl Circuit for PermuteCircuit { + impl, const WIDTH: usize, const RATE: usize> Circuit + for PermuteCircuit + { type Config = Pow5Config; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { - PermuteCircuit {} + PermuteCircuit::(PhantomData) } fn configure(meta: &mut ConstraintSystem) -> Pow5Config { - let state = [ - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - ]; + let state = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); let partial_sbox = meta.advice_column(); - let rc_a = [ - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - ]; - let rc_b = [ - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - ]; + let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); - Pow5Chip::configure::(meta, state, partial_sbox, rc_a, rc_b) + Pow5Chip::configure::( + meta, + state.try_into().unwrap(), + partial_sbox, + rc_a.try_into().unwrap(), + rc_b.try_into().unwrap(), + ) } fn synthesize( @@ -659,7 +656,7 @@ mod tests { let initial_state = layouter.assign_region( || "prepare initial state", |mut region| { - let mut state_word = |i: usize| -> Result<_, Error> { + let state_word = |i: usize| { let value = Some(Fp::from(i as u64)); let var = region.assign_advice( || format!("load state_{}", i), @@ -670,22 +667,27 @@ mod tests { Ok(StateWord { var, value }) }; - Ok([state_word(0)?, state_word(1)?, state_word(2)?]) + let state: Result, Error> = (0..WIDTH).map(state_word).collect(); + Ok(state?.try_into().unwrap()) }, )?; let chip = Pow5Chip::construct(config.clone()); let final_state = as PoseidonInstructions< Fp, - OrchardNullifier, + S, WIDTH, - 2, + RATE, >>::permute(&chip, &mut layouter, &initial_state)?; // For the purpose of this test, compute the real final state inline. - let mut expected_final_state = [Fp::zero(), Fp::one(), Fp::from_u64(2)]; - let (round_constants, mds, _) = OrchardNullifier::constants(); - poseidon::permute::<_, OrchardNullifier, WIDTH, RATE>( + let mut expected_final_state = (0..WIDTH) + .map(|idx| Fp::from_u64(idx as u64)) + .collect::>() + .try_into() + .unwrap(); + let (round_constants, mds, _) = S::constants(); + poseidon::permute::<_, S, WIDTH, RATE>( &mut expected_final_state, &mds, &round_constants, @@ -704,9 +706,11 @@ mod tests { region.constrain_equal(final_state[i].var, var) }; - final_state_word(0)?; - final_state_word(1)?; - final_state_word(2) + for i in 0..(WIDTH - 1) { + final_state_word(i)?; + } + + final_state_word(WIDTH - 1) }, ) } @@ -715,49 +719,49 @@ mod tests { #[test] fn poseidon_permute() { let k = 6; - let circuit = PermuteCircuit {}; + let circuit = PermuteCircuit::(PhantomData); let prover = MockProver::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())) } - #[derive(Default)] - struct HashCircuit { + struct HashCircuit, const WIDTH: usize, const RATE: usize> { message: Option<[Fp; 2]>, // For the purpose of this test, witness the result. // TODO: Move this into an instance column. output: Option, + _spec: PhantomData, } - impl Circuit for HashCircuit { + impl, const WIDTH: usize, const RATE: usize> Circuit + for HashCircuit + { type Config = Pow5Config; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { - Self::default() + Self { + message: None, + output: None, + _spec: PhantomData, + } } fn configure(meta: &mut ConstraintSystem) -> Pow5Config { - let state = [ - meta.advice_column(), - meta.advice_column(), - meta.advice_column(), - ]; + let state = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); let partial_sbox = meta.advice_column(); - let rc_a = [ - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - ]; - let rc_b = [ - meta.fixed_column(), - meta.fixed_column(), - meta.fixed_column(), - ]; + let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); meta.enable_constant(rc_b[0]); - Pow5Chip::configure::(meta, state, partial_sbox, rc_a, rc_b) + Pow5Chip::configure::( + meta, + state.try_into().unwrap(), + partial_sbox, + rc_a.try_into().unwrap(), + rc_b.try_into().unwrap(), + ) } fn synthesize( @@ -770,7 +774,7 @@ mod tests { let message = layouter.assign_region( || "load message", |mut region| { - let mut message_word = |i: usize| -> Result<_, Error> { + let message_word = |i: usize| { let value = self.message.map(|message_vals| message_vals[i]); let cell = region.assign_advice( || format!("load message_{}", i), @@ -781,14 +785,15 @@ mod tests { Ok(CellValue::new(cell, value)) }; - Ok([message_word(0)?, message_word(1)?]) + let message: Result, Error> = (0..RATE).map(message_word).collect(); + Ok(message?.try_into().unwrap()) }, )?; - let hasher = Hash::<_, _, OrchardNullifier, _, WIDTH, 2>::init( + let hasher = Hash::<_, _, S, _, WIDTH, RATE>::init( chip, layouter.namespace(|| "init"), - ConstantLength::<2>, + ConstantLength::, )?; let output = hasher.hash(layouter.namespace(|| "hash"), message)?; @@ -814,9 +819,10 @@ mod tests { poseidon::Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength::<2>).hash(message); let k = 6; - let circuit = HashCircuit { + let circuit = HashCircuit:: { message: Some(message), output: Some(output), + _spec: PhantomData, }; let prover = MockProver::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())) @@ -833,9 +839,10 @@ mod tests { poseidon::Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength).hash(message); let k = 6; - let circuit = HashCircuit { + let circuit = HashCircuit:: { message: Some(message), output: Some(output), + _spec: PhantomData, }; let prover = MockProver::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())); @@ -853,9 +860,10 @@ mod tests { .titled("Poseidon Chip Layout", ("sans-serif", 60)) .unwrap(); - let circuit = HashCircuit { + let circuit = HashCircuit:: { message: None, output: None, + _spec: PhantomData, }; halo2::dev::CircuitLayout::default() .render(6, &circuit, &root) diff --git a/src/primitives/poseidon.rs b/src/primitives/poseidon.rs index 281614b0..0921fffc 100644 --- a/src/primitives/poseidon.rs +++ b/src/primitives/poseidon.rs @@ -30,7 +30,7 @@ pub(crate) type SpongeState = [Option; RATE]; pub(crate) type Mds = [[F; T]; T]; /// A specification for a Poseidon permutation. -pub trait Spec { +pub trait Spec: fmt::Debug { /// The number of full rounds for this specification. /// /// This must be an even number. From 421891f065a1d84cfa0b6e6fcd246660dc427770 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 4 Nov 2021 02:07:28 +0100 Subject: [PATCH 3/4] Benchmark proof creation and verification for RATE = 2, 8, 11. --- Cargo.toml | 4 + benches/poseidon.rs | 255 ++++++++++++++++++++++++++++ src/circuit.rs | 2 +- src/circuit/gadget.rs | 6 +- src/circuit/gadget/poseidon.rs | 8 +- src/circuit/gadget/poseidon/pow5.rs | 22 ++- src/circuit/gadget/utilities.rs | 13 +- src/primitives/poseidon.rs | 1 + 8 files changed, 299 insertions(+), 12 deletions(-) create mode 100644 benches/poseidon.rs diff --git a/Cargo.toml b/Cargo.toml index d12d0b32..2b38917e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,10 @@ harness = false name = "circuit" harness = false +[[bench]] +name = "poseidon" +harness = false + [profile.release] debug = true diff --git a/benches/poseidon.rs b/benches/poseidon.rs new file mode 100644 index 00000000..3658cfc3 --- /dev/null +++ b/benches/poseidon.rs @@ -0,0 +1,255 @@ +use ff::Field; +use halo2::{ + circuit::{Layouter, SimpleFloorPlanner}, + pasta::Fp, + plonk::{ + create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, + ConstraintSystem, Error, + }, + poly::commitment::Params, + transcript::{Blake2bRead, Blake2bWrite, Challenge255}, +}; +use pasta_curves::{pallas, vesta}; + +use orchard::{ + circuit::gadget::{ + poseidon::{Hash, Pow5Chip, Pow5Config}, + utilities::{CellValue, Var}, + }, + primitives::poseidon::{self, ConstantLength, Spec}, +}; +use std::convert::TryInto; +use std::marker::PhantomData; + +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::rngs::OsRng; + +#[derive(Clone, Copy)] +struct HashCircuit +where + S: Spec + Clone + Copy, +{ + message: Option<[Fp; RATE]>, + // For the purpose of this test, witness the result. + // TODO: Move this into an instance column. + output: Option, + _spec: PhantomData, +} + +#[derive(Debug, Clone)] +struct MyConfig { + input: [Column; RATE], + poseidon_config: Pow5Config, +} + +impl Circuit for HashCircuit +where + S: Spec + Copy + Clone, +{ + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self { + message: None, + output: None, + _spec: PhantomData, + } + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let state = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); + let partial_sbox = meta.advice_column(); + + let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); + + meta.enable_constant(rc_b[0]); + + Self::Config { + input: state[..RATE].try_into().unwrap(), + poseidon_config: Pow5Chip::configure::( + meta, + state.try_into().unwrap(), + partial_sbox, + rc_a.try_into().unwrap(), + rc_b.try_into().unwrap(), + ), + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = Pow5Chip::construct(config.poseidon_config.clone()); + + let message = layouter.assign_region( + || "load message", + |mut region| { + let message_word = |i: usize| { + let value = self.message.map(|message_vals| message_vals[i]); + let cell = region.assign_advice( + || format!("load message_{}", i), + config.input[i], + 0, + || value.ok_or(Error::SynthesisError), + )?; + Ok(CellValue::new(cell, value)) + }; + + let message: Result, Error> = (0..RATE).map(message_word).collect(); + Ok(message?.try_into().unwrap()) + }, + )?; + + let hasher = Hash::<_, _, S, _, WIDTH, RATE>::init( + chip, + layouter.namespace(|| "init"), + ConstantLength::, + )?; + let output = hasher.hash(layouter.namespace(|| "hash"), message)?; + + layouter.assign_region( + || "constrain output", + |mut region| { + let expected_var = region.assign_advice( + || "load output", + config.input[0], + 0, + || self.output.ok_or(Error::SynthesisError), + )?; + region.constrain_equal(output.cell(), expected_var) + }, + ) + } +} + +#[derive(Debug, Clone, Copy)] +struct MySpec; + +impl Spec for MySpec<3, 2> { + fn full_rounds() -> usize { + 8 + } + + fn partial_rounds() -> usize { + 56 + } + + fn sbox(val: Fp) -> Fp { + val.pow_vartime(&[5]) + } + + fn secure_mds() -> usize { + 0 + } +} + +impl Spec for MySpec<9, 8> { + fn full_rounds() -> usize { + 8 + } + + fn partial_rounds() -> usize { + 56 + } + + fn sbox(val: Fp) -> Fp { + val.pow_vartime(&[5]) + } + + fn secure_mds() -> usize { + 0 + } +} + +impl Spec for MySpec<12, 11> { + fn full_rounds() -> usize { + 8 + } + + fn partial_rounds() -> usize { + 56 + } + + fn sbox(val: Fp) -> Fp { + val.pow_vartime(&[5]) + } + + fn secure_mds() -> usize { + 0 + } +} + +const K: u32 = 6; + +fn bench_poseidon(name: &str, c: &mut Criterion) +where + S: Spec + Copy + Clone, +{ + // Initialize the polynomial commitment parameters + let params: Params = Params::new(K); + + let empty_circuit = HashCircuit:: { + message: None, + output: None, + _spec: PhantomData, + }; + + // Initialize the proving key + let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); + let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); + + let prover_name = name.to_string() + "-prover"; + let verifier_name = name.to_string() + "-verifier"; + + let rng = OsRng; + let message = (0..RATE) + .map(|_| pallas::Base::random(rng)) + .collect::>() + .try_into() + .unwrap(); + let output = poseidon::Hash::<_, S, _, WIDTH, RATE>::init(ConstantLength::).hash(message); + + let circuit = HashCircuit:: { + message: Some(message), + output: Some(output), + _spec: PhantomData, + }; + + c.bench_function(&prover_name, |b| { + b.iter(|| { + // Create a proof + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof(¶ms, &pk, &[circuit], &[&[]], &mut transcript) + .expect("proof generation should not fail") + }) + }); + + // Create a proof + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof(¶ms, &pk, &[circuit], &[&[]], &mut transcript) + .expect("proof generation should not fail"); + let proof = transcript.finalize(); + + c.bench_function(&verifier_name, |b| { + b.iter(|| { + let msm = params.empty_msm(); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + let guard = verify_proof(¶ms, pk.get_vk(), msm, &[&[]], &mut transcript).unwrap(); + let msm = guard.clone().use_challenges(); + assert!(msm.eval()); + }); + }); +} + +fn criterion_benchmark(c: &mut Criterion) { + bench_poseidon::, 3, 2>("WIDTH = 3, RATE = 2", c); + bench_poseidon::, 9, 8>("WIDTH = 9, RATE = 8", c); + bench_poseidon::, 12, 11>("WIDTH = 12, RATE = 11", c); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/src/circuit.rs b/src/circuit.rs index 001a6d34..c383b7ef 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -57,7 +57,7 @@ use std::convert::TryInto; use self::gadget::utilities::lookup_range_check::LookupRangeCheckConfig; -pub(crate) mod gadget; +pub mod gadget; /// Size of the Orchard circuit. const K: u32 = 11; diff --git a/src/circuit/gadget.rs b/src/circuit/gadget.rs index b0d8b247..4bf51c93 100644 --- a/src/circuit/gadget.rs +++ b/src/circuit/gadget.rs @@ -1,3 +1,5 @@ +//! Gadgets used in the Orchard circuit. + use pasta_curves::pallas; use ecc::chip::EccChip; @@ -5,9 +7,9 @@ use poseidon::Pow5Chip as PoseidonChip; use sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip}; pub(crate) mod ecc; -pub(crate) mod poseidon; +pub mod poseidon; pub(crate) mod sinsemilla; -pub(crate) mod utilities; +pub mod utilities; impl super::Config { pub(super) fn ecc_chip(&self) -> EccChip { diff --git a/src/circuit/gadget/poseidon.rs b/src/circuit/gadget/poseidon.rs index 8a891ccf..96e5917d 100644 --- a/src/circuit/gadget/poseidon.rs +++ b/src/circuit/gadget/poseidon.rs @@ -80,11 +80,13 @@ impl< const RATE: usize, > Word { - pub(crate) fn inner(&self) -> PoseidonChip::Word { + /// The word contained in this gadget. + pub fn inner(&self) -> PoseidonChip::Word { self.inner } - pub(crate) fn from_inner(inner: PoseidonChip::Word) -> Self { + /// Construct a [`Word`] gadget from the inner word. + pub fn from_inner(inner: PoseidonChip::Word) -> Self { Self { inner } } } @@ -109,6 +111,7 @@ fn poseidon_duplex< } /// A Poseidon duplex sponge. +#[derive(Debug)] pub struct Duplex< F: FieldExt, PoseidonChip: PoseidonDuplexInstructions, @@ -210,6 +213,7 @@ impl< } /// A Poseidon hash function, built around a duplex sponge. +#[derive(Debug)] pub struct Hash< F: FieldExt, PoseidonChip: PoseidonDuplexInstructions, diff --git a/src/circuit/gadget/poseidon/pow5.rs b/src/circuit/gadget/poseidon/pow5.rs index 3c1525a9..93310650 100644 --- a/src/circuit/gadget/poseidon/pow5.rs +++ b/src/circuit/gadget/poseidon/pow5.rs @@ -199,6 +199,7 @@ impl Pow5Chip) -> Self { Pow5Chip { config } } @@ -401,18 +402,13 @@ impl, const WIDTH: usize, const RATE: usize } } +/// A word in the Poseidon state. #[derive(Clone, Copy, Debug)] pub struct StateWord { var: Cell, value: Option, } -impl StateWord { - pub fn new(var: Cell, value: Option) -> Self { - Self { var, value } - } -} - impl From> for CellValue { fn from(state_word: StateWord) -> CellValue { CellValue::new(state_word.var, state_word.value) @@ -425,6 +421,20 @@ impl From> for StateWord { } } +impl Var for StateWord { + fn new(var: Cell, value: Option) -> Self { + Self { var, value } + } + + fn cell(&self) -> Cell { + self.var + } + + fn value(&self) -> Option { + self.value + } +} + #[derive(Debug)] struct Pow5State([StateWord; WIDTH]); diff --git a/src/circuit/gadget/utilities.rs b/src/circuit/gadget/utilities.rs index 79fc4c48..743bd4ea 100644 --- a/src/circuit/gadget/utilities.rs +++ b/src/circuit/gadget/utilities.rs @@ -1,3 +1,5 @@ +//! Utility gadgets. + use ff::PrimeFieldBits; use halo2::{ circuit::{Cell, Layouter, Region}, @@ -17,9 +19,15 @@ pub struct CellValue { value: Option, } +/// Trait for a variable in the circuit. pub trait Var: Copy + Clone + std::fmt::Debug { + /// Construct a new variable. fn new(cell: Cell, value: Option) -> Self; + + /// The cell at which this variable was allocated. fn cell(&self) -> Cell; + + /// The value allocated to this variable. fn value(&self) -> Option; } @@ -37,9 +45,12 @@ impl Var for CellValue { } } +/// Trait for utilities used across circuits. pub trait UtilitiesInstructions { + /// Variable in the circuit. type Var: Var; + /// Load a variable. fn load_private( &self, mut layouter: impl Layouter, @@ -88,7 +99,7 @@ where Ok(CellValue::new(cell, copy.value)) } -pub fn transpose_option_array( +pub(crate) fn transpose_option_array( option_array: Option<[T; LEN]>, ) -> [Option; LEN] { let mut ret = [None; LEN]; diff --git a/src/primitives/poseidon.rs b/src/primitives/poseidon.rs index 0921fffc..8078fac2 100644 --- a/src/primitives/poseidon.rs +++ b/src/primitives/poseidon.rs @@ -141,6 +141,7 @@ fn poseidon_duplex, const T: usize, const RATE: output } +#[derive(Debug)] pub(crate) enum Sponge { Absorbing(SpongeState), Squeezing(SpongeState), From b02628d2632b7feed6944285333a5ccf610436b0 Mon Sep 17 00:00:00 2001 From: ying tong Date: Tue, 30 Nov 2021 20:08:56 -0500 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: str4d --- benches/poseidon.rs | 41 ++++++++++++++++------------- src/circuit/gadget/poseidon/pow5.rs | 27 +++++++++++-------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/benches/poseidon.rs b/benches/poseidon.rs index 3658cfc3..4f40d9a6 100644 --- a/benches/poseidon.rs +++ b/benches/poseidon.rs @@ -25,11 +25,11 @@ use criterion::{criterion_group, criterion_main, Criterion}; use rand::rngs::OsRng; #[derive(Clone, Copy)] -struct HashCircuit +struct HashCircuit where S: Spec + Clone + Copy, { - message: Option<[Fp; RATE]>, + message: Option<[Fp; L]>, // For the purpose of this test, witness the result. // TODO: Move this into an instance column. output: Option, @@ -37,16 +37,17 @@ where } #[derive(Debug, Clone)] -struct MyConfig { - input: [Column; RATE], +struct MyConfig { + input: [Column; L], poseidon_config: Pow5Config, } -impl Circuit for HashCircuit +impl Circuit + for HashCircuit where S: Spec + Copy + Clone, { - type Config = MyConfig; + type Config = MyConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -94,12 +95,12 @@ where || format!("load message_{}", i), config.input[i], 0, - || value.ok_or(Error::SynthesisError), + || value.ok_or(Error::Synthesis), )?; Ok(CellValue::new(cell, value)) }; - let message: Result, Error> = (0..RATE).map(message_word).collect(); + let message: Result, Error> = (0..L).map(message_word).collect(); Ok(message?.try_into().unwrap()) }, )?; @@ -107,7 +108,7 @@ where let hasher = Hash::<_, _, S, _, WIDTH, RATE>::init( chip, layouter.namespace(|| "init"), - ConstantLength::, + ConstantLength::, )?; let output = hasher.hash(layouter.namespace(|| "hash"), message)?; @@ -118,7 +119,7 @@ where || "load output", config.input[0], 0, - || self.output.ok_or(Error::SynthesisError), + || self.output.ok_or(Error::Synthesis), )?; region.constrain_equal(output.cell(), expected_var) }, @@ -185,14 +186,16 @@ impl Spec for MySpec<12, 11> { const K: u32 = 6; -fn bench_poseidon(name: &str, c: &mut Criterion) -where +fn bench_poseidon( + name: &str, + c: &mut Criterion, +) where S: Spec + Copy + Clone, { // Initialize the polynomial commitment parameters let params: Params = Params::new(K); - let empty_circuit = HashCircuit:: { + let empty_circuit = HashCircuit:: { message: None, output: None, _spec: PhantomData, @@ -206,14 +209,14 @@ where let verifier_name = name.to_string() + "-verifier"; let rng = OsRng; - let message = (0..RATE) + let message = (0..L) .map(|_| pallas::Base::random(rng)) .collect::>() .try_into() .unwrap(); - let output = poseidon::Hash::<_, S, _, WIDTH, RATE>::init(ConstantLength::).hash(message); + let output = poseidon::Hash::<_, S, _, WIDTH, RATE>::init(ConstantLength::).hash(message); - let circuit = HashCircuit:: { + let circuit = HashCircuit:: { message: Some(message), output: Some(output), _spec: PhantomData, @@ -246,9 +249,9 @@ where } fn criterion_benchmark(c: &mut Criterion) { - bench_poseidon::, 3, 2>("WIDTH = 3, RATE = 2", c); - bench_poseidon::, 9, 8>("WIDTH = 9, RATE = 8", c); - bench_poseidon::, 12, 11>("WIDTH = 12, RATE = 11", c); + bench_poseidon::, 3, 2, 2>("WIDTH = 3, RATE = 2", c); + bench_poseidon::, 9, 8, 8>("WIDTH = 9, RATE = 8", c); + bench_poseidon::, 12, 11, 11>("WIDTH = 12, RATE = 11", c); } criterion_group!(benches, criterion_benchmark); diff --git a/src/circuit/gadget/poseidon/pow5.rs b/src/circuit/gadget/poseidon/pow5.rs index 93310650..f6829791 100644 --- a/src/circuit/gadget/poseidon/pow5.rs +++ b/src/circuit/gadget/poseidon/pow5.rs @@ -716,11 +716,11 @@ mod tests { region.constrain_equal(final_state[i].var, var) }; - for i in 0..(WIDTH - 1) { + for i in 0..(WIDTH) { final_state_word(i)?; } - final_state_word(WIDTH - 1) + Ok(()) }, ) } @@ -734,16 +734,21 @@ mod tests { assert_eq!(prover.verify(), Ok(())) } - struct HashCircuit, const WIDTH: usize, const RATE: usize> { - message: Option<[Fp; 2]>, + struct HashCircuit< + S: Spec, + const WIDTH: usize, + const RATE: usize, + const L: usize, + > { + message: Option<[Fp; L]>, // For the purpose of this test, witness the result. // TODO: Move this into an instance column. output: Option, _spec: PhantomData, } - impl, const WIDTH: usize, const RATE: usize> Circuit - for HashCircuit + impl, const WIDTH: usize, const RATE: usize, const L: usize> + Circuit for HashCircuit { type Config = Pow5Config; type FloorPlanner = SimpleFloorPlanner; @@ -795,7 +800,7 @@ mod tests { Ok(CellValue::new(cell, value)) }; - let message: Result, Error> = (0..RATE).map(message_word).collect(); + let message: Result, Error> = (0..L).map(message_word).collect(); Ok(message?.try_into().unwrap()) }, )?; @@ -803,7 +808,7 @@ mod tests { let hasher = Hash::<_, _, S, _, WIDTH, RATE>::init( chip, layouter.namespace(|| "init"), - ConstantLength::, + ConstantLength::, )?; let output = hasher.hash(layouter.namespace(|| "hash"), message)?; @@ -829,7 +834,7 @@ mod tests { poseidon::Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength::<2>).hash(message); let k = 6; - let circuit = HashCircuit:: { + let circuit = HashCircuit:: { message: Some(message), output: Some(output), _spec: PhantomData, @@ -849,7 +854,7 @@ mod tests { poseidon::Hash::<_, OrchardNullifier, _, 3, 2>::init(ConstantLength).hash(message); let k = 6; - let circuit = HashCircuit:: { + let circuit = HashCircuit:: { message: Some(message), output: Some(output), _spec: PhantomData, @@ -870,7 +875,7 @@ mod tests { .titled("Poseidon Chip Layout", ("sans-serif", 60)) .unwrap(); - let circuit = HashCircuit:: { + let circuit = HashCircuit:: { message: None, output: None, _spec: PhantomData,