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    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/// # Example
13///
14/// ```ignore
15/// use crypto::mlkem::{SecretKey1024, generate_keypair_1024};
16///
17/// let (secret_key, public_key) = generate_keypair_1024();
18/// let (ciphertext, shared_secret) = public_key.encapsulate();
19/// let decapsulated = secret_key.decapsulate(&ciphertext).unwrap();
20/// assert_eq!(shared_secret, decapsulated);
21/// ```
22#[derive(Clone, Debug, PartialEq, Eq)]
23#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
24pub struct SecretKey1024 {
25    bytes: [u8; SECRET_KEY_SIZE_1024],
26}
27
28/// ML-KEM-1024 encapsulation key (public key) as defined in FIPS 203.
29///
30/// See [`SecretKey1024`] for a full usage example.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct PublicKey1024 {
33    bytes: [u8; PUBLIC_KEY_SIZE_1024],
34}
35
36/// Generate an ML-KEM-1024 keypair.
37///
38/// This is a convenience wrapper around [`SecretKey1024::generate`].
39///
40/// See [`SecretKey1024`] for a usage example.
41#[inline]
42#[cfg(feature = "random")]
43pub fn generate_keypair_1024() -> (SecretKey1024, PublicKey1024) {
44    SecretKey1024::generate()
45}
46
47impl SecretKey1024 {
48    pub fn from_bytes(bytes: &[u8; SECRET_KEY_SIZE_1024]) -> Self {
49        Self {
50            bytes: *bytes,
51        }
52    }
53
54    pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE_1024] {
55        self.bytes
56    }
57
58    #[cfg(feature = "random")]
59    pub fn generate() -> (Self, PublicKey1024) {
60        let coins: [u8; 64] = crate::random::random_bytes();
61        Self::generate_derand(&coins)
62    }
63
64    fn generate_derand(coins: &[u8; 64]) -> (Self, PublicKey1024) {
65        let (sk_bytes, pk_bytes) =
66            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, coins);
67        (
68            Self {
69                bytes: sk_bytes,
70            },
71            PublicKey1024 {
72                bytes: pk_bytes,
73            },
74        )
75    }
76
77    pub fn decapsulate(&self, ciphertext: &[u8; CIPHERTEXT_SIZE_1024]) -> Result<[u8; SHARED_SECRET_SIZE], MlKemError> {
78        crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &self.bytes, ciphertext)
79    }
80
81    pub fn public_key(&self) -> PublicKey1024 {
82        let offset = indcpa_secret_key_bytes::<4>();
83        let mut pk_bytes = [0u8; PUBLIC_KEY_SIZE_1024];
84        pk_bytes.copy_from_slice(&self.bytes[offset..offset + PUBLIC_KEY_SIZE_1024]);
85        PublicKey1024 {
86            bytes: pk_bytes,
87        }
88    }
89}
90
91impl From<&[u8; SECRET_KEY_SIZE_1024]> for SecretKey1024 {
92    fn from(bytes: &[u8; SECRET_KEY_SIZE_1024]) -> Self {
93        Self::from_bytes(bytes)
94    }
95}
96
97impl TryFrom<&[u8]> for SecretKey1024 {
98    type Error = MlKemError;
99
100    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
101        Ok(Self::from_bytes(bytes.try_into().map_err(|_| MlKemError::InvalidKey)?))
102    }
103}
104
105impl PublicKey1024 {
106    pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_SIZE_1024]) -> Self {
107        Self {
108            bytes: *bytes,
109        }
110    }
111
112    pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE_1024] {
113        self.bytes
114    }
115
116    #[cfg(feature = "random")]
117    pub fn encapsulate(&self) -> ([u8; CIPHERTEXT_SIZE_1024], [u8; SHARED_SECRET_SIZE]) {
118        let coins: [u8; 32] = crate::random::random_bytes();
119        self.encapsulate_derand(&coins)
120    }
121
122    fn encapsulate_derand(&self, coins: &[u8; 32]) -> ([u8; CIPHERTEXT_SIZE_1024], [u8; SHARED_SECRET_SIZE]) {
123        crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &self.bytes, coins)
124    }
125}
126
127impl From<&[u8; PUBLIC_KEY_SIZE_1024]> for PublicKey1024 {
128    fn from(bytes: &[u8; PUBLIC_KEY_SIZE_1024]) -> Self {
129        Self::from_bytes(bytes)
130    }
131}
132
133impl TryFrom<&[u8]> for PublicKey1024 {
134    type Error = MlKemError;
135
136    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
137        Ok(Self::from_bytes(bytes.try_into().map_err(|_| MlKemError::InvalidKey)?))
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::{
144        super::mlkem::{
145            ML_KEM_1024, crypto_kem_dec, crypto_kem_enc_derand, crypto_kem_keypair_derand, decode_hex_array,
146            sha3_256_hex,
147        },
148        *,
149    };
150
151    #[test]
152    fn ml_kem_1024_round_trip() {
153        let (private_key, public_key) = generate_keypair_1024();
154        let (ciphertext, encapsulated_secret) = public_key.encapsulate();
155        let decapsulated_secret = private_key.decapsulate(&ciphertext).unwrap();
156
157        assert_eq!(encapsulated_secret, decapsulated_secret);
158    }
159
160    #[test]
161    fn ml_kem_1024_deterministic_derand_vectors_are_stable() {
162        let key_coins = [3u8; 64];
163        let enc_coins = [5u8; 32];
164        let (secret_key, public_key) =
165            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &key_coins);
166        let (ciphertext, shared_secret) = crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(
167            &ML_KEM_1024,
168            &public_key,
169            &enc_coins,
170        );
171        let decapsulated =
172            crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &secret_key, &ciphertext)
173                .unwrap();
174
175        assert_eq!(shared_secret, decapsulated);
176        assert_eq!(
177            hex::encode(&public_key[..32]),
178            "2dd29da8b193397a4336c02382aab3bcfbac25f0cd71c888af379e1e75149a79"
179        );
180        assert_eq!(
181            hex::encode(&ciphertext[..32]),
182            "5f12f173ef59a45f910d3a225913f3297b2277636a72401a273648015cccf079"
183        );
184        assert_eq!(
185            hex::encode(shared_secret),
186            "8bf157178aa556b55f95686ba9b5afe13a6b75c848f1ddd9a334d50287bec24e"
187        );
188    }
189
190    #[test]
191    fn ml_kem_1024_cctv_accumulated_10k() {
192        use crate::{Xof, sha3::Shake128};
193
194        let mut rng = Shake128::new();
195        rng.absorb(&[]);
196
197        let mut acc = Shake128::new();
198
199        for _ in 0..10_000u32 {
200            let mut d = [0u8; 32];
201            let mut z = [0u8; 32];
202            let mut m = [0u8; 32];
203            let mut ct_random = [0u8; CIPHERTEXT_SIZE_1024];
204
205            rng.squeeze(&mut d);
206            rng.squeeze(&mut z);
207            rng.squeeze(&mut m);
208            rng.squeeze(&mut ct_random);
209
210            let mut coins = [0u8; 64];
211            coins[..32].copy_from_slice(&d);
212            coins[32..].copy_from_slice(&z);
213
214            let (dk, ek) =
215                crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &coins);
216            let (ct, k_encaps) =
217                crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &ek, &m);
218
219            let k_decaps =
220                crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &dk, &ct).unwrap();
221            assert_eq!(k_encaps, k_decaps);
222
223            let k_decaps_random =
224                crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &dk, &ct_random).unwrap();
225
226            acc.absorb(&ek);
227            acc.absorb(&dk);
228            acc.absorb(&ct);
229            acc.absorb(&k_encaps);
230            acc.absorb(&k_decaps_random);
231        }
232
233        let mut hash = [0u8; 32];
234        acc.squeeze(&mut hash);
235        assert_eq!(
236            hex::encode(hash),
237            "e3bf82b013307b2e9d47dde791ff6dfc82e694e6382404abdb948b908b75bad5",
238            "ML-KEM-1024 CCTV accumulated hash mismatch"
239        );
240    }
241
242    #[test]
243    fn ml_kem_1024_cctv_intermediate_vector() {
244        let d: [u8; 32] = decode_hex_array("2a62c39ef4fc499f2d132716f480bb7521a49558ae84ee80d9352e66daf1e3a8");
245        let z: [u8; 32] = decode_hex_array("5f574ef7f013d4336801fed022178c3ed91d0b6d51325315fc1dcabf4770a2ea");
246        let m: [u8; 32] = decode_hex_array("e07d685ed308e609c9c7842026e35732f6ffc6e2fee10f0afd348f2b42a8acb4");
247
248        let mut coins = [0u8; 64];
249        coins[..32].copy_from_slice(&d);
250        coins[32..].copy_from_slice(&z);
251
252        let (dk, ek) = crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &coins);
253        let (ct, k) = crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &ek, &m);
254
255        assert_eq!(
256            sha3_256_hex(&ek),
257            "3b308d1344ed70366b84d790acb705b86cd3dfd471fff171969aaa338f26dca5"
258        );
259        assert_eq!(
260            sha3_256_hex(&dk),
261            "aa63a9e0c035ada6635e7938b71856b24917ff9b3ebca1a4d205a83b502a415a"
262        );
263        assert_eq!(
264            sha3_256_hex(&ct),
265            "8caba02733421f12a7ba9a2bcbe4de7c9853156a0637df5a7a0f9127c81da943"
266        );
267        assert_eq!(
268            hex::encode(k),
269            "d53825c3ff666bb2881215dbec04a8bdce9099b2a3680938c2f199b54d505953"
270        );
271    }
272
273    #[test]
274    fn ml_kem_1024_decapsulation_rejects_tampered_ciphertext() {
275        let (private_key, public_key) = generate_keypair_1024();
276        let (mut ciphertext, encapsulated_secret) = public_key.encapsulate();
277
278        ciphertext[0] ^= 0x80;
279
280        let decapsulated_secret = private_key.decapsulate(&ciphertext).unwrap();
281
282        assert_ne!(encapsulated_secret, decapsulated_secret);
283    }
284
285    #[test]
286    fn ml_kem_1024_decapsulation_with_wrong_key_rejects() {
287        let (_, alice_pk) = generate_keypair_1024();
288        let (bob_sk, _bob_pk) = generate_keypair_1024();
289        let (ct, _alice_ss) = alice_pk.encapsulate();
290
291        let wrong_ss = bob_sk.decapsulate(&ct).unwrap();
292        assert_ne!(_alice_ss, wrong_ss);
293    }
294
295    #[test]
296    fn ml_kem_1024_round_trip_many() {
297        for _ in 0..100 {
298            let (sk, pk) = generate_keypair_1024();
299            let (ct, ss_enc) = pk.encapsulate();
300            let ss_dec = sk.decapsulate(&ct).unwrap();
301            assert_eq!(ss_enc, ss_dec);
302        }
303    }
304
305    #[test]
306    fn ml_kem_1024_all_zero_ciphertext_does_not_panic() {
307        let (sk, _pk) = generate_keypair_1024();
308        let ct = [0u8; CIPHERTEXT_SIZE_1024];
309        let _result = sk.decapsulate(&ct);
310    }
311
312    #[test]
313    fn ml_kem_1024_all_ones_ciphertext_does_not_panic() {
314        let (sk, _pk) = generate_keypair_1024();
315        let ct = [0xffu8; CIPHERTEXT_SIZE_1024];
316        let _result = sk.decapsulate(&ct);
317    }
318
319    #[test]
320    fn ml_kem_1024_derand_keygen_is_deterministic() {
321        let coins = [3u8; 64];
322        let (sk1, pk1) =
323            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &coins);
324        let (sk2, pk2) =
325            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &coins);
326        assert_eq!(sk1, sk2);
327        assert_eq!(pk1, pk2);
328    }
329
330    #[test]
331    fn ml_kem_1024_key_sizes_are_correct() {
332        let (sk, pk) = generate_keypair_1024();
333        let sk_bytes = sk.to_bytes();
334        let pk_bytes = pk.to_bytes();
335        assert_eq!(sk_bytes.len(), SECRET_KEY_SIZE_1024);
336        assert_eq!(pk_bytes.len(), PUBLIC_KEY_SIZE_1024);
337        let (ct, _) = pk.encapsulate();
338        assert_eq!(ct.len(), CIPHERTEXT_SIZE_1024);
339    }
340
341    #[test]
342    fn ml_kem_1024_encaps_is_deterministic_with_same_coins() {
343        let enc_coins = [5u8; 32];
344        let key_coins = [3u8; 64];
345        let (_sk, pk) =
346            crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &key_coins);
347        let (ct1, ss1) =
348            crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &pk, &enc_coins);
349        let (ct2, ss2) =
350            crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &pk, &enc_coins);
351        assert_eq!(ct1, ct2);
352        assert_eq!(ss1, ss2);
353    }
354
355    #[test]
356    fn ml_kem_1024_decapsulation_with_wrong_key_is_deterministic() {
357        let (_, pk_a) = generate_keypair_1024();
358        let (sk_b, _pk_b) = generate_keypair_1024();
359        let (ct, _) = pk_a.encapsulate();
360
361        let ss1 = sk_b.decapsulate(&ct).unwrap();
362        let ss2 = sk_b.decapsulate(&ct).unwrap();
363        assert_eq!(ss1, ss2, "implicit rejection must be deterministic");
364    }
365
366    #[test]
367    fn ml_kem_1024_wycheproof_keygen() {
368        let data: serde_json::Value = serde_json::from_str(include_str!(
369            "../../testdata/wycheproof/testvectors_v1/mlkem_1024_keygen_seed_test.json"
370        ))
371        .unwrap();
372        let mut tested = 0u64;
373        for group in data["testGroups"].as_array().unwrap() {
374            if group["parameterSet"].as_str() != Some("ML-KEM-1024") {
375                continue;
376            }
377            for test in group["tests"].as_array().unwrap() {
378                let seed_hex = test["seed"].as_str().unwrap();
379                let expected_ek_hex = test["ek"].as_str().unwrap();
380                let expected_dk_hex = test["dk"].as_str().unwrap();
381                let result = test["result"].as_str().unwrap();
382
383                let seed = hex::decode_array::<64>(seed_hex.as_bytes()).unwrap();
384
385                let (dk, ek) =
386                    crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &seed);
387
388                let ek_hex = hex::encode(ek);
389                let dk_hex = hex::encode(dk);
390
391                if result == "valid" {
392                    assert_eq!(
393                        ek_hex, expected_ek_hex,
394                        "wycheproof keygen KAT tcId={} ek mismatch",
395                        test["tcId"]
396                    );
397                    assert_eq!(
398                        dk_hex, expected_dk_hex,
399                        "wycheproof keygen KAT tcId={} dk mismatch",
400                        test["tcId"]
401                    );
402                }
403                tested += 1;
404            }
405        }
406        assert!(tested > 0, "no ML-KEM-1024 keygen tests were run");
407    }
408
409    fn wycheproof_kem_skip_invalid_lengths(seed_hex: &str, c_hex: &str, ct_size: usize) -> bool {
410        seed_hex.len() != 128 || c_hex.len() != ct_size * 2
411    }
412
413    #[test]
414    fn ml_kem_1024_wycheproof_kem() {
415        let data: serde_json::Value =
416            serde_json::from_str(include_str!("../../testdata/wycheproof/testvectors_v1/mlkem_1024_test.json"))
417                .unwrap();
418        let mut tested = 0u64;
419        for group in data["testGroups"].as_array().unwrap() {
420            if group["parameterSet"].as_str() != Some("ML-KEM-1024") {
421                continue;
422            }
423            for test in group["tests"].as_array().unwrap() {
424                let seed_hex = test["seed"].as_str().unwrap();
425                let c_hex = test["c"].as_str().unwrap();
426                let expected_k_hex = test["K"].as_str().unwrap();
427                let result = test["result"].as_str().unwrap();
428
429                if wycheproof_kem_skip_invalid_lengths(seed_hex, c_hex, CIPHERTEXT_SIZE_1024) {
430                    tested += 1;
431                    continue;
432                }
433
434                let seed = hex::decode_array::<64>(seed_hex.as_bytes()).unwrap();
435
436                let (dk, ek) =
437                    crypto_kem_keypair_derand::<4, SECRET_KEY_SIZE_1024, PUBLIC_KEY_SIZE_1024>(&ML_KEM_1024, &seed);
438
439                if let Some(expected_ek_hex) = test.get("ek").and_then(|v| v.as_str()) {
440                    let ek_hex = hex::encode(ek);
441                    assert_eq!(ek_hex, expected_ek_hex, "wycheproof KEM KAT tcId={} ek mismatch", test["tcId"]);
442                }
443
444                let c = decode_hex_array::<CIPHERTEXT_SIZE_1024>(c_hex);
445                let shared_secret =
446                    crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &dk, &c);
447
448                if result == "valid" {
449                    let k = shared_secret.unwrap();
450                    let k_hex = hex::encode(k);
451                    assert_eq!(k_hex, expected_k_hex, "wycheproof KEM KAT tcId={} K mismatch", test["tcId"]);
452                } else {
453                    assert!(
454                        shared_secret.is_ok(),
455                        "wycheproof KEM KAT tcId={} unexpected error",
456                        test["tcId"]
457                    );
458                }
459                tested += 1;
460            }
461        }
462        assert!(tested > 0, "no ML-KEM-1024 KEM tests were run");
463    }
464
465    #[test]
466    fn ml_kem_1024_wycheproof_encaps() {
467        let data: serde_json::Value = serde_json::from_str(include_str!(
468            "../../testdata/wycheproof/testvectors_v1/mlkem_1024_encaps_test.json"
469        ))
470        .unwrap();
471        let mut tested = 0u64;
472        for group in data["testGroups"].as_array().unwrap() {
473            if group["parameterSet"].as_str() != Some("ML-KEM-1024") {
474                continue;
475            }
476            for test in group["tests"].as_array().unwrap() {
477                let ek_hex = test["ek"].as_str().unwrap();
478                let m_hex = test["m"].as_str().unwrap();
479                let expected_c_hex = test["c"].as_str().unwrap();
480                let expected_k_hex = test["K"].as_str().unwrap();
481                let result = test["result"].as_str().unwrap();
482
483                if ek_hex.len() != PUBLIC_KEY_SIZE_1024 * 2 {
484                    tested += 1;
485                    continue;
486                }
487
488                let ek = decode_hex_array::<PUBLIC_KEY_SIZE_1024>(ek_hex);
489
490                if result == "valid" {
491                    let m = decode_hex_array::<32>(m_hex);
492                    let (c, k) =
493                        crypto_kem_enc_derand::<4, PUBLIC_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &ek, &m);
494                    let c_hex_out = hex::encode(c);
495                    let k_hex_out = hex::encode(k);
496                    assert_eq!(
497                        c_hex_out, expected_c_hex,
498                        "wycheproof encaps KAT tcId={} c mismatch",
499                        test["tcId"]
500                    );
501                    assert_eq!(
502                        k_hex_out, expected_k_hex,
503                        "wycheproof encaps KAT tcId={} K mismatch",
504                        test["tcId"]
505                    );
506                }
507                tested += 1;
508            }
509        }
510        assert!(tested > 0, "no ML-KEM-1024 encaps tests were run");
511    }
512
513    #[test]
514    fn ml_kem_1024_wycheproof_decaps_validation() {
515        let data: serde_json::Value = serde_json::from_str(include_str!(
516            "../../testdata/wycheproof/testvectors_v1/mlkem_1024_semi_expanded_decaps_test.json"
517        ))
518        .unwrap();
519        let mut tested = 0u64;
520        for group in data["testGroups"].as_array().unwrap() {
521            if group["parameterSet"].as_str() != Some("ML-KEM-1024") {
522                continue;
523            }
524            for test in group["tests"].as_array().unwrap() {
525                let flags: Vec<&str> = test["flags"]
526                    .as_array()
527                    .map(|a| a.iter().filter_map(|v| v.as_str()).collect())
528                    .unwrap_or_default();
529                let dk_hex = test["dk"].as_str().unwrap();
530                let c_hex = test["c"].as_str().unwrap();
531
532                if flags.contains(&"IncorrectDecapsulationKeyLength") || flags.contains(&"IncorrectCiphertextLength") {
533                    tested += 1;
534                    continue;
535                }
536
537                let dk = decode_hex_array::<SECRET_KEY_SIZE_1024>(dk_hex);
538                let c = decode_hex_array::<CIPHERTEXT_SIZE_1024>(c_hex);
539
540                let result = crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &dk, &c);
541
542                assert!(result.is_ok(), "wycheproof decaps tcId={} panicked", test["tcId"]);
543                tested += 1;
544            }
545        }
546        assert!(tested > 0, "no ML-KEM-1024 decaps validation tests were run");
547    }
548
549    #[test]
550    fn ml_kem_1024_cross_implementation_pqcrypto() {
551        // Cross-implementation validation: decapsulate ciphertexts generated by
552        // the pqcrypto (liboqs) ML-KEM-1024 implementation.
553        let data: serde_json::Value =
554            serde_json::from_str(include_str!("../../testdata/mlkem/pqcrypto_1024_vectors.json")).unwrap();
555        let vectors = data.as_array().unwrap();
556        assert!(vectors.len() >= 5, "not enough cross-impl vectors");
557
558        for (i, vector) in vectors.iter().enumerate() {
559            let sk_hex = vector["sk"].as_str().unwrap();
560            let ct_hex = vector["ct"].as_str().unwrap();
561            let expected_ss_hex = vector["ss"].as_str().unwrap();
562
563            let sk = decode_hex_array::<SECRET_KEY_SIZE_1024>(sk_hex);
564            let ct = decode_hex_array::<CIPHERTEXT_SIZE_1024>(ct_hex);
565
566            let ss = crypto_kem_dec::<4, SECRET_KEY_SIZE_1024, CIPHERTEXT_SIZE_1024>(&ML_KEM_1024, &sk, &ct).unwrap();
567            assert_eq!(
568                hex::encode(ss),
569                expected_ss_hex,
570                "cross-impl pqcrypto vector {i} decapsulation mismatch"
571            );
572        }
573    }
574}