ed25519_dalek_hpke/
lib.rs

1//! HPKE encryption/decryption for Ed25519 keys.
2//!
3//! This library provides HPKE (Hybrid Public Key Encryption) support for Ed25519 keys
4//! by converting them to X25519 keys internally. It implements simple encryption and
5//! decryption traits on the standard `ed25519-dalek` key types.
6//!
7//! # Examples
8//!
9//! ```
10//! use ed25519_dalek::SigningKey;
11//! use ed25519_dalek_hpke::{Ed25519hpkeEncryption, Ed25519hpkeDecryption};
12//!
13//! let signing_key = SigningKey::generate(&mut rand::rng());
14//! let verifying_key = signing_key.verifying_key();
15//!
16//! let plaintext = b"secret message";
17//! let ciphertext = verifying_key.encrypt(plaintext).unwrap();
18//! let decrypted = signing_key.decrypt(&ciphertext).unwrap();
19//!
20//! assert_eq!(plaintext, decrypted.as_slice());
21//! ```
22
23use std::error::Error as StdError;
24use std::fmt;
25
26use hpke::aead::ChaCha20Poly1305;
27use hpke::kdf::HkdfSha256;
28use hpke::kem::X25519HkdfSha256;
29use hpke::Deserializable;
30use hpke::Kem;
31use hpke::OpModeS;
32use hpke::Serializable;
33use x25519_dalek::PublicKey as X25519PublicKey;
34use x25519_dalek::StaticSecret as X25519SecretKey;
35
36/// Errors that can occur during HPKE operations.
37#[derive(Debug)]
38pub enum Error {
39    /// Key format is invalid or conversion failed.
40    InvalidKey,
41    /// HPKE encryption failed.
42    EncryptionFailed,
43    /// HPKE decryption failed or ciphertext is malformed.
44    DecryptionFailed,
45}
46
47impl fmt::Display for Error {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Error::InvalidKey => write!(f, "Invalid key format"),
51            Error::EncryptionFailed => write!(f, "Encryption failed"),
52            Error::DecryptionFailed => write!(f, "Decryption failed"),
53        }
54    }
55}
56
57impl StdError for Error {
58    fn source(&self) -> Option<&(dyn StdError + 'static)> {
59        match self {
60            _ => None,
61        }
62    }
63}
64
65/// Trait for encrypting data with an Ed25519 verifying (public) key.
66///
67/// Uses HPKE with X25519, HKDF-SHA256, and ChaCha20Poly1305 AEAD.
68pub trait Ed25519hpkeEncryption {
69    /// Encrypts data using HPKE.
70    ///
71    /// # Returns
72    ///
73    /// Format: `[8-byte length][encapsulated key][ciphertext]`
74    fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, Error>;
75}
76
77/// Trait for decrypting data with an Ed25519 signing (private) key.
78///
79/// Uses HPKE with X25519, HKDF-SHA256, and ChaCha20Poly1305 AEAD.
80pub trait Ed25519hpkeDecryption {
81    /// Decrypts HPKE ciphertext in the format produced by `encrypt`.
82    fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, Error>;
83}
84
85impl Ed25519hpkeEncryption for ed25519_dalek::VerifyingKey {
86    fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
87        let mut csprng = rand::rng();
88
89        let recipient_x25519_public: X25519PublicKey = public_key_from_ed25519_to_x25519(self)?;
90        let recipient_pk_bytes = recipient_x25519_public.to_bytes();
91        let hpke_recipient_pk =
92            <X25519HkdfSha256 as hpke::Kem>::PublicKey::from_bytes(&recipient_pk_bytes)
93                .map_err(|_| Error::InvalidKey)?;
94        let (encap_key, mut sender_context) =
95            hpke::setup_sender::<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256, _>(
96                &OpModeS::Base,
97                &hpke_recipient_pk,
98                b"arbitrary_info_bytes",
99                &mut csprng,
100            )
101            .map_err(|_| Error::EncryptionFailed)?;
102
103        let enc_result = sender_context
104            .seal(data, b"arbitrary_seal_bytes")
105            .map_err(|_| Error::EncryptionFailed)?;
106
107        let mut buf = Vec::with_capacity(enc_result.len() + 8);
108        let encap_key_bytes = encap_key.to_bytes().to_vec();
109        let key_len = encap_key_bytes.len() as u64;
110        buf.extend_from_slice(&key_len.to_le_bytes());
111        buf.extend(encap_key_bytes);
112        buf.extend(enc_result);
113
114        Ok(buf)
115    }
116}
117
118impl Ed25519hpkeDecryption for ed25519_dalek::SigningKey {
119    fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
120        let static_secret_key = secret_key_from_ed25519_to_x25519(self)?;
121        let hpke_recipient_sk =
122            <X25519HkdfSha256 as Kem>::PrivateKey::from_bytes(static_secret_key.as_bytes())
123                .map_err(|_| Error::InvalidKey)?;
124
125        if data.len() < 8 {
126            return Err(Error::DecryptionFailed);
127        }
128        let (key_len, data) = data.split_at(8);
129        let key_len = u64::from_le_bytes(key_len.try_into().unwrap()) as usize;
130        if key_len > 128 || data.len() < key_len {
131            return Err(Error::DecryptionFailed);
132        }
133        let (encapped_key, data) = data.split_at(key_len);
134        if data.len() == 0 {
135            return Err(Error::DecryptionFailed);
136        }
137        let enc_key = <X25519HkdfSha256 as hpke::Kem>::EncappedKey::from_bytes(encapped_key)
138            .map_err(|_| Error::InvalidKey)?;
139        let mut receiver_context = hpke::setup_receiver::<ChaCha20Poly1305, HkdfSha256, X25519HkdfSha256>(
140            &hpke::OpModeR::Base,
141            &hpke_recipient_sk,
142            &enc_key,
143            b"arbitrary_info_bytes",
144        )
145        .map_err(|_| Error::InvalidKey)?;
146
147        receiver_context
148            .open(data, b"arbitrary_seal_bytes")
149            .map_err(|_| Error::DecryptionFailed)
150    }
151}
152
153/// Converts an Ed25519 public key to X25519 (Curve25519 Montgomery form).
154pub fn public_key_from_ed25519_to_x25519(
155    ed_verifying_key: &ed25519_dalek::VerifyingKey,
156) -> Result<X25519PublicKey, Error> {
157    ed_verifying_key
158        .to_montgomery()
159        .to_bytes()
160        .try_into()
161        .map_err(|_| Error::InvalidKey)
162}
163
164/// Converts an Ed25519 private key to X25519 scalar.
165pub fn secret_key_from_ed25519_to_x25519(
166    ed_signing_key: &ed25519_dalek::SigningKey,
167) -> Result<X25519SecretKey, Error> {
168    ed_signing_key
169        .to_scalar_bytes()
170        .try_into()
171        .map_err(|_| Error::InvalidKey)
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use ed25519_dalek::SigningKey;
178
179    #[test]
180    fn test_key_conversion_and_derivation() {
181        // Generate Ed25519 keypair
182        let signing_key = SigningKey::generate(&mut rand::rng());
183        let verifying_key = signing_key.verifying_key();
184
185        // Convert Ed25519 public key to X25519
186        let x25519_pub_from_ed = public_key_from_ed25519_to_x25519(&verifying_key)
187            .expect("Failed to convert Ed25519 public key to X25519");
188
189        // Convert Ed25519 secret key to X25519
190        let x25519_secret = secret_key_from_ed25519_to_x25519(&signing_key)
191            .expect("Failed to convert Ed25519 secret key to X25519");
192
193        // Derive X25519 public key from secret key
194        let x25519_pub_from_secret = X25519PublicKey::from(&x25519_secret);
195
196        // Both derived public keys should match
197        assert_eq!(
198            x25519_pub_from_ed.as_bytes(),
199            x25519_pub_from_secret.as_bytes(),
200            "Public keys derived through different paths should match"
201        );
202    }
203
204    #[test]
205    fn test_encrypt_decrypt_roundtrip() {
206        // Generate Ed25519 keypair
207        let signing_key = SigningKey::generate(&mut rand::rng());
208        let verifying_key = signing_key.verifying_key();
209
210        // Test data
211        let plaintext = b"Hello, HPKE!";
212
213        // Encrypt using Ed25519 public key
214        let ciphertext = verifying_key.encrypt(plaintext);
215        println!("chiphertext: {:?}", ciphertext);
216
217        // Decrypt using Ed25519 private key
218        let decrypted = signing_key
219            .decrypt(&ciphertext.unwrap())
220            .expect("Decryption failed");
221
222        // Check if decrypted matches original
223        assert_eq!(
224            plaintext,
225            decrypted.as_slice(),
226            "Decrypted data should match original plaintext"
227        );
228    }
229
230    #[test]
231    fn test_encryption_different_keys() {
232        // Generate two different keypairs
233        let signing_key1 = SigningKey::generate(&mut rand::rng());
234        let signing_key2 = SigningKey::generate(&mut rand::rng());
235        let verifying_key1 = signing_key1.verifying_key();
236
237        let plaintext = b"Secret message";
238
239        // Encrypt with first public key
240        let ciphertext = verifying_key1
241            .encrypt(plaintext)
242            .expect("Encryption failed");
243
244        // Try to decrypt with second private key (should fail)
245        let result = signing_key2.decrypt(&ciphertext);
246        assert!(result.is_err(), "Decryption should fail with wrong key");
247    }
248
249    #[test]
250    fn test_invalid_ciphertext() {
251        let signing_key = SigningKey::generate(&mut rand::rng());
252
253        // Try to decrypt invalid data
254        let invalid_data = vec![1, 2, 3, 4, 5];
255        let result = signing_key.decrypt(&invalid_data);
256        println!("Decrypt: {result:?}");
257        assert!(result.is_err(), "Decryption should fail with invalid data");
258    }
259}