Skip to main content

crypto/mlkem/
mlkem1024.rs

1use super::mlkem::{
2    ML_KEM_1024, MlKemError, SHARED_SECRET_SIZE, crypto_kem_dec, crypto_kem_enc_derand, crypto_kem_keypair_derand,
3    encapsulation_key_modulus_check, indcpa_secret_key_bytes,
4};
5
6pub const PUBLIC_KEY_SIZE_1024: usize = 1568;
7pub const SECRET_KEY_SIZE_1024: usize = 3168;
8pub const CIPHERTEXT_SIZE_1024: usize = 1568;
9
10/// ML-KEM-1024 decapsulation key (secret key) as defined in FIPS 203.
11///
12/// A key is created with [`SecretKey1024::generate`] (fresh random key) or
13/// [`SecretKey1024::generate_derand`] (deterministic, for test vectors).
14///
15/// # Example
16///
17/// ```
18/// use crypto::mlkem::SecretKey1024;
19///
20/// let (secret_key, public_key) = SecretKey1024::generate_derand(&[0u8; 64]);
21/// let (shared_secret, ciphertext) = public_key.encapsulate_derand(&[0u8; 32]);
22/// let decapsulated = secret_key.decapsulate(&ciphertext).unwrap();
23/// assert_eq!(shared_secret, decapsulated);
24/// ```
25#[derive(Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
27pub struct SecretKey1024 {
28    bytes: [u8; SECRET_KEY_SIZE_1024],
29}
30
31/// ML-KEM-1024 encapsulation key (public key) as defined in FIPS 203.
32///
33/// See [`SecretKey1024`] for a full usage example.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct PublicKey1024 {
36    bytes: [u8; PUBLIC_KEY_SIZE_1024],
37}
38
39impl SecretKey1024 {
40    /// Creates a decapsulation key from its FIPS 203 encoding.
41    ///
42    /// The key is not validated here; use [`SecretKey1024::decapsulate`], which
43    /// performs the FIPS 203 decapsulation-key check.
44    pub fn from_bytes(bytes: &[u8; SECRET_KEY_SIZE_1024]) -> Self {
45        Self {
46            bytes: *bytes,
47        }
48    }
49
50    /// Returns the FIPS 203 encoding of this decapsulation key.
51    pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE_1024] {
52        self.bytes
53    }
54
55    /// Generates a fresh random keypair.
56    ///
57    /// Returns [`MlKemError::Random`] when the operating system's random
58    /// number generator is unavailable or fails.
59    #[cfg(feature = "random")]
60    pub fn generate() -> Result<(Self, PublicKey1024), MlKemError> {
61        let coins: [u8; 64] = crate::random::bytes()?;
62        Ok(Self::generate_derand(&coins))
63    }
64
65    /// Generates a keypair deterministically from 64 bytes of `coins`.
66    ///
67    /// The first 32 bytes seed the key generation matrix and the last 32 bytes
68    /// become the implicit-rejection secret. Useful for reproducing FIPS 203 /
69    /// ACVP test vectors; the same `coins` must not be reused for a real key.
70    pub fn generate_derand(coins: &[u8; 64]) -> (Self, PublicKey1024) {
71        let (sk_bytes, pk_bytes) =
72            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, coins);
73        (
74            Self {
75                bytes: sk_bytes,
76            },
77            PublicKey1024 {
78                bytes: pk_bytes,
79            },
80        )
81    }
82
83    /// Decapsulates `ciphertext`, recovering the shared secret encapsulated in it.
84    ///
85    /// Implements the FIPS 203 implicit-rejection transform: a ciphertext that
86    /// does not re-encrypt to itself does not produce an error, but instead
87    /// yields a pseudorandom shared secret derived from the key and ciphertext.
88    ///
89    /// Returns [`MlKemError::InvalidKey`] if this key fails the FIPS 203
90    /// decapsulation-key check, i.e. the hash of its embedded encapsulation key
91    /// does not match the hash stored in the key.
92    pub fn decapsulate(&self, ciphertext: &[u8; CIPHERTEXT_SIZE_1024]) -> Result<[u8; SHARED_SECRET_SIZE], MlKemError> {
93        crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &self.bytes, ciphertext)
94    }
95
96    /// Returns the encapsulation key embedded in this decapsulation key.
97    pub fn public_key(&self) -> PublicKey1024 {
98        let offset = indcpa_secret_key_bytes::<4>();
99        let mut pk_bytes = [0u8; PUBLIC_KEY_SIZE_1024];
100        pk_bytes.copy_from_slice(&self.bytes[offset..offset + PUBLIC_KEY_SIZE_1024]);
101        PublicKey1024 {
102            bytes: pk_bytes,
103        }
104    }
105}
106
107impl From<&[u8; SECRET_KEY_SIZE_1024]> for SecretKey1024 {
108    fn from(bytes: &[u8; SECRET_KEY_SIZE_1024]) -> Self {
109        Self::from_bytes(bytes)
110    }
111}
112
113impl TryFrom<&[u8]> for SecretKey1024 {
114    type Error = MlKemError;
115
116    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
117        Ok(Self::from_bytes(bytes.try_into().map_err(|_| MlKemError::InvalidKey)?))
118    }
119}
120
121impl PublicKey1024 {
122    /// Parses an encapsulation key from its FIPS 203 encoding.
123    ///
124    /// Returns [`MlKemError::InvalidKey`] when the encoded `t̂` is not
125    /// canonical, i.e. when any of its coefficients is not in `[0, q-1]`
126    /// (FIPS 203 Section 7.2 modulus check).
127    pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_SIZE_1024]) -> Result<Self, MlKemError> {
128        if !encapsulation_key_modulus_check::<4>(bytes) {
129            return Err(MlKemError::InvalidKey);
130        }
131        Ok(Self {
132            bytes: *bytes,
133        })
134    }
135
136    /// Returns the FIPS 203 encoding of this encapsulation key.
137    pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE_1024] {
138        self.bytes
139    }
140
141    /// Encapsulates a fresh random shared secret against this public key.
142    ///
143    /// Returns [`MlKemError::Random`] when the operating system's random
144    /// number generator is unavailable or fails.
145    #[cfg(feature = "random")]
146    pub fn encapsulate(&self) -> Result<([u8; SHARED_SECRET_SIZE], [u8; CIPHERTEXT_SIZE_1024]), MlKemError> {
147        let coins: [u8; 32] = crate::random::bytes()?;
148        Ok(self.encapsulate_derand(&coins))
149    }
150
151    /// Encapsulates a shared secret against this public key using the provided
152    /// 32-byte `coins` as the encapsulation randomness.
153    ///
154    /// This is the deterministic counterpart of [`PublicKey1024::encapsulate`],
155    /// useful for reproducing FIPS 203 / ACVP test vectors. It must not be used
156    /// with repeated `coins`.
157    pub fn encapsulate_derand(&self, coins: &[u8; 32]) -> ([u8; SHARED_SECRET_SIZE], [u8; CIPHERTEXT_SIZE_1024]) {
158        crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &self.bytes, coins)
159    }
160}
161
162impl TryFrom<&[u8]> for PublicKey1024 {
163    type Error = MlKemError;
164
165    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
166        Self::from_bytes(bytes.try_into().map_err(|_| MlKemError::InvalidKey)?)
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::{
173        super::mlkem::{
174            ML_KEM_1024, Q, crypto_kem_dec, crypto_kem_enc_derand, crypto_kem_keypair_derand, decode_hex_array,
175            sha3_256_hex,
176        },
177        *,
178    };
179
180    #[test]
181    fn ml_kem_1024_round_trip() {
182        let (private_key, public_key) = SecretKey1024::generate().unwrap();
183        let (encapsulated_secret, ciphertext) = public_key.encapsulate().unwrap();
184        let decapsulated_secret = private_key.decapsulate(&ciphertext).unwrap();
185
186        assert_eq!(encapsulated_secret, decapsulated_secret);
187    }
188
189    #[test]
190    fn ml_kem_1024_deterministic_derand_vectors_are_stable() {
191        let key_coins = [3u8; 64];
192        let enc_coins = [5u8; 32];
193        let (secret_key, public_key) =
194            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &key_coins);
195        let (shared_secret, ciphertext) = crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(
196            &ML_KEM_1024,
197            &public_key,
198            &enc_coins,
199        );
200        let decapsulated =
201            crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &secret_key, &ciphertext)
202                .unwrap();
203
204        assert_eq!(shared_secret, decapsulated);
205        assert_eq!(
206            hex::encode(&public_key[..32]),
207            "2dd29da8b193397a4336c02382aab3bcfbac25f0cd71c888af379e1e75149a79"
208        );
209        assert_eq!(
210            hex::encode(&ciphertext[..32]),
211            "5f12f173ef59a45f910d3a225913f3297b2277636a72401a273648015cccf079"
212        );
213        assert_eq!(
214            hex::encode(shared_secret),
215            "8bf157178aa556b55f95686ba9b5afe13a6b75c848f1ddd9a334d50287bec24e"
216        );
217    }
218
219    #[test]
220    fn ml_kem_1024_cctv_accumulated_10k() {
221        use crate::{Xof, sha3::Shake128};
222
223        let mut rng = Shake128::new();
224        rng.absorb(&[]);
225
226        let mut acc = Shake128::new();
227
228        for _ in 0..10_000u32 {
229            let mut d = [0u8; 32];
230            let mut z = [0u8; 32];
231            let mut m = [0u8; 32];
232            let mut ct_random = [0u8; CIPHERTEXT_SIZE_1024];
233
234            rng.squeeze(&mut d);
235            rng.squeeze(&mut z);
236            rng.squeeze(&mut m);
237            rng.squeeze(&mut ct_random);
238
239            let mut coins = [0u8; 64];
240            coins[..32].copy_from_slice(&d);
241            coins[32..].copy_from_slice(&z);
242
243            let (dk, ek) =
244                crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &coins);
245            let (k_encaps, ct) =
246                crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &ek, &m);
247
248            let k_decaps =
249                crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &dk, &ct).unwrap();
250            assert_eq!(k_encaps, k_decaps);
251
252            let k_decaps_random =
253                crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &dk, &ct_random).unwrap();
254
255            acc.absorb(&ek);
256            acc.absorb(&dk);
257            acc.absorb(&ct);
258            acc.absorb(&k_encaps);
259            acc.absorb(&k_decaps_random);
260        }
261
262        let mut hash = [0u8; 32];
263        acc.squeeze(&mut hash);
264        assert_eq!(
265            hex::encode(hash),
266            "e3bf82b013307b2e9d47dde791ff6dfc82e694e6382404abdb948b908b75bad5",
267            "ML-KEM-1024 CCTV accumulated hash mismatch"
268        );
269    }
270
271    #[test]
272    fn ml_kem_1024_cctv_intermediate_vector() {
273        let d: [u8; 32] = decode_hex_array("2a62c39ef4fc499f2d132716f480bb7521a49558ae84ee80d9352e66daf1e3a8");
274        let z: [u8; 32] = decode_hex_array("5f574ef7f013d4336801fed022178c3ed91d0b6d51325315fc1dcabf4770a2ea");
275        let m: [u8; 32] = decode_hex_array("e07d685ed308e609c9c7842026e35732f6ffc6e2fee10f0afd348f2b42a8acb4");
276
277        let mut coins = [0u8; 64];
278        coins[..32].copy_from_slice(&d);
279        coins[32..].copy_from_slice(&z);
280
281        let (dk, ek) = crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &coins);
282        let (k, ct) = crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &ek, &m);
283
284        assert_eq!(
285            sha3_256_hex(&ek),
286            "3b308d1344ed70366b84d790acb705b86cd3dfd471fff171969aaa338f26dca5"
287        );
288        assert_eq!(
289            sha3_256_hex(&dk),
290            "aa63a9e0c035ada6635e7938b71856b24917ff9b3ebca1a4d205a83b502a415a"
291        );
292        assert_eq!(
293            sha3_256_hex(&ct),
294            "8caba02733421f12a7ba9a2bcbe4de7c9853156a0637df5a7a0f9127c81da943"
295        );
296        assert_eq!(
297            hex::encode(k),
298            "d53825c3ff666bb2881215dbec04a8bdce9099b2a3680938c2f199b54d505953"
299        );
300    }
301
302    #[test]
303    fn ml_kem_1024_decapsulation_rejects_tampered_ciphertext() {
304        let (private_key, public_key) = SecretKey1024::generate().unwrap();
305        let (encapsulated_secret, mut ciphertext) = public_key.encapsulate().unwrap();
306
307        ciphertext[0] ^= 0x80;
308
309        let decapsulated_secret = private_key.decapsulate(&ciphertext).unwrap();
310
311        assert_ne!(encapsulated_secret, decapsulated_secret);
312    }
313
314    #[test]
315    fn ml_kem_1024_decapsulation_with_wrong_key_rejects() {
316        let (_, alice_pk) = SecretKey1024::generate().unwrap();
317        let (bob_sk, _bob_pk) = SecretKey1024::generate().unwrap();
318        let (_alice_ss, ct) = alice_pk.encapsulate().unwrap();
319
320        let wrong_ss = bob_sk.decapsulate(&ct).unwrap();
321        assert_ne!(_alice_ss, wrong_ss);
322    }
323
324    #[test]
325    fn ml_kem_1024_round_trip_many() {
326        for _ in 0..100 {
327            let (sk, pk) = SecretKey1024::generate().unwrap();
328            let (ss_enc, ct) = pk.encapsulate().unwrap();
329            let ss_dec = sk.decapsulate(&ct).unwrap();
330            assert_eq!(ss_enc, ss_dec);
331        }
332    }
333
334    #[test]
335    fn ml_kem_1024_all_zero_ciphertext_does_not_panic() {
336        let (sk, _pk) = SecretKey1024::generate().unwrap();
337        let ct = [0u8; CIPHERTEXT_SIZE_1024];
338        let _result = sk.decapsulate(&ct);
339    }
340
341    #[test]
342    fn ml_kem_1024_all_ones_ciphertext_does_not_panic() {
343        let (sk, _pk) = SecretKey1024::generate().unwrap();
344        let ct = [0xffu8; CIPHERTEXT_SIZE_1024];
345        let _result = sk.decapsulate(&ct);
346    }
347
348    #[test]
349    fn ml_kem_1024_derand_keygen_is_deterministic() {
350        let coins = [3u8; 64];
351        let (sk1, pk1) =
352            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &coins);
353        let (sk2, pk2) =
354            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &coins);
355        assert_eq!(sk1, sk2);
356        assert_eq!(pk1, pk2);
357    }
358
359    #[test]
360    fn ml_kem_1024_key_sizes_are_correct() {
361        let (sk, pk) = SecretKey1024::generate().unwrap();
362        let sk_bytes = sk.to_bytes();
363        let pk_bytes = pk.to_bytes();
364        assert_eq!(sk_bytes.len(), SECRET_KEY_SIZE_1024);
365        assert_eq!(pk_bytes.len(), PUBLIC_KEY_SIZE_1024);
366        let (_, ct) = pk.encapsulate().unwrap();
367        assert_eq!(ct.len(), CIPHERTEXT_SIZE_1024);
368    }
369
370    #[test]
371    fn ml_kem_1024_rejects_non_canonical_encapsulation_key() {
372        let (_sk, pk) = SecretKey1024::generate().unwrap();
373        let base = pk.to_bytes();
374
375        assert!(PublicKey1024::from_bytes(&base).is_ok());
376
377        let set_first = |c: u16| {
378            let mut bytes = base;
379            bytes[0] = (c & 0xff) as u8;
380            bytes[1] = (bytes[1] & 0xf0) | ((c >> 8) as u8 & 0x0f);
381            bytes
382        };
383
384        assert!(PublicKey1024::from_bytes(&set_first(Q as u16)).is_err());
385        assert!(PublicKey1024::from_bytes(&set_first(0x0fff)).is_err());
386        assert!(PublicKey1024::from_bytes(&set_first(Q as u16 - 1)).is_ok());
387    }
388
389    #[test]
390    fn ml_kem_1024_encaps_is_deterministic_with_same_coins() {
391        let enc_coins = [5u8; 32];
392        let key_coins = [3u8; 64];
393        let (_sk, pk) =
394            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &key_coins);
395        let (ss1, ct1) =
396            crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &pk, &enc_coins);
397        let (ss2, ct2) =
398            crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &pk, &enc_coins);
399        assert_eq!(ct1, ct2);
400        assert_eq!(ss1, ss2);
401    }
402
403    #[test]
404    fn ml_kem_1024_decapsulation_with_wrong_key_is_deterministic() {
405        let (_, pk_a) = SecretKey1024::generate().unwrap();
406        let (sk_b, _pk_b) = SecretKey1024::generate().unwrap();
407        let (_, ct) = pk_a.encapsulate().unwrap();
408
409        let ss1 = sk_b.decapsulate(&ct).unwrap();
410        let ss2 = sk_b.decapsulate(&ct).unwrap();
411        assert_eq!(ss1, ss2, "implicit rejection must be deterministic");
412    }
413
414    #[test]
415    fn ml_kem_1024_wycheproof_keygen() {
416        let data: serde_json::Value = serde_json::from_str(include_str!(
417            "../../testdata/wycheproof/testvectors_v1/mlkem_1024_keygen_seed_test.json"
418        ))
419        .unwrap();
420        let mut tested = 0u64;
421        for group in data["testGroups"].as_array().unwrap() {
422            if group["parameterSet"].as_str() != Some("ML-KEM-1024") {
423                continue;
424            }
425            for test in group["tests"].as_array().unwrap() {
426                let seed_hex = test["seed"].as_str().unwrap();
427                let expected_ek_hex = test["ek"].as_str().unwrap();
428                let expected_dk_hex = test["dk"].as_str().unwrap();
429                let result = test["result"].as_str().unwrap();
430
431                let seed = hex::decode_array::<64>(seed_hex.as_bytes()).unwrap();
432
433                let (dk, ek) =
434                    crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &seed);
435
436                let ek_hex = hex::encode(ek);
437                let dk_hex = hex::encode(dk);
438
439                if result == "valid" {
440                    assert_eq!(
441                        ek_hex, expected_ek_hex,
442                        "wycheproof keygen KAT tcId={} ek mismatch",
443                        test["tcId"]
444                    );
445                    assert_eq!(
446                        dk_hex, expected_dk_hex,
447                        "wycheproof keygen KAT tcId={} dk mismatch",
448                        test["tcId"]
449                    );
450                }
451                tested += 1;
452            }
453        }
454        assert!(tested > 0, "no ML-KEM-1024 keygen tests were run");
455    }
456
457    fn wycheproof_kem_has_invalid_lengths(seed_hex: &str, c_hex: &str, ct_size: usize) -> bool {
458        seed_hex.len() != 128 || c_hex.len() != ct_size * 2
459    }
460
461    #[test]
462    fn ml_kem_1024_wycheproof_kem() {
463        let data: serde_json::Value =
464            serde_json::from_str(include_str!("../../testdata/wycheproof/testvectors_v1/mlkem_1024_test.json"))
465                .unwrap();
466        let mut tested = 0u64;
467        let mut skipped = 0u64;
468        for group in data["testGroups"].as_array().unwrap() {
469            if group["parameterSet"].as_str() != Some("ML-KEM-1024") {
470                continue;
471            }
472            for test in group["tests"].as_array().unwrap() {
473                let seed_hex = test["seed"].as_str().unwrap();
474                let c_hex = test["c"].as_str().unwrap();
475                let expected_k_hex = test["K"].as_str().unwrap();
476                let result = test["result"].as_str().unwrap();
477
478                if wycheproof_kem_has_invalid_lengths(seed_hex, c_hex, CIPHERTEXT_SIZE_1024) {
479                    // The key-generation seed is a fixed `[u8; 64]` and the
480                    // ciphertext is a fixed `[u8; CIPHERTEXT_SIZE_1024]`, so
481                    // wrong-length inputs are unrepresentable through the API.
482                    assert!(
483                        hex::decode(seed_hex).unwrap().len() != 64
484                            || hex::decode(c_hex).unwrap().len() != CIPHERTEXT_SIZE_1024,
485                        "wycheproof KEM KAT tcId={} unexpectedly has correct lengths",
486                        test["tcId"]
487                    );
488                    skipped += 1;
489                    continue;
490                }
491
492                let seed = hex::decode_array::<64>(seed_hex.as_bytes()).unwrap();
493
494                let (dk, ek) =
495                    crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &seed);
496
497                if let Some(expected_ek_hex) = test.get("ek").and_then(|v| v.as_str()) {
498                    let ek_hex = hex::encode(ek);
499                    assert_eq!(ek_hex, expected_ek_hex, "wycheproof KEM KAT tcId={} ek mismatch", test["tcId"]);
500                }
501
502                let c = decode_hex_array::<CIPHERTEXT_SIZE_1024>(c_hex);
503                let shared_secret =
504                    crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &dk, &c);
505
506                if result == "valid" {
507                    let k = shared_secret.unwrap();
508                    let k_hex = hex::encode(k);
509                    assert_eq!(k_hex, expected_k_hex, "wycheproof KEM KAT tcId={} K mismatch", test["tcId"]);
510                } else {
511                    assert!(
512                        shared_secret.is_ok(),
513                        "wycheproof KEM KAT tcId={} unexpected error",
514                        test["tcId"]
515                    );
516                }
517                tested += 1;
518            }
519        }
520        assert!(tested > 0, "no ML-KEM-1024 KEM tests were run");
521        eprintln!("wycheproof ML-KEM-1024 KEM: {tested} tested, {skipped} skipped (wrong-length inputs)");
522    }
523
524    #[test]
525    fn ml_kem_1024_wycheproof_encaps() {
526        let data: serde_json::Value = serde_json::from_str(include_str!(
527            "../../testdata/wycheproof/testvectors_v1/mlkem_1024_encaps_test.json"
528        ))
529        .unwrap();
530        let mut tested = 0u64;
531        let mut skipped = 0u64;
532        for group in data["testGroups"].as_array().unwrap() {
533            if group["parameterSet"].as_str() != Some("ML-KEM-1024") {
534                continue;
535            }
536            for test in group["tests"].as_array().unwrap() {
537                let ek_hex = test["ek"].as_str().unwrap();
538                let m_hex = test["m"].as_str().unwrap();
539                let expected_c_hex = test["c"].as_str().unwrap();
540                let expected_k_hex = test["K"].as_str().unwrap();
541                let result = test["result"].as_str().unwrap();
542
543                if ek_hex.len() != PUBLIC_KEY_SIZE_1024 * 2 {
544                    let ek_bytes = hex::decode(ek_hex).unwrap();
545                    assert!(
546                        PublicKey1024::try_from(ek_bytes.as_slice()).is_err(),
547                        "wycheproof encaps tcId={} flagged wrong-length ek but it parsed",
548                        test["tcId"]
549                    );
550                    skipped += 1;
551                    continue;
552                }
553
554                let ek = decode_hex_array::<PUBLIC_KEY_SIZE_1024>(ek_hex);
555
556                if result == "valid" {
557                    let public_key = PublicKey1024::from_bytes(&ek)
558                        .unwrap_or_else(|_| panic!("wycheproof encaps tcId={} valid ek rejected", test["tcId"]));
559                    let m = decode_hex_array::<32>(m_hex);
560                    let (k, c) = public_key.encapsulate_derand(&m);
561                    let c_hex_out = hex::encode(c);
562                    let k_hex_out = hex::encode(k);
563                    assert_eq!(
564                        c_hex_out, expected_c_hex,
565                        "wycheproof encaps KAT tcId={} c mismatch",
566                        test["tcId"]
567                    );
568                    assert_eq!(
569                        k_hex_out, expected_k_hex,
570                        "wycheproof encaps KAT tcId={} K mismatch",
571                        test["tcId"]
572                    );
573                } else {
574                    assert!(
575                        PublicKey1024::from_bytes(&ek).is_err(),
576                        "wycheproof encaps tcId={} invalid ek accepted",
577                        test["tcId"]
578                    );
579                }
580                tested += 1;
581            }
582        }
583        assert!(tested > 0, "no ML-KEM-1024 encaps tests were run");
584        eprintln!("wycheproof ML-KEM-1024 encaps: {tested} tested, {skipped} skipped (wrong-length ek)");
585    }
586
587    #[test]
588    fn ml_kem_1024_wycheproof_decaps_validation() {
589        let data: serde_json::Value = serde_json::from_str(include_str!(
590            "../../testdata/wycheproof/testvectors_v1/mlkem_1024_semi_expanded_decaps_test.json"
591        ))
592        .unwrap();
593        let mut valid = 0u64;
594        let mut invalid = 0u64;
595        let mut skipped = 0u64;
596        for group in data["testGroups"].as_array().unwrap() {
597            if group["parameterSet"].as_str() != Some("ML-KEM-1024") {
598                continue;
599            }
600            for test in group["tests"].as_array().unwrap() {
601                let flags: Vec<&str> = test["flags"]
602                    .as_array()
603                    .map(|a| a.iter().filter_map(|v| v.as_str()).collect())
604                    .unwrap_or_default();
605                let tc_id = test["tcId"].clone();
606                let dk_hex = test["dk"].as_str().unwrap();
607                let c_hex = test["c"].as_str().unwrap();
608
609                if flags.contains(&"IncorrectDecapsulationKeyLength") {
610                    let dk_bytes = hex::decode(dk_hex).unwrap();
611                    assert!(
612                        SecretKey1024::try_from(dk_bytes.as_slice()).is_err(),
613                        "wycheproof decaps tcId={tc_id} flagged wrong-length dk but it parsed",
614                    );
615                    skipped += 1;
616                    continue;
617                }
618
619                if flags.contains(&"IncorrectCiphertextLength") {
620                    // Ciphertexts are raw fixed-size arrays with no fallible
621                    // decoder, so a wrong-length ciphertext cannot reach the API.
622                    assert!(
623                        hex::decode(c_hex).unwrap().len() != CIPHERTEXT_SIZE_1024,
624                        "wycheproof decaps tcId={tc_id} flagged wrong-length ct but length is correct",
625                    );
626                    skipped += 1;
627                    continue;
628                }
629
630                let dk = decode_hex_array::<SECRET_KEY_SIZE_1024>(dk_hex);
631                let c = decode_hex_array::<CIPHERTEXT_SIZE_1024>(c_hex);
632
633                let result = crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &dk, &c);
634
635                match test["result"].as_str().unwrap() {
636                    "valid" => {
637                        let k = result
638                            .unwrap_or_else(|_| panic!("wycheproof decaps tcId={tc_id} ({flags:?}): expected success"));
639                        let expected = test["K"].as_str().expect("valid vector without K");
640                        assert_eq!(
641                            hex::encode(k),
642                            expected,
643                            "wycheproof decaps tcId={tc_id} ({flags:?}) shared secret mismatch"
644                        );
645                        valid += 1;
646                    }
647                    "invalid" => {
648                        assert!(
649                            result.is_err(),
650                            "wycheproof decaps tcId={tc_id} ({flags:?}): expected rejection but decapsulation succeeded",
651                        );
652                        invalid += 1;
653                    }
654                    other => panic!("wycheproof decaps tcId={tc_id}: unknown result {other}"),
655                }
656            }
657        }
658        assert!(valid > 0, "no valid ML-KEM-1024 decaps tests were run");
659        assert!(invalid > 0, "no invalid ML-KEM-1024 decaps tests were run");
660        eprintln!("wycheproof ML-KEM-1024 decaps: {valid} valid, {invalid} invalid, {skipped} skipped (wrong lengths)");
661    }
662
663    /// See [`mlkem768`] equivalent: implicit rejection must be `SHAKE256(z||c)`
664    /// for every ciphertext byte, including the `c2` tail.
665    #[test]
666    fn ml_kem_1024_implicit_rejection_matches_spec() {
667        use crate::{Xof, sha3::Shake256};
668
669        let (sk, pk) = SecretKey1024::generate().unwrap();
670        let sk_bytes = sk.to_bytes();
671        let z = &sk_bytes[SECRET_KEY_SIZE_1024 - 32..];
672        let (k_ok, ct) = pk.encapsulate().unwrap();
673
674        for offset in [0, CIPHERTEXT_SIZE_1024 / 2, 1407, CIPHERTEXT_SIZE_1024 - 1] {
675            let mut bad = ct;
676            bad[offset] ^= 0x01;
677            let k_bad = sk.decapsulate(&bad).unwrap();
678            assert_ne!(k_bad, k_ok, "tampered ciphertext byte {offset} returned the valid secret");
679
680            let mut hasher = Shake256::new();
681            hasher.absorb(z);
682            hasher.absorb(&bad);
683            let mut expected = [0u8; SHARED_SECRET_SIZE];
684            hasher.squeeze(&mut expected);
685            assert_eq!(k_bad, expected, "implicit rejection at byte {offset} is not SHAKE256(z||c)");
686        }
687    }
688
689    #[test]
690    fn ml_kem_1024_rejects_key_with_wrong_hash() {
691        let (sk, _pk) = SecretKey1024::generate().unwrap();
692        let (_, ct) = sk.public_key().encapsulate().unwrap();
693
694        let mut bytes = sk.to_bytes();
695        assert!(SecretKey1024::from_bytes(&bytes).decapsulate(&ct).is_ok());
696
697        bytes[SECRET_KEY_SIZE_1024 - 64] ^= 0x01;
698        assert_eq!(SecretKey1024::from_bytes(&bytes).decapsulate(&ct), Err(MlKemError::InvalidKey));
699
700        let mut bytes = sk.to_bytes();
701        bytes[SECRET_KEY_SIZE_1024 - PUBLIC_KEY_SIZE_1024 - 64] ^= 0x01;
702        assert_eq!(SecretKey1024::from_bytes(&bytes).decapsulate(&ct), Err(MlKemError::InvalidKey));
703    }
704
705    #[test]
706    fn ml_kem_1024_cross_implementation_pqcrypto() {
707        // Cross-implementation validation: decapsulate ciphertexts generated by
708        // the pqcrypto (liboqs) ML-KEM-1024 implementation.
709        let data: serde_json::Value =
710            serde_json::from_str(include_str!("../../testdata/mlkem/pqcrypto_1024_vectors.json")).unwrap();
711        let vectors = data.as_array().unwrap();
712        assert!(vectors.len() >= 5, "not enough cross-impl vectors");
713
714        for (i, vector) in vectors.iter().enumerate() {
715            let sk_hex = vector["sk"].as_str().unwrap();
716            let ct_hex = vector["ct"].as_str().unwrap();
717            let expected_ss_hex = vector["ss"].as_str().unwrap();
718
719            let sk = decode_hex_array::<SECRET_KEY_SIZE_1024>(sk_hex);
720            let ct = decode_hex_array::<CIPHERTEXT_SIZE_1024>(ct_hex);
721
722            let ss = crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &sk, &ct).unwrap();
723            assert_eq!(
724                hex::encode(ss),
725                expected_ss_hex,
726                "cross-impl pqcrypto vector {i} decapsulation mismatch"
727            );
728        }
729    }
730}