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