Skip to main content

ark_vrf/
lib.rs

1//! # Elliptic Curve VRF-AD
2//!
3//! Implementations of Verifiable Random Functions with Additional Data (VRF-AD)
4//! based on elliptic curve cryptography. Built on the [Arkworks](https://github.com/arkworks-rs)
5//! framework with configurable cryptographic parameters.
6//!
7//! VRF-AD extends standard VRF constructions by binding auxiliary data to the proof,
8//! providing stronger contextual security guarantees.
9//!
10//! ## Schemes
11//!
12//! - **IETF VRF**: ECVRF implementation compliant with [RFC9381](https://datatracker.ietf.org/doc/rfc9381)
13//!
14//! - **Pedersen VRF**: Key-hiding VRF using Pedersen commitments as described in
15//!   [BCHSV23](https://eprint.iacr.org/2023/002)
16//!
17//! - **Ring VRF**: Zero-knowledge VRF with signer anonymity within a key set, based on
18//!   [BCHSV23](https://eprint.iacr.org/2023/002)
19//!
20//! ### Specifications
21//!
22//! - [VRF Schemes](https://github.com/davxy/bandersnatch-vrf-spec)
23//! - [Ring Proof](https://github.com/davxy/ring-proof-spec)
24//!
25//! ## Built-In suites
26//!
27//! The library conditionally includes the following pre-configured suites (see features section):
28//!
29//! - **Ed25519-SHA-512-TAI**: Supports IETF and Pedersen VRF.
30//! - **Secp256r1-SHA-256-TAI**: Supports IETF and Pedersen VRF.
31//! - **Bandersnatch** (_Edwards curve on BLS12-381_): Supports IETF, Pedersen, and Ring VRF.
32//! - **JubJub** (_Edwards curve on BLS12-381_): Supports IETF, Pedersen, and Ring VRF.
33//! - **Baby-JubJub** (_Edwards curve on BN254_): Supports IETF, Pedersen, and Ring VRF.
34//!
35//! ## Usage
36//!
37//! ```rust,ignore
38//! use ark_vrf::suites::bandersnatch::*;
39//!
40//! let secret = Secret::from_seed(b"example seed");
41//! let public = secret.public();
42//! let input = Input::new(b"example input").unwrap();
43//! let output = secret.output(input);
44//! let hash_bytes = output.hash();
45//! ```
46//!
47//! ### Proof Generation Schemes
48//!
49//! - [ietf] vrf proof
50//! - [pedersen] vrf proof
51//! - [ring] vrf proof
52//!
53//! ## Features
54//!
55//! - `default`: `std`
56//! - `full`: Enables all features listed below except `secret-split`, `parallel`, `asm`, `rfc-6979`, `test-vectors`.
57//! - `secret-split`: Point scalar multiplication with secret split. Secret scalar is split into the sum
58//!   of two scalars, which randomly mutate but retain the same sum. Incurs 2x penalty in some internal
59//!   sensible scalar multiplications, but provides side channel defenses.
60//! - `ring`: Ring-VRF for the curves supporting it.
61//! - `rfc-6979`: Support for nonce generation according to RFC-9381 section 5.4.2.1.
62//! - `test-vectors`: Deterministic ring-vrf proof. Useful for reproducible test vectors generation.
63//!
64//! ### Curves
65//!
66//! - `ed25519`
67//! - `jubjub`
68//! - `bandersnatch`
69//! - `baby-jubjub`
70//! - `secp256r1`
71//!
72//! ### Arkworks optimizations
73//!
74//! - `parallel`: Parallel execution where worth using `rayon`.
75//! - `asm`: Assembly implementation of some low level operations.
76//!
77//! ## License
78//!
79//! Distributed under the [MIT License](./LICENSE).
80
81#![cfg_attr(not(feature = "std"), no_std)]
82#![deny(unsafe_code)]
83
84use ark_ec::{AffineRepr, CurveGroup};
85use ark_ff::{PrimeField, Zero};
86use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
87use ark_std::vec::Vec;
88
89use digest::Digest;
90use zeroize::Zeroize;
91
92pub mod codec;
93pub mod ietf;
94pub mod pedersen;
95pub mod suites;
96pub mod utils;
97
98#[cfg(feature = "ring")]
99pub mod ring;
100
101#[cfg(test)]
102mod testing;
103
104/// Re-export stuff that may be useful downstream.
105pub mod reexports {
106    pub use ark_ec;
107    pub use ark_ff;
108    pub use ark_serialize;
109    pub use ark_std;
110}
111
112use codec::Codec;
113
114/// Suite's affine curve point type.
115pub type AffinePoint<S> = <S as Suite>::Affine;
116/// Suite's base field type.
117pub type BaseField<S> = <AffinePoint<S> as AffineRepr>::BaseField;
118/// Suite's scalar field type.
119pub type ScalarField<S> = <AffinePoint<S> as AffineRepr>::ScalarField;
120/// Suite's curve configuration type.
121pub type CurveConfig<S> = <AffinePoint<S> as AffineRepr>::Config;
122
123/// Suite's hash output type.
124pub type HashOutput<S> = digest::Output<<S as Suite>::Hasher>;
125
126/// Overarching errors.
127#[derive(Debug)]
128pub enum Error {
129    /// Verification error
130    VerificationFailure,
131    /// Bad input data
132    InvalidData,
133}
134
135impl From<ark_serialize::SerializationError> for Error {
136    fn from(_err: ark_serialize::SerializationError) -> Self {
137        Error::InvalidData
138    }
139}
140
141/// Defines a cipher suite.
142///
143/// This trait can be used to easily implement a VRF which follows the guidelines
144/// given by RFC-9381 section 5.5.
145///
146/// Can be easily customized to implement more exotic VRF types by overwriting
147/// the default methods implementations.
148pub trait Suite: Copy {
149    /// Suite identifier (aka `suite_string` in RFC-9381)
150    const SUITE_ID: &'static [u8];
151
152    /// Challenge encoded length.
153    ///
154    /// Must be at least equal to the Hash length.
155    const CHALLENGE_LEN: usize;
156
157    /// Curve point in affine representation.
158    ///
159    /// The point is guaranteed to be in the correct prime order subgroup
160    /// by the `AffineRepr` bound.
161    type Affine: AffineRepr;
162
163    /// Overarching hasher.
164    ///
165    /// Used wherever an hash is required: nonce, challenge, MAC, etc.
166    type Hasher: Digest + Clone;
167
168    /// Overarching codec.
169    ///
170    /// Used wherever we need to encode/decode points and scalars.
171    type Codec: codec::Codec<Self>;
172
173    /// Nonce generation as described by RFC-9381 section 5.4.2.
174    ///
175    /// The default implementation provides the variant described
176    /// by section 5.4.2.2 of RFC-9381 which in turn is a derived
177    /// from steps 2 and 3 in section 5.1.6 of
178    /// [RFC8032](https://tools.ietf.org/html/rfc8032).
179    ///
180    /// The algorithm generate the nonce value in a deterministic
181    /// pseudorandom fashion.
182    ///
183    /// `Hasher` output **MUST** be be at least 64 bytes.
184    ///
185    /// # Panics
186    ///
187    /// This function panics if `Hasher` output is less than 64 bytes.
188    #[inline(always)]
189    fn nonce(sk: &ScalarField<Self>, pt: Input<Self>, ad: &[u8]) -> ScalarField<Self> {
190        utils::nonce_rfc_8032::<Self>(sk, &pt.0, ad)
191    }
192
193    /// Challenge generation as described by RCF-9381 section 5.4.3.
194    ///
195    /// Hashes several points on the curve.
196    ///
197    /// This implementation extends the RFC procedure to allow adding
198    /// some optional additional data too the hashing procedure.
199    #[inline(always)]
200    fn challenge(pts: &[&AffinePoint<Self>], ad: &[u8]) -> ScalarField<Self> {
201        utils::challenge_rfc_9381::<Self>(pts, ad)
202    }
203
204    /// Hash data to a curve point.
205    ///
206    /// By default uses "try and increment" method described by RFC-9381.
207    ///
208    /// The input `data` is assumed to be `[salt||]alpha` according to the RFC-9381.
209    /// In other words, salt is not applied by this function.
210    #[inline(always)]
211    fn data_to_point(data: &[u8]) -> Option<AffinePoint<Self>> {
212        utils::hash_to_curve_tai_rfc_9381::<Self>(data)
213    }
214
215    /// Map the point to a hash value using `Self::Hasher`.
216    ///
217    /// By default uses the algorithm described by RFC-9381 without cofactor clearing.
218    #[inline(always)]
219    fn point_to_hash(pt: &AffinePoint<Self>) -> HashOutput<Self> {
220        utils::point_to_hash_rfc_9381::<Self>(pt, false)
221    }
222
223    /// Generator used through all the suite.
224    ///
225    /// Defaults to Arkworks provided generator.
226    #[inline(always)]
227    fn generator() -> AffinePoint<Self> {
228        Self::Affine::generator()
229    }
230}
231
232/// Secret key for VRF operations.
233///
234/// Contains the private scalar and cached public key.
235/// Implements automatic zeroization on drop.
236#[derive(Debug, Clone, PartialEq)]
237pub struct Secret<S: Suite> {
238    /// Secret scalar.
239    pub(crate) scalar: ScalarField<S>,
240    /// Cached public key.
241    pub(crate) public: Public<S>,
242}
243
244impl<S: Suite> Drop for Secret<S> {
245    fn drop(&mut self) {
246        self.scalar.zeroize()
247    }
248}
249
250impl<S: Suite> CanonicalSerialize for Secret<S> {
251    fn serialize_with_mode<W: ark_std::io::prelude::Write>(
252        &self,
253        writer: W,
254        compress: ark_serialize::Compress,
255    ) -> Result<(), ark_serialize::SerializationError> {
256        self.scalar.serialize_with_mode(writer, compress)
257    }
258
259    fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
260        self.scalar.serialized_size(compress)
261    }
262}
263
264impl<S: Suite> CanonicalDeserialize for Secret<S> {
265    fn deserialize_with_mode<R: ark_std::io::prelude::Read>(
266        reader: R,
267        compress: ark_serialize::Compress,
268        validate: ark_serialize::Validate,
269    ) -> Result<Self, ark_serialize::SerializationError> {
270        let scalar = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
271            reader, compress, validate,
272        )?;
273        Ok(Self::from_scalar(scalar))
274    }
275}
276
277impl<S: Suite> ark_serialize::Valid for Secret<S> {
278    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
279        self.scalar.check()
280    }
281}
282
283impl<S: Suite> Secret<S> {
284    /// Construct a `Secret` from the given scalar.
285    pub fn from_scalar(scalar: ScalarField<S>) -> Self {
286        let public = Public((S::generator() * scalar).into_affine());
287        Self { scalar, public }
288    }
289
290    /// Derives a `Secret` scalar deterministically from a seed.
291    ///
292    /// The seed is hashed using `Suite::Hasher`, and the output is reduced
293    /// modulo the curve's order to produce a valid scalar in the range
294    /// `[1, n - 1]`. No clamping or multiplication by the cofactor is
295    /// performed, regardless of the curve.
296    ///
297    /// The caller is responsible for ensuring that the resulting scalar is
298    /// used safely with respect to the target curve's cofactor and subgroup
299    /// properties.
300    pub fn from_seed(seed: &[u8]) -> Self {
301        let mut cnt = 0_u8;
302        let scalar = loop {
303            let mut hasher = S::Hasher::new();
304            hasher.update(seed);
305            if cnt > 0 {
306                hasher.update([cnt]);
307            }
308            let bytes = hasher.finalize();
309            let scalar = ScalarField::<S>::from_le_bytes_mod_order(&bytes[..]);
310            if !scalar.is_zero() {
311                break scalar;
312            }
313            cnt += 1;
314        };
315        Self::from_scalar(scalar)
316    }
317
318    /// Construct an ephemeral `Secret` using the provided randomness source.
319    pub fn from_rand(rng: &mut impl ark_std::rand::RngCore) -> Self {
320        let mut seed = [0u8; 32];
321        rng.fill_bytes(&mut seed);
322        Self::from_seed(&seed)
323    }
324
325    /// Get the secret scalar.
326    pub fn scalar(&self) -> &ScalarField<S> {
327        &self.scalar
328    }
329
330    /// Get the associated public key.
331    pub fn public(&self) -> Public<S> {
332        self.public
333    }
334
335    /// Get the VRF output point relative to input.
336    pub fn output(&self, input: Input<S>) -> Output<S> {
337        Output(smul!(input.0, self.scalar).into_affine())
338    }
339}
340
341/// Public key generic over the cipher suite.
342///
343/// Elliptic curve point representing the public component of a VRF key pair.
344#[derive(Debug, Copy, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
345pub struct Public<S: Suite>(pub AffinePoint<S>);
346
347impl<S: Suite> Public<S> {
348    /// Construct from an affine point.
349    pub fn from_affine(value: AffinePoint<S>) -> Self {
350        Self(value)
351    }
352}
353
354/// VRF input point generic over the cipher suite.
355///
356/// Elliptic curve point representing the VRF input.
357#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
358pub struct Input<S: Suite>(pub AffinePoint<S>);
359
360impl<S: Suite> Input<S> {
361    /// Construct from [`Suite::data_to_point`].
362    ///
363    /// Maps arbitrary data to a curve point via hash-to-curve.
364    pub fn new(data: &[u8]) -> Option<Self> {
365        S::data_to_point(data).map(Input)
366    }
367}
368
369impl<S: Suite> Input<S> {
370    /// Construct from an affine point.
371    pub fn from_affine(value: AffinePoint<S>) -> Self {
372        Self(value)
373    }
374}
375
376/// VRF output point generic over the cipher suite.
377///
378/// Elliptic curve point representing the VRF output.
379#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
380pub struct Output<S: Suite>(pub AffinePoint<S>);
381
382impl<S: Suite> Output<S> {
383    /// Construct from an affine point.
384    pub fn from_affine(value: AffinePoint<S>) -> Self {
385        Self(value)
386    }
387}
388
389impl<S: Suite> Output<S> {
390    /// Hash the output point to a deterministic byte string.
391    pub fn hash(&self) -> HashOutput<S> {
392        S::point_to_hash(&self.0)
393    }
394}
395
396/// Type aliases for the given suite.
397#[macro_export]
398macro_rules! suite_types {
399    ($suite:ident) => {
400        #[allow(dead_code)]
401        pub type Secret = $crate::Secret<$suite>;
402        #[allow(dead_code)]
403        pub type Public = $crate::Public<$suite>;
404        #[allow(dead_code)]
405        pub type Input = $crate::Input<$suite>;
406        #[allow(dead_code)]
407        pub type Output = $crate::Output<$suite>;
408        #[allow(dead_code)]
409        pub type AffinePoint = $crate::AffinePoint<$suite>;
410        #[allow(dead_code)]
411        pub type ScalarField = $crate::ScalarField<$suite>;
412        #[allow(dead_code)]
413        pub type BaseField = $crate::BaseField<$suite>;
414        #[allow(dead_code)]
415        pub type IetfProof = $crate::ietf::Proof<$suite>;
416        #[allow(dead_code)]
417        pub type PedersenProof = $crate::pedersen::Proof<$suite>;
418        #[allow(dead_code)]
419        pub type PedersenBatchItem = $crate::pedersen::BatchItem<$suite>;
420        #[allow(dead_code)]
421        pub type PedersenBatchVerifier = $crate::pedersen::BatchVerifier<$suite>;
422    };
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428    use suites::testing::{Input, Secret};
429    use testing::{TEST_SEED, random_val};
430
431    #[test]
432    fn vrf_output_check() {
433        use ark_std::rand::SeedableRng;
434        let mut rng = rand_chacha::ChaCha20Rng::from_seed([42; 32]);
435        let secret = Secret::from_seed(TEST_SEED);
436        let input = Input::from_affine(random_val(Some(&mut rng)));
437        let output = secret.output(input);
438
439        let expected = "71c1b2ee6e46c59e3bd0e2f0e2852b90ab56abb223180b00bd6c8ec6b11af18c";
440        assert_eq!(expected, hex::encode(output.hash()));
441    }
442}