From 74792f9324e317020a11d265d7379aca7ca99f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:37:10 -0500 Subject: [PATCH] add a hashchain example (Nova Forward port) (#339) * add a hashchain example (#311) * add a hashchain example * update crate version * fix clippy; fmt * Fix typo in examples/hashchain.rs Co-authored-by: tchataigner --------- Co-authored-by: Srinath Setty Co-authored-by: tchataigner --- examples/hashchain.rs | 227 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 examples/hashchain.rs diff --git a/examples/hashchain.rs b/examples/hashchain.rs new file mode 100644 index 000000000..746168bb5 --- /dev/null +++ b/examples/hashchain.rs @@ -0,0 +1,227 @@ +//! This example proves the knowledge of preimage to a hash chain tail, with a configurable number of elements per hash chain node. +//! The output of each step tracks the current tail of the hash chain +use arecibo::{ + provider::{Bn256EngineKZG, GrumpkinEngine}, + traits::{ + circuit::{StepCircuit, TrivialCircuit}, + snark::RelaxedR1CSSNARKTrait, + Engine, Group, + }, + CompressedSNARK, PublicParams, RecursiveSNARK, +}; +use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; +use ff::Field; +use flate2::{write::ZlibEncoder, Compression}; +use generic_array::typenum::U24; +use halo2curves::bn256::Bn256; +use neptune::{ + circuit2::Elt, + sponge::{ + api::{IOPattern, SpongeAPI, SpongeOp}, + circuit::SpongeCircuit, + vanilla::{Mode::Simplex, Sponge, SpongeTrait}, + }, + Strength, +}; +use std::time::Instant; + +type E1 = Bn256EngineKZG; +type E2 = GrumpkinEngine; +type EE1 = arecibo::provider::hyperkzg::EvaluationEngine; +type EE2 = arecibo::provider::ipa_pc::EvaluationEngine; +type S1 = arecibo::spartan::snark::RelaxedR1CSSNARK; // non-preprocessing SNARK +type S2 = arecibo::spartan::snark::RelaxedR1CSSNARK; // non-preprocessing SNARK + +#[derive(Clone, Debug)] +struct HashChainCircuit { + num_elts_per_step: usize, + x_i: Vec, +} + +impl HashChainCircuit { + // produces a preimage to be hashed + fn new(num_elts_per_step: usize) -> Self { + let mut rng = rand::thread_rng(); + let x_i = (0..num_elts_per_step) + .map(|_| G::Scalar::random(&mut rng)) + .collect::>(); + + Self { + num_elts_per_step, + x_i, + } + } +} + +impl StepCircuit for HashChainCircuit { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z_in: &[AllocatedNum], + ) -> Result>, SynthesisError> { + // z_in provides the running digest + assert_eq!(z_in.len(), 1); + + // allocate x_i + let x_i = (0..self.num_elts_per_step) + .map(|i| AllocatedNum::alloc(cs.namespace(|| format!("x_{}", i)), || Ok(self.x_i[i]))) + .collect::, _>>()?; + + // concatenate z_in and x_i + let mut m = z_in.to_vec(); + m.extend(x_i); + + let elt = m + .iter() + .map(|x| Elt::Allocated(x.clone())) + .collect::>(); + + let num_absorbs = 1 + self.num_elts_per_step as u32; + + let parameter = IOPattern(vec![SpongeOp::Absorb(num_absorbs), SpongeOp::Squeeze(1u32)]); + + let pc = Sponge::::api_constants(Strength::Standard); + let mut ns = cs.namespace(|| "ns"); + + let z_out = { + let mut sponge = SpongeCircuit::new_with_constants(&pc, Simplex); + let acc = &mut ns; + + sponge.start(parameter, None, acc); + neptune::sponge::api::SpongeAPI::absorb(&mut sponge, num_absorbs, &elt, acc); + + let output = neptune::sponge::api::SpongeAPI::squeeze(&mut sponge, 1, acc); + sponge.finish(acc).unwrap(); + Elt::ensure_allocated(&output[0], &mut ns.namespace(|| "ensure allocated"), true)? + }; + + Ok(vec![z_out]) + } +} + +/// cargo run --release --example hashchain +fn main() { + println!("========================================================="); + println!("Nova-based hashchain example"); + println!("========================================================="); + + let num_steps = 10; + for num_elts_per_step in [1024, 2048, 4096] { + // number of instances of AND per Nova's recursive step + let circuit_primary = HashChainCircuit::<::GE>::new(num_elts_per_step); + let circuit_secondary = TrivialCircuit::default(); + + // produce public parameters + let start = Instant::now(); + println!("Producing public parameters..."); + let pp = PublicParams::::setup( + &circuit_primary, + &circuit_secondary, + &*S1::ck_floor(), + &*S2::ck_floor(), + ) + .unwrap(); + println!("PublicParams::setup, took {:?} ", start.elapsed()); + + println!( + "Number of constraints per step (primary circuit): {}", + pp.num_constraints().0 + ); + println!( + "Number of constraints per step (secondary circuit): {}", + pp.num_constraints().1 + ); + + println!( + "Number of variables per step (primary circuit): {}", + pp.num_variables().0 + ); + println!( + "Number of variables per step (secondary circuit): {}", + pp.num_variables().1 + ); + + // produce non-deterministic advice + type C1 = HashChainCircuit<::GE>; + + let circuits = (0..num_steps) + .map(|_| C1::new(num_elts_per_step)) + .collect::>(); + + // produce a recursive SNARK + println!( + "Generating a RecursiveSNARK with {num_elts_per_step} field elements per hashchain node..." + ); + let mut recursive_snark: RecursiveSNARK = RecursiveSNARK::::new( + &pp, + &circuits[0], + &circuit_secondary, + &[::Scalar::zero()], + &[::Scalar::zero()], + ) + .unwrap(); + + for (i, circuit_primary) in circuits.iter().enumerate() { + let start = Instant::now(); + let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + + println!("RecursiveSNARK::prove {} : took {:?} ", i, start.elapsed()); + } + + // verify the recursive SNARK + println!("Verifying a RecursiveSNARK..."); + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ZERO], + &[::Scalar::ZERO], + ); + println!("RecursiveSNARK::verify: {:?}", res.is_ok(),); + assert!(res.is_ok()); + + // produce a compressed SNARK + println!("Generating a CompressedSNARK using Spartan with HyperKZG..."); + let (pk, vk) = CompressedSNARK::<_, S1, S2>::setup(&pp).unwrap(); + + let start = Instant::now(); + + let res = CompressedSNARK::<_, S1, S2>::prove(&pp, &pk, &recursive_snark); + println!( + "CompressedSNARK::prove: {:?}, took {:?}", + res.is_ok(), + start.elapsed() + ); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); + bincode::serialize_into(&mut encoder, &compressed_snark).unwrap(); + let compressed_snark_encoded = encoder.finish().unwrap(); + println!( + "CompressedSNARK::len {:?} bytes", + compressed_snark_encoded.len() + ); + + // verify the compressed SNARK + println!("Verifying a CompressedSNARK..."); + let start = Instant::now(); + let res = compressed_snark.verify( + &vk, + num_steps, + &[::Scalar::ZERO], + &[::Scalar::ZERO], + ); + println!( + "CompressedSNARK::verify: {:?}, took {:?}", + res.is_ok(), + start.elapsed() + ); + assert!(res.is_ok()); + println!("========================================================="); + } +}