Skip to main content

ark_vrf/
tiny.rs

1//! # Tiny VRF
2//!
3//! Compact VRF-AD scheme producing a short `(c, s)` proof. Prepends the Schnorr
4//! pair `(G, Y)` to the I/O list and proves a single DLEQ on the delinearized
5//! merged pair. The challenge scalar `c` is stored instead of the nonce commitment,
6//! yielding a smaller proof at the cost of not supporting batch verification.
7//!
8//! ## Usage
9//!
10//! ```rust,ignore
11//! use ark_vrf::suites::bandersnatch::*;
12//! use ark_vrf::tiny::{Prover, Verifier};
13//!
14//! let secret = Secret::from_seed([0; 32]);
15//! let public = secret.public();
16//! let input = Input::new(b"example input").unwrap();
17//! let io = secret.vrf_io(input);
18//!
19//! // Proving
20//! let proof = secret.prove(io, b"aux data");
21//!
22//! // Verification
23//! let result = public.verify(io, b"aux data", &proof);
24//! ```
25
26use super::*;
27use utils::common::DomSep;
28use utils::straus::short_msm;
29
30/// Marker trait for suites that support the Tiny VRF scheme.
31///
32/// Blanket-implemented for all types implementing [`Suite`].
33pub trait TinySuite: Suite {}
34
35impl<T> TinySuite for T where T: Suite {}
36
37#[inline(always)]
38fn vrf_transcript<S: TinySuite>(
39    public: AffinePoint<S>,
40    ios: impl AsRef<[VrfIo<S>]>,
41    ad: impl AsRef<[u8]>,
42) -> (S::Transcript, VrfIo<S>) {
43    utils::vrf_transcript_with_schnorr(DomSep::TinyVrf, public, ios, ad)
44}
45
46/// Tiny VRF proof.
47///
48/// Schnorr-based proof of correctness for a VRF evaluation:
49/// - `c`: Challenge scalar derived from public parameters
50/// - `s`: Response scalar satisfying the verification equation
51#[derive(Debug, Clone)]
52pub struct Proof<S: TinySuite> {
53    /// Challenge scalar derived from public parameters.
54    pub c: ScalarField<S>,
55    /// Response scalar satisfying the verification equation.
56    pub s: ScalarField<S>,
57}
58
59impl<S: TinySuite> CanonicalSerialize for Proof<S> {
60    fn serialize_with_mode<W: ark_serialize::Write>(
61        &self,
62        mut writer: W,
63        compress: ark_serialize::Compress,
64    ) -> Result<(), ark_serialize::SerializationError> {
65        let scalar_len = ScalarField::<S>::MODULUS_BIT_SIZE.div_ceil(8) as usize;
66        if scalar_len < utils::common::CHALLENGE_LEN {
67            // Encoded scalar length must be at least utils::common::CHALLENGE_LEN
68            return Err(ark_serialize::SerializationError::InvalidData);
69        }
70        let mut c_buf = [0; 128];
71        self.c
72            .serialize_compressed(&mut c_buf[..])
73            .expect("c_buf is big enough");
74        let c_buf = &c_buf[..utils::common::CHALLENGE_LEN];
75        writer.write_all(c_buf)?;
76        self.s.serialize_with_mode(&mut writer, compress)?;
77        Ok(())
78    }
79
80    fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
81        utils::common::CHALLENGE_LEN + self.s.serialized_size(compress)
82    }
83}
84
85impl<S: TinySuite> CanonicalDeserialize for Proof<S> {
86    fn deserialize_with_mode<R: ark_serialize::Read>(
87        mut reader: R,
88        compress: ark_serialize::Compress,
89        validate: ark_serialize::Validate,
90    ) -> Result<Self, ark_serialize::SerializationError> {
91        let mut c_buf = [0u8; utils::common::CHALLENGE_LEN];
92        if reader.read_exact(&mut c_buf[..]).is_err() {
93            return Err(ark_serialize::SerializationError::InvalidData);
94        }
95        let c = ScalarField::<S>::from_le_bytes_mod_order(&c_buf);
96        let s = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
97            &mut reader,
98            compress,
99            validate,
100        )?;
101        Ok(Proof { c, s })
102    }
103}
104
105impl<S: TinySuite> ark_serialize::Valid for Proof<S> {
106    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
107        self.c.check()?;
108        self.s.check()?;
109        Ok(())
110    }
111}
112
113/// Trait for types that can generate Tiny VRF proofs.
114pub trait Prover<S: TinySuite> {
115    /// Generate a proof for the given VRF I/O pairs and additional data.
116    ///
117    /// Multiple I/O pairs are delinearized into a single merged pair before proving.
118    fn prove(&self, ios: impl AsRef<[VrfIo<S>]>, ad: impl AsRef<[u8]>) -> Proof<S>;
119}
120
121/// Trait for entities that can verify Tiny VRF proofs.
122///
123/// All curve points involved in verification (public key and I/O pairs)
124/// are assumed to be in the prime-order subgroup. This is guaranteed
125/// when points are constructed through checked constructors ([`Public::from_affine`],
126/// [`Input::from_affine`], [`Output::from_affine`]) or through trusted
127/// operations like [`Input::new`] (hash-to-curve) and [`Secret::vrf_io`].
128///
129/// Using unchecked constructors (e.g. [`Input::from_affine_unchecked`]) places
130/// the burden of subgroup validation on the caller. Passing points with
131/// cofactor components leads to undefined verification behavior.
132///
133/// The group identity is checked unconditionally, for the public key and for
134/// every I/O pair. Neither binds the proof to a signer: the secret scalar of
135/// the identity key is publicly known, and a pair holding the identity is
136/// satisfied by every secret key.
137pub trait Verifier<S: TinySuite> {
138    /// Verify a proof for the given VRF I/O pairs and additional data.
139    ///
140    /// Multiple I/O pairs are delinearized into a single merged pair before verifying.
141    ///
142    /// Returns `Ok(())` if verification succeeds, `Err(Error::InvalidData)` if the
143    /// public key or any I/O pair point is the group identity,
144    /// `Err(Error::VerificationFailure)` otherwise.
145    fn verify(
146        &self,
147        ios: impl AsRef<[VrfIo<S>]>,
148        aux: impl AsRef<[u8]>,
149        proof: &Proof<S>,
150    ) -> Result<(), Error>;
151}
152
153impl<S: TinySuite> Prover<S> for Secret<S> {
154    /// Tiny VRF proving algorithm.
155    ///
156    /// Prepends the Schnorr pair (G, Y) to the I/O list and proves a single
157    /// DLEQ on the delinearized merged pair:
158    ///
159    /// 1. Generate a deterministic nonce `k`
160    /// 2. Compute nonce commitment `R = k * I_m`
161    /// 3. Compute the challenge `c`
162    /// 4. Compute the response `s = k + c * x`
163    fn prove(&self, ios: impl AsRef<[VrfIo<S>]>, ad: impl AsRef<[u8]>) -> Proof<S> {
164        let (t, io) = vrf_transcript::<S>(self.public.0, ios, ad);
165
166        let k = S::nonce(&self.scalar, Some(t.clone()));
167
168        // R = k * I_m
169        let r = smul!(io.input.0, k).into_affine();
170
171        let c = S::challenge(&[&r], Some(t));
172        let s = k + c * self.scalar;
173        Proof { c, s }
174    }
175}
176
177impl<S: TinySuite> Verifier<S> for Public<S> {
178    /// Tiny VRF verification algorithm.
179    ///
180    /// 1. Compute `R = s * I_m - c * O_m`
181    /// 2. Recompute the expected challenge `c_exp`
182    /// 3. Verify that `c_exp == c`
183    fn verify(
184        &self,
185        ios: impl AsRef<[VrfIo<S>]>,
186        ad: impl AsRef<[u8]>,
187        proof: &Proof<S>,
188    ) -> Result<(), Error> {
189        // With Y = 0 the challenge term drops out of the equation below and
190        // anyone can produce a matching (c, s) pair.
191        if self.is_identity() {
192            return Err(Error::InvalidData);
193        }
194
195        // A pair holding the identity satisfies O = x*I for every x, so it
196        // binds its VRF output to no signer.
197        let ios = ios.as_ref();
198        if ios.iter().any(VrfIo::has_identity) {
199            return Err(Error::InvalidData);
200        }
201
202        let (t, io) = vrf_transcript::<S>(self.0, ios, ad);
203
204        let Proof { c, s } = proof;
205
206        // R = s * I_m - c * O_m
207        let r = short_msm(&[io.input.0, io.output.0], &[*s, -*c], 2).into_affine();
208
209        let c_exp = S::challenge(&[&r], Some(t));
210        (c_exp == *c)
211            .then_some(())
212            .ok_or(Error::VerificationFailure)
213    }
214}
215
216#[cfg(test)]
217pub mod testing {
218    use super::*;
219    use crate::testing::{self as common, SuiteExt};
220
221    pub fn prove_verify<S: TinySuite>() {
222        let secret = Secret::<S>::from_seed(common::TEST_SEED);
223        let public = secret.public();
224        let input = Input::from_affine_unchecked(common::random_val(None));
225        let io = secret.vrf_io(input);
226
227        let proof = secret.prove(io, b"foo");
228        let result = public.verify(io, b"foo", &proof);
229        assert!(result.is_ok());
230    }
231
232    pub fn prove_verify_multi_empty<S: TinySuite>() {
233        let secret = Secret::<S>::from_seed(common::TEST_SEED);
234        let public = secret.public();
235
236        let ios: [VrfIo<S>; 0] = [];
237        let proof = secret.prove(ios, b"bar");
238
239        assert!(public.verify(ios, b"bar", &proof).is_ok());
240
241        // Wrong ad should fail
242        assert!(public.verify(ios, b"baz", &proof).is_err());
243    }
244
245    /// N=1 slice produces same proof as passing a single `VrfIo`.
246    pub fn prove_verify_multi_single<S: TinySuite>() {
247        let secret = Secret::<S>::from_seed(common::TEST_SEED);
248        let public = secret.public();
249        let input = Input::from_affine_unchecked(common::random_val(None));
250        let io = secret.vrf_io(input);
251
252        let proof_single = secret.prove(io, b"foo");
253        let proof_slice = secret.prove([io], b"foo");
254
255        // Byte-identical proofs
256        let encode = |p: &tiny::Proof<S>| {
257            let mut buf = Vec::new();
258            p.serialize_compressed(&mut buf).unwrap();
259            buf
260        };
261        assert_eq!(encode(&proof_single), encode(&proof_slice));
262
263        // Cross-verification
264        assert!(public.verify(io, b"foo", &proof_slice).is_ok());
265        assert!(public.verify([io], b"foo", &proof_single).is_ok());
266    }
267
268    /// An identity public key must be rejected by the verifier.
269    ///
270    /// `Y = 0` is the public key of the zero secret key, which everybody knows,
271    /// so the proof built below is one any attacker can build. Verification is
272    /// handed a raw `Public` to make sure the rejection does not depend on the
273    /// key having gone through a checked constructor.
274    pub fn identity_public_key_rejected<S: TinySuite>() {
275        let identity = Public::<S>(AffinePoint::<S>::zero());
276        let zero_secret = Secret::<S>::from_scalar(ScalarField::<S>::zero());
277
278        let proof = zero_secret.prove([], b"forgery");
279        assert!(identity.verify([], b"forgery", &proof).is_err());
280    }
281
282    /// An I/O pair holding the identity must be rejected by the verifier.
283    ///
284    /// `(I, O) = (0, 0)` satisfies `O = x * I` for every secret key, so the
285    /// verification equation accepts it and two different keys produce two
286    /// valid proofs for the same pair. The pair therefore binds its VRF output
287    /// to nobody, and only an explicit check keeps it out. The second case
288    /// hides the bad pair behind a good one, where the merged pair alone is not
289    /// enough to catch it.
290    pub fn identity_io_pair_rejected<S: TinySuite>() {
291        let identity_io = VrfIo::<S> {
292            input: Input(AffinePoint::<S>::zero()),
293            output: Output(AffinePoint::<S>::zero()),
294        };
295
296        for seed in [common::TEST_SEED, [0x11; 32]] {
297            let secret = Secret::<S>::from_seed(seed);
298            let public = secret.public();
299
300            let proof = secret.prove([identity_io], b"forgery");
301            assert!(public.verify([identity_io], b"forgery", &proof).is_err());
302
303            let good_io = secret.vrf_io(Input::new(b"good").unwrap());
304            let ios = [good_io, identity_io];
305            let proof = secret.prove(ios, b"forgery");
306            assert!(public.verify(ios, b"forgery", &proof).is_err());
307        }
308    }
309
310    /// N=3 multi proof: verify succeeds; tampered output/input/ad fails.
311    pub fn prove_verify_multi<S: TinySuite>() {
312        let secret = Secret::<S>::from_seed(common::TEST_SEED);
313        let public = secret.public();
314
315        let mut ios: Vec<VrfIo<S>> = (0..3u8)
316            .map(|i| {
317                let input = Input::new(&[i + 1]).unwrap();
318                secret.vrf_io(input)
319            })
320            .collect();
321        ios.push(VrfIo {
322            input: Input(S::Affine::generator()),
323            output: Output(public.0),
324        });
325
326        let proof = secret.prove(&ios[..], b"bar");
327        assert!(public.verify(&ios[..], b"bar", &proof).is_ok());
328
329        // Tamper: wrong output on ios[1]
330        let mut bad_ios = ios.clone();
331        bad_ios[1].output = secret.output(ios[0].input);
332        assert!(public.verify(&bad_ios[..], b"bar", &proof).is_err());
333
334        // Tamper: wrong input on ios[0]
335        let mut bad_ios = ios.clone();
336        bad_ios[0].input = ios[1].input;
337        assert!(public.verify(&bad_ios[..], b"bar", &proof).is_err());
338
339        // Tamper: wrong ad
340        assert!(public.verify(&ios[..], b"baz", &proof).is_err());
341    }
342
343    #[macro_export]
344    macro_rules! tiny_suite_tests {
345        ($suite:ty) => {
346            mod tiny {
347                use super::*;
348
349                #[test]
350                fn prove_verify() {
351                    $crate::tiny::testing::prove_verify::<$suite>();
352                }
353
354                #[test]
355                fn prove_verify_multi_single() {
356                    $crate::tiny::testing::prove_verify_multi_single::<$suite>();
357                }
358
359                #[test]
360                fn prove_verify_multi() {
361                    $crate::tiny::testing::prove_verify_multi::<$suite>();
362                }
363
364                #[test]
365                fn prove_verify_multi_empty() {
366                    $crate::tiny::testing::prove_verify_multi_empty::<$suite>();
367                }
368
369                #[test]
370                fn identity_public_key_rejected() {
371                    $crate::tiny::testing::identity_public_key_rejected::<$suite>();
372                }
373
374                #[test]
375                fn identity_io_pair_rejected() {
376                    $crate::tiny::testing::identity_io_pair_rejected::<$suite>();
377                }
378
379                $crate::test_vectors!($crate::tiny::testing::TestVector<$suite>);
380            }
381        };
382    }
383
384    pub struct TestVector<S: TinySuite> {
385        pub base: common::TestVector<S>,
386        pub c: ScalarField<S>,
387        pub s: ScalarField<S>,
388    }
389
390    impl<S: TinySuite> core::fmt::Debug for TestVector<S> {
391        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
392            let c = hex::encode(common::scalar_encode::<S>(&self.c));
393            let s = hex::encode(common::scalar_encode::<S>(&self.s));
394            f.debug_struct("TestVector")
395                .field("base", &self.base)
396                .field("proof_c", &c)
397                .field("proof_s", &s)
398                .finish()
399        }
400    }
401
402    impl<S> common::TestVectorTrait for TestVector<S>
403    where
404        S: TinySuite + SuiteExt + std::fmt::Debug,
405    {
406        fn name() -> String {
407            S::SUITE_NAME.to_string() + "_tiny"
408        }
409
410        fn new(comment: &str, seed: &[u8; 32], alpha: &[u8], ad: &[u8]) -> Self {
411            use super::Prover;
412            let base = common::TestVector::new(comment, seed, alpha, ad);
413            let io = VrfIo {
414                input: Input::from_affine_unchecked(base.h),
415                output: Output::from_affine_unchecked(base.gamma),
416            };
417            let sk = Secret::from_scalar(base.sk);
418            let proof: Proof<S> = sk.prove(io, ad);
419            Self {
420                base,
421                c: proof.c,
422                s: proof.s,
423            }
424        }
425
426        fn from_map(map: &common::TestVectorMap) -> Self {
427            let base = common::TestVector::from_map(map);
428            let c = common::scalar_decode::<S>(&map.get_bytes("proof_c"));
429            let s = common::scalar_decode::<S>(&map.get_bytes("proof_s"));
430            Self { base, c, s }
431        }
432
433        fn to_map(&self) -> common::TestVectorMap {
434            let buf = common::scalar_encode::<S>(&self.c);
435            let proof_c = &buf[..utils::common::CHALLENGE_LEN];
436            let items = [
437                ("proof_c", hex::encode(proof_c)),
438                ("proof_s", hex::encode(common::scalar_encode::<S>(&self.s))),
439            ];
440            let mut map = self.base.to_map();
441            items.into_iter().for_each(|(name, value)| {
442                map.0.insert(name.to_string(), value);
443            });
444            map
445        }
446
447        fn run(&self) {
448            self.base.run();
449            let io = VrfIo {
450                input: Input::<S>::from_affine_unchecked(self.base.h),
451                output: Output::from_affine_unchecked(self.base.gamma),
452            };
453            let sk = Secret::from_scalar(self.base.sk);
454            let proof = sk.prove(io, &self.base.ad);
455            assert_eq!(self.c, proof.c, "VRF proof challenge ('c') mismatch");
456            assert_eq!(self.s, proof.s, "VRF proof response ('s') mismatch");
457
458            let pk = Public(self.base.pk);
459            assert!(pk.verify(io, &self.base.ad, &proof).is_ok());
460        }
461    }
462}