Skip to main content

chie_crypto/
keyexchange.rs

1//! X25519 key exchange for secure P2P communication.
2//!
3//! This module provides Diffie-Hellman key exchange using the X25519 elliptic curve,
4//! enabling peers in the CHIE network to establish secure encrypted channels.
5//!
6//! # Features
7//! - X25519 Diffie-Hellman key exchange
8//! - Ephemeral and static key support
9//! - Shared secret derivation with HKDF
10//! - Key serialization for network transmission
11//!
12//! # Example
13//! ```
14//! use chie_crypto::keyexchange::{KeyExchange, KeyExchangeKeypair};
15//!
16//! // Alice generates a keypair
17//! let alice = KeyExchangeKeypair::generate();
18//! // Bob generates a keypair
19//! let bob = KeyExchangeKeypair::generate();
20//!
21//! // Exchange public keys and derive shared secret
22//! let alice_shared = alice.exchange(bob.public_key());
23//! let bob_shared = bob.exchange(alice.public_key());
24//!
25//! // Both parties now have the same shared secret
26//! assert_eq!(alice_shared.as_bytes(), bob_shared.as_bytes());
27//! ```
28
29use thiserror::Error;
30use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
31use zeroize::{Zeroize, ZeroizeOnDrop};
32
33/// Shared secret derived from key exchange (32 bytes).
34#[derive(Clone, Zeroize, ZeroizeOnDrop)]
35pub struct SharedSecret([u8; 32]);
36
37/// Key exchange keypair for X25519 Diffie-Hellman.
38pub struct KeyExchangeKeypair {
39    secret: StaticSecret,
40    public: X25519PublicKey,
41}
42
43/// Key exchange trait for performing Diffie-Hellman.
44pub trait KeyExchange {
45    /// Perform key exchange to derive a shared secret.
46    fn exchange(&self, their_public: &X25519PublicKey) -> SharedSecret;
47}
48
49/// Errors that can occur during key exchange operations.
50#[derive(Debug, Error)]
51pub enum KeyExchangeError {
52    /// Invalid public key (low-order point).
53    #[error("Invalid public key")]
54    InvalidPublicKey,
55
56    /// Invalid secret key.
57    #[error("Invalid secret key")]
58    InvalidSecretKey,
59
60    /// Shared secret derivation failed.
61    #[error("Shared secret derivation failed")]
62    DerivationFailed,
63}
64
65pub type KeyExchangeResult<T> = Result<T, KeyExchangeError>;
66
67impl SharedSecret {
68    /// Create a new shared secret from bytes.
69    pub fn from_bytes(bytes: [u8; 32]) -> Self {
70        Self(bytes)
71    }
72
73    /// Get the shared secret as a byte slice.
74    pub fn as_bytes(&self) -> &[u8; 32] {
75        &self.0
76    }
77
78    /// Convert to byte array.
79    pub fn to_bytes(&self) -> [u8; 32] {
80        self.0
81    }
82
83    /// Derive an encryption key from the shared secret using HKDF.
84    ///
85    /// # Arguments
86    /// * `info` - Context-specific information for key derivation
87    ///
88    /// # Returns
89    /// A 32-byte derived key suitable for symmetric encryption.
90    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    /// Derive multiple keys from the shared secret.
97    ///
98    /// # Arguments
99    /// * `infos` - Slice of context information for each key
100    ///
101    /// # Returns
102    /// Vector of 32-byte derived keys.
103    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    /// Generate a new random keypair.
110    ///
111    /// # Example
112    /// ```
113    /// use chie_crypto::keyexchange::KeyExchangeKeypair;
114    ///
115    /// let keypair = KeyExchangeKeypair::generate();
116    /// ```
117    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    /// Create a keypair from a secret key (32 bytes).
125    ///
126    /// # Arguments
127    /// * `secret_bytes` - 32-byte secret key
128    ///
129    /// # Example
130    /// ```
131    /// use chie_crypto::keyexchange::KeyExchangeKeypair;
132    ///
133    /// let secret = [1u8; 32];
134    /// let keypair = KeyExchangeKeypair::from_bytes(secret);
135    /// ```
136    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    /// Get the public key.
144    ///
145    /// # Returns
146    /// Reference to the X25519 public key.
147    pub fn public_key(&self) -> &X25519PublicKey {
148        &self.public
149    }
150
151    /// Get the public key as bytes.
152    ///
153    /// # Returns
154    /// 32-byte array containing the public key.
155    pub fn public_key_bytes(&self) -> [u8; 32] {
156        *self.public.as_bytes()
157    }
158}
159
160impl KeyExchange for KeyExchangeKeypair {
161    /// Perform X25519 Diffie-Hellman key exchange.
162    ///
163    /// # Arguments
164    /// * `their_public` - The other party's public key
165    ///
166    /// # Returns
167    /// Shared secret derived from the key exchange.
168    ///
169    /// # Example
170    /// ```
171    /// use chie_crypto::keyexchange::{KeyExchange, KeyExchangeKeypair};
172    ///
173    /// let alice = KeyExchangeKeypair::generate();
174    /// let bob = KeyExchangeKeypair::generate();
175    ///
176    /// let shared = alice.exchange(bob.public_key());
177    /// ```
178    fn exchange(&self, their_public: &X25519PublicKey) -> SharedSecret {
179        let shared = self.secret.diffie_hellman(their_public);
180        SharedSecret(*shared.as_bytes())
181    }
182}
183
184/// Create an ephemeral keypair for one-time key exchange.
185///
186/// # Returns
187/// A newly generated keypair intended for ephemeral use.
188///
189/// # Example
190/// ```
191/// use chie_crypto::keyexchange::ephemeral_keypair;
192///
193/// let ephemeral = ephemeral_keypair();
194/// ```
195pub fn ephemeral_keypair() -> KeyExchangeKeypair {
196    KeyExchangeKeypair::generate()
197}
198
199/// Perform a complete key exchange and derive an encryption key.
200///
201/// # Arguments
202/// * `our_secret` - Our keypair
203/// * `their_public` - The other party's public key
204/// * `context` - Context information for key derivation
205///
206/// # Returns
207/// A 32-byte encryption key derived from the shared secret.
208///
209/// # Example
210/// ```
211/// use chie_crypto::keyexchange::{KeyExchangeKeypair, exchange_and_derive};
212///
213/// let alice = KeyExchangeKeypair::generate();
214/// let bob = KeyExchangeKeypair::generate();
215///
216/// let alice_key = exchange_and_derive(&alice, bob.public_key(), b"session-1");
217/// let bob_key = exchange_and_derive(&bob, alice.public_key(), b"session-1");
218///
219/// assert_eq!(alice_key, bob_key);
220/// ```
221pub 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        // Should be 32 bytes
290        assert_eq!(public_bytes.len(), 32);
291
292        // Should be non-zero
293        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        // Should produce valid public key
302        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        // Same pair = same secret
316        assert_eq!(alice_bob.as_bytes(), bob_alice.as_bytes());
317
318        // Different pair = different secret
319        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        // Different ephemeral keypairs
328        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}