rust_bottle/
ecdh.rs

1use crate::errors::{BottleError, Result};
2use crate::tpm::ECDHHandler;
3use p256::ecdh::EphemeralSecret;
4use p256::{PublicKey, SecretKey};
5use rand::{CryptoRng, RngCore};
6use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
7
8#[cfg(feature = "ml-kem")]
9use hybrid_array::{
10    sizes::{U1088, U1568},
11    Array,
12};
13#[cfg(feature = "ml-kem")]
14use ml_kem::{kem::Kem, EncodedSizeUser, KemCore, MlKem1024Params, MlKem768Params};
15#[cfg(feature = "ml-kem")]
16use zerocopy::AsBytes;
17
18/// ECDH encryption using P-256 elliptic curve.
19///
20/// This function performs Elliptic Curve Diffie-Hellman key exchange using
21/// the P-256 (secp256r1) curve. It generates an ephemeral key pair, computes
22/// a shared secret with the recipient's public key, derives an AES-256-GCM
23/// encryption key, and encrypts the plaintext.
24///
25/// # Arguments
26///
27/// * `rng` - A cryptographically secure random number generator
28/// * `plaintext` - The message to encrypt
29/// * `public_key` - The recipient's P-256 public key
30///
31/// # Returns
32///
33/// * `Ok(Vec<u8>)` - Encrypted data: ephemeral public key (65 bytes) + ciphertext
34/// * `Err(BottleError::Encryption)` - If encryption fails
35///
36/// # Format
37///
38/// The output format is: `[ephemeral_public_key (65 bytes)][encrypted_data]`
39///
40/// # Example
41///
42/// ```rust
43/// use rust_bottle::ecdh::ecdh_encrypt_p256;
44/// use rust_bottle::keys::EcdsaP256Key;
45/// use rand::rngs::OsRng;
46///
47/// let rng = &mut OsRng;
48/// let key = EcdsaP256Key::generate(rng);
49/// let pub_key = p256::PublicKey::from_sec1_bytes(&key.public_key_bytes()).unwrap();
50///
51/// let plaintext = b"Secret message";
52/// let ciphertext = ecdh_encrypt_p256(rng, plaintext, &pub_key).unwrap();
53/// ```
54pub fn ecdh_encrypt_p256<R: RngCore + CryptoRng>(
55    rng: &mut R,
56    plaintext: &[u8],
57    public_key: &PublicKey,
58) -> Result<Vec<u8>> {
59    let secret = EphemeralSecret::random(rng);
60    let shared_secret = secret.diffie_hellman(public_key);
61
62    // Derive encryption key from shared secret
63    // For p256 0.13, the shared secret is a SharedSecret type
64    // Extract shared secret bytes - raw_secret_bytes() returns a GenericArray
65    let shared_bytes = shared_secret.raw_secret_bytes();
66    // Convert to slice for key derivation (use as_ref() instead of deprecated as_slice())
67    let key = derive_key(shared_bytes.as_ref());
68
69    // Encrypt using AES-GCM (simplified - in production use proper AEAD)
70    let encrypted = encrypt_aes_gcm(&key, plaintext)?;
71
72    // Include ephemeral public key
73    let ephemeral_pub = secret.public_key();
74    let mut result = ephemeral_pub.to_sec1_bytes().to_vec();
75    result.extend_from_slice(&encrypted);
76
77    Ok(result)
78}
79
80/// ECDH decryption using P-256 elliptic curve.
81///
82/// This function decrypts data encrypted with `ecdh_encrypt_p256`. It extracts
83/// the ephemeral public key from the ciphertext, computes the shared secret
84/// using the recipient's private key, derives the AES-256-GCM key, and decrypts.
85///
86/// # Arguments
87///
88/// * `ciphertext` - Encrypted data: ephemeral public key (65 bytes) + ciphertext
89/// * `private_key` - The recipient's P-256 private key
90///
91/// # Returns
92///
93/// * `Ok(Vec<u8>)` - Decrypted plaintext
94/// * `Err(BottleError::InvalidFormat)` - If ciphertext is too short
95/// * `Err(BottleError::Decryption)` - If decryption fails
96///
97/// # Example
98///
99/// ```rust
100/// use rust_bottle::ecdh::{ecdh_encrypt_p256, ecdh_decrypt_p256};
101/// use rust_bottle::keys::EcdsaP256Key;
102/// use rand::rngs::OsRng;
103/// use p256::elliptic_curve::sec1::FromEncodedPoint;
104///
105/// let rng = &mut OsRng;
106/// let key = EcdsaP256Key::generate(rng);
107/// let pub_key = p256::PublicKey::from_sec1_bytes(&key.public_key_bytes()).unwrap();
108/// let priv_key_bytes = key.private_key_bytes();
109/// let priv_key = p256::SecretKey::from_bytes(priv_key_bytes.as_slice().into()).unwrap();
110///
111/// let plaintext = b"Secret message";
112/// let ciphertext = ecdh_encrypt_p256(rng, plaintext, &pub_key).unwrap();
113/// let decrypted = ecdh_decrypt_p256(&ciphertext, &priv_key).unwrap();
114/// assert_eq!(decrypted, plaintext);
115/// ```
116pub fn ecdh_decrypt_p256(ciphertext: &[u8], private_key: &SecretKey) -> Result<Vec<u8>> {
117    if ciphertext.len() < 65 {
118        return Err(BottleError::InvalidFormat);
119    }
120
121    // Extract ephemeral public key
122    let ephemeral_pub = PublicKey::from_sec1_bytes(&ciphertext[..65])
123        .map_err(|_| BottleError::Decryption("Invalid ephemeral public key".to_string()))?;
124
125    // Compute shared secret using ECDH
126    // For p256 0.13, use the SecretKey with the ephemeral public key
127    // Create a SharedSecret by multiplying the private scalar with the public point
128    use p256::elliptic_curve::sec1::ToEncodedPoint;
129    let scalar = private_key.to_nonzero_scalar();
130    let point = ephemeral_pub.as_affine();
131    // Perform ECDH: shared_secret = private_scalar * public_point
132    let shared_point = (*point * scalar.as_ref()).to_encoded_point(false);
133    // Use x-coordinate as shared secret (standard ECDH) (use as_ref() instead of deprecated as_slice())
134    let shared_bytes = shared_point.x().unwrap().as_ref();
135    let key = derive_key(shared_bytes);
136
137    // Decrypt
138    decrypt_aes_gcm(&key, &ciphertext[65..])
139}
140
141/// X25519 ECDH encryption.
142///
143/// This function performs Elliptic Curve Diffie-Hellman key exchange using
144/// the X25519 curve (Curve25519). It generates an ephemeral key pair, computes
145/// a shared secret with the recipient's public key, derives an AES-256-GCM
146/// encryption key, and encrypts the plaintext.
147///
148/// # Arguments
149///
150/// * `rng` - A random number generator
151/// * `plaintext` - The message to encrypt
152/// * `public_key` - The recipient's X25519 public key
153///
154/// # Returns
155///
156/// * `Ok(Vec<u8>)` - Encrypted data: ephemeral public key (32 bytes) + ciphertext
157/// * `Err(BottleError::Encryption)` - If encryption fails
158///
159/// # Format
160///
161/// The output format is: `[ephemeral_public_key (32 bytes)][encrypted_data]`
162///
163/// # Example
164///
165/// ```rust
166/// use rust_bottle::ecdh::ecdh_encrypt_x25519;
167/// use rust_bottle::keys::X25519Key;
168/// use rand::rngs::OsRng;
169///
170/// let rng = &mut OsRng;
171/// let key = X25519Key::generate(rng);
172/// let pub_key_bytes: [u8; 32] = key.public_key_bytes().try_into().unwrap();
173/// let pub_key = x25519_dalek::PublicKey::from(pub_key_bytes);
174///
175/// let plaintext = b"Secret message";
176/// let ciphertext = ecdh_encrypt_x25519(rng, plaintext, &pub_key).unwrap();
177/// ```
178pub fn ecdh_encrypt_x25519<R: RngCore>(
179    rng: &mut R,
180    plaintext: &[u8],
181    public_key: &X25519PublicKey,
182) -> Result<Vec<u8>> {
183    // Generate random secret key (32 bytes for X25519)
184    let mut secret_bytes = [0u8; 32];
185    rng.fill_bytes(&mut secret_bytes);
186
187    // Use StaticSecret from x25519-dalek 1.0
188    let secret = StaticSecret::from(secret_bytes);
189
190    // Compute shared secret
191    let shared_secret = secret.diffie_hellman(public_key);
192
193    // Derive encryption key from shared secret
194    let key = derive_key(shared_secret.as_bytes());
195
196    // Encrypt
197    let encrypted = encrypt_aes_gcm(&key, plaintext)?;
198
199    // Get ephemeral public key
200    let ephemeral_pub = X25519PublicKey::from(&secret);
201
202    let mut result = ephemeral_pub.as_bytes().to_vec();
203    result.extend_from_slice(&encrypted);
204
205    Ok(result)
206}
207
208/// X25519 ECDH decryption.
209///
210/// This function decrypts data encrypted with `ecdh_encrypt_x25519`. It extracts
211/// the ephemeral public key from the ciphertext, computes the shared secret
212/// using the recipient's private key, derives the AES-256-GCM key, and decrypts.
213///
214/// # Arguments
215///
216/// * `ciphertext` - Encrypted data: ephemeral public key (32 bytes) + ciphertext
217/// * `private_key` - The recipient's X25519 private key (32 bytes)
218///
219/// # Returns
220///
221/// * `Ok(Vec<u8>)` - Decrypted plaintext
222/// * `Err(BottleError::InvalidFormat)` - If ciphertext is too short
223/// * `Err(BottleError::Decryption)` - If decryption fails
224///
225/// # Example
226///
227/// ```rust
228/// use rust_bottle::ecdh::{ecdh_encrypt_x25519, ecdh_decrypt_x25519};
229/// use rust_bottle::keys::X25519Key;
230/// use rand::rngs::OsRng;
231///
232/// let rng = &mut OsRng;
233/// let key = X25519Key::generate(rng);
234/// let pub_key_bytes: [u8; 32] = key.public_key_bytes().try_into().unwrap();
235/// let pub_key = x25519_dalek::PublicKey::from(pub_key_bytes);
236/// let priv_key_bytes: [u8; 32] = key.private_key_bytes().try_into().unwrap();
237///
238/// let plaintext = b"Secret message";
239/// let ciphertext = ecdh_encrypt_x25519(rng, plaintext, &pub_key).unwrap();
240/// let decrypted = ecdh_decrypt_x25519(&ciphertext, &priv_key_bytes).unwrap();
241/// assert_eq!(decrypted, plaintext);
242/// ```
243pub fn ecdh_decrypt_x25519(ciphertext: &[u8], private_key: &[u8; 32]) -> Result<Vec<u8>> {
244    if ciphertext.len() < 32 {
245        return Err(BottleError::InvalidFormat);
246    }
247
248    // Create StaticSecret from private key bytes
249    let priv_key = StaticSecret::from(*private_key);
250
251    // Extract ephemeral public key (32 bytes)
252    let ephemeral_pub_bytes: [u8; 32] = ciphertext[..32]
253        .try_into()
254        .map_err(|_| BottleError::InvalidFormat)?;
255    let ephemeral_pub = X25519PublicKey::from(ephemeral_pub_bytes);
256
257    // Compute shared secret
258    let shared_secret = priv_key.diffie_hellman(&ephemeral_pub);
259    let key = derive_key(shared_secret.as_bytes());
260
261    // Decrypt
262    decrypt_aes_gcm(&key, &ciphertext[32..])
263}
264
265/// Trait for ECDH encryption operations.
266///
267/// This trait allows different ECDH implementations to be used polymorphically.
268/// Currently not used in the public API but available for extension.
269pub trait ECDHEncrypt {
270    /// Encrypt plaintext to a public key using ECDH.
271    fn encrypt<R: RngCore>(
272        &self,
273        rng: &mut R,
274        plaintext: &[u8],
275        public_key: &[u8],
276    ) -> Result<Vec<u8>>;
277}
278
279/// Trait for ECDH decryption operations.
280///
281/// This trait allows different ECDH implementations to be used polymorphically.
282/// Currently not used in the public API but available for extension.
283pub trait ECDHDecrypt {
284    /// Decrypt ciphertext using a private key.
285    fn decrypt(&self, ciphertext: &[u8], private_key: &[u8]) -> Result<Vec<u8>>;
286}
287
288/// Generic ECDH encryption function with automatic key type detection.
289///
290/// This function automatically detects the key type based on the public key
291/// length and format, then uses the appropriate encryption implementation.
292///
293/// # Key Type Detection
294///
295/// * 32 bytes: X25519 (Curve25519)
296/// * 64 or 65 bytes: P-256 (secp256r1) in SEC1 format
297/// * 1184 bytes: ML-KEM-768 public key
298/// * 1568 bytes: ML-KEM-1024 public key
299///
300/// # Arguments
301///
302/// * `rng` - A cryptographically secure random number generator
303/// * `plaintext` - The message to encrypt
304/// * `public_key` - The recipient's public key (any supported format)
305///
306/// # Returns
307///
308/// * `Ok(Vec<u8>)` - Encrypted data with ephemeral public key prepended
309/// * `Err(BottleError::InvalidKeyType)` - If the key format is not recognized
310/// * `Err(BottleError::Encryption)` - If encryption fails
311///
312/// # Example
313///
314/// ```rust
315/// use rust_bottle::ecdh::ecdh_encrypt;
316/// use rust_bottle::keys::X25519Key;
317/// use rand::rngs::OsRng;
318///
319/// let rng = &mut OsRng;
320/// let key = X25519Key::generate(rng);
321/// let plaintext = b"Secret message";
322///
323/// let ciphertext = ecdh_encrypt(rng, plaintext, &key.public_key_bytes()).unwrap();
324/// ```
325pub fn ecdh_encrypt<R: RngCore + CryptoRng>(
326    rng: &mut R,
327    plaintext: &[u8],
328    public_key: &[u8],
329) -> Result<Vec<u8>> {
330    ecdh_encrypt_with_handler(rng, plaintext, public_key, None)
331}
332
333/// Generic ECDH encryption function with optional TPM/HSM handler.
334///
335/// This function is similar to `ecdh_encrypt`. The handler parameter is currently
336/// unused for encryption (encryption always uses software-generated ephemeral keys),
337/// but is provided for API consistency. Handlers are primarily used during
338/// decryption when the recipient's private key is stored in TPM/HSM.
339///
340/// # Arguments
341///
342/// * `rng` - A cryptographically secure random number generator
343/// * `plaintext` - The message to encrypt
344/// * `public_key` - The recipient's public key (any supported format)
345/// * `handler` - Optional TPM/HSM handler (currently unused for encryption)
346///
347/// # Returns
348///
349/// * `Ok(Vec<u8>)` - Encrypted data with ephemeral public key prepended
350/// * `Err(BottleError::InvalidKeyType)` - If the key format is not recognized
351/// * `Err(BottleError::Encryption)` - If encryption fails
352///
353/// # Example
354///
355/// ```rust
356/// use rust_bottle::ecdh::ecdh_encrypt_with_handler;
357/// use rust_bottle::keys::X25519Key;
358/// use rand::rngs::OsRng;
359///
360/// let rng = &mut OsRng;
361/// let key = X25519Key::generate(rng);
362/// let plaintext = b"Secret message";
363///
364/// // Encryption always uses software implementation
365/// let ciphertext = ecdh_encrypt_with_handler(
366///     rng,
367///     plaintext,
368///     &key.public_key_bytes(),
369///     None,
370/// ).unwrap();
371/// ```
372pub fn ecdh_encrypt_with_handler<R: RngCore + CryptoRng>(
373    rng: &mut R,
374    plaintext: &[u8],
375    public_key: &[u8],
376    _handler: Option<&dyn ECDHHandler>,
377) -> Result<Vec<u8>> {
378    // Encryption always uses software implementation with ephemeral keys
379    // Handlers are used during decryption when the recipient's key is in TPM/HSM
380    // Try to determine key type and use appropriate function
381    // X25519 keys are 32 bytes
382    if public_key.len() == 32 {
383        let pub_key_bytes: [u8; 32] = public_key
384            .try_into()
385            .map_err(|_| BottleError::InvalidKeyType)?;
386        let pub_key = X25519PublicKey::from(pub_key_bytes);
387        ecdh_encrypt_x25519(rng, plaintext, &pub_key)
388    } else if public_key.len() == 65 || public_key.len() == 64 {
389        let pub_key =
390            PublicKey::from_sec1_bytes(public_key).map_err(|_| BottleError::InvalidKeyType)?;
391        ecdh_encrypt_p256(rng, plaintext, &pub_key)
392    } else {
393        #[cfg(feature = "ml-kem")]
394        {
395            if public_key.len() == 1184 {
396                // ML-KEM-768 public key
397                return mlkem768_encrypt(rng, plaintext, public_key);
398            } else if public_key.len() == 1568 {
399                // ML-KEM-1024 public key
400                return mlkem1024_encrypt(rng, plaintext, public_key);
401            }
402        }
403        Err(BottleError::InvalidKeyType)
404    }
405}
406
407/// Generic ECDH decryption function with automatic key type detection.
408///
409/// This function automatically detects the key type and uses the appropriate
410/// decryption implementation. It tries X25519 first, then P-256, then ML-KEM.
411///
412/// # Key Type Detection
413///
414/// * 32 bytes: Tries X25519 first, then P-256 if X25519 fails
415/// * 2400 bytes: ML-KEM-768 secret key
416/// * 3168 bytes: ML-KEM-1024 secret key
417///
418/// # Arguments
419///
420/// * `ciphertext` - Encrypted data with ephemeral public key prepended
421/// * `private_key` - The recipient's private key
422///
423/// # Returns
424///
425/// * `Ok(Vec<u8>)` - Decrypted plaintext
426/// * `Err(BottleError::InvalidKeyType)` - If the key format is not recognized
427/// * `Err(BottleError::Decryption)` - If decryption fails
428///
429/// # Example
430///
431/// ```rust
432/// use rust_bottle::ecdh::{ecdh_encrypt, ecdh_decrypt};
433/// use rust_bottle::keys::X25519Key;
434/// use rand::rngs::OsRng;
435///
436/// let rng = &mut OsRng;
437/// let key = X25519Key::generate(rng);
438/// let plaintext = b"Secret message";
439///
440/// let ciphertext = ecdh_encrypt(rng, plaintext, &key.public_key_bytes()).unwrap();
441/// let decrypted = ecdh_decrypt(&ciphertext, &key.private_key_bytes()).unwrap();
442/// assert_eq!(decrypted, plaintext);
443/// ```
444pub fn ecdh_decrypt(ciphertext: &[u8], private_key: &[u8]) -> Result<Vec<u8>> {
445    ecdh_decrypt_with_handler(ciphertext, private_key, None)
446}
447
448/// Generic ECDH decryption function with optional TPM/HSM handler.
449///
450/// This function is similar to `ecdh_decrypt`, but allows specifying a TPM/HSM
451/// handler for hardware-backed key operations. If a handler is provided, it will
452/// use the handler's private key (stored in TPM/HSM) for ECDH operations instead
453/// of the provided private_key parameter.
454///
455/// # Arguments
456///
457/// * `ciphertext` - Encrypted data with ephemeral public key prepended
458/// * `private_key` - The recipient's private key (ignored if handler is provided)
459/// * `handler` - Optional TPM/HSM handler for hardware-backed operations
460///
461/// # Returns
462///
463/// * `Ok(Vec<u8>)` - Decrypted plaintext
464/// * `Err(BottleError::InvalidKeyType)` - If the key format is not recognized
465/// * `Err(BottleError::Decryption)` - If decryption fails
466///
467/// # Example
468///
469/// ```rust,no_run
470/// use rust_bottle::ecdh::ecdh_decrypt_with_handler;
471///
472/// // Without handler (software implementation)
473/// // let decrypted = ecdh_decrypt_with_handler(&ciphertext, &private_key, None)?;
474///
475/// // With TPM handler (if available)
476/// // The handler's private key (stored in TPM) will be used for decryption
477/// // let tpm_handler = TpmHandler::new()?;
478/// // let decrypted = ecdh_decrypt_with_handler(&ciphertext, &[], Some(&tpm_handler))?;
479/// ```
480pub fn ecdh_decrypt_with_handler(
481    ciphertext: &[u8],
482    private_key: &[u8],
483    handler: Option<&dyn ECDHHandler>,
484) -> Result<Vec<u8>> {
485    // If a handler is provided, use it for decryption
486    if let Some(h) = handler {
487        // Get the handler's public key to determine key type and ephemeral key size
488        let handler_pub_key = h.public_key()?;
489        
490        // Extract ephemeral public key from ciphertext
491        // The format depends on the key type (32 bytes for X25519, 65 for P-256)
492        if ciphertext.len() < handler_pub_key.len() {
493            return Err(BottleError::InvalidFormat);
494        }
495        
496        let ephemeral_pub_key = &ciphertext[..handler_pub_key.len()];
497        let encrypted_data = &ciphertext[handler_pub_key.len()..];
498        
499        // Use handler for ECDH (uses TPM/HSM private key)
500        let shared_secret = h.ecdh(ephemeral_pub_key)?;
501        let key = derive_key(&shared_secret);
502        
503        // Decrypt
504        return decrypt_aes_gcm(&key, encrypted_data);
505    }
506    
507    // Fall back to software implementation
508    #[cfg(feature = "ml-kem")]
509    {
510        // Try ML-KEM-768 (2400 bytes decapsulation key, or 3584 bytes full private key)
511        if private_key.len() == 2400 || private_key.len() == 3584 {
512            if let Ok(result) = mlkem768_decrypt(ciphertext, private_key) {
513                return Ok(result);
514            }
515        }
516
517        // Try ML-KEM-1024 (3168 bytes decapsulation key, or 4736 bytes full private key)
518        if private_key.len() == 3168 || private_key.len() == 4736 {
519            if let Ok(result) = mlkem1024_decrypt(ciphertext, private_key) {
520                return Ok(result);
521            }
522        }
523    }
524
525    // Try X25519 first (32 bytes)
526    if private_key.len() == 32 && ciphertext.len() >= 32 {
527        // Try to create X25519 key
528        let priv_key_bytes: [u8; 32] = match private_key.try_into() {
529            Ok(bytes) => bytes,
530            Err(_) => return Err(BottleError::InvalidKeyType),
531        };
532        match ecdh_decrypt_x25519(ciphertext, &priv_key_bytes) {
533            Ok(result) => return Ok(result),
534            Err(_) => {
535                // Not X25519, try P-256
536            }
537        }
538    }
539
540    // Try P-256 (32 bytes private key, but different format)
541    // P-256 keys are also 32 bytes, so we need to try both
542    if private_key.len() == 32 {
543        if let Ok(priv_key) = SecretKey::from_bytes(private_key.into()) {
544            if let Ok(result) = ecdh_decrypt_p256(ciphertext, &priv_key) {
545                return Ok(result);
546            }
547        }
548    }
549
550    Err(BottleError::InvalidKeyType)
551}
552
553#[cfg(feature = "ml-kem")]
554/// ML-KEM-768 encryption (post-quantum).
555///
556/// This function performs ML-KEM key encapsulation and encrypts the plaintext
557/// using the derived shared secret with AES-256-GCM.
558///
559/// # Arguments
560///
561/// * `rng` - A cryptographically secure random number generator (not used, ML-KEM is deterministic)
562/// * `plaintext` - The message to encrypt
563/// * `public_key` - The recipient's ML-KEM-768 public key (1184 bytes)
564///
565/// # Returns
566///
567/// * `Ok(Vec<u8>)` - Encrypted data: ML-KEM ciphertext (1088 bytes) + AES-GCM encrypted message
568/// * `Err(BottleError::Encryption)` - If encryption fails
569/// * `Err(BottleError::InvalidKeyType)` - If the key format is invalid
570pub fn mlkem768_encrypt<R: RngCore + CryptoRng>(
571    rng: &mut R,
572    plaintext: &[u8],
573    public_key: &[u8],
574) -> Result<Vec<u8>> {
575    // Parse public key (encapsulation key)
576    // from_bytes expects a generic-array Array type, so we need to convert
577    if public_key.len() != 1184 {
578        return Err(BottleError::InvalidKeyType);
579    }
580    let pub_key_array: [u8; 1184] = public_key
581        .try_into()
582        .map_err(|_| BottleError::InvalidKeyType)?;
583    let ek =
584        <Kem<MlKem768Params> as KemCore>::EncapsulationKey::from_bytes((&pub_key_array).into());
585
586    // ml-kem uses rand_core 0.9, create adapter
587    use rand_core_09::{CryptoRng as CryptoRng09, RngCore as RngCore09};
588    struct RngAdapter<'a, R: RngCore + CryptoRng>(&'a mut R);
589    impl<'a, R: RngCore + CryptoRng> RngCore09 for RngAdapter<'a, R> {
590        fn next_u32(&mut self) -> u32 {
591            self.0.next_u32()
592        }
593        fn next_u64(&mut self) -> u64 {
594            self.0.next_u64()
595        }
596        fn fill_bytes(&mut self, dest: &mut [u8]) {
597            self.0.fill_bytes(dest)
598        }
599        // try_fill_bytes has a default implementation that calls fill_bytes, so we don't need to implement it
600    }
601    impl<'a, R: RngCore + CryptoRng> CryptoRng09 for RngAdapter<'a, R> {}
602
603    let mut adapter = RngAdapter(rng);
604    // Encapsulate (generate shared secret and ciphertext)
605    use ml_kem::kem::Encapsulate;
606    let (ct, shared_secret) = ek
607        .encapsulate(&mut adapter)
608        .map_err(|_| BottleError::Encryption("ML-KEM encapsulation failed".to_string()))?;
609
610    // Derive AES key from shared secret (shared_secret is [u8; 32])
611    let key = derive_key(&shared_secret);
612
613    // Encrypt plaintext with AES-GCM
614    let encrypted = encrypt_aes_gcm(&key, plaintext)?;
615
616    // Combine: ML-KEM ciphertext + AES-GCM encrypted data
617    let mut result = ct.as_bytes().to_vec();
618    result.extend_from_slice(&encrypted);
619
620    Ok(result)
621}
622
623/// ML-KEM-768 decryption (post-quantum).
624///
625/// # Arguments
626///
627/// * `ciphertext` - Encrypted data: ML-KEM ciphertext (1088 bytes) + AES-GCM encrypted message
628/// * `secret_key` - The recipient's ML-KEM-768 secret key (2400 bytes)
629///
630/// # Returns
631///
632/// * `Ok(Vec<u8>)` - Decrypted plaintext
633/// * `Err(BottleError::Decryption)` - If decryption fails
634/// * `Err(BottleError::InvalidFormat)` - If ciphertext is too short
635#[cfg(feature = "ml-kem")]
636pub fn mlkem768_decrypt(ciphertext: &[u8], secret_key: &[u8]) -> Result<Vec<u8>> {
637    // Parse secret key (decapsulation key)
638    // Accept either 2400 bytes (decapsulation key only) or 3584 bytes (full private key: decaps + encaps)
639    let dk_bytes = if secret_key.len() == 2400 {
640        secret_key
641    } else if secret_key.len() == 3584 {
642        // Extract decapsulation key from full private key (first 2400 bytes)
643        &secret_key[..2400]
644    } else {
645        return Err(BottleError::InvalidKeyType);
646    };
647    let sec_key_array: [u8; 2400] = dk_bytes
648        .try_into()
649        .map_err(|_| BottleError::InvalidKeyType)?;
650    let dk =
651        <Kem<MlKem768Params> as KemCore>::DecapsulationKey::from_bytes((&sec_key_array).into());
652
653    // Extract ML-KEM ciphertext (first 1088 bytes for ML-KEM-768)
654    const CT_SIZE: usize = 1088; // ML-KEM-768 ciphertext size
655                                 // AES-GCM needs at least 12 bytes (nonce) + 16 bytes (tag) = 28 bytes minimum
656    if ciphertext.len() < CT_SIZE + 28 {
657        return Err(BottleError::InvalidFormat);
658    }
659    // Extract exactly CT_SIZE bytes for the ML-KEM ciphertext
660    let mlkem_ct_bytes = &ciphertext[..CT_SIZE];
661    let ct_array: [u8; CT_SIZE] = mlkem_ct_bytes
662        .try_into()
663        .map_err(|_| BottleError::InvalidFormat)?;
664    // Ciphertext type: use Array with size constant from hybrid_array::sizes
665    // ML-KEM-768 ciphertext is 1088 bytes
666    // Array implements From<[T; N]>, so we pass the array by value
667    let mlkem_ct: Array<u8, U1088> = ct_array.into();
668    let aes_ct = &ciphertext[CT_SIZE..];
669
670    // Decapsulate to get shared secret
671    use ml_kem::kem::Decapsulate;
672    let shared_secret = dk
673        .decapsulate(&mlkem_ct)
674        .map_err(|_| BottleError::Decryption("ML-KEM decapsulation failed".to_string()))?;
675
676    // Derive AES key (shared_secret is [u8; 32])
677    let key = derive_key(&shared_secret);
678
679    // Decrypt with AES-GCM
680    decrypt_aes_gcm(&key, aes_ct)
681}
682
683/// ML-KEM-1024 encryption (post-quantum).
684///
685/// # Arguments
686///
687/// * `rng` - A cryptographically secure random number generator (not used)
688/// * `plaintext` - The message to encrypt
689/// * `public_key` - The recipient's ML-KEM-1024 public key (1568 bytes)
690///
691/// # Returns
692///
693/// * `Ok(Vec<u8>)` - Encrypted data: ML-KEM ciphertext (1568 bytes) + AES-GCM encrypted message
694#[cfg(feature = "ml-kem")]
695pub fn mlkem1024_encrypt<R: RngCore + CryptoRng>(
696    rng: &mut R,
697    plaintext: &[u8],
698    public_key: &[u8],
699) -> Result<Vec<u8>> {
700    // Parse public key (encapsulation key)
701    if public_key.len() != 1568 {
702        return Err(BottleError::InvalidKeyType);
703    }
704    let pub_key_array: [u8; 1568] = public_key
705        .try_into()
706        .map_err(|_| BottleError::InvalidKeyType)?;
707    let ek =
708        <Kem<MlKem1024Params> as KemCore>::EncapsulationKey::from_bytes((&pub_key_array).into());
709
710    // ml-kem uses rand_core 0.9, create adapter
711    use rand_core_09::{CryptoRng as CryptoRng09, RngCore as RngCore09};
712    struct RngAdapter<'a, R: RngCore + CryptoRng>(&'a mut R);
713    impl<'a, R: RngCore + CryptoRng> RngCore09 for RngAdapter<'a, R> {
714        fn next_u32(&mut self) -> u32 {
715            self.0.next_u32()
716        }
717        fn next_u64(&mut self) -> u64 {
718            self.0.next_u64()
719        }
720        fn fill_bytes(&mut self, dest: &mut [u8]) {
721            self.0.fill_bytes(dest)
722        }
723        // try_fill_bytes has a default implementation that calls fill_bytes, so we don't need to implement it
724    }
725    impl<'a, R: RngCore + CryptoRng> CryptoRng09 for RngAdapter<'a, R> {}
726
727    let mut adapter = RngAdapter(rng);
728    // Encapsulate (generate shared secret and ciphertext)
729    use ml_kem::kem::Encapsulate;
730    let (ct, shared_secret) = ek
731        .encapsulate(&mut adapter)
732        .map_err(|_| BottleError::Encryption("ML-KEM encapsulation failed".to_string()))?;
733
734    // Derive AES key from shared secret (shared_secret is [u8; 32])
735    let key = derive_key(&shared_secret);
736
737    // Encrypt plaintext with AES-GCM
738    let encrypted = encrypt_aes_gcm(&key, plaintext)?;
739
740    // Combine: ML-KEM ciphertext + AES-GCM encrypted data
741    let mut result = ct.as_bytes().to_vec();
742    result.extend_from_slice(&encrypted);
743    Ok(result)
744}
745
746/// ML-KEM-1024 decryption (post-quantum).
747///
748/// # Arguments
749///
750/// * `ciphertext` - Encrypted data: ML-KEM ciphertext (1568 bytes) + AES-GCM encrypted message
751/// * `secret_key` - The recipient's ML-KEM-1024 secret key (3168 bytes)
752///
753/// # Returns
754///
755/// * `Ok(Vec<u8>)` - Decrypted plaintext
756#[cfg(feature = "ml-kem")]
757pub fn mlkem1024_decrypt(ciphertext: &[u8], secret_key: &[u8]) -> Result<Vec<u8>> {
758    // Parse secret key (decapsulation key)
759    // Accept either 3168 bytes (decapsulation key only) or 4736 bytes (full private key: decaps + encaps)
760    let dk_bytes = if secret_key.len() == 3168 {
761        secret_key
762    } else if secret_key.len() == 4736 {
763        // Extract decapsulation key from full private key (first 3168 bytes)
764        &secret_key[..3168]
765    } else {
766        return Err(BottleError::InvalidKeyType);
767    };
768    let sec_key_array: [u8; 3168] = dk_bytes
769        .try_into()
770        .map_err(|_| BottleError::InvalidKeyType)?;
771    let dk =
772        <Kem<MlKem1024Params> as KemCore>::DecapsulationKey::from_bytes((&sec_key_array).into());
773
774    // Extract ML-KEM ciphertext (first 1568 bytes for ML-KEM-1024)
775    const CT_SIZE: usize = 1568; // ML-KEM-1024 ciphertext size
776                                 // AES-GCM needs at least 12 bytes (nonce) + 16 bytes (tag) = 28 bytes minimum
777    if ciphertext.len() < CT_SIZE + 28 {
778        return Err(BottleError::InvalidFormat);
779    }
780    let ct_array: [u8; CT_SIZE] = ciphertext[..CT_SIZE]
781        .try_into()
782        .map_err(|_| BottleError::InvalidFormat)?;
783    // Ciphertext type: use Array with size constant from hybrid_array::sizes
784    // ML-KEM-1024 ciphertext is 1568 bytes
785    // Array implements From<[T; N]>, so we pass the array by value
786    let mlkem_ct: Array<u8, U1568> = ct_array.into();
787    let aes_ct = &ciphertext[CT_SIZE..];
788
789    // Decapsulate to get shared secret
790    use ml_kem::kem::Decapsulate;
791    let shared_secret = dk
792        .decapsulate(&mlkem_ct)
793        .map_err(|_| BottleError::Decryption("ML-KEM decapsulation failed".to_string()))?;
794
795    // Derive AES key (shared_secret is [u8; 32])
796    let key = derive_key(&shared_secret);
797    decrypt_aes_gcm(&key, aes_ct)
798}
799
800/// Hybrid encryption: ML-KEM-768 + X25519.
801///
802/// This provides both post-quantum and classical security by combining
803/// ML-KEM and X25519 key exchange. The plaintext is encrypted with both
804/// algorithms, and either can be used for decryption.
805///
806/// # Arguments
807///
808/// * `rng` - A cryptographically secure random number generator
809/// * `plaintext` - The message to encrypt
810/// * `mlkem_pub` - ML-KEM-768 public key (1184 bytes)
811/// * `x25519_pub` - X25519 public key (32 bytes)
812///
813/// # Returns
814///
815/// * `Ok(Vec<u8>)` - Encrypted data: [mlkem_len: u32][mlkem_ct][x25519_ct]
816#[cfg(feature = "ml-kem")]
817pub fn hybrid_encrypt_mlkem768_x25519<R: RngCore + CryptoRng>(
818    rng: &mut R,
819    plaintext: &[u8],
820    mlkem_pub: &[u8],
821    x25519_pub: &[u8],
822) -> Result<Vec<u8>> {
823    // Encrypt with both ML-KEM and X25519
824    let mlkem_ct = mlkem768_encrypt(rng, plaintext, mlkem_pub)?;
825    let x25519_pub_bytes: [u8; 32] = x25519_pub
826        .try_into()
827        .map_err(|_| BottleError::InvalidKeyType)?;
828    let x25519_pub_key = X25519PublicKey::from(x25519_pub_bytes);
829    let x25519_ct = ecdh_encrypt_x25519(rng, plaintext, &x25519_pub_key)?;
830
831    // Combine: ML-KEM ciphertext + X25519 ciphertext
832    // Format: [mlkem_len: u32][mlkem_ct][x25519_ct]
833    let mut result = Vec::new();
834    result.extend_from_slice(&(mlkem_ct.len() as u32).to_le_bytes());
835    result.extend_from_slice(&mlkem_ct);
836    result.extend_from_slice(&x25519_ct);
837
838    Ok(result)
839}
840
841#[cfg(feature = "ml-kem")]
842/// Hybrid decryption: ML-KEM-768 + X25519.
843///
844/// Attempts to decrypt using ML-KEM first, then falls back to X25519.
845///
846/// # Arguments
847///
848/// * `ciphertext` - Encrypted data: [mlkem_len: u32][mlkem_ct][x25519_ct]
849/// * `mlkem_sec` - ML-KEM-768 secret key (2400 bytes)
850/// * `x25519_sec` - X25519 secret key (32 bytes)
851///
852/// # Returns
853///
854/// * `Ok(Vec<u8>)` - Decrypted plaintext
855pub fn hybrid_decrypt_mlkem768_x25519(
856    ciphertext: &[u8],
857    mlkem_sec: &[u8],
858    x25519_sec: &[u8; 32],
859) -> Result<Vec<u8>> {
860    if ciphertext.len() < 4 {
861        return Err(BottleError::InvalidFormat);
862    }
863
864    // Extract lengths
865    let mlkem_len = u32::from_le_bytes(ciphertext[..4].try_into().unwrap()) as usize;
866    if ciphertext.len() < 4 + mlkem_len {
867        return Err(BottleError::InvalidFormat);
868    }
869
870    let mlkem_ct = &ciphertext[4..4 + mlkem_len];
871    let x25519_ct = &ciphertext[4 + mlkem_len..];
872
873    // Try ML-KEM first, fall back to X25519
874    match mlkem768_decrypt(mlkem_ct, mlkem_sec) {
875        Ok(plaintext) => Ok(plaintext),
876        Err(_) => ecdh_decrypt_x25519(x25519_ct, x25519_sec),
877    }
878}
879
880// Helper functions
881
882/// Derive a 32-byte encryption key from a shared secret using SHA-256.
883///
884/// This function uses SHA-256 to derive a deterministic encryption key
885/// from the ECDH shared secret. The output is always 32 bytes, suitable
886/// for AES-256.
887///
888/// # Arguments
889///
890/// * `shared_secret` - The ECDH shared secret bytes
891///
892/// # Returns
893///
894/// A 32-byte array containing the derived key
895fn derive_key(shared_secret: &[u8]) -> [u8; 32] {
896    use sha2::Digest;
897    use sha2::Sha256;
898    let mut hasher = Sha256::new();
899    hasher.update(shared_secret);
900    let hash = hasher.finalize();
901    let mut key = [0u8; 32];
902    key.copy_from_slice(&hash);
903    key
904}
905
906/// Encrypt plaintext using AES-256-GCM authenticated encryption.
907///
908/// This function uses AES-256-GCM for authenticated encryption with
909/// associated data (AEAD). It generates a random 12-byte nonce and
910/// prepends it to the ciphertext.
911///
912/// # Arguments
913///
914/// * `key` - 32-byte AES-256 key
915/// * `plaintext` - The message to encrypt
916///
917/// # Returns
918///
919/// * `Ok(Vec<u8>)` - Encrypted data: nonce (12 bytes) + ciphertext + tag (16 bytes)
920/// * `Err(BottleError::Encryption)` - If encryption fails
921///
922/// # Format
923///
924/// The output format is: `[nonce (12 bytes)][ciphertext + tag (16 bytes)]`
925///
926/// # Security Note
927///
928/// The nonce is randomly generated for each encryption operation. The
929/// authentication tag is automatically appended by the GCM mode.
930fn encrypt_aes_gcm(key: &[u8; 32], plaintext: &[u8]) -> Result<Vec<u8>> {
931    use ring::aead::{self, BoundKey, NonceSequence, UnboundKey};
932    use ring::rand::{SecureRandom, SystemRandom};
933
934    let rng = SystemRandom::new();
935    let mut nonce_bytes = [0u8; 12];
936    rng.fill(&mut nonce_bytes)
937        .map_err(|_| BottleError::Encryption("RNG failure".to_string()))?;
938
939    let _nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
940
941    let unbound_key = UnboundKey::new(&aead::AES_256_GCM, key)
942        .map_err(|_| BottleError::Encryption("Key creation failed".to_string()))?;
943
944    struct SingleNonceSequence([u8; 12]);
945    impl NonceSequence for SingleNonceSequence {
946        fn advance(&mut self) -> std::result::Result<aead::Nonce, ring::error::Unspecified> {
947            Ok(aead::Nonce::assume_unique_for_key(self.0))
948        }
949    }
950
951    let mut sealing_key = aead::SealingKey::new(unbound_key, SingleNonceSequence(nonce_bytes));
952
953    // seal_in_place_append_tag encrypts the data in the buffer and appends the tag.
954    // According to ring docs, the buffer must have enough capacity for the tag.
955    // The function will extend the vector to append the tag.
956    let mut in_out = plaintext.to_vec();
957    // Reserve capacity for the tag (ring will extend the vector when appending)
958    let tag_len = sealing_key.algorithm().tag_len();
959    in_out.reserve(tag_len);
960
961    // seal_in_place_append_tag encrypts the plaintext and appends the tag
962    // It requires the vector to have enough capacity, which we've reserved
963    sealing_key
964        .seal_in_place_append_tag(aead::Aad::empty(), &mut in_out)
965        .map_err(|_| BottleError::Encryption("Encryption failed".to_string()))?;
966
967    // Prepend nonce to the result
968    let mut result = nonce_bytes.to_vec();
969    result.extend_from_slice(&in_out);
970    Ok(result)
971}
972
973/// Decrypt ciphertext using AES-256-GCM authenticated encryption.
974///
975/// This function decrypts data encrypted with `encrypt_aes_gcm`. It extracts
976/// the nonce, verifies the authentication tag, and returns the plaintext.
977///
978/// # Arguments
979///
980/// * `key` - 32-byte AES-256 key (same as used for encryption)
981/// * `ciphertext` - Encrypted data: nonce (12 bytes) + ciphertext + tag (16 bytes)
982///
983/// # Returns
984///
985/// * `Ok(Vec<u8>)` - Decrypted plaintext (with padding zeros removed if present)
986/// * `Err(BottleError::InvalidFormat)` - If ciphertext is too short
987/// * `Err(BottleError::Decryption)` - If decryption or authentication fails
988///
989/// # Security Note
990///
991/// This function automatically verifies the authentication tag. If verification
992/// fails, decryption returns an error. The function also trims trailing zeros
993/// that may have been added during encryption for tag space.
994fn decrypt_aes_gcm(key: &[u8; 32], ciphertext: &[u8]) -> Result<Vec<u8>> {
995    use ring::aead::{self, BoundKey, NonceSequence, OpeningKey, UnboundKey};
996
997    if ciphertext.len() < 12 {
998        return Err(BottleError::InvalidFormat);
999    }
1000
1001    let nonce_bytes: [u8; 12] = ciphertext[..12]
1002        .try_into()
1003        .map_err(|_| BottleError::Decryption("Invalid nonce length".to_string()))?;
1004    let _nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
1005
1006    let unbound_key = UnboundKey::new(&aead::AES_256_GCM, key)
1007        .map_err(|_| BottleError::Decryption("Key creation failed".to_string()))?;
1008
1009    struct SingleNonceSequence([u8; 12]);
1010    impl NonceSequence for SingleNonceSequence {
1011        fn advance(&mut self) -> std::result::Result<aead::Nonce, ring::error::Unspecified> {
1012            Ok(aead::Nonce::assume_unique_for_key(self.0))
1013        }
1014    }
1015
1016    let mut opening_key = OpeningKey::new(unbound_key, SingleNonceSequence(nonce_bytes));
1017
1018    // The ciphertext format is: nonce (12 bytes) + encrypted_data + tag (16 bytes)
1019    // open_in_place expects the ciphertext + tag (without the nonce)
1020    let mut in_out = ciphertext[12..].to_vec();
1021
1022    // open_in_place decrypts and verifies the tag, returning the plaintext
1023    // It expects the tag to be at the end of the buffer
1024    let plaintext = opening_key
1025        .open_in_place(aead::Aad::empty(), &mut in_out)
1026        .map_err(|_| BottleError::Decryption("Decryption failed".to_string()))?;
1027
1028    Ok(plaintext.to_vec())
1029}
1030
1031/// RSA-OAEP encryption.
1032///
1033/// This function encrypts data using RSA-OAEP (Optimal Asymmetric Encryption
1034/// Padding) with SHA-256. RSA can only encrypt small amounts of data (typically
1035/// up to key_size - 42 bytes for OAEP with SHA-256). For larger messages,
1036/// consider using RSA to encrypt a symmetric key and then encrypt the message
1037/// with that key.
1038///
1039/// # Arguments
1040///
1041/// * `rng` - A cryptographically secure random number generator
1042/// * `plaintext` - The message to encrypt (must be smaller than key_size - 42 bytes)
1043/// * `public_key` - The recipient's RSA public key
1044///
1045/// # Returns
1046///
1047/// * `Ok(Vec<u8>)` - Encrypted ciphertext
1048/// * `Err(BottleError::Encryption)` - If encryption fails
1049///
1050/// # Example
1051///
1052/// ```rust
1053/// use rust_bottle::ecdh::rsa_encrypt;
1054/// use rust_bottle::keys::RsaKey;
1055/// use rand::rngs::OsRng;
1056///
1057/// let rng = &mut OsRng;
1058/// let key = RsaKey::generate(rng, 2048).unwrap();
1059/// let plaintext = b"Small message";
1060///
1061/// let ciphertext = rsa_encrypt(rng, plaintext, key.public_key()).unwrap();
1062/// ```
1063pub fn rsa_encrypt<R: RngCore + CryptoRng>(
1064    rng: &mut R,
1065    plaintext: &[u8],
1066    public_key: &rsa::RsaPublicKey,
1067) -> Result<Vec<u8>> {
1068    use rsa::Oaep;
1069    use sha2::Sha256;
1070
1071    // RSA-OAEP with SHA-256
1072    let padding = Oaep::new::<Sha256>();
1073    public_key
1074        .encrypt(rng, padding, plaintext)
1075        .map_err(|e| BottleError::Encryption(format!("RSA encryption failed: {}", e)))
1076}
1077
1078/// RSA-OAEP decryption.
1079///
1080/// This function decrypts data encrypted with RSA-OAEP.
1081///
1082/// # Arguments
1083///
1084/// * `ciphertext` - The encrypted data
1085/// * `rsa_key` - The recipient's RSA key (as RsaKey)
1086///
1087/// # Returns
1088///
1089/// * `Ok(Vec<u8>)` - Decrypted plaintext
1090/// * `Err(BottleError::Decryption)` - If decryption fails
1091///
1092/// # Example
1093///
1094/// ```rust
1095/// use rust_bottle::ecdh::{rsa_encrypt, rsa_decrypt};
1096/// use rust_bottle::keys::RsaKey;
1097/// use rand::rngs::OsRng;
1098///
1099/// let rng = &mut OsRng;
1100/// let key = RsaKey::generate(rng, 2048).unwrap();
1101/// let plaintext = b"Small message";
1102///
1103/// let ciphertext = rsa_encrypt(rng, plaintext, key.public_key()).unwrap();
1104/// let decrypted = rsa_decrypt(&ciphertext, &key).unwrap();
1105/// assert_eq!(decrypted, plaintext);
1106/// ```
1107pub fn rsa_decrypt(ciphertext: &[u8], rsa_key: &crate::keys::RsaKey) -> Result<Vec<u8>> {
1108    rsa_key.decrypt(ciphertext)
1109}