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