-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]> --------- Co-authored-by: Srinath Setty <[email protected]> Co-authored-by: tchataigner <[email protected]>
- Loading branch information
1 parent
346b9cf
commit 74792f9
Showing
1 changed file
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!("========================================================="); | ||
} | ||
} |
74792f9
There was a problem hiding this comment.
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