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