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#[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#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct PublicKey1024 {
36 bytes: [u8; PUBLIC_KEY_SIZE_1024],
37}
38
39impl SecretKey1024 {
40 pub fn from_bytes(bytes: &[u8; SECRET_KEY_SIZE_1024]) -> Self {
45 Self {
46 bytes: *bytes,
47 }
48 }
49
50 pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE_1024] {
52 self.bytes
53 }
54
55 #[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 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 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 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 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 pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE_1024] {
138 self.bytes
139 }
140
141 #[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 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 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 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 #[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 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}