Skip to main content

bouncycastle_mlkem_lowmemory/
mlkem.rs

1//! There are no advanced features in this low memory crate that are not already documented in the standard \[bouncycastle_mlkem] crate.
2
3use crate::aux_functions::sample_poly_CBD;
4use crate::low_memory_helpers::{
5    compress_u_row, compute_A_hat_dot_y_hat, compute_t_hat_dot_y_hat_row, unpack_ciphertext_u_row,
6    unpack_ciphertext_v, unpack_t_hat_row,
7};
8use crate::mlkem_keys::{
9    MLKEM512PrivateKey, MLKEM512PublicKey, MLKEM768PrivateKey, MLKEM768PublicKey,
10    MLKEM1024PrivateKey, MLKEM1024PublicKey,
11};
12use crate::mlkem_keys::{MLKEMPrivateKeyInternalTrait, MLKEMPrivateKeyTrait};
13use crate::mlkem_keys::{MLKEMPublicKeyInternalTrait, MLKEMPublicKeyTrait};
14use crate::polynomial::Polynomial;
15use bouncycastle_core::errors::{KEMError, RNGError};
16use bouncycastle_core::key_material::{
17    KeyMaterial, KeyMaterialTrait, KeyType, do_hazardous_operations,
18};
19use bouncycastle_core::traits::{
20    Algorithm, AlgorithmOID, Hash, KEMDecapsulator, KEMEncapsulator, RNG, SecurityStrength, XOF,
21};
22use bouncycastle_rng::HashDRBG_SHA512;
23use bouncycastle_sha3::{SHA3_256, SHA3_512, SHAKE256};
24use bouncycastle_utils::ct::{conditional_copy_bytes, ct_eq_bytes};
25use bouncycastle_utils::secret::Secret;
26use core::marker::PhantomData;
27/*** Constants ***/
28
29///
30pub const ML_KEM_512_NAME: &str = "ML-KEM-512";
31///
32pub const ML_KEM_768_NAME: &str = "ML-KEM-768";
33///
34pub const ML_KEM_1024_NAME: &str = "ML-KEM-1024";
35
36// From FIPS 203 Table 2 and Table 3
37
38// Constants that are the same for all parameter sets
39/// Length of the \[u8] holding an ML-KEM seed value.
40pub const MLKEM_SEED_LEN: usize = 64;
41/// Length of the \[u8] holding an ML-KEM encaps random value, also sometimes called the message `m`
42pub const MLKEM_RND_LEN: usize = 32;
43/// Size of in bytes of an ML-KEM shared secret key.
44pub const MLKEM_SS_LEN: usize = 32;
45pub(crate) const N: usize = 256;
46pub(crate) const q: i16 = 3329;
47pub(crate) const q_inv: i32 = 62209;
48pub(crate) const ETA2: i16 = 2;
49pub(crate) const POLY_BYTES: usize = 384;
50
51/* ML-KEM-512 params */
52
53/// Length of the \[u8] holding a ML-KEM-512 public key.
54pub const MLKEM512_PK_LEN: usize = 800;
55/// Length of the \[u8] holding a ML-KEM-512 seed-based private key.
56pub const MLKEM512_SK_LEN: usize = MLKEM_SEED_LEN;
57/// Length of the \[u8] holding a full ML-KEM-512 private key in the NIST encoding.
58pub const MLKEM512_FULL_SK_LEN: usize = 1632;
59/// Length of the \[u8] holding a ML-KEM-512 ciphertext.
60pub const MLKEM512_CT_LEN: usize = 768;
61pub(crate) const MLKEM512_k: usize = 2;
62pub(crate) const MLKEM512_ETA1: i16 = 3;
63pub(crate) const MLKEM512_DU: i16 = 10;
64pub(crate) const MLKEM512_DV: i16 = 4;
65/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2
66pub(crate) const MLKEM512_LAMBDA: i16 = 128;
67
68// internal derived values
69pub(crate) const MLKEM512_T_PACKED_LEN: usize = 12 * MLKEM512_k * 32;
70
71/* ML-KEM-768 params */
72
73/// Length of the \[u8] holding a ML-KEM-768 public key.
74pub const MLKEM768_PK_LEN: usize = 1184;
75/// Length of the \[u8] holding a ML-KEM-768 seed-based private key.
76pub const MLKEM768_SK_LEN: usize = MLKEM_SEED_LEN;
77/// Length of the \[u8] holding a full ML-KEM-768 private key in the NIST encoding.
78pub const MLKEM768_FULL_SK_LEN: usize = 2400;
79/// Length of the \[u8] holding a ML-KEM-768 ciphertext.
80pub const MLKEM768_CT_LEN: usize = 1088;
81pub(crate) const MLKEM768_k: usize = 3;
82pub(crate) const MLKEM768_ETA1: i16 = 2;
83pub(crate) const MLKEM768_DU: i16 = 10;
84pub(crate) const MLKEM768_DV: i16 = 4;
85/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2
86pub(crate) const MLKEM768_LAMBDA: i16 = 192;
87
88// internal derived values
89pub(crate) const MLKEM768_T_PACKED_LEN: usize = 12 * MLKEM768_k * 32;
90
91/* ML-KEM-1024 params */
92
93/// Length of the \[u8] holding a ML-KEM-1024 public key.
94pub const MLKEM1024_PK_LEN: usize = 1568;
95/// Length of the \[u8] holding a ML-KEM-512 seed-based private key.
96pub const MLKEM1024_SK_LEN: usize = MLKEM_SEED_LEN;
97/// Length of the \[u8] holding a full ML-KEM-512 private key in the NIST encoding.
98pub const MLKEM1024_FULL_SK_LEN: usize = 3168;
99/// Length of the \[u8] holding a ML-KEM-1024 ciphertext.
100pub const MLKEM1024_CT_LEN: usize = 1568;
101pub(crate) const MLKEM1024_k: usize = 4;
102pub(crate) const MLKEM1024_ETA1: i16 = 2;
103pub(crate) const MLKEM1024_DU: i16 = 11;
104pub(crate) const MLKEM1024_DV: i16 = 5;
105/// Maps to "required RBG strength (bits)" in FIPS 203 Table 2
106pub(crate) const MLKEM1024_LAMBDA: i16 = 256;
107
108// internal derived values
109pub(crate) const MLKEM1024_T_PACKED_LEN: usize = 12 * MLKEM1024_k * 32;
110
111// Typedefs just to make the algorithms look more like the FIPS 204 sample code.
112pub(crate) type G = SHA3_512;
113pub(crate) type H = SHA3_256;
114pub(crate) type J = SHAKE256;
115
116/*** Pub Types ***/
117
118/// The ML-KEM-512 algorithm.
119pub type MLKEM512 = MLKEM<
120    MLKEM512_PK_LEN,
121    MLKEM512_SK_LEN,
122    MLKEM512_FULL_SK_LEN,
123    MLKEM512_CT_LEN,
124    MLKEM_SS_LEN,
125    MLKEM512PublicKey,
126    MLKEM512PrivateKey,
127    MLKEM512_k,
128    MLKEM512_ETA1,
129    MLKEM512_DU,
130    MLKEM512_DV,
131    MLKEM512_LAMBDA,
132    MLKEM512_T_PACKED_LEN,
133>;
134
135impl Algorithm for MLKEM512 {
136    const ALG_NAME: &'static str = ML_KEM_512_NAME;
137    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_128bit;
138}
139/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-512 { kems 1 }
140impl AlgorithmOID for MLKEM512 {
141    const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 1];
142    const OID_DER: &'static [u8] =
143        &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01];
144}
145
146/// The ML-KEM-768 algorithm.
147pub type MLKEM768 = MLKEM<
148    MLKEM768_PK_LEN,
149    MLKEM768_SK_LEN,
150    MLKEM768_FULL_SK_LEN,
151    MLKEM768_CT_LEN,
152    MLKEM_SS_LEN,
153    MLKEM768PublicKey,
154    MLKEM768PrivateKey,
155    MLKEM768_k,
156    MLKEM768_ETA1,
157    MLKEM768_DU,
158    MLKEM768_DV,
159    MLKEM768_LAMBDA,
160    MLKEM768_T_PACKED_LEN,
161>;
162
163impl Algorithm for MLKEM768 {
164    const ALG_NAME: &'static str = ML_KEM_768_NAME;
165    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_192bit;
166}
167/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-768 { kems 2 }
168impl AlgorithmOID for MLKEM768 {
169    const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 2];
170    const OID_DER: &'static [u8] =
171        &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02];
172}
173
174/// The ML-KEM-1024 algorithm.
175pub type MLKEM1024 = MLKEM<
176    MLKEM1024_PK_LEN,
177    MLKEM1024_SK_LEN,
178    MLKEM1024_FULL_SK_LEN,
179    MLKEM1024_CT_LEN,
180    MLKEM_SS_LEN,
181    MLKEM1024PublicKey,
182    MLKEM1024PrivateKey,
183    MLKEM1024_k,
184    MLKEM1024_ETA1,
185    MLKEM1024_DU,
186    MLKEM1024_DV,
187    MLKEM1024_LAMBDA,
188    MLKEM1024_T_PACKED_LEN,
189>;
190
191impl Algorithm for MLKEM1024 {
192    const ALG_NAME: &'static str = ML_KEM_1024_NAME;
193    const MAX_SECURITY_STRENGTH: SecurityStrength = SecurityStrength::_256bit;
194}
195/// Assigned by NIST in the Computer Security Objects Register: id-alg-ml-kem-1024 { kems 3 }
196impl AlgorithmOID for MLKEM1024 {
197    const OID: &'static [u32] = &[2, 16, 840, 1, 101, 3, 4, 4, 3];
198    const OID_DER: &'static [u8] =
199        &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03];
200}
201
202/// The core internal implementation of the ML-KEM algorithm.
203/// This needs to be public for the compiler to be able to find it,
204/// but is shouldn't ever need to be used directly.
205/// Please use the named public types.
206pub struct MLKEM<
207    const PK_LEN: usize,
208    const SK_LEN: usize,
209    const FULL_SK_LEN: usize,
210    const CT_LEN: usize,
211    const SS_LEN: usize,
212    PK: MLKEMPublicKeyTrait<k, PK_LEN, T_PACKED_LEN>
213        + MLKEMPublicKeyInternalTrait<k, T_PACKED_LEN, PK_LEN>,
214    SK: MLKEMPrivateKeyTrait<k, SK_LEN, FULL_SK_LEN, PK_LEN, T_PACKED_LEN>
215        + MLKEMPrivateKeyInternalTrait<k, SK_LEN, PK_LEN, T_PACKED_LEN>,
216    const k: usize,
217    const eta1: i16,
218    const du: i16,
219    const dv: i16,
220    const LAMBDA: i16,
221    const T_PACKED_LEN: usize,
222> {
223    _phantom: PhantomData<(PK, SK)>,
224}
225
226impl<
227    const PK_LEN: usize,
228    const SK_LEN: usize,
229    const FULL_SK_LEN: usize,
230    const CT_LEN: usize,
231    const SS_LEN: usize,
232    PK: MLKEMPublicKeyTrait<k, PK_LEN, T_PACKED_LEN>
233        + MLKEMPublicKeyInternalTrait<k, T_PACKED_LEN, PK_LEN>,
234    SK: MLKEMPrivateKeyTrait<k, SK_LEN, FULL_SK_LEN, PK_LEN, T_PACKED_LEN>
235        + MLKEMPrivateKeyInternalTrait<k, SK_LEN, PK_LEN, T_PACKED_LEN>,
236    const k: usize,
237    const eta1: i16,
238    const du: i16,
239    const dv: i16,
240    const LAMBDA: i16,
241    const T_PACKED_LEN: usize,
242>
243    MLKEM<
244        PK_LEN,
245        SK_LEN,
246        FULL_SK_LEN,
247        CT_LEN,
248        SS_LEN,
249        PK,
250        SK,
251        k,
252        eta1,
253        du,
254        dv,
255        LAMBDA,
256        T_PACKED_LEN,
257    >
258{
259    /// Performs the first step of key generation to transform the single provided seed into a set of internal intermediate seeds.
260    ///
261    /// Unlike other interfaces across the library that take an &impl KeyMaterial, this one
262    /// specifically takes a 64-byte [`KeyMaterial512`] and checks that it has [`KeyType::Seed`] and
263    /// the appropriate [`SecurityStrength`] for the requested ML-KEM parameter set.
264    ///
265    /// If you happen to have your seed in a larger KeyMaterial, you'll have to copy it into a
266    /// correctly-sized [`KeyMaterial512`] using [`KeyMaterialTrait::truncate`].
267    pub(crate) fn keygen_internal(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError> {
268        let sk = SK::from_keymaterial(seed)?;
269        let pk = sk.pk();
270        let pk = PK::new(pk.t_hat_packed, pk.rho); // stupid conversion, but it gets around these overly-generified rust types
271        Ok((pk, sk))
272    }
273
274    /// Algorithm 14 K-PKE.Encrypt(ekPKE, ๐‘š, ๐‘Ÿ)
275    /// Uses the encryption key to encrypt a plaintext message using the randomness ๐‘Ÿ.
276    /// Input: encryption key ekPKE โˆˆ ๐”น384๐‘˜+32 .
277    /// Input: message ๐‘š โˆˆ ๐”น32 .
278    /// Input: randomness ๐‘Ÿ โˆˆ ๐”น32 .
279    /// Output: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
280    fn pke_encrypt(
281        t_hat_packed: &[u8; T_PACKED_LEN],
282        rho: &[u8; 32],
283        m: [u8; 32],
284        r: &[u8; 32],
285    ) -> [u8; CT_LEN] {
286        let mut ct = [0u8; CT_LEN];
287
288        // 1: ๐‘ โ† 0
289        //  since the number of loops here is static; the N values can be hard-coded rather than using a counter
290
291        // 2: ๐ญ โ† ByteDecode12(ekPKE[0 โˆถ 384๐‘˜])
292        // 3: ๐œŒ โ† ekPKE[384๐‘˜ โˆถ 384๐‘˜ + 32]
293        // not necessary here because ek is already decoded
294
295        // 19: ๐ฎ โ† NTTโˆ’1(๐€_hat^โŠบ โˆ˜ ๐ฒ_hat) + ๐ž1
296        // 22: ๐‘1 โ† ByteEncode_๐‘‘๐‘ข(Compress_๐‘‘๐‘ข(๐ฎ))
297
298        // Note: y_hat is needed twice: once here at line 19, and again at line 21.
299        // Here it is generated each time it is needed in order to save memory.
300        for i in 0..k {
301            let mut u_i = compute_A_hat_dot_y_hat::<k, eta1>(rho, &r, i);
302
303            let e1_i = sample_poly_CBD::<ETA2>(&r, (k + i) as u8);
304            u_i.add(&e1_i);
305            u_i.poly_reduce();
306
307            compress_u_row::<du, CT_LEN>(u_i, i, &mut ct);
308        }
309
310        // 17: ๐‘’2 โ† SamplePolyCBD_๐œ‚2(PRF๐œ‚2 (๐‘Ÿ, ๐‘))
311        // 20: ๐œ‡ โ† Decompress1(ByteDecode1(๐‘š))
312        // 21: ๐‘ฃ โ† NTTโˆ’1(๐ญ_hat_T โˆ˜ ๐ฒ_hat) + ๐‘’2 + ๐œ‡
313        // 23: ๐‘2 โ† ByteEncode_๐‘‘๐‘ฃ(Compress_๐‘‘๐‘ฃ(๐‘ฃ))
314        {
315            // compute v, which is a single polynomial, but requires iterating over the vectors t_hat and y_hat
316            let mut v = compute_t_hat_dot_y_hat_row::<k, eta1>(
317                &r,
318                &unpack_t_hat_row(t_hat_packed, 0),
319                /*row*/ 0,
320            );
321
322            for i in 1..k {
323                let v_i = compute_t_hat_dot_y_hat_row::<k, eta1>(
324                    &r,
325                    &unpack_t_hat_row(t_hat_packed, i),
326                    /*row*/ i,
327                );
328                v.add(&v_i);
329            }
330
331            // perform polynomial addition
332            let e2 = sample_poly_CBD::<ETA2>(&r, 2 * k as u8);
333            v.add(&e2);
334
335            let mu = Polynomial::from_msg(m);
336            v.add(&mu);
337
338            v.poly_reduce();
339
340            v.compress_poly::<dv>(&mut ct[CT_LEN - (N * (dv as usize) / 8)..]);
341        }
342
343        ct
344    }
345
346    /// Algorithm 17 ML-KEM.Encaps_internal(ek, ๐‘š)
347    /// Uses the encapsulation key and randomness to generate a key and an associated ciphertext.
348    /// Input: encapsulation key ek โˆˆ ๐”น384๐‘˜+32 .
349    /// Input: randomness ๐‘š โˆˆ ๐”น32 .
350    /// Output: shared secret key ๐พ โˆˆ ๐”น32 .
351    /// Output: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
352    ///
353    /// Unlike the more public function exposed by [`KEMEncapsulator::encaps`], this returns the shared secret as raw bytes
354    /// instead of wrapped in an appropriately-set [`KeyMaterialTrait`].
355    /// Proper handling is up to the user's own judgement.
356    ///
357    /// Note: this is an internal function that allows the caller to specify the encapsulation
358    /// randomness (which is the message `m` to be encrypted by the underlying PKE scheme).
359    /// This function should not be used directly unless there is a good reason to do so.
360    /// [`KEMEncapsulator::encaps`] should be used in 99.9% of cases.
361    /// The reason this is exposed publicly is:
362    ///     A) for unit testing that requires access to the deterministically reproducible function, and
363    ///     B) for operational environments that wish to provide randomness from their own source instead
364    ///        of the built-in RNG in bc-rust.
365    /// As a reminder, any deterministic KEM (or any encryption mechanism) fails to satisfy any security
366    /// notion involving indistinguishability (e.g. IND-CPA, IND-CCA2, etc.).
367    /// Failing to use this properly will result in catastrophic vulnerabilities.
368    /// Please don't do it.
369    pub fn encaps_internal(ek: &PK, m: [u8; 32]) -> ([u8; 32], [u8; CT_LEN]) {
370        debug_assert_eq!(CT_LEN, 32 * ((du as usize) * k + (dv as usize)));
371
372        // 1: (๐พ, ๐‘Ÿ) โ† G(๐‘šโ€–H(ek))
373        //  โ–ท derive shared secret key ๐พ and randomness ๐‘Ÿ
374        let K: [u8; MLKEM_SS_LEN];
375        let r: [u8; 32];
376        (K, r) = {
377            let mut g = G::new();
378            g.do_update(&m);
379            g.do_update(&ek.compute_hash());
380            let mut buf = [0u8; 64];
381            let bytes_written = g.do_final_out(&mut buf);
382            debug_assert_eq!(bytes_written, 64);
383
384            (buf[..32].try_into().unwrap(), buf[32..64].try_into().unwrap())
385        };
386
387        // 2: ๐‘ โ† K-PKE.Encrypt(ek, ๐‘š, ๐‘Ÿ)
388        //  โ–ท encrypt ๐‘š using K-PKE with randomness ๐‘Ÿ
389        // deviation from FIPS:
390        let ct = Self::pke_encrypt(ek.t_hat_packed(), ek.rho(), m, &r);
391
392        (K, ct)
393    }
394
395    /// Algorithm 15 K-PKE.Decrypt(dkPKE, ๐‘)
396    /// Uses the decryption key to decrypt a ciphertext
397    /// Input: decryption key dkPKE โˆˆ ๐”น384๐‘˜.
398    /// Input: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
399    /// Output: message ๐‘š โˆˆ ๐”น32 .
400    fn pke_decrypt(dk: &SK, ct: [u8; CT_LEN]) -> [u8; 32] {
401        // 1: ๐‘1 โ† ๐‘[0 โˆถ 32๐‘‘๐‘ข๐‘˜]
402        // 3: ๐ฎโ€ฒ โ† Decompress_๐‘‘๐‘ข(ByteDecode_๐‘‘๐‘ข(๐‘1))
403
404        // 5: ๐ฌ_hat โ† ByteDecode12(dkPKE)
405        //   Unnecessary here because they are re-computed row-by-row
406
407        // first half of
408        // 6: ๐‘ค โ† ๐‘ฃโ€ฒ โˆ’ NTTโˆ’1(๐ฌ_hat^T โˆ˜ NTT(๐ฎโ€ฒ))
409        let v1 = {
410            // i = 0 case
411            let mut v1 = {
412                let mut s_hat_i = dk.compute_s_hat_row(0);
413                {
414                    let mut u_prime_i = unpack_ciphertext_u_row::<du, CT_LEN>(0, &ct);
415                    u_prime_i.ntt();
416                    s_hat_i.base_mult_montgomery(&u_prime_i);
417                }
418                s_hat_i.inv_ntt();
419
420                s_hat_i
421            };
422
423            for i in 1..k {
424                let mut s_hat_i = dk.compute_s_hat_row(i);
425                {
426                    let mut u_prime_i = unpack_ciphertext_u_row::<du, CT_LEN>(i, &ct);
427                    u_prime_i.ntt();
428                    s_hat_i.base_mult_montgomery(&u_prime_i);
429                }
430                s_hat_i.inv_ntt();
431                v1.add(&s_hat_i);
432            }
433
434            v1
435        };
436
437        // 2: ๐‘2 โ† ๐‘[32๐‘‘๐‘ข๐‘˜ โˆถ 32(๐‘‘๐‘ข๐‘˜ + ๐‘‘๐‘ฃ)]
438        // 4: ๐‘ฃโ€ฒ โ† Decompress_๐‘‘๐‘ฃ(ByteDecode_๐‘‘๐‘ฃ(๐‘2))
439        let w = {
440            // second half of
441            // 6: ๐‘ค โ† ๐‘ฃโ€ฒ โˆ’ NTTโˆ’1(๐ฌ_hat^T โˆ˜ NTT(๐ฎโ€ฒ))
442            let mut v_prime = unpack_ciphertext_v::<k, CT_LEN, du, dv>(&ct);
443
444            v_prime.sub(&v1);
445            v_prime.poly_reduce();
446
447            v_prime // rename to w
448        };
449
450        // 7: ๐‘š โ† ByteEncode1(Compress1(๐‘ค))
451        //   โ–ท decode plaintext ๐‘š from polynomial ๐‘ค
452        w.to_msg()
453    }
454
455    /// Algorithm 18 ML-KEM.Decaps_internal(dk, ๐‘)
456    /// Uses the decapsulation key to produce a shared secret key from a ciphertext.
457    /// Input: decapsulation key dk โˆˆ ๐”น768๐‘˜+96 .
458    /// Input: ciphertext ๐‘ โˆˆ ๐”น32(๐‘‘๐‘ข๐‘˜+๐‘‘๐‘ฃ).
459    /// Output: shared secret key ๐พ โˆˆ ๐”น32 .
460    fn decaps_internal(dk: &SK, c: [u8; CT_LEN]) -> [u8; MLKEM_SS_LEN] {
461        // I have tried to keep this as clean as possible for correspondence with the FIPS,
462        // but I have moved things around so that I can use unnamed scopes to limit how many
463        // stack variables are alive at the same time.
464
465        // 1: dkPKE โ† dk[0 โˆถ 384๐‘˜] โ–ท extract (from KEM decaps key) the PKE decryption key
466        // 2: ekPKE โ† dk[384๐‘˜ โˆถ 768๐‘˜ + 32] โ–ท extract PKE encryption key
467        // 3: โ„Ž โ† dk[768๐‘˜ + 32 โˆถ 768๐‘˜ + 64] โ–ท extract hash of PKE encryption key
468        // 4: ๐‘ง โ† dk[768๐‘˜ + 64 โˆถ 768๐‘˜ + 96] โ–ท extract implicit rejection value
469        // Nothing to do since dk is already decoded.
470
471        // 5: ๐‘šโ€ฒ โ† K-PKE.Decrypt(dkPKE, ๐‘)
472        let m_prime = Self::pke_decrypt(&dk, c);
473
474        // Compute the trial shared secret key
475        // 6: (๐พโ€ฒ, ๐‘Ÿโ€ฒ) โ† G(๐‘šโ€ฒโ€–โ„Ž)ฬ„
476        let K_prime: Secret<[u8; MLKEM_SS_LEN]>;
477        let r_prime: [u8; 32];
478        (K_prime, r_prime) = {
479            let mut buf: Secret<[u8; 64]> = Secret::new();
480            let mut g = G::new();
481            g.do_update(&m_prime);
482            g.do_update(&dk.pk().compute_hash());
483            let bytes_written = g.do_final_out(&mut *buf);
484            debug_assert_eq!(bytes_written, 64);
485
486            let mut K_prime: Secret<[u8; MLKEM_SS_LEN]> = Secret::new();
487            K_prime.copy_from_slice(&buf[..32]);
488            (K_prime, buf[32..64].try_into().unwrap())
489        };
490
491        // 7: ๐พ_bar โ† J(๐‘งโ€–๐‘)
492        //   Compute the rejection sampling key.
493        //   Note to future optimizers: this needs to be computed outside of the if at line 9 below
494        //   because if its computation is conditional on the Fujisaki-Okamoto check failing, then
495        //   there will be a timing difference between success and failure.
496
497        let K_bar: Secret<[u8; MLKEM_SS_LEN]>;
498        K_bar = {
499            let mut K_bar: Secret<[u8; MLKEM_SS_LEN]> = Secret::new();
500            let mut j = J::new();
501            j.absorb(dk.z()).expect("absorb before squeeze is infallible");
502            j.absorb(&c).expect("absorb before squeeze is infallible");
503            let bytes_written = j.squeeze_out(&mut *K_bar);
504            debug_assert_eq!(bytes_written, MLKEM_SS_LEN);
505
506            K_bar
507        };
508
509        // 8: ๐‘โ€ฒ โ† K-PKE.Encrypt(ekPKE, ๐‘šโ€ฒ, ๐‘Ÿโ€ฒ)
510        //   โ–ท re-encrypt using the derived randomness ๐‘Ÿโ€ฒ
511        let c_prime = Self::pke_encrypt(&dk.t_hat_packed(), dk.rho(), m_prime, &r_prime);
512
513        // 9: if ๐‘ โ‰  ๐‘โ€ฒ then
514        // 10: ๐พโ€ฒ โ† ๐พ_bar
515        //  โ–ท if ciphertexts do not match, โ€œimplicitly reject"
516        let mut K_out = [0u8; MLKEM_SS_LEN];
517        conditional_copy_bytes(&K_prime, &K_bar, &mut K_out, ct_eq_bytes(&c, &c_prime));
518
519        K_out
520    }
521
522    /// Alternative initialization of the streaming signer where there is a private key
523    /// as a seed and its expansion should be delayed as late as possible to reduce memory-usage.
524    pub fn decaps_from_seed(
525        seed: &KeyMaterial<64>,
526        ct: &[u8],
527    ) -> Result<KeyMaterial<SS_LEN>, KEMError> {
528        let sk = SK::from_keymaterial(seed)?;
529
530        Self::decaps(&sk, ct)
531    }
532}
533
534impl<
535    const PK_LEN: usize,
536    const SK_LEN: usize,
537    const FULL_SK_LEN: usize,
538    const CT_LEN: usize,
539    const SS_LEN: usize,
540    PK: MLKEMPublicKeyTrait<k, PK_LEN, T_PACKED_LEN>
541        + MLKEMPublicKeyInternalTrait<k, T_PACKED_LEN, PK_LEN>,
542    SK: MLKEMPrivateKeyTrait<k, SK_LEN, FULL_SK_LEN, PK_LEN, T_PACKED_LEN>
543        + MLKEMPrivateKeyInternalTrait<k, SK_LEN, PK_LEN, T_PACKED_LEN>,
544    const k: usize,
545    const eta1: i16,
546    const du: i16,
547    const dv: i16,
548    const LAMBDA: i16,
549    const T_PACKED_LEN: usize,
550>
551    MLKEMTrait<
552        PK_LEN,
553        SK_LEN,
554        FULL_SK_LEN,
555        CT_LEN,
556        SS_LEN,
557        PK,
558        SK,
559        k,
560        eta1,
561        du,
562        dv,
563        LAMBDA,
564        T_PACKED_LEN,
565    >
566    for MLKEM<
567        PK_LEN,
568        SK_LEN,
569        FULL_SK_LEN,
570        CT_LEN,
571        SS_LEN,
572        PK,
573        SK,
574        k,
575        eta1,
576        du,
577        dv,
578        LAMBDA,
579        T_PACKED_LEN,
580    >
581{
582    /// Imports a secret key from a seed.
583    fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError> {
584        Self::keygen_internal(seed)
585    }
586    /// Imports a secret key from both a seed and an encoded_sk.
587    ///
588    /// This is a convenience function to expand the key from seed and compare it against
589    /// the provided `encoded_sk` using a constant-time equality check.
590    /// If everything checks out, the secret key is returned fully populated with pk and seed.
591    /// If the provided key and derived key don't match, an error is returned.
592    fn keygen_from_seed_and_encoded(
593        seed: &KeyMaterial<64>,
594        encoded_sk: &[u8; SK_LEN],
595    ) -> Result<(PK, SK), KEMError> {
596        let (pk, sk) = Self::keygen_internal(seed)?;
597
598        let sk_from_bytes = SK::sk_decode(encoded_sk);
599
600        // MLKEMPrivateKey impls PartialEq with a constant-time equality check.
601        if sk != sk_from_bytes {
602            return Err(KEMError::KeyGenError("Encoded key does not match generated key"));
603        }
604
605        Ok((pk, sk))
606    }
607    /// Given a public key and a secret key, check that the public key matches the secret key.
608    /// This is a sanity check that the public key was generated correctly from the secret key.
609    ///
610    /// At the current time, this is only possible if `sk` either contains a public key (in which case
611    /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
612    /// (in which case a keygen_from_seed is run and then the pk's compared).
613    ///
614    /// Returns either `()` or [`KEMError::ConsistencyCheckFailed`].
615    fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), KEMError> {
616        let derived_pk = sk.pk();
617        if derived_pk.compute_hash() == pk.compute_hash() {
618            Ok(())
619        } else {
620            Err(KEMError::ConsistencyCheckFailed(""))
621        }
622    }
623}
624
625/// Trait for all three of the ML-DSA algorithm variants.
626pub trait MLKEMTrait<
627    const PK_LEN: usize,
628    const SK_LEN: usize,
629    const FULL_SK_LEN: usize,
630    const CT_LEN: usize,
631    const SS_LEN: usize,
632    PK: MLKEMPublicKeyTrait<k, PK_LEN, T_PACKED_LEN>
633        + MLKEMPublicKeyInternalTrait<k, T_PACKED_LEN, PK_LEN>,
634    SK: MLKEMPrivateKeyTrait<k, SK_LEN, FULL_SK_LEN, PK_LEN, T_PACKED_LEN>
635        + MLKEMPrivateKeyInternalTrait<k, SK_LEN, PK_LEN, T_PACKED_LEN>,
636    const k: usize,
637    const eta: i16,
638    const du: i16,
639    const dv: i16,
640    const LAMBDA: i16,
641    const T_PACKED_LEN: usize,
642>: Sized
643{
644    /// Generates a fresh key pair.
645    fn keygen() -> Result<(PK, SK), KEMError> {
646        let mut os_rng = HashDRBG_SHA512::new_from_os();
647        Self::keygen_from_rng(&mut os_rng)
648    }
649    /// Run a keygen using the provided RNG implementation.
650    // Should still be ok in FIPS mode, provided that you're using the FIPS-approved RNG.
651    fn keygen_from_rng(rng: &mut dyn RNG) -> Result<(PK, SK), KEMError> {
652        // Source the seed from the provided RNG
653        if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) {
654            return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?;
655        }
656        let mut seed = KeyMaterial::<64>::new();
657        rng.fill_keymaterial_out(&mut seed)?;
658        Self::keygen_from_seed(&seed)
659    }
660    /// Imports a secret key from a seed.
661    fn keygen_from_seed(seed: &KeyMaterial<64>) -> Result<(PK, SK), KEMError>;
662    /// Imports a secret key from both a seed and an encoded_sk.
663    ///
664    /// This is a convenience function to expand the key from seed and compare it against
665    /// the provided `encoded_sk` using a constant-time equality check.
666    /// If everything checks out, the secret key is returned fully populated with pk and seed.
667    /// If the provided key and derived key don't match, an error is returned.
668    fn keygen_from_seed_and_encoded(
669        seed: &KeyMaterial<64>,
670        encoded_sk: &[u8; SK_LEN],
671    ) -> Result<(PK, SK), KEMError>;
672    /// Given a public key and a secret key, check that the public key matches the secret key.
673    /// This is a sanity check that the public key was generated correctly from the secret key.
674    ///
675    /// At the current time, this is only possible if `sk` either contains a public key (in which case
676    /// the two pk's are encoded and compared for byte equality), or if `sk` contains a seed
677    /// (in which case a keygen_from_seed is run and then the pk's compared).
678    ///
679    /// Returns either `()` or [`KEMError::ConsistencyCheckFailed`].
680    fn keypair_consistency_check(pk: &PK, sk: &SK) -> Result<(), KEMError>;
681}
682
683impl<
684    const PK_LEN: usize,
685    const SK_LEN: usize,
686    const FULL_SK_LEN: usize,
687    const CT_LEN: usize,
688    const SS_LEN: usize,
689    PK: MLKEMPublicKeyTrait<k, PK_LEN, T_PACKED_LEN>
690        + MLKEMPublicKeyInternalTrait<k, T_PACKED_LEN, PK_LEN>,
691    SK: MLKEMPrivateKeyTrait<k, SK_LEN, FULL_SK_LEN, PK_LEN, T_PACKED_LEN>
692        + MLKEMPrivateKeyInternalTrait<k, SK_LEN, PK_LEN, T_PACKED_LEN>,
693    const k: usize,
694    const eta: i16,
695    const du: i16,
696    const dv: i16,
697    const LAMBDA: i16,
698    const T_PACKED_LEN: usize,
699> KEMEncapsulator<PK, PK_LEN, CT_LEN, SS_LEN>
700    for MLKEM<
701        PK_LEN,
702        SK_LEN,
703        FULL_SK_LEN,
704        CT_LEN,
705        SS_LEN,
706        PK,
707        SK,
708        k,
709        eta,
710        du,
711        dv,
712        LAMBDA,
713        T_PACKED_LEN,
714    >
715{
716    fn encaps(pk: &PK) -> Result<(KeyMaterial<SS_LEN>, [u8; CT_LEN]), KEMError> {
717        let mut os_rng = HashDRBG_SHA512::new_from_os();
718        Self::encaps_rng(pk, &mut os_rng)
719    }
720
721    fn encaps_rng(
722        pk: &PK,
723        rng: &mut dyn RNG,
724    ) -> Result<(KeyMaterial<SS_LEN>, [u8; CT_LEN]), KEMError> {
725        // Source the random message m from the provided RNG
726        if rng.security_strength() < SecurityStrength::from_bits(LAMBDA as usize) {
727            return Err(RNGError::SecurityStrengthInsufficientForAlgorithm)?;
728        }
729        let mut m = [0u8; 32];
730        rng.next_bytes_out(&mut m)?;
731
732        let (ss_bytes, ct) = Self::encaps_internal(pk, m);
733
734        let mut ss_keymaterial =
735            KeyMaterial::<SS_LEN>::from_bytes_as_type(&ss_bytes, KeyType::CryptographicRandom)?;
736        do_hazardous_operations(&mut ss_keymaterial, |ss_keymaterial| {
737            ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize))
738        })?;
739
740        Ok((ss_keymaterial, ct))
741    }
742}
743
744impl<
745    const PK_LEN: usize,
746    const SK_LEN: usize,
747    const FULL_SK_LEN: usize,
748    const CT_LEN: usize,
749    const SS_LEN: usize,
750    PK: MLKEMPublicKeyTrait<k, PK_LEN, T_PACKED_LEN>
751        + MLKEMPublicKeyInternalTrait<k, T_PACKED_LEN, PK_LEN>,
752    SK: MLKEMPrivateKeyTrait<k, SK_LEN, FULL_SK_LEN, PK_LEN, T_PACKED_LEN>
753        + MLKEMPrivateKeyInternalTrait<k, SK_LEN, PK_LEN, T_PACKED_LEN>,
754    const k: usize,
755    const eta: i16,
756    const du: i16,
757    const dv: i16,
758    const LAMBDA: i16,
759    const T_PACKED_LEN: usize,
760> KEMDecapsulator<SK, SK_LEN, CT_LEN, SS_LEN>
761    for MLKEM<
762        PK_LEN,
763        SK_LEN,
764        FULL_SK_LEN,
765        CT_LEN,
766        SS_LEN,
767        PK,
768        SK,
769        k,
770        eta,
771        du,
772        dv,
773        LAMBDA,
774        T_PACKED_LEN,
775    >
776{
777    /// Performs a decapsulation of the given ciphertext.
778    /// Returns the shared secret key.
779    /// The derived shared secret key is returned as a KeyMaterial with the SecurityStrength set to
780    /// the security level of the ML-KEM parameter set.
781    /// As ML-KEM is an implicitly-rejecting KEM, this returns an error only if the ciphertext is invalid (ie the wrong length)..
782    fn decaps(sk: &SK, ct: &[u8]) -> Result<KeyMaterial<SS_LEN>, KEMError> {
783        if ct.len() != CT_LEN {
784            return Err(KEMError::LengthError("Invalid ciphertext length"));
785        }
786
787        let ss_bytes = Self::decaps_internal(sk, ct.try_into().unwrap());
788
789        let mut ss_keymaterial =
790            KeyMaterial::<SS_LEN>::from_bytes_as_type(&ss_bytes, KeyType::CryptographicRandom)?;
791        do_hazardous_operations(&mut ss_keymaterial, |ss_keymaterial| {
792            ss_keymaterial.set_security_strength(SecurityStrength::from_bits(LAMBDA as usize))
793        })?;
794
795        Ok(ss_keymaterial)
796    }
797}