Nova/benches/recursive-snark.rs
benches/recursive-snark.rsRaw
1#![allow(non_snake_case)]
2
3use bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
4use core::marker::PhantomData;
5use criterion::*;
6use ff::PrimeField;
7use nova_snark::{
8 traits::{
9 circuit::{StepCircuit, TrivialTestCircuit},
10 Group,
11 },
12 PublicParams, RecursiveSNARK,
13};
14use std::time::Duration;
15
16type G1 = pasta_curves::pallas::Point;
17type G2 = pasta_curves::vesta::Point;
18type C1 = NonTrivialTestCircuit<<G1 as Group>::Scalar>;
19type C2 = TrivialTestCircuit<<G2 as Group>::Scalar>;
20
21// To run these benchmarks, first download `criterion` with `cargo install cargo install cargo-criterion`.
22// Then `cargo criterion --bench recursive-snark`. The results are located in `target/criterion/data/<name-of-benchmark>`.
23// For flamegraphs, run `cargo criterion --bench recursive-snark --features flamegraph -- --profile-time <secs>`.
24// The results are located in `target/criterion/profile/<name-of-benchmark>`.
25cfg_if::cfg_if! {
26 if #[cfg(feature = "flamegraph")] {
27 criterion_group! {
28 name = recursive_snark;
29 config = Criterion::default().warm_up_time(Duration::from_millis(3000)).with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None)));
30 targets = bench_recursive_snark
31 }
32 } else {
33 criterion_group! {
34 name = recursive_snark;
35 config = Criterion::default().warm_up_time(Duration::from_millis(3000));
36 targets = bench_recursive_snark
37 }
38 }
39}
40
41criterion_main!(recursive_snark);
42
43fn bench_recursive_snark(c: &mut Criterion) {
44 let num_cons_verifier_circuit_primary = 9819;
45 // we vary the number of constraints in the step circuit
46 for &num_cons_in_augmented_circuit in
47 [9819, 16384, 32768, 65536, 131072, 262144, 524288, 1048576].iter()
48 {
49 // number of constraints in the step circuit
50 let num_cons = num_cons_in_augmented_circuit - num_cons_verifier_circuit_primary;
51
52 let mut group = c.benchmark_group(format!("RecursiveSNARK-StepCircuitSize-{num_cons}"));
53 group.sample_size(10);
54
55 let c_primary = NonTrivialTestCircuit::new(num_cons);
56 let c_secondary = TrivialTestCircuit::default();
57
58 // Produce public parameters
59 let pp = PublicParams::<G1, G2, C1, C2>::setup(c_primary.clone(), c_secondary.clone());
60
61 // Bench time to produce a recursive SNARK;
62 // we execute a certain number of warm-up steps since executing
63 // the first step is cheaper than other steps owing to the presence of
64 // a lot of zeros in the satisfying assignment
65 let num_warmup_steps = 10;
66 let mut recursive_snark: RecursiveSNARK<G1, G2, C1, C2> = RecursiveSNARK::new(
67 &pp,
68 &c_primary,
69 &c_secondary,
70 vec![<G1 as Group>::Scalar::from(2u64)],
71 vec![<G2 as Group>::Scalar::from(2u64)],
72 );
73
74 for i in 0..num_warmup_steps {
75 let res = recursive_snark.prove_step(
76 &pp,
77 &c_primary,
78 &c_secondary,
79 vec![<G1 as Group>::Scalar::from(2u64)],
80 vec![<G2 as Group>::Scalar::from(2u64)],
81 );
82 assert!(res.is_ok());
83
84 // verify the recursive snark at each step of recursion
85 let res = recursive_snark.verify(
86 &pp,
87 i + 1,
88 &[<G1 as Group>::Scalar::from(2u64)],
89 &[<G2 as Group>::Scalar::from(2u64)],
90 );
91 assert!(res.is_ok());
92 }
93
94 group.bench_function("Prove", |b| {
95 b.iter(|| {
96 // produce a recursive SNARK for a step of the recursion
97 assert!(black_box(&mut recursive_snark.clone())
98 .prove_step(
99 black_box(&pp),
100 black_box(&c_primary),
101 black_box(&c_secondary),
102 black_box(vec![<G1 as Group>::Scalar::from(2u64)]),
103 black_box(vec![<G2 as Group>::Scalar::from(2u64)]),
104 )
105 .is_ok());
106 })
107 });
108
109 // Benchmark the verification time
110 group.bench_function("Verify", |b| {
111 b.iter(|| {
112 assert!(black_box(&recursive_snark)
113 .verify(
114 black_box(&pp),
115 black_box(num_warmup_steps),
116 black_box(&[<G1 as Group>::Scalar::from(2u64)]),
117 black_box(&[<G2 as Group>::Scalar::from(2u64)]),
118 )
119 .is_ok());
120 });
121 });
122 group.finish();
123 }
124}
125
126#[derive(Clone, Debug, Default)]
127struct NonTrivialTestCircuit<F: PrimeField> {
128 num_cons: usize,
129 _p: PhantomData<F>,
130}
131
132impl<F> NonTrivialTestCircuit<F>
133where
134 F: PrimeField,
135{
136 pub fn new(num_cons: usize) -> Self {
137 Self {
138 num_cons,
139 _p: Default::default(),
140 }
141 }
142}
143impl<F> StepCircuit<F> for NonTrivialTestCircuit<F>
144where
145 F: PrimeField,
146{
147 fn arity(&self) -> usize {
148 1
149 }
150
151 fn synthesize<CS: ConstraintSystem<F>>(
152 &self,
153 cs: &mut CS,
154 z: &[AllocatedNum<F>],
155 ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
156 // Consider a an equation: `x^2 = y`, where `x` and `y` are respectively the input and output.
157 let mut x = z[0].clone();
158 let mut y = x.clone();
159 for i in 0..self.num_cons {
160 y = x.square(cs.namespace(|| format!("x_sq_{i}")))?;
161 x = y.clone();
162 }
163 Ok(vec![y])
164 }
165
166 fn output(&self, z: &[F]) -> Vec<F> {
167 let mut x = z[0];
168 let mut y = x;
169 for _i in 0..self.num_cons {
170 y = x * x;
171 x = y;
172 }
173 vec![y]
174 }
175}