1use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
2use curve25519_dalek::scalar::Scalar;
3use sha2::{Digest, Sha512};
4
5#[derive(Copy, Clone)]
7pub struct KeyPair {
8 pub secret_key: ExpandedSecretKey,
9 pub public_key: PublicKey,
10}
11
12impl KeyPair {
13 #[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 #[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 #[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#[derive(Copy, Clone)]
81pub struct PublicKey {
82 compressed: CompressedEdwardsY,
83 neg_point: EdwardsPoint,
84}
85
86impl PublicKey {
87 #[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 #[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 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 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}