diff --git a/w3f-ring-proof/Cargo.toml b/w3f-ring-proof/Cargo.toml index 6f1fef5..d189244 100644 --- a/w3f-ring-proof/Cargo.toml +++ b/w3f-ring-proof/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "w3f-ring-proof" -version = "0.0.6" +version = "0.0.7" edition = "2021" authors = ["Sergey Vasilyev "] license = "MIT/Apache-2.0" diff --git a/w3f-ring-proof/benches/ring_proof.rs b/w3f-ring-proof/benches/ring_proof.rs index b0ab568..56f8158 100644 --- a/w3f-ring-proof/benches/ring_proof.rs +++ b/w3f-ring-proof/benches/ring_proof.rs @@ -195,6 +195,9 @@ fn bench_verify_batch_kzg(c: &mut Criterion) { .map(|_| generate_proof(&piop_params, &pcs_params, &pks, rng)) .collect(); + let (_, verifier_key) = index::<_, CS, _>(&pcs_params, &piop_params, &pks); + let verifier = RingVerifier::init(verifier_key, piop_params, make_transcript()); + for batch_size in [1, 4, 16, 32] { let (results, proofs): (Vec<_>, Vec<_>) = claims[..batch_size].iter().cloned().unzip(); @@ -202,15 +205,9 @@ fn bench_verify_batch_kzg(c: &mut Criterion) { BenchmarkId::new("kzg_accumulator", batch_size), &batch_size, |b, _| { - // Recreate verifier each iteration since verify_batch_kzg consumes self. b.iter_batched( - || { - let (_, vk) = index::<_, CS, _>(&pcs_params, &piop_params, &pks); - let verifier = - RingVerifier::init(vk, piop_params.clone(), make_transcript()); - (verifier, proofs.clone(), results.clone()) - }, - |(verifier, proofs, results)| verifier.verify_batch_kzg(proofs, results), + || (proofs.clone(), results.clone()), + |(proofs, results)| verifier.verify_batch_kzg(proofs, results), BatchSize::LargeInput, ); }, diff --git a/w3f-ring-proof/src/lib.rs b/w3f-ring-proof/src/lib.rs index 4c3ae5f..738a845 100644 --- a/w3f-ring-proof/src/lib.rs +++ b/w3f-ring-proof/src/lib.rs @@ -261,8 +261,11 @@ mod tests { } // Multi-ring batch verification - use crate::multi_ring_batch_verifier::MultiRingBatchVerifier; - let mut batch = MultiRingBatchVerifier::new(verifier_a.pcs_vk().clone()); + use crate::multi_ring_batch_verifier::BatchVerifier; + let mut batch = BatchVerifier::new( + verifier_a.pcs_vk().clone(), + verifier_a.plonk_verifier.transcript_prelude.clone(), + ); for (result, proof) in claims_a { batch.push(&verifier_a, proof, result); } diff --git a/w3f-ring-proof/src/multi_ring_batch_verifier.rs b/w3f-ring-proof/src/multi_ring_batch_verifier.rs index 3d048f5..dc19b33 100644 --- a/w3f-ring-proof/src/multi_ring_batch_verifier.rs +++ b/w3f-ring-proof/src/multi_ring_batch_verifier.rs @@ -15,53 +15,33 @@ use crate::ring_verifier::RingVerifier; use crate::RingProof; /// A ring proof preprocessed for multi-ring batch verification. -/// -/// Holds a reference to the `RingVerifier` that was used during preparation, -/// so that `push_prepared` can access the correct ring's transcript prelude. -pub struct PreparedMultiRingItem<'a, E, J, T> +pub struct BatchItem where E: Pairing, J: TECurveConfig, - T: PlonkTranscript>, { - verifier: &'a RingVerifier, J, T>, piop: PiopVerifier as PCS>::C, Affine>, proof: RingProof>, challenges: Challenges, entropy: [u8; 32], } -/// Accumulating batch verifier for ring proofs across multiple rings. -/// -/// Unlike `KzgBatchVerifier` which is tied to a single ring, -/// this verifier can accumulate proofs from different rings (keysets) -/// into a single batched pairing check. -/// -/// All rings must share the same KZG SRS (same `KzgVerifierKey`). -pub struct MultiRingBatchVerifier { - acc: KzgAccumulator, -} - -impl MultiRingBatchVerifier { - /// Creates a new multi-ring batch verifier. - pub fn new(kzg_vk: KzgVerifierKey) -> Self { - Self { - acc: KzgAccumulator::::new(kzg_vk), - } - } - +impl BatchItem +where + E: Pairing, + J: TECurveConfig, +{ /// Prepares a ring proof for batch verification without accumulating it. /// - /// The returned item holds a reference to the `verifier` and is independent - /// of the accumulator state, so multiple proofs (even from different rings) - /// can be prepared in parallel. - pub fn prepare<'a, J, T>( - verifier: &'a RingVerifier, J, T>, + /// The returned item is independent of both any accumulator state and + /// the originating `RingVerifier`, so multiple proofs (even from + /// different rings) can be prepared in parallel. + pub fn new( + verifier: &RingVerifier, J, T>, proof: RingProof>, result: Affine, - ) -> PreparedMultiRingItem<'a, E, J, T> + ) -> Self where - J: TECurveConfig, T: PlonkTranscript>, { let (challenges, mut rng) = verifier.plonk_verifier.restore_challenges( @@ -85,43 +65,72 @@ impl MultiRingBatchVerifier { let mut entropy = [0_u8; 32]; rng.fill_bytes(&mut entropy); - PreparedMultiRingItem { - verifier, + Self { piop, proof, challenges, entropy, } } +} - /// Accumulates a previously prepared proof into the batch. - /// - /// This is the second step of the two-phase batch verification workflow: - /// 1. `prepare` - can be parallelized across multiple proofs - /// 2. `push_prepared` - must be called sequentially (mutates the accumulator) - pub fn push_prepared(&mut self, item: PreparedMultiRingItem<'_, E, J, T>) - where - J: TECurveConfig, - T: PlonkTranscript>, - { - let mut ts = item.verifier.plonk_verifier.transcript_prelude.clone(); - ts._add_serializable(b"batch-entropy", &item.entropy); - self.acc - .accumulate(item.piop, item.proof, item.challenges, &mut ts.to_rng()); +/// Accumulating batch verifier for ring proofs across one or more rings. +/// +/// Accumulates proofs from one or more rings (keysets) into a single batched +/// pairing check. All rings must share the same KZG SRS. +/// +/// Holds its own transcript instance, cloned on each `push_prepared` call so +/// the per-proof entropy can be folded in without touching the originating +/// `RingVerifier`. The transcript's initial state is not load-bearing; any +/// valid `T` works (e.g. the prelude of any ring verifier being batched). +pub struct BatchVerifier +where + T: PlonkTranscript>, +{ + acc: KzgAccumulator, + transcript: T, +} + +impl BatchVerifier +where + T: PlonkTranscript>, +{ + /// Creates a new multi-ring batch verifier. + pub fn new(kzg_vk: KzgVerifierKey, transcript: T) -> Self { + Self { + acc: KzgAccumulator::::new(kzg_vk), + transcript, + } } - /// Adds a ring proof to the batch, preparing and accumulating it immediately. - pub fn push( + /// Adds a ring proof to the batch. + pub fn push( &mut self, verifier: &RingVerifier, J, T>, proof: RingProof>, result: Affine, ) where J: TECurveConfig, - T: PlonkTranscript>, { - let item = Self::prepare(verifier, proof, result); - self.push_prepared(item); + self.push_prepared(BatchItem::new(verifier, proof, result)); + } + + /// Accumulates a prepared [`BatchItem`] into the batch. + /// + /// Equivalent to [`push`](Self::push), but splits the work: the caller + /// builds the [`BatchItem`] (transcript replay, challenge derivation, + /// PIOP setup) separately from accumulation. Useful when preparation + /// should be parallelized. `BatchItem::new` is independent of the + /// accumulator state, so multiple items can be built in parallel and + /// then pushed sequentially here. + pub fn push_prepared(&mut self, item: BatchItem) + where + J: TECurveConfig, + { + let mut ts = self.transcript.clone(); + ts._add_serializable(b"batch-entropy", &item.entropy); + self.acc + .accumulate(item.piop, item.proof, item.challenges, &mut ts.to_rng()); } /// Verifies all accumulated proofs in a single batched pairing check. diff --git a/w3f-ring-proof/src/ring_verifier.rs b/w3f-ring-proof/src/ring_verifier.rs index 9765b11..39b6309 100644 --- a/w3f-ring-proof/src/ring_verifier.rs +++ b/w3f-ring-proof/src/ring_verifier.rs @@ -2,14 +2,13 @@ use ark_ec::pairing::Pairing; use ark_ec::twisted_edwards::{Affine, TECurveConfig}; use ark_ec::CurveGroup; use ark_ff::PrimeField; -use ark_std::rand::RngCore; use w3f_pcs::pcs::kzg::KZG; use w3f_pcs::pcs::{RawVerifierKey, PCS}; -use w3f_plonk_common::kzg_acc::KzgAccumulator; use w3f_plonk_common::piop::VerifierPiop; use w3f_plonk_common::transcript::PlonkTranscript; -use w3f_plonk_common::verifier::{Challenges, PlonkVerifier}; +use w3f_plonk_common::verifier::PlonkVerifier; +use crate::multi_ring_batch_verifier::BatchVerifier; use crate::piop::params::PiopParams; use crate::piop::{FixedColumnsCommitted, PiopVerifier, VerifierKey}; use crate::{ArkTranscript, RingProof}; @@ -95,131 +94,25 @@ where } } -/// Accumulating batch verifier for ring proofs using KZG polynomial commitment scheme. -pub struct KzgBatchVerifier -where - E: Pairing, - J: TECurveConfig, - T: PlonkTranscript>, -{ - pub acc: KzgAccumulator, - pub verifier: RingVerifier, J, T>, -} - -/// A ring proof that has been preprocessed for batch verification. -pub struct PreparedBatchItem -where - E: Pairing, - J: TECurveConfig, -{ - piop: PiopVerifier as PCS>::C, Affine>, - proof: RingProof>, - challenges: Challenges, - entropy: [u8; 32], -} - -impl KzgBatchVerifier -where - E: Pairing, - J: TECurveConfig, - T: PlonkTranscript>, -{ - /// Prepares a ring proof for batch verification without accumulating it. - /// - /// Returns a `PreparedBatchItem` that can later be passed to `push_prepared`. - /// - /// This method is independent of the accumulator state, so multiple proofs can be - /// prepared in parallel (e.g., using `rayon`). Each prepared item is in the order - /// of a few KB, so for large batches you may want to prepare and push incrementally - /// rather than holding all prepared items in memory at once. - pub fn prepare( - &self, - proof: RingProof>, - result: Affine, - ) -> PreparedBatchItem { - 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), - ); - - // Pick some entropy from plonk verifier for later usage - let mut entropy = [0_u8; 32]; - rng.fill_bytes(&mut entropy); - - PreparedBatchItem { - piop, - proof, - challenges, - entropy, - } - } - - /// Accumulates a previously prepared proof into the batch. - /// - /// This is the second step of the two-phase batch verification workflow: - /// 1. `prepare` - can be parallelized across multiple proofs - /// 2. `push_prepared` - must be called sequentially (mutates the accumulator) - /// - /// For simpler usage where parallelism isn't needed, use `push` instead. - pub fn push_prepared(&mut self, item: PreparedBatchItem) { - let mut ts = self.verifier.plonk_verifier.transcript_prelude.clone(); - ts._add_serializable(b"batch-entropy", &item.entropy); - self.acc - .accumulate(item.piop, item.proof, item.challenges, &mut ts.to_rng()); - } - - /// Adds a ring proof to the batch, preparing and accumulating it immediately. - /// - /// The proof's pairing equation is aggregated into the internal accumulator. - /// Call `verify` after pushing all proofs to perform the batched verification. - pub fn push(&mut self, proof: RingProof>, result: Affine) { - let item = self.prepare(proof, result); - self.push_prepared(item); - } - - /// Verifies all accumulated proofs in a single batched pairing check. - 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. + /// Verifies a batch of proofs against this ring in a single batched + /// pairing check, using a `MultiRingBatchVerifier` under the hood. pub fn verify_batch_kzg( - self, + &self, proofs: Vec>>, results: Vec>, ) -> bool { - let mut batch = self.kzg_batch_verifier(); + let mut batch = BatchVerifier::new( + self.plonk_verifier.pcs_vk.clone(), + self.plonk_verifier.transcript_prelude.clone(), + ); for (proof, result) in proofs.into_iter().zip(results) { - batch.push(proof, result); + batch.push(self, proof, result); } batch.verify() }