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
114pub type AffinePoint<S> = <S as Suite>::Affine;
115pub type BaseField<S> = <AffinePoint<S> as AffineRepr>::BaseField;
116pub type ScalarField<S> = <AffinePoint<S> as AffineRepr>::ScalarField;
117pub type CurveConfig<S> = <AffinePoint<S> as AffineRepr>::Config;
118
119pub type HashOutput<S> = digest::Output<<S as Suite>::Hasher>;
120
121/// Overarching errors.
122#[derive(Debug)]
123pub enum Error {
124 /// Verification error
125 VerificationFailure,
126 /// Bad input data
127 InvalidData,
128}
129
130impl From<ark_serialize::SerializationError> for Error {
131 fn from(_err: ark_serialize::SerializationError) -> Self {
132 Error::InvalidData
133 }
134}
135
136/// Defines a cipher suite.
137///
138/// This trait can be used to easily implement a VRF which follows the guidelines
139/// given by RFC-9381 section 5.5.
140///
141/// Can be easily customized to implement more exotic VRF types by overwriting
142/// the default methods implementations.
143pub trait Suite: Copy {
144 /// Suite identifier (aka `suite_string` in RFC-9381)
145 const SUITE_ID: &'static [u8];
146
147 /// Challenge encoded length.
148 ///
149 /// Must be at least equal to the Hash length.
150 const CHALLENGE_LEN: usize;
151
152 /// Curve point in affine representation.
153 ///
154 /// The point is guaranteed to be in the correct prime order subgroup
155 /// by the `AffineRepr` bound.
156 type Affine: AffineRepr;
157
158 /// Overarching hasher.
159 ///
160 /// Used wherever an hash is required: nonce, challenge, MAC, etc.
161 type Hasher: Digest;
162
163 /// Overarching codec.
164 ///
165 /// Used wherever we need to encode/decode points and scalars.
166 type Codec: codec::Codec<Self>;
167
168 /// Nonce generation as described by RFC-9381 section 5.4.2.
169 ///
170 /// The default implementation provides the variant described
171 /// by section 5.4.2.2 of RFC-9381 which in turn is a derived
172 /// from steps 2 and 3 in section 5.1.6 of
173 /// [RFC8032](https://tools.ietf.org/html/rfc8032).
174 ///
175 /// The algorithm generate the nonce value in a deterministic
176 /// pseudorandom fashion.
177 ///
178 /// `Hasher` output **MUST** be be at least 64 bytes.
179 ///
180 /// # Panics
181 ///
182 /// This function panics if `Hasher` output is less than 64 bytes.
183 #[inline(always)]
184 fn nonce(sk: &ScalarField<Self>, pt: Input<Self>) -> ScalarField<Self> {
185 utils::nonce_rfc_8032::<Self>(sk, &pt.0)
186 }
187
188 /// Challenge generation as described by RCF-9381 section 5.4.3.
189 ///
190 /// Hashes several points on the curve.
191 ///
192 /// This implementation extends the RFC procedure to allow adding
193 /// some optional additional data too the hashing procedure.
194 #[inline(always)]
195 fn challenge(pts: &[&AffinePoint<Self>], ad: &[u8]) -> ScalarField<Self> {
196 utils::challenge_rfc_9381::<Self>(pts, ad)
197 }
198
199 /// Hash data to a curve point.
200 ///
201 /// By default uses "try and increment" method described by RFC-9381.
202 ///
203 /// The input `data` is assumed to be `[salt||]alpha` according to the RFC-9381.
204 /// In other words, salt is not applied by this function.
205 #[inline(always)]
206 fn data_to_point(data: &[u8]) -> Option<AffinePoint<Self>> {
207 utils::hash_to_curve_tai_rfc_9381::<Self>(data)
208 }
209
210 /// Map the point to a hash value using `Self::Hasher`.
211 ///
212 /// By default uses the algorithm described by RFC-9381 without cofactor clearing.
213 #[inline(always)]
214 fn point_to_hash(pt: &AffinePoint<Self>) -> HashOutput<Self> {
215 utils::point_to_hash_rfc_9381::<Self>(pt, false)
216 }
217
218 /// Generator used through all the suite.
219 ///
220 /// Defaults to Arkworks provided generator.
221 #[inline(always)]
222 fn generator() -> AffinePoint<Self> {
223 Self::Affine::generator()
224 }
225}
226
227/// Secret key for VRF operations.
228///
229/// Contains the private scalar and cached public key.
230/// Implements automatic zeroization on drop.
231#[derive(Debug, Clone, PartialEq)]
232pub struct Secret<S: Suite> {
233 // Secret scalar.
234 pub scalar: ScalarField<S>,
235 // Cached public point.
236 pub public: Public<S>,
237}
238
239impl<S: Suite> Drop for Secret<S> {
240 fn drop(&mut self) {
241 self.scalar.zeroize()
242 }
243}
244
245impl<S: Suite> CanonicalSerialize for Secret<S> {
246 fn serialize_with_mode<W: ark_std::io::prelude::Write>(
247 &self,
248 writer: W,
249 compress: ark_serialize::Compress,
250 ) -> Result<(), ark_serialize::SerializationError> {
251 self.scalar.serialize_with_mode(writer, compress)
252 }
253
254 fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
255 self.scalar.serialized_size(compress)
256 }
257}
258
259impl<S: Suite> CanonicalDeserialize for Secret<S> {
260 fn deserialize_with_mode<R: ark_std::io::prelude::Read>(
261 reader: R,
262 compress: ark_serialize::Compress,
263 validate: ark_serialize::Validate,
264 ) -> Result<Self, ark_serialize::SerializationError> {
265 let scalar = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
266 reader, compress, validate,
267 )?;
268 Ok(Self::from_scalar(scalar))
269 }
270}
271
272impl<S: Suite> ark_serialize::Valid for Secret<S> {
273 fn check(&self) -> Result<(), ark_serialize::SerializationError> {
274 self.scalar.check()
275 }
276}
277
278impl<S: Suite> Secret<S> {
279 /// Construct a `Secret` from the given scalar.
280 pub fn from_scalar(scalar: ScalarField<S>) -> Self {
281 let public = Public((S::generator() * scalar).into_affine());
282 Self { scalar, public }
283 }
284
285 /// Derives a `Secret` scalar deterministically from a seed.
286 ///
287 /// The seed is hashed using `Suite::Hasher`, and the output is reduced
288 /// modulo the curve's order to produce a valid scalar in the range
289 /// `[1, n - 1]`. No clamping or multiplication by the cofactor is
290 /// performed, regardless of the curve.
291 ///
292 /// The caller is responsible for ensuring that the resulting scalar is
293 /// used safely with respect to the target curve's cofactor and subgroup
294 /// properties.
295 pub fn from_seed(seed: &[u8]) -> Self {
296 let mut cnt = 0_u8;
297 let scalar = loop {
298 let mut hasher = S::Hasher::new();
299 hasher.update(seed);
300 if cnt > 0 {
301 hasher.update([cnt]);
302 }
303 let bytes = hasher.finalize();
304 let scalar = ScalarField::<S>::from_le_bytes_mod_order(&bytes[..]);
305 if !scalar.is_zero() {
306 break scalar;
307 }
308 cnt += 1;
309 };
310 Self::from_scalar(scalar)
311 }
312
313 /// Construct an ephemeral `Secret` using the provided randomness source.
314 pub fn from_rand(rng: &mut impl ark_std::rand::RngCore) -> Self {
315 let mut seed = [0u8; 32];
316 rng.fill_bytes(&mut seed);
317 Self::from_seed(&seed)
318 }
319
320 /// Get the associated public key.
321 pub fn public(&self) -> Public<S> {
322 self.public
323 }
324
325 /// Get the VRF output point relative to input.
326 pub fn output(&self, input: Input<S>) -> Output<S> {
327 Output(smul!(input.0, self.scalar).into_affine())
328 }
329}
330
331/// Public key generic over the cipher suite.
332///
333/// Elliptic curve point representing the public component of a VRF key pair.
334#[derive(Debug, Copy, Clone, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
335pub struct Public<S: Suite>(pub AffinePoint<S>);
336
337impl<S: Suite> Public<S> {
338 /// Construct from inner affine point.
339 ///
340 /// This allows creating a public key from an existing curve point.
341 pub fn from(value: AffinePoint<S>) -> Self {
342 Self(value)
343 }
344}
345
346/// VRF input point generic over the cipher suite.
347///
348/// Elliptic curve point representing the VRF input.
349#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
350pub struct Input<S: Suite>(pub AffinePoint<S>);
351
352impl<S: Suite> Input<S> {
353 /// Construct from [`Suite::data_to_point`].
354 ///
355 /// Maps arbitrary data to a curve point via hash-to-curve.
356 pub fn new(data: &[u8]) -> Option<Self> {
357 S::data_to_point(data).map(Input)
358 }
359
360 /// Construct from inner affine point.
361 pub fn from(value: AffinePoint<S>) -> Self {
362 Self(value)
363 }
364}
365
366/// VRF output point generic over the cipher suite.
367///
368/// Elliptic curve point representing the VRF output.
369#[derive(Debug, Clone, Copy, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
370pub struct Output<S: Suite>(pub AffinePoint<S>);
371
372impl<S: Suite> Output<S> {
373 /// Construct from inner affine point.
374 ///
375 /// This allows creating an output from an existing curve point.
376 pub fn from(value: AffinePoint<S>) -> Self {
377 Self(value)
378 }
379
380 /// Hash the output point to a deterministic byte string.
381 pub fn hash(&self) -> HashOutput<S> {
382 S::point_to_hash(&self.0)
383 }
384}
385
386/// Type aliases for the given suite.
387#[macro_export]
388macro_rules! suite_types {
389 ($suite:ident) => {
390 #[allow(dead_code)]
391 pub type Secret = $crate::Secret<$suite>;
392 #[allow(dead_code)]
393 pub type Public = $crate::Public<$suite>;
394 #[allow(dead_code)]
395 pub type Input = $crate::Input<$suite>;
396 #[allow(dead_code)]
397 pub type Output = $crate::Output<$suite>;
398 #[allow(dead_code)]
399 pub type AffinePoint = $crate::AffinePoint<$suite>;
400 #[allow(dead_code)]
401 pub type ScalarField = $crate::ScalarField<$suite>;
402 #[allow(dead_code)]
403 pub type BaseField = $crate::BaseField<$suite>;
404 #[allow(dead_code)]
405 pub type IetfProof = $crate::ietf::Proof<$suite>;
406 #[allow(dead_code)]
407 pub type PedersenProof = $crate::pedersen::Proof<$suite>;
408 };
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use suites::testing::{Input, Secret};
415 use testing::{TEST_SEED, random_val};
416
417 #[test]
418 fn vrf_output_check() {
419 use ark_std::rand::SeedableRng;
420 let mut rng = rand_chacha::ChaCha20Rng::from_seed([42; 32]);
421 let secret = Secret::from_seed(TEST_SEED);
422 let input = Input::from(random_val(Some(&mut rng)));
423 let output = secret.output(input);
424
425 let expected = "71c1b2ee6e46c59e3bd0e2f0e2852b90ab56abb223180b00bd6c8ec6b11af18c";
426 assert_eq!(expected, hex::encode(output.hash()));
427 }
428}