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 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    /// Create a keypair from a secret key (32 bytes).
126    ///
127    /// # Arguments
128    /// * `secret_bytes` - 32-byte secret key
129    ///
130    /// # Example
131    /// ```
132    /// use chie_crypto::keyexchange::KeyExchangeKeypair;
133    ///
134    /// let secret = [1u8; 32];
135    /// let keypair = KeyExchangeKeypair::from_bytes(secret);
136    /// ```
137    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    /// Get the public key.
145    ///
146    /// # Returns
147    /// Reference to the X25519 public key.
148    pub fn public_key(&self) -> &X25519PublicKey {
149        &self.public
150    }
151
152    /// Get the public key as bytes.
153    ///
154    /// # Returns
155    /// 32-byte array containing the public key.
156    pub fn public_key_bytes(&self) -> [u8; 32] {
157        *self.public.as_bytes()
158    }
159}
160
161impl KeyExchange for KeyExchangeKeypair {
162    /// Perform X25519 Diffie-Hellman key exchange.
163    ///
164    /// # Arguments
165    /// * `their_public` - The other party's public key
166    ///
167    /// # Returns
168    /// Shared secret derived from the key exchange.
169    ///
170    /// # Example
171    /// ```
172    /// use chie_crypto::keyexchange::{KeyExchange, KeyExchangeKeypair};
173    ///
174    /// let alice = KeyExchangeKeypair::generate();
175    /// let bob = KeyExchangeKeypair::generate();
176    ///
177    /// let shared = alice.exchange(bob.public_key());
178    /// ```
179    fn exchange(&self, their_public: &X25519PublicKey) -> SharedSecret {
180        let shared = self.secret.diffie_hellman(their_public);
181        SharedSecret(*shared.as_bytes())
182    }
183}
184
185/// Create an ephemeral keypair for one-time key exchange.
186///
187/// # Returns
188/// A newly generated keypair intended for ephemeral use.
189///
190/// # Example
191/// ```
192/// use chie_crypto::keyexchange::ephemeral_keypair;
193///
194/// let ephemeral = ephemeral_keypair();
195/// ```
196pub fn ephemeral_keypair() -> KeyExchangeKeypair {
197    KeyExchangeKeypair::generate()
198}
199
200/// Perform a complete key exchange and derive an encryption key.
201///
202/// # Arguments
203/// * `our_secret` - Our keypair
204/// * `their_public` - The other party's public key
205/// * `context` - Context information for key derivation
206///
207/// # Returns
208/// A 32-byte encryption key derived from the shared secret.
209///
210/// # Example
211/// ```
212/// use chie_crypto::keyexchange::{KeyExchangeKeypair, exchange_and_derive};
213///
214/// let alice = KeyExchangeKeypair::generate();
215/// let bob = KeyExchangeKeypair::generate();
216///
217/// let alice_key = exchange_and_derive(&alice, bob.public_key(), b"session-1");
218/// let bob_key = exchange_and_derive(&bob, alice.public_key(), b"session-1");
219///
220/// assert_eq!(alice_key, bob_key);
221/// ```
222pub 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        // Should be 32 bytes
291        assert_eq!(public_bytes.len(), 32);
292
293        // Should be non-zero
294        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        // Should produce valid public key
303        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        // Same pair = same secret
317        assert_eq!(alice_bob.as_bytes(), bob_alice.as_bytes());
318
319        // Different pair = different secret
320        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        // Different ephemeral keypairs
329        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}