Skip to main content

ark_vrf/
ring.rs

1//! # Ring VRF
2//!
3//! Anonymized ring VRF combining Pedersen VRF with the ring proof scheme derived
4//! from [CSSV22](https://eprint.iacr.org/2022/1205). Proves that a single blinded
5//! key is a member of a committed ring without revealing which one.
6//!
7//! This module is gated by the `ring` feature.
8//!
9//! ## Usage
10//!
11//! ```rust,ignore
12//! use ark_vrf::suites::bandersnatch::*;
13//! use ark_vrf::ring::Prover;
14//!
15//! const RING_SIZE: usize = 100;
16//! let prover_key_index = 3;
17//!
18//! // Create a ring of public keys
19//! let mut ring = (0..RING_SIZE)
20//!     .map(|i| {
21//!         let mut seed = [0u8; 32];
22//!         seed[..8].copy_from_slice(&i.to_le_bytes());
23//!         Secret::from_seed(seed).public().0
24//!     })
25//!     .collect::<Vec<_>>();
26//! ring[prover_key_index] = public.0;
27//!
28//! // Initialize ring parameters
29//! let ring_setup = RingSetup::from_seed(RING_SIZE, [0x42; 32]);
30//! let ring_ctx = ring_setup.ring_context();
31//!
32//! // Proving
33//! let prover_key = ring_setup.prover_key(&ring).unwrap();
34//! let prover = ring_ctx.ring_prover(prover_key, prover_key_index);
35//! let io = secret.vrf_io(input);
36//! let proof = secret.prove(io, b"aux data", &prover);
37//!
38//! // Verification
39//! use ark_vrf::ring::Verifier;
40//! let verifier_key = ring_setup.verifier_key(&ring).unwrap();
41//! let verifier = ring_ctx.ring_verifier(verifier_key);
42//! let result = Public::verify(io, b"aux data", &proof, &verifier);
43//!
44//! // Efficient verification with commitment
45//! let ring_commitment = verifier_key.commitment();
46//! let reconstructed_key = ring_setup.verifier_key_from_commitment(ring_commitment);
47//!
48//! // Same, without the setup: the PCS verifier params are a few points,
49//! // independent of ring size, and can be distributed separately.
50//! let pcs_params = ring_setup.pcs_verifier_params();
51//! let reconstructed_key =
52//!     ark_vrf::ring::verifier_key_from_commitment::<BandersnatchSha512Ell2>(ring_commitment, pcs_params);
53//! ```
54
55use crate::*;
56use ark_ec::{
57    pairing::Pairing,
58    twisted_edwards::{Affine as TEAffine, TECurveConfig},
59};
60use ark_std::ops::Range;
61use pedersen::{PedersenSuite, Proof as PedersenProof};
62use utils::te_sw_map::TEMapping;
63use w3f_ring_proof as ring_proof;
64
65/// Seed hashed to curve to produce [`RingSuite::ACCUMULATOR_BASE`] in built-in suites.
66pub const ACCUMULATOR_BASE_SEED: &[u8] = b"ring-accumulator";
67
68/// Seed hashed to curve to produce [`RingSuite::PADDING`] in built-in suites.
69pub const PADDING_SEED: &[u8] = b"ring-padding";
70
71/// Ring suite.
72///
73/// This trait provides the cryptographic primitives needed for ring VRF signatures.
74/// All required bounds are expressed directly on the associated type for better ergonomics.
75pub trait RingSuite:
76    PedersenSuite<
77    Affine: AffineRepr<BaseField: ark_ff::PrimeField, Config: TECurveConfig + Clone>
78                + TEMapping<<Self::Affine as AffineRepr>::Config>,
79>
80{
81    /// Pairing type.
82    type Pairing: ark_ec::pairing::Pairing<ScalarField = BaseField<Self>>;
83
84    /// Accumulator base.
85    ///
86    /// In order for the ring-proof backend to work correctly, this is required to be
87    /// in the prime order subgroup.
88    const ACCUMULATOR_BASE: AffinePoint<Self>;
89
90    /// Padding point with unknown discrete log.
91    const PADDING: AffinePoint<Self>;
92}
93
94/// KZG Polynomial Commitment Scheme.
95pub type Kzg<S> = ring_proof::pcs::kzg::KZG<<S as RingSuite>::Pairing>;
96
97/// KZG commitment.
98pub type PcsCommitment<S> = <Kzg<S> as ring_proof::pcs::PCS<BaseField<S>>>::C;
99
100/// KZG Polynomial Commitment Scheme parameters.
101///
102/// Basically powers of tau SRS.
103pub type PcsParams<S> = ring_proof::pcs::kzg::urs::URS<<S as RingSuite>::Pairing>;
104
105/// PCS parameters required by the verifier.
106///
107/// A few points extracted from the SRS, independent of ring size. Together
108/// with a [`RingCommitment`] it is sufficient to reconstruct a
109/// [`RingVerifierKey`] via [`verifier_key_from_commitment`], without access
110/// to the full [`RingSetup`].
111pub type PcsVerifierParams<S> = <PcsParams<S> as ring_proof::pcs::PcsParams>::RVK;
112
113/// Polynomial Interactive Oracle Proof (IOP) parameters.
114///
115/// Basically all the application specific parameters required to construct and
116/// verify the ring proof.
117pub type PiopParams<S> = ring_proof::PiopParams<TEAffine<CurveConfig<S>>>;
118
119/// Ring keys commitment.
120pub type RingCommitment<S> = ring_proof::FixedColumnsCommitted<BaseField<S>, PcsCommitment<S>>;
121
122/// Ring prover key.
123pub type RingProverKey<S> = ring_proof::ProverKey<BaseField<S>, Kzg<S>, TEAffine<CurveConfig<S>>>;
124
125/// Ring verifier key.
126pub type RingVerifierKey<S> = ring_proof::VerifierKey<BaseField<S>, Kzg<S>>;
127
128/// Ring prover.
129pub type RingProver<S> = ring_proof::ring_prover::RingProver<BaseField<S>, Kzg<S>, CurveConfig<S>>;
130
131/// Ring verifier.
132pub type RingVerifier<S> =
133    ring_proof::ring_verifier::RingVerifier<BaseField<S>, Kzg<S>, CurveConfig<S>>;
134
135/// Multi-ring KZG batch verifier.
136///
137/// Accumulates ring proofs from one or more rings (sharing the same KZG SRS)
138/// into a single batched pairing check.
139pub type RingBatchVerifier<S> = ring_proof::multi_ring_batch_verifier::BatchVerifier<
140    <S as RingSuite>::Pairing,
141    ring_proof::ArkTranscript,
142>;
143
144/// Raw ring proof.
145///
146/// This is the primitive ring proof used in conjunction with Pedersen proof to
147/// construct the actual ring vrf proof [`Proof`].
148pub type RingBareProof<S> = ring_proof::RingProof<BaseField<S>, Kzg<S>>;
149
150/// Ring VRF proof.
151///
152/// Two-part zero-knowledge proof with signer anonymity:
153/// - `pedersen_proof`: Key commitment and VRF correctness proof
154/// - `ring_proof`: Membership proof binding the commitment to the ring
155///
156/// Deserialization via [`CanonicalDeserialize`] includes subgroup checks for
157/// curve points, so deserialized proofs are guaranteed to contain valid points.
158#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)]
159pub struct Proof<S: RingSuite> {
160    /// Pedersen VRF proof (key commitment and VRF correctness).
161    pub pedersen_proof: PedersenProof<S>,
162    /// Ring membership proof binding the key commitment to the ring.
163    pub ring_proof: RingBareProof<S>,
164}
165
166/// Trait for types that can generate Ring VRF proofs.
167pub trait Prover<S: RingSuite> {
168    /// Generate a proof for the given VRF I/O pairs and additional data.
169    ///
170    /// Multiple I/O pairs are delinearized into a single merged pair before proving.
171    fn prove(
172        &self,
173        ios: impl AsRef<[VrfIo<S>]>,
174        ad: impl AsRef<[u8]>,
175        prover: &RingProver<S>,
176    ) -> Proof<S>;
177}
178
179/// Trait for entities that can verify Ring VRF proofs.
180///
181/// Verifies that a VRF output was correctly derived using a secret key
182/// belonging to one of the ring's public keys, without revealing which one.
183///
184/// All curve points involved in verification (I/O pairs and proof points)
185/// are assumed to be in the prime-order subgroup. This is guaranteed when
186/// points are constructed through checked constructors ([`Input::from_affine`],
187/// [`Output::from_affine`]) or through trusted operations like [`Input::new`]
188/// (hash-to-curve) and [`Secret::vrf_io`]. Proof points are guaranteed valid
189/// when deserialized via [`CanonicalDeserialize`] (which includes subgroup
190/// checks) or produced by [`Prover::prove`].
191///
192/// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places
193/// the burden of subgroup validation on the caller. Passing points with
194/// cofactor components leads to undefined verification behavior.
195pub trait Verifier<S: RingSuite> {
196    /// Verify a proof for the given VRF I/O pairs and additional data.
197    ///
198    /// Multiple I/O pairs are delinearized into a single merged pair before verifying.
199    ///
200    /// Returns `Ok(())` if verification succeeds, `Err(Error::VerificationFailure)` otherwise.
201    fn verify(
202        ios: impl AsRef<[VrfIo<S>]>,
203        ad: impl AsRef<[u8]>,
204        sig: &Proof<S>,
205        verifier: &RingVerifier<S>,
206    ) -> Result<(), Error>;
207}
208
209impl<S: RingSuite> Prover<S> for Secret<S> {
210    fn prove(
211        &self,
212        ios: impl AsRef<[VrfIo<S>]>,
213        ad: impl AsRef<[u8]>,
214        ring_prover: &RingProver<S>,
215    ) -> Proof<S> {
216        use pedersen::Prover as PedersenProver;
217        let (pedersen_proof, secret_blinding) = <Self as PedersenProver<S>>::prove(self, ios, ad);
218        let ring_proof = ring_prover.prove(secret_blinding);
219        Proof {
220            pedersen_proof,
221            ring_proof,
222        }
223    }
224}
225
226impl<S: RingSuite> Verifier<S> for Public<S> {
227    fn verify(
228        ios: impl AsRef<[VrfIo<S>]>,
229        ad: impl AsRef<[u8]>,
230        proof: &Proof<S>,
231        verifier: &RingVerifier<S>,
232    ) -> Result<(), Error> {
233        use pedersen::Verifier as PedersenVerifier;
234        <Self as PedersenVerifier<S>>::verify(ios, ad, &proof.pedersen_proof)?;
235        let key_commitment = proof
236            .pedersen_proof
237            .key_commitment()
238            .into_te()
239            .ok_or(Error::InvalidData)?;
240        if !verifier.verify(proof.ring_proof.clone(), key_commitment) {
241            return Err(Error::VerificationFailure);
242        }
243        Ok(())
244    }
245}
246
247/// Lightweight ring proof context.
248///
249/// Contains only the PIOP parameters needed to construct prover and verifier
250/// instances from pre-built keys, without the KZG SRS required for key generation.
251///
252/// Cheap to construct from a ring size alone via [`RingContext::new`], or
253/// extractable from a [`RingSetup`] via [`RingSetup::ring_context`].
254#[derive(Clone)]
255pub struct RingContext<S: RingSuite> {
256    /// PIOP parameters.
257    pub piop_params: PiopParams<S>,
258}
259
260impl<S: RingSuite> RingContext<S> {
261    /// Construct context for the given ring size.
262    pub fn new(ring_size: usize) -> Self {
263        Self::construct(ring_size, true)
264    }
265
266    /// Construct a context whose provers generate deterministic proofs.
267    ///
268    /// Column blinding is disabled: proofs are reproducible, thus NOT zero-knowledge,
269    /// but remain valid for verifiers using a regular context for the same ring size.
270    /// Useful for reproducible test vectors generation.
271    pub fn new_without_blinding(ring_size: usize) -> Self {
272        Self::construct(ring_size, false)
273    }
274
275    fn construct(ring_size: usize, blinding: bool) -> Self {
276        let domain_size = piop_domain_size::<S>(ring_size);
277        let mut domain =
278            ring_proof::Domain::with_zk_rows(domain_size, ring_proof::piop::params::ZK_ROWS);
279        if !blinding {
280            domain = domain.without_blinding();
281        }
282        let piop_params = PiopParams::<S>::setup(
283            domain,
284            S::BLINDING_BASE
285                .into_te()
286                .expect("BLINDING_BASE must not be identity"),
287            S::ACCUMULATOR_BASE
288                .into_te()
289                .expect("ACCUMULATOR_BASE must not be identity"),
290            S::PADDING.into_te().expect("PADDING must not be identity"),
291        );
292        Self { piop_params }
293    }
294
295    /// The max ring size this context is able to handle.
296    #[inline(always)]
297    pub fn max_ring_size(&self) -> usize {
298        self.piop_params.keyset_part_size
299    }
300
301    /// Create a prover instance for a specific position in the ring.
302    pub fn ring_prover(&self, prover_key: RingProverKey<S>, key_index: usize) -> RingProver<S> {
303        self.clone().into_ring_prover(prover_key, key_index)
304    }
305
306    /// Create a verifier instance from a verifier key.
307    pub fn ring_verifier(&self, verifier_key: RingVerifierKey<S>) -> RingVerifier<S> {
308        self.clone().into_ring_verifier(verifier_key)
309    }
310
311    /// Create a prover instance, consuming the context to avoid cloning.
312    pub fn into_ring_prover(self, prover_key: RingProverKey<S>, key_index: usize) -> RingProver<S> {
313        RingProver::<S>::init(
314            prover_key,
315            self.piop_params,
316            key_index,
317            ring_proof::ArkTranscript::new(S::SUITE_ID),
318        )
319    }
320
321    /// Create a verifier instance, consuming the context to avoid cloning.
322    pub fn into_ring_verifier(self, verifier_key: RingVerifierKey<S>) -> RingVerifier<S> {
323        RingVerifier::<S>::init(
324            verifier_key,
325            self.piop_params,
326            ring_proof::ArkTranscript::new(S::SUITE_ID),
327        )
328    }
329}
330
331/// Ring proof setup.
332///
333/// Contains the cryptographic parameters needed for ring proof key construction,
334/// proving and verification:
335/// - `pcs_params`: Polynomial Commitment Scheme parameters (KZG setup)
336/// - `ring_ctx`: Ring context containing the PIOP parameters
337#[derive(Clone)]
338pub struct RingSetup<S: RingSuite> {
339    /// PCS parameters.
340    pub pcs_params: PcsParams<S>,
341    /// Ring context (PIOP parameters).
342    pub ring_ctx: RingContext<S>,
343}
344
345impl<S: RingSuite> core::ops::Deref for RingSetup<S> {
346    type Target = RingContext<S>;
347
348    fn deref(&self) -> &Self::Target {
349        &self.ring_ctx
350    }
351}
352
353impl<S: RingSuite> RingSetup<S> {
354    /// Construct deterministic ring proof params for the given ring size.
355    ///
356    /// Creates parameters using a transcript-based RNG seeded with `seed`.
357    pub fn from_seed(ring_size: usize, seed: [u8; 32]) -> Self {
358        let mut t = S::Transcript::new(S::SUITE_ID);
359        t.absorb_raw(&seed);
360        let mut rng = t.to_rng();
361        Self::from_rand(ring_size, &mut rng)
362    }
363
364    /// Construct random ring proof params for the given ring size.
365    ///
366    /// Generates a new KZG setup with sufficient degree to support the specified ring size.
367    pub fn from_rand(ring_size: usize, rng: &mut impl ark_std::rand::RngCore) -> Self {
368        use ring_proof::pcs::PCS;
369        let max_degree = pcs_domain_size::<S>(ring_size) - 1;
370        let pcs_params = Kzg::<S>::setup(max_degree, rng);
371        Self::from_pcs_params(ring_size, pcs_params).expect("PCS params is correct")
372    }
373
374    /// Construct ring proof params from existing KZG setup.
375    ///
376    /// Truncates the setup if larger than needed, or returns an error if it is
377    /// insufficient for the specified ring size.
378    pub fn from_pcs_params(ring_size: usize, mut pcs_params: PcsParams<S>) -> Result<Self, Error> {
379        let pcs_domain_size = pcs_domain_size::<S>(ring_size);
380        if pcs_params.powers_in_g1.len() < pcs_domain_size || pcs_params.powers_in_g2.len() < 2 {
381            return Err(Error::InvalidData);
382        }
383        // Keep only the required powers of tau
384        pcs_params.powers_in_g1.truncate(pcs_domain_size);
385        pcs_params.powers_in_g2.truncate(2);
386
387        Ok(Self {
388            pcs_params,
389            ring_ctx: RingContext::new(ring_size),
390        })
391    }
392
393    /// Create a prover key for the given ring of public keys.
394    ///
395    /// Returns `Error::InvalidData` if `pks` exceeds the max ring size.
396    pub fn prover_key(&self, pks: &[AffinePoint<S>]) -> Result<RingProverKey<S>, Error> {
397        if pks.len() > self.piop_params.keyset_part_size {
398            return Err(Error::InvalidData);
399        }
400        let pks = TEMapping::to_te_slice(pks).ok_or(Error::InvalidData)?;
401        Ok(ring_proof::index(&self.pcs_params, &self.piop_params, &pks).0)
402    }
403
404    /// Create a verifier key for the given ring of public keys.
405    ///
406    /// Returns `Error::InvalidData` if `pks` exceeds the max ring size.
407    pub fn verifier_key(&self, pks: &[AffinePoint<S>]) -> Result<RingVerifierKey<S>, Error> {
408        if pks.len() > self.piop_params.keyset_part_size {
409            return Err(Error::InvalidData);
410        }
411        let pks = TEMapping::to_te_slice(pks).ok_or(Error::InvalidData)?;
412        Ok(ring_proof::index(&self.pcs_params, &self.piop_params, &pks).1)
413    }
414
415    /// Create a verifier key from a precomputed ring commitment.
416    ///
417    /// The commitment can be obtained from an existing verifier key via
418    /// [`RingVerifierKey::commitment`].
419    pub fn verifier_key_from_commitment(
420        &self,
421        commitment: RingCommitment<S>,
422    ) -> RingVerifierKey<S> {
423        verifier_key_from_commitment::<S>(commitment, self.pcs_verifier_params())
424    }
425
426    /// Extract the PCS parameters required by the verifier.
427    ///
428    /// Small (a few points) and independent of ring size. Sufficient,
429    /// together with a ring commitment, to reconstruct a verifier key via
430    /// [`verifier_key_from_commitment`] without access to the full setup.
431    pub fn pcs_verifier_params(&self) -> PcsVerifierParams<S> {
432        use ring_proof::pcs::PcsParams;
433        self.pcs_params.raw_vk()
434    }
435
436    /// Create a builder for incremental construction of the verifier key.
437    pub fn verifier_key_builder(&self) -> (VerifierKeyBuilder<S>, RingBuilderPcsParams<S>) {
438        type RingBuilderKey<S> =
439            ring_proof::ring::RingBuilderKey<BaseField<S>, <S as RingSuite>::Pairing>;
440        let piop_domain_size = piop_domain_size::<S>(self.piop_params.keyset_part_size);
441        let builder_key = RingBuilderKey::<S>::from_srs(&self.pcs_params, piop_domain_size);
442        let builder_pcs_params = RingBuilderPcsParams(builder_key.lis_in_g1);
443        let builder = VerifierKeyBuilder::new(self, &builder_pcs_params);
444        (builder, builder_pcs_params)
445    }
446
447    /// Get a reference to the lightweight [`RingContext`].
448    pub fn ring_context(&self) -> &RingContext<S> {
449        &self.ring_ctx
450    }
451
452    /// Get the padding point.
453    ///
454    /// This is a point of unknown dlog that can be used in place of any key during
455    /// ring construction.
456    #[inline(always)]
457    pub const fn padding_point() -> AffinePoint<S> {
458        S::PADDING
459    }
460}
461
462/// Create a verifier key from a precomputed ring commitment and the PCS
463/// verifier parameters.
464///
465/// Lightweight alternative to [`RingSetup::verifier_key_from_commitment`] for
466/// verifier-only users: no SRS required. The parameters can be obtained once
467/// via [`RingSetup::pcs_verifier_params`] and distributed independently.
468///
469/// Soundness rests on `pcs_params` matching the trusted setup the commitment
470/// was produced under. Deserialization validates the points, but cannot tell
471/// a legitimate setup from a malicious one: obtain the parameters from a
472/// trusted source, not alongside untrusted proof data.
473pub fn verifier_key_from_commitment<S: RingSuite>(
474    commitment: RingCommitment<S>,
475    pcs_params: PcsVerifierParams<S>,
476) -> RingVerifierKey<S> {
477    RingVerifierKey::<S>::from_commitment_and_kzg_vk(commitment, pcs_params)
478}
479
480impl<S: RingSuite> CanonicalSerialize for RingSetup<S> {
481    fn serialize_with_mode<W: ark_serialize::Write>(
482        &self,
483        mut writer: W,
484        compress: ark_serialize::Compress,
485    ) -> Result<(), ark_serialize::SerializationError> {
486        self.pcs_params.serialize_with_mode(&mut writer, compress)
487    }
488
489    fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
490        self.pcs_params.serialized_size(compress)
491    }
492}
493
494impl<S: RingSuite> CanonicalDeserialize for RingSetup<S> {
495    fn deserialize_with_mode<R: ark_serialize::Read>(
496        mut reader: R,
497        compress: ark_serialize::Compress,
498        validate: ark_serialize::Validate,
499    ) -> Result<Self, ark_serialize::SerializationError> {
500        let pcs_params = <PcsParams<S> as CanonicalDeserialize>::deserialize_with_mode(
501            &mut reader,
502            compress,
503            validate,
504        )?;
505        let ring_size = max_ring_size_from_pcs_domain_size::<S>(pcs_params.powers_in_g1.len());
506        Ok(Self {
507            pcs_params,
508            ring_ctx: RingContext::new(ring_size),
509        })
510    }
511}
512
513impl<S: RingSuite> ark_serialize::Valid for RingSetup<S> {
514    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
515        self.pcs_params.check()
516    }
517}
518
519/// Information required for incremental ring construction.
520///
521/// Basically the SRS in Lagrangian form.
522/// Can be constructed via the `PcsParams::ck_with_lagrangian()` method.
523#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)]
524pub struct RingBuilderPcsParams<S: RingSuite>(pub Vec<G1Affine<S>>);
525
526// Under construction ring commitment.
527type PartialRingCommitment<S> =
528    ring_proof::ring::Ring<BaseField<S>, <S as RingSuite>::Pairing, TEAffine<CurveConfig<S>>>;
529
530/// Builder for incremental construction of ring verifier keys.
531///
532/// Allows constructing a verifier key by adding public keys in batches,
533/// which is useful for large rings or memory-constrained environments.
534#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)]
535pub struct VerifierKeyBuilder<S: RingSuite> {
536    partial: PartialRingCommitment<S>,
537    pcs_params: PcsVerifierParams<S>,
538}
539
540/// Pairing G1 affine point type.
541pub type G1Affine<S> = <<S as RingSuite>::Pairing as Pairing>::G1Affine;
542/// Pairing G2 affine point type.
543pub type G2Affine<S> = <<S as RingSuite>::Pairing as Pairing>::G2Affine;
544
545/// Trait for accessing Structured Reference String entries in Lagrangian basis.
546///
547/// Provides access to precomputed SRS elements needed for efficient ring operations.
548pub trait SrsLookup<S: RingSuite> {
549    /// Look up a range of SRS elements. Returns `None` if the range is out of bounds.
550    fn lookup(&self, range: Range<usize>) -> Option<Vec<G1Affine<S>>>;
551}
552
553impl<S: RingSuite, F> SrsLookup<S> for F
554where
555    F: Fn(Range<usize>) -> Option<Vec<G1Affine<S>>>,
556{
557    fn lookup(&self, range: Range<usize>) -> Option<Vec<G1Affine<S>>> {
558        self(range)
559    }
560}
561
562impl<S: RingSuite> SrsLookup<S> for &RingBuilderPcsParams<S> {
563    fn lookup(&self, range: Range<usize>) -> Option<Vec<G1Affine<S>>> {
564        if range.end > self.0.len() {
565            return None;
566        }
567        Some(self.0[range].to_vec())
568    }
569}
570
571impl<S: RingSuite> VerifierKeyBuilder<S> {
572    /// Create a new empty ring verifier key builder.
573    pub fn new(ring_setup: &RingSetup<S>, lookup: impl SrsLookup<S>) -> Self {
574        let lookup = |range: Range<usize>| lookup.lookup(range).ok_or(());
575        let pcs_params = ring_setup.pcs_verifier_params();
576        let partial = PartialRingCommitment::<S>::empty(
577            &ring_setup.piop_params,
578            lookup,
579            pcs_params.g1.into_group(),
580        );
581        VerifierKeyBuilder {
582            partial,
583            pcs_params,
584        }
585    }
586
587    /// Get the number of remaining slots available in the ring.
588    #[inline(always)]
589    pub fn free_slots(&self) -> usize {
590        self.partial.max_keys - self.partial.curr_keys
591    }
592
593    /// Get the PCS parameters required by the verifier.
594    ///
595    /// Same value as [`RingSetup::pcs_verifier_params`] for the setup this
596    /// builder was created from.
597    pub fn pcs_verifier_params(&self) -> PcsVerifierParams<S> {
598        self.pcs_params.clone()
599    }
600
601    /// Add public keys to the ring being built.
602    ///
603    /// Returns `Err(available_slots)` if there's not enough space, or
604    /// `Err(usize::MAX)` if the SRS lookup fails.
605    pub fn append(
606        &mut self,
607        pks: &[AffinePoint<S>],
608        lookup: impl SrsLookup<S>,
609    ) -> Result<(), usize> {
610        let avail_slots = self.free_slots();
611        if avail_slots < pks.len() {
612            return Err(avail_slots);
613        }
614        // Currently `ring-proof` backend panics if lookup fails.
615        // This workaround makes lookup failures a bit less harsh.
616        let segment = lookup
617            .lookup(self.partial.curr_keys..self.partial.curr_keys + pks.len())
618            .ok_or(usize::MAX)?;
619        let lookup = |range: Range<usize>| {
620            debug_assert_eq!(segment.len(), range.len());
621            Ok(segment.clone())
622        };
623        let pks = TEMapping::to_te_slice(pks).ok_or(usize::MAX)?;
624        self.partial.append(&pks, lookup);
625        Ok(())
626    }
627
628    /// Complete the building process and create the verifier key.
629    pub fn finalize(self) -> RingVerifierKey<S> {
630        RingVerifierKey::<S>::from_ring_and_kzg_vk(&self.partial, self.pcs_params)
631    }
632}
633
634type RingProofBatchItem<S> =
635    ring_proof::multi_ring_batch_verifier::BatchItem<<S as RingSuite>::Pairing, CurveConfig<S>>;
636
637/// Pre-processed data for a single ring proof awaiting batch verification.
638pub struct BatchItem<S: RingSuite> {
639    ring: RingProofBatchItem<S>,
640    pedersen: pedersen::BatchItem<S>,
641}
642
643impl<S: RingSuite> BatchItem<S> {
644    /// Prepare a proof for deferred batch verification.
645    ///
646    /// Performs the cheap per-proof work (hashing, transcript setup) without
647    /// the expensive pairing and MSM checks. `verifier` must be the ring
648    /// verifier the proof was produced against.
649    ///
650    /// Returns `Error::InvalidData` if the proof's key commitment cannot be
651    /// converted (e.g. identity point on SW-form suites).
652    pub fn new(
653        verifier: &RingVerifier<S>,
654        ios: impl AsRef<[VrfIo<S>]>,
655        ad: impl AsRef<[u8]>,
656        proof: &Proof<S>,
657    ) -> Result<Self, Error> {
658        let key_commitment = proof
659            .pedersen_proof
660            .key_commitment()
661            .into_te()
662            .ok_or(Error::InvalidData)?;
663        let pedersen = pedersen::BatchItem::new(ios, ad, &proof.pedersen_proof);
664        let ring = RingProofBatchItem::<S>::new(verifier, proof.ring_proof.clone(), key_commitment);
665        Ok(Self { ring, pedersen })
666    }
667}
668
669/// Batch verifier for ring VRF proofs.
670///
671/// Collects ring proofs from one or more rings (sharing the same KZG SRS)
672/// and verifies them together, amortizing the cost of pairing checks and
673/// multi-scalar multiplications.
674///
675/// The same subgroup membership assumptions as [`Verifier`] apply to all
676/// points fed into the batch (I/O pairs and proof points).
677pub struct BatchVerifier<S: RingSuite> {
678    ring_batch: RingBatchVerifier<S>,
679    pedersen_batch: pedersen::BatchVerifier<S>,
680}
681
682impl<S: RingSuite> BatchVerifier<S> {
683    /// Create a new batch verifier seeded with the KZG SRS taken from `ring_verifier`.
684    ///
685    /// Any ring verifier sharing the same SRS can later be passed to
686    /// [`Self::push`] or [`BatchItem::new`]; the verifier supplied here is
687    /// only used to extract the KZG verifier key.
688    pub fn new(ring_verifier: &RingVerifier<S>) -> Self {
689        Self {
690            ring_batch: RingBatchVerifier::<S>::new(
691                ring_verifier.pcs_vk().clone(),
692                ring_proof::ArkTranscript::new(S::SUITE_ID),
693            ),
694            pedersen_batch: pedersen::BatchVerifier::new(),
695        }
696    }
697
698    /// Push a previously prepared item into the batch.
699    pub fn push_prepared(&mut self, item: BatchItem<S>) {
700        self.pedersen_batch.push_prepared(item.pedersen);
701        self.ring_batch.push_prepared(item.ring);
702    }
703
704    /// Prepare and push a proof in one step.
705    ///
706    /// Returns `Error::InvalidData` if the proof's key commitment cannot be
707    /// converted (e.g. identity point on SW-form suites).
708    pub fn push(
709        &mut self,
710        verifier: &RingVerifier<S>,
711        ios: impl AsRef<[VrfIo<S>]>,
712        ad: impl AsRef<[u8]>,
713        proof: &Proof<S>,
714    ) -> Result<(), Error> {
715        let item = BatchItem::new(verifier, ios, ad, proof)?;
716        self.push_prepared(item);
717        Ok(())
718    }
719
720    /// Verify all collected proofs in a single batch.
721    ///
722    /// Checks both the Pedersen proofs (via MSM) and the ring proofs (via pairing).
723    /// Returns `Ok(())` if all proofs verify, `Err(VerificationFailure)` otherwise.
724    pub fn verify(&self) -> Result<(), Error> {
725        self.pedersen_batch.verify()?;
726        self.ring_batch
727            .verify()
728            .then_some(())
729            .ok_or(Error::VerificationFailure)
730    }
731}
732
733/// Type aliases for the given ring suite.
734#[macro_export]
735macro_rules! ring_suite_types {
736    ($suite:ident) => {
737        #[allow(dead_code)]
738        pub type PcsParams = $crate::ring::PcsParams<$suite>;
739        #[allow(dead_code)]
740        pub type PcsVerifierParams = $crate::ring::PcsVerifierParams<$suite>;
741        #[allow(dead_code)]
742        pub type PiopParams = $crate::ring::PiopParams<$suite>;
743        #[allow(dead_code)]
744        pub type RingContext = $crate::ring::RingContext<$suite>;
745        #[allow(dead_code)]
746        pub type RingSetup = $crate::ring::RingSetup<$suite>;
747        #[allow(dead_code)]
748        pub type RingProverKey = $crate::ring::RingProverKey<$suite>;
749        #[allow(dead_code)]
750        pub type RingVerifierKey = $crate::ring::RingVerifierKey<$suite>;
751        #[allow(dead_code)]
752        pub type RingCommitment = $crate::ring::RingCommitment<$suite>;
753        #[allow(dead_code)]
754        pub type RingProver = $crate::ring::RingProver<$suite>;
755        #[allow(dead_code)]
756        pub type RingVerifier = $crate::ring::RingVerifier<$suite>;
757        #[allow(dead_code)]
758        pub type RingProof = $crate::ring::Proof<$suite>;
759        #[allow(dead_code)]
760        pub type RingVerifierKeyBuilder = $crate::ring::VerifierKeyBuilder<$suite>;
761        #[allow(dead_code)]
762        pub type RingBatchItem = $crate::ring::BatchItem<$suite>;
763        #[allow(dead_code)]
764        pub type RingBatchVerifier = $crate::ring::BatchVerifier<$suite>;
765    };
766}
767
768/// Domain size conversion utilities
769///
770/// The ring proof system operates with three related size parameters:
771///
772/// 1. `min_ring_size`: Number of keys that the ring should accomodate (user-facing parameter)
773/// 2. `max_ring_size`: Max number of keys that the ring can accomodate
774/// 3. `piop_domain_size`: Size of the PIOP (Polynomial IOP) domain
775/// 4. `pcs_domain_size`: Size of the PCS (Polynomial Commitment Scheme) domain
776///
777/// Relationships:
778///   piop_domain_size = (ring_size + PIOP_OVERHEAD).next_power_of_two()
779///   pcs_domain_size  = 3 * piop_domain_size + 1
780///   max_ring_size    = piop_domain_size - PIOP_OVERHEAD
781///
782/// where PIOP_OVERHEAD = 4 + MODULUS_BIT_SIZE accounts for:
783///   - 3 points for zero-knowledge blinding
784///   - 1 extra point used internally by the PIOP
785///   - MODULUS_BIT_SIZE bits for blinding factor
786///
787/// Note: Multiple ring sizes map to the same domain sizes due to power-of-2 rounding.
788/// For example, ring sizes 1-254 (with 254-bit scalar) all map to piop_domain_size=512
789/// and pcs_domain_size=1537.
790pub mod dom_utils {
791    use super::*;
792
793    /// Returns the actual ring capacity for a given minimum size requirement.
794    ///
795    /// Because domain sizes round up to powers of 2, allocating for `min_ring_size`
796    /// keys typically provides capacity for more. This function returns that actual
797    /// capacity: the largest ring size that uses the same domain as `min_ring_size`.
798    ///
799    /// Always returns a value `>= min_ring_size`.
800    pub const fn max_ring_size<S: Suite>(min_ring_size: usize) -> usize {
801        max_ring_size_from_piop_domain_size::<S>(piop_domain_size::<S>(min_ring_size))
802    }
803
804    /// PIOP overhead: accounts for 3 ZK blinding points + 1 internal point + scalar field bits.
805    pub const fn piop_overhead<S: Suite>() -> usize {
806        4 + ScalarField::<S>::MODULUS_BIT_SIZE as usize
807    }
808
809    /// PIOP domain size required to support the given ring size.
810    ///
811    /// Returns the smallest power of 2 that can accommodate `min_ring_capactity` members.
812    /// This is the domain size used for polynomial operations in the ring proof and
813    /// already accounts for the PIOP overhead.
814    pub const fn piop_domain_size<S: Suite>(min_ring_capacity: usize) -> usize {
815        (min_ring_capacity + piop_overhead::<S>()).next_power_of_two()
816    }
817
818    /// Maximum ring size supported by a given PIOP domain size.
819    ///
820    /// Returns the largest ring that fits in the domain.
821    pub const fn max_ring_size_from_piop_domain_size<S: Suite>(piop_domain_size: usize) -> usize {
822        piop_domain_size - piop_overhead::<S>()
823    }
824
825    /// PCS domain size required to support the given ring size.
826    ///
827    /// Returns `3 * piop_domain_size + 1`. This is the number of G1 elements required
828    /// in the SRS (powers of tau) for the prover. The verifier only needs the PIOP domain size.
829    pub const fn pcs_domain_size<S: Suite>(min_ring_size: usize) -> usize {
830        pcs_domain_size_from_piop_domain_size(piop_domain_size::<S>(min_ring_size))
831    }
832
833    /// PCS domain size for a given PIOP domain size.
834    ///
835    /// Returns `3 * piop_domain_size + 1`.
836    pub const fn pcs_domain_size_from_piop_domain_size(piop_domain_size: usize) -> usize {
837        3 * piop_domain_size + 1
838    }
839
840    /// PIOP domain size extracted from a PCS domain size.
841    ///
842    /// Recovers the PIOP domain size from a PCS domain size. The ilog2 ensures we get
843    /// a valid power of 2 even if the input wasn't properly constructed.
844    pub const fn piop_domain_size_from_pcs_domain_size(pcs_domain_size: usize) -> usize {
845        1 << ((pcs_domain_size - 1) / 3).ilog2()
846    }
847
848    /// Maximum ring size supported by a given PCS domain size.
849    ///
850    /// Composes `piop_domain_size_from_pcs_domain_size` and `max_ring_size_from_piop_domain_size`.
851    pub const fn max_ring_size_from_pcs_domain_size<S: Suite>(pcs_domain_size: usize) -> usize {
852        let piop_domain_size = piop_domain_size_from_pcs_domain_size(pcs_domain_size);
853        max_ring_size_from_piop_domain_size::<S>(piop_domain_size)
854    }
855}
856pub use dom_utils::*;
857
858#[cfg(test)]
859pub(crate) mod testing {
860    use super::*;
861    use crate::pedersen;
862    use crate::testing::{self as common, CheckPoint, TEST_SEED};
863    use ark_ec::{
864        short_weierstrass::{Affine as SWAffine, SWCurveConfig},
865        twisted_edwards::{Affine as TEAffine, TECurveConfig},
866    };
867
868    pub const TEST_RING_SIZE: usize = 8;
869
870    const MAX_AD_LEN: usize = 100;
871
872    fn find_complement_point<C: SWCurveConfig>() -> SWAffine<C> {
873        use ark_ff::{One, Zero};
874        assert!(!C::cofactor_is_one());
875        let mut x = C::BaseField::zero();
876        loop {
877            if let Some(p) = SWAffine::get_point_from_x_unchecked(x, false)
878                .filter(|p| !p.is_in_correct_subgroup_assuming_on_curve())
879            {
880                return p;
881            }
882            x += C::BaseField::one();
883        }
884    }
885
886    pub trait FindAccumulatorBase<S: Suite>: Sized {
887        const IN_PRIME_ORDER_SUBGROUP: bool;
888        fn find_accumulator_base(data: &[u8]) -> Option<Self>;
889    }
890
891    impl<S, C> FindAccumulatorBase<S> for SWAffine<C>
892    where
893        C: SWCurveConfig,
894        S: Suite<Affine = Self>,
895    {
896        const IN_PRIME_ORDER_SUBGROUP: bool = false;
897
898        fn find_accumulator_base(data: &[u8]) -> Option<Self> {
899            let p = S::data_to_point(data)?;
900            let c = find_complement_point();
901            let res = (p + c).into_affine();
902            debug_assert!(!res.is_in_correct_subgroup_assuming_on_curve());
903            Some(res)
904        }
905    }
906
907    impl<S, C> FindAccumulatorBase<S> for TEAffine<C>
908    where
909        C: TECurveConfig,
910        S: Suite<Affine = Self>,
911    {
912        const IN_PRIME_ORDER_SUBGROUP: bool = true;
913
914        fn find_accumulator_base(data: &[u8]) -> Option<Self> {
915            let res = S::data_to_point(data)?;
916            debug_assert!(res.is_in_correct_subgroup_assuming_on_curve());
917            Some(res)
918        }
919    }
920
921    struct TestItem<S: RingSuite> {
922        io: VrfIo<S>,
923        ad: Vec<u8>,
924        proof: Proof<S>,
925    }
926
927    impl<S: RingSuite> TestItem<S> {
928        fn new(
929            secret: &Secret<S>,
930            prover: &RingProver<S>,
931            rng: &mut dyn ark_std::rand::RngCore,
932        ) -> Self {
933            let input = Input::from_affine_unchecked(common::random_val(Some(rng)));
934            let io = secret.vrf_io(input);
935            let ad_len = common::random_val::<usize>(Some(rng)) % (MAX_AD_LEN + 1);
936            let ad = common::random_vec(ad_len, Some(rng));
937            let proof = secret.prove(io, &ad, prover);
938            Self { io, ad, proof }
939        }
940    }
941
942    #[allow(unused)]
943    pub fn prove_verify<S: RingSuite>() {
944        let rng = &mut ark_std::test_rng();
945        let ring_setup = RingSetup::<S>::from_rand(TEST_RING_SIZE, rng);
946
947        let secret = Secret::<S>::from_seed(TEST_SEED);
948        let public = secret.public();
949
950        let mut pks = common::random_vec::<AffinePoint<S>>(TEST_RING_SIZE, Some(rng));
951        let prover_idx = 3;
952        pks[prover_idx] = public.0;
953
954        let ring_ctx = ring_setup.ring_context();
955        let prover_key = ring_setup.prover_key(&pks).unwrap();
956        let prover = ring_ctx.ring_prover(prover_key, prover_idx);
957
958        let item = TestItem::<S>::new(&secret, &prover, rng);
959
960        let verifier_key = ring_setup.verifier_key(&pks).unwrap();
961        let verifier = ring_ctx.ring_verifier(verifier_key);
962        let result = Public::verify(item.io, &item.ad, &item.proof, &verifier);
963        assert!(result.is_ok());
964    }
965
966    /// N=3 multi proof via ring prove/verify.
967    #[allow(unused)]
968    pub fn prove_verify_multi<S: RingSuite>() {
969        use ring::{Prover, Verifier};
970
971        let rng = &mut ark_std::test_rng();
972        let ring_setup = RingSetup::<S>::from_rand(TEST_RING_SIZE, rng);
973
974        let secret = Secret::<S>::from_seed(TEST_SEED);
975        let public = secret.public();
976
977        let mut pks = common::random_vec::<AffinePoint<S>>(TEST_RING_SIZE, Some(rng));
978        let prover_idx = 3;
979        pks[prover_idx] = public.0;
980
981        let ring_ctx = ring_setup.ring_context();
982        let prover_key = ring_setup.prover_key(&pks).unwrap();
983        let prover = ring_ctx.ring_prover(prover_key, prover_idx);
984
985        let verifier_key = ring_setup.verifier_key(&pks).unwrap();
986        let verifier = ring_ctx.ring_verifier(verifier_key);
987
988        let mut ios: Vec<VrfIo<S>> = (0..3u8)
989            .map(|i| {
990                let input = Input::new(&[i + 1]).unwrap();
991                secret.vrf_io(input)
992            })
993            .collect();
994        ios.push(VrfIo {
995            input: Input(S::Affine::generator()),
996            output: Output(public.0),
997        });
998
999        let proof = secret.prove(&ios[..], b"bar", &prover);
1000        assert!(Public::verify(&ios[..], b"bar", &proof, &verifier).is_ok());
1001
1002        // Tamper: wrong output on ios[1]
1003        let mut bad_ios = ios.clone();
1004        bad_ios[1].output = secret.output(ios[0].input);
1005        assert!(Public::verify(&bad_ios[..], b"bar", &proof, &verifier).is_err());
1006
1007        // Tamper: wrong ad
1008        assert!(Public::verify(&ios[..], b"baz", &proof, &verifier).is_err());
1009    }
1010
1011    #[allow(unused)]
1012    pub fn prove_verify_batch<S: RingSuite>() {
1013        use rayon::prelude::*;
1014
1015        const BATCH_SIZE: usize = 3 * TEST_RING_SIZE;
1016
1017        let rng = &mut ark_std::test_rng();
1018        let ring_setup = RingSetup::<S>::from_rand(TEST_RING_SIZE, rng);
1019
1020        let secret = Secret::<S>::from_seed(TEST_SEED);
1021        let public = secret.public();
1022
1023        let mut pks = common::random_vec::<AffinePoint<S>>(TEST_RING_SIZE, Some(rng));
1024        let prover_idx = 3;
1025        pks[prover_idx] = public.0;
1026
1027        let ring_ctx = ring_setup.ring_context();
1028        let prover_key = ring_setup.prover_key(&pks).unwrap();
1029        let prover = ring_ctx.ring_prover(prover_key, prover_idx);
1030
1031        // Generate proofs in parallel
1032        let batch: Vec<_> = (0..BATCH_SIZE)
1033            .into_par_iter()
1034            .map_init(ark_std::test_rng, |rng, _| {
1035                TestItem::<S>::new(&secret, &prover, rng)
1036            })
1037            .collect();
1038
1039        let verifier_key = ring_setup.verifier_key(&pks).unwrap();
1040        let verifier = ring_ctx.ring_verifier(verifier_key);
1041
1042        // Batch verify all proofs
1043        let mut batch_verifier = BatchVerifier::<S>::new(&verifier);
1044        let res = batch_verifier.verify();
1045        assert!(res.is_ok());
1046
1047        // Prove incrementally constructed batches
1048        for item in batch.iter() {
1049            batch_verifier
1050                .push(&verifier, item.io, &item.ad, &item.proof)
1051                .unwrap();
1052            let res = batch_verifier.verify();
1053            assert!(res.is_ok());
1054        }
1055
1056        println!("Batch size = {BATCH_SIZE}");
1057
1058        println!("============================================================");
1059
1060        let mut batch_verifier = BatchVerifier::<S>::new(&verifier);
1061        let start = std::time::Instant::now();
1062        common::timed("Proofs push", || {
1063            for item in batch.iter() {
1064                batch_verifier
1065                    .push(&verifier, item.io, &item.ad, &item.proof)
1066                    .unwrap();
1067            }
1068        });
1069        common::timed("Unprepared batch verification", || batch_verifier.verify());
1070        println!("Total time: {:?}", start.elapsed());
1071
1072        println!("============================================================");
1073
1074        let mut batch_verifier = BatchVerifier::<S>::new(&verifier);
1075        let start = std::time::Instant::now();
1076        let prepared = common::timed("Proofs prepare", || {
1077            batch
1078                .par_iter()
1079                .map(|item| BatchItem::<S>::new(&verifier, item.io, &item.ad, &item.proof).unwrap())
1080                .collect::<Vec<_>>()
1081        });
1082        common::timed("Proofs push prepared", || {
1083            prepared
1084                .into_iter()
1085                .for_each(|p| batch_verifier.push_prepared(p))
1086        });
1087        common::timed("Prepared batch verification", || batch_verifier.verify());
1088        println!("Total time: {:?}", start.elapsed());
1089
1090        println!("============================================================");
1091
1092        // Multi-ring batch: build a second ring sharing the same KZG SRS,
1093        // then aggregate proofs from both rings into a single batch verifier.
1094        let mut pks_b = common::random_vec::<AffinePoint<S>>(TEST_RING_SIZE, Some(rng));
1095        let prover_idx_b = 1;
1096        pks_b[prover_idx_b] = public.0;
1097        let prover_key_b = ring_setup.prover_key(&pks_b).unwrap();
1098        let prover_b = ring_ctx.ring_prover(prover_key_b, prover_idx_b);
1099        let verifier_key_b = ring_setup.verifier_key(&pks_b).unwrap();
1100        let verifier_b = ring_ctx.ring_verifier(verifier_key_b);
1101
1102        let batch_b: Vec<_> = (0..TEST_RING_SIZE)
1103            .into_par_iter()
1104            .map_init(ark_std::test_rng, |rng, _| {
1105                TestItem::<S>::new(&secret, &prover_b, rng)
1106            })
1107            .collect();
1108
1109        let mut batch_verifier = BatchVerifier::<S>::new(&verifier);
1110        for item in batch.iter() {
1111            batch_verifier
1112                .push(&verifier, item.io, &item.ad, &item.proof)
1113                .unwrap();
1114        }
1115        for item in batch_b.iter() {
1116            batch_verifier
1117                .push(&verifier_b, item.io, &item.ad, &item.proof)
1118                .unwrap();
1119        }
1120        common::timed("Multi-ring batch verification", || batch_verifier.verify())
1121            .expect("multi-ring batch verifies");
1122
1123        // Negative case: pushing a ring-B proof against verifier_a must not
1124        // produce a batch that verifies. This guards against the per-item
1125        // verifier argument being silently ignored.
1126        let mut batch_verifier = BatchVerifier::<S>::new(&verifier);
1127        let item_b = &batch_b[0];
1128        batch_verifier
1129            .push(&verifier, item_b.io, &item_b.ad, &item_b.proof)
1130            .unwrap();
1131        assert!(
1132            batch_verifier.verify().is_err(),
1133            "ring-B proof must not verify against verifier_a"
1134        );
1135    }
1136
1137    #[allow(unused)]
1138    pub fn padding_check<S: RingSuite>()
1139    where
1140        AffinePoint<S>: CheckPoint,
1141    {
1142        // Check that point has been computed using the magic spell.
1143        assert_eq!(S::PADDING, S::data_to_point(PADDING_SEED).unwrap());
1144
1145        // Check that the point is on curve.
1146        assert!(S::PADDING.check(true).is_ok());
1147    }
1148
1149    #[allow(unused)]
1150    pub fn accumulator_base_check<S: RingSuite>()
1151    where
1152        AffinePoint<S>: FindAccumulatorBase<S> + CheckPoint,
1153    {
1154        // Check that point has been computed using the magic spell.
1155        assert_eq!(
1156            S::ACCUMULATOR_BASE,
1157            AffinePoint::<S>::find_accumulator_base(ACCUMULATOR_BASE_SEED).unwrap()
1158        );
1159
1160        // SW form requires accumulator seed to be outside prime order subgroup.
1161        // TE form requires accumulator seed to be in prime order subgroup.
1162        let in_prime_subgroup = <AffinePoint<S> as FindAccumulatorBase<S>>::IN_PRIME_ORDER_SUBGROUP;
1163        assert!(S::ACCUMULATOR_BASE.check(in_prime_subgroup).is_ok());
1164    }
1165
1166    #[allow(unused)]
1167    pub fn verifier_key_from_commitment<S: RingSuite>() {
1168        let rng = &mut ark_std::test_rng();
1169        let ring_setup = RingSetup::<S>::from_rand(TEST_RING_SIZE, rng);
1170
1171        let secret = Secret::<S>::from_seed(TEST_SEED);
1172        let public = secret.public();
1173
1174        let mut pks = common::random_vec::<AffinePoint<S>>(TEST_RING_SIZE, Some(rng));
1175        let prover_idx = 3;
1176        pks[prover_idx] = public.0;
1177
1178        let prover_key = ring_setup.prover_key(&pks).unwrap();
1179        let prover = ring_setup
1180            .ring_context()
1181            .ring_prover(prover_key, prover_idx);
1182        let item = TestItem::<S>::new(&secret, &prover, rng);
1183
1184        let commitment = ring_setup.verifier_key(&pks).unwrap().commitment();
1185
1186        // Round-trip the params to mimic a verifier-only user holding just
1187        // the serialized params, the ring commitment and the ring size.
1188        let mut buf = Vec::new();
1189        ring_setup
1190            .pcs_verifier_params()
1191            .serialize_compressed(&mut buf)
1192            .unwrap();
1193        let pcs_params = PcsVerifierParams::<S>::deserialize_compressed(&buf[..]).unwrap();
1194
1195        let ring_ctx = RingContext::<S>::new(TEST_RING_SIZE);
1196        let verifier_key = super::verifier_key_from_commitment::<S>(commitment, pcs_params);
1197        let verifier = ring_ctx.ring_verifier(verifier_key);
1198        assert!(Public::verify(item.io, &item.ad, &item.proof, &verifier).is_ok());
1199    }
1200
1201    #[allow(unused)]
1202    pub fn verifier_key_builder<S: RingSuite>() {
1203        use crate::testing::{random_val, random_vec};
1204
1205        let rng = &mut ark_std::test_rng();
1206        let ring_setup = RingSetup::<S>::from_rand(TEST_RING_SIZE, rng);
1207
1208        let secret = Secret::<S>::from_seed(TEST_SEED);
1209        let public = secret.public();
1210        let input = Input::from_affine_unchecked(common::random_val(Some(rng)));
1211        let io = secret.vrf_io(input);
1212
1213        let ring_ctx = ring_setup.ring_context();
1214        let ring_size = ring_ctx.max_ring_size();
1215        let prover_idx = random_val::<usize>(Some(rng)) % ring_size;
1216        let mut pks = random_vec::<AffinePoint<S>>(ring_size, Some(rng));
1217        pks[prover_idx] = public.0;
1218
1219        let prover_key = ring_setup.prover_key(&pks).unwrap();
1220        let prover = ring_ctx.ring_prover(prover_key, prover_idx);
1221        let proof = secret.prove(io, b"foo", &prover);
1222
1223        // Incremental ring verifier key construction
1224        let (mut vk_builder, lookup) = ring_setup.verifier_key_builder();
1225        assert_eq!(vk_builder.free_slots(), pks.len());
1226        assert_eq!(
1227            vk_builder.pcs_verifier_params(),
1228            ring_setup.pcs_verifier_params()
1229        );
1230
1231        let extra_pk = random_val::<AffinePoint<S>>(Some(rng));
1232        assert_eq!(
1233            vk_builder.append(&[extra_pk], |_| None).unwrap_err(),
1234            usize::MAX
1235        );
1236
1237        while !pks.is_empty() {
1238            let chunk_len = 1 + random_val::<usize>(Some(rng)) % 5;
1239            let chunk = pks.drain(..pks.len().min(chunk_len)).collect::<Vec<_>>();
1240            vk_builder.append(&chunk[..], &lookup).unwrap();
1241            assert_eq!(vk_builder.free_slots(), pks.len());
1242        }
1243        // No more space left
1244        let extra_pk = random_val::<AffinePoint<S>>(Some(rng));
1245        assert_eq!(vk_builder.append(&[extra_pk], &lookup).unwrap_err(), 0);
1246        let verifier_key = vk_builder.finalize();
1247        let verifier = ring_ctx.ring_verifier(verifier_key);
1248        let result = Public::verify(io, b"foo", &proof, &verifier);
1249        assert!(result.is_ok());
1250    }
1251
1252    pub fn domain_size_conversions<S: RingSuite>() {
1253        let overhead = piop_overhead::<S>();
1254
1255        for ring_size in [1, 10, 200, 300, 500, 1000, 2000, 10000] {
1256            let piop_dom_size = piop_domain_size::<S>(ring_size);
1257            let pcs_dom_size = pcs_domain_size::<S>(ring_size);
1258            let max_ring_size = max_ring_size_from_piop_domain_size::<S>(piop_dom_size);
1259
1260            assert!(piop_dom_size.is_power_of_two());
1261            assert_eq!(pcs_dom_size, 3 * piop_dom_size + 1);
1262
1263            // piop_domain_size must fit ring_size + overhead
1264            assert!(piop_dom_size >= ring_size + overhead);
1265            // piop_domain_size is the smallest power of 2 that fits
1266            assert!(piop_dom_size / 2 < ring_size + overhead);
1267            // piop_dom_size is sufficient for max_ring_size
1268            assert_eq!(piop_dom_size, piop_domain_size::<S>(max_ring_size));
1269            // ring_size <= max_ring_size for the computed domain
1270            assert!(ring_size <= max_ring_size);
1271
1272            // max_ring_size() helper equivalence
1273            assert_eq!(dom_utils::max_ring_size::<S>(ring_size), max_ring_size);
1274            // max_ring_size() is idempotent
1275            assert_eq!(dom_utils::max_ring_size::<S>(max_ring_size), max_ring_size);
1276
1277            // Round-trip
1278            let piop_dom_rt = piop_domain_size_from_pcs_domain_size(pcs_dom_size);
1279            assert_eq!(piop_dom_size, piop_dom_rt);
1280            let pcs_dom_rt = pcs_domain_size_from_piop_domain_size(piop_dom_rt);
1281            assert_eq!(pcs_dom_size, pcs_dom_rt);
1282
1283            let max_ring_from_pcs = max_ring_size_from_pcs_domain_size::<S>(pcs_dom_size);
1284            assert_eq!(max_ring_size, max_ring_from_pcs);
1285
1286            // max_ring + 1 should require a larger piop domain
1287            let next_piop = piop_domain_size::<S>(max_ring_size + 1);
1288            assert!(next_piop > piop_dom_size,);
1289            assert!(next_piop.is_power_of_two());
1290        }
1291
1292        // Test inverse with arbitrary PCS values (not necessarily properly constructed)
1293        // The inverse function should recover the largest valid piop that fits
1294        for pcs_dom_size in [1 << 11, 1 << 12, 1 << 14, 1 << 16] {
1295            let piop_dom = piop_domain_size_from_pcs_domain_size(pcs_dom_size);
1296            let max_ring = max_ring_size_from_pcs_domain_size::<S>(pcs_dom_size);
1297
1298            assert!(piop_dom.is_power_of_two());
1299            // piop should satisfy: 3 * piop + 1 <= pcs
1300            assert!(3 * piop_dom < pcs_dom_size);
1301            // but 3 * (2 * piop) + 1 > pcs (piop is maximal)
1302            assert!(3 * (2 * piop_dom) + 1 > pcs_dom_size);
1303            // max_ring should map back to this piop
1304            assert_eq!(piop_domain_size::<S>(max_ring), piop_dom);
1305            // max_ring + 1 should require larger piop
1306            assert!(piop_domain_size::<S>(max_ring + 1) > piop_dom);
1307        }
1308
1309        // Edge case: ring_size = 0 (degenerate but shouldn't panic)
1310        let piop_zero = piop_domain_size::<S>(0);
1311        assert!(piop_zero.is_power_of_two());
1312        assert_eq!(piop_zero, overhead.next_power_of_two());
1313    }
1314
1315    #[macro_export]
1316    macro_rules! ring_suite_tests {
1317        ($suite:ty) => {
1318            mod ring {
1319                use super::*;
1320
1321                #[test]
1322                fn prove_verify() {
1323                    $crate::ring::testing::prove_verify::<$suite>()
1324                }
1325
1326                #[test]
1327                fn prove_verify_multi() {
1328                    $crate::ring::testing::prove_verify_multi::<$suite>()
1329                }
1330
1331                #[test]
1332                fn prove_verify_batch() {
1333                    $crate::ring::testing::prove_verify_batch::<$suite>()
1334                }
1335
1336                #[test]
1337                fn padding_check() {
1338                    $crate::ring::testing::padding_check::<$suite>()
1339                }
1340
1341                #[test]
1342                fn accumulator_base_check() {
1343                    $crate::ring::testing::accumulator_base_check::<$suite>()
1344                }
1345
1346                #[test]
1347                fn verifier_key_builder() {
1348                    $crate::ring::testing::verifier_key_builder::<$suite>()
1349                }
1350
1351                #[test]
1352                fn verifier_key_from_commitment() {
1353                    $crate::ring::testing::verifier_key_from_commitment::<$suite>()
1354                }
1355
1356                #[test]
1357                fn domain_size_conversions() {
1358                    $crate::ring::testing::domain_size_conversions::<$suite>()
1359                }
1360
1361                $crate::test_vectors!($crate::ring::testing::TestVector<$suite>);
1362            }
1363        };
1364    }
1365
1366    pub trait RingSuiteExt: RingSuite + crate::testing::SuiteExt {
1367        const SRS_FILE: &str;
1368
1369        fn ring_setup() -> &'static RingSetup<Self>;
1370
1371        #[allow(unused)]
1372        fn load_ring_setup() -> RingSetup<Self> {
1373            use ark_serialize::CanonicalDeserialize;
1374            use std::{fs::File, io::Read};
1375            let mut file = File::open(Self::SRS_FILE).unwrap();
1376            let mut buf = Vec::new();
1377            file.read_to_end(&mut buf).unwrap();
1378            let pcs_params =
1379                PcsParams::<Self>::deserialize_uncompressed_unchecked(&mut &buf[..]).unwrap();
1380            RingSetup::from_pcs_params(crate::ring::testing::TEST_RING_SIZE, pcs_params).unwrap()
1381        }
1382
1383        #[allow(unused)]
1384        fn write_ring_setup(ring_setup: &RingSetup<Self>) {
1385            use ark_serialize::CanonicalSerialize;
1386            use std::{fs::File, io::Write};
1387            let mut file = File::create(Self::SRS_FILE).unwrap();
1388            let mut buf = Vec::new();
1389            ring_setup
1390                .pcs_params
1391                .serialize_uncompressed(&mut buf)
1392                .unwrap();
1393            file.write_all(&buf).unwrap();
1394        }
1395    }
1396
1397    pub struct TestVector<S: RingSuite> {
1398        pub pedersen: pedersen::testing::TestVector<S>,
1399        pub ring_pks: [AffinePoint<S>; TEST_RING_SIZE],
1400        pub ring_pks_com: RingCommitment<S>,
1401        pub ring_proof: RingBareProof<S>,
1402    }
1403
1404    impl<S: RingSuite> core::fmt::Debug for TestVector<S> {
1405        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1406            f.debug_struct("TestVector")
1407                .field("pedersen", &self.pedersen)
1408                .field("ring_proof", &"...")
1409                .finish()
1410        }
1411    }
1412
1413    impl<S> common::TestVectorTrait for TestVector<S>
1414    where
1415        S: RingSuiteExt + std::fmt::Debug + 'static,
1416    {
1417        fn name() -> String {
1418            S::SUITE_NAME.to_string() + "_ring"
1419        }
1420
1421        fn new(comment: &str, seed: &[u8; 32], alpha: &[u8], ad: &[u8]) -> Self {
1422            use super::Prover;
1423            let pedersen = pedersen::testing::TestVector::new(comment, seed, alpha, ad);
1424
1425            let secret = Secret::<S>::from_scalar(pedersen.base.sk);
1426            let public = secret.public();
1427
1428            let io = VrfIo {
1429                input: Input::<S>::from_affine_unchecked(pedersen.base.h),
1430                output: Output::from_affine_unchecked(pedersen.base.gamma),
1431            };
1432
1433            let ring_setup = <S as RingSuiteExt>::ring_setup();
1434
1435            use ark_std::rand::SeedableRng;
1436            let rng = &mut ark_std::rand::rngs::StdRng::from_seed([42; 32]);
1437            let prover_idx = 3;
1438            let mut ring_pks = common::random_vec::<AffinePoint<S>>(TEST_RING_SIZE, Some(rng));
1439            ring_pks[prover_idx] = public.0;
1440
1441            // Blinding is disabled to make the proof reproducible
1442            let ring_ctx = RingContext::<S>::new_without_blinding(TEST_RING_SIZE);
1443            let prover_key = ring_setup.prover_key(&ring_pks).unwrap();
1444            let prover = ring_ctx.into_ring_prover(prover_key, prover_idx);
1445            let proof = secret.prove(io, ad, &prover);
1446
1447            let verifier_key = ring_setup.verifier_key(&ring_pks).unwrap();
1448            let ring_pks_com = verifier_key.commitment();
1449
1450            {
1451                // Just in case...
1452                let mut p = (Vec::new(), Vec::new());
1453                pedersen.proof.serialize_compressed(&mut p.0).unwrap();
1454                proof.pedersen_proof.serialize_compressed(&mut p.1).unwrap();
1455                assert_eq!(p.0, p.1);
1456            }
1457
1458            Self {
1459                pedersen,
1460                ring_pks: ring_pks.try_into().unwrap(),
1461                ring_pks_com,
1462                ring_proof: proof.ring_proof,
1463            }
1464        }
1465
1466        fn from_map(map: &common::TestVectorMap) -> Self {
1467            let pedersen = pedersen::testing::TestVector::from_map(map);
1468
1469            let ring_pks = map.get::<[AffinePoint<S>; TEST_RING_SIZE]>("ring_pks");
1470            let ring_pks_com = map.get::<RingCommitment<S>>("ring_pks_com");
1471            let ring_proof = map.get::<RingBareProof<S>>("ring_proof");
1472
1473            Self {
1474                pedersen,
1475                ring_pks,
1476                ring_pks_com,
1477                ring_proof,
1478            }
1479        }
1480
1481        fn to_map(&self) -> common::TestVectorMap {
1482            let mut map = self.pedersen.to_map();
1483            map.set("ring_pks", &self.ring_pks);
1484            map.set("ring_pks_com", &self.ring_pks_com);
1485            map.set("ring_proof", &self.ring_proof);
1486            map
1487        }
1488
1489        fn run(&self) {
1490            self.pedersen.run();
1491
1492            let io = VrfIo {
1493                input: Input::<S>::from_affine_unchecked(self.pedersen.base.h),
1494                output: Output::from_affine_unchecked(self.pedersen.base.gamma),
1495            };
1496            let secret = Secret::from_scalar(self.pedersen.base.sk);
1497            let public = secret.public();
1498            assert_eq!(public.0, self.pedersen.base.pk);
1499
1500            let ring_setup = <S as RingSuiteExt>::ring_setup();
1501
1502            let prover_idx = self.ring_pks.iter().position(|&pk| pk == public.0).unwrap();
1503
1504            // Blinding is disabled to reproduce the exact proof in the vector
1505            let ring_ctx = RingContext::<S>::new_without_blinding(TEST_RING_SIZE);
1506            let prover_key = ring_setup.prover_key(&self.ring_pks).unwrap();
1507            let prover = ring_ctx.ring_prover(prover_key, prover_idx);
1508
1509            let verifier_key = ring_setup.verifier_key(&self.ring_pks).unwrap();
1510            let verifier = ring_ctx.ring_verifier(verifier_key);
1511
1512            let proof = secret.prove(io, &self.pedersen.base.ad, &prover);
1513
1514            {
1515                // Check if Pedersen proof matches
1516                let mut p = (Vec::new(), Vec::new());
1517                self.pedersen.proof.serialize_compressed(&mut p.0).unwrap();
1518                proof.pedersen_proof.serialize_compressed(&mut p.1).unwrap();
1519                assert_eq!(p.0, p.1);
1520            }
1521
1522            {
1523                // Check if the (deterministic) ring proof matches
1524                let mut p = (Vec::new(), Vec::new());
1525                self.ring_proof.serialize_compressed(&mut p.0).unwrap();
1526                proof.ring_proof.serialize_compressed(&mut p.1).unwrap();
1527                assert_eq!(p.0, p.1);
1528            }
1529
1530            assert!(Public::verify(io, &self.pedersen.base.ad, &proof, &verifier).is_ok());
1531        }
1532    }
1533}