ed25519_dalek_hpke/
lib.rs1use 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#[derive(Debug)]
38pub enum Error {
39 InvalidKey,
41 EncryptionFailed,
43 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
65pub trait Ed25519hpkeEncryption {
69 fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, Error>;
75}
76
77pub trait Ed25519hpkeDecryption {
81 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
153pub 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
164pub 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 let signing_key = SigningKey::generate(&mut rand::rng());
183 let verifying_key = signing_key.verifying_key();
184
185 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 let x25519_secret = secret_key_from_ed25519_to_x25519(&signing_key)
191 .expect("Failed to convert Ed25519 secret key to X25519");
192
193 let x25519_pub_from_secret = X25519PublicKey::from(&x25519_secret);
195
196 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 let signing_key = SigningKey::generate(&mut rand::rng());
208 let verifying_key = signing_key.verifying_key();
209
210 let plaintext = b"Hello, HPKE!";
212
213 let ciphertext = verifying_key.encrypt(plaintext);
215 println!("chiphertext: {:?}", ciphertext);
216
217 let decrypted = signing_key
219 .decrypt(&ciphertext.unwrap())
220 .expect("Decryption failed");
221
222 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 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 let ciphertext = verifying_key1
241 .encrypt(plaintext)
242 .expect("Encryption failed");
243
244 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 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}