Skip to main content

tycho_crypto/ed25519/
mod.rs

1use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
2use curve25519_dalek::scalar::Scalar;
3use sha2::{Digest, Sha512};
4
5/// Ed25519 key pair
6#[derive(Copy, Clone)]
7pub struct KeyPair {
8    pub secret_key: ExpandedSecretKey,
9    pub public_key: PublicKey,
10}
11
12impl KeyPair {
13    /// Signs a serialized TL representation of data
14    #[inline(always)]
15    #[cfg(feature = "tl-proto")]
16    pub fn sign_tl<T: tl_proto::TlWrite>(&self, data: T) -> [u8; 64] {
17        self.secret_key.sign_tl(data, &self.public_key)
18    }
19
20    /// Signs raw bytes
21    #[inline(always)]
22    pub fn sign_raw(&self, data: &[u8]) -> [u8; 64] {
23        self.secret_key.sign_raw(data, &self.public_key)
24    }
25
26    /// Computes shared secret using x25519
27    #[inline(always)]
28    pub fn compute_shared_secret(&self, other_public_key: &PublicKey) -> [u8; 32] {
29        self.secret_key.compute_shared_secret(other_public_key)
30    }
31}
32
33#[cfg(feature = "rand8")]
34impl rand8::distributions::Distribution<KeyPair> for rand8::distributions::Standard {
35    #[inline]
36    fn sample<R: rand8::Rng + ?Sized>(&self, rng: &mut R) -> KeyPair {
37        let secret_key = rng.r#gen::<SecretKey>();
38
39        KeyPair {
40            secret_key: ExpandedSecretKey::from(&secret_key),
41            public_key: PublicKey::from(&secret_key),
42        }
43    }
44}
45
46#[cfg(feature = "rand9")]
47impl rand9::distr::Distribution<KeyPair> for rand9::distr::StandardUniform {
48    fn sample<R: rand9::Rng + ?Sized>(&self, rng: &mut R) -> KeyPair {
49        let secret_key = rng.random::<SecretKey>();
50
51        KeyPair {
52            secret_key: ExpandedSecretKey::from(&secret_key),
53            public_key: PublicKey::from(&secret_key),
54        }
55    }
56}
57
58impl From<ExpandedSecretKey> for KeyPair {
59    fn from(secret_key: ExpandedSecretKey) -> Self {
60        let public_key = PublicKey::from(&secret_key);
61        Self {
62            secret_key,
63            public_key,
64        }
65    }
66}
67
68impl From<&'_ SecretKey> for KeyPair {
69    fn from(secret_key: &SecretKey) -> Self {
70        let secret_key = secret_key.expand();
71        let public_key = PublicKey::from(&secret_key);
72        Self {
73            secret_key,
74            public_key,
75        }
76    }
77}
78
79/// Ed25519 public key
80#[derive(Copy, Clone)]
81pub struct PublicKey {
82    compressed: CompressedEdwardsY,
83    neg_point: EdwardsPoint,
84}
85
86impl PublicKey {
87    /// Tries to create public key from
88    #[inline(always)]
89    pub fn from_bytes(bytes: [u8; 32]) -> Option<Self> {
90        let compressed = CompressedEdwardsY(bytes);
91        let point = compressed.decompress()?;
92        Some(PublicKey {
93            compressed,
94            neg_point: -point,
95        })
96    }
97
98    #[inline(always)]
99    #[cfg(feature = "tl-proto")]
100    pub fn from_tl(tl: crate::tl::PublicKey<'_>) -> Option<Self> {
101        match tl {
102            crate::tl::PublicKey::Ed25519 { key } => Self::from_bytes(*key),
103            _ => None,
104        }
105    }
106
107    #[inline(always)]
108    #[cfg(feature = "tl-proto")]
109    pub fn as_tl(&'_ self) -> crate::tl::PublicKey<'_> {
110        crate::tl::PublicKey::Ed25519 {
111            key: self.compressed.as_bytes(),
112        }
113    }
114
115    #[inline(always)]
116    pub fn to_bytes(&self) -> [u8; 32] {
117        self.compressed.to_bytes()
118    }
119
120    #[inline(always)]
121    pub fn as_bytes(&'_ self) -> &'_ [u8; 32] {
122        self.compressed.as_bytes()
123    }
124
125    /// Verifies message signature using its TL representation
126    ///
127    /// NOTE: `[u8]` is representation differently in TL. Use [PublicKey::verify_raw] if
128    /// you need to verify raw bytes signature
129    #[cfg(feature = "tl-proto")]
130    pub fn verify_tl<T: tl_proto::TlWrite>(&self, message: T, signature: &[u8; 64]) -> bool {
131        let target_r = CompressedEdwardsY(signature[..32].try_into().unwrap());
132        let s = match check_scalar(signature[32..].try_into().unwrap()) {
133            Some(s) => s,
134            None => return false,
135        };
136
137        let mut h = Sha512::new();
138        h.update(target_r.as_bytes());
139        h.update(self.compressed.as_bytes());
140        tl_proto::HashWrapper(message).update_hasher(&mut h);
141
142        let k = Scalar::from_bytes_mod_order_wide(&h.finalize().into());
143        let r = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &self.neg_point, &s);
144
145        r.compress() == target_r
146    }
147
148    /// Verifies message signature as it is
149    pub fn verify_raw(&self, message: &[u8], signature: &[u8; 64]) -> bool {
150        let target_r = CompressedEdwardsY(signature[..32].try_into().unwrap());
151        let s = match check_scalar(signature[32..].try_into().unwrap()) {
152            Some(s) => s,
153            None => return false,
154        };
155
156        let mut h = Sha512::new();
157        h.update(target_r.as_bytes());
158        h.update(self.compressed.as_bytes());
159        h.update(message);
160
161        let k = Scalar::from_bytes_mod_order_wide(&h.finalize().into());
162        let r = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &self.neg_point, &s);
163
164        r.compress() == target_r
165    }
166
167    #[inline(always)]
168    fn from_scalar(bits: [u8; 32]) -> PublicKey {
169        let point = EdwardsPoint::mul_base_clamped(bits);
170        let compressed = point.compress();
171        Self {
172            compressed,
173            neg_point: -point,
174        }
175    }
176}
177
178impl From<&'_ SecretKey> for PublicKey {
179    fn from(secret_key: &SecretKey) -> Self {
180        let mut h = Sha512::new();
181        h.update(secret_key.0.as_slice());
182        let hash: [u8; 64] = h.finalize().into();
183        Self::from_scalar(hash[..32].try_into().unwrap())
184    }
185}
186
187impl From<&'_ ExpandedSecretKey> for PublicKey {
188    fn from(expanded_secret_key: &ExpandedSecretKey) -> Self {
189        Self::from_scalar(expanded_secret_key.key_bytes)
190    }
191}
192
193impl AsRef<[u8; 32]> for PublicKey {
194    fn as_ref(&self) -> &[u8; 32] {
195        self.as_bytes()
196    }
197}
198
199impl PartialEq for PublicKey {
200    #[inline(always)]
201    fn eq(&self, other: &Self) -> bool {
202        self.compressed.eq(&other.compressed)
203    }
204}
205
206impl Eq for PublicKey {}
207
208impl std::fmt::Display for PublicKey {
209    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
210        let mut output = [0u8; 64];
211        hex::encode_to_slice(self.compressed.as_bytes(), &mut output).ok();
212
213        // SAFETY: output is guaranteed to contain only [0-9a-f]
214        let output = unsafe { std::str::from_utf8_unchecked(&output) };
215        f.write_str(output)
216    }
217}
218
219impl std::fmt::Debug for PublicKey {
220    #[inline]
221    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
222        std::fmt::Display::fmt(self, f)
223    }
224}
225
226#[cfg(feature = "serde")]
227impl serde::Serialize for PublicKey {
228    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229    where
230        S: serde::Serializer,
231    {
232        if serializer.is_human_readable() {
233            serializer.collect_str(self)
234        } else {
235            self.as_bytes().serialize(serializer)
236        }
237    }
238}
239
240#[cfg(feature = "serde")]
241impl<'de> serde::Deserialize<'de> for PublicKey {
242    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
243    where
244        D: serde::Deserializer<'de>,
245    {
246        use serde::de::{Error, Visitor};
247
248        struct BytesVisitor;
249
250        impl Visitor<'_> for BytesVisitor {
251            type Value = [u8; 32];
252
253            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254                formatter.write_str("hex-encoded public key")
255            }
256
257            fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
258                let mut result = [0; 32];
259                match hex::decode_to_slice(value, &mut result) {
260                    Ok(()) => Ok(result),
261                    Err(_) => Err(Error::invalid_value(
262                        serde::de::Unexpected::Str(value),
263                        &self,
264                    )),
265                }
266            }
267        }
268
269        let bytes = if deserializer.is_human_readable() {
270            deserializer.deserialize_str(BytesVisitor)
271        } else {
272            <[u8; 32]>::deserialize(deserializer)
273        }?;
274
275        Self::from_bytes(bytes).ok_or_else(|| Error::custom("invalid public key"))
276    }
277}
278
279#[derive(Copy, Clone)]
280pub struct ExpandedSecretKey {
281    key: Scalar,
282    key_bytes: [u8; 32],
283    nonce: [u8; 32],
284}
285
286impl ExpandedSecretKey {
287    #[inline(always)]
288    pub fn nonce(&'_ self) -> &'_ [u8; 32] {
289        &self.nonce
290    }
291
292    #[cfg(feature = "tl-proto")]
293    pub fn sign_tl<T: tl_proto::TlWrite>(&self, message: T, public_key: &PublicKey) -> [u8; 64] {
294        #![allow(non_snake_case)]
295
296        let message = tl_proto::HashWrapper(message);
297
298        let mut h = Sha512::new();
299        h.update(self.nonce.as_slice());
300        message.update_hasher(&mut h);
301
302        let r = Scalar::from_bytes_mod_order_wide(&h.finalize().into());
303        let R = EdwardsPoint::mul_base(&r).compress();
304
305        h = Sha512::new();
306        h.update(R.as_bytes());
307        h.update(public_key.as_bytes());
308        message.update_hasher(&mut h);
309
310        let k = Scalar::from_bytes_mod_order_wide(&h.finalize().into());
311        let s = (k * self.key) + r;
312
313        let mut result = [0u8; 64];
314        result[..32].copy_from_slice(R.as_bytes().as_slice());
315        result[32..].copy_from_slice(s.as_bytes().as_slice());
316        result
317    }
318
319    pub fn sign_raw(&self, message: &[u8], public_key: &PublicKey) -> [u8; 64] {
320        #![allow(non_snake_case)]
321
322        let mut h = Sha512::new();
323        h.update(self.nonce.as_slice());
324        h.update(message);
325
326        let r = Scalar::from_bytes_mod_order_wide(&h.finalize().into());
327        let R = EdwardsPoint::mul_base(&r).compress();
328
329        h = Sha512::new();
330        h.update(R.as_bytes());
331        h.update(public_key.as_bytes());
332        h.update(message);
333
334        let k = Scalar::from_bytes_mod_order_wide(&h.finalize().into());
335        let s = (k * self.key) + r;
336
337        let mut result = [0u8; 64];
338        result[..32].copy_from_slice(R.as_bytes().as_slice());
339        result[32..].copy_from_slice(s.as_bytes().as_slice());
340        result
341    }
342
343    #[inline(always)]
344    pub fn compute_shared_secret(&self, other_public_key: &PublicKey) -> [u8; 32] {
345        let point = (-other_public_key.neg_point).to_montgomery();
346        (point * self.key).to_bytes()
347    }
348}
349
350impl From<&'_ SecretKey> for ExpandedSecretKey {
351    fn from(secret_key: &SecretKey) -> Self {
352        let mut h = Sha512::new();
353        h.update(secret_key.0.as_slice());
354        let hash: [u8; 64] = h.finalize().into();
355
356        let lower: [u8; 32] = hash[..32].try_into().unwrap();
357        let nonce: [u8; 32] = hash[32..].try_into().unwrap();
358
359        let key_bytes = curve25519_dalek::scalar::clamp_integer(lower);
360
361        Self {
362            key: Scalar::from_bytes_mod_order(key_bytes),
363            key_bytes,
364            nonce,
365        }
366    }
367}
368
369#[derive(Copy, Clone)]
370pub struct SecretKey([u8; 32]);
371
372impl SecretKey {
373    #[inline(always)]
374    pub fn from_bytes(bytes: [u8; 32]) -> Self {
375        Self(bytes)
376    }
377
378    #[inline(always)]
379    pub fn to_bytes(&self) -> [u8; 32] {
380        self.0
381    }
382
383    #[inline(always)]
384    pub fn as_bytes(&'_ self) -> &'_ [u8; 32] {
385        &self.0
386    }
387
388    #[inline(always)]
389    pub fn expand(&self) -> ExpandedSecretKey {
390        ExpandedSecretKey::from(self)
391    }
392}
393
394#[cfg(feature = "rand8")]
395impl rand8::distributions::Distribution<SecretKey> for rand8::distributions::Standard {
396    #[inline]
397    fn sample<R: rand8::Rng + ?Sized>(&self, rng: &mut R) -> SecretKey {
398        SecretKey(rng.r#gen())
399    }
400}
401
402#[cfg(feature = "rand9")]
403impl rand9::distr::Distribution<SecretKey> for rand9::distr::StandardUniform {
404    #[inline]
405    fn sample<R: rand9::Rng + ?Sized>(&self, rng: &mut R) -> SecretKey {
406        SecretKey(rng.random())
407    }
408}
409
410#[inline(always)]
411fn check_scalar(bytes: [u8; 32]) -> Option<Scalar> {
412    Scalar::from_canonical_bytes(bytes).into()
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418
419    #[test]
420    fn correct_signature() {
421        let secret = SecretKey::from_bytes([
422            99, 87, 207, 105, 199, 108, 51, 89, 172, 108, 232, 48, 240, 147, 49, 155, 145, 60, 66,
423            55, 98, 149, 119, 0, 251, 19, 132, 69, 151, 132, 184, 53,
424        ]);
425
426        let pubkey = PublicKey::from(&secret);
427        assert_eq!(
428            pubkey.to_bytes(),
429            [
430                75, 54, 96, 93, 16, 21, 8, 159, 230, 42, 68, 148, 54, 18, 251, 196, 205, 254, 252,
431                114, 76, 87, 204, 218, 132, 26, 196, 181, 191, 188, 115, 123
432            ]
433        );
434        println!("{pubkey:?}");
435
436        let data = b"hello world";
437
438        let extended = ExpandedSecretKey::from(&secret);
439        let signature = extended.sign_tl(data, &pubkey);
440        assert_eq!(
441            signature,
442            [
443                76, 51, 131, 27, 77, 188, 20, 26, 229, 121, 93, 100, 10, 166, 183, 121, 12, 48, 17,
444                239, 115, 184, 50, 162, 103, 228, 3, 136, 213, 165, 246, 113, 220, 84, 255, 136,
445                251, 141, 229, 52, 236, 249, 135, 182, 242, 198, 171, 1, 194, 148, 164, 8, 131,
446                253, 205, 112, 112, 145, 6, 225, 71, 78, 138, 1
447            ]
448        );
449
450        assert!(pubkey.verify_tl(data, &signature))
451    }
452
453    #[test]
454    fn verify_with_different_key() {
455        let first = rand9::random::<SecretKey>();
456        let first_pubkey = PublicKey::from(&first);
457
458        let second = rand9::random::<SecretKey>();
459        let second_pubkey = PublicKey::from(&second);
460
461        let data = b"hello world";
462
463        let extended = ExpandedSecretKey::from(&first);
464        let signature = extended.sign_tl(data, &first_pubkey);
465
466        assert!(!second_pubkey.verify_tl(data, &signature))
467    }
468
469    #[test]
470    fn correct_shared_secret() {
471        let first = ExpandedSecretKey::from(&SecretKey::from_bytes([
472            215, 30, 117, 171, 183, 9, 171, 48, 212, 45, 10, 198, 14, 66, 109, 80, 163, 180, 194,
473            66, 82, 184, 13, 48, 240, 102, 40, 110, 156, 5, 13, 143,
474        ]));
475        let first_pubkey = PublicKey::from(&first);
476
477        let second = ExpandedSecretKey::from(&SecretKey::from_bytes([
478            181, 115, 13, 55, 26, 150, 138, 43, 66, 28, 162, 50, 0, 133, 120, 24, 20, 142, 183, 60,
479            159, 53, 200, 97, 14, 123, 63, 249, 222, 211, 186, 99,
480        ]));
481        let second_pubkey = PublicKey::from(&second);
482
483        let first_shared_key = first.compute_shared_secret(&second_pubkey);
484        let second_shared_key = second.compute_shared_secret(&first_pubkey);
485
486        assert_eq!(
487            first_shared_key,
488            [
489                30, 243, 238, 65, 216, 53, 237, 172, 6, 120, 204, 220, 34, 163, 18, 28, 181, 245,
490                215, 233, 98, 0, 87, 11, 85, 6, 41, 130, 140, 95, 66, 72
491            ]
492        );
493        assert_eq!(first_shared_key, second_shared_key);
494    }
495
496    #[test]
497    fn same_shared_secret() {
498        let first = ExpandedSecretKey::from(&rand9::random::<SecretKey>());
499        let first_pubkey = PublicKey::from(&first);
500
501        let second = ExpandedSecretKey::from(&rand9::random::<SecretKey>());
502        let second_pubkey = PublicKey::from(&second);
503
504        let first_shared_key = first.compute_shared_secret(&second_pubkey);
505        let second_shared_key = second.compute_shared_secret(&first_pubkey);
506
507        assert_eq!(first_shared_key, second_shared_key);
508    }
509
510    #[test]
511    fn shared_secret_on_self() {
512        let secret = rand9::random::<SecretKey>();
513        let pubkey = PublicKey::from(&secret);
514
515        let shared = ExpandedSecretKey::from(&secret).compute_shared_secret(&pubkey);
516        assert_ne!(secret.as_bytes(), &shared);
517    }
518}