chie_crypto/
keyexchange.rs1use thiserror::Error;
30use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
31use zeroize::{Zeroize, ZeroizeOnDrop};
32
33#[derive(Clone, Zeroize, ZeroizeOnDrop)]
35pub struct SharedSecret([u8; 32]);
36
37pub struct KeyExchangeKeypair {
39 secret: StaticSecret,
40 public: X25519PublicKey,
41}
42
43pub trait KeyExchange {
45 fn exchange(&self, their_public: &X25519PublicKey) -> SharedSecret;
47}
48
49#[derive(Debug, Error)]
51pub enum KeyExchangeError {
52 #[error("Invalid public key")]
54 InvalidPublicKey,
55
56 #[error("Invalid secret key")]
58 InvalidSecretKey,
59
60 #[error("Shared secret derivation failed")]
62 DerivationFailed,
63}
64
65pub type KeyExchangeResult<T> = Result<T, KeyExchangeError>;
66
67impl SharedSecret {
68 pub fn from_bytes(bytes: [u8; 32]) -> Self {
70 Self(bytes)
71 }
72
73 pub fn as_bytes(&self) -> &[u8; 32] {
75 &self.0
76 }
77
78 pub fn to_bytes(&self) -> [u8; 32] {
80 self.0
81 }
82
83 pub fn derive_key(&self, info: &[u8]) -> [u8; 32] {
91 use crate::kdf::hkdf_extract_expand;
92 let salt = b"chie-p2p-v1";
93 hkdf_extract_expand(&self.0, salt, info)
94 }
95
96 pub fn derive_keys(&self, infos: &[&[u8]]) -> Vec<[u8; 32]> {
104 infos.iter().map(|info| self.derive_key(info)).collect()
105 }
106}
107
108impl KeyExchangeKeypair {
109 pub fn generate() -> Self {
118 let mut rng = rand::thread_rng();
119 let secret = StaticSecret::random_from_rng(&mut rng);
120 let public = X25519PublicKey::from(&secret);
121
122 Self { secret, public }
123 }
124
125 pub fn from_bytes(secret_bytes: [u8; 32]) -> Self {
138 let secret = StaticSecret::from(secret_bytes);
139 let public = X25519PublicKey::from(&secret);
140
141 Self { secret, public }
142 }
143
144 pub fn public_key(&self) -> &X25519PublicKey {
149 &self.public
150 }
151
152 pub fn public_key_bytes(&self) -> [u8; 32] {
157 *self.public.as_bytes()
158 }
159}
160
161impl KeyExchange for KeyExchangeKeypair {
162 fn exchange(&self, their_public: &X25519PublicKey) -> SharedSecret {
180 let shared = self.secret.diffie_hellman(their_public);
181 SharedSecret(*shared.as_bytes())
182 }
183}
184
185pub fn ephemeral_keypair() -> KeyExchangeKeypair {
197 KeyExchangeKeypair::generate()
198}
199
200pub fn exchange_and_derive(
223 our_secret: &KeyExchangeKeypair,
224 their_public: &X25519PublicKey,
225 context: &[u8],
226) -> [u8; 32] {
227 let shared = our_secret.exchange(their_public);
228 shared.derive_key(context)
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_key_exchange_roundtrip() {
237 let alice = KeyExchangeKeypair::generate();
238 let bob = KeyExchangeKeypair::generate();
239
240 let alice_shared = alice.exchange(bob.public_key());
241 let bob_shared = bob.exchange(alice.public_key());
242
243 assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
244 }
245
246 #[test]
247 fn test_different_pairs_different_secrets() {
248 let alice1 = KeyExchangeKeypair::generate();
249 let alice2 = KeyExchangeKeypair::generate();
250 let bob = KeyExchangeKeypair::generate();
251
252 let shared1 = alice1.exchange(bob.public_key());
253 let shared2 = alice2.exchange(bob.public_key());
254
255 assert_ne!(shared1.as_bytes(), shared2.as_bytes());
256 }
257
258 #[test]
259 fn test_derive_key_from_shared_secret() {
260 let alice = KeyExchangeKeypair::generate();
261 let bob = KeyExchangeKeypair::generate();
262
263 let alice_shared = alice.exchange(bob.public_key());
264 let bob_shared = bob.exchange(alice.public_key());
265
266 let alice_key = alice_shared.derive_key(b"encryption");
267 let bob_key = bob_shared.derive_key(b"encryption");
268
269 assert_eq!(alice_key, bob_key);
270 }
271
272 #[test]
273 fn test_different_info_different_keys() {
274 let alice = KeyExchangeKeypair::generate();
275 let bob = KeyExchangeKeypair::generate();
276
277 let shared = alice.exchange(bob.public_key());
278
279 let key1 = shared.derive_key(b"encryption");
280 let key2 = shared.derive_key(b"authentication");
281
282 assert_ne!(key1, key2);
283 }
284
285 #[test]
286 fn test_public_key_serialization() {
287 let keypair = KeyExchangeKeypair::generate();
288 let public_bytes = keypair.public_key_bytes();
289
290 assert_eq!(public_bytes.len(), 32);
292
293 assert_ne!(public_bytes, [0u8; 32]);
295 }
296
297 #[test]
298 fn test_keypair_from_bytes() {
299 let secret_bytes = [42u8; 32];
300 let keypair = KeyExchangeKeypair::from_bytes(secret_bytes);
301
302 assert_ne!(keypair.public_key_bytes(), [0u8; 32]);
304 }
305
306 #[test]
307 fn test_commutative_exchange() {
308 let alice = KeyExchangeKeypair::generate();
309 let bob = KeyExchangeKeypair::generate();
310 let carol = KeyExchangeKeypair::generate();
311
312 let alice_bob = alice.exchange(bob.public_key());
313 let bob_alice = bob.exchange(alice.public_key());
314 let alice_carol = alice.exchange(carol.public_key());
315
316 assert_eq!(alice_bob.as_bytes(), bob_alice.as_bytes());
318
319 assert_ne!(alice_bob.as_bytes(), alice_carol.as_bytes());
321 }
322
323 #[test]
324 fn test_ephemeral_keypair() {
325 let ephemeral1 = ephemeral_keypair();
326 let ephemeral2 = ephemeral_keypair();
327
328 assert_ne!(ephemeral1.public_key_bytes(), ephemeral2.public_key_bytes());
330 }
331
332 #[test]
333 fn test_exchange_and_derive() {
334 let alice = KeyExchangeKeypair::generate();
335 let bob = KeyExchangeKeypair::generate();
336
337 let alice_key = exchange_and_derive(&alice, bob.public_key(), b"test-session");
338 let bob_key = exchange_and_derive(&bob, alice.public_key(), b"test-session");
339
340 assert_eq!(alice_key, bob_key);
341 }
342
343 #[test]
344 fn test_derive_multiple_keys() {
345 let alice = KeyExchangeKeypair::generate();
346 let bob = KeyExchangeKeypair::generate();
347
348 let shared = alice.exchange(bob.public_key());
349
350 let keys = shared.derive_keys(&[b"key1", b"key2", b"key3"]);
351
352 assert_eq!(keys.len(), 3);
353 assert_ne!(keys[0], keys[1]);
354 assert_ne!(keys[1], keys[2]);
355 assert_ne!(keys[0], keys[2]);
356 }
357
358 #[test]
359 fn test_shared_secret_serialization() {
360 let alice = KeyExchangeKeypair::generate();
361 let bob = KeyExchangeKeypair::generate();
362
363 let shared = alice.exchange(bob.public_key());
364
365 let bytes = shared.to_bytes();
366 let restored = SharedSecret::from_bytes(bytes);
367
368 assert_eq!(shared.as_bytes(), restored.as_bytes());
369 }
370}