ark_vrf/
ietf.rs

1//! # IETF-VRF
2//!
3//! Implementation of the ECVRF scheme defined in [RFC-9381](https://datatracker.ietf.org/doc/rfc9381),
4//! extended to support binding additional data to the proof.
5//!
6//! The extension specification is available at:
7//! <https://github.com/davxy/bandersnatch-vrf-spec>
8//!
9//! ## Usage Example
10//!
11//! ```rust,ignore
12//! // Key generation
13//! let secret = Secret::<MySuite>::from_seed(b"seed");
14//! let public = secret.public();
15//!
16//! // Proving
17//! use ark_vrf::ietf::Prover;
18//! let input = Input::from(my_data);
19//! let output = secret.output(input);
20//! let proof = secret.prove(input, output, aux_data);
21//!
22//! // Verification
23//! use ark_vrf::ietf::Verifier;
24//! let result = public.verify(input, output, aux_data, &proof);
25//! ```
26
27use super::*;
28
29pub trait IetfSuite: Suite {}
30
31impl<T> IetfSuite for T where T: Suite {}
32
33/// IETF VRF proof.
34///
35/// Schnorr-based proof of correctness for a VRF evaluation:
36/// - `c`: Challenge scalar derived from public parameters
37/// - `s`: Response scalar satisfying the verification equation
38#[derive(Debug, Clone)]
39pub struct Proof<S: IetfSuite> {
40    pub c: ScalarField<S>,
41    pub s: ScalarField<S>,
42}
43
44impl<S: IetfSuite> CanonicalSerialize for Proof<S> {
45    fn serialize_with_mode<W: ark_serialize::Write>(
46        &self,
47        mut writer: W,
48        compress: ark_serialize::Compress,
49    ) -> Result<(), ark_serialize::SerializationError> {
50        let c_buf = codec::scalar_encode::<S>(&self.c);
51        if c_buf.len() < S::CHALLENGE_LEN {
52            // Encoded scalar length must be at least S::CHALLENGE_LEN
53            return Err(ark_serialize::SerializationError::NotEnoughSpace);
54        }
55        let buf = if S::Codec::BIG_ENDIAN {
56            &c_buf[c_buf.len() - S::CHALLENGE_LEN..]
57        } else {
58            &c_buf[..S::CHALLENGE_LEN]
59        };
60        writer.write_all(buf)?;
61        self.s.serialize_with_mode(&mut writer, compress)?;
62        Ok(())
63    }
64
65    fn serialized_size(&self, _compress_always: ark_serialize::Compress) -> usize {
66        S::CHALLENGE_LEN + self.s.compressed_size()
67    }
68}
69
70impl<S: IetfSuite> CanonicalDeserialize for Proof<S> {
71    fn deserialize_with_mode<R: ark_serialize::Read>(
72        mut reader: R,
73        compress: ark_serialize::Compress,
74        validate: ark_serialize::Validate,
75    ) -> Result<Self, ark_serialize::SerializationError> {
76        let mut c_buf = ark_std::vec![0; S::CHALLENGE_LEN];
77        if reader.read_exact(&mut c_buf[..]).is_err() {
78            return Err(ark_serialize::SerializationError::InvalidData);
79        }
80        let c = S::Codec::scalar_decode(&c_buf);
81        let s = <ScalarField<S> as CanonicalDeserialize>::deserialize_with_mode(
82            &mut reader,
83            compress,
84            validate,
85        )?;
86        Ok(Proof { c, s })
87    }
88}
89
90impl<S: IetfSuite> ark_serialize::Valid for Proof<S> {
91    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
92        self.c.check()?;
93        self.s.check()?;
94        Ok(())
95    }
96}
97
98/// Trait for types that can generate VRF proofs.
99///
100/// Implementors can create cryptographic proofs that a VRF output
101/// is correctly derived from an input using their secret key.
102pub trait Prover<S: IetfSuite> {
103    /// Generate a proof for the given input/output and additional data.
104    ///
105    /// Creates a non-interactive zero-knowledge proof binding the input, output,
106    /// and additional data to the prover's public key.
107    ///
108    /// * `input` - VRF input point
109    /// * `output` - VRF output point (γ = x·H)
110    /// * `ad` - Additional data to bind to the proof
111    fn prove(&self, input: Input<S>, output: Output<S>, ad: impl AsRef<[u8]>) -> Proof<S>;
112}
113
114/// Trait for entities that can verify VRF proofs.
115///
116/// Implementors can verify that a VRF output is correctly derived
117/// from an input using a specific public key.
118pub trait Verifier<S: IetfSuite> {
119    /// Verify a proof for the given input/output and additional data.
120    ///
121    /// Verifies the cryptographic relationship between input, output, and proof
122    /// under the verifier's public key.
123    ///
124    /// * `input` - VRF input point
125    /// * `output` - Claimed VRF output point
126    /// * `aux` - Additional data bound to the proof
127    /// * `proof` - The proof to verify
128    ///
129    /// Returns `Ok(())` if verification succeeds, `Err(Error::VerificationFailure)` otherwise.
130    fn verify(
131        &self,
132        input: Input<S>,
133        output: Output<S>,
134        aux: impl AsRef<[u8]>,
135        proof: &Proof<S>,
136    ) -> Result<(), Error>;
137}
138
139impl<S: IetfSuite> Prover<S> for Secret<S> {
140    /// Implements the IETF VRF proving algorithm.
141    ///
142    /// This follows the procedure specified in RFC-9381 section 5.1, with extensions
143    /// to support binding additional data to the proof:
144    ///
145    /// 1. Generate a deterministic nonce `k` based on the secret key and input
146    /// 2. Compute nonce commitments `k_b` and `k_h`
147    /// 3. Compute the challenge `c` using all public values, nonce commitments and the
148    ///    additional data
149    /// 4. Compute the response `s = k + c * secret`
150    fn prove(&self, input: Input<S>, output: Output<S>, ad: impl AsRef<[u8]>) -> Proof<S> {
151        let k = S::nonce(&self.scalar, input);
152
153        let k_b = smul!(S::generator(), k).into_affine();
154        let k_h = smul!(input.0, k).into_affine();
155
156        let c = S::challenge(
157            &[&self.public.0, &input.0, &output.0, &k_b, &k_h],
158            ad.as_ref(),
159        );
160        let s = k + c * self.scalar;
161        Proof { c, s }
162    }
163}
164
165impl<S: IetfSuite> Verifier<S> for Public<S> {
166    /// Implements the IETF VRF verification algorithm.
167    ///
168    /// This follows the procedure specified in RFC-9381 section 5.3, with extensions
169    /// to support verifying additional data bound to the proof:
170    ///
171    /// 1. Compute `u = s*G - c*Y` where G is the generator and Y is the public key
172    /// 2. Compute `v = s*H - c*O` where H is the input point and O is the output point
173    /// 3. Recompute the expected challenge `c_exp` using all public values, `u`, `v` and
174    ///    the additional data
175    /// 4. Verify that `c_exp == c` from the proof
176    fn verify(
177        &self,
178        input: Input<S>,
179        output: Output<S>,
180        aux: impl AsRef<[u8]>,
181        proof: &Proof<S>,
182    ) -> Result<(), Error> {
183        let Proof { c, s } = proof;
184
185        let s_b = S::generator() * s;
186        let c_y = self.0 * c;
187        let u = (s_b - c_y).into_affine();
188
189        let s_h = input.0 * s;
190        let c_o = output.0 * c;
191        let v = (s_h - c_o).into_affine();
192
193        let c_exp = S::challenge(&[&self.0, &input.0, &output.0, &u, &v], aux.as_ref());
194        (&c_exp == c)
195            .then_some(())
196            .ok_or(Error::VerificationFailure)
197    }
198}
199
200#[cfg(test)]
201pub mod testing {
202    use super::*;
203    use crate::testing::{self as common, SuiteExt};
204
205    pub fn prove_verify<S: IetfSuite>() {
206        use ietf::{Prover, Verifier};
207
208        let secret = Secret::<S>::from_seed(common::TEST_SEED);
209        let public = secret.public();
210        let input = Input::from(common::random_val(None));
211        let output = secret.output(input);
212
213        let proof = secret.prove(input, output, b"foo");
214        let result = public.verify(input, output, b"foo", &proof);
215        assert!(result.is_ok());
216    }
217
218    #[macro_export]
219    macro_rules! ietf_suite_tests {
220        ($suite:ty) => {
221            mod ietf {
222                use super::*;
223
224                #[test]
225                fn prove_verify() {
226                    $crate::ietf::testing::prove_verify::<$suite>();
227                }
228
229                $crate::test_vectors!($crate::ietf::testing::TestVector<$suite>);
230            }
231        };
232    }
233
234    pub struct TestVector<S: IetfSuite> {
235        pub base: common::TestVector<S>,
236        pub c: ScalarField<S>,
237        pub s: ScalarField<S>,
238    }
239
240    impl<S: IetfSuite> core::fmt::Debug for TestVector<S> {
241        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
242            let c = hex::encode(codec::scalar_encode::<S>(&self.c));
243            let s = hex::encode(codec::scalar_encode::<S>(&self.s));
244            f.debug_struct("TestVector")
245                .field("base", &self.base)
246                .field("proof_c", &c)
247                .field("proof_s", &s)
248                .finish()
249        }
250    }
251
252    impl<S> common::TestVectorTrait for TestVector<S>
253    where
254        S: IetfSuite + SuiteExt + std::fmt::Debug,
255    {
256        fn name() -> String {
257            S::suite_name() + "_ietf"
258        }
259
260        fn new(comment: &str, seed: &[u8], alpha: &[u8], salt: &[u8], ad: &[u8]) -> Self {
261            use super::Prover;
262            let base = common::TestVector::new(comment, seed, alpha, salt, ad);
263            // TODO: store constructed types in the vectors
264            let input = Input::from(base.h);
265            let output = Output::from(base.gamma);
266            let sk = Secret::from_scalar(base.sk);
267            let proof: Proof<S> = sk.prove(input, output, ad);
268            Self {
269                base,
270                c: proof.c,
271                s: proof.s,
272            }
273        }
274
275        fn from_map(map: &common::TestVectorMap) -> Self {
276            let base = common::TestVector::from_map(map);
277            let c = S::Codec::scalar_decode(&map.get_bytes("proof_c"));
278            let s = S::Codec::scalar_decode(&map.get_bytes("proof_s"));
279            Self { base, c, s }
280        }
281
282        fn to_map(&self) -> common::TestVectorMap {
283            let buf = codec::scalar_encode::<S>(&self.c);
284            let proof_c = if S::Codec::BIG_ENDIAN {
285                let len = buf.len();
286                &buf[len - S::CHALLENGE_LEN..]
287            } else {
288                &buf[..S::CHALLENGE_LEN]
289            };
290            let items = [
291                ("proof_c", hex::encode(proof_c)),
292                ("proof_s", hex::encode(codec::scalar_encode::<S>(&self.s))),
293            ];
294            let mut map = self.base.to_map();
295            items.into_iter().for_each(|(name, value)| {
296                map.0.insert(name.to_string(), value);
297            });
298            map
299        }
300
301        fn run(&self) {
302            self.base.run();
303            let input = Input::<S>::from(self.base.h);
304            let output = Output::from(self.base.gamma);
305            let sk = Secret::from_scalar(self.base.sk);
306            let proof = sk.prove(input, output, &self.base.ad);
307            assert_eq!(self.c, proof.c, "VRF proof challenge ('c') mismatch");
308            assert_eq!(self.s, proof.s, "VRF proof response ('s') mismatch");
309
310            let pk = Public(self.base.pk);
311            assert!(pk.verify(input, output, &self.base.ad, &proof).is_ok());
312        }
313    }
314}