diff --git a/w3f-plonk-common/src/kzg_acc.rs b/w3f-plonk-common/src/kzg_acc.rs index d49af79..8b487e7 100644 --- a/w3f-plonk-common/src/kzg_acc.rs +++ b/w3f-plonk-common/src/kzg_acc.rs @@ -4,11 +4,10 @@ use crate::{ColumnsCommited, ColumnsEvaluated, Proof}; use ark_ec::pairing::Pairing; use ark_ec::{CurveGroup, VariableBaseMSM}; use ark_ff::{PrimeField, Zero}; -use ark_std::iterable::Iterable; use ark_std::rand::Rng; use w3f_pcs::pcs::kzg::params::KzgVerifierKey; use w3f_pcs::pcs::kzg::{AccumulatedOpening, KZG}; -use w3f_pcs::pcs::{Commitment, PCS}; +use w3f_pcs::pcs::PCS; // Aggregates opennings for KZG commitments. // Somewhat similar to https://eprint.iacr.org/2020/499.pdf, section 8. diff --git a/w3f-ring-proof/src/lib.rs b/w3f-ring-proof/src/lib.rs index 239d20c..f44f898 100644 --- a/w3f-ring-proof/src/lib.rs +++ b/w3f-ring-proof/src/lib.rs @@ -153,11 +153,29 @@ mod tests { (pcs_params, piop_params) } - #[test] // cargo test test_ring_proof_kzg --release --features="print-trace" -- --show-output + // + // Batch vs sequential verification times (ms): + // + // | proofs | batch | sequential | speedup | + // |--------|--------|------------|---------| + // | 1 | 2.790 | 3.032 | 1.09x | + // | 2 | 3.218 | 6.425 | 2.00x | + // | 4 | 5.122 | 11.968 | 2.34x | + // | 8 | 6.487 | 23.922 | 3.69x | + // | 16 | 10.002 | 47.773 | 4.78x | + // | 32 | 16.601 | 95.570 | 5.76x | + // | 64 | 29.484 | 210.959 | 7.15x | + // | 128 | 52.170 | 422.217 | 8.09x | + // | 256 | 85.164 | 762.874 | 8.96x | + // + // Sequential verification scales linearly with proof count. Batch verification + // scales sub-linearly. + #[test] fn test_ring_proof_kzg() { - let (verifier, claims) = _test_ring_proof::>(2usize.pow(10), 10); - let t_verify_batch = start_timer!(|| "Verify Batch KZG"); + let batch_size: usize = 16; + let (verifier, claims) = _test_ring_proof::>(2usize.pow(10), batch_size); + let t_verify_batch = start_timer!(|| format!("Verify Batch KZG (batch={batch_size})")); let (blinded_pks, proofs) = claims.into_iter().unzip(); assert!(verifier.verify_batch_kzg(proofs, blinded_pks)); end_timer!(t_verify_batch); diff --git a/w3f-ring-proof/src/ring_verifier.rs b/w3f-ring-proof/src/ring_verifier.rs index 4f38cac..5f9aa2b 100644 --- a/w3f-ring-proof/src/ring_verifier.rs +++ b/w3f-ring-proof/src/ring_verifier.rs @@ -89,40 +89,76 @@ where } } -impl RingVerifier, Jubjub, T> +/// Batch verifier for KZG PCS +pub struct KzgBatchVerifier where E: Pairing, - Jubjub: TECurveConfig, + J: TECurveConfig, T: PlonkTranscript>, { + pub acc: KzgAccumulator, + pub verifier: RingVerifier, J, T>, +} + +impl KzgBatchVerifier +where + E: Pairing, + J: TECurveConfig, + T: PlonkTranscript>, +{ + /// Push a proof in the batch + pub fn push(&mut self, proof: RingProof>, result: Affine) { + let (challenges, mut rng) = self.verifier.plonk_verifier.restore_challenges( + &result, + &proof, + // '1' accounts for the quotient polynomial that is aggregated together with the columns + PiopVerifier:: as PCS<_>>::C, Affine>::N_COLUMNS + 1, + PiopVerifier:: as PCS<_>>::C, Affine>::N_CONSTRAINTS, + ); + let seed = self.verifier.piop_params.seed; + let seed_plus_result = (seed + result).into_affine(); + let domain_at_zeta = self.verifier.piop_params.domain.evaluate(challenges.zeta); + let piop = PiopVerifier::<_, _, Affine>::init( + domain_at_zeta, + self.verifier.fixed_columns_committed.clone(), + proof.column_commitments.clone(), + proof.columns_at_zeta.clone(), + (seed.x, seed.y), + (seed_plus_result.x, seed_plus_result.y), + ); + self.acc.accumulate(piop, proof, challenges, &mut rng); + } + + /// Batch verify + pub fn verify(&self) -> bool { + self.acc.verify() + } +} + +impl RingVerifier, J, T> +where + E: Pairing, + J: TECurveConfig, + T: PlonkTranscript>, +{ + /// Build a new batch verifier. + pub fn kzg_batch_verifier(self) -> KzgBatchVerifier { + KzgBatchVerifier { + acc: KzgAccumulator::::new(self.plonk_verifier.pcs_vk.clone()), + verifier: self, + } + } + // Verifies a batch of proofs against the same ring. pub fn verify_batch_kzg( - &self, + self, proofs: Vec>>, - results: Vec>, + results: Vec>, ) -> bool { - let mut acc = KzgAccumulator::::new(self.plonk_verifier.pcs_vk.clone()); + let mut batch = self.kzg_batch_verifier(); for (proof, result) in proofs.into_iter().zip(results) { - let (challenges, mut rng) = self.plonk_verifier.restore_challenges( - &result, - &proof, - // '1' accounts for the quotient polynomial that is aggregated together with the columns - PiopVerifier:: as PCS<_>>::C, Affine>::N_COLUMNS + 1, - PiopVerifier:: as PCS<_>>::C, Affine>::N_CONSTRAINTS, - ); - let seed = self.piop_params.seed; - let seed_plus_result = (seed + result).into_affine(); - let domain_at_zeta = self.piop_params.domain.evaluate(challenges.zeta); - let piop = PiopVerifier::<_, _, Affine>::init( - domain_at_zeta, - self.fixed_columns_committed.clone(), - proof.column_commitments.clone(), - proof.columns_at_zeta.clone(), - (seed.x, seed.y), - (seed_plus_result.x, seed_plus_result.y), - ); - acc.accumulate(piop, proof, challenges, &mut rng); + batch.push(proof, result); } - acc.verify() + batch.verify() } }