use ff::Field; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, pasta::Fp, plonk::{ create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, ConstraintSystem, Error, SingleVerifier, }, poly::commitment::Params, transcript::{Blake2bRead, Blake2bWrite, Challenge255}, }; use pasta_curves::{pallas, vesta}; use halo2_gadgets::{ poseidon::{Hash, Pow5Chip, Pow5Config}, 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; L]>, // 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; L], 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]); region.assign_advice( || format!("load message_{}", i), config.input[i], 0, || value.ok_or(Error::Synthesis), ) }; let message: Result, Error> = (0..L).map(message_word).collect(); Ok(message?.try_into().unwrap()) }, )?; let hasher = Hash::<_, _, S, ConstantLength, WIDTH, RATE>::init( chip, layouter.namespace(|| "init"), )?; 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::Synthesis), )?; region.constrain_equal(output.cell(), expected_var.cell()) }, ) } } #[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 mut rng = OsRng; let message = (0..L) .map(|_| pallas::Base::random(rng)) .collect::>() .try_into() .unwrap(); let output = poseidon::Hash::<_, S, ConstantLength, WIDTH, RATE>::init().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 rng, &mut transcript) .expect("proof generation should not fail") }) }); // Create a proof let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); create_proof(¶ms, &pk, &[circuit], &[&[]], &mut rng, &mut transcript) .expect("proof generation should not fail"); let proof = transcript.finalize(); c.bench_function(&verifier_name, |b| { b.iter(|| { let strategy = SingleVerifier::new(¶ms); let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); assert!(verify_proof(¶ms, pk.get_vk(), strategy, &[&[]], &mut transcript).is_ok()); }); }); } fn criterion_benchmark(c: &mut Criterion) { 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); criterion_main!(benches);