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 secret = StaticSecret::random_from_rng(rand_core06::OsRng);
119 let public = X25519PublicKey::from(&secret);
120
121 Self { secret, public }
122 }
123
124 pub fn from_bytes(secret_bytes: [u8; 32]) -> Self {
137 let secret = StaticSecret::from(secret_bytes);
138 let public = X25519PublicKey::from(&secret);
139
140 Self { secret, public }
141 }
142
143 pub fn public_key(&self) -> &X25519PublicKey {
148 &self.public
149 }
150
151 pub fn public_key_bytes(&self) -> [u8; 32] {
156 *self.public.as_bytes()
157 }
158}
159
160impl KeyExchange for KeyExchangeKeypair {
161 fn exchange(&self, their_public: &X25519PublicKey) -> SharedSecret {
179 let shared = self.secret.diffie_hellman(their_public);
180 SharedSecret(*shared.as_bytes())
181 }
182}
183
184pub fn ephemeral_keypair() -> KeyExchangeKeypair {
196 KeyExchangeKeypair::generate()
197}
198
199pub fn exchange_and_derive(
222 our_secret: &KeyExchangeKeypair,
223 their_public: &X25519PublicKey,
224 context: &[u8],
225) -> [u8; 32] {
226 let shared = our_secret.exchange(their_public);
227 shared.derive_key(context)
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_key_exchange_roundtrip() {
236 let alice = KeyExchangeKeypair::generate();
237 let bob = KeyExchangeKeypair::generate();
238
239 let alice_shared = alice.exchange(bob.public_key());
240 let bob_shared = bob.exchange(alice.public_key());
241
242 assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
243 }
244
245 #[test]
246 fn test_different_pairs_different_secrets() {
247 let alice1 = KeyExchangeKeypair::generate();
248 let alice2 = KeyExchangeKeypair::generate();
249 let bob = KeyExchangeKeypair::generate();
250
251 let shared1 = alice1.exchange(bob.public_key());
252 let shared2 = alice2.exchange(bob.public_key());
253
254 assert_ne!(shared1.as_bytes(), shared2.as_bytes());
255 }
256
257 #[test]
258 fn test_derive_key_from_shared_secret() {
259 let alice = KeyExchangeKeypair::generate();
260 let bob = KeyExchangeKeypair::generate();
261
262 let alice_shared = alice.exchange(bob.public_key());
263 let bob_shared = bob.exchange(alice.public_key());
264
265 let alice_key = alice_shared.derive_key(b"encryption");
266 let bob_key = bob_shared.derive_key(b"encryption");
267
268 assert_eq!(alice_key, bob_key);
269 }
270
271 #[test]
272 fn test_different_info_different_keys() {
273 let alice = KeyExchangeKeypair::generate();
274 let bob = KeyExchangeKeypair::generate();
275
276 let shared = alice.exchange(bob.public_key());
277
278 let key1 = shared.derive_key(b"encryption");
279 let key2 = shared.derive_key(b"authentication");
280
281 assert_ne!(key1, key2);
282 }
283
284 #[test]
285 fn test_public_key_serialization() {
286 let keypair = KeyExchangeKeypair::generate();
287 let public_bytes = keypair.public_key_bytes();
288
289 assert_eq!(public_bytes.len(), 32);
291
292 assert_ne!(public_bytes, [0u8; 32]);
294 }
295
296 #[test]
297 fn test_keypair_from_bytes() {
298 let secret_bytes = [42u8; 32];
299 let keypair = KeyExchangeKeypair::from_bytes(secret_bytes);
300
301 assert_ne!(keypair.public_key_bytes(), [0u8; 32]);
303 }
304
305 #[test]
306 fn test_commutative_exchange() {
307 let alice = KeyExchangeKeypair::generate();
308 let bob = KeyExchangeKeypair::generate();
309 let carol = KeyExchangeKeypair::generate();
310
311 let alice_bob = alice.exchange(bob.public_key());
312 let bob_alice = bob.exchange(alice.public_key());
313 let alice_carol = alice.exchange(carol.public_key());
314
315 assert_eq!(alice_bob.as_bytes(), bob_alice.as_bytes());
317
318 assert_ne!(alice_bob.as_bytes(), alice_carol.as_bytes());
320 }
321
322 #[test]
323 fn test_ephemeral_keypair() {
324 let ephemeral1 = ephemeral_keypair();
325 let ephemeral2 = ephemeral_keypair();
326
327 assert_ne!(ephemeral1.public_key_bytes(), ephemeral2.public_key_bytes());
329 }
330
331 #[test]
332 fn test_exchange_and_derive() {
333 let alice = KeyExchangeKeypair::generate();
334 let bob = KeyExchangeKeypair::generate();
335
336 let alice_key = exchange_and_derive(&alice, bob.public_key(), b"test-session");
337 let bob_key = exchange_and_derive(&bob, alice.public_key(), b"test-session");
338
339 assert_eq!(alice_key, bob_key);
340 }
341
342 #[test]
343 fn test_derive_multiple_keys() {
344 let alice = KeyExchangeKeypair::generate();
345 let bob = KeyExchangeKeypair::generate();
346
347 let shared = alice.exchange(bob.public_key());
348
349 let keys = shared.derive_keys(&[b"key1", b"key2", b"key3"]);
350
351 assert_eq!(keys.len(), 3);
352 assert_ne!(keys[0], keys[1]);
353 assert_ne!(keys[1], keys[2]);
354 assert_ne!(keys[0], keys[2]);
355 }
356
357 #[test]
358 fn test_shared_secret_serialization() {
359 let alice = KeyExchangeKeypair::generate();
360 let bob = KeyExchangeKeypair::generate();
361
362 let shared = alice.exchange(bob.public_key());
363
364 let bytes = shared.to_bytes();
365 let restored = SharedSecret::from_bytes(bytes);
366
367 assert_eq!(shared.as_bytes(), restored.as_bytes());
368 }
369}