Skip to main content

w3f_ring_proof/
ring_verifier.rs

1use ark_ec::pairing::Pairing;
2use ark_ec::twisted_edwards::{Affine, TECurveConfig};
3use ark_ec::CurveGroup;
4use ark_ff::PrimeField;
5use ark_std::rand::RngCore;
6use w3f_pcs::pcs::kzg::KZG;
7use w3f_pcs::pcs::{RawVerifierKey, PCS};
8use w3f_plonk_common::kzg_acc::KzgAccumulator;
9use w3f_plonk_common::piop::VerifierPiop;
10use w3f_plonk_common::transcript::PlonkTranscript;
11use w3f_plonk_common::verifier::{Challenges, PlonkVerifier};
12
13use crate::piop::params::PiopParams;
14use crate::piop::{FixedColumnsCommitted, PiopVerifier, VerifierKey};
15use crate::{ArkTranscript, RingProof};
16use ark_std::vec::Vec;
17
18pub struct RingVerifier<F, CS, Jubjub, T = ArkTranscript>
19where
20    F: PrimeField,
21    CS: PCS<F>,
22    Jubjub: TECurveConfig<BaseField = F>,
23    T: PlonkTranscript<F, CS>,
24{
25    piop_params: PiopParams<F, Jubjub>,
26    fixed_columns_committed: FixedColumnsCommitted<F, CS::C>,
27    plonk_verifier: PlonkVerifier<F, CS, T>,
28}
29
30impl<F, CS, Jubjub, T> RingVerifier<F, CS, Jubjub, T>
31where
32    F: PrimeField,
33    CS: PCS<F>,
34    Jubjub: TECurveConfig<BaseField = F>,
35    T: PlonkTranscript<F, CS>,
36{
37    pub fn init(
38        verifier_key: VerifierKey<F, CS>,
39        piop_params: PiopParams<F, Jubjub>,
40        empty_transcript: T,
41    ) -> Self {
42        let pcs_vk = verifier_key.pcs_raw_vk.prepare();
43        let plonk_verifier = PlonkVerifier::init(pcs_vk, &verifier_key, empty_transcript);
44        Self {
45            piop_params,
46            fixed_columns_committed: verifier_key.fixed_columns_committed,
47            plonk_verifier,
48        }
49    }
50
51    pub fn verify(&self, proof: RingProof<F, CS>, result: Affine<Jubjub>) -> bool {
52        let (challenges, mut rng) = self.plonk_verifier.restore_challenges(
53            &result,
54            &proof,
55            // '1' accounts for the quotient polynomial that is aggregated together with the columns
56            PiopVerifier::<F, CS::C, Affine<Jubjub>>::N_COLUMNS + 1,
57            PiopVerifier::<F, CS::C, Affine<Jubjub>>::N_CONSTRAINTS,
58        );
59        let seed = self.piop_params.seed;
60        let seed_plus_result = (seed + result).into_affine();
61        let domain_at_zeta = self.piop_params.domain.evaluate(challenges.zeta);
62        let piop = PiopVerifier::<_, _, Affine<Jubjub>>::init(
63            domain_at_zeta,
64            self.fixed_columns_committed.clone(),
65            proof.column_commitments.clone(),
66            proof.columns_at_zeta.clone(),
67            (seed.x, seed.y),
68            (seed_plus_result.x, seed_plus_result.y),
69        );
70
71        self.plonk_verifier
72            .verify(piop, proof, challenges, &mut rng)
73    }
74
75    pub fn piop_params(&self) -> &PiopParams<F, Jubjub> {
76        &self.piop_params
77    }
78
79    pub fn verify_batch(
80        &self,
81        proofs: Vec<RingProof<F, CS>>,
82        results: Vec<Affine<Jubjub>>,
83    ) -> bool {
84        for (proof, result) in proofs.into_iter().zip(results) {
85            let res = self.verify(proof, result);
86            if !res {
87                return false;
88            }
89        }
90        true
91    }
92}
93
94/// Accumulating batch verifier for ring proofs using KZG polynomial commitment scheme.
95pub struct KzgBatchVerifier<E, J, T = ArkTranscript>
96where
97    E: Pairing,
98    J: TECurveConfig<BaseField = E::ScalarField>,
99    T: PlonkTranscript<E::ScalarField, KZG<E>>,
100{
101    pub acc: KzgAccumulator<E>,
102    pub verifier: RingVerifier<E::ScalarField, KZG<E>, J, T>,
103}
104
105/// A ring proof that has been preprocessed for batch verification.
106pub struct PreparedBatchItem<E, J>
107where
108    E: Pairing,
109    J: TECurveConfig<BaseField = E::ScalarField>,
110{
111    piop: PiopVerifier<E::ScalarField, <KZG<E> as PCS<E::ScalarField>>::C, Affine<J>>,
112    proof: RingProof<E::ScalarField, KZG<E>>,
113    challenges: Challenges<E::ScalarField>,
114    entropy: [u8; 32],
115}
116
117impl<E, J, T> KzgBatchVerifier<E, J, T>
118where
119    E: Pairing,
120    J: TECurveConfig<BaseField = E::ScalarField>,
121    T: PlonkTranscript<E::ScalarField, KZG<E>>,
122{
123    /// Prepares a ring proof for batch verification without accumulating it.
124    ///
125    /// Returns a `PreparedBatchItem` that can later be passed to `push_prepared`.
126    ///
127    /// This method is independent of the accumulator state, so multiple proofs can be
128    /// prepared in parallel (e.g., using `rayon`). Each prepared item is in the order
129    /// of a few KB, so for large batches you may want to prepare and push incrementally
130    /// rather than holding all prepared items in memory at once.
131    pub fn prepare(
132        &self,
133        proof: RingProof<E::ScalarField, KZG<E>>,
134        result: Affine<J>,
135    ) -> PreparedBatchItem<E, J> {
136        let (challenges, mut rng) = self.verifier.plonk_verifier.restore_challenges(
137            &result,
138            &proof,
139            // '1' accounts for the quotient polynomial that is aggregated together with the columns
140            PiopVerifier::<E::ScalarField, <KZG<E> as PCS<_>>::C, Affine<J>>::N_COLUMNS + 1,
141            PiopVerifier::<E::ScalarField, <KZG<E> as PCS<_>>::C, Affine<J>>::N_CONSTRAINTS,
142        );
143        let seed = self.verifier.piop_params.seed;
144        let seed_plus_result = (seed + result).into_affine();
145        let domain_at_zeta = self.verifier.piop_params.domain.evaluate(challenges.zeta);
146        let piop = PiopVerifier::<_, _, Affine<J>>::init(
147            domain_at_zeta,
148            self.verifier.fixed_columns_committed.clone(),
149            proof.column_commitments.clone(),
150            proof.columns_at_zeta.clone(),
151            (seed.x, seed.y),
152            (seed_plus_result.x, seed_plus_result.y),
153        );
154
155        // Pick some entropy from plonk verifier for later usage
156        let mut entropy = [0_u8; 32];
157        rng.fill_bytes(&mut entropy);
158
159        PreparedBatchItem {
160            piop,
161            proof,
162            challenges,
163            entropy,
164        }
165    }
166
167    /// Accumulates a previously prepared proof into the batch.
168    ///
169    /// This is the second step of the two-phase batch verification workflow:
170    /// 1. `prepare` - can be parallelized across multiple proofs
171    /// 2. `push_prepared` - must be called sequentially (mutates the accumulator)
172    ///
173    /// For simpler usage where parallelism isn't needed, use `push` instead.
174    pub fn push_prepared(&mut self, item: PreparedBatchItem<E, J>) {
175        let mut ts = self.verifier.plonk_verifier.transcript_prelude.clone();
176        ts._add_serializable(b"batch-entropy", &item.entropy);
177        self.acc
178            .accumulate(item.piop, item.proof, item.challenges, &mut ts.to_rng());
179    }
180
181    /// Adds a ring proof to the batch, preparing and accumulating it immediately.
182    ///
183    /// The proof's pairing equation is aggregated into the internal accumulator.
184    /// Call `verify` after pushing all proofs to perform the batched verification.
185    pub fn push(&mut self, proof: RingProof<E::ScalarField, KZG<E>>, result: Affine<J>) {
186        let item = self.prepare(proof, result);
187        self.push_prepared(item);
188    }
189
190    /// Verifies all accumulated proofs in a single batched pairing check.
191    pub fn verify(&self) -> bool {
192        self.acc.verify()
193    }
194}
195
196impl<E, J, T> RingVerifier<E::ScalarField, KZG<E>, J, T>
197where
198    E: Pairing,
199    J: TECurveConfig<BaseField = E::ScalarField>,
200    T: PlonkTranscript<E::ScalarField, KZG<E>>,
201{
202    /// Build a new batch verifier.
203    pub fn kzg_batch_verifier(self) -> KzgBatchVerifier<E, J, T> {
204        KzgBatchVerifier {
205            acc: KzgAccumulator::<E>::new(self.plonk_verifier.pcs_vk.clone()),
206            verifier: self,
207        }
208    }
209
210    /// Verifies a batch of proofs against the same ring.
211    pub fn verify_batch_kzg(
212        self,
213        proofs: Vec<RingProof<E::ScalarField, KZG<E>>>,
214        results: Vec<Affine<J>>,
215    ) -> bool {
216        let mut batch = self.kzg_batch_verifier();
217        for (proof, result) in proofs.into_iter().zip(results) {
218            batch.push(proof, result);
219        }
220        batch.verify()
221    }
222}