Skip to content

Commit b56a3eb

Browse files
huitseekerwyattbenno777winston-h-zhanghero78119porcuquine
committed
feat: implement SuperNova
This implements [Supernova](https://eprint.iacr.org/2022/1758), allowing: - a 'pay-as-you-go' cost structure for folding operations, through the SuperNova folding scheme, - a final SNARK that efficiently compresses an instance of this folded proof, through batching techniques. References: - the [blog post](https://blog.lurk-lang.org/posts/arecibo-supernova/#technical-release-note-supernova-protocol-integration-into-nova) goes into our construction, and links to two more specialized notes on [the `CompressedSNARK` for Supernova](https://hackmd.io/@adr1anh/BJw1g0aBT) along with our variant of the [public input padding issue](https://hackmd.io/@adr1anh/Sy08YaVBa). - the Readme at `src/supernova/Readme.md` This backports the following Arecibo PRs: - lurk-lang/arecibo#2 - lurk-lang/arecibo#3 - lurk-lang/arecibo#10 - lurk-lang/arecibo#16 - lurk-lang/arecibo#23 - lurk-lang/arecibo#30 - lurk-lang/arecibo#28 - lurk-lang/arecibo#41 - lurk-lang/arecibo#45 - lurk-lang/arecibo#50 - lurk-lang/arecibo#56 - lurk-lang/arecibo#51 - lurk-lang/arecibo#72 - lurk-lang/arecibo#92 - lurk-lang/arecibo#95 - lurk-lang/arecibo#97 - lurk-lang/arecibo#101 - lurk-lang/arecibo#110 - lurk-lang/arecibo#106 - lurk-lang/arecibo#112 - lurk-lang/arecibo#114 - lurk-lang/arecibo#119 - lurk-lang/arecibo#120 - lurk-lang/arecibo#127 - lurk-lang/arecibo#123 - lurk-lang/arecibo#131 - lurk-lang/arecibo#174 - lurk-lang/arecibo#175 - lurk-lang/arecibo#182 Co-authored-by: WYATT <[email protected]> Co-authored-by: Hanting Zhang <[email protected]> Co-authored-by: Ming <[email protected]> Co-authored-by: porcuquine <[email protected]> Co-authored-by: Samuel Burnham <[email protected]> Co-authored-by: Matej Penciak <[email protected]> Co-authored-by: Adrian Hamelink <[email protected]>
1 parent 0fb0b49 commit b56a3eb

38 files changed

+8138
-572
lines changed

Cargo.toml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ byteorder = "1.4.3"
3434
thiserror = "1.0"
3535
halo2curves = { version = "0.4.0", features = ["derive_serde"] }
3636
group = "0.13.0"
37+
pairing = "0.23.0"
38+
tap = "1.0.1"
39+
cfg-if = "1.0.0"
3740
once_cell = "1.18.0"
41+
itertools = "0.12.0"
42+
log = "0.4.20"
3843

3944
[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies]
4045
pasta-msm = { version = "0.1.4" }
@@ -48,10 +53,10 @@ criterion = { version = "0.4", features = ["html_reports"] }
4853
flate2 = "1.0"
4954
hex = "0.4.3"
5055
pprof = { version = "0.11" }
51-
cfg-if = "1.0.0"
56+
rand = "0.8.5"
5257
sha2 = "0.10.7"
58+
tracing-test = "0.2.4"
5359
proptest = "1.2.0"
54-
rand = "0.8.5"
5560

5661
[[bench]]
5762
name = "recursive-snark"
@@ -69,6 +74,14 @@ harness = false
6974
name = "sha256"
7075
harness = false
7176

77+
[[bench]]
78+
name = "recursive-snark-supernova"
79+
harness = false
80+
81+
[[bench]]
82+
name = "compressed-snark-supernova"
83+
harness = false
84+
7285
[features]
7386
default = []
7487
asm = ["halo2curves/asm"]
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
#![allow(non_snake_case)]
2+
use nova_snark::{
3+
supernova::NonUniformCircuit,
4+
supernova::{snark::CompressedSNARK, PublicParams, RecursiveSNARK},
5+
traits::{
6+
circuit_supernova::{StepCircuit, TrivialTestCircuit},
7+
snark::BatchedRelaxedR1CSSNARKTrait,
8+
snark::RelaxedR1CSSNARKTrait,
9+
Engine,
10+
},
11+
};
12+
use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError};
13+
use core::marker::PhantomData;
14+
use criterion::{measurement::WallTime, *};
15+
use ff::PrimeField;
16+
use std::time::Duration;
17+
18+
type E1 = nova_snark::provider::PallasEngine;
19+
type E2 = nova_snark::provider::VestaEngine;
20+
type EE1 = nova_snark::provider::ipa_pc::EvaluationEngine<E1>;
21+
type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine<E2>;
22+
// SNARKs without computation commitmnets
23+
type S1 = nova_snark::spartan::batched::BatchedRelaxedR1CSSNARK<E1, EE1>;
24+
type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK<E2, EE2>;
25+
// SNARKs with computation commitmnets
26+
type SS1 = nova_snark::spartan::batched_ppsnark::BatchedRelaxedR1CSSNARK<E1, EE1>;
27+
type SS2 = nova_snark::spartan::ppsnark::RelaxedR1CSSNARK<E2, EE2>;
28+
29+
// To run these benchmarks, first download `criterion` with `cargo install cargo-criterion`.
30+
// Then `cargo criterion --bench compressed-snark-supernova`. The results are located in `target/criterion/data/<name-of-benchmark>`.
31+
// For flamegraphs, run `cargo criterion --bench compressed-snark-supernova --features flamegraph -- --profile-time <secs>`.
32+
// The results are located in `target/criterion/profile/<name-of-benchmark>`.
33+
cfg_if::cfg_if! {
34+
if #[cfg(feature = "flamegraph")] {
35+
criterion_group! {
36+
name = compressed_snark_supernova;
37+
config = Criterion::default().warm_up_time(Duration::from_millis(3000)).with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None)));
38+
targets = bench_one_augmented_circuit_compressed_snark, bench_two_augmented_circuit_compressed_snark, bench_two_augmented_circuit_compressed_snark_with_computational_commitments
39+
}
40+
} else {
41+
criterion_group! {
42+
name = compressed_snark_supernova;
43+
config = Criterion::default().warm_up_time(Duration::from_millis(3000));
44+
targets = bench_one_augmented_circuit_compressed_snark, bench_two_augmented_circuit_compressed_snark, bench_two_augmented_circuit_compressed_snark_with_computational_commitments
45+
}
46+
}
47+
}
48+
49+
criterion_main!(compressed_snark_supernova);
50+
51+
// This should match the value in test_supernova_recursive_circuit_pasta
52+
// TODO: This should also be a table matching the num_augmented_circuits in the below
53+
const NUM_CONS_VERIFIER_CIRCUIT_PRIMARY: usize = 9844;
54+
const NUM_SAMPLES: usize = 10;
55+
56+
struct NonUniformBench<E1, E2, S>
57+
where
58+
E1: Engine<Base = <E2 as Engine>::Scalar>,
59+
E2: Engine<Base = <E1 as Engine>::Scalar>,
60+
S: StepCircuit<E2::Scalar> + Default,
61+
{
62+
num_circuits: usize,
63+
num_cons: usize,
64+
_p: PhantomData<(E1, E2, S)>,
65+
}
66+
67+
impl<E1, E2, S> NonUniformBench<E1, E2, S>
68+
where
69+
E1: Engine<Base = <E2 as Engine>::Scalar>,
70+
E2: Engine<Base = <E1 as Engine>::Scalar>,
71+
S: StepCircuit<E2::Scalar> + Default,
72+
{
73+
fn new(num_circuits: usize, num_cons: usize) -> Self {
74+
Self {
75+
num_circuits,
76+
num_cons,
77+
_p: Default::default(),
78+
}
79+
}
80+
}
81+
82+
impl<E1, E2, S>
83+
NonUniformCircuit<E1, E2, NonTrivialTestCircuit<E1::Scalar>, TrivialTestCircuit<E2::Scalar>>
84+
for NonUniformBench<E1, E2, S>
85+
where
86+
E1: Engine<Base = <E2 as Engine>::Scalar>,
87+
E2: Engine<Base = <E1 as Engine>::Scalar>,
88+
S: StepCircuit<E2::Scalar> + Default,
89+
{
90+
fn num_circuits(&self) -> usize {
91+
self.num_circuits
92+
}
93+
94+
fn primary_circuit(&self, circuit_index: usize) -> NonTrivialTestCircuit<E1::Scalar> {
95+
assert!(
96+
circuit_index < self.num_circuits,
97+
"Circuit index out of bounds: asked for {circuit_index}, but there are only {} circuits.",
98+
self.num_circuits
99+
);
100+
101+
NonTrivialTestCircuit::new(self.num_cons)
102+
}
103+
104+
fn secondary_circuit(&self) -> TrivialTestCircuit<E2::Scalar> {
105+
Default::default()
106+
}
107+
}
108+
109+
/// Benchmarks the compressed SNARK at a provided number of constraints
110+
///
111+
/// Parameters
112+
/// - `num_augmented_circuits`: the number of augmented circuits in this configuration
113+
/// - `group`: the criterion benchmark group
114+
/// - `num_cons`: the number of constraints in the step circuit
115+
fn bench_compressed_snark_internal_with_arity<
116+
S1: BatchedRelaxedR1CSSNARKTrait<E1>,
117+
S2: RelaxedR1CSSNARKTrait<E2>,
118+
>(
119+
group: &mut BenchmarkGroup<'_, WallTime>,
120+
num_augmented_circuits: usize,
121+
num_cons: usize,
122+
) {
123+
let bench: NonUniformBench<E1, E2, TrivialTestCircuit<<E2 as Engine>::Scalar>> =
124+
NonUniformBench::new(num_augmented_circuits, num_cons);
125+
let pp = PublicParams::setup(&bench, &*S1::ck_floor(), &*S2::ck_floor());
126+
127+
let num_steps = 3;
128+
let z0_primary = vec![<E1 as Engine>::Scalar::from(2u64)];
129+
let z0_secondary = vec![<E2 as Engine>::Scalar::from(2u64)];
130+
let mut recursive_snark_option: Option<RecursiveSNARK<E1, E2>> = None;
131+
let mut selected_augmented_circuit = 0;
132+
133+
for _ in 0..num_steps {
134+
let mut recursive_snark = recursive_snark_option.unwrap_or_else(|| {
135+
RecursiveSNARK::new(
136+
&pp,
137+
&bench,
138+
&bench.primary_circuit(0),
139+
&bench.secondary_circuit(),
140+
&z0_primary,
141+
&z0_secondary,
142+
)
143+
.unwrap()
144+
});
145+
146+
if selected_augmented_circuit == 0 || selected_augmented_circuit == 1 {
147+
let res = recursive_snark.prove_step(
148+
&pp,
149+
&bench.primary_circuit(selected_augmented_circuit),
150+
&bench.secondary_circuit(),
151+
);
152+
res.expect("Prove step failed");
153+
154+
let res = recursive_snark.verify(&pp, &z0_primary, &z0_secondary);
155+
res.expect("Verify failed");
156+
} else {
157+
unimplemented!()
158+
}
159+
160+
selected_augmented_circuit = (selected_augmented_circuit + 1) % num_augmented_circuits;
161+
recursive_snark_option = Some(recursive_snark)
162+
}
163+
164+
assert!(recursive_snark_option.is_some());
165+
let recursive_snark = recursive_snark_option.unwrap();
166+
167+
let (prover_key, verifier_key) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap();
168+
169+
// Benchmark the prove time
170+
group.bench_function("Prove", |b| {
171+
b.iter(|| {
172+
assert!(CompressedSNARK::<_, _, _, _, S1, S2>::prove(
173+
black_box(&pp),
174+
black_box(&prover_key),
175+
black_box(&recursive_snark)
176+
)
177+
.is_ok());
178+
})
179+
});
180+
181+
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &prover_key, &recursive_snark);
182+
183+
assert!(res.is_ok());
184+
let compressed_snark = res.unwrap();
185+
186+
// Benchmark the verification time
187+
group.bench_function("Verify", |b| {
188+
b.iter(|| {
189+
assert!(black_box(&compressed_snark)
190+
.verify(
191+
black_box(&pp),
192+
black_box(&verifier_key),
193+
black_box(&z0_primary),
194+
black_box(&z0_secondary),
195+
)
196+
.is_ok());
197+
})
198+
});
199+
}
200+
201+
fn bench_one_augmented_circuit_compressed_snark(c: &mut Criterion) {
202+
// we vary the number of constraints in the step circuit
203+
for &num_cons_in_augmented_circuit in [
204+
NUM_CONS_VERIFIER_CIRCUIT_PRIMARY,
205+
16384,
206+
32768,
207+
65536,
208+
131072,
209+
262144,
210+
524288,
211+
1048576,
212+
]
213+
.iter()
214+
{
215+
// number of constraints in the step circuit
216+
let num_cons = num_cons_in_augmented_circuit - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY;
217+
218+
let mut group = c.benchmark_group(format!(
219+
"CompressedSNARKSuperNova-1circuit-StepCircuitSize-{num_cons}"
220+
));
221+
group.sample_size(NUM_SAMPLES);
222+
223+
bench_compressed_snark_internal_with_arity::<S1, S2>(&mut group, 1, num_cons);
224+
225+
group.finish();
226+
}
227+
}
228+
229+
fn bench_two_augmented_circuit_compressed_snark(c: &mut Criterion) {
230+
// we vary the number of constraints in the step circuit
231+
for &num_cons_in_augmented_circuit in [
232+
NUM_CONS_VERIFIER_CIRCUIT_PRIMARY,
233+
16384,
234+
32768,
235+
65536,
236+
131072,
237+
262144,
238+
524288,
239+
1048576,
240+
]
241+
.iter()
242+
{
243+
// number of constraints in the step circuit
244+
let num_cons = num_cons_in_augmented_circuit - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY;
245+
246+
let mut group = c.benchmark_group(format!(
247+
"CompressedSNARKSuperNova-2circuit-StepCircuitSize-{num_cons}"
248+
));
249+
group.sample_size(NUM_SAMPLES);
250+
251+
bench_compressed_snark_internal_with_arity::<S1, S2>(&mut group, 2, num_cons);
252+
253+
group.finish();
254+
}
255+
}
256+
257+
fn bench_two_augmented_circuit_compressed_snark_with_computational_commitments(c: &mut Criterion) {
258+
// we vary the number of constraints in the step circuit
259+
for &num_cons_in_augmented_circuit in [
260+
NUM_CONS_VERIFIER_CIRCUIT_PRIMARY,
261+
16384,
262+
32768,
263+
65536,
264+
131072,
265+
262144,
266+
524288,
267+
1048576,
268+
]
269+
.iter()
270+
{
271+
// number of constraints in the step circuit
272+
let num_cons = num_cons_in_augmented_circuit - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY;
273+
274+
let mut group = c.benchmark_group(format!(
275+
"CompressedSNARKSuperNova-Commitments-2circuit-StepCircuitSize-{num_cons}"
276+
));
277+
group.sample_size(NUM_SAMPLES);
278+
279+
bench_compressed_snark_internal_with_arity::<SS1, SS2>(&mut group, 2, num_cons);
280+
281+
group.finish();
282+
}
283+
}
284+
#[derive(Clone, Debug, Default)]
285+
struct NonTrivialTestCircuit<F: PrimeField> {
286+
num_cons: usize,
287+
_p: PhantomData<F>,
288+
}
289+
290+
impl<F> NonTrivialTestCircuit<F>
291+
where
292+
F: PrimeField,
293+
{
294+
pub fn new(num_cons: usize) -> Self {
295+
Self {
296+
num_cons,
297+
_p: Default::default(),
298+
}
299+
}
300+
}
301+
impl<F> StepCircuit<F> for NonTrivialTestCircuit<F>
302+
where
303+
F: PrimeField,
304+
{
305+
fn arity(&self) -> usize {
306+
1
307+
}
308+
309+
fn circuit_index(&self) -> usize {
310+
0
311+
}
312+
313+
fn synthesize<CS: ConstraintSystem<F>>(
314+
&self,
315+
cs: &mut CS,
316+
pc: Option<&AllocatedNum<F>>,
317+
z: &[AllocatedNum<F>],
318+
) -> Result<(Option<AllocatedNum<F>>, Vec<AllocatedNum<F>>), SynthesisError> {
319+
// Consider a an equation: `x^{2 * num_cons} = y`, where `x` and `y` are respectively the input and output.
320+
let mut x = z[0].clone();
321+
let mut y = x.clone();
322+
for i in 0..self.num_cons {
323+
y = x.square(cs.namespace(|| format!("x_sq_{i}")))?;
324+
x = y.clone();
325+
}
326+
Ok((pc.cloned(), vec![y]))
327+
}
328+
}

benches/compressed-snark.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ type E1 = PallasEngine;
1919
type E2 = VestaEngine;
2020
type EE1 = nova_snark::provider::ipa_pc::EvaluationEngine<E1>;
2121
type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine<E2>;
22-
// SNARKs without computational commitments
22+
// SNARKs without computation commitmnets
2323
type S1 = nova_snark::spartan::snark::RelaxedR1CSSNARK<E1, EE1>;
2424
type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK<E2, EE2>;
25-
// SNARKs with computational commitments
25+
// SNARKs with computation commitmnets
2626
type SS1 = nova_snark::spartan::ppsnark::RelaxedR1CSSNARK<E1, EE1>;
2727
type SS2 = nova_snark::spartan::ppsnark::RelaxedR1CSSNARK<E2, EE2>;
2828
type C1 = NonTrivialCircuit<<E1 as Engine>::Scalar>;

0 commit comments

Comments
 (0)