vrf_rfc9381/ec/
edwards25519.rs

1use curve25519_dalek::{EdwardsPoint, Scalar};
2use sha2::Sha512;
3
4use crate::{
5    Ciphersuite,
6    ec::util,
7    error::{VrfError, VrfResult},
8};
9
10const CHALLENGE_LEN: usize = 16;
11const Q_LEN: usize = 32;
12const PT_LEN: usize = 32;
13
14pub mod tai {
15    use sha2::Sha512;
16
17    use crate::{
18        Ciphersuite,
19        ec::edwards25519::{
20            EdVrfProof,
21            internal::{EdVrfEdwards25519PublicKey, EdVrfEdwards25519SecretKey},
22        },
23        error::VrfResult,
24    };
25
26    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
27    pub struct EdVrfEdwards25519Tai;
28
29    impl crate::VRF for EdVrfEdwards25519Tai {
30        type Hash = Sha512;
31        type Proof = EdVrfProof;
32        type Verifier = EdVrfEdwards25519TaiPublicKey;
33        type Prover = EdVrfEdwards25519TaiSecretKey;
34
35        fn ciphersuite(&self) -> Ciphersuite {
36            Ciphersuite::ECVRF_EDWARDS25519_SHA512_TAI
37        }
38    }
39
40    #[derive(zeroize::ZeroizeOnDrop, PartialEq, Eq)]
41    #[repr(transparent)]
42    pub struct EdVrfEdwards25519TaiSecretKey(EdVrfEdwards25519SecretKey);
43
44    impl crate::Prover<Sha512> for EdVrfEdwards25519TaiSecretKey {
45        type Proof = EdVrfProof;
46        type Verifier = EdVrfEdwards25519TaiPublicKey;
47
48        fn from_slice(bytes: &[u8]) -> VrfResult<Self>
49        where
50            Self: Sized,
51        {
52            Ok(Self(EdVrfEdwards25519SecretKey::from_sk(bytes.try_into()?)))
53        }
54
55        #[cfg(feature = "hazmat")]
56        fn x_equals(&self, value: &[u8]) -> bool {
57            self.0.x_equals(value)
58        }
59
60        fn verifier(&self) -> Self::Verifier {
61            EdVrfEdwards25519TaiPublicKey(self.0.public_key())
62        }
63
64        fn prove(&self, alpha: &[u8]) -> VrfResult<Self::Proof> {
65            self.0
66                .prove(Ciphersuite::ECVRF_EDWARDS25519_SHA512_TAI, alpha)
67        }
68    }
69
70    #[derive(Debug, PartialEq, Eq)]
71    #[repr(transparent)]
72    pub struct EdVrfEdwards25519TaiPublicKey(EdVrfEdwards25519PublicKey);
73
74    impl crate::Verifier<Sha512> for EdVrfEdwards25519TaiPublicKey {
75        type Proof = EdVrfProof;
76
77        fn from_slice(bytes: &[u8]) -> VrfResult<Self>
78        where
79            Self: Sized,
80        {
81            Ok(Self(EdVrfEdwards25519PublicKey::from_bytes(bytes)?))
82        }
83
84        fn verify(&self, alpha: &[u8], proof: Self::Proof) -> VrfResult<digest::Output<Sha512>> {
85            use crate::Proof as _;
86            self.0
87                .verify(Ciphersuite::ECVRF_EDWARDS25519_SHA512_TAI, alpha, proof)?
88                .proof_to_hash(Ciphersuite::ECVRF_EDWARDS25519_SHA512_TAI)
89        }
90    }
91}
92
93pub mod elligator2 {
94    use sha2::Sha512;
95
96    use crate::{
97        Ciphersuite,
98        ec::edwards25519::{
99            EdVrfProof,
100            internal::{EdVrfEdwards25519PublicKey, EdVrfEdwards25519SecretKey},
101        },
102        error::VrfResult,
103    };
104
105    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
106    pub struct EdVrfEdwards25519Ell2;
107
108    impl crate::VRF for EdVrfEdwards25519Ell2 {
109        type Hash = Sha512;
110        type Proof = EdVrfProof;
111        type Verifier = EdVrfEdwards25519Ell2PublicKey;
112        type Prover = EdVrfEdwards25519Ell2SecretKey;
113
114        fn ciphersuite(&self) -> Ciphersuite {
115            Ciphersuite::ECVRF_EDWARDS25519_SHA512_ELL2
116        }
117    }
118
119    #[derive(zeroize::ZeroizeOnDrop, PartialEq, Eq)]
120    #[repr(transparent)]
121    pub struct EdVrfEdwards25519Ell2SecretKey(EdVrfEdwards25519SecretKey);
122
123    impl crate::Prover<Sha512> for EdVrfEdwards25519Ell2SecretKey {
124        type Proof = EdVrfProof;
125        type Verifier = EdVrfEdwards25519Ell2PublicKey;
126
127        fn from_slice(bytes: &[u8]) -> VrfResult<Self>
128        where
129            Self: Sized,
130        {
131            Ok(Self(EdVrfEdwards25519SecretKey::from_sk(bytes.try_into()?)))
132        }
133
134        #[cfg(feature = "hazmat")]
135        fn x_equals(&self, value: &[u8]) -> bool {
136            self.0.x_equals(value)
137        }
138
139        fn verifier(&self) -> Self::Verifier {
140            EdVrfEdwards25519Ell2PublicKey(self.0.public_key())
141        }
142
143        fn prove(&self, alpha: &[u8]) -> VrfResult<Self::Proof> {
144            self.0
145                .prove(Ciphersuite::ECVRF_EDWARDS25519_SHA512_ELL2, alpha)
146        }
147    }
148
149    #[derive(Debug, PartialEq, Eq)]
150    #[repr(transparent)]
151    pub struct EdVrfEdwards25519Ell2PublicKey(EdVrfEdwards25519PublicKey);
152
153    impl crate::Verifier<Sha512> for EdVrfEdwards25519Ell2PublicKey {
154        type Proof = EdVrfProof;
155
156        fn from_slice(bytes: &[u8]) -> VrfResult<Self>
157        where
158            Self: Sized,
159        {
160            Ok(Self(EdVrfEdwards25519PublicKey::from_bytes(bytes)?))
161        }
162
163        fn verify(&self, alpha: &[u8], proof: Self::Proof) -> VrfResult<digest::Output<Sha512>> {
164            use crate::Proof as _;
165            self.0
166                .verify(Ciphersuite::ECVRF_EDWARDS25519_SHA512_ELL2, alpha, proof)?
167                .proof_to_hash(Ciphersuite::ECVRF_EDWARDS25519_SHA512_ELL2)
168        }
169    }
170}
171
172#[derive(Debug, PartialEq, Eq, zeroize::ZeroizeOnDrop)]
173pub struct EdVrfProof {
174    gamma: EdwardsPoint,
175    c: Scalar,
176    s: Scalar,
177}
178
179impl crate::Proof<Sha512> for EdVrfProof {
180    const PROOF_LEN: usize = CHALLENGE_LEN + Q_LEN + PT_LEN;
181
182    fn decode_pi(pi: &[u8]) -> VrfResult<Self> {
183        if pi.len() != Self::PROOF_LEN {
184            return Err(VrfError::IncorrectPiLength {
185                expected: Self::PROOF_LEN,
186                actual: pi.len(),
187            });
188        }
189
190        let gamma_string = &pi[..PT_LEN];
191        let c_string = &pi[PT_LEN..PT_LEN + CHALLENGE_LEN];
192        let s_string = &pi[PT_LEN + CHALLENGE_LEN..];
193        debug_assert_eq!(s_string.len(), Q_LEN);
194        let gamma = curve25519_dalek::edwards::CompressedEdwardsY(gamma_string.try_into()?);
195        let gamma = gamma.decompress().ok_or(VrfError::InvalidEcPoint)?;
196
197        // Extend the bytes to a 32-byte array, mimicking Scalar's impl of `From<u128>`
198        let mut c_bytes = [0u8; 32];
199        c_bytes[..c_string.len()].copy_from_slice(c_string);
200
201        let c = Scalar::from_bytes_mod_order(c_bytes);
202        let s = Scalar::from_bytes_mod_order(s_string.try_into()?);
203
204        Ok(EdVrfProof { gamma, c, s })
205    }
206
207    fn encode_to_pi(&self) -> Vec<u8> {
208        let ret = [
209            self.gamma.compress().as_bytes(),
210            &self.c.as_bytes()[..CHALLENGE_LEN],
211            self.s.as_bytes(),
212        ]
213        .concat();
214
215        debug_assert_eq!(ret.len(), Self::PROOF_LEN);
216
217        ret
218    }
219
220    fn proof_to_hash(&self, suite: Ciphersuite) -> VrfResult<digest::Output<Sha512>> {
221        Ok(util::proof_to_hash::<Sha512>(
222            suite,
223            self.gamma.mul_by_cofactor().compress().as_bytes(),
224        ))
225    }
226}
227
228mod internal {
229    use curve25519_dalek::{
230        EdwardsPoint, Scalar, edwards::CompressedEdwardsY, scalar::clamp_integer,
231    };
232    use digest::Digest as _;
233    use sha2::Sha512;
234    use subtle::ConstantTimeEq;
235
236    use crate::{
237        Ciphersuite,
238        consts::ecvrf::{
239            ciphersuites::ECVRF_EDWARDS25519_SHA512_ELL2,
240            e2c::{ECVRF_E2C_H2C_DST, ECVRF_EDWARDS25519_ELL2_DST},
241        },
242        ec::{
243            edwards25519::{CHALLENGE_LEN, EdVrfProof},
244            util,
245        },
246        error::{VrfError, VrfResult},
247    };
248
249    #[derive(zeroize::ZeroizeOnDrop)]
250    pub struct EdVrfEdwards25519SecretKey {
251        pub(super) x: Scalar,
252        hash_prefix: [u8; 32],
253    }
254
255    impl std::cmp::PartialEq for EdVrfEdwards25519SecretKey {
256        fn eq(&self, other: &Self) -> bool {
257            (self.hash_prefix.ct_eq(&other.hash_prefix) & self.x.ct_eq(&other.x)).into()
258        }
259    }
260
261    impl std::cmp::Eq for EdVrfEdwards25519SecretKey {}
262
263    impl EdVrfEdwards25519SecretKey {
264        pub fn from_sk(sk: [u8; 32]) -> Self {
265            let hashed = Sha512::digest(sk);
266            let mut x_bytes = [0u8; 32];
267            x_bytes.copy_from_slice(&hashed[..32]);
268            let mut hash_prefix = [0u8; 32];
269            hash_prefix.copy_from_slice(&hashed[32..]);
270            #[allow(deprecated)]
271            let x = Scalar::from_bits(clamp_integer(x_bytes));
272            Self { x, hash_prefix }
273        }
274
275        #[cfg(feature = "hazmat")]
276        pub fn x_equals(&self, value: &[u8]) -> bool {
277            self.x.as_bytes().as_slice().ct_eq(value).into()
278        }
279
280        pub fn public_key(&self) -> EdVrfEdwards25519PublicKey {
281            let point = EdwardsPoint::mul_base(&self.x);
282            EdVrfEdwards25519PublicKey {
283                compressed: point.compress(),
284                point,
285            }
286        }
287
288        fn generate_nonce(&self, h_string: &[u8]) -> Scalar {
289            let k_string = Sha512::new()
290                .chain_update(self.hash_prefix)
291                .chain_update(h_string)
292                .finalize();
293
294            Scalar::from_bytes_mod_order_wide(&k_string.into())
295        }
296
297        pub(super) fn prove(&self, suite: Ciphersuite, alpha: &[u8]) -> VrfResult<EdVrfProof> {
298            // 1. Use SK to derive the VRF secret scalar x and the VRF public key Y = x*B
299            let y = self.public_key();
300            // 2. H = ECVRF_encode_to_curve(encode_to_curve_salt, alpha_string) (see Section 5.4.1)
301            let h = y.encode_to_curve(suite, alpha)?;
302            // 3. Gamma = x*H
303            let gamma = self.x * h.point;
304
305            // 4. k = ECVRF_nonce_generation(SK, h_string) (see Section 5.4.2)
306            let k = self.generate_nonce(h.encode_to_curve_salt());
307            // 5. c = ECVRF_challenge_generation(Y, H, Gamma, k*B, k*H) (see Section 5.4.3)
308            let c = EdChallenge::generate(
309                suite,
310                &[
311                    &y.point,
312                    &h.point,
313                    &gamma,
314                    &EdwardsPoint::mul_base(&k),
315                    &(k * h.point),
316                ],
317            );
318
319            // Note: all scalar math on dalek is done mod q so we don't need to explicit it
320            // 7. s = (k + c*x) mod q
321            let s = k + c * self.x;
322
323            // 8. pi_string = point_to_string(Gamma) || int_to_string(c, cLen) || int_to_string(s, qLen)
324            // EdProof here can be encoded into pi_string at any point so we return the struct as-is
325            Ok(EdVrfProof { gamma, c, s })
326        }
327    }
328
329    #[derive(Debug, Clone, PartialEq, Eq)]
330    pub struct EdVrfEdwards25519PublicKey {
331        compressed: CompressedEdwardsY,
332        point: EdwardsPoint,
333    }
334
335    impl EdVrfEdwards25519PublicKey {
336        pub fn from_bytes(bytes: &[u8]) -> VrfResult<Self> {
337            let compressed = CompressedEdwardsY::from_slice(bytes)?;
338            let point = compressed.decompress().ok_or(VrfError::InvalidEcPoint)?;
339            if point.is_small_order() {
340                return Err(VrfError::InvalidEcPoint);
341            }
342
343            Ok(Self { compressed, point })
344        }
345
346        pub fn encode_to_curve(&self, suite: Ciphersuite, alpha: &[u8]) -> VrfResult<Self> {
347            Ok(match suite {
348                Ciphersuite::ECVRF_EDWARDS25519_SHA512_TAI => self.encode_to_curve_tai(alpha)?,
349                Ciphersuite::ECVRF_EDWARDS25519_SHA512_ELL2 => self.h2c_encode_to_curve_ell2(alpha),
350                _ => return Err(VrfError::UnsupportedCiphersuite(suite)),
351            })
352        }
353
354        pub fn encode_to_curve_salt(&self) -> &[u8] {
355            self.compressed.as_bytes().as_slice()
356        }
357
358        fn encode_to_curve_tai(&self, alpha: &[u8]) -> VrfResult<Self> {
359            let salt = self.encode_to_curve_salt();
360
361            for candidate in util::encode_to_curve_tai_generator::<Sha512>(
362                Ciphersuite::ECVRF_EDWARDS25519_SHA512_TAI,
363                salt,
364                alpha,
365            ) {
366                // 5. While H is "INVALID" or H is the identity element of the elliptic curve group:
367                // H = interpret_hash_value_as_a_point(hash_string)
368                // Addendum: "With interpret_hash_value_as_a_point(s) = string_to_point(s[0]...s[31])."
369                let Ok(mut pk) = Self::from_bytes(&candidate[..32]) else {
370                    continue;
371                };
372
373                // 6. Output H
374                pk.point = pk.point.mul_by_cofactor();
375                pk.compressed = pk.point.compress();
376                return Ok(pk);
377            }
378
379            Err(VrfError::TryAndIncrementNoCandidatesFound)
380        }
381
382        fn h2c_encode_to_curve_ell2(&self, alpha: &[u8]) -> Self {
383            // string_to_be_hashed = encode_to_curve_salt || alpha_string
384            let string_to_be_hashed = [self.encode_to_curve_salt(), alpha].concat();
385            // "ECVRF_" || h2c_suite_ID_string || suite_string
386            let dst = [
387                &ECVRF_E2C_H2C_DST[..],
388                &ECVRF_EDWARDS25519_ELL2_DST[..],
389                &[ECVRF_EDWARDS25519_SHA512_ELL2],
390            ]
391            .concat();
392
393            let point =
394                EdwardsPoint::encode_to_curve::<sha2::Sha512>(&[&string_to_be_hashed], &[&dst]);
395
396            // let point =
397            //     EdwardsPoint::hash_to_curve::<sha2::Sha512>(&[&string_to_be_hashed], &[&dst]);
398
399            Self {
400                compressed: point.compress(),
401                point,
402            }
403        }
404
405        pub(super) fn verify(
406            &self,
407            suite: Ciphersuite,
408            alpha: &[u8],
409            proof: EdVrfProof,
410        ) -> VrfResult<EdVrfProof> {
411            // 4. D = ECVRF_decode_proof(pi_string) (see Section 5.4.4)
412            // 6. (Gamma, c, s) = D
413            let EdVrfProof { gamma, c, s } = proof;
414            // 7. H = ECVRF_encode_to_curve(encode_to_curve_salt, alpha_string) (see Section 5.4.1)
415            let h = self.encode_to_curve(suite, alpha)?;
416
417            // 8. U = s*B - c*Y
418            let u = EdwardsPoint::mul_base(&s) - c * self.point;
419            // 9. V = s*H - c*Gamma
420            let v = s * h.point - c * gamma;
421            // 10. c' = ECVRF_challenge_generation(Y, H, Gamma, U, V) (see Section 5.4.3)
422            let c_prime = EdChallenge::generate(suite, &[&self.point, &h.point, &gamma, &u, &v]);
423
424            // 11.  If c and c' are equal, output ("VALID", ECVRF_proof_to_hash(pi_string)); else output "INVALID"
425            if c_prime != c {
426                return Err(VrfError::ProofVerificationFailure);
427            }
428
429            Ok(EdVrfProof { gamma, c, s })
430        }
431    }
432
433    pub struct EdChallenge;
434
435    impl EdChallenge {
436        pub fn from_challenge_bytes(c_string: &[u8; CHALLENGE_LEN]) -> Scalar {
437            let mut c_scalar_bytes = [0u8; 32];
438            c_scalar_bytes[..CHALLENGE_LEN].copy_from_slice(c_string);
439            Scalar::from_bytes_mod_order(c_scalar_bytes)
440        }
441
442        pub fn generate(suite: Ciphersuite, points: &[&EdwardsPoint; 5]) -> Scalar {
443            let compressed = points.map(EdwardsPoint::compress);
444
445            Self::from_challenge_bytes(&util::challenge_bytes::<CHALLENGE_LEN, Sha512>(
446                suite,
447                compressed.iter().map(|c| c.as_bytes().as_slice()),
448            ))
449        }
450    }
451}