Skip to main content

ark_vrf/
lib.rs

1//! # Elliptic Curve VRF
2//!
3//! Implementations of Verifiable Random Function with Additional Data (VRF-AD)
4//! schemes built on a transcript-based Fiat-Shamir transform with support for
5//! multiple input/output pairs via delinearization.
6//!
7//! Built on the [Arkworks](https://github.com/arkworks-rs) framework with
8//! configurable cryptographic parameters and `no_std` support.
9//!
10//! ## Security
11//!
12//! VRF input points **must** be constructed via hash-to-curve (e.g.
13//! [`Input::new`]) so that nobody knows their discrete-log relation to the
14//! generator `G`. If the prover knew such a relation, they could forge
15//! outputs. This is critical because the delinearization merges the Schnorr
16//! and VRF pairs into a single check.
17//!
18//! ## Schemes
19//!
20//! - **Tiny VRF**: Compact proof. Loosely inspired by
21//!   [RFC-9381](https://datatracker.ietf.org/doc/rfc9381), adapted with a
22//!   transcript-based Fiat-Shamir transform, support for additional data, and
23//!   multiple I/O pairs via delinearization.
24//!
25//! - **Thin VRF**: Same structure as Tiny VRF but stores the nonce commitment
26//!   instead of the challenge, enabling batch verification at the cost of a
27//!   slightly larger proof.
28//!
29//! - **Pedersen VRF**: Key-hiding VRF based on the construction introduced by
30//!   [BCHSV23](https://eprint.iacr.org/2023/002). Replaces the public key with a
31//!   Pedersen commitment to the secret key, serving as a building block for
32//!   anonymized ring signatures.
33//!
34//! - **Ring VRF**: Anonymized ring VRF combining Pedersen VRF with the ring proof
35//!   scheme derived from [CSSV22](https://eprint.iacr.org/2022/1362). Proves that
36//!   a single blinded key is a member of a committed ring without revealing which one.
37//!
38//! ### Specifications
39//!
40//! - [VRF Schemes](https://github.com/davxy/bandersnatch-vrf-spec)
41//! - [Ring Proof](https://github.com/davxy/ring-proof-spec)
42//!
43//! ## Built-In suites
44//!
45//! The library conditionally includes the following pre-configured suites (see features section):
46//!
47//! - **Ed25519**: Supports Tiny, Thin, and Pedersen VRF.
48//! - **Secp256r1**: Supports Tiny, Thin, and Pedersen VRF.
49//! - **Bandersnatch** (_Edwards curve on BLS12-381_): Supports Tiny, Thin, Pedersen, and Ring VRF.
50//! - **JubJub** (_Edwards curve on BLS12-381_): Supports Tiny, Thin, Pedersen, and Ring VRF.
51//! - **Baby-JubJub** (_Edwards curve on BN254_): Supports Tiny, Thin, Pedersen, and Ring VRF.
52//!
53//! ## Usage
54//!
55//! ```rust,ignore
56//! use ark_vrf::suites::bandersnatch::*;
57//!
58//! let secret = Secret::from_seed([0; 32]);
59//! let public = secret.public();
60//! let input = Input::new(b"example input").unwrap();
61//! let output = secret.output(input);
62//! let hash_bytes: [u8; 32] = output.hash();
63//! ```
64//!
65//! ## Features
66//!
67//! - `default`: `std`
68//! - `full`: Enables all features listed below except `secret-split`, `parallel`, `asm`.
69//! - `secret-split`: Split-secret scalar multiplication. Secret scalar is split into the sum
70//!   of two scalars, which randomly mutate but retain the same sum. Incurs 2x penalty in the
71//!   secret scalar multiplications of the Tiny, Thin and Pedersen VRFs (output, nonce and
72//!   blinding), but provides side channel defenses for them. Ring proof witness generation is
73//!   not covered by this feature: it relies on the branch-free handling of the secret bits
74//!   implemented in the `w3f-ring-proof` and `w3f-plonk-common` crates.
75//! - `ring`: Ring-VRF for the curves supporting it.
76//!
77//! ### Curves
78//!
79//! - `ed25519`
80//! - `jubjub`
81//! - `bandersnatch`
82//! - `baby-jubjub`
83//! - `secp256r1`
84//!
85//! ### Arkworks optimizations
86//!
87//! - `parallel`: Parallel execution where worth using `rayon`.
88//! - `asm`: Assembly implementation of some low level operations.
89//!
90//! ## License
91//!
92//! Distributed under the [MIT License](./LICENSE).
93
94#![cfg_attr(not(feature = "std"), no_std)]
95#![deny(unsafe_code)]
96
97use ark_ec::{AffineRepr, CurveGroup};
98use ark_ff::{PrimeField, Zero};
99use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
100use ark_std::vec::Vec;
101
102use utils::transcript::Transcript;
103use zeroize::Zeroize;
104
105pub mod pedersen;
106pub mod suites;
107pub mod thin;
108pub mod tiny;
109pub mod utils;
110
111#[cfg(feature = "ring")]
112pub mod ring;
113
114#[cfg(test)]
115mod testing;
116
117/// Re-export stuff that may be useful downstream.
118pub mod reexports {
119    pub use ark_ec;
120    pub use ark_ff;
121    pub use ark_serialize;
122    pub use ark_std;
123}
124
125/// Suite's affine curve point type.
126pub type AffinePoint<S> = <S as Suite>::Affine;
127/// Suite's base field type.
128pub type BaseField<S> = <AffinePoint<S> as AffineRepr>::BaseField;
129/// Suite's scalar field type.
130pub type ScalarField<S> = <AffinePoint<S> as AffineRepr>::ScalarField;
131/// Suite's curve configuration type.
132pub type CurveConfig<S> = <AffinePoint<S> as AffineRepr>::Config;
133
134/// Crate error type.
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum Error {
137    /// Proof verification failed.
138    VerificationFailure,
139    /// Invalid input data (e.g. point not in the prime-order subgroup,
140    /// forbidden identity point, deserialization failure).
141    InvalidData,
142    /// Ring capacity exceeded (requested ring size beyond the parameters
143    /// capacity, SRS too short, or no free slots left in the builder).
144    RingCapacityExceeded,
145    /// SRS lookup failed during incremental ring construction.
146    SrsLookupFailed,
147}
148
149impl core::fmt::Display for Error {
150    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
151        let msg = match self {
152            Error::VerificationFailure => "proof verification failed",
153            Error::InvalidData => "invalid data",
154            Error::RingCapacityExceeded => "ring capacity exceeded",
155            Error::SrsLookupFailed => "SRS lookup failed",
156        };
157        f.write_str(msg)
158    }
159}
160
161impl core::error::Error for Error {}
162
163impl From<ark_serialize::SerializationError> for Error {
164    fn from(_err: ark_serialize::SerializationError) -> Self {
165        Error::InvalidData
166    }
167}
168
169/// Defines a cipher suite.
170///
171/// Configures the elliptic curve, transcript, and core operations (nonce
172/// generation, challenge derivation, hash-to-curve) for a VRF-AD scheme.
173/// The default implementations are inspired by RFC-9381 and RFC-8032 but
174/// use a pluggable [`Transcript`]-based Fiat-Shamir transform rather than
175/// the specific hash constructions prescribed by the RFC. Default methods
176/// can be overridden to implement custom VRF variants.
177pub trait Suite: Copy {
178    /// Suite identifier.
179    ///
180    /// A unique byte string used for transcript domain separation and as the
181    /// hash-to-curve DST prefix. The actual constructions a `SUITE_ID` stands
182    /// for are defined by the suite specification (see each suite's module
183    /// docs). Implementations targeting interop must use the same string.
184    const SUITE_ID: &'static [u8];
185
186    /// Curve point in affine representation.
187    ///
188    /// The point is guaranteed to be in the correct prime order subgroup
189    /// by the `AffineRepr` bound.
190    type Affine: AffineRepr;
191
192    /// Fiat-Shamir transcript.
193    ///
194    /// Provides absorb/squeeze interface for challenge generation,
195    /// nonce derivation, delinearization, and other hash-based operations.
196    type Transcript: Transcript;
197
198    /// Generator used through all the suite.
199    ///
200    /// Defaults to Arkworks provided generator.
201    #[inline(always)]
202    fn generator() -> AffinePoint<Self> {
203        Self::Affine::generator()
204    }
205
206    /// Generate a nonce scalar from the secret key and transcript state.
207    ///
208    /// The transcript typically carries shared state from `vrf_transcript`,
209    /// binding the nonce to the I/O pairs and additional data.
210    ///
211    /// Defaults to [`utils::nonce`] (deterministic, inspired by RFC-8032 section 5.1.6).
212    #[inline(always)]
213    fn nonce(sk: &ScalarField<Self>, transcript: Option<Self::Transcript>) -> ScalarField<Self> {
214        utils::nonce::<Self>(sk, transcript)
215    }
216
217    /// Derive a challenge scalar from curve points and transcript state.
218    ///
219    /// Absorbs curve points into the transcript and squeezes a scalar.
220    /// The transcript typically carries shared state from `vrf_transcript`.
221    ///
222    /// Defaults to [`utils::challenge`] (inspired by RFC-9381 section 5.4.3).
223    #[inline(always)]
224    fn challenge(
225        pts: &[&AffinePoint<Self>],
226        transcript: Option<Self::Transcript>,
227    ) -> ScalarField<Self> {
228        utils::challenge::<Self>(pts, transcript)
229    }
230
231    /// Hash data to a curve point.
232    ///
233    /// The input `data` is the raw pre-image; any salting must be applied
234    /// by the caller before invoking this method.
235    ///
236    /// Defaults to [`utils::hash_to_curve_tai`] (try-and-increment).
237    /// Override for alternative methods like [`utils::hash_to_curve_ell2_xmd`] (Elligator2).
238    #[inline(always)]
239    fn data_to_point(data: &[u8]) -> Option<AffinePoint<Self>> {
240        utils::hash_to_curve_tai::<Self>(data)
241    }
242
243    /// Map a curve point to a hash value.
244    ///
245    /// Defaults to [`utils::point_to_hash`].
246    #[inline(always)]
247    fn point_to_hash<const N: usize>(pt: &AffinePoint<Self>) -> [u8; N] {
248        utils::point_to_hash::<Self, N>(pt, false)
249    }
250}
251
252/// Secret key for VRF operations.
253///
254/// Contains the private scalar and cached public key.
255/// Implements automatic zeroization on drop. The `Debug` output redacts
256/// the scalar, and equality is evaluated in constant time.
257#[derive(Clone)]
258pub struct Secret<S: Suite> {
259    /// Secret scalar.
260    pub(crate) scalar: ScalarField<S>,
261    /// Cached public key.
262    pub(crate) public: Public<S>,
263}
264
265impl<S: Suite> core::fmt::Debug for Secret<S> {
266    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
267        f.debug_struct("Secret")
268            .field("scalar", &"<redacted>")
269            .field("public", &self.public.0)
270            .finish()
271    }
272}
273
274impl<S: Suite> PartialEq for Secret<S> {
275    /// Timing does not depend on the scalars content or on where they differ.
276    fn eq(&self, other: &Self) -> bool {
277        let mut lhs = self.scalar.into_bigint();
278        let mut rhs = other.scalar.into_bigint();
279        let diff = lhs
280            .as_ref()
281            .iter()
282            .zip(rhs.as_ref())
283            .fold(0u64, |acc, (a, b)| acc | (a ^ b));
284        lhs.as_mut().zeroize();
285        rhs.as_mut().zeroize();
286        diff == 0
287    }
288}
289
290impl<S: Suite> Drop for Secret<S> {
291    fn drop(&mut self) {
292        self.scalar.zeroize()
293    }
294}
295
296impl<S: Suite> CanonicalSerialize for Secret<S> {
297    fn serialize_with_mode<W: ark_std::io::prelude::Write>(
298        &self,
299        writer: W,
300        compress: ark_serialize::Compress,
301    ) -> Result<(), ark_serialize::SerializationError> {
302        self.scalar.serialize_with_mode(writer, compress)
303    }
304
305    fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
306        self.scalar.serialized_size(compress)
307    }
308}
309
310impl<S: Suite> CanonicalDeserialize for Secret<S> {
311    fn deserialize_with_mode<R: ark_std::io::prelude::Read>(
312        reader: R,
313        compress: ark_serialize::Compress,
314        validate: ark_serialize::Validate,
315    ) -> Result<Self, ark_serialize::SerializationError> {
316        let scalar = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
317            reader, compress, validate,
318        )?;
319        Ok(Self::from_scalar(scalar))
320    }
321}
322
323impl<S: Suite> ark_serialize::Valid for Secret<S> {
324    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
325        self.scalar.check()
326    }
327}
328
329impl<S: Suite> Secret<S> {
330    /// Construct a `Secret` from the given scalar.
331    pub fn from_scalar(scalar: ScalarField<S>) -> Self {
332        let public = Public((S::generator() * scalar).into_affine());
333        Self { scalar, public }
334    }
335
336    /// Derives a `Secret` scalar deterministically from a seed.
337    ///
338    /// The seed is hashed using the suite's transcript, and the output is
339    /// reduced modulo the curve's order to produce a valid scalar in the
340    /// range `[1, n - 1]`. No clamping or multiplication by the cofactor is
341    /// performed, regardless of the curve.
342    ///
343    /// The caller is responsible for ensuring that the resulting scalar is
344    /// used safely with respect to the target curve's cofactor and subgroup
345    /// properties.
346    pub fn from_seed(mut seed: [u8; 32]) -> Self {
347        let mut cnt = 0_u8;
348        let mut sk = ScalarField::<S>::from_le_bytes_mod_order(&seed);
349        let scalar = loop {
350            let mut transcript = S::Transcript::new(S::SUITE_ID);
351            transcript.absorb_raw(&seed);
352            if cnt > 0 {
353                transcript.absorb_raw(&[cnt]);
354            }
355            let scalar = utils::nonce::<S>(&sk, Some(transcript.clone()));
356            if !scalar.is_zero() {
357                break scalar;
358            }
359            // Reaching 256 consecutive zero scalars is unreachable under
360            // standard assumptions on the transcript hash (probability
361            // ≈ 2^(-65000)); hitting it implies a broken primitive.
362            cnt = cnt
363                .checked_add(1)
364                .expect("unreachable: transcript hash produced 256 consecutive zero scalars");
365        };
366        seed.zeroize();
367        sk.zeroize();
368        Self::from_scalar(scalar)
369    }
370
371    /// Construct an ephemeral `Secret` using the provided randomness source.
372    pub fn from_rand(rng: &mut impl ark_std::rand::RngCore) -> Self {
373        let mut seed = [0u8; 32];
374        rng.fill_bytes(&mut seed);
375        let secret = Self::from_seed(seed);
376        seed.zeroize();
377        secret
378    }
379
380    /// Get the secret scalar.
381    pub fn scalar(&self) -> &ScalarField<S> {
382        &self.scalar
383    }
384
385    /// Get the associated public key.
386    pub fn public(&self) -> Public<S> {
387        self.public
388    }
389
390    /// Get the VRF output point relative to input.
391    pub fn output(&self, input: Input<S>) -> Output<S> {
392        Output(smul!(input.0, self.scalar).into_affine())
393    }
394
395    /// Get the VRF input-output pair relative to input.
396    pub fn vrf_io(&self, input: Input<S>) -> VrfIo<S> {
397        VrfIo {
398            input,
399            output: self.output(input),
400        }
401    }
402}
403
404/// Public key generic over the cipher suite.
405///
406/// Elliptic curve point representing the public component of a VRF key pair.
407#[derive(Debug, Copy, Clone, PartialEq, CanonicalSerialize)]
408pub struct Public<S: Suite>(pub AffinePoint<S>);
409
410impl<S: Suite> ark_serialize::Valid for Public<S> {
411    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
412        if self.is_identity() {
413            return Err(ark_serialize::SerializationError::InvalidData);
414        }
415        self.0.check()
416    }
417}
418
419impl<S: Suite> CanonicalDeserialize for Public<S> {
420    fn deserialize_with_mode<R: ark_serialize::Read>(
421        reader: R,
422        compress: ark_serialize::Compress,
423        validate: ark_serialize::Validate,
424    ) -> Result<Self, ark_serialize::SerializationError> {
425        let point =
426            AffinePoint::<S>::deserialize_with_mode(reader, compress, ark_serialize::Validate::No)?;
427        let public = Self(point);
428        if matches!(validate, ark_serialize::Validate::Yes) {
429            ark_serialize::Valid::check(&public)?;
430        }
431        Ok(public)
432    }
433}
434
435impl<S: Suite> Public<S> {
436    /// Construct from an affine point with validation.
437    ///
438    /// Returns `Error::InvalidData` if the point is not in the prime-order
439    /// subgroup or is the group identity.
440    pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
441        let public = Self(value);
442        ark_serialize::Valid::check(&public).map_err(|_| Error::InvalidData)?;
443        Ok(public)
444    }
445
446    /// Construct from an affine point without validation.
447    ///
448    /// The caller must ensure `value` is in the prime-order subgroup and is not
449    /// the group identity.
450    pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
451        Self(value)
452    }
453
454    /// Whether the key is the group identity.
455    ///
456    /// The identity is not a usable public key: its secret scalar is zero,
457    /// which everybody knows, so anyone can produce proofs that verify against
458    /// it. Verifiers reject it explicitly rather than relying on the caller
459    /// having gone through a checked constructor.
460    pub(crate) fn is_identity(&self) -> bool {
461        self.0.is_zero()
462    }
463}
464
465/// VRF input point generic over the cipher suite.
466///
467/// Elliptic curve point representing the VRF input.
468#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize)]
469pub struct Input<S: Suite>(pub AffinePoint<S>);
470
471impl<S: Suite> ark_serialize::Valid for Input<S> {
472    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
473        if self.is_identity() {
474            return Err(ark_serialize::SerializationError::InvalidData);
475        }
476        self.0.check()
477    }
478}
479
480impl<S: Suite> CanonicalDeserialize for Input<S> {
481    fn deserialize_with_mode<R: ark_serialize::Read>(
482        reader: R,
483        compress: ark_serialize::Compress,
484        validate: ark_serialize::Validate,
485    ) -> Result<Self, ark_serialize::SerializationError> {
486        let point =
487            AffinePoint::<S>::deserialize_with_mode(reader, compress, ark_serialize::Validate::No)?;
488        let input = Self(point);
489        if matches!(validate, ark_serialize::Validate::Yes) {
490            ark_serialize::Valid::check(&input)?;
491        }
492        Ok(input)
493    }
494}
495
496impl<S: Suite> Input<S> {
497    /// Construct from [`Suite::data_to_point`].
498    ///
499    /// Maps arbitrary data to a curve point via hash-to-curve.
500    pub fn new(data: &[u8]) -> Option<Self> {
501        S::data_to_point(data).map(Input)
502    }
503}
504
505impl<S: Suite> Input<S> {
506    /// Construct from an affine point with validation.
507    ///
508    /// Returns `Error::InvalidData` if the point is not in the prime-order
509    /// subgroup or is the group identity.
510    ///
511    /// Note: this only validates subgroup membership, not that the point was
512    /// produced by hash-to-curve. The caller is still responsible for ensuring
513    /// the point is not in a known discrete-log relation with the suite
514    /// generator (required for Thin-VRF soundness).
515    pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
516        let input = Self(value);
517        ark_serialize::Valid::check(&input).map_err(|_| Error::InvalidData)?;
518        Ok(input)
519    }
520
521    /// Construct from an affine point without validation.
522    ///
523    /// # Safety
524    ///
525    /// The caller must ensure that `value` is in the prime-order subgroup, is
526    /// not the group identity, and was produced by a hash-to-curve procedure
527    /// (or is otherwise not in a known discrete-log relation with the suite
528    /// generator). The latter is required for the soundness of schemes like
529    /// Thin-VRF where the input and generator are delinearized into a single
530    /// check.
531    pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
532        Self(value)
533    }
534
535    /// Whether the point is the group identity.
536    ///
537    /// The identity is not a usable VRF input: its output is the identity for
538    /// every secret key, so the pair proves nothing about the signer. Verifiers
539    /// reject it explicitly rather than relying on the caller having gone
540    /// through a checked constructor.
541    pub(crate) fn is_identity(&self) -> bool {
542        self.0.is_zero()
543    }
544}
545
546/// VRF output point generic over the cipher suite.
547///
548/// Elliptic curve point representing the VRF output.
549#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize)]
550pub struct Output<S: Suite>(pub AffinePoint<S>);
551
552impl<S: Suite> ark_serialize::Valid for Output<S> {
553    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
554        if self.is_identity() {
555            return Err(ark_serialize::SerializationError::InvalidData);
556        }
557        self.0.check()
558    }
559}
560
561impl<S: Suite> CanonicalDeserialize for Output<S> {
562    fn deserialize_with_mode<R: ark_serialize::Read>(
563        reader: R,
564        compress: ark_serialize::Compress,
565        validate: ark_serialize::Validate,
566    ) -> Result<Self, ark_serialize::SerializationError> {
567        let point =
568            AffinePoint::<S>::deserialize_with_mode(reader, compress, ark_serialize::Validate::No)?;
569        let output = Self(point);
570        if matches!(validate, ark_serialize::Validate::Yes) {
571            ark_serialize::Valid::check(&output)?;
572        }
573        Ok(output)
574    }
575}
576
577impl<S: Suite> Output<S> {
578    /// Construct from an affine point with validation.
579    ///
580    /// Returns `Error::InvalidData` if the point is not in the prime-order
581    /// subgroup or is the group identity.
582    pub fn from_affine(value: AffinePoint<S>) -> Result<Self, Error> {
583        let output = Self(value);
584        ark_serialize::Valid::check(&output).map_err(|_| Error::InvalidData)?;
585        Ok(output)
586    }
587
588    /// Construct from an affine point without validation.
589    ///
590    /// The caller must ensure `value` is in the prime-order subgroup and is not
591    /// the group identity.
592    pub fn from_affine_unchecked(value: AffinePoint<S>) -> Self {
593        Self(value)
594    }
595
596    /// Whether the point is the group identity.
597    ///
598    /// The identity is the VRF output of every secret key over the identity
599    /// input, so a pair holding it proves nothing about the signer. Verifiers
600    /// reject it explicitly rather than relying on the caller having gone
601    /// through a checked constructor.
602    pub(crate) fn is_identity(&self) -> bool {
603        self.0.is_zero()
604    }
605}
606
607impl<S: Suite> Output<S> {
608    /// Hash the output point to a deterministic byte string.
609    pub fn hash<const N: usize>(&self) -> [u8; N] {
610        S::point_to_hash(&self.0)
611    }
612}
613
614/// VRF input-output pair.
615#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
616pub struct VrfIo<S: Suite> {
617    pub input: Input<S>,
618    pub output: Output<S>,
619}
620
621impl<S: Suite> AsRef<[VrfIo<S>]> for VrfIo<S> {
622    fn as_ref(&self) -> &[VrfIo<S>] {
623        core::slice::from_ref(self)
624    }
625}
626
627impl<S: Suite> VrfIo<S> {
628    /// Whether either point of the pair is the group identity.
629    ///
630    /// Such a pair is satisfied by every secret key, so it binds its VRF output
631    /// to no signer. Verifiers reject it before evaluating their equations.
632    pub(crate) fn has_identity(&self) -> bool {
633        self.input.is_identity() || self.output.is_identity()
634    }
635}
636
637/// Type aliases for the given suite.
638#[macro_export]
639macro_rules! suite_types {
640    ($suite:ident) => {
641        #[allow(dead_code)]
642        pub type Secret = $crate::Secret<$suite>;
643        #[allow(dead_code)]
644        pub type Public = $crate::Public<$suite>;
645        #[allow(dead_code)]
646        pub type Input = $crate::Input<$suite>;
647        #[allow(dead_code)]
648        pub type Output = $crate::Output<$suite>;
649        #[allow(dead_code)]
650        pub type AffinePoint = $crate::AffinePoint<$suite>;
651        #[allow(dead_code)]
652        pub type ScalarField = $crate::ScalarField<$suite>;
653        #[allow(dead_code)]
654        pub type BaseField = $crate::BaseField<$suite>;
655        #[allow(dead_code)]
656        pub type TinyProof = $crate::tiny::Proof<$suite>;
657        #[allow(dead_code)]
658        pub type PedersenProof = $crate::pedersen::Proof<$suite>;
659        #[allow(dead_code)]
660        pub type PedersenBatchItem = $crate::pedersen::BatchItem<$suite>;
661        #[allow(dead_code)]
662        pub type PedersenBatchVerifier = $crate::pedersen::BatchVerifier<$suite>;
663        #[allow(dead_code)]
664        pub type ThinProof = $crate::thin::Proof<$suite>;
665        #[allow(dead_code)]
666        pub type ThinBatchItem = $crate::thin::BatchItem<$suite>;
667        #[allow(dead_code)]
668        pub type ThinBatchVerifier = $crate::thin::BatchVerifier<$suite>;
669        #[allow(dead_code)]
670        pub type VrfIo = $crate::VrfIo<$suite>;
671    };
672}
673
674#[cfg(test)]
675mod tests {
676    use super::*;
677    use crate::tiny::{Prover, Verifier};
678    use ark_ec::AffineRepr;
679    use suites::testing::{Input, Secret, TestSuite};
680    use testing::{TEST_SEED, random_val};
681
682    #[test]
683    fn vrf_output_check() {
684        use ark_std::rand::SeedableRng;
685        let mut rng = ark_std::rand::rngs::StdRng::from_seed([42; 32]);
686        let secret = Secret::from_seed(TEST_SEED);
687        let input = Input::from_affine_unchecked(random_val(Some(&mut rng)));
688        let output = secret.output(input);
689
690        let expected = "4af9bf572a107a8f61faa380667efe27eaf399cc8e718d57ef328924eb51d450";
691        assert_eq!(expected, hex::encode(output.hash::<32>()));
692    }
693
694    /// One `{:?}` on a secret in a downstream log must not leak the key.
695    #[test]
696    fn secret_debug_redacts_scalar() {
697        let secret = Secret::from_seed(TEST_SEED);
698        let out = std::format!("{:?}", secret);
699        let scalar_str = std::format!("{:?}", secret.scalar());
700        assert!(!out.contains(&scalar_str));
701    }
702
703    /// Equality semantics must survive the switch to the constant-time impl.
704    #[test]
705    fn secret_partial_eq() {
706        let secret = Secret::from_seed(TEST_SEED);
707        assert_eq!(secret, Secret::from_seed(TEST_SEED));
708        assert_ne!(secret, Secret::from_seed([0xff; 32]));
709    }
710
711    /// `Error` must stay usable downstream: kinds comparable in tests, and
712    /// values convertible into `dyn Error` chains (`anyhow`, `thiserror`).
713    /// The exact messages are not part of the contract; the impls are.
714    #[test]
715    fn error_type_ergonomics() {
716        let err: &dyn core::error::Error = &Error::VerificationFailure;
717        assert!(!err.to_string().is_empty());
718        assert_eq!(
719            Error::from(ark_serialize::SerializationError::InvalidData),
720            Error::InvalidData
721        );
722        assert_ne!(Error::InvalidData, Error::RingCapacityExceeded);
723    }
724
725    /// The identity is a well-formed subgroup element, so the subgroup check
726    /// alone lets it through. It must be rejected on every checked path, since
727    /// its secret scalar is zero and hence known to anybody.
728    #[test]
729    fn identity_public_key_construction_rejected() {
730        type S = TestSuite;
731
732        let identity = AffinePoint::<S>::zero();
733        assert!(ark_serialize::Valid::check(&identity).is_ok());
734        assert!(crate::Public::<S>::from_affine(identity).is_err());
735
736        let mut buf = Vec::new();
737        identity.serialize_compressed(&mut buf).unwrap();
738        assert!(crate::Public::<S>::deserialize_compressed(&buf[..]).is_err());
739
740        // Unchecked paths are documented as skipping validation.
741        assert!(crate::Public::<S>::deserialize_compressed_unchecked(&buf[..]).is_ok());
742        assert!(crate::Public::<S>::from_affine_unchecked(identity).is_identity());
743    }
744
745    /// The pair `(I, O) = (0, 0)` satisfies `O = x * I` for every secret key,
746    /// so it binds a VRF output to no key at all. Like the identity public key
747    /// it passes the subgroup check, so the checked constructors must reject it
748    /// on their own.
749    #[test]
750    fn identity_io_point_construction_rejected() {
751        type S = TestSuite;
752
753        let identity = AffinePoint::<S>::zero();
754
755        assert!(crate::Input::<S>::from_affine(identity).is_err());
756        assert!(crate::Output::<S>::from_affine(identity).is_err());
757
758        let mut buf = Vec::new();
759        identity.serialize_compressed(&mut buf).unwrap();
760        assert!(crate::Input::<S>::deserialize_compressed(&buf[..]).is_err());
761        assert!(crate::Output::<S>::deserialize_compressed(&buf[..]).is_err());
762
763        // Unchecked paths are documented as skipping validation.
764        assert!(crate::Input::<S>::deserialize_compressed_unchecked(&buf[..]).is_ok());
765        assert!(crate::Output::<S>::deserialize_compressed_unchecked(&buf[..]).is_ok());
766        assert!(crate::Input::<S>::from_affine_unchecked(identity).is_identity());
767        assert!(crate::Output::<S>::from_affine_unchecked(identity).is_identity());
768    }
769
770    #[test]
771    fn prove_uniqueness_vulnerability() {
772        use ark_ff::BigInteger;
773        use ark_std::{One, Zero};
774        use utils::common::{DomSep, ExactChain};
775
776        type S = TestSuite;
777        type Sc = ScalarField<S>;
778
779        let secret = crate::Secret::<S>::from_seed(TEST_SEED);
780        let public = secret.public();
781        let input = Input::new(b"uniqueness attack").unwrap();
782        let honest_output = secret.output(input);
783
784        // 1. Find a low-order point L (order 2 for Ed25519)
785        // For Ed25519, (0, -1) is order 2.
786        let low_order_pt =
787            AffinePoint::<S>::new_unchecked(BaseField::<S>::zero(), -BaseField::<S>::one());
788        assert!(!low_order_pt.is_zero());
789        // Verify it's order 2: 2 * L = O
790        assert!((low_order_pt.into_group() + low_order_pt.into_group()).is_zero());
791
792        // 2. Compute gamma' = gamma + L
793        let malicious_output =
794            Output::from_affine_unchecked((honest_output.0 + low_order_pt).into_affine());
795        assert_ne!(honest_output, malicious_output);
796        assert_ne!(honest_output.hash::<32>(), malicious_output.hash::<32>());
797
798        // 3. Forge a proof by grinding k until c*z_1 is even (so c*z_1*L = 0)
799        //
800        // The verify equation for the VRF I/O part is s*I_m - c*O_m = k*I_m,
801        // where O_m includes z_1*(O_honest + L). For this to hold we need
802        // c*z_1*L = 0, i.e. c*z_1 must be even (since L has order 2).
803        // Since c is odd (ground below) we also need z_1 to be even.
804        // z_1 is the delinearization scalar determined by (pk, ios, ad), so
805        // we iterate over ad values to find one where z_1 is even.
806        let malicious_io = VrfIo {
807            input,
808            output: malicious_output,
809        };
810        let mal_ios = [malicious_io];
811
812        // Search for an ad that produces an even delinearization scalar z_1.
813        let mut ad_ctr = 0u32;
814        let (ad, t, merged_input) = loop {
815            let ad = format!("ad-{ad_ctr}");
816            let schnorr = core::iter::once(VrfIo {
817                input: Input(S::generator()),
818                output: Output(public.0),
819            });
820            let chain = ExactChain::new(schnorr, mal_ios.iter().copied());
821            let (t, zs) =
822                utils::vrf_transcript_scalars_from_iter(DomSep::TinyVrf, chain, ad.as_bytes());
823            // z_1 is the delinearization scalar for the VRF pair
824            if zs[1].into_bigint().is_even() {
825                // Compute merged input: I_m = z_0*G + z_1*I
826                let i_m = (S::generator() * zs[0] + input.0 * zs[1]).into_affine();
827                break (ad, t, i_m);
828            }
829            ad_ctr += 1;
830            assert!(ad_ctr < 100, "Failed to find suitable ad");
831        };
832
833        // Now grind k to get an odd challenge c (so that q-c is even, i.e. (-c)*L = 0).
834        let mut ctr = 0u64;
835        let proof = loop {
836            let mut k_seed = [0u8; 8];
837            k_seed.copy_from_slice(&ctr.to_le_bytes());
838            let k = Sc::from_le_bytes_mod_order(&k_seed);
839
840            // R = k * I_m (merged input including Schnorr pair)
841            let r = (merged_input * k).into_affine();
842
843            let c = S::challenge(&[&r], Some(t.clone()));
844
845            if !c.into_bigint().is_even() {
846                let s = k + c * secret.scalar;
847                break crate::tiny::Proof { c, s };
848            }
849            ctr += 1;
850            assert!(ctr <= 1000, "Grinding failed");
851        };
852
853        // 4. Verify the malicious proof
854        assert!(public.verify(malicious_io, ad.as_bytes(), &proof).is_ok());
855
856        // 5. Verify the honest proof still works
857        let honest_io = VrfIo {
858            input,
859            output: honest_output,
860        };
861        let honest_proof = secret.prove(honest_io, ad.as_bytes());
862        assert!(
863            public
864                .verify(honest_io, ad.as_bytes(), &honest_proof)
865                .is_ok()
866        );
867
868        // Two different outputs for the same input and public key.
869        assert_ne!(honest_output.hash::<32>(), malicious_output.hash::<32>());
870    }
871}