|
| 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 | +} |
0 commit comments