Skip to main content

bouncycastle_mlkem/
mlkem.rs

1//! This page documents advanced features of the Module Lattice Key-Encapsulation Algorithm (ML-KEM)
2//! available in this crate.
3//!
4//! # Pre-expanding the public key for repeated use
5//!
6//! Within the usual ML-KEM public key representation, the public matrix A is stored as a seed rho, which
7//! means that both the ML-KEM.encops() and ML-KEM.decaps() operations need to expand it into a full matrix
8//! before performing the matrix multiplication.
9//! We offer a version of the public and private key structs that pre-expand the public matrix for repeated use.
10//!
11//! When done as part of the keygen, expansion of the public matrix accounts for roughly 25% of the keygen time,
12//! however it accounts for roughly 35% / 60% / 80% of an encaps and 30% / 45% / 65% of a decaps
13//! for MLKEM512 / MLKEM768 / MLKEM1024.
14//!
15//! Most often, ML-KEM is used in an ephemeral mode where a key pair is generated, used for a single encaps
16//! and decaps and then discarded. In this mode, there is no performance difference to whether the
17//! public matrix A is expanded as part of keygen or as part of encaps / decaps, but it does make both
18//! the public and private key take up more space in memory, so the default ML-KEM public and private key
19//! objects defer expansion until it is needed.
20//!
21//! However, in non-ephemeral uses where many encaps or decaps operations are performed against the same
22//! key pair in quick succession, there can be substantial performance improvements to pre-computing
23//! this and holding on to a larger key object.
24//! This is accomplished via constructing a [`MLKEMPublicKeyExpanded`] or [`MLKEMPrivateKeyExpanded`] object
25//! of the appropriate parameter set from the original key, and then using this with [`MLKEM::encaps_for_expanded_key`]
26//! or [`MLKEM::decaps_with_expanded_key`].
27//! Both [`MLKEMPublicKeyExpanded`] and [`MLKEMPrivateKeyExpanded`] implement the same traits
28//! and therefore behave the same as their non-expanded counterparts in most regards.
29//!
30//! ```rust
31//! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait};
32//! use bouncycastle_mlkem::{MLKEM768PublicKeyExpanded, MLKEM768PrivateKeyExpanded};
33//! use bouncycastle_core::errors::KEMError;
34//!
35//! let (pk, sk) = MLKEM768::keygen().unwrap();
36//!
37//! // Pre-expand the public key uses more memory, but has performance
38//! // improvements if doing multiple encapsulations for the same key
39//! let pk_expanded = MLKEM768PublicKeyExpanded::from(&pk);
40//! let (ss, ct) = MLKEM768::encaps_for_expanded_key(&pk_expanded).unwrap();
41//!
42//! // Pre-expand the private key, which uses more memory, but has performance
43//! // improvements if doing multiple decapsulations with the same key
44//! let sk_expanded = MLKEM768PrivateKeyExpanded::from(&sk);
45//! let ss1 = match MLKEM768::decaps_with_expanded_key(&sk_expanded, &ct) {
46//!     Err(KEMError) => panic!("Error decapsulating"),
47//!     Ok(ss) => ss,
48//! };
49//!
50//! assert_eq!(ss, ss1);
51//! ```
52//!
53//! # decaps_from_seed
54//!
55//! This mode is intended for users who want the simplicity of storing only the seed form of the private key.
56//! This is merely a convnience function that calls [MLKEM::keygen_from_seed) before performing a decapsulation.
57//!
58//! Example usage:
59//!
60//! ```rust
61//! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait};
62//! use bouncycastle_core::traits::KEMEncapsulator;
63//! use bouncycastle_core::errors::KEMError;
64//! use bouncycastle_core::key_material::{KeyMaterial512, KeyType};
65//! use bouncycastle_hex as hex;
66//!
67//! let seed = KeyMaterial512::from_bytes_as_type(
68//!     &hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
69//!                   202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f").unwrap(),
70//!     KeyType::Seed,
71//! ).unwrap();
72//!
73//! // for this demo, it is necessary to run keygen only to get the public key
74//! let (pk, _sk) = MLKEM768::keygen_from_seed(&seed).unwrap();
75//!
76//! // Create the shared secret and ciphertext using the public key
77//! let (ss, ct) = MLKEM768::encaps(&pk).unwrap();
78//!
79//! // Recover the shared secret using the private key seed
80//! let ss1 = match MLKEM768::decaps_from_seed(&seed, &ct) {
81//!     Err(KEMError) => panic!("Error decapsulating"),
82//!     Ok(ss) => ss,
83//! };
84//!
85//! assert_eq!(ss, ss1);
86//! ```
87//!
88//! While this is currently only supported when operating from a seed-based private key, something analogous
89//! could be done that merges the sk_decode() and sign() routines when working with the standardized
90//! private key encoding (which is often called the "semi-expanded format" since the in-memory representation
91//! is still larger).
92//! Contact us if you need such a thing implemented.
93//! ## Deterministic encapsulation
94//!
95//! This section pertains to [`MLKEM::encaps_internal`] which allows to pass in the encapsulation randomness
96//! and thus obtain a deterministic encapsulation.
97//!
98//! The only good reasons for doing this are:
99//!    A) testing, if reproducible results are needed; or
100//!    B) if the user wants to use their own source of randomness, such as a hardware RNG, instead of the library's
101//!    default RNG.
102//! As a reminder, any deterministic KEM (or any encryption mechanism) fails to satisfy any security
103//! notion involving indistinguishability (e.g. IND-CPA, IND-CCA2, etc.).
104//! Any custom randomness construction will have serious consequences.
105//! Failing to use this properly, as indicated, will result in catastrophic vulnerabilities.
106//!
107//! ```rust
108//! use bouncycastle_mlkem::{MLKEM768, MLKEMTrait};
109//! use bouncycastle_core::traits::KEMDecapsulator;
110//! use bouncycastle_core::errors::KEMError;
111//! use bouncycastle_core::key_material::KeyMaterialTrait;
112//!
113//! let (pk, sk) = MLKEM768::keygen().unwrap();
114//! // note: totally insecure and for demonstration purposes only.
115//! //       The message `m` needs to be sourced from a cryptographically-secure RNG.
116//! let m: [u8; 32] = [0; 32];
117//!
118//! // Create the shared secret and ciphertext using the public key and the random message `m`
119//! let (ss, ct) = MLKEM768::encaps_internal(&pk, None, m);
120//!
121//! // Recover the shared secret using the private key//!
122//! let ss1 = match MLKEM768::decaps(&sk, &ct) {
123//!     Err(KEMError) => panic!("Error decapsulating"),
124//!     Ok(ss) => ss,
125//! };
126//!
127//! assert_eq!(ss, ss1.ref_to_bytes());
128//! ```
129
130use crate::MLKEMPublicKeyExpanded;
131use crate::aux_functions::{
132    expandA, pack_ciphertext, sample_poly_CBD, sample_vector_CBD, unpack_ciphertext_u,
133    unpack_ciphertext_v,
134};
135use crate::matrix::{Matrix, Vector};
136use crate::mlkem_keys::{
137    MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey,
138    MLKEM1024PrivateKey, MLKEM1024PublicKey,
139};
140use crate::mlkem_keys::{
141    MLKEMPrivateKeyExpanded, MLKEMPublicKeyInternalTrait, MLKEMPublicKeyTrait,
142};
143use crate::mlkem_keys::{MLKEMPrivateKeyInternalTrait, MLKEMPrivateKeyTrait};
144use crate::polynomial::Polynomial;
145use bouncycastle_core::errors::KEMError;
146use bouncycastle_core::errors::RNGError;
147use bouncycastle_core::key_material::{
148    KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations,
149};
150use bouncycastle_core::traits::{
151    Algorithm, AlgorithmOID, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF,
152};
153use bouncycastle_rng::HashDRBG_SHA512;
154use bouncycastle_sha3::{SHA3_256, SHA3_512, SHAKE256};
155use bouncycastle_utils::ct::{conditional_copy_bytes, ct_eq_bytes};
156use bouncycastle_utils::secret::Secret;
157use core::marker::PhantomData;
158/*** Constants ***/
159///
160pub const ML_KEM_512_NAME: &str = "ML-KEM-512";
161///
162pub const ML_KEM_768_NAME: &str = "ML-KEM-768";
163///
164pub const ML_KEM_1024_NAME: &str = "ML-KEM-1024";
165
166// From FIPS 203 Table 2 and Table 3
167
168// Constants that are the same for all parameter sets
169/// Length of the \[u8] holding an ML-KEM seed value.
170pub const MLKEM_SEED_LEN: usize = 64;
171/// Length of the \[u8] holding an ML-KEM encaps random value, also sometimes called the message `m`
172pub const MLKEM_RND_LEN: usize = 32;
173/// Size of in bytes of an ML-KEM shared secret key.
174pub const MLKEM_SS_LEN: usize = 32;
175pub(crate) const N: usize = 256;
176pub(crate) const q: i16 = 3329;
177pub(crate) const q_inv: i32 = 62209;
178pub(crate) const ETA2: i16 = 2;
179pub(crate) const POLY_BYTES: usize = 384;
180
181/* ML-KEM-512 params */
182
183/// Length of the \[u8] holding a ML-KEM-512 public key.
184pub const MLKEM512_PK_LEN: usize = 800;
185/// Length of the \[u8] holding a ML-KEM-512 private key.
186pub const MLKEM512_SK_LEN: usize = 1632;
187/// Length of the \[u8] holding a ML-KEM-512 ciphertext.
188pub const MLKEM512_CT_LEN: usize = 768;
189pub(crate) const MLKEM512_k: usize = 2;
190pub(crate) const MLKEM512_ETA1: i16 = 3;
191pub(crate) const MLKEM512_DU: i16 = 10;
192pub(crate) const MLKEM512_DV: i16 = 4;
193/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2
194pub(crate) const MLKEM512_LAMBDA: i16 = 128;
195
196/* ML-KEM-768 params */
197
198/// Length of the \[u8] holding a ML-KEM-768 public key.
199pub const MLKEM768_PK_LEN: usize = 1184;
200/// Length of the \[u8] holding a ML-KEM-768 private key.
201pub const MLKEM768_SK_LEN: usize = 2400;
202/// Length of the \[u8] holding a ML-KEM-768 ciphertext.
203pub const MLKEM768_CT_LEN: usize = 1088;
204pub(crate) const MLKEM768_k: usize = 3;
205pub(crate) const MLKEM768_ETA1: i16 = 2;
206pub(crate) const MLKEM768_DU: i16 = 10;
207pub(crate) const MLKEM768_DV: i16 = 4;
208/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2
209pub(crate) const MLKEM768_LAMBDA: i16 = 192;
210
211/* ML-KEM-1024 params */
212
213/// Length of the \[u8] holding a ML-KEM-1024 public key.
214pub const MLKEM1024_PK_LEN: usize = 1568;
215/// Length of the \[u8] holding a ML-KEM-1024 private key.
216pub const MLKEM1024_SK_LEN: usize = 3168;
217/// Length of the \[u8] holding a ML-KEM-1024 ciphertext.
218pub const MLKEM1024_CT_LEN: usize = 1568;
219pub(crate) const MLKEM1024_k: usize = 4;
220pub(crate) const MLKEM1024_ETA1: i16 = 2;
221pub(crate) const MLKEM1024_DU: i16 = 11;
222pub(crate) const MLKEM1024_DV: i16 = 5;
223/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2
224pub(crate) const MLKEM1024_LAMBDA: i16 = 256;
225
226// Typedefs just to make the algorithms look more like the FIPS 204 sample code.
227pub(crate) type G = SHA3_512;
228pub(crate) type H = SHA3_256;
229pub(crate) type J = SHAKE256;
230
231/*** Pub Types ***/
232
233/// The ML-KEM-512 algorithm.
234pub type MLKEM512 = MLKEM<
235    MLKEM512_PK_LEN,
236    MLKEM512_SK_LEN,
237    MLKEM512_CT_LEN,
238    MLKEM_SS_LEN,
239    MLKEM512PublicKey,
240    MLKEM512PrivateKey,
241    MLKEM512_k,
242    MLKEM512_ETA1,
243    MLKEM512_DU,
244    MLKEM512_DV,
245    MLKEM512_LAMBDA,
246>;
247
248impl Algorithm for MLKEM512 {
249    const ALG_NAME: &'static str = ML_KEM_512_NAME;
250    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
251}
252/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 }
253impl AlgorithmOID for MLKEM512 {
254    const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 1];
255    const OID_DER: &'static [u8] =
256        &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01];
257}
258
259/// The ML-KEM-768 algorithm.
260pub type MLKEM768 = MLKEM<
261    MLKEM768_PK_LEN,
262    MLKEM768_SK_LEN,
263    MLKEM768_CT_LEN,
264    MLKEM_SS_LEN,
265    MLKEM768PublicKey,
266    MLKEM768PrivateKey,
267    MLKEM768_k,
268    MLKEM768_ETA1,
269    MLKEM768_DU,
270    MLKEM768_DV,
271    MLKEM768_LAMBDA,
272>;
273
274impl Algorithm for MLKEM768 {
275    const ALG_NAME: &'static str = ML_KEM_768_NAME;
276    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
277}
278/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-768 { kems 2 }
279impl AlgorithmOID for MLKEM768 {
280    const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 2];
281    const OID_DER: &'static [u8] =
282        &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02];
283}
284
285/// The ML-KEM-1024 algorithm.
286pub type MLKEM1024 = MLKEM<
287    MLKEM1024_PK_LEN,
288    MLKEM1024_SK_LEN,
289    MLKEM1024_CT_LEN,
290    MLKEM_SS_LEN,
291    MLKEM1024PublicKey,
292    MLKEM1024PrivateKey,
293    MLKEM1024_k,
294    MLKEM1024_ETA1,
295    MLKEM1024_DU,
296    MLKEM1024_DV,
297    MLKEM1024_LAMBDA,
298>;
299
300impl Algorithm for MLKEM1024 {
301    const ALG_NAME: &'static str = ML_KEM_1024_NAME;
302    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
303}
304/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-1024 { kems 3 }
305impl AlgorithmOID for MLKEM1024 {
306    const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 3];
307    const OID_DER: &'static [u8] =
308        &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03];
309}
310
311/// The core internal implementation of the ML-KEM algorithm.
312/// This needs to be public for the compiler to be able to find it, but you shouldn't ever
313/// need to use this directly. Please use the named public types.
314pub struct MLKEM<
315    const PK_LEN: usize,
316    const SK_LEN: usize,
317    const CT_LEN: usize,
318    const SS_LEN: usize,
319    PK: MLKEMPublicKeyTrait<k, PK_LEN> + MLKEMPublicKeyInternalTrait<k, PK_LEN>,
320    SK: MLKEMPrivateKeyTrait<k, PK, SK_LEN, PK_LEN>
321        + MLKEMPrivateKeyInternalTrait<k, PK, SK_LEN, PK_LEN>,
322    const k: usize,
323    const eta: i16,
324    const du: i16,
325    const dv: i16,
326    const LAMBDA: i16,
327> {
328    _phantom: PhantomData<(PK, SK)>,
329}
330
331impl<
332    const PK_LEN: usize,
333    const SK_LEN: usize,
334    const CT_LEN: usize,
335    const SS_LEN: usize,
336    PK: MLKEMPublicKeyTrait<k, PK_LEN> + MLKEMPublicKeyInternalTrait<k, PK_LEN>,
337    SK: MLKEMPrivateKeyTrait<k, PK, SK_LEN, PK_LEN>
338        + MLKEMPrivateKeyInternalTrait<k, PK, SK_LEN, PK_LEN>,
339    const k: usize,
340    const eta1: i16,
341    const du: i16,
342    const dv: i16,
343    const LAMBDA: i16,
344> MLKEM<PK_LEN, SK_LEN, CT_LEN, SS_LEN, PK, SK, k, eta1, du, dv, LAMBDA>
345{
346    /// Algorithm 16 ML-KEM.KeyGen_internal(๐‘‘, ๐‘ง)
347    /// Uses randomness to generate an encapsulation key and a corresponding decapsulation key.
348    /// Input: randomness ๐‘‘ โˆˆ ๐”น32 .
349    /// Input: randomness ๐‘ง โˆˆ ๐”น32 .
350    /// Output: encapsulation key ek โˆˆ ๐”น384๐‘˜+32 .
351    /// Output: decapsulation key dk โˆˆ ๐”น768๐‘˜+96 .
352    pub(crate) fn keygen_internal(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError> {
353        if !(seed.key_type() == KeyType::Seed || seed.key_type() == KeyType::CryptographicRandom)
354            || seed.key_len() != 64
355        {
356            return Err(KEMError::KeyGenError(
357                "Seed must be 64 bytes and KeyType::Seed or KeyType::BytesFullEntropy.",
358            ));
359        }
360
361        if seed.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) {
362            return Err(KEMError::KeyGenError(
363                "Seed SecurityStrength must match algorithm security strength",
364            ));
365        }
366
367        // 1: (ekPKE, dkPKE) โ† K-PKE.KeyGen(๐‘‘)
368        let (pk, s_hat) = Self::pke_keygen(&seed.ref_to_bytes()[..32].try_into().unwrap());
369
370        // 2: ek โ† ekPKE โ–ท KEM encaps key is just the PKE encryption key
371        // 3: dk โ† (dkPKEโ€–ekโ€–H(ek)โ€–๐‘ง) โ–ท KEM decaps key includes PKE decryption key
372        // 4: return (ek, dk)
373        let pk_hash = pk.compute_hash();
374
375        let mut z = Secret::<[u8; 32]>::new();
376        z.copy_from_slice(&seed.ref_to_bytes()[32..]);
377
378        let mut seed_d = Secret::<[u8; 32]>::new();
379        seed_d.copy_from_slice(&seed.ref_to_bytes()[..32]);
380        Ok((pk.clone(), SK::new(s_hat, pk, pk_hash, z, Some(seed_d))))
381    }
382
383    /// Algorithm 13 K-PKE.KeyGen(๐‘‘)
384    /// Uses randomness to generate an encryption key and a corresponding decryption key.
385    /// Input: randomness ๐‘‘ โˆˆ ๐”น32 .
386    /// Output: encryption key ek_PKE โˆˆ ๐”น384๐‘˜+32.
387    /// Output: decryption key dk_PKE โˆˆ ๐”น384๐‘˜.
388    fn pke_keygen(d: &[u8; 32]) -> (PK, Secret<Vector<k>>) {
389        // 1: (๐œŒ, ๐œŽ) โ† G(๐‘‘โ€–๐‘˜)
390        //  โ–ท expand 32+1 bytes to two pseudorandom 32-byte seeds1
391        // rho: public seed
392        // sigma: noise seed
393        let (rho, mut sigma) = {
394            let mut g = G::new();
395            g.do_update(d);
396            g.do_update(&[k as u8]);
397            let mut buf = [0u8; 64];
398            let bytes_written = g.do_final_out(&mut buf);
399            debug_assert_eq!(bytes_written, 64);
400
401            (buf[..32].try_into().unwrap(), buf[32..64].try_into().unwrap())
402        };
403
404        // 2: ๐‘ โ† 0
405        //  Note: in the definition of PRF_eta on page 18, it's said to be one byte.
406        //  since the number of loops here is static, it is possible to hard-code the N values
407        //  rather than using a counter
408
409        // 8: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++)
410        //  โ–ท generate ๐ฌ โˆˆ (โ„ค256)^k
411        // 9: ๐ฌ[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘ ))
412        //   โ–ท ๐ฌ[๐‘–] โˆˆ โ„ค256 sampled from CBD
413        // 10: ๐‘ โ† ๐‘ + 1
414        // Note: here n = 0
415        let s_hat: Secret<Vector<k>> = {
416            let mut s: Secret<Vector<k>> = Secret::new();
417            *s = sample_vector_CBD::<k, eta1>(&sigma, 0);
418
419            // 16: ๐ฌ_hat โ† NTT(๐ฌ)ฬ‚
420            s.ntt();
421            s.reduce();
422            s
423        };
424
425        // first half of
426        // 18: ๐ญ_hat โ† ๐€_hat โˆ˜ ๐ฌ_hat + ๐ž_hat
427        let mut t_hat = {
428            // 3: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++)
429            //  โ–ท generate matrix A_hat โˆˆ (โ„ค256)^k x k
430            let A_hat = expandA(&rho);
431
432            A_hat.matrix_vector_ntt::<false>(&s_hat)
433        };
434
435        // second half of
436        // 18: ๐ญ_hat โ† ๐€_hat โˆ˜ ๐ฌ_hat + ๐ž_hat
437        {
438            // 12: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++)
439            //  โ–ท generate ๐ž โˆˆ (โ„ค256)^k
440            // 13: ๐ž[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘ ))
441            //   โ–ท ๐ž[๐‘–] โˆˆ โ„ค256 sampled from CBD
442            // 14: ๐‘ โ† ๐‘ + 1
443            // Note: here n = k
444            let mut e = sample_vector_CBD::<k, eta1>(&sigma, k as u8);
445
446            e.ntt(); // technically now e_hat
447            e.reduce();
448            t_hat.add_vector_ntt(&e);
449        }
450
451        // Clear the secret data before returning memory to the OS
452        sigma.fill(0u8);
453
454        // 19: ekPKE โ† ByteEncode12(๐ญ)โ€–๐œŒ โ–ท run ByteEncode12 ๐‘˜ times, then append ๐€-seed
455        // 20: dkPKE โ† ByteEncode12(๐ฌ)ฬ‚ โ–ท run ByteEncode12 ๐‘˜ times
456        // Note: The encoding is skipped at this stage and left expanded for future efficiency when it's used.
457        // 21: return (ekPKE, dkPKE)
458        (PK::new(t_hat, rho), s_hat)
459    }
460
461    /// Algorithm 14 K-PKE.Encrypt(ekPKE, ๐‘š, ๐‘Ÿ)
462    /// Uses the encryption key to encrypt a plaintext message using the randomness ๐‘Ÿ.
463    /// Input: encryption key ekPKE โˆˆ ๐”น384๐‘˜+32 .
464    /// Input: message ๐‘š โˆˆ ๐”น32 .
465    /// Input: randomness ๐‘Ÿ โˆˆ ๐”น32 .
466    /// Output: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
467    fn pke_encrypt(ek: &PK, A_hat: &Matrix<k, k>, m: [u8; 32], r: &[u8; 32]) -> [u8; CT_LEN] {
468        // 1: ๐‘ โ† 0
469        //  since the number of loops here is static, the N values can be hard-coded rather than using a counter
470
471        // 2: ๐ญ โ† ByteDecode12(ekPKE[0 โˆถ 384๐‘˜])
472        // 3: ๐œŒ โ† ekPKE[384๐‘˜ โˆถ 384๐‘˜ + 32]
473        // not necessary here because ek is already decoded
474
475        // 4: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++)
476        //   โ–ท re-generate matrix ๐€ โˆˆ (โ„ค256_๐‘ž )๐‘˜ร—๐‘˜ sampled in Alg. 13
477        // An optimization is done where the user can pre-expand A_hat within the
478        // public key object for faster repeated encapsulations against this public key.
479
480        // 9: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++)
481        //  โ–ท generate ๐ฒ โˆˆ (โ„ค256_๐‘ž)k
482        // 10: ๐ฒ[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐‘Ÿ, ๐‘))
483        //   โ–ท ๐ฒ[๐‘–] โˆˆ โ„ค256 sampled from CBD
484        // 11: ๐‘ โ† ๐‘ + 1
485        // Note: here n = 0
486        let y_hat = {
487            let mut y = sample_vector_CBD::<k, eta1>(&r, 0);
488
489            // 18: ๐ฒ_hat โ† NTT(๐ฒ)
490            y.ntt();
491
492            y
493        };
494
495        // 19: ๐ฎ โ† NTTโˆ’1(๐€_hat^โŠบ โˆ˜ ๐ฒ_hat) + ๐ž
496        let mut u = A_hat.matrix_vector_ntt::<true>(&y_hat);
497        u.inv_ntt();
498        {
499            // 12: for (๐‘– โ† 0; ๐‘– < ๐‘˜; ๐‘–++)
500            //  โ–ท generate ๐ž โˆˆ (โ„ค256_๐‘ž)k
501            // 13: ๐ž[๐‘–] โ† SamplePolyCBD๐œ‚1(PRF๐œ‚1 (๐œŽ, ๐‘))
502            //  โ–ท ๐ž[๐‘–] โˆˆ โ„ค256 sampled from CBD๐‘ž
503            // 14: ๐‘ โ† ๐‘ + 1
504            // note: here n = k
505            let e1 = sample_vector_CBD::<k, ETA2>(&r, k as u8);
506
507            u.add_vector_ntt(&e1);
508        }
509        u.reduce();
510
511        // 20: ๐œ‡ โ† Decompress1(ByteDecode1(๐‘š))
512        // 21: ๐‘ฃ โ† NTTโˆ’1(๐ญ_hat^T โˆ˜ ๐ฒ_hat) + ๐‘’2 + ๐œ‡
513        //  โ–ท encode plaintext ๐‘š into polynomial ๐‘ฃ
514        let mut v = ek.t_hat().dot_product(&y_hat);
515        v.inv_ntt();
516
517        // 17: ๐‘’2 โ† SamplePolyCBD๐œ‚2(PRF๐œ‚2 (๐‘Ÿ, ๐‘))
518        //  โ–ท sample ๐‘’2 โˆˆ โ„ค256 from CBD
519        // note: here n = 2k
520        let e2 = sample_poly_CBD::<ETA2>(&r, 2 * k as u8);
521        v.add(&e2);
522
523        let mu = Polynomial::from_msg(m);
524        v.add(&mu);
525
526        v.poly_reduce();
527
528        pack_ciphertext::<k, CT_LEN, du, dv>(&u, &v)
529    }
530
531    /// Algorithm 17 ML-KEM.Encaps_internal(ek, ๐‘š)
532    /// Uses the encapsulation key and randomness to generate a key and an associated ciphertext.
533    /// Input: encapsulation key ek โˆˆ ๐”น384๐‘˜+32 .
534    /// Input: randomness ๐‘š โˆˆ ๐”น32 .
535    /// Output: shared secret key ๐พ โˆˆ ๐”น32 .
536    /// Output: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
537    ///
538    /// This function also takes an Option for the public matrix `A`.
539    /// If `A` is not known, `None` may be provided.
540    /// This is to enable performance
541    /// optimizations when the same public key is used for multiple encapsulations and the intermediate
542    /// value called the public matrix A_hat can be re-used for multiple encapsulations.
543    /// A_hat can be obtained from [`MLKEMPublicKeyTrait::A_hat`].
544    /// Alternatively, a [`MLKEMPublicKeyExpanded`] with [`MLKEM::encaps_for_expanded_key`] can be used.
545    /// If `None` is specified, the function will compute A_hat internally and everything will work fine.
546    ///
547    /// Unlike the more public function exposed by [`KEMEncapsulator::encaps`], this returns the shared secret as raw bytes
548    /// instead of wrapped in an appropriately-set [`KeyMaterialTrait`].
549    /// Proper handling is up to the user's own judgement.
550    ///
551    /// Note: this is an internal function that allows the caller to specify the encapsulation
552    /// randomness (which is the message `m` to be encrypted by the underlying PKE scheme).
553    /// This function should not be used directly unless there is a good reason to do so.
554    /// [`KEMEncapsulator::encaps`] should be used in 99.9% of cases.
555    /// The reason this is exposed publicly is:
556    ///     A) for unit testing that requires access to the deterministically reproducible function, and
557    ///     B) for operational environments that wish to provide randomness from their own source instead
558    ///        of the built-in RNG in bc-rust.
559    /// As a reminder, any deterministic KEM (or any encryption mechanism) fails to satisfy any security
560    /// notion involving indistinguishability (e.g. IND-CPA, IND-CCA2, etc.).
561    /// Failing to use this properly will result in catastrophic vulnerabilities.
562    /// Please don't do it.
563    pub fn encaps_internal(
564        ek: &PK,
565        A_hat: Option<&Matrix<k, k>>,
566        m: [u8; 32],
567    ) -> ([u8; 32], [u8; CT_LEN]) {
568        debug_assert_eq!(CT_LEN, 32 * ((du as usize) * k + (dv as usize)));
569
570        // 1: (๐พ, ๐‘Ÿ) โ† G(๐‘šโ€–H(ek))
571        //  โ–ท derive shared secret key ๐พ and randomness ๐‘Ÿ
572        let K: [u8; MLKEM_SS_LEN];
573        let r: [u8; 32];
574        (K, r) = {
575            let mut g = G::new();
576            g.do_update(&m);
577            g.do_update(&ek.compute_hash());
578            let mut buf = [0u8; 64];
579            let bytes_written = g.do_final_out(&mut buf);
580            debug_assert_eq!(bytes_written, 64);
581
582            (buf[..32].try_into().unwrap(), buf[32..64].try_into().unwrap())
583        };
584
585        // 2: ๐‘ โ† K-PKE.Encrypt(ek, ๐‘š, ๐‘Ÿ)
586        //  โ–ท encrypt ๐‘š using K-PKE with randomness ๐‘Ÿ
587        // deviation from FIPS:
588        //  To allow for pre-computing A_hat for multiple encapsulations, the code either takes
589        // A_hat passed in, or computes it fresh.
590        let ct = match A_hat {
591            Some(A_hat) => Self::pke_encrypt(ek, A_hat, m, &r),
592            None => Self::pke_encrypt(ek, &ek.A_hat(), m, &r),
593        };
594
595        (K, ct)
596    }
597
598    /// Algorithm 15 K-PKE.Decrypt(dkPKE, ๐‘)
599    /// Uses the decryption key to decrypt a ciphertext.
600    /// Input: decryption key dkPKE  โˆˆ ๐”น384๐‘˜.
601    /// Input: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
602    /// Output: message ๐‘š โˆˆ ๐”น32 .
603    fn pke_decrypt(dk: &SK, ct: [u8; CT_LEN]) -> [u8; 32] {
604        // 1: ๐‘1 โ† ๐‘[0 โˆถ 32๐‘‘๐‘ข๐‘˜]
605        // 2: ๐‘2 โ† ๐‘[32๐‘‘๐‘ข๐‘˜ โˆถ 32(๐‘‘๐‘ข๐‘˜ + ๐‘‘๐‘ฃ)]
606        // 3: ๐ฎโ€ฒ โ† Decompress_๐‘‘๐‘ข(ByteDecode_๐‘‘๐‘ข(๐‘1))
607        // 4: ๐‘ฃโ€ฒ โ† Decompress_๐‘‘๐‘ฃ(ByteDecode_๐‘‘๐‘ฃ(๐‘2))
608        let v1 = {
609            let mut u_prime = unpack_ciphertext_u::<k, CT_LEN, du, dv>(&ct);
610
611            // 5: ๐ฌ_hat โ† ByteDecode12(dkPKE)
612            //   Unnecessary here because dk is already decoded
613
614            // 6: ๐‘ค โ† ๐‘ฃโ€ฒ โˆ’ NTTโˆ’1(๐ฌ_hat^T โˆ˜ NTT(๐ฎโ€ฒ))
615            u_prime.ntt();
616            let mut v1 = dk.s_hat().dot_product(&u_prime);
617            v1.inv_ntt();
618
619            v1
620        };
621
622        let w = {
623            let mut v_prime = unpack_ciphertext_v::<k, CT_LEN, du, dv>(&ct);
624
625            v_prime.sub(&v1);
626            v_prime.poly_reduce();
627            v_prime // rename to w
628        };
629
630        // 7: ๐‘š โ† ByteEncode1(Compress1(๐‘ค))
631        //   โ–ท decode plaintext ๐‘š from polynomial ๐‘ค
632        w.to_msg()
633    }
634
635    /// Algorithm 18 ML-KEM.Decaps_internal(dk, ๐‘)
636    /// Uses the decapsulation key to produce a shared secret key from a ciphertext.
637    /// Input: decapsulation key dk โˆˆ ๐”น768๐‘˜+96 .
638    /// Input: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
639    /// Output: shared secret key ๐พ โˆˆ ๐”น32 .
640    fn decaps_internal(
641        dk: &SK,
642        A_hat: Option<&Matrix<k, k>>,
643        c: [u8; CT_LEN],
644    ) -> [u8; MLKEM_SS_LEN] {
645        // Structured to mirror the FIPS as closely as possible, with unnamed scopes
646        // used to limit the number of live stack variables at any given time.
647
648        // 1: dkPKE โ† dk[0 โˆถ 384๐‘˜] โ–ท extract (from KEM decaps key) the PKE decryption key
649        // 2: ekPKE โ† dk[384๐‘˜ โˆถ 768๐‘˜ + 32] โ–ท extract PKE encryption key
650        // 3: โ„Ž โ† dk[768๐‘˜ + 32 โˆถ 768๐‘˜ + 64] โ–ท extract hash of PKE encryption key
651        // 4: ๐‘ง โ† dk[768๐‘˜ + 64 โˆถ 768๐‘˜ + 96] โ–ท extract implicit rejection value
652        // Nothing to do since dk is already decoded.
653
654        // 5: ๐‘šโ€ฒ โ† K-PKE.Decrypt(dkPKE, ๐‘)
655        let m_prime = Self::pke_decrypt(&dk, c);
656
657        // Compute the trial shared secret key
658        // 6: (๐พโ€ฒ, ๐‘Ÿโ€ฒ) โ† G(๐‘šโ€ฒโ€–โ„Ž)ฬ„
659        let K_prime: [u8; MLKEM_SS_LEN];
660        let r_prime: [u8; 32];
661        (K_prime, r_prime) = {
662            let mut g = G::new();
663            g.do_update(&m_prime);
664            g.do_update(&dk.pk().compute_hash());
665            let mut buf = [0u8; 64];
666            let bytes_written = g.do_final_out(&mut buf);
667            debug_assert_eq!(bytes_written, 64);
668
669            (buf[..32].try_into().unwrap(), buf[32..64].try_into().unwrap())
670        };
671
672        // 7: ๐พ_bar โ† J(๐‘งโ€–๐‘)
673        //   Compute the rejection sampling key.
674        //   Note to future optimizers: this needs to be computed outside of the conditional at line 9 below.
675        //   This is because if the computation is conditional on the Fujisaki-Okamoto check failing, then
676        //   it will result in a timing difference between success and failure.
677
678        let K_bar: [u8; MLKEM_SS_LEN];
679        K_bar = {
680            let mut j = J::new();
681            j.absorb(dk.z().as_ref()).expect("absorb before squeeze is infallible");
682            j.absorb(&c).expect("absorb before squeeze is infallible");
683            let mut buf = [0u8; MLKEM_SS_LEN];
684            let bytes_written = j.squeeze_out(&mut buf);
685            debug_assert_eq!(bytes_written, MLKEM_SS_LEN);
686
687            buf
688        };
689
690        // 8: ๐‘โ€ฒ โ† K-PKE.Encrypt(ekPKE, ๐‘šโ€ฒ, ๐‘Ÿโ€ฒ)
691        //   โ–ท re-encrypt using the derived randomness ๐‘Ÿโ€ฒ
692        // deviation from FIPS:
693        // To allow for pre-computing A_hat for multiple encapsulations, we will either take
694        // A_hat passed in, or compute it fresh.
695        let c_prime = match A_hat {
696            Some(A_hat) => Self::pke_encrypt(dk.pk(), A_hat, m_prime, &r_prime),
697            None => Self::pke_encrypt(dk.pk(), &dk.pk().A_hat(), m_prime, &r_prime),
698        };
699
700        // 9: if ๐‘ โ‰  ๐‘โ€ฒ then
701        // 10: ๐พโ€ฒ โ† ๐พ_bar
702        //  โ–ท if ciphertexts do not match, โ€œimplicitly reject"
703        let mut K_out = [0u8; MLKEM_SS_LEN];
704        conditional_copy_bytes(&K_prime, &K_bar, &mut K_out, ct_eq_bytes(&c, &c_prime));
705
706        K_out
707    }
708
709    /// Alternative initialization of the streaming signer where you have your private key
710    /// as a seed and you want to delay its expansion as late as possible for memory-usage reasons.
711    // TODO: Check whether a fully-stitched-together decaps-from-seed implementation is beneficial
712    pub fn decaps_from_seed(
713        seed: &KeyMaterial<64>,
714        ct: &[u8],
715    ) -> Result<KeyMaterial<SS_LEN>, KEMError> {
716        let (_pk, sk) = Self::keygen_from_seed(seed)?;
717
718        Self::decaps(&sk, ct)
719    }
720}
721
722impl<
723    const PK_LEN: usize,
724    const SK_LEN: usize,
725    const CT_LEN: usize,
726    const SS_LEN: usize,
727    PK: MLKEMPublicKeyTrait<k, PK_LEN> + MLKEMPublicKeyInternalTrait<k, PK_LEN>,
728    SK: MLKEMPrivateKeyTrait<k, PK, SK_LEN, PK_LEN>
729        + MLKEMPrivateKeyInternalTrait<k, PK, SK_LEN, PK_LEN>,
730    const k: usize,
731    const eta1: i16,
732    const du: i16,
733    const dv: i16,
734    const LAMBDA: i16,
735> MLKEMTrait<PK_LEN, SK_LEN, CT_LEN, SS_LEN, PK, SK, k, eta1, du, dv, LAMBDA>
736    for MLKEM<PK_LEN, SK_LEN, CT_LEN, SS_LEN, PK, SK, k, eta1, du, dv, LAMBDA>
737{
738    /// Imports a secret key from a seed.
739    fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError> {
740        Self::keygen_internal(seed)
741    }
742    /// Imports a secret key from both a seed and an encoded_sk.
743    ///
744    /// This is a convenience function to expand the key from seed and compare it against
745    /// the provided `encoded_sk` using a constant-time equality check.
746    /// If everything checks out, the secret key is returned fully populated with pk and seed.
747    /// If the provided key and derived key don't match, an error is returned.
748    fn keygen_from_seed_and_encoded(
749        seed: &KeyMaterial<64>,
750        encoded_sk: &[u8; SK_LEN],
751    ) -> Result<(PK, SK), KEMError> {
752        let (pk, sk) = Self::keygen_internal(seed)?;
753
754        let sk_from_bytes = SK::sk_decode(encoded_sk)?;
755
756        // MLKEMPrivateKey impls PartialEq with a constant-time equality check.
757        if sk != sk_from_bytes {
758            return Err(KEMError::KeyGenError("Encoded key does not match generated key"));
759        }
760
761        Ok((pk, sk))
762    }
763    /// Given a public key and a secret key, check that the public key matches the secret key.
764    /// This is a sanity check that the public key was generated correctly from the secret key.
765    ///
766    /// At the current time, this is only possible if `sk` either contains a public key (in which case
767    /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
768    /// (in which case a keygen_from_seed is run and then the pk's compared).
769    ///
770    /// Returns either `()` or [`KEMError::ConsistencyCheckFailed`].
771    fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), KEMError> {
772        let derived_pk = sk.pk();
773        if derived_pk.compute_hash() == pk.compute_hash() {
774            Ok(())
775        } else {
776            Err(KEMError::ConsistencyCheckFailed(""))
777        }
778    }
779
780    fn encaps_for_expanded_key(
781        pk: &MLKEMPublicKeyExpanded<k, PK, PK_LEN>,
782    ) -> Result<(KeyMaterial<SS_LEN>, [u8; CT_LEN]), KEMError> {
783        let mut os_rng = HashDRBG_SHA512::new_from_os();
784        Self::encaps_for_expanded_key_rng(pk, &mut os_rng)
785    }
786
787    fn encaps_for_expanded_key_rng(
788        pk: &MLKEMPublicKeyExpanded<k, PK, PK_LEN>,
789        rng: &mut dyn RNG,
790    ) -> Result<(KeyMaterial<SS_LEN>, [u8; CT_LEN]), KEMError> {
791        // Source the random message m from the provided RNG
792        if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) {
793            return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?;
794        }
795        let mut m = [0u8; 32];
796        rng.next_bytes_out(&mut m)?;
797
798        let (ss, ct) = Self::encaps_internal(&pk.ek, Some(&pk.A_hat), m);
799
800        let mut key = KeyMaterial::<SS_LEN>::from_bytes_as_type(&ss, KeyType::CryptographicRandom)?;
801        do_hazardous_operations(&mut key, |key| {
802            key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize))
803        })?;
804
805        Ok((key, ct))
806    }
807
808    fn decaps_with_expanded_key(
809        sk: &MLKEMPrivateKeyExpanded<k, PK, SK, SK_LEN, PK_LEN>,
810        ct: &[u8],
811    ) -> Result<KeyMaterial<SS_LEN>, KEMError> {
812        /* decapsulation inputs checks described on FIPS 203 section 7.3 */
813        // 1. (Ciphertext type check) If ๐‘ is not a byte array of length 32(๐‘‘๐‘ข ๐‘˜ + ๐‘‘๐‘ฃ) for the values of ๐‘‘๐‘ข,
814        //     ๐‘‘๐‘ฃ, and ๐‘˜ specified by the relevant parameter set, then input checking has failed.
815        debug_assert_eq!(CT_LEN, 32 * ((du as usize) * k + (dv as usize)));
816
817        if ct.len() != CT_LEN {
818            return Err(KEMError::LengthError("Ciphertext has the incorrect length"));
819        }
820
821        // 2. (Decapsulation key type check) If dk is not a byte array of length 768๐‘˜ + 96 for the value of
822        //     ๐‘˜ specified by the relevant parameter set, then input checking has failed.
823        // This is handled at the time of loading dk into MLKEMPrivateKey
824
825        // 3. Check that the H(ek) stored in the private key matches the ek also stored in the private key.
826        // Again, this is handled by the MLKEMPrivateKey trait.
827
828        /* the actual decaps operation */
829        let K = Self::decaps_internal(&sk.dk, Some(&sk.A_hat), ct.try_into().unwrap());
830
831        let mut key = KeyMaterial::<SS_LEN>::from_bytes_as_type(&K, KeyType::CryptographicRandom)?;
832        do_hazardous_operations(&mut key, |key| {
833            key.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize))
834        })?;
835
836        Ok(key)
837    }
838}
839
840/// Trait for all three of the ML-DSA algorithm variants.
841pub trait MLKEMTrait<
842    const PK_LEN: usize,
843    const SK_LEN: usize,
844    const CT_LEN: usize,
845    const SS_LEN: usize,
846    PK: MLKEMPublicKeyTrait<k, PK_LEN> + MLKEMPublicKeyInternalTrait<k, PK_LEN>,
847    SK: MLKEMPrivateKeyTrait<k, PK, SK_LEN, PK_LEN>
848        + MLKEMPrivateKeyInternalTrait<k, PK, SK_LEN, PK_LEN>,
849    const k: usize,
850    const eta: i16,
851    const du: i16,
852    const dv: i16,
853    const LAMBDA: i16,
854>: Sized
855{
856    /// Generates a fresh key pair.
857    fn keygen() -> Result<(PK, SK), KEMError> {
858        let mut os_rng = HashDRBG_SHA512::new_from_os();
859        Self::keygen_from_rng(&mut os_rng)
860    }
861    /// Run a keygen using the provided RNG implementation.
862    // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG.
863    fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), KEMError> {
864        // Source the seed from the provided RNG
865        if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) {
866            return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?;
867        }
868        let mut seed = KeyMaterial::<64>::new();
869        rng.fill_keymaterial_out(&mut seed)?;
870        Self::keygen_from_seed(&seed)
871    }
872    /// Imports a secret key from a seed.
873    fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError>;
874    /// Imports a secret key from both a seed and an encoded_sk.
875    ///
876    /// This is a convenience function to expand the key from seed and compare it against
877    /// the provided `encoded_sk` using a constant-time equality check.
878    /// If everything checks out, the secret key is returned fully populated with pk and seed.
879    /// If the provided key and derived key don't match, an error is returned.
880    fn keygen_from_seed_and_encoded(
881        seed: &KeyMaterial<64>,
882        encoded_sk: &[u8; SK_LEN],
883    ) -> Result<(PK, SK), KEMError>;
884    /// Given a public key and a secret key, check that the public key matches the secret key.
885    /// This is a sanity check that the public key was generated correctly from the secret key.
886    ///
887    /// At the current time, this is only possible if `sk` either contains a public key (in which case
888    /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
889    /// (in which case a keygen_from_seed is run and then the pk's compared).
890    ///
891    /// Returns either `()` or [`KEMError::ConsistencyCheckFailed`].
892    fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), KEMError>;
893
894    /// Same as [`KEMEncapsulator::encaps`], but acts on an [`MLKEMPublicKeyExpanded`].
895    fn encaps_for_expanded_key(
896        pk: &MLKEMPublicKeyExpanded<k, PK, PK_LEN>,
897    ) -> Result<(KeyMaterial<SS_LEN>, [u8; CT_LEN]), KEMError>;
898
899    /// Same as [`KEMEncapsulator::encaps`], but acts on an [`MLKEMPublicKeyExpanded`] and uses a provided RNG.
900    fn encaps_for_expanded_key_rng(
901        pk: &MLKEMPublicKeyExpanded<k, PK, PK_LEN>,
902        rng: &mut dyn RNG,
903    ) -> Result<(KeyMaterial<SS_LEN>, [u8; CT_LEN]), KEMError>;
904
905    /// Same as [`KEMDecapsulator::decaps`], but acts on an [`MLKEMPrivateKeyExpanded`].
906    fn decaps_with_expanded_key(
907        sk: &MLKEMPrivateKeyExpanded<k, PK, SK, SK_LEN, PK_LEN>,
908        ct: &[u8],
909    ) -> Result<KeyMaterial<SS_LEN>, KEMError>;
910}
911
912impl<
913    const PK_LEN: usize,
914    const SK_LEN: usize,
915    const CT_LEN: usize,
916    const SS_LEN: usize,
917    PK: MLKEMPublicKeyTrait<k, PK_LEN> + MLKEMPublicKeyInternalTrait<k, PK_LEN>,
918    SK: MLKEMPrivateKeyTrait<k, PK, SK_LEN, PK_LEN>
919        + MLKEMPrivateKeyInternalTrait<k, PK, SK_LEN, PK_LEN>,
920    const k: usize,
921    const eta: i16,
922    const du: i16,
923    const dv: i16,
924    const LAMBDA: i16,
925> KEMEncapsulator<PK, PK_LEN, CT_LEN, SS_LEN>
926    for MLKEM<PK_LEN, SK_LEN, CT_LEN, SS_LEN, PK, SK, k, eta, du, dv, LAMBDA>
927{
928    /// Performs an encapsulation against the given public key, using the library's default internal RNG.
929    /// Returns (shared_secret_key, ciphertext)
930    /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to
931    /// the security level of the ML-KEM parameter set.
932    ///
933    /// Algorithm 20 ML-KEM.Encaps(ek)
934    /// Uses the encapsulation key to generate a shared secret key and an associated ciphertext.
935    /// Checked input: encapsulation key ek โˆˆ ๐”น384๐‘˜+32 .
936    /// Output: shared secret key ๐พ โˆˆ ๐”น32 .
937    /// Output: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
938    fn encaps(pk: &PK) -> Result<(KeyMaterial<SS_LEN>, [u8; CT_LEN]), KEMError> {
939        let mut os_rng = HashDRBG_SHA512::new_from_os();
940        Self::encaps_rng(pk, &mut os_rng)
941    }
942
943    fn encaps_rng(
944        pk: &PK,
945        rng: &mut dyn RNG,
946    ) -> Result<(KeyMaterial<SS_LEN>, [u8; CT_LEN]), KEMError> {
947        Self::encaps_for_expanded_key_rng(&MLKEMPublicKeyExpanded::<k, PK, PK_LEN>::from(pk), rng)
948    }
949}
950
951impl<
952    const PK_LEN: usize,
953    const SK_LEN: usize,
954    const CT_LEN: usize,
955    const SS_LEN: usize,
956    PK: MLKEMPublicKeyTrait<k, PK_LEN> + MLKEMPublicKeyInternalTrait<k, PK_LEN>,
957    SK: MLKEMPrivateKeyTrait<k, PK, SK_LEN, PK_LEN>
958        + MLKEMPrivateKeyInternalTrait<k, PK, SK_LEN, PK_LEN>,
959    const k: usize,
960    const eta: i16,
961    const du: i16,
962    const dv: i16,
963    const LAMBDA: i16,
964> KEMDecapsulator<SK, SK_LEN, CT_LEN, SS_LEN>
965    for MLKEM<PK_LEN, SK_LEN, CT_LEN, SS_LEN, PK, SK, k, eta, du, dv, LAMBDA>
966{
967    /// Performs a decapsulation of the given ciphertext.
968    /// Returns the shared secret key.
969    /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to
970    /// the security level of the ML-KEM parameter set.
971    /// As ML-KEM is an implicitly-rejecting KEM, this returns an error only if the ciphertext is invalid (ie the wrong length)..
972    fn decaps(sk: &SK, ct: &[u8]) -> Result<KeyMaterial<SS_LEN>, KEMError> {
973        Self::decaps_with_expanded_key(
974            &MLKEMPrivateKeyExpanded::<k, PK, SK, SK_LEN, PK_LEN>::from(sk),
975            ct,
976        )
977    }
978}