From 4ae3a571d7915b94032a9120ec4a6c521a59a096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:09:57 -0500 Subject: [PATCH] Rewire batched SNARK to act as a regular SNARK (#320) * feat: Implement RelaxedR1CSSNARK in batched.rs - Extended `snark` functionality with the addition of `RelaxedR1CSSNARKTrait`. - Added implementation for `RelaxedR1CSSNARKTrait` in `BatchedRelaxedR1CSSNARK`, - Leveraged unsafe Rust for creating slices for the `U` and `W` single elements in the new `RelaxedR1CSSNARKTrait` methods. * feat: Implement batched Spartan tests - Included testing for the batched workflow across non-trivial circuits, and "spark" compression. * feat: Implement new benchmarks for batched compressed SNARKs - Two new benchmark targets have been added: `bench_compressed_batched_snark` and `bench_compressed_batched_snark_with_computational_commitments` * fix: use slice::from_ref instead of unsafe --- benches/compressed-snark.rs | 63 ++++++++++++++++++++++++++++++++-- src/lib.rs | 57 ++++++++++++++++++++++++++++++ src/spartan/batched.rs | 39 ++++++++++++++++++++- src/spartan/batched_ppsnark.rs | 42 +++++++++++++++++++++-- 4 files changed, 196 insertions(+), 5 deletions(-) diff --git a/benches/compressed-snark.rs b/benches/compressed-snark.rs index b25232e2..f4da6fdc 100644 --- a/benches/compressed-snark.rs +++ b/benches/compressed-snark.rs @@ -37,13 +37,13 @@ cfg_if::cfg_if! { criterion_group! { name = compressed_snark; config = Criterion::default().warm_up_time(Duration::from_millis(3000)).with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None))); - targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments + targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments, bench_compressed_batched_snark, bench_compressed_batched_snark_with_computational_commitments } } else { criterion_group! { name = compressed_snark; config = Criterion::default().warm_up_time(Duration::from_millis(3000)); - targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments + targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments, bench_compressed_batched_snark, bench_compressed_batched_snark_with_computational_commitments } } } @@ -186,6 +186,65 @@ fn bench_compressed_snark_with_computational_commitments(c: &mut Criterion) { } } +// SNARKs without computation commitmnets +type BS1 = arecibo::spartan::batched::BatchedRelaxedR1CSSNARK; +type BS2 = arecibo::spartan::batched::BatchedRelaxedR1CSSNARK; +// SNARKs with computation commitmnets +type BSS1 = arecibo::spartan::batched_ppsnark::BatchedRelaxedR1CSSNARK; +type BSS2 = arecibo::spartan::batched_ppsnark::BatchedRelaxedR1CSSNARK; + +fn bench_compressed_batched_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, + ] + .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("BatchedCompressedSNARK"); + group.sampling_mode(SamplingMode::Flat); + group.sample_size(NUM_SAMPLES); + group.noise_threshold(noise_threshold_env().unwrap_or(0.05)); + + bench_compressed_snark_internal::(&mut group, num_cons); + + group.finish(); + } +} + +fn bench_compressed_batched_snark_with_computational_commitments(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, + ] + .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("BatchedCompressedSNARK-Commitments"); + group.sampling_mode(SamplingMode::Flat); + group.sample_size(NUM_SAMPLES); + group.noise_threshold(noise_threshold_env().unwrap_or(0.05)); + + bench_compressed_snark_internal::(&mut group, num_cons); + + group.finish(); + } +} + #[derive(Clone, Debug, Default)] struct NonTrivialCircuit { num_cons: usize, diff --git a/src/lib.rs b/src/lib.rs index c103bb91..c3a49639 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1397,6 +1397,63 @@ mod tests { >(); } + type BatchedS = spartan::batched::BatchedRelaxedR1CSSNARK; + type BatchedSPrime = spartan::batched::BatchedRelaxedR1CSSNARK; + + fn test_ivc_nontrivial_with_batched_compression_with() + where + E1: CurveCycleEquipped, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait>, + // this is due to the reliance on Abomonation + ::Repr: Abomonation, + < as Engine>::Scalar as PrimeField>::Repr: Abomonation, + { + // this tests compatibility of the batched workflow with the non-batched one + test_ivc_nontrivial_with_some_compression_with::, BatchedS<_, EE2>>() + } + + #[test] + fn test_ivc_nontrivial_with_batched_compression() { + test_ivc_nontrivial_with_batched_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_batched_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_batched_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_batched_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_batched_compression_with::< + Bn256EngineKZG, + provider::hyperkzg::EvaluationEngine, + EE<_>, + >(); + } + + fn test_ivc_nontrivial_with_batched_spark_compression_with() + where + E1: CurveCycleEquipped, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait>, + // this is due to the reliance on Abomonation + ::Repr: Abomonation, + < as Engine>::Scalar as PrimeField>::Repr: Abomonation, + { + // this tests compatibility of the batched workflow with the non-batched one + test_ivc_nontrivial_with_some_compression_with::, BatchedSPrime<_, EE2>>( + ) + } + + #[test] + fn test_ivc_nontrivial_with_batched_spark_compression() { + test_ivc_nontrivial_with_batched_spark_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_batched_spark_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_batched_spark_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_batched_spark_compression_with::, EE<_>>( + ); + test_ivc_nontrivial_with_batched_spark_compression_with::< + Bn256EngineKZG, + provider::hyperkzg::EvaluationEngine, + EE<_>, + >(); + } + fn test_ivc_nondet_with_compression_with() where E1: CurveCycleEquipped, diff --git a/src/spartan/batched.rs b/src/spartan/batched.rs index a72de841..0bbe2305 100644 --- a/src/spartan/batched.rs +++ b/src/spartan/batched.rs @@ -6,6 +6,7 @@ use ff::Field; use serde::{Deserialize, Serialize}; +use core::slice; use itertools::Itertools; use once_cell::sync::OnceCell; use rayon::prelude::*; @@ -31,7 +32,7 @@ use crate::{ }, traits::{ evaluation::EvaluationEngineTrait, - snark::{BatchedRelaxedR1CSSNARKTrait, DigestHelperTrait}, + snark::{BatchedRelaxedR1CSSNARKTrait, DigestHelperTrait, RelaxedR1CSSNARKTrait}, Engine, TranscriptEngineTrait, }, zip_with, CommitmentKey, @@ -588,3 +589,39 @@ impl> BatchedRelaxedR1CSSNARKTrait Ok(()) } } + +impl> RelaxedR1CSSNARKTrait + for BatchedRelaxedR1CSSNARK +{ + type ProverKey = ProverKey; + + type VerifierKey = VerifierKey; + + fn ck_floor() -> Box Fn(&'a R1CSShape) -> usize> { + >::ck_floor() + } + + fn setup( + ck: Arc>, + S: &R1CSShape, + ) -> Result<(Self::ProverKey, Self::VerifierKey), NovaError> { + >::setup(ck, vec![S]) + } + + fn prove( + ck: &CommitmentKey, + pk: &Self::ProverKey, + S: &R1CSShape, + U: &RelaxedR1CSInstance, + W: &RelaxedR1CSWitness, + ) -> Result { + let slice_U = slice::from_ref(U); + let slice_W = slice::from_ref(W); + >::prove(ck, pk, vec![S], slice_U, slice_W) + } + + fn verify(&self, vk: &Self::VerifierKey, U: &RelaxedR1CSInstance) -> Result<(), NovaError> { + let slice = slice::from_ref(U); + >::verify(self, vk, slice) + } +} diff --git a/src/spartan/batched_ppsnark.rs b/src/spartan/batched_ppsnark.rs index 717e91f5..fa369ba6 100644 --- a/src/spartan/batched_ppsnark.rs +++ b/src/spartan/batched_ppsnark.rs @@ -29,11 +29,12 @@ use crate::{ traits::{ commitment::{CommitmentEngineTrait, CommitmentTrait, Len}, evaluation::EvaluationEngineTrait, - snark::{BatchedRelaxedR1CSSNARKTrait, DigestHelperTrait}, + snark::{BatchedRelaxedR1CSSNARKTrait, DigestHelperTrait, RelaxedR1CSSNARKTrait}, Engine, TranscriptEngineTrait, }, zip_with, zip_with_for_each, Commitment, CommitmentKey, CompressedCommitment, }; +use core::slice; use ff::Field; use itertools::{chain, Itertools as _}; use once_cell::sync::*; @@ -145,7 +146,7 @@ impl> BatchedRelaxedR1CSSNARKTrait ) -> Result<(Self::ProverKey, Self::VerifierKey), NovaError> { for s in S.iter() { // check the provided commitment key meets minimal requirements - if ck.length() < Self::ck_floor()(s) { + if ck.length() < >::ck_floor()(s) { // return Err(NovaError::InvalidCommitmentKeyLength); return Err(NovaError::InternalError); } @@ -1338,3 +1339,40 @@ impl> BatchedRelaxedR1CSSNARK { .collect() } } + +impl> RelaxedR1CSSNARKTrait + for BatchedRelaxedR1CSSNARK +{ + type ProverKey = ProverKey; + + type VerifierKey = VerifierKey; + + fn ck_floor() -> Box Fn(&'a R1CSShape) -> usize> { + >::ck_floor() + } + + fn setup( + ck: Arc>, + S: &R1CSShape, + ) -> Result<(Self::ProverKey, Self::VerifierKey), NovaError> { + >::setup(ck, vec![S]) + } + + fn prove( + ck: &CommitmentKey, + pk: &Self::ProverKey, + S: &R1CSShape, + U: &RelaxedR1CSInstance, + W: &RelaxedR1CSWitness, + ) -> Result { + let slice_U = slice::from_ref(U); + let slice_W = slice::from_ref(W); + + >::prove(ck, pk, vec![S], slice_U, slice_W) + } + + fn verify(&self, vk: &Self::VerifierKey, U: &RelaxedR1CSInstance) -> Result<(), NovaError> { + let slice = slice::from_ref(U); + >::verify(self, vk, slice) + } +}