1use super::mlkem::{
2 ML_KEM_768, 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_768: usize = 1184;
7pub const SECRET_KEY_SIZE_768: usize = 2400;
8pub const CIPHERTEXT_SIZE_768: usize = 1088;
9
10#[derive(Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop))]
27pub struct SecretKey768 {
28 bytes: [u8; SECRET_KEY_SIZE_768],
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct PublicKey768 {
36 bytes: [u8; PUBLIC_KEY_SIZE_768],
37}
38
39impl SecretKey768 {
40 pub fn from_bytes(bytes: &[u8; SECRET_KEY_SIZE_768]) -> Self {
41 Self {
42 bytes: *bytes,
43 }
44 }
45
46 pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE_768] {
47 self.bytes
48 }
49
50 #[cfg(feature = "random")]
55 pub fn generate() -> Result<(Self, PublicKey768), MlKemError> {
56 let coins: [u8; 64] = crate::random::bytes()?;
57 Ok(Self::generate_derand(&coins))
58 }
59
60 pub fn generate_derand(coins: &[u8; 64]) -> (Self, PublicKey768) {
61 let (sk_bytes, pk_bytes) =
62 crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, coins);
63 (
64 Self {
65 bytes: sk_bytes,
66 },
67 PublicKey768 {
68 bytes: pk_bytes,
69 },
70 )
71 }
72
73 pub fn decapsulate(&self, ciphertext: &[u8; CIPHERTEXT_SIZE_768]) -> Result<[u8; SHARED_SECRET_SIZE], MlKemError> {
83 crypto_kem_dec::<3, SECRET_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &self.bytes, ciphertext)
84 }
85
86 pub fn public_key(&self) -> PublicKey768 {
87 let offset = indcpa_secret_key_bytes::<3>();
88 let mut pk_bytes = [0u8; PUBLIC_KEY_SIZE_768];
89 pk_bytes.copy_from_slice(&self.bytes[offset..offset + PUBLIC_KEY_SIZE_768]);
90 PublicKey768 {
91 bytes: pk_bytes,
92 }
93 }
94}
95
96impl From<&[u8; SECRET_KEY_SIZE_768]> for SecretKey768 {
97 fn from(bytes: &[u8; SECRET_KEY_SIZE_768]) -> Self {
98 Self::from_bytes(bytes)
99 }
100}
101
102impl TryFrom<&[u8]> for SecretKey768 {
103 type Error = MlKemError;
104
105 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
106 Ok(Self::from_bytes(bytes.try_into().map_err(|_| MlKemError::InvalidKey)?))
107 }
108}
109
110impl PublicKey768 {
111 pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_SIZE_768]) -> Result<Self, MlKemError> {
117 if !encapsulation_key_modulus_check::<3>(bytes) {
118 return Err(MlKemError::InvalidKey);
119 }
120 Ok(Self {
121 bytes: *bytes,
122 })
123 }
124
125 pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE_768] {
126 self.bytes
127 }
128
129 #[cfg(feature = "random")]
134 pub fn encapsulate(&self) -> Result<([u8; SHARED_SECRET_SIZE], [u8; CIPHERTEXT_SIZE_768]), MlKemError> {
135 let coins: [u8; 32] = crate::random::bytes()?;
136 Ok(self.encapsulate_derand(&coins))
137 }
138
139 pub fn encapsulate_derand(&self, coins: &[u8; 32]) -> ([u8; SHARED_SECRET_SIZE], [u8; CIPHERTEXT_SIZE_768]) {
146 crypto_kem_enc_derand::<3, PUBLIC_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &self.bytes, coins)
147 }
148}
149
150impl TryFrom<&[u8]> for PublicKey768 {
151 type Error = MlKemError;
152
153 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
154 Self::from_bytes(bytes.try_into().map_err(|_| MlKemError::InvalidKey)?)
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::{
161 super::mlkem::{
162 ML_KEM_768, Q, crypto_kem_dec, crypto_kem_enc_derand, crypto_kem_keypair_derand, decode_hex_array,
163 sha3_256_hex,
164 },
165 *,
166 };
167
168 #[test]
169 fn ml_kem_768_round_trip() {
170 let (private_key, public_key) = SecretKey768::generate().unwrap();
171 let (encapsulated_secret, ciphertext) = public_key.encapsulate().unwrap();
172 let decapsulated_secret = private_key.decapsulate(&ciphertext).unwrap();
173
174 assert_eq!(encapsulated_secret, decapsulated_secret);
175 }
176
177 #[test]
178 fn ml_kem_768_decapsulation_rejects_tampered_ciphertext() {
179 let (private_key, public_key) = SecretKey768::generate().unwrap();
180 let (encapsulated_secret, mut ciphertext) = public_key.encapsulate().unwrap();
181
182 ciphertext[0] ^= 0x80;
183
184 let decapsulated_secret = private_key.decapsulate(&ciphertext).unwrap();
185
186 assert_ne!(encapsulated_secret, decapsulated_secret);
187 }
188
189 #[test]
190 fn ml_kem_768_deterministic_derand_vectors_are_stable() {
191 let key_coins = [7u8; 64];
192 let enc_coins = [9u8; 32];
193 let (secret_key, public_key) =
194 crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, &key_coins);
195 let (shared_secret, ciphertext) =
196 crypto_kem_enc_derand::<3, PUBLIC_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &public_key, &enc_coins);
197 let decapsulated =
198 crypto_kem_dec::<3, SECRET_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &secret_key, &ciphertext)
199 .unwrap();
200
201 assert_eq!(shared_secret, decapsulated);
202 assert_eq!(
203 hex::encode(&public_key[..32]),
204 "925a2700ad064ff778b4da4cf51457a48224a52751250a8ee10b251c818bafca"
205 );
206 assert_eq!(
207 hex::encode(&ciphertext[..32]),
208 "766c326c3483444c5b6d917cdddc3c07fbf935295c8f17c92a187a80dc4d15f2"
209 );
210 assert_eq!(
211 hex::encode(shared_secret),
212 "afcf18dfd6b710a09b5cf591d0eb8229d83aa10904934a3ca60a52da5ff36b96"
213 );
214 }
215
216 #[test]
217 fn ml_kem_768_cctv_accumulated_10k() {
218 use crate::{Xof, sha3::Shake128};
219
220 let mut rng = Shake128::new();
221 rng.absorb(&[]);
222
223 let mut acc = Shake128::new();
224
225 for _ in 0..10_000u32 {
226 let mut d = [0u8; 32];
227 let mut z = [0u8; 32];
228 let mut m = [0u8; 32];
229 let mut ct_random = [0u8; CIPHERTEXT_SIZE_768];
230
231 rng.squeeze(&mut d);
232 rng.squeeze(&mut z);
233 rng.squeeze(&mut m);
234 rng.squeeze(&mut ct_random);
235
236 let mut coins = [0u8; 64];
237 coins[..32].copy_from_slice(&d);
238 coins[32..].copy_from_slice(&z);
239
240 let (dk, ek) =
241 crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, &coins);
242 let (k_encaps, ct) =
243 crypto_kem_enc_derand::<3, PUBLIC_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &ek, &m);
244
245 let k_decaps =
246 crypto_kem_dec::<3, SECRET_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &dk, &ct).unwrap();
247 assert_eq!(k_encaps, k_decaps);
248
249 let k_decaps_random =
250 crypto_kem_dec::<3, SECRET_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &dk, &ct_random).unwrap();
251
252 acc.absorb(&ek);
253 acc.absorb(&dk);
254 acc.absorb(&ct);
255 acc.absorb(&k_encaps);
256 acc.absorb(&k_decaps_random);
257 }
258
259 let mut hash = [0u8; 32];
260 acc.squeeze(&mut hash);
261 assert_eq!(
262 hex::encode(hash),
263 "f959d18d3d1180121433bf0e05f11e7908cf9d03edc150b2b07cb90bef5bc1c1",
264 "ML-KEM-768 CCTV accumulated hash mismatch"
265 );
266 }
267
268 #[test]
269 fn ml_kem_768_cctv_intermediate_vector() {
270 let d: [u8; 32] = decode_hex_array("f688563f7c66a5da2d8bdb5a5f3e07bd8dce6f7efcec7f41298d79863459f7cd");
271 let z: [u8; 32] = decode_hex_array("d1d49a515250dbceb9f6e3fcc1c7d5306918964b21ddb22207e03e57f0600da8");
272 let m: [u8; 32] = decode_hex_array("3dc27ca0a6594b0e56320457c45a0f76bb8a213ea4a76d442186a0aefadbcdb9");
273
274 let mut coins = [0u8; 64];
275 coins[..32].copy_from_slice(&d);
276 coins[32..].copy_from_slice(&z);
277
278 let (dk, ek) = crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, &coins);
279 let (k, ct) = crypto_kem_enc_derand::<3, PUBLIC_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &ek, &m);
280
281 assert_eq!(
282 sha3_256_hex(&ek),
283 "42d930a50dfd1f0541ca45c4598daebb4f51cd10d711a001bd9bb87d5c87a4bf"
284 );
285 assert_eq!(
286 sha3_256_hex(&dk),
287 "db563aebd9fdc875e88563693edad1e5e359cc37b0f685d2d0a3723b37253192"
288 );
289 assert_eq!(
290 sha3_256_hex(&ct),
291 "9d6e358208c4d583050becb319050b7f916de47caad1d589a1d01fea43fe1750"
292 );
293 assert_eq!(
294 hex::encode(k),
295 "ae726da2df66601c6648a7565c02b203a089276ac30f6cc226d048f93fafd78c"
296 );
297 }
298
299 #[test]
300 fn ml_kem_768_decapsulation_with_wrong_key_rejects() {
301 let (_, alice_pk) = SecretKey768::generate().unwrap();
302 let (bob_sk, _bob_pk) = SecretKey768::generate().unwrap();
303 let (_alice_ss, ct) = alice_pk.encapsulate().unwrap();
304
305 let wrong_ss = bob_sk.decapsulate(&ct).unwrap();
306 assert_ne!(_alice_ss, wrong_ss);
307 }
308
309 #[test]
310 fn ml_kem_768_round_trip_many() {
311 for _ in 0..100 {
312 let (sk, pk) = SecretKey768::generate().unwrap();
313 let (ss_enc, ct) = pk.encapsulate().unwrap();
314 let ss_dec = sk.decapsulate(&ct).unwrap();
315 assert_eq!(ss_enc, ss_dec);
316 }
317 }
318
319 #[test]
320 fn ml_kem_768_all_zero_ciphertext_does_not_panic() {
321 let (sk, _pk) = SecretKey768::generate().unwrap();
322 let ct = [0u8; CIPHERTEXT_SIZE_768];
323 let _result = sk.decapsulate(&ct);
324 }
325
326 #[test]
327 fn ml_kem_768_all_ones_ciphertext_does_not_panic() {
328 let (sk, _pk) = SecretKey768::generate().unwrap();
329 let ct = [0xffu8; CIPHERTEXT_SIZE_768];
330 let _result = sk.decapsulate(&ct);
331 }
332
333 #[test]
334 fn ml_kem_768_derand_keygen_is_deterministic() {
335 let coins = [7u8; 64];
336 let (sk1, pk1) = crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, &coins);
337 let (sk2, pk2) = crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, &coins);
338 assert_eq!(sk1, sk2);
339 assert_eq!(pk1, pk2);
340 }
341
342 #[test]
343 fn ml_kem_768_key_sizes_are_correct() {
344 let (sk, pk) = SecretKey768::generate().unwrap();
345 let sk_bytes = sk.to_bytes();
346 let pk_bytes = pk.to_bytes();
347 assert_eq!(sk_bytes.len(), SECRET_KEY_SIZE_768);
348 assert_eq!(pk_bytes.len(), PUBLIC_KEY_SIZE_768);
349 let (_, ct) = pk.encapsulate().unwrap();
350 assert_eq!(ct.len(), CIPHERTEXT_SIZE_768);
351 }
352
353 #[test]
354 fn ml_kem_768_rejects_non_canonical_encapsulation_key() {
355 let (_sk, pk) = SecretKey768::generate().unwrap();
356 let base = pk.to_bytes();
357
358 assert!(PublicKey768::from_bytes(&base).is_ok());
360
361 let set_first = |c: u16| {
364 let mut bytes = base;
365 bytes[0] = (c & 0xff) as u8;
366 bytes[1] = (bytes[1] & 0xf0) | ((c >> 8) as u8 & 0x0f);
367 bytes
368 };
369
370 assert!(PublicKey768::from_bytes(&set_first(Q as u16)).is_err());
371 assert!(PublicKey768::from_bytes(&set_first(0x0fff)).is_err());
372 assert!(PublicKey768::from_bytes(&set_first(Q as u16 - 1)).is_ok());
373 }
374
375 #[test]
376 fn ml_kem_768_encaps_is_deterministic_with_same_coins() {
377 let enc_coins = [9u8; 32];
378 let key_coins = [7u8; 64];
379 let (_sk, pk) =
380 crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, &key_coins);
381 let (ss1, ct1) =
382 crypto_kem_enc_derand::<3, PUBLIC_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &pk, &enc_coins);
383 let (ss2, ct2) =
384 crypto_kem_enc_derand::<3, PUBLIC_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &pk, &enc_coins);
385 assert_eq!(ct1, ct2);
386 assert_eq!(ss1, ss2);
387 }
388
389 #[test]
390 fn ml_kem_768_decapsulation_with_wrong_key_is_deterministic() {
391 let (_, pk_a) = SecretKey768::generate().unwrap();
392 let (sk_b, _pk_b) = SecretKey768::generate().unwrap();
393 let (_, ct) = pk_a.encapsulate().unwrap();
394
395 let ss1 = sk_b.decapsulate(&ct).unwrap();
396 let ss2 = sk_b.decapsulate(&ct).unwrap();
397 assert_eq!(ss1, ss2, "implicit rejection must be deterministic");
398 }
399
400 #[test]
401 fn ml_kem_768_wycheproof_keygen() {
402 let data: serde_json::Value = serde_json::from_str(include_str!(
403 "../../testdata/wycheproof/testvectors_v1/mlkem_768_keygen_seed_test.json"
404 ))
405 .unwrap();
406 let mut tested = 0u64;
407 for group in data["testGroups"].as_array().unwrap() {
408 if group["parameterSet"].as_str() != Some("ML-KEM-768") {
409 continue;
410 }
411 for test in group["tests"].as_array().unwrap() {
412 let seed_hex = test["seed"].as_str().unwrap();
413 let expected_ek_hex = test["ek"].as_str().unwrap();
414 let expected_dk_hex = test["dk"].as_str().unwrap();
415 let result = test["result"].as_str().unwrap();
416
417 let seed = hex::decode_array::<64>(seed_hex.as_bytes()).unwrap();
418
419 let (dk, ek) =
420 crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, &seed);
421
422 let ek_hex = hex::encode(ek);
423 let dk_hex = hex::encode(dk);
424
425 if result == "valid" {
426 assert_eq!(
427 ek_hex, expected_ek_hex,
428 "wycheproof keygen KAT tcId={} ek mismatch",
429 test["tcId"]
430 );
431 assert_eq!(
432 dk_hex, expected_dk_hex,
433 "wycheproof keygen KAT tcId={} dk mismatch",
434 test["tcId"]
435 );
436 }
437 tested += 1;
438 }
439 }
440 assert!(tested > 0, "no ML-KEM-768 keygen tests were run");
441 }
442
443 fn wycheproof_kem_has_invalid_lengths(seed_hex: &str, c_hex: &str, ct_size: usize) -> bool {
444 seed_hex.len() != 128 || c_hex.len() != ct_size * 2
445 }
446
447 #[test]
448 fn ml_kem_768_wycheproof_kem() {
449 let data: serde_json::Value =
450 serde_json::from_str(include_str!("../../testdata/wycheproof/testvectors_v1/mlkem_768_test.json")).unwrap();
451 let mut tested = 0u64;
452 let mut skipped = 0u64;
453 for group in data["testGroups"].as_array().unwrap() {
454 if group["parameterSet"].as_str() != Some("ML-KEM-768") {
455 continue;
456 }
457 for test in group["tests"].as_array().unwrap() {
458 let seed_hex = test["seed"].as_str().unwrap();
459 let c_hex = test["c"].as_str().unwrap();
460 let expected_k_hex = test["K"].as_str().unwrap();
461 let result = test["result"].as_str().unwrap();
462
463 if wycheproof_kem_has_invalid_lengths(seed_hex, c_hex, CIPHERTEXT_SIZE_768) {
464 assert!(
468 hex::decode(seed_hex).unwrap().len() != 64
469 || hex::decode(c_hex).unwrap().len() != CIPHERTEXT_SIZE_768,
470 "wycheproof KEM KAT tcId={} unexpectedly has correct lengths",
471 test["tcId"]
472 );
473 skipped += 1;
474 continue;
475 }
476
477 let seed = hex::decode_array::<64>(seed_hex.as_bytes()).unwrap();
478
479 let (dk, ek) =
480 crypto_kem_keypair_derand::<3, SECRET_KEY_SIZE_768, PUBLIC_KEY_SIZE_768>(&ML_KEM_768, &seed);
481
482 if let Some(expected_ek_hex) = test.get("ek").and_then(|v| v.as_str()) {
483 let ek_hex = hex::encode(ek);
484 assert_eq!(ek_hex, expected_ek_hex, "wycheproof KEM KAT tcId={} ek mismatch", test["tcId"]);
485 }
486
487 let c = decode_hex_array::<CIPHERTEXT_SIZE_768>(c_hex);
488 let shared_secret = crypto_kem_dec::<3, SECRET_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &dk, &c);
489
490 if result == "valid" {
491 let k = shared_secret.unwrap();
492 let k_hex = hex::encode(k);
493 assert_eq!(k_hex, expected_k_hex, "wycheproof KEM KAT tcId={} K mismatch", test["tcId"]);
494 } else {
495 assert!(
496 shared_secret.is_ok(),
497 "wycheproof KEM KAT tcId={} unexpected error",
498 test["tcId"]
499 );
500 }
501 tested += 1;
502 }
503 }
504 assert!(tested > 0, "no ML-KEM-768 KEM tests were run");
505 eprintln!("wycheproof ML-KEM-768 KEM: {tested} tested, {skipped} skipped (wrong-length inputs)");
506 }
507
508 #[test]
509 fn ml_kem_768_wycheproof_encaps() {
510 let data: serde_json::Value = serde_json::from_str(include_str!(
511 "../../testdata/wycheproof/testvectors_v1/mlkem_768_encaps_test.json"
512 ))
513 .unwrap();
514 let mut tested = 0u64;
515 let mut skipped = 0u64;
516 for group in data["testGroups"].as_array().unwrap() {
517 if group["parameterSet"].as_str() != Some("ML-KEM-768") {
518 continue;
519 }
520 for test in group["tests"].as_array().unwrap() {
521 let ek_hex = test["ek"].as_str().unwrap();
522 let m_hex = test["m"].as_str().unwrap();
523 let expected_c_hex = test["c"].as_str().unwrap();
524 let expected_k_hex = test["K"].as_str().unwrap();
525 let result = test["result"].as_str().unwrap();
526
527 if ek_hex.len() != PUBLIC_KEY_SIZE_768 * 2 {
528 let ek_bytes = hex::decode(ek_hex).unwrap();
529 assert!(
530 PublicKey768::try_from(ek_bytes.as_slice()).is_err(),
531 "wycheproof encaps tcId={} flagged wrong-length ek but it parsed",
532 test["tcId"]
533 );
534 skipped += 1;
535 continue;
536 }
537
538 let ek = decode_hex_array::<PUBLIC_KEY_SIZE_768>(ek_hex);
539
540 if result == "valid" {
541 let public_key = PublicKey768::from_bytes(&ek)
542 .unwrap_or_else(|_| panic!("wycheproof encaps tcId={} valid ek rejected", test["tcId"]));
543 let m = decode_hex_array::<32>(m_hex);
544 let (k, c) = public_key.encapsulate_derand(&m);
545 let c_hex_out = hex::encode(c);
546 let k_hex_out = hex::encode(k);
547 assert_eq!(
548 c_hex_out, expected_c_hex,
549 "wycheproof encaps KAT tcId={} c mismatch",
550 test["tcId"]
551 );
552 assert_eq!(
553 k_hex_out, expected_k_hex,
554 "wycheproof encaps KAT tcId={} K mismatch",
555 test["tcId"]
556 );
557 } else {
558 assert!(
559 PublicKey768::from_bytes(&ek).is_err(),
560 "wycheproof encaps tcId={} invalid ek accepted",
561 test["tcId"]
562 );
563 }
564 tested += 1;
565 }
566 }
567 assert!(tested > 0, "no ML-KEM-768 encaps tests were run");
568 eprintln!("wycheproof ML-KEM-768 encaps: {tested} tested, {skipped} skipped (wrong-length ek)");
569 }
570
571 #[test]
572 fn ml_kem_768_wycheproof_decaps_validation() {
573 let data: serde_json::Value = serde_json::from_str(include_str!(
574 "../../testdata/wycheproof/testvectors_v1/mlkem_768_semi_expanded_decaps_test.json"
575 ))
576 .unwrap();
577 let mut valid = 0u64;
578 let mut invalid = 0u64;
579 let mut skipped = 0u64;
580 for group in data["testGroups"].as_array().unwrap() {
581 if group["parameterSet"].as_str() != Some("ML-KEM-768") {
582 continue;
583 }
584 for test in group["tests"].as_array().unwrap() {
585 let flags: Vec<&str> = test["flags"]
586 .as_array()
587 .map(|a| a.iter().filter_map(|v| v.as_str()).collect())
588 .unwrap_or_default();
589 let tc_id = test["tcId"].clone();
590 let dk_hex = test["dk"].as_str().unwrap();
591 let c_hex = test["c"].as_str().unwrap();
592
593 if flags.contains(&"IncorrectDecapsulationKeyLength") {
594 let dk_bytes = hex::decode(dk_hex).unwrap();
595 assert!(
596 SecretKey768::try_from(dk_bytes.as_slice()).is_err(),
597 "wycheproof decaps tcId={tc_id} flagged wrong-length dk but it parsed",
598 );
599 skipped += 1;
600 continue;
601 }
602
603 if flags.contains(&"IncorrectCiphertextLength") {
604 assert!(
607 hex::decode(c_hex).unwrap().len() != CIPHERTEXT_SIZE_768,
608 "wycheproof decaps tcId={tc_id} flagged wrong-length ct but length is correct",
609 );
610 skipped += 1;
611 continue;
612 }
613
614 let dk = decode_hex_array::<SECRET_KEY_SIZE_768>(dk_hex);
615 let c = decode_hex_array::<CIPHERTEXT_SIZE_768>(c_hex);
616
617 let result = crypto_kem_dec::<3, SECRET_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &dk, &c);
618
619 match test["result"].as_str().unwrap() {
620 "valid" => {
621 let k = result
622 .unwrap_or_else(|_| panic!("wycheproof decaps tcId={tc_id} ({flags:?}): expected success"));
623 let expected = test["K"].as_str().expect("valid vector without K");
624 assert_eq!(
625 hex::encode(k),
626 expected,
627 "wycheproof decaps tcId={tc_id} ({flags:?}) shared secret mismatch"
628 );
629 valid += 1;
630 }
631 "invalid" => {
632 assert!(
633 result.is_err(),
634 "wycheproof decaps tcId={tc_id} ({flags:?}): expected rejection but decapsulation succeeded",
635 );
636 invalid += 1;
637 }
638 other => panic!("wycheproof decaps tcId={tc_id}: unknown result {other}"),
639 }
640 }
641 }
642 assert!(valid > 0, "no valid ML-KEM-768 decaps tests were run");
643 assert!(invalid > 0, "no invalid ML-KEM-768 decaps tests were run");
644 eprintln!("wycheproof ML-KEM-768 decaps: {valid} valid, {invalid} invalid, {skipped} skipped (wrong lengths)");
645 }
646
647 #[test]
653 fn ml_kem_768_implicit_rejection_matches_spec() {
654 use crate::{Xof, sha3::Shake256};
655
656 let (sk, pk) = SecretKey768::generate().unwrap();
657 let sk_bytes = sk.to_bytes();
658 let z = &sk_bytes[SECRET_KEY_SIZE_768 - 32..];
659 let (k_ok, ct) = pk.encapsulate().unwrap();
660
661 for offset in [0, CIPHERTEXT_SIZE_768 / 2, 959, CIPHERTEXT_SIZE_768 - 1] {
662 let mut bad = ct;
663 bad[offset] ^= 0x01;
664 let k_bad = sk.decapsulate(&bad).unwrap();
665 assert_ne!(k_bad, k_ok, "tampered ciphertext byte {offset} returned the valid secret");
666
667 let mut hasher = Shake256::new();
668 hasher.absorb(z);
669 hasher.absorb(&bad);
670 let mut expected = [0u8; SHARED_SECRET_SIZE];
671 hasher.squeeze(&mut expected);
672 assert_eq!(k_bad, expected, "implicit rejection at byte {offset} is not SHAKE256(z||c)");
673 }
674 }
675
676 #[test]
677 fn ml_kem_768_rejects_key_with_wrong_hash() {
678 let (sk, _pk) = SecretKey768::generate().unwrap();
679 let mut bytes = sk.to_bytes();
680 let (_, ct) = sk.public_key().encapsulate().unwrap();
681
682 assert!(SecretKey768::from_bytes(&bytes).decapsulate(&ct).is_ok());
684
685 bytes[SECRET_KEY_SIZE_768 - 64] ^= 0x01;
687 assert_eq!(SecretKey768::from_bytes(&bytes).decapsulate(&ct), Err(MlKemError::InvalidKey));
688
689 let mut bytes = sk.to_bytes();
692 bytes[SECRET_KEY_SIZE_768 - PUBLIC_KEY_SIZE_768 - 64] ^= 0x01;
693 assert_eq!(SecretKey768::from_bytes(&bytes).decapsulate(&ct), Err(MlKemError::InvalidKey));
694 }
695
696 #[test]
697 fn ml_kem_768_cross_implementation_pqcrypto() {
698 let data: serde_json::Value =
701 serde_json::from_str(include_str!("../../testdata/mlkem/pqcrypto_768_vectors.json")).unwrap();
702 let vectors = data.as_array().unwrap();
703 assert!(vectors.len() >= 5, "not enough cross-impl vectors");
704
705 for (i, vector) in vectors.iter().enumerate() {
706 let sk_hex = vector["sk"].as_str().unwrap();
707 let ct_hex = vector["ct"].as_str().unwrap();
708 let expected_ss_hex = vector["ss"].as_str().unwrap();
709
710 let sk = decode_hex_array::<SECRET_KEY_SIZE_768>(sk_hex);
711 let ct = decode_hex_array::<CIPHERTEXT_SIZE_768>(ct_hex);
712
713 let ss = crypto_kem_dec::<3, SECRET_KEY_SIZE_768, CIPHERTEXT_SIZE_768>(&ML_KEM_768, &sk, &ct).unwrap();
714 assert_eq!(
715 hex::encode(ss),
716 expected_ss_hex,
717 "cross-impl pqcrypto vector {i} decapsulation mismatch"
718 );
719 }
720 }
721}