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}