diff --git a/.gitignore b/.gitignore index 52271a5ba..04070d9c8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ Cargo.lock **/*.rs.bk # Configurations for VSCode -.vscode/ \ No newline at end of file +.vscode/ + +# Configuration for Jetbrains +.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 9293f65ab..ee7718e01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,10 @@ harness = false name = "compressed-snark-supernova" harness = false +[[bench]] +name = "pcs" +harness = false + [features] default = [] abomonate = [] diff --git a/benches/compressed-snark.rs b/benches/compressed-snark.rs index a77aafbfe..a620c93cb 100644 --- a/benches/compressed-snark.rs +++ b/benches/compressed-snark.rs @@ -27,7 +27,7 @@ type SS2 = arecibo::spartan::ppsnark::RelaxedR1CSSNARK; type C1 = NonTrivialCircuit<::Scalar>; type C2 = TrivialCircuit<::Scalar>; -// To run these benchmarks, first download `criterion` with `cargo install cargo install cargo-criterion`. +// To run these benchmarks, first download `criterion` with `cargo install cargo-criterion`. // Then `cargo criterion --bench compressed-snark`. The results are located in `target/criterion/data/`. // For flamegraphs, run `cargo criterion --bench compressed-snark --features flamegraph -- --profile-time `. // The results are located in `target/criterion/profile/`. diff --git a/benches/pcs.rs b/benches/pcs.rs new file mode 100644 index 000000000..f7946e71d --- /dev/null +++ b/benches/pcs.rs @@ -0,0 +1,198 @@ +use arecibo::provider::{ + ipa_pc::EvaluationEngine as IPAEvaluationEngine, mlkzg::EvaluationEngine as MLEvaluationEngine, + non_hiding_zeromorph::ZMPCS, Bn256EngineKZG, Bn256EngineZM, GrumpkinEngine, +}; +use arecibo::spartan::polys::multilinear::MultilinearPolynomial; +use arecibo::traits::{ + commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait, Engine, + TranscriptEngineTrait, +}; +use criterion::{criterion_group, criterion_main, Bencher, BenchmarkId, Criterion, SamplingMode}; +use ff::Field; +use halo2curves::bn256::Bn256; +use rand::rngs::StdRng; +use rand_core::{CryptoRng, RngCore, SeedableRng}; +use std::any::type_name; +use std::time::Duration; + +// To run these benchmarks, first download `criterion` with `cargo install cargo-criterion`. +// Then `cargo criterion --bench pcs`. +// For flamegraphs, run `cargo criterion --bench pcs --features flamegraph -- --profile-time `. +// The results are located in `target/criterion/profile/`. +cfg_if::cfg_if! { + if #[cfg(feature = "flamegraph")] { + criterion_group! { + name = pcs; + config = Criterion::default().warm_up_time(Duration::from_millis(3000)).with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None))); + targets = bench_pcs + } + } else { + criterion_group! { + name = pcs; + config = Criterion::default().warm_up_time(Duration::from_millis(3000)); + targets = bench_pcs + } + } +} + +criterion_main!(pcs); + +const NUM_VARS_TEST_VECTOR: [usize; 6] = [10, 12, 14, 16, 18, 20]; + +struct BenchAssests> { + poly: MultilinearPolynomial<::Scalar>, + point: Vec<::Scalar>, + eval: ::Scalar, + ck: <::CE as CommitmentEngineTrait>::CommitmentKey, + commitment: <::CE as CommitmentEngineTrait>::Commitment, + prover_key: >::ProverKey, + verifier_key: >::VerifierKey, + proof: Option<>::EvaluationArgument>, +} + +/// Returns a random polynomial, a point and calculate its evaluation. +pub fn random_poly_with_eval( + num_vars: usize, + mut rng: &mut R, +) -> ( + MultilinearPolynomial<::Scalar>, + Vec<::Scalar>, + ::Scalar, +) { + // Generate random polynomial and point. + let poly = MultilinearPolynomial::random(num_vars, &mut rng); + let point = (0..num_vars) + .map(|_| ::Scalar::random(&mut rng)) + .collect::>(); + + // Calculation evaluation of point over polynomial. + let eval = MultilinearPolynomial::evaluate_with(poly.evaluations(), &point); + + (poly, point, eval) +} + +impl> BenchAssests { + pub(crate) fn from_num_vars(num_vars: usize, rng: &mut R) -> Self { + let (poly, point, eval) = random_poly_with_eval::(num_vars, rng); + + // Mock commitment key. + let ck = E::CE::setup(b"test", 1 << num_vars); + // Commits to the provided vector using the provided generators. + let commitment = E::CE::commit(&ck, poly.evaluations()); + + let (prover_key, verifier_key) = EE::setup(&ck); + + // Generate proof so that we can bench verification. + let proof = EE::prove( + &ck, + &prover_key, + &mut E::TE::new(b"TestEval"), + &commitment, + poly.evaluations(), + &point, + &eval, + ) + .unwrap(); + + Self { + poly, + point, + eval, + ck, + commitment, + prover_key, + verifier_key, + proof: Some(proof), + } + } +} + +// Macro to generate benchmark code for multiple evaluation engine types +macro_rules! benchmark_all_engines { + ($criterion:expr, $test_vector:expr, $proving_fn:expr, $verifying_fn:expr, $( ($assets:ident, $eval_engine:ty) ),*) => { + for num_vars in $test_vector.iter() { + let mut rng = rand::rngs::StdRng::seed_from_u64(*num_vars as u64); + + $( + let $assets: BenchAssests<_, $eval_engine> = BenchAssests::from_num_vars::(*num_vars, &mut rng); + )* + + // Proving group + let mut proving_group = $criterion.benchmark_group(format!("PCS-Proving {}", num_vars)); + proving_group + .sampling_mode(SamplingMode::Auto) + .sample_size(10); + + $( + proving_group.bench_with_input(BenchmarkId::new(type_name::<$eval_engine>(), num_vars), &num_vars, |b, _| { + $proving_fn(b, &$assets); + }); + )* + + proving_group.finish(); + + // Verifying group + let mut verifying_group = $criterion.benchmark_group(format!("PCS-Verifying {}", num_vars)); + verifying_group + .sampling_mode(SamplingMode::Auto) + .sample_size(10); + + $( + verifying_group.bench_with_input(BenchmarkId::new(type_name::<$eval_engine>(), num_vars), &num_vars, |b, _| { + $verifying_fn(b, &$assets); + }); + )* + + verifying_group.finish(); + } + }; +} + +fn bench_pcs(c: &mut Criterion) { + benchmark_all_engines!( + c, + NUM_VARS_TEST_VECTOR, + bench_pcs_proving_internal, + bench_pcs_verifying_internal, + (ipa_assets, IPAEvaluationEngine), + (mlkzg_assets, MLEvaluationEngine), + (zm_assets, ZMPCS) + ); +} + +fn bench_pcs_proving_internal>( + b: &mut Bencher<'_>, + bench_assets: &BenchAssests, +) { + // Bench generate proof. + b.iter(|| { + EE::prove( + &bench_assets.ck, + &bench_assets.prover_key, + &mut E::TE::new(b"TestEval"), + &bench_assets.commitment, + bench_assets.poly.evaluations(), + &bench_assets.point, + &bench_assets.eval, + ) + .unwrap(); + }); +} + +fn bench_pcs_verifying_internal>( + b: &mut Bencher<'_>, + bench_assets: &BenchAssests, +) { + // Bench verify proof. + b.iter(|| { + EE::verify( + &bench_assets.verifier_key, + &mut E::TE::new(b"TestEval"), + &bench_assets.commitment, + &bench_assets.point, + &bench_assets.eval, + bench_assets.proof.as_ref().unwrap(), + ) + .unwrap(); + }); +} diff --git a/benches/recursive-snark.rs b/benches/recursive-snark.rs index df65dd475..353b7fa56 100644 --- a/benches/recursive-snark.rs +++ b/benches/recursive-snark.rs @@ -19,7 +19,7 @@ type E2 = VestaEngine; type C1 = NonTrivialCircuit<::Scalar>; type C2 = TrivialCircuit<::Scalar>; -// To run these benchmarks, first download `criterion` with `cargo install cargo install cargo-criterion`. +// To run these benchmarks, first download `criterion` with `cargo install cargo-criterion`. // Then `cargo criterion --bench recursive-snark`. The results are located in `target/criterion/data/`. // For flamegraphs, run `cargo criterion --bench recursive-snark --features flamegraph -- --profile-time `. // The results are located in `target/criterion/profile/`. diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 14414a55a..471a095eb 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -410,3 +410,17 @@ where } } } + +#[cfg(test)] +mod test { + use crate::provider::ipa_pc::EvaluationEngine; + use crate::provider::util::test_utils::prove_verify_from_num_vars; + use crate::provider::GrumpkinEngine; + + #[test] + fn test_multiple_polynomial_size() { + for num_vars in [4, 5, 6] { + prove_verify_from_num_vars::<_, EvaluationEngine>(num_vars); + } + } +} diff --git a/src/provider/mlkzg.rs b/src/provider/mlkzg.rs index 6cf6e0c83..9cbd1b748 100644 --- a/src/provider/mlkzg.rs +++ b/src/provider/mlkzg.rs @@ -429,12 +429,11 @@ where #[cfg(test)] mod tests { use super::*; + use crate::provider::util::test_utils::prove_verify_from_num_vars; use crate::{ - provider::keccak::Keccak256Transcript, spartan::polys::multilinear::MultilinearPolynomial, - traits::commitment::CommitmentTrait, CommitmentKey, + provider::keccak::Keccak256Transcript, traits::commitment::CommitmentTrait, CommitmentKey, }; use bincode::Options; - use rand::SeedableRng; type E = halo2curves::bn256::Bn256; type NE = crate::provider::Bn256EngineKZG; @@ -558,54 +557,8 @@ mod tests { #[test] fn test_mlkzg_more() { // test the mlkzg prover and verifier with random instances (derived from a seed) - for ell in [4, 5, 6] { - let mut rng = rand::rngs::StdRng::seed_from_u64(ell as u64); - - let n = 1 << ell; // n = 2^ell - - let poly = (0..n).map(|_| Fr::random(&mut rng)).collect::>(); - let point = (0..ell).map(|_| Fr::random(&mut rng)).collect::>(); - let eval = MultilinearPolynomial::evaluate_with(&poly, &point); - - let ck: CommitmentKey = - as CommitmentEngineTrait>::setup(b"test", n); - let (pk, vk): (KZGProverKey, KZGVerifierKey) = EvaluationEngine::::setup(&ck); - - // make a commitment - let C = as CommitmentEngineTrait>::commit(&ck, &poly); - - // prove an evaluation - let mut prover_transcript = Keccak256Transcript::::new(b"TestEval"); - let proof: EvaluationArgument = EvaluationEngine::::prove( - &ck, - &pk, - &mut prover_transcript, - &C, - &poly, - &point, - &eval, - ) - .unwrap(); - - // verify the evaluation - let mut verifier_tr = Keccak256Transcript::::new(b"TestEval"); - assert!( - EvaluationEngine::::verify(&vk, &mut verifier_tr, &C, &point, &eval, &proof).is_ok() - ); - - // Change the proof and expect verification to fail - let mut bad_proof = proof.clone(); - bad_proof.comms[0] = (bad_proof.comms[0] + bad_proof.comms[1]).to_affine(); - let mut verifier_tr2 = Keccak256Transcript::::new(b"TestEval"); - assert!(EvaluationEngine::::verify( - &vk, - &mut verifier_tr2, - &C, - &point, - &eval, - &bad_proof - ) - .is_err()); + for num_vars in [4, 5, 6] { + prove_verify_from_num_vars::<_, EvaluationEngine>(num_vars); } } } diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 2e05b92ea..1f4dd7a19 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -15,7 +15,7 @@ pub(crate) mod traits; // a non-hiding variant of {kzg, zeromorph} pub(crate) mod kzg_commitment; pub(crate) mod non_hiding_kzg; -mod util; +pub(crate) mod util; // crate-private modules mod keccak; diff --git a/src/provider/non_hiding_zeromorph.rs b/src/provider/non_hiding_zeromorph.rs index e70e2d064..f7cea4b7c 100644 --- a/src/provider/non_hiding_zeromorph.rs +++ b/src/provider/non_hiding_zeromorph.rs @@ -443,19 +443,19 @@ fn eval_and_quotient_scalars(y: F, x: F, z: F, point: &[F]) -> (F, (Ve // = [- (y^i * x^(2^num_vars - d_i - 1) + z * (x^(2^i) * Φ_(n-i-1)(x^(2^(i+1))) - u_i * Φ_(n-i)(x^(2^i)))), i ∈ [0, num_vars-1]] #[allow(clippy::disallowed_methods)] let q_scalars = iter::successors(Some(F::ONE), |acc| Some(*acc * y)).take(num_vars) - .zip_eq(offsets_of_x) - // length: num_vars + 1 - .zip(squares_of_x) - // length: num_vars + 1 - .zip(&vs) - .zip_eq(&vs[1..]) - .zip_eq(point.iter().rev()) // assume variables come in BE form - .map( - |(((((power_of_y, offset_of_x), square_of_x), v_i), v_j), u_i)| { - (-(power_of_y * offset_of_x), -(z * (square_of_x * v_j - *u_i * v_i))) - }, - ) - .unzip(); + .zip_eq(offsets_of_x) + // length: num_vars + 1 + .zip(squares_of_x) + // length: num_vars + 1 + .zip(&vs) + .zip_eq(&vs[1..]) + .zip_eq(point.iter().rev()) // assume variables come in BE form + .map( + |(((((power_of_y, offset_of_x), square_of_x), v_i), v_j), u_i)| { + (-(power_of_y * offset_of_x), -(z * (square_of_x * v_j - *u_i * v_i))) + }, + ) + .unzip(); // -vs[0] * z = -z * (x^(2^num_vars) - 1) / (x - 1) = -z Φ_n(x) (-vs[0] * z, q_scalars) @@ -529,17 +529,18 @@ mod test { use rand_core::SeedableRng; use super::quotients; - use crate::errors::PCSError; - use crate::provider::non_hiding_kzg::{KZGProverKey, UVKZGCommitment, UVKZGPCS}; + use crate::{ + errors::PCSError, provider::{ keccak::Keccak256Transcript, - non_hiding_kzg::{UVKZGPoly, UniversalKZGParam}, + non_hiding_kzg::{KZGProverKey, UVKZGCommitment, UVKZGPoly, UniversalKZGParam, UVKZGPCS}, non_hiding_zeromorph::{ batched_lifted_degree_quotient, eval_and_quotient_scalars, trim, ZMEvaluation, ZMPCS, }, traits::DlogGroup, - Bn256Engine, + util::test_utils::prove_verify_from_num_vars, + Bn256Engine, Bn256EngineZM, }, spartan::polys::multilinear::MultilinearPolynomial, traits::{Engine as NovaEngine, Group, TranscriptEngineTrait, TranscriptReprTrait}, @@ -603,6 +604,13 @@ mod test { commit_open_verify_with::(); } + #[test] + fn test_multiple_polynomial_size() { + for num_vars in [4, 5, 6] { + prove_verify_from_num_vars::<_, ZMPCS>(num_vars); + } + } + #[test] fn test_quotients() { // Define size parameters diff --git a/src/provider/util/mod.rs b/src/provider/util/mod.rs index ec949c239..c7d12f20f 100644 --- a/src/provider/util/mod.rs +++ b/src/provider/util/mod.rs @@ -1,4 +1,4 @@ -/// Utilities for provider module +//! Utilities for provider module. pub(in crate::provider) mod fb_msm; pub mod msm { use halo2curves::msm::best_multiexp; @@ -10,3 +10,125 @@ pub mod msm { best_multiexp(scalars, bases) } } + +#[cfg(test)] +pub mod test_utils { + //! Contains utilities for testing and benchmarking. + use crate::spartan::polys::multilinear::MultilinearPolynomial; + use crate::traits::{ + commitment::CommitmentEngineTrait, evaluation::EvaluationEngineTrait, Engine, + }; + use ff::Field; + use rand::rngs::StdRng; + use rand_core::{CryptoRng, RngCore}; + + /// Returns a random polynomial, a point and calculate its evaluation. + pub fn random_poly_with_eval( + num_vars: usize, + mut rng: &mut R, + ) -> ( + MultilinearPolynomial<::Scalar>, + Vec<::Scalar>, + ::Scalar, + ) { + // Generate random polynomial and point. + let poly = MultilinearPolynomial::random(num_vars, &mut rng); + let point = (0..num_vars) + .map(|_| ::Scalar::random(&mut rng)) + .collect::>(); + + // Calculation evaluation of point over polynomial. + let eval = MultilinearPolynomial::evaluate_with(poly.evaluations(), &point); + + (poly, point, eval) + } + + /// Methods used to test the prove and verify flow of [`MultilinearPolynomial`] Commitment Schemes + /// (PCS). + /// + /// Generates a random polynomial and point from a seed to test a proving/verifying flow of one + /// of our [`EvaluationEngine`]. + pub(crate) fn prove_verify_from_num_vars>( + num_vars: usize, + ) { + use rand_core::SeedableRng; + + let mut rng = rand::rngs::StdRng::seed_from_u64(num_vars as u64); + + let (poly, point, eval) = random_poly_with_eval::(num_vars, &mut rng); + + // Mock commitment key. + let ck = E::CE::setup(b"test", 1 << num_vars); + // Commits to the provided vector using the provided generators. + let commitment = E::CE::commit(&ck, poly.evaluations()); + + prove_verify_with::(&ck, &commitment, &poly, &point, &eval, true) + } + + pub(crate) fn prove_verify_with>( + ck: &<::CE as CommitmentEngineTrait>::CommitmentKey, + commitment: &<::CE as CommitmentEngineTrait>::Commitment, + poly: &MultilinearPolynomial<::Scalar>, + point: &[::Scalar], + eval: &::Scalar, + evaluate_bad_proof: bool, + ) { + use crate::traits::TranscriptEngineTrait; + use std::ops::Add; + + // Generate Prover and verifier key for given commitment key. + let (prover_key, verifier_key) = EE::setup(ck); + + // Generate proof. + let mut prover_transcript = E::TE::new(b"TestEval"); + let proof = EE::prove( + ck, + &prover_key, + &mut prover_transcript, + commitment, + poly.evaluations(), + point, + eval, + ) + .unwrap(); + let pcp = prover_transcript.squeeze(b"c").unwrap(); + + // Verify proof. + let mut verifier_transcript = E::TE::new(b"TestEval"); + EE::verify( + &verifier_key, + &mut verifier_transcript, + commitment, + point, + eval, + &proof, + ) + .unwrap(); + let pcv = verifier_transcript.squeeze(b"c").unwrap(); + + // Check if the prover transcript and verifier transcript are kept in the same state. + assert_eq!(pcp, pcv); + + if evaluate_bad_proof { + // Generate another point to verify proof. Also produce eval. + let altered_verifier_point = point + .iter() + .map(|s| s.add(::Scalar::ONE)) + .collect::>(); + let altered_verifier_eval = + MultilinearPolynomial::evaluate_with(poly.evaluations(), &altered_verifier_point); + + // Verify proof, should fail. + let mut verifier_transcript = E::TE::new(b"TestEval"); + assert!(EE::verify( + &verifier_key, + &mut verifier_transcript, + commitment, + &altered_verifier_point, + &altered_verifier_eval, + &proof, + ) + .is_err()); + } + } +} diff --git a/src/spartan/polys/mod.rs b/src/spartan/polys/mod.rs index a1a192ef8..cee41b385 100644 --- a/src/spartan/polys/mod.rs +++ b/src/spartan/polys/mod.rs @@ -2,6 +2,6 @@ pub(crate) mod eq; pub(crate) mod identity; pub(crate) mod masked_eq; -pub(crate) mod multilinear; +pub mod multilinear; pub(crate) mod power; pub(crate) mod univariate; diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index 6e1ea97d9..5c6cc0703 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -63,8 +63,12 @@ impl MultilinearPolynomial { self.Z.len() } + /// Returns true if no evaluations. + pub fn is_empty(&self) -> bool { + self.Z.len() == 0 + } + /// Returns a random polynomial - /// pub fn random(num_vars: usize, mut rng: &mut R) -> Self { Self::new( std::iter::from_fn(|| Some(Scalar::random(&mut rng)))