Skip to content

Commit

Permalink
add a hashchain example (Nova Forward port) (#339)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

---------

Co-authored-by: Srinath Setty <[email protected]>
Co-authored-by: tchataigner <[email protected]>
  • Loading branch information
3 people authored Feb 22, 2024
1 parent 346b9cf commit 74792f9
Showing 1 changed file with 227 additions and 0 deletions.
227 changes: 227 additions & 0 deletions examples/hashchain.rs
Original file line number Diff line number Diff line change
@@ -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<Bn256, E1>;
type EE2 = arecibo::provider::ipa_pc::EvaluationEngine<E2>;
type S1 = arecibo::spartan::snark::RelaxedR1CSSNARK<E1, EE1>; // non-preprocessing SNARK
type S2 = arecibo::spartan::snark::RelaxedR1CSSNARK<E2, EE2>; // non-preprocessing SNARK

#[derive(Clone, Debug)]
struct HashChainCircuit<G: Group> {
num_elts_per_step: usize,
x_i: Vec<G::Scalar>,
}

impl<G: Group> HashChainCircuit<G> {
// 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::<Vec<_>>();

Self {
num_elts_per_step,
x_i,
}
}
}

impl<G: Group> StepCircuit<G::Scalar> for HashChainCircuit<G> {
fn arity(&self) -> usize {
1
}

fn synthesize<CS: ConstraintSystem<G::Scalar>>(
&self,
cs: &mut CS,
z_in: &[AllocatedNum<G::Scalar>],
) -> Result<Vec<AllocatedNum<G::Scalar>>, 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::<Result<Vec<_>, _>>()?;

// 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::<Vec<_>>();

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::<G::Scalar, U24>::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::<<E1 as Engine>::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::<E1>::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<<E1 as Engine>::GE>;

let circuits = (0..num_steps)
.map(|_| C1::new(num_elts_per_step))
.collect::<Vec<_>>();

// produce a recursive SNARK
println!(
"Generating a RecursiveSNARK with {num_elts_per_step} field elements per hashchain node..."
);
let mut recursive_snark: RecursiveSNARK<E1> = RecursiveSNARK::<E1>::new(
&pp,
&circuits[0],
&circuit_secondary,
&[<E1 as Engine>::Scalar::zero()],
&[<E2 as Engine>::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,
&[<E1 as Engine>::Scalar::ZERO],
&[<E2 as Engine>::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,
&[<E1 as Engine>::Scalar::ZERO],
&[<E2 as Engine>::Scalar::ZERO],
);
println!(
"CompressedSNARK::verify: {:?}, took {:?}",
res.is_ok(),
start.elapsed()
);
assert!(res.is_ok());
println!("=========================================================");
}
}

1 comment on commit 74792f9

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks

Table of Contents

Overview

This benchmark report shows the Arecibo GPU benchmarks.
NVIDIA L4
Intel(R) Xeon(R) CPU @ 2.20GHz
32 vCPUs
125 GB RAM
Workflow run: https://github.com/lurk-lab/arecibo/actions/runs/8006218009

Benchmark Results

RecursiveSNARK-NIVC-2

ref=346b9cf ref=74792f9
Prove-NumCons-6540 44.48 ms (✅ 1.00x) 44.59 ms (✅ 1.00x slower)
Verify-NumCons-6540 34.47 ms (✅ 1.00x) 34.33 ms (✅ 1.00x faster)
Prove-NumCons-1028888 319.93 ms (✅ 1.00x) 337.97 ms (✅ 1.06x slower)
Verify-NumCons-1028888 249.51 ms (✅ 1.00x) 270.51 ms (✅ 1.08x slower)

CompressedSNARK-NIVC-Commitments-2

ref=346b9cf ref=74792f9
Prove-NumCons-6540 10.68 s (✅ 1.00x) 10.71 s (✅ 1.00x slower)
Verify-NumCons-6540 51.32 ms (✅ 1.00x) 50.78 ms (✅ 1.01x faster)
Prove-NumCons-1028888 52.30 s (✅ 1.00x) 51.76 s (✅ 1.01x faster)
Verify-NumCons-1028888 51.24 ms (✅ 1.00x) 51.21 ms (✅ 1.00x faster)

Made with criterion-table

Please sign in to comment.