1use std::fmt::Display;
16
17use base64::decoded_len_estimate;
18use base64ct::Encoding;
19use curve25519_dalek::EdwardsPoint;
20use ed25519_dalek::{
21 PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, Signature, Signer, SigningKey, VerifyingKey,
22};
23use rand::rng;
24use serde::{Deserialize, Deserializer, Serialize, Serializer};
25use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes};
26use sha2::Sha512;
27use thiserror::Error;
28use zeroize::Zeroize;
29
30use crate::utilities::{base64_decode, base64_encode};
31
32#[derive(Debug, Error)]
34pub enum SignatureError {
35 #[error("The signature couldn't be decoded: {0}")]
37 Base64(#[from] base64::DecodeError),
38 #[error("The signature was invalid: {0}")]
40 Signature(#[from] ed25519_dalek::SignatureError),
41}
42
43#[derive(Deserialize, Serialize)]
45#[serde(try_from = "Ed25519KeypairPickle")]
46#[serde(into = "Ed25519KeypairPickle")]
47pub struct Ed25519Keypair {
48 secret_key: SecretKeys,
49 public_key: Ed25519PublicKey,
50}
51
52struct ExpandedSecretKey {
53 source: Box<[u8; 64]>,
54 inner: Box<ed25519_dalek::hazmat::ExpandedSecretKey>,
55}
56
57impl ExpandedSecretKey {
58 fn from_bytes(bytes: &[u8; 64]) -> Self {
59 let mut source = Box::new([0u8; 64]);
60 source.copy_from_slice(bytes);
61
62 Self { source, inner: ed25519_dalek::hazmat::ExpandedSecretKey::from_bytes(bytes).into() }
63 }
64
65 const fn as_bytes(&self) -> &[u8; 64] {
66 &self.source
67 }
68
69 fn sign(&self, message: &[u8]) -> Signature {
70 ed25519_dalek::hazmat::raw_sign::<Sha512>(&self.inner, message, &self.public_key().0)
71 }
72
73 fn public_key(&self) -> Ed25519PublicKey {
74 let point = EdwardsPoint::mul_base(&self.inner.scalar);
75 Ed25519PublicKey(VerifyingKey::from(point))
76 }
77}
78
79impl Clone for ExpandedSecretKey {
80 fn clone(&self) -> Self {
81 let source = self.source.clone();
82 Self {
83 source,
84 inner: ed25519_dalek::hazmat::ExpandedSecretKey::from_bytes(&self.source).into(),
85 }
86 }
87}
88
89impl Serialize for ExpandedSecretKey {
90 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
91 where
92 S: Serializer,
93 {
94 let bytes = self.as_bytes();
95 SerdeBytes::new(bytes).serialize(serializer)
96 }
97}
98
99impl<'d> Deserialize<'d> for ExpandedSecretKey {
100 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
101 where
102 D: Deserializer<'d>,
103 {
104 let mut bytes = <SerdeByteBuf>::deserialize(deserializer)?;
105 let length = bytes.len();
106
107 if bytes.len() != 64 {
108 bytes.zeroize();
109
110 Err(serde::de::Error::custom(format!(
111 "Invalid secret key length: expected 64 bytes, got {length}"
112 )))
113 } else {
114 let mut slice = [0u8; 64];
115 slice.copy_from_slice(&bytes);
116
117 let ret = ExpandedSecretKey::from_bytes(&slice);
118
119 slice.zeroize();
120 bytes.zeroize();
121
122 Ok(ret)
123 }
124 }
125}
126
127impl Ed25519Keypair {
128 pub fn new() -> Self {
130 let mut rng = rng();
131 let signing_key = SigningKey::generate(&mut rng);
132
133 Self {
134 public_key: Ed25519PublicKey(signing_key.verifying_key()),
135 secret_key: signing_key.into(),
136 }
137 }
138
139 pub(crate) fn from_unexpanded_key(secret_key: &[u8; 32]) -> Result<Self, crate::KeyError> {
140 let secret_key = SigningKey::from_bytes(secret_key);
141 let public_key = secret_key.verifying_key();
142 Ok(Self { secret_key: secret_key.into(), public_key: Ed25519PublicKey(public_key) })
143 }
144
145 pub(crate) fn unexpanded_secret_key(&self) -> Option<Box<[u8; 32]>> {
146 match &self.secret_key {
147 SecretKeys::Normal(k) => Some(Box::new(k.to_bytes())),
148 SecretKeys::Expanded(_) => None,
149 }
150 }
151
152 #[cfg(feature = "libolm-compat")]
153 pub(crate) fn from_expanded_key(secret_key: &[u8; 64]) -> Result<Self, crate::KeyError> {
154 let secret_key = ExpandedSecretKey::from_bytes(secret_key);
155 let public_key = secret_key.public_key();
156
157 Ok(Self { secret_key: secret_key.into(), public_key })
158 }
159
160 #[cfg(feature = "libolm-compat")]
161 pub(crate) fn expanded_secret_key(&self) -> Box<[u8; 64]> {
162 use std::ops::DerefMut;
163
164 use sha2::Digest;
165
166 let mut expanded = Box::new([0u8; 64]);
167
168 match &self.secret_key {
169 SecretKeys::Normal(k) => {
170 let mut k = k.to_bytes();
171 Sha512::new().chain_update(k).finalize_into(expanded.deref_mut().into());
172
173 k.zeroize();
174 }
175 SecretKeys::Expanded(k) => expanded.copy_from_slice(k.as_bytes()),
176 }
177
178 expanded
179 }
180
181 pub const fn public_key(&self) -> Ed25519PublicKey {
183 self.public_key
184 }
185
186 pub fn sign(&self, message: &[u8]) -> Ed25519Signature {
188 self.secret_key.sign(message)
189 }
190}
191
192impl Default for Ed25519Keypair {
193 fn default() -> Self {
194 Self::new()
195 }
196}
197
198#[derive(Deserialize, Serialize)]
200#[serde(transparent)]
201pub struct Ed25519SecretKey(Box<SigningKey>);
202
203impl Ed25519SecretKey {
204 pub const LENGTH: usize = ed25519_dalek::SECRET_KEY_LENGTH;
206
207 const BASE64_LENGTH: usize = 43;
208 const PADDED_BASE64_LENGTH: usize = 44;
209
210 pub fn new() -> Self {
212 let mut rng = rng();
213 let signing_key = SigningKey::generate(&mut rng);
214 let key = Box::new(signing_key);
215
216 Self(key)
217 }
218
219 pub fn to_bytes(&self) -> Box<[u8; 32]> {
224 Box::new(self.0.to_bytes())
225 }
226
227 pub fn from_slice(bytes: &[u8; 32]) -> Self {
229 Self(Box::new(SigningKey::from_bytes(bytes)))
230 }
231
232 pub fn to_base64(&self) -> String {
240 let mut bytes = self.to_bytes();
241 let ret = base64ct::Base64Unpadded::encode_string(bytes.as_ref());
242
243 bytes.zeroize();
244
245 ret
246 }
247
248 pub fn from_base64(input: &str) -> Result<Self, crate::KeyError> {
250 if input.len() != Self::BASE64_LENGTH && input.len() != Self::PADDED_BASE64_LENGTH {
251 Err(crate::KeyError::InvalidKeyLength {
252 key_type: "Ed25519",
253 expected_length: ed25519_dalek::SECRET_KEY_LENGTH,
254 length: decoded_len_estimate(input.len()),
255 })
256 } else {
257 let mut bytes = if let Ok(bytes) = base64ct::Base64Unpadded::decode_vec(input) {
261 bytes
262 } else {
263 base64ct::Base64::decode_vec(input)?
264 };
265
266 let mut key_bytes = [0u8; 32];
267 key_bytes.copy_from_slice(&bytes);
268 let key = Self::from_slice(&key_bytes);
269
270 bytes.zeroize();
271 key_bytes.zeroize();
272
273 Ok(key)
274 }
275 }
276
277 pub fn public_key(&self) -> Ed25519PublicKey {
279 Ed25519PublicKey(self.0.verifying_key())
280 }
281
282 pub fn sign(&self, message: &[u8]) -> Ed25519Signature {
301 Ed25519Signature(self.0.sign(message))
302 }
303}
304
305impl Default for Ed25519SecretKey {
306 fn default() -> Self {
307 Self::new()
308 }
309}
310
311#[derive(Serialize, Deserialize)]
312enum SecretKeys {
313 Normal(Box<SigningKey>),
314 Expanded(Box<ExpandedSecretKey>),
315}
316
317impl SecretKeys {
318 fn public_key(&self) -> Ed25519PublicKey {
319 match &self {
320 SecretKeys::Normal(k) => Ed25519PublicKey(k.verifying_key()),
321 SecretKeys::Expanded(k) => k.public_key(),
322 }
323 }
324
325 fn sign(&self, message: &[u8]) -> Ed25519Signature {
326 let signature = match &self {
327 SecretKeys::Normal(k) => k.sign(message),
328 SecretKeys::Expanded(k) => k.sign(message),
329 };
330
331 Ed25519Signature(signature)
332 }
333}
334
335#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
337#[serde(transparent)]
338pub struct Ed25519PublicKey(VerifyingKey);
339
340impl Ed25519PublicKey {
341 pub const LENGTH: usize = PUBLIC_KEY_LENGTH;
343
344 const BASE64_LENGTH: usize = 43;
345 const PADDED_BASE64_LENGTH: usize = 44;
346
347 pub fn from_slice(bytes: &[u8; 32]) -> Result<Self, crate::KeyError> {
349 Ok(Self(VerifyingKey::from_bytes(bytes).map_err(SignatureError::from)?))
350 }
351
352 pub fn as_bytes(&self) -> &[u8; Self::LENGTH] {
354 self.0.as_bytes()
355 }
356
357 pub fn from_base64(input: &str) -> Result<Self, crate::KeyError> {
360 if input.len() != Self::BASE64_LENGTH && input.len() != Self::PADDED_BASE64_LENGTH {
361 Err(crate::KeyError::InvalidKeyLength {
362 key_type: "Ed25519",
363 expected_length: Self::LENGTH,
364 length: decoded_len_estimate(input.len()),
365 })
366 } else {
367 let mut bytes = base64_decode(input)?;
368 let mut key_bytes = [0u8; 32];
369
370 key_bytes.copy_from_slice(&bytes);
371 let key = Self::from_slice(&key_bytes);
372
373 bytes.zeroize();
374 key_bytes.zeroize();
375
376 key
377 }
378 }
379
380 pub fn to_base64(&self) -> String {
383 base64_encode(self.as_bytes())
384 }
385
386 #[cfg(not(fuzzing))]
389 pub fn verify(
390 &self,
391 message: &[u8],
392 signature: &Ed25519Signature,
393 ) -> Result<(), SignatureError> {
394 Ok(self.0.verify_strict(message, &signature.0)?)
395 }
396
397 #[cfg(fuzzing)]
398 #[allow(missing_docs)]
399 pub fn verify(
400 &self,
401 _message: &[u8],
402 _signature: &Ed25519Signature,
403 ) -> Result<(), SignatureError> {
404 Ok(())
405 }
406}
407
408impl Display for Ed25519PublicKey {
409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410 write!(f, "{}", self.to_base64())
411 }
412}
413
414impl std::fmt::Debug for Ed25519PublicKey {
415 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
416 let s = format!("ed25519:{self}");
417 <str as std::fmt::Debug>::fmt(&s, f)
418 }
419}
420
421#[derive(Clone, Copy, PartialEq, Eq)]
424pub struct Ed25519Signature(pub(crate) Signature);
425
426impl Ed25519Signature {
427 pub const LENGTH: usize = SIGNATURE_LENGTH;
429
430 pub fn from_slice(bytes: &[u8]) -> Result<Self, SignatureError> {
432 Ok(Self(Signature::try_from(bytes)?))
433 }
434
435 pub fn from_base64(signature: &str) -> Result<Self, SignatureError> {
438 Ok(Self(Signature::try_from(base64_decode(signature)?.as_slice())?))
439 }
440
441 pub fn to_base64(&self) -> String {
443 base64_encode(self.0.to_bytes())
444 }
445
446 pub fn to_bytes(&self) -> [u8; Self::LENGTH] {
448 self.0.to_bytes()
449 }
450}
451
452impl Display for Ed25519Signature {
453 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454 write!(f, "{}", self.to_base64())
455 }
456}
457
458impl std::fmt::Debug for Ed25519Signature {
459 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460 let s = format!("ed25519:{self}");
461 <str as std::fmt::Debug>::fmt(&s, f)
462 }
463}
464
465impl Clone for Ed25519Keypair {
466 fn clone(&self) -> Self {
467 let secret_key: SecretKeys = match &self.secret_key {
468 SecretKeys::Normal(k) => SecretKeys::Normal(k.clone()),
469 SecretKeys::Expanded(k) => SecretKeys::Expanded(k.clone()),
470 };
471
472 Self { secret_key, public_key: self.public_key }
473 }
474}
475
476impl From<Ed25519Keypair> for Ed25519KeypairPickle {
477 fn from(key: Ed25519Keypair) -> Self {
478 Self(key.secret_key)
479 }
480}
481
482impl From<SigningKey> for SecretKeys {
483 fn from(key: SigningKey) -> Self {
484 Self::Normal(Box::new(key))
485 }
486}
487
488impl From<ExpandedSecretKey> for SecretKeys {
489 fn from(key: ExpandedSecretKey) -> Self {
490 Self::Expanded(Box::new(key))
491 }
492}
493
494#[derive(Serialize, Deserialize)]
495#[serde(transparent)]
496pub struct Ed25519KeypairPickle(SecretKeys);
497
498impl From<Ed25519KeypairPickle> for Ed25519Keypair {
499 fn from(pickle: Ed25519KeypairPickle) -> Self {
500 let secret_key = pickle.0;
501 let public_key = secret_key.public_key();
502
503 Self { secret_key, public_key }
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use assert_matches2::assert_matches;
510
511 use super::ExpandedSecretKey;
512 use crate::{Ed25519Keypair, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature, KeyError};
513
514 #[test]
515 fn byte_decoding_roundtrip_succeeds_for_secret_key() {
516 let bytes = *b"oooooooooooooooooooooooooooooooo";
517 let key = Ed25519SecretKey::from_slice(&bytes);
518 assert_eq!(*(key.to_bytes()), bytes);
519 }
520
521 #[test]
522 fn base64_decoding_incorrect_num_of_bytes_fails_for_secret_key() {
523 assert!(matches!(
524 Ed25519SecretKey::from_base64("foo"),
525 Err(KeyError::InvalidKeyLength { .. })
526 ));
527 }
528
529 #[test]
530 fn unpadded_base64_decoding_roundtrip_succeeds_for_secret_key() {
531 let base64 = "MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE";
532 let key = Ed25519SecretKey::from_base64(base64).expect("Should decode key from base64");
533 assert_eq!(key.to_base64(), base64);
534 }
535
536 #[test]
537 fn padded_base64_decoding_roundtrip_succeeds_for_secret_key() {
538 let base64 = "MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE=";
539 let key = Ed25519SecretKey::from_base64(base64).expect("Should decode key from base64");
540 assert_eq!(key.to_base64(), base64.trim_end_matches('='));
541 }
542
543 #[test]
544 fn byte_decoding_roundtrip_succeeds_for_public_key() {
545 let bytes = *b"oooooooooooooooooooooooooooooooo";
546 let key = Ed25519PublicKey::from_slice(&bytes).expect("Should decode key from bytes");
547 assert_eq!(key.as_bytes(), &bytes);
548 }
549
550 #[test]
551 fn base64_decoding_incorrect_num_of_bytes_fails_for_public_key() {
552 assert_matches!(
553 Ed25519PublicKey::from_base64("foo"),
554 Err(KeyError::InvalidKeyLength { .. })
555 );
556 }
557
558 #[test]
559 fn unpadded_base64_decoding_roundtrip_succeeds_for_public_key() {
560 let base64 = "b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb28";
561 let key = Ed25519PublicKey::from_base64(base64).expect("Should decode key from base64");
562 assert_eq!(key.to_base64(), base64);
563 }
564
565 #[test]
566 fn padded_base64_decoding_roundtrip_succeeds_for_public_key() {
567 let base64 = "b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb28=";
568 let key = Ed25519PublicKey::from_base64(base64).expect("Should decode key from base64");
569 assert_eq!(key.to_base64(), base64.trim_end_matches('='));
570 }
571
572 #[test]
573 fn verifying_valid_signature_succeeds() {
574 let key_pair = Ed25519Keypair::new();
575 let signature = key_pair.secret_key.sign(b"foo");
576 key_pair.public_key().verify(b"foo", &signature).expect("Should verify valid signature");
577 }
578
579 #[test]
580 fn verifying_invalid_signature_fails() {
581 let key_pair = Ed25519Keypair::new();
582 let signature = key_pair.secret_key.sign(b"foo");
583 key_pair
584 .public_key()
585 .verify(b"bar", &signature)
586 .expect_err("Should reject invalid signature");
587 }
588
589 #[test]
590 #[cfg(feature = "libolm-compat")]
591 fn can_only_expand_secret_key_once() {
592 let key_pair = Ed25519Keypair::new();
593 assert!(matches!(key_pair.secret_key, crate::types::ed25519::SecretKeys::Normal(_)));
594
595 let expanded_key = key_pair.expanded_secret_key();
596 let expanded_key_pair = Ed25519Keypair::from_expanded_key(&expanded_key).unwrap();
597 assert!(matches!(
598 expanded_key_pair.secret_key,
599 crate::types::ed25519::SecretKeys::Expanded(_)
600 ));
601 assert_eq!(expanded_key_pair.public_key(), key_pair.public_key());
602
603 let reexpanded_key = expanded_key_pair.expanded_secret_key();
604 assert_eq!(reexpanded_key, expanded_key);
605 }
606
607 #[test]
608 fn serialization_roundtrip_succeeds() {
609 let bytes = b"9999999999999999999999999999999999999999999999999999999999999999";
610 let key = ExpandedSecretKey::from_bytes(bytes);
611 let serialized = serde_json::to_value(key).expect("Should serialize key");
612 let deserialized = serde_json::from_value::<ExpandedSecretKey>(serialized)
613 .expect("Should deserialize key");
614 assert_eq!(deserialized.as_bytes(), bytes);
615 }
616
617 #[test]
618 fn deserializing_from_invalid_length_fails() {
619 let serialized = serde_json::to_value(b"foo").expect("Should serialize key");
620 let deserialized = serde_json::from_value::<ExpandedSecretKey>(serialized);
621 assert!(deserialized.is_err());
622 }
623
624 #[test]
625 fn unexpanded_key_roundtrip_succeeds() {
626 let key_pair = Ed25519Keypair::new();
627
628 let unexpanded_key = key_pair.unexpanded_secret_key().expect("Should have unexpanded key");
629 let recovered_key_pair = Ed25519Keypair::from_unexpanded_key(unexpanded_key.as_ref())
630 .expect("Should create new keypair");
631
632 assert_eq!(key_pair.public_key().to_base64(), recovered_key_pair.public_key().to_base64());
633 }
634
635 #[test]
636 fn snapshot_public_key_debug() {
637 let public_key = Ed25519PublicKey::from_slice(&[0; 32])
638 .expect("We should be able to create a public key from zero bytes");
639
640 insta::assert_debug_snapshot!(public_key)
641 }
642
643 #[test]
644 fn snapshot_signature_debug() {
645 let signature = Ed25519Signature::from_slice(&[0; 64])
646 .expect("We should be able to create a public key from zero bytes");
647
648 insta::assert_debug_snapshot!(signature)
649 }
650}