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