Skip to main content

w3f_ring_proof/
multi_ring_batch_verifier.rs

1use ark_ec::pairing::Pairing;
2use ark_ec::twisted_edwards::{Affine, TECurveConfig};
3use ark_ec::CurveGroup;
4use ark_std::rand::RngCore;
5use w3f_pcs::pcs::kzg::params::KzgVerifierKey;
6use w3f_pcs::pcs::kzg::KZG;
7use w3f_pcs::pcs::PCS;
8use w3f_plonk_common::kzg_acc::KzgAccumulator;
9use w3f_plonk_common::transcript::PlonkTranscript;
10use w3f_plonk_common::verifier::Challenges;
11
12use crate::piop::PiopVerifier;
13use crate::ring_verifier::RingVerifier;
14use crate::RingProof;
15
16/// A ring proof preprocessed for multi-ring batch verification.
17pub struct BatchItem<E, J>
18where
19    E: Pairing,
20    J: TECurveConfig<BaseField = E::ScalarField>,
21{
22    piop: PiopVerifier<E::ScalarField, <KZG<E> as PCS<E::ScalarField>>::C, Affine<J>>,
23    proof: RingProof<E::ScalarField, KZG<E>>,
24    challenges: Challenges<E::ScalarField>,
25    entropy: [u8; 32],
26}
27
28impl<E, J> BatchItem<E, J>
29where
30    E: Pairing,
31    J: TECurveConfig<BaseField = E::ScalarField>,
32{
33    /// Prepares a ring proof for batch verification without accumulating it.
34    ///
35    /// The returned item is independent of both any accumulator state and
36    /// the originating `RingVerifier`, so multiple proofs (even from
37    /// different rings) can be prepared in parallel.
38    pub fn new<T>(
39        verifier: &RingVerifier<E::ScalarField, KZG<E>, J, T>,
40        proof: RingProof<E::ScalarField, KZG<E>>,
41        result: Affine<J>,
42    ) -> Self
43    where
44        T: PlonkTranscript<E::ScalarField, KZG<E>>,
45    {
46        let (challenges, mut fs_rng) = verifier
47            .plonk_verifier
48            .restore_fs_with_rng::<PiopVerifier<_, _, Affine<J>>, _, _>(&result, &proof);
49        let seed = verifier.piop_params.seed;
50        let seed_plus_result = (seed + result).into_affine();
51        let domain_at_zeta = verifier.piop_params.domain.evaluate(challenges.zeta);
52        let piop = PiopVerifier::<_, _, Affine<J>>::init(
53            domain_at_zeta,
54            verifier.fixed_columns_committed.clone(),
55            proof.column_commitments.clone(),
56            proof.columns_at_zeta.clone(),
57            (seed.x, seed.y),
58            (seed_plus_result.x, seed_plus_result.y),
59        );
60
61        let mut entropy = [0_u8; 32];
62        fs_rng.fill_bytes(&mut entropy);
63
64        Self {
65            piop,
66            proof,
67            challenges,
68            entropy,
69        }
70    }
71}
72
73/// Accumulating batch verifier for ring proofs across one or more rings.
74///
75/// Accumulates proofs from one or more rings (keysets) into a single batched
76/// pairing check. All rings must share the same KZG SRS.
77///
78/// Holds its own transcript instance, cloned on each `push_prepared` call so
79/// the per-proof entropy can be folded in without touching the originating
80/// `RingVerifier`. Per-proof independence is ensured by the entropy derived
81/// during preparation (which absorbs the full proof via the per-ring
82/// transcript), so the base transcript only needs to be deterministic, not
83/// proof-specific.
84pub struct BatchVerifier<E: Pairing, T>
85where
86    T: PlonkTranscript<E::ScalarField, KZG<E>>,
87{
88    acc: KzgAccumulator<E>,
89    transcript: T,
90}
91
92impl<E: Pairing, T> BatchVerifier<E, T>
93where
94    T: PlonkTranscript<E::ScalarField, KZG<E>>,
95{
96    /// Creates a new multi-ring batch verifier.
97    pub fn new(kzg_vk: KzgVerifierKey<E>, transcript: T) -> Self {
98        Self {
99            acc: KzgAccumulator::<E>::new(kzg_vk),
100            transcript,
101        }
102    }
103
104    /// Adds a ring proof to the batch.
105    pub fn push<J>(
106        &mut self,
107        verifier: &RingVerifier<E::ScalarField, KZG<E>, J, T>,
108        proof: RingProof<E::ScalarField, KZG<E>>,
109        result: Affine<J>,
110    ) where
111        J: TECurveConfig<BaseField = E::ScalarField>,
112    {
113        self.push_prepared(BatchItem::new(verifier, proof, result));
114    }
115
116    /// Accumulates a prepared [`BatchItem`] into the batch.
117    ///
118    /// Equivalent to [`push`](Self::push), but splits the work: the caller
119    /// builds the [`BatchItem`] (transcript replay, challenge derivation,
120    /// PIOP setup) separately from accumulation. Useful when preparation
121    /// should be parallelized. `BatchItem::new` is independent of the
122    /// accumulator state, so multiple items can be built in parallel and
123    /// then pushed sequentially here.
124    pub fn push_prepared<J>(&mut self, item: BatchItem<E, J>)
125    where
126        J: TECurveConfig<BaseField = E::ScalarField>,
127    {
128        let mut ts = self.transcript.clone();
129        ts._add_serializable(b"batch-entropy", &item.entropy);
130        self.acc
131            .accumulate(item.piop, item.proof, item.challenges, &mut ts.to_rng());
132    }
133
134    /// Verifies all accumulated proofs in a single batched pairing check.
135    pub fn verify(&self) -> bool {
136        self.acc.verify()
137    }
138}