ferritls-core 0.5.0

Pure-Rust cryptographic core for ferritls; designed as a FIPS 140-3 module boundary
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
//! 上电自检(FIPS 140-3 ISO/IEC 19790 §7.9.2 强制项)。
//!
//! 模块首次使用前必须执行**已知答案测试(KAT)**,覆盖全部批准算法:
//! SHA-256/384/512、HMAC-SHA256、HKDF-SHA256、AES-128-GCM、AES-128-CCM、
//! ECDSA P-256(RFC 6979 确定性签名逐字节比对 + 验证)、RSA PKCS#1 v1.5
//! 签名/验证(内建 KAT 密钥)、CTR-DRBG(CAVP 向量流程)。任一失败 →
//! 模块进入错误状态,此后所有密码操作返回
//! [`Error::SelfTestFailed`](crate::Error);整个模块拒绝服务,不做部分
//! 降级(非批准算法亦不例外)。
//!
//! **完整性测试决策(M5 定型)**:KAT 全集事实上锁定了关键常量
//! (S-box、曲线参数、模数、Drbg 常数等)的行为——任何常量损坏都会
//! 使至少一个 KAT 失败。对模块二进制做签名/摘要校验需要平台特定的
//! 加载期机制(代码段自校验在纯 Rust/`forbid(unsafe_code)` 边界内
//! 不可移植实现),属 CMVP 阶段 C 与实验室商定的交付物,此处明确
//! 记录该决策而非提供形式化的空壳实现。
//!
//! 非批准算法(X25519、ChaCha20-Poly1305、Ed25519)不参与 KAT。
//!
//! 里程碑:M5。

use std::sync::Mutex;
use std::sync::atomic::{AtomicU8, Ordering};

use crate::sha2::{Sha256, Sha384, Sha512};

const STATUS_NOT_RUN: u8 = 0;
const STATUS_PASSED: u8 = 1;
const STATUS_FAILED: u8 = 2;

static STATUS: AtomicU8 = AtomicU8::new(STATUS_NOT_RUN);
static FAILED_REASON: Mutex<Option<&'static str>> = Mutex::new(None);

/// 自检状态。
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SelfTestStatus {
    /// 尚未运行(首次调用任一密码 API 时懒触发)。
    NotRun,
    /// 全部通过。
    Passed,
    /// 失败:模块处于错误状态,拒绝服务。
    Failed(&'static str),
}

fn set_failed(reason: &'static str) -> SelfTestStatus {
    STATUS.store(STATUS_FAILED, Ordering::SeqCst);
    if let Ok(mut slot) = FAILED_REASON.lock() {
        *slot = Some(reason);
    }
    SelfTestStatus::Failed(reason)
}

/// 执行上电自检(幂等:已运行则直接返回当前状态)。
pub fn run_power_on_self_tests() -> SelfTestStatus {
    match STATUS.load(Ordering::SeqCst) {
        STATUS_PASSED => return SelfTestStatus::Passed,
        STATUS_FAILED => {
            return SelfTestStatus::Failed(
                FAILED_REASON
                    .lock()
                    .ok()
                    .and_then(|g| *g)
                    .unwrap_or("unknown"),
            );
        }
        _ => {}
    }
    // 单飞:并发触发时只跑一次,其余等待结果
    match SELF_TEST_RUN.lock() {
        Ok(mut guard) => {
            if *guard {
                // 别的线程正在跑或已完成:以状态为准
                return match STATUS.load(Ordering::SeqCst) {
                    STATUS_PASSED => SelfTestStatus::Passed,
                    STATUS_FAILED => SelfTestStatus::Failed("self-test failed"),
                    _ => SelfTestStatus::NotRun,
                };
            }
            *guard = true;
        }
        Err(_) => return SelfTestStatus::Failed("self-test lock poisoned"),
    }
    run_kats()
}

static SELF_TEST_RUN: Mutex<bool> = Mutex::new(false);

/// 单个 KAT 的统一形态。
type KatFn = fn() -> Result<(), crate::Error>;

fn run_kats() -> SelfTestStatus {
    let kats: [(&'static str, KatFn); 12] = [
        ("sha256", kat_sha256),
        ("sha384", kat_sha384),
        ("sha512", kat_sha512),
        ("hmac-sha256", kat_hmac_sha256),
        ("hkdf-sha256", kat_hkdf_sha256),
        ("aes128-gcm", kat_aes128_gcm),
        ("aes128-ccm", kat_aes128_ccm),
        ("ecdsa-p256", kat_ecdsa_p256),
        ("rsa-pkcs1v15", kat_rsa_pkcs1v15),
        ("mlkem512", kat_mlkem512),
        ("mlkem768", kat_mlkem768),
        ("mlkem1024", kat_mlkem1024),
    ];
    for (name, kat) in kats {
        if let Err(_e) = kat() {
            return set_failed(name);
        }
    }
    if let Err(_e) = kat_drbg() {
        return set_failed("ctr-drbg");
    }
    STATUS.store(STATUS_PASSED, Ordering::SeqCst);
    SelfTestStatus::Passed
}

/// 查询当前状态(不触发自检)。
pub fn status() -> SelfTestStatus {
    match STATUS.load(Ordering::SeqCst) {
        STATUS_PASSED => SelfTestStatus::Passed,
        STATUS_FAILED => SelfTestStatus::Failed(
            FAILED_REASON
                .lock()
                .ok()
                .and_then(|g| *g)
                .unwrap_or("unknown"),
        ),
        _ => SelfTestStatus::NotRun,
    }
}

/// 所有密码 API 的入口守卫:自检未通过则拒绝服务。
pub(crate) fn ensure_passed() -> Result<(), crate::Error> {
    match status() {
        SelfTestStatus::Passed => Ok(()),
        SelfTestStatus::NotRun => match run_power_on_self_tests() {
            SelfTestStatus::Passed => Ok(()),
            SelfTestStatus::Failed(which) => Err(crate::Error::SelfTestFailed(which)),
            SelfTestStatus::NotRun => Err(crate::Error::SelfTestFailed("self-test did not run")),
        },
        SelfTestStatus::Failed(which) => Err(crate::Error::SelfTestFailed(which)),
    }
}

/// 测试钩子:强制模块进入失败状态(仅测试构建)。
#[cfg(test)]
pub(crate) fn force_fail_for_tests(which: &'static str) {
    set_failed(which);
}

/// 测试钩子:复位自检状态(仅测试构建,用于失败注入后的恢复)。
#[cfg(test)]
pub(crate) fn reset_for_tests() {
    STATUS.store(STATUS_NOT_RUN, Ordering::SeqCst);
    if let Ok(mut run) = SELF_TEST_RUN.lock() {
        *run = false;
    }
    if let Ok(mut slot) = FAILED_REASON.lock() {
        *slot = None;
    }
}

// ---------------------------------------------------------------------------
// KAT:SHA-2(FIPS 180-4,消息 "abc")
// ---------------------------------------------------------------------------

fn kat_sha256() -> Result<(), crate::Error> {
    let expect: [u8; 32] = [
        0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22,
        0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00,
        0x15, 0xad,
    ];
    if Sha256::one_shot(b"abc") == expect {
        Ok(())
    } else {
        Err(crate::Error::SelfTestFailed("sha256"))
    }
}

fn kat_sha384() -> Result<(), crate::Error> {
    let expect: [u8; 48] = [
        0xcb, 0x00, 0x75, 0x3f, 0x45, 0xa3, 0x5e, 0x8b, 0xb5, 0xa0, 0x3d, 0x69, 0x9a, 0xc6, 0x50,
        0x07, 0x27, 0x2c, 0x32, 0xab, 0x0e, 0xde, 0xd1, 0x63, 0x1a, 0x8b, 0x60, 0x5a, 0x43, 0xff,
        0x5b, 0xed, 0x80, 0x86, 0x07, 0x2b, 0xa1, 0xe7, 0xcc, 0x23, 0x58, 0xba, 0xec, 0xa1, 0x34,
        0xc8, 0x25, 0xa7,
    ];
    if Sha384::one_shot(b"abc") == expect {
        Ok(())
    } else {
        Err(crate::Error::SelfTestFailed("sha384"))
    }
}

fn kat_sha512() -> Result<(), crate::Error> {
    let expect: [u8; 64] = [
        0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, 0xcc, 0x41, 0x73, 0x49, 0xae, 0x20, 0x41,
        0x31, 0x12, 0xe6, 0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, 0x0a, 0x9e, 0xee, 0xe6, 0x4b, 0x55,
        0xd3, 0x9a, 0x21, 0x92, 0x99, 0x2a, 0x27, 0x4f, 0xc1, 0xa8, 0x36, 0xba, 0x3c, 0x23, 0xa3,
        0xfe, 0xeb, 0xbd, 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c, 0xe8, 0x0e, 0x2a, 0x9a, 0xc9, 0x4f,
        0xa5, 0x4c, 0xa4, 0x9f,
    ];
    if Sha512::one_shot(b"abc") == expect {
        Ok(())
    } else {
        Err(crate::Error::SelfTestFailed("sha512"))
    }
}

// ---------------------------------------------------------------------------
// KAT:HMAC-SHA256(RFC 4231 Test Case 1)
// ---------------------------------------------------------------------------

fn kat_hmac_sha256() -> Result<(), crate::Error> {
    let expect: [u8; 32] = [
        0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1,
        0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32,
        0xcf, 0xf7,
    ];
    let key = [0x0bu8; 20];
    if crate::hmac::HmacSha256::one_shot(&key, b"Hi There") == expect {
        Ok(())
    } else {
        Err(crate::Error::SelfTestFailed("hmac-sha256"))
    }
}

// ---------------------------------------------------------------------------
// KAT:HKDF-SHA256(RFC 5869 Test Case 1)
// ---------------------------------------------------------------------------

fn kat_hkdf_sha256() -> Result<(), crate::Error> {
    let expect: [u8; 42] = [
        0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f,
        0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4,
        0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
    ];
    let ikm = [0x0bu8; 22];
    let salt: [u8; 13] = [
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
    ];
    let info: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
    let prk = crate::hkdf::extract_sha256(&salt, &ikm);
    let mut okm = [0u8; 42];
    crate::hkdf::expand_sha256(&prk, &info, &mut okm)?;
    if okm == expect {
        Ok(())
    } else {
        Err(crate::Error::SelfTestFailed("hkdf-sha256"))
    }
}

// ---------------------------------------------------------------------------
// KAT:AES-128-GCM(GCM 规范 Test Case 3:加密 + 打开 + 篡改拒绝)
// ---------------------------------------------------------------------------

fn kat_aes128_gcm() -> Result<(), crate::Error> {
    const KEY: [u8; 16] = [0; 16];
    const NONCE: [u8; 12] = [0; 12];
    const PT: [u8; 16] = [0; 16];
    const CT: [u8; 16] = [
        0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe,
        0x78,
    ];
    const TAG: [u8; 16] = [
        0xab, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd, 0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd,
        0xdf,
    ];
    let gcm = crate::gcm::Aes128Gcm::new(&KEY);
    let sealed = gcm.seal(&NONCE, b"", &PT);
    if sealed[..16] != CT || sealed[16..] != TAG {
        return Err(crate::Error::SelfTestFailed("aes128-gcm"));
    }
    let opened = gcm.open(&NONCE, b"", &sealed)?;
    if opened != PT {
        return Err(crate::Error::SelfTestFailed("aes128-gcm"));
    }
    Ok(())
}

// ---------------------------------------------------------------------------
// KAT:AES-128-CCM(加解密往返 + 标签篡改拒绝)
// ---------------------------------------------------------------------------

fn kat_aes128_ccm() -> Result<(), crate::Error> {
    const KEY: [u8; 16] = [
        0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
        0x4f,
    ];
    const NONCE: [u8; 13] = [
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
    ];
    let ccm = crate::ccm::Aes128Ccm::new(&KEY);
    let pt = b"self-test ccm kat";
    let sealed = ccm.seal(&NONCE, b"", pt)?;
    let opened = ccm.open(&NONCE, b"", &sealed)?;
    if opened != pt {
        return Err(crate::Error::SelfTestFailed("aes128-ccm"));
    }
    let mut bad = sealed.clone();
    let last = bad.len() - 1;
    bad[last] ^= 1;
    if ccm.open(&NONCE, b"", &bad).is_ok() {
        return Err(crate::Error::SelfTestFailed("aes128-ccm"));
    }
    Ok(())
}

// ---------------------------------------------------------------------------
// KAT:ECDSA P-256(RFC 6979 A.2.5 确定性签名逐字节 + 验证)
// ---------------------------------------------------------------------------

fn kat_ecdsa_p256() -> Result<(), crate::Error> {
    use crate::sign::ecdsa;
    const D_HEX: &str = "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721";
    const SIG_HEX: &str = "\
3046022100efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716\
022100f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8";
    let d = hex(D_HEX);
    let sk = ecdsa::p256::SigningKey::from_seed(d.as_slice().try_into().expect("fixed width"));
    let sig = sk.sign(b"sample")?;
    if hex(SIG_HEX) != sig {
        return Err(crate::Error::SelfTestFailed("ecdsa-p256"));
    }
    let q = sk.public_key_sec1();
    ecdsa::p256::VerifyKey::from_sec1_point(&q)?.verify(b"sample", &sig)?;
    Ok(())
}

// ---------------------------------------------------------------------------
// KAT:ML-KEM-768(NIST ACVP keyGen tcId 26 + encapDecap tcId 26,全值锚定;
// keyGen 与 encapDecap 是不同案例,密钥材料不可混用)
// ---------------------------------------------------------------------------

fn kat_mlkem768() -> Result<(), crate::Error> {
    use crate::mlkem;
    // keyGen tcId 26:(d, z) → (ek, dk)
    const D_HEX: &str = "e34a701c4c87582f42264ee422d3c684d97611f2523efe0c998af05056d693dc";
    const Z_HEX: &str = "a85768f3486bd32a01bf9a8f21ea938e648eae4e5448c34c3eb88820b159eedd";
    const EK_HEX: &str = "\n        6d14a071f7cc452558d5e71a7b087062ecb1386844588246126402b1fa163773\n        3cd5f60cc84bcb646a7892614d7c51b1c7f1a2799132f13427dc482158da2544\n        70a59e00a4e49686fdc077559367270c2153f11007592c9c4310cf8a12c6a871\n        3bd6bb51f3124f989ba0d54073cc242e0968780b875a869efb851586b9a868a3\n        84b9e6821b201b932c455369a739ec22569c977c212b381871813656af5b567e\n        f893b584624c863a259000f17b254b98b185097c50ebb68b244342e05d4de520\n        125b8e1033b1436093ace7ce8e71b458d525673363045a3b3eea9455428a3987\n        05a42327adb3774b7057f42b017ec0739a983f19e8214d09195fa24d2d571db7\n        3c19a6f8460e50830d415f627b88e94a7b153791a0c0c7e9484c74d53c714889\n        f0e321b6660a532a5bc0e557fbca35e29bc611200ed3c633077a4d873c5cc670\n        06b753bf6d6b7af6ca402ab618236c0affbc801f8222fbc36ce0984e2b18c944\n        bbcbef03b1e1361c1f44b0d734afb1566cff8744da8b9943d6b45a3c09030702\n        ca201ffe20cb7ec5b0d4149ee2c28e8b23374f471b57150d0ec9336261a2d5cb\n        84a3acacc4289473a4c0abc617c9abc178734434c82e1685588a5c2ea2678f6b\n        3c2228733130c466e5b86ef491153e48662247b875d201020b566b81b64d839a\n        b4633baa8ace202baab4496297f9807adbbb1e332c6f8022b2a18cfdd4a82530\n        b6d3f007c3353898d966cc2c21cb4244bd00443f209870acc42bc33068c724ec\n        17223619c1093cca6aeb29500664d1225036b4b81091906969481f1c723c140b\n        9d6c168f5b64bea69c5fd6385df7364b8723bcc85e038c7e464a900d68a21278\n        18994217aec8bdb39a970a9963de93688e2ac82abcc22fb9277ba22009e87838\n        1a38163901c7d4c85019538d35caae9c41af8c929ee20bb08ca619e72c2f2262\n        c1c9938572551ac02dc9268fbcc35d79011c3c090ad40a4f111c9be55c427eb7\n        96c1932d8673579af1b4c638b0944489012a2559a3b02481b01ac30ba8960f80\n        c0c2b3947d36a12c080498bee448716c973416c8242804a3da099ee137b0ba90\n        fe4a5c6a89200276a0cfb643ec2c56a2d708d7b4373e44c1502a763a600586e6\n        cda6273897d44448287dc2e602dc39200bf6166236559fd12a60892aeb153dd6\n        51bb469910b4b34669f91da8654d1eb72eb6e02800b3b0a7d0a48c836854d3a8\n        3e65569cb7230bb44f3f143a6dec5f2c39ab90f274f2088bd3d6a6fca0070273\n        bedc84777fb52e3c558b0ae06183d5a48d452f68e15207f861627aca14279630\n        f82ec3a0ca078633b600afa79743a600215be5637458ce2ce8aff5a08eb5017b\n        2c766577479f8dc6bf9f5cc75089932161b96cea406620aedb630407f7687ebb\n        b4814c7981637a48a90de68031e062a7af7612b4f5c7a6da86bd136529e64295\n        a5613ea73bd3d4448cb81f243135c0a660beb9c17e651def469a7d90a15d3481\n        090bcbf227012328941fa46f39c5006ad93d458aa6add655862b418c3094f551\n        460df2153a5810a7da74f0614c2588be49dc6f5e88154642bd1d376256332643\n        3507156a57c57694bdd26e7a246feb723aed67b04887c8e476b48cab59e5362f\n        26a9ef50c2bc80ba146226216fe62968a60d04e8c170d741c7a2b0e1abdac968";
    // encapDecap tcId 26:同案例的 (ek, dk, m) → (c, ss)
    const EK2_HEX: &str = "\n        89d2cb65f94dcbfc890efc7d0e5a7a38344d1641a3d0b024d50797a5f23c3a18\n        b3101a1269069f43a842bacc098a8821271c673db1beb33034e4d7774d16635c\n        7c2c3c2763453538bc1632e1851591a51642974e5928abb8e55fe55612f9b141\n        aff015545394b2092e590970ec29a7b7e7aa1fb4493bf7cb731906c2a5cb49e6\n        614859064e19b8fa26af51c44b5e7535bfdac072b646d3ea490d277f0d97ced4\n        7395fed91e8f2bce0e3ca122c2025f74067ab928a822b35653a74f06757629af\n        b1a1caf237100ea935e793c8f58a71b3d6ae2c8658b10150d4a38f572a0d49d2\n        8ae89451d338326fdb3b4350036c1081117740edb86b12081c5c1223dbb5660d\n        5b3cb3787d481849304c68be875466f14ee5495c2bd795ae412d09002d65b871\n        9b90cba3603ac4958ea03cc138c86f7851593125334701b677f82f4952a4c93b\n        5b4c134bb42a857fd15c650864a6aa94eb691c0b691be4684c1f5b7490467fc0\n        1b1d1fda4dda35c4ecc231bc73a6fef42c99d34eb82a4d014987b3e386910c62\n        679a118f3c5bd9f467e4162042424357db92ef484a4a1798c1257e870a30cb20\n        aaa0335d83314fe0aa7e63a862648041a72a6321523220b1ace9bb701b21ac12\n        53cb812c15575a9085eabeade73a4ae76e6a7b158a20586d78a5ac620a5c9abc\n        c9c043350a73656b0abe822da5e0ba76045fad75401d7a3b703791b7e9926171\n        0f86b72421d240a347638377205a152c794130a4e047742b888303bddc309116\n        764de7424cebea6db65348ac537e01a9cc56ea667d5aa87ac9aaa4317d262c10\n        143050b8d07a728ca633c13e468abcead372c77b8ecf3b986b98c1e55860b2b4\n        216766ad874c35ed7205068739230220b5a2317d102c598356f168acbe80608d\n        e4c9a710b8dd07078cd7c671058af1b0b8304a314f7b29be78a933c7b9294424\n        954a1bf8bc745de86198659e0e1225a910726074969c39a97c19240601a46e01\n        3dcdcb677a8cbd2c95a40629c256f24a328951df57502ab30772cc7e5b850027\n        c8551781ce4985bdacf6b865c104e8a4bc65c41694d456b7169e45ab3d7acabe\n        afe23ad6a7b94d1979a2f4c1cae7cd77d681d290b5d8e451bfdcccf5310b9d12\n        a88ec29b10255d5e17a192670aa9731c5ca67ec784c502781be8527d6fc003c6\n        701b3632284b40307a527c7620377feb0b73f722c9e3cd4dec64876b93ab5b7c\n        fc4a657f852b659282864384f442b22e8a21109387b8b47585fc680d0ba45c7a\n        8b1d7274bda57845d100d0f42a3b74628773351fd7ac305b2497639be90b3f4f\n        71a6aa3561eecc6a691bb5cb3914d8634ca1e1af543c049a8c6e868c51f0423b\n        d2d5ae09b79e57c27f3fe3ae2b26a441babfc6718ce8c05b4fe793b910b8fbcb\n        be7f1013242b40e0514d0bdc5c88bac594c794ce5122fbf34896819147b92838\n        1587963b0b90034aa07a10be176e01c80ad6a4b71b10af4241400a2a4cbbc059\n        61a15ec1474ed51a3cc6d35800679a462809caa3ab4f7094cd6610b4a700cba9\n        39e7eac93e38c99755908727619ed76a34e53c4fa25bfc97008206697dd145e5\n        b9188e5b014e941681e15fe3e132b8a3903474148ba28b987111c9bcb3989bbb\n        c671c581b44a492845f288e62196e471fed3c39c1bbddb0837d0d4706b0922c4";
    const DK2_HEX: &str = "\n        b09125afb3cfb5295581373ab6885284d9706318280d223edc987fd14410dbe8\n        2e6ac89adfab70e67ca4b1c641ad037fd8c47870f159ec79cdcd52605b989049\n        9bb6dbd8347f342c61436b642c0ddf4617db06198b8285dce4c09d9775a2f41c\n        8cd18af8e75f57d4127df94d901ac83bacbd584cc50c43750f49b357f5935087\n        5c9b475480a8aaa168592ddb158614a639813566d205368c6c39f0413ca3230d\n        f60d44008282b682ac66b76c3c95f00b2a555035529c86ef3905b4a3968fea78\n        02b6c5eecb08e8f0c42d7ab7cd21a62fb136412a1840b52c99970ccf51892f73\n        497c3775be2189f7fc25e7c74d81fc217683292aa4866ddb04469855323a0810\n        f0893de5c7f94a9c0b5337db83c44891b2e694695b76575032bf51761682958b\n        d4f97be9a355b4a85bb6858b7e5a5ef653ab781056af9187d811c3a8936e5706\n        503db57062410bcc9421f1ab867a657856c411c4e025ecb3c387729ae8e112f3\n        30b988e22f47c35c280750d21b107687af7b329ef3cb5289f06fb7d44548391e\n        97ba6dd499b5907c54958413d92aa99d5646cf47a8f48cb70a07ad056b4eefe6\n        c8c46645f7028a32410558638c48e83ac1570160c3833bf64052f5b7df4364d3\n        e0b24e790aa7c98cee0441e6731d9de22d156c61e1c740397672ef54724f01b9\n        d49923aa321f86b98823f21360138392b90c69434635275f9bfbb9b8a99e8e1b\n        7f4ec25f75dbce33c13f750170bd6722efe496e7463e16aaa5867b869a96ad41\n        b22bd2556c924596fd778d79a102f6e46d8eb18fefac8db19993e5414ac81670\n        5286892492c8c9e852d6145dff0c10e4a6703a459e7e732a6dfa2766a622b062\n        2bfedb8f41c125f61b2ec264853b9ccc165979f6a263beb148905aac7618a70e\n        829e23f28696f92ef6fa07c102cdbdb1288ba5cff3a81abba15974535fe3106a\n        80068f14e98964572350a7112b1601c196710c096ccf164fbce1aabac9c5b953\n        5070e61ab8068d611ca765fabb6412607dab30c4fc6ad073731fdc4c48b88e26\n        7c47b439ad2560c30561815ceb1f52c896489944bbbab52b1b1d1680a1057964\n        dafa600c93a39a447ddbb0adf911afe3e823d8acc7cc04659f625f2c1837bb17\n        5282542cd22601f621581ab5a6c0384e087ccd32a5380b522fdd3a4202b5b41c\n        85caff2903b2dc2645703d9bc711fbb404c0c0376187ac588aaf5718522d2273\n        a9408dabcbc9701698d2da172aa6267a4c9693a24011c2265a2b6dc8e96304a9\n        8ddc5319a3140c399a08412c20f48537870bb84c32a094457895511ff7ec421d\n        e01a64b78534653f78327441b90cd115939dfaafa95b40d0a63d62d12eb5c909\n        6018cc83871e44e6cd0be26d16b7b5a209b8e6471d2954adf9fabd0153707c9c\n        aa2bcc38ded841c791a0eb597eeee2c518d926edb28ab53caa5b7746466931b0\n        ac9150688bf37049c1f82bcf648332434cd0a92fd2c958353a26cb65cb499057\n        109b2d688cc43c4b385da7c50868af1b8075e57088f5db12dfa493eacb6dc4ec\n        6e205baa2a89858ec2823c00553714cde47a96e36c7c198b3ec57ccf74d92cdd\n        b86aa0a8b8b5ca9d52bb60aba79f4f72b0125532ceb7a9077480d2bb60df51a9\n        89d2cb65f94dcbfc890efc7d0e5a7a38344d1641a3d0b024d50797a5f23c3a18\n        b3101a1269069f43a842bacc098a8821271c673db1beb33034e4d7774d16635c\n        7c2c3c2763453538bc1632e1851591a51642974e5928abb8e55fe55612f9b141\n        aff015545394b2092e590970ec29a7b7e7aa1fb4493bf7cb731906c2a5cb49e6\n        614859064e19b8fa26af51c44b5e7535bfdac072b646d3ea490d277f0d97ced4\n        7395fed91e8f2bce0e3ca122c2025f74067ab928a822b35653a74f06757629af\n        b1a1caf237100ea935e793c8f58a71b3d6ae2c8658b10150d4a38f572a0d49d2\n        8ae89451d338326fdb3b4350036c1081117740edb86b12081c5c1223dbb5660d\n        5b3cb3787d481849304c68be875466f14ee5495c2bd795ae412d09002d65b871\n        9b90cba3603ac4958ea03cc138c86f7851593125334701b677f82f4952a4c93b\n        5b4c134bb42a857fd15c650864a6aa94eb691c0b691be4684c1f5b7490467fc0\n        1b1d1fda4dda35c4ecc231bc73a6fef42c99d34eb82a4d014987b3e386910c62\n        679a118f3c5bd9f467e4162042424357db92ef484a4a1798c1257e870a30cb20\n        aaa0335d83314fe0aa7e63a862648041a72a6321523220b1ace9bb701b21ac12\n        53cb812c15575a9085eabeade73a4ae76e6a7b158a20586d78a5ac620a5c9abc\n        c9c043350a73656b0abe822da5e0ba76045fad75401d7a3b703791b7e9926171\n        0f86b72421d240a347638377205a152c794130a4e047742b888303bddc309116\n        764de7424cebea6db65348ac537e01a9cc56ea667d5aa87ac9aaa4317d262c10\n        143050b8d07a728ca633c13e468abcead372c77b8ecf3b986b98c1e55860b2b4\n        216766ad874c35ed7205068739230220b5a2317d102c598356f168acbe80608d\n        e4c9a710b8dd07078cd7c671058af1b0b8304a314f7b29be78a933c7b9294424\n        954a1bf8bc745de86198659e0e1225a910726074969c39a97c19240601a46e01\n        3dcdcb677a8cbd2c95a40629c256f24a328951df57502ab30772cc7e5b850027\n        c8551781ce4985bdacf6b865c104e8a4bc65c41694d456b7169e45ab3d7acabe\n        afe23ad6a7b94d1979a2f4c1cae7cd77d681d290b5d8e451bfdcccf5310b9d12\n        a88ec29b10255d5e17a192670aa9731c5ca67ec784c502781be8527d6fc003c6\n        701b3632284b40307a527c7620377feb0b73f722c9e3cd4dec64876b93ab5b7c\n        fc4a657f852b659282864384f442b22e8a21109387b8b47585fc680d0ba45c7a\n        8b1d7274bda57845d100d0f42a3b74628773351fd7ac305b2497639be90b3f4f\n        71a6aa3561eecc6a691bb5cb3914d8634ca1e1af543c049a8c6e868c51f0423b\n        d2d5ae09b79e57c27f3fe3ae2b26a441babfc6718ce8c05b4fe793b910b8fbcb\n        be7f1013242b40e0514d0bdc5c88bac594c794ce5122fbf34896819147b92838\n        1587963b0b90034aa07a10be176e01c80ad6a4b71b10af4241400a2a4cbbc059\n        61a15ec1474ed51a3cc6d35800679a462809caa3ab4f7094cd6610b4a700cba9\n        39e7eac93e38c99755908727619ed76a34e53c4fa25bfc97008206697dd145e5\n        b9188e5b014e941681e15fe3e132b8a3903474148ba28b987111c9bcb3989bbb\n        c671c581b44a492845f288e62196e471fed3c39c1bbddb0837d0d4706b0922c4\n        72e31df613da9a1dd33b5d2d8939684b89f7649e1c59b959ffbe972786c477f6\n        6177dbf3b059173fd06afcd90e80e862174fc57f97607bbff5b73d6360fb5c37";
    const M_HEX: &str = "2ce74ad291133518fe60c7df5d251b9d82add48462ff505c6e547e949e6b6bf7";
    const C_HEX: &str = "\n        56b42d593aab8e8773bd92d76eabddf3b1546f8326f57a7b773764b6c0dd3047\n        0f68dff82e0dca92509274ecfe83a954735fde6e14676daaa3680c30d524f4ef\n        a79ed6a1f9ed7e1c00560e8683538c3105ab931be0d2b249b38cb9b13af5ceaf\n        7887a59dba16688a7f28de0b14d19f391eb41832a56479416ccf94e997390ed7\n        878eeaff49328a70e0ab5fce6c63c09b35f4e45994de615b88bb722f70e87d2b\n        bd72ae71e1ee9008e459d8e743039a8ddeb874fce5301a2f8c0ee8c2fee7a4ee\n        68b5ed6a6d9ab74f98bb3ba0fe89e82bd5a525c5e8790f818ccc605877d46c8b\n        db5c337b025bb840ff471896e43bfa99d73dbe31805c27a43e57f0618b3ae522\n        a4644e0d4e4c1c548489431be558f3bfc50e16617e110dd7af9a6fd83e3fbb68\n        c304d15f6cb700d61d7aa915a6751ea3ba80223e654132a20999a43bf4085927\n        30b9a9499636c09fa729f9cb1f9d3442f47357a2b9cf15d3103b9bf396c23088\n        f118ede346b5c03891cfa5d517cef8471322e7e31087c4b036abad784bff72a9\n        b11fa198facbcb91f067feaf76fcfe5327c1070b3da6988400756760d2d1f060\n        298f1683d51e3616e98c51c9c03aa42f2e633651a47ad3cc2ab4a852ae0c4b04\n        b4e1c3dd944445a2b12b4f42a6435105c04122fc3587afe409a00b308d63c5dd\n        8163654504eedbb7b5329577c35fbeb3f463872cac28142b3c12a740ec6ea7ce\n        9ad78c6fc8fe1b4df5fc55c1667f31f2312da07799dc870a478608549fedafe0\n        21f1cf2984180364e90ad98d845652aa3cdd7a8eb09f5e51423fab42a7b7bb4d\n        514864be8d71297e9c3b17a993f0ae62e8ef52637bd1b885bd9b6ab727854d70\n        3d8dc478f96cb81fce4c60383ac01fcf0f971d4c8f352b7a82e218652f2c106c\n        a92ae686bacfcef5d327347a97a9b375d67341552bc2c538778e0f9801823ccd\n        fcd1eaaded55b18c9757e3f212b2889d3857db51f981d16185fd0f900853a750\n        05e3020a8b95b7d8f2f2631c70d78a957c7a62e1b3719070acd1fd480c25b838\n        47da027b6ebbc2eec2df22c87f9b46d5d7baf156b53cee929572b92c4784c4e8\n        29f3446a1ffe47f99decd0436029ddebd3ed8e87e5e73d123dbe8a4ddacf2abd\n        e87f33ae2b621c0ec5d5cad1259deec2aeff6088f04f27a20338b5762543e510\n        0899a4cbfb7b3ca456b3a19b83a4c432230c23e1c7f107c4cb112152f1c0f30d\n        a0bb33f4f11f47eea43872bafa84ae22256d708e0604dade4b2a4dde8cccf119\n        30e13553934ae3ece52f3d7ccc00287377879fe6b8ece7ef79423507c9da3395\n        59c20de1c51955999bae47401dc3cdfaa1b256d09c7db9fc8698bfcefa7302d5\n        6fbcde1fbaaa1c653454e6fd3d84e4f79a931c681cbb6cb462b10dae112bdfb7\n        f65c7fdf6e5fc594ec3a474a94bd97e6ec81f71c230bf70ca0f13ce3dffbd9ff\n        9804efd8f37a4d3629b43a8f55544ebc5ac0abd9a33d79699068346a0f1a3a96\n        e115a5d80be165b562d082984d5aacc3a2301981a6418f8ba7d7b0d7ca5875c6";
    const K_HEX: &str = "2696d28e9c61c2a01ce9b1608dcb9d292785a0cd58efb7fe13b1de95f0db55b3";

    // 1) 种子展开
    let d: [u8; 32] = hex(D_HEX).try_into().expect("32 bytes");
    let z: [u8; 32] = hex(Z_HEX).try_into().expect("32 bytes");
    let (ek, _dk) = mlkem::keypair_from_seed(&d, &z);
    if ek.as_bytes() != hex(EK_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem768"));
    }
    // 2) 封装(案例自带 ek)
    let ek2 = mlkem::Mlkem768EncapsKey::from_bytes(hex(EK2_HEX).as_slice())?;
    let m: [u8; 32] = hex(M_HEX).try_into().expect("32 bytes");
    let (c, ss) = mlkem::encapsulate_with_seed(&ek2, &m)?;
    if c.as_bytes() != hex(C_HEX).as_slice() || ss.expose_bytes() != hex(K_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem768"));
    }
    // 3) 解封装(案例自带 dk)
    let dk2 = mlkem::Mlkem768DecapsKey::from_bytes(hex(DK2_HEX).as_slice())?;
    let ss2 = mlkem::decapsulate(&dk2, &c);
    if ss2.expose_bytes() != hex(K_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem768"));
    }
    Ok(())
}

fn kat_mlkem512() -> Result<(), crate::Error> {
    use crate::mlkem::k512;
    // NIST ACVP(2026-09 代):步骤 1 用 keyGen tcId 1,
    // 步骤 2/3 用 encapDecap encapsulation tcId 1 的完整五元组(ek/dk/m/c/k 同案例)
    const D_HEX: &str = "47b893474672ba92e4b12ee44fb32953af8e8503b5fb471d1614fb8a021a660a";
    const Z_HEX: &str = "1f8cb39e9e30bc458a0dc5408884b1187fb217018df760fa57317703b844a0a9";
    const EK_KEYGEN_HEX: &str = "
        28266a088b3482439bca01afb7ca5c6136a979b5159985a9484b36b679a5f7b9
        819eb63577891f7bb9cb98413ccc434adc79a16d6ab3076569ce6291c59b5d64
        612a7fb0c15013200bc8bebb03a570174b5e4363aed86eb02a220d281fb5457f
        0a549fc5051d49a6b2015259a2c3084f405e1769952260675586a58490405927
        5a265234ef3abf88c171a80898fc783358bbc9803c8789027d917c9ebacbc568
        cc18de84c85454b94249586c0c6e2b8a16fa789c51212dd1728ee9b8c6c40528
        bf93826fa82368419623032af27b5694305816811d3ca85805100e9c1a9621e5
        089e54cb47f5a8fea0b49ef81c6b5187f48924c7947d6b61697a4a8a18452ef8
        03336ad4be503275bcacc03c181405f7b1dc9b47fb169eb37bbe27e29c763a4e
        52b9a42520388cf09b8edbcdf41ccf6537190e6156c37cc1aac63c0f90ce78d0
        b9b190c548d71b6f26cc8f585ea14004b5b30aaa100b2adc1263828833b24e46
        163b41446f98c882092a39941867b80632e2097674a793935227db0b8577e03a
        69c50a514c7473c892e3fba7c4316bdabc952a70644176687d4191323bad93d8
        5a3ca250868c0747e6c44f6126c874afbec0bdd4503cb2c59a69816e7d410994
        1467579a1ffe6a4f50fa379051729dab6e2f61432f15be67d667c7cc1054742b
        2b953078a5cf88d9133087309d88c61da240d99c59137329907b47865321ecd5
        564e987333b4cb607b0afca86769dc95b2f921357213fcb80c3b152918e9bab2
        228c0a1b77897ac68ce55088165f87f397da9790873b62c5383c0ccc370f0267
        cbe195651ccf336182c22ac3924b76c9e779b7a271d166b6d24b84242b7e73cc
        723f764039f6c851744034c3304db0c091a5764fdc9d593556ff734b82a87ccb
        c38ca99564d988bbd2d1bf071bb160722d365104fb27610651a8ed817f2742a6
        b5a1273a61acaf4460b0ab1456a9922351400a1c7d95d856d6e3370622c9c416
        4bc6b401435624a98b95caeb274f34ce92038d785068cdd8cf44c38d84acb2c4
        66a2756c870ee78c26e738cc451002304eb8c90ab24b6463eb124d779f937a2e
        3692611d2e34d57b36cc4b2cd3b31ff485c6684d408b972e0d5ca7d2224aae4e";
    const EK_HEX: &str = "
        17e5129b2029f3281987d6624725b64c51cf8dca3562372bacb7ae15fa9f2ff6
        ab47659b7d305b61f55f571315ff69aa49e1388100319a650e86c59ba3024c3d
        ec83d4aaab661c452f6cb6a8d2638c133c599045329ccc8f677d24683df1146e
        e1c7318c3763a47acff81a03927b9bac5a49dc285c4ee204c4d72dbb1c97fe53
        c622e621338669fcfac1e30a36f0a9769bbb2ba408787ee629cec383f29aaaab
        d46e22133f339c08e29b82c4faaf0f676e1c2b04377975dc3a3b246488edd163
        6c58abcd65b8198b6fa8a8475ef541daf34b5adb9db0589d8958b62a88930eeb
        1c0c352b1e57bc7882c89efa318457813f2b55b96881e7f75c3dc97357681e54
        553ae099095ac38b34c00199952602c481c0f1cf74b550ce7c4f6c5876aff076
        dd921942db377e1749f0306f77443a2f058d785854c7e32b67f49bcb99a5e18a
        ab607c649b892b4da7cc7af31557149f02b19460fb49e5051a7251ade0083cdc
        1b0b0ca9633220c2b3c532fa2cbc0dc6cffcd4455e0005d06cafc727c778375c
        b67ac461b3627a653c843baeaef866c7f67746262ff6d76536255c89045a172c
        bfaa123d6eca3e56fc922f437a14f78d1adb54b3e54e8dd530450b8500e15a54
        c97471cb26ab437394480282ba6b7a786c28857baf387725bb4342b4c3abe4cb
        f90612b04c8bdc1599c2d4c66b80b1a1941440040a42aabe03e1ccb0b4190780
        b6603bb69ec199b063826beaa5447c88427c6bd7a06e4164a490b955152979b8
        f5bc9417598af01bcc371577189e1775165b9610b9f75aeea277018b1c2c0439
        1115b6f353afeecbaa18aa4001db94fcdc5f46ea520e9836bc5a4576b58c59fc
        3f75634946d8cc1b1081ce93aba8566304169332fa316d4ba675a086ad879379
        63511e4a1845ea38689b564e11295672098cf01d5d4bab0338121128a682b048
        e6aa31b83c65ff861e123010433a3c7607ae5e90c438067f637b44adf8798cd0
        980ec83bdaeb4d427a8f8d88c519e52542f07734a645a2a5bd4d6521a4b64a96
        a10f35002780169b35e35f01ca74fe6207b8b475ebc079647cc10aec3c29683e
        071d87b82abbdd6d369e326e475325ed5ae7ed232b37f49388c06a740d421204";
    const DK_HEX: &str = "
        c9096f060a1c4c974253d2b64feb0e40d05ff5426fe9665252968c4948a2a363
        a5b7f5523e5abd4e63393e9cab0148701529548cfc58901a4840517423f9a3e5
        0327d1610bd291a2a051ac231144ac924b6845a735784daf24cacc9c17e11739
        9b94967284900390ad35857979385a04929eb2094bc6444236da94d39ab74ea6
        40470a068b69880da1846c6211b0222174d84f5f0649a7e266a58091bfd9b49b
        9478f1801934a86293e8af643340541676f98a2d1b62a4835766fb6441172cbb
        a0942b3665bebb1ca202ab8a7a0c655bc232e89a0027fc49f7ea49ff21564482
        30602b70c6988e4fd182dc95338f1206d2fbc699fb12ac636ef6084735f3962e
        e26d494423baa907d8d05d6076556f6279bb878b6466559d309a57c93843173d
        ae1b281ea2a30a7c7325d8593579b07d574a73c51a8ae17092a80aa30c35bd6c
        b76e122019ba2668551cb9640a07a3b5aa3c83a575ae78552928344dc4388433
        3414c48a9bcaa45620c46334e508e7750b24cc696002995725159c334a909074
        a1a3719d53c3ea2cb8df03c3953192162444f116453ba19b2ad380769851dd59
        455fa0b4e1367da0bab063922846ea85ff119fb7e0447d464cbac53fbdfaa0ea
        ba3e1be31b1243c6b1468369523c03e06ffdc714bf2cc171a8189cc3a8f4840d
        b69860a6608b1b9c56a356cebc6c8fd01763e086566bc582ecb52db10a0c4ac9
        7d94c25b5ab2cef0c42fcf26bd7a88044ea20b16041727573d156a8fb69a31b5
        c015201733a0e84ded1757e3988dbcbc5571c8588286a418c149a49ca31b73cc
        7321cbf83947395538152ac6b36b39daa538aea236db64827d660e574911fa09
        4d4e9a279820be06d4bd4bd3492643964d432bd8dc5c0828b7f5993a50d25415
        88a6b37022cce055814845cbac226807216b0839f449039651a43488400ba602
        e6244b720629ddf3c9e88a894d524a0d60327f748a1be86a25a2bf41225fcfd9
        848b917edb0b081e71bd50c09c0eb38286939879e23e942a6d4d649b4ca123f7
        2c202f1240b9ec63c7410340c5c7c78baa5ba70b55172bc1c44abde4af86d380
        17e5129b2029f3281987d6624725b64c51cf8dca3562372bacb7ae15fa9f2ff6
        ab47659b7d305b61f55f571315ff69aa49e1388100319a650e86c59ba3024c3d
        ec83d4aaab661c452f6cb6a8d2638c133c599045329ccc8f677d24683df1146e
        e1c7318c3763a47acff81a03927b9bac5a49dc285c4ee204c4d72dbb1c97fe53
        c622e621338669fcfac1e30a36f0a9769bbb2ba408787ee629cec383f29aaaab
        d46e22133f339c08e29b82c4faaf0f676e1c2b04377975dc3a3b246488edd163
        6c58abcd65b8198b6fa8a8475ef541daf34b5adb9db0589d8958b62a88930eeb
        1c0c352b1e57bc7882c89efa318457813f2b55b96881e7f75c3dc97357681e54
        553ae099095ac38b34c00199952602c481c0f1cf74b550ce7c4f6c5876aff076
        dd921942db377e1749f0306f77443a2f058d785854c7e32b67f49bcb99a5e18a
        ab607c649b892b4da7cc7af31557149f02b19460fb49e5051a7251ade0083cdc
        1b0b0ca9633220c2b3c532fa2cbc0dc6cffcd4455e0005d06cafc727c778375c
        b67ac461b3627a653c843baeaef866c7f67746262ff6d76536255c89045a172c
        bfaa123d6eca3e56fc922f437a14f78d1adb54b3e54e8dd530450b8500e15a54
        c97471cb26ab437394480282ba6b7a786c28857baf387725bb4342b4c3abe4cb
        f90612b04c8bdc1599c2d4c66b80b1a1941440040a42aabe03e1ccb0b4190780
        b6603bb69ec199b063826beaa5447c88427c6bd7a06e4164a490b955152979b8
        f5bc9417598af01bcc371577189e1775165b9610b9f75aeea277018b1c2c0439
        1115b6f353afeecbaa18aa4001db94fcdc5f46ea520e9836bc5a4576b58c59fc
        3f75634946d8cc1b1081ce93aba8566304169332fa316d4ba675a086ad879379
        63511e4a1845ea38689b564e11295672098cf01d5d4bab0338121128a682b048
        e6aa31b83c65ff861e123010433a3c7607ae5e90c438067f637b44adf8798cd0
        980ec83bdaeb4d427a8f8d88c519e52542f07734a645a2a5bd4d6521a4b64a96
        a10f35002780169b35e35f01ca74fe6207b8b475ebc079647cc10aec3c29683e
        071d87b82abbdd6d369e326e475325ed5ae7ed232b37f49388c06a740d421204
        f31404c1c625039560adfe0bf427c4502b115fa8d02d78b4f2ef1a45078a1f80
        52c2a1a06087a52fc79b8cf7f2fcd41d18f5a98190d84a0379870db22186ecb7";
    const M_HEX: &str = "dcacfe4de1c115da106acd1eefeafdc7f0f4e5707453ee2d6b0d69d34cc0ef4a";
    const C_HEX: &str = "
        1c3204a5a2c031077459e24a179fc80f8833e19f36e7ac0d3071bfbd2d48fcf1
        352b96efd0fa3195b44a27ec575b2794909e4089421e56409ad00cf472680f43
        8e0a6d39e88fe6b938ef722c7b7f75f714264c8f22c528a63985c75d2412278b
        137acd29003cad1711a2637c630164507b7d3c0acd1dd3ba6e689411df6d3eee
        410ec8c93ef27bc82019c3943b85e645519bec1105d4738388c7a5452a67880d
        e88d65c1626a55a4565b5c26b20bbfc33f2dcecf938149d8b58b19dfb5f451c3
        a9fb5ab3dc486c435f5397b6e32416a9306d9869b91231adfcc9a4ad3d956ec4
        9832c3ef2a6ed50638f6633d5a8fa7bb7b04ef45ff9b57d6ea7b771d57c3e5b9
        bf96e03abd601bb46e5ce3e104233e7be642b082a610fcebea684c516aa39a05
        1ddb87025ca849816e177c17c10115939b98b5d9f95323328cb5250c4e38b7e9
        32481d20bcbc66b0becb3dc1aa196f5fe207b28f36344a3c00f2fbe179878d6c
        7981467fc2df70d079088f09b4d2cb56d5dede593a77b31930e8f6138dfc7f9a
        7295fb372c1a33713610b2be51f311e7ce6050ec276e5c3a50790ee4cb815110
        52ab659dd54f4baf211eacdc2a4e987c7e2eb8b384f06813c2542bf765c3e6b1
        42968bf3c66414d01a205964e743777040a6a9879c84681b6f6b3f2f2b0b0455
        952a99a21cd609c1fd7be71ba6a1c9589445dc69cf1d4937763ac640df853fd3
        e6dc9143467e473ff4b89975f24cca51506830c279ad8bf806611e836f405e8c
        1fc36208e99acb764316bd7c42d2e248b1dc3d5c551ab77fc168296853129c31
        f5722707791fd5f9aaabc4fd4c82a99970a93f274fd0d6252edcaedc076bf598
        4e6e28056bb831ff289cb0f6ee87bc4816712da0f322a51765f5f230d0de72cb
        b4714c9cfe56c1bd727dd7d3f7c1b5b83029c81a22b8fd430b137003b63f29b7
        10a1a2c549f9ab0b5ac8a123935a1a3bf627690252aef65f20681e2e9a664321
        cdf5fde1943c196ba2f04e42e35f62ce77198f01a747a14d57d7ed46259189a6
        aa3363fc178a8cc5499f033f9b8d90d327aafab7de16831e8325d337ab3dcdf1";
    const K_HEX: &str = "2d74374b55d29aa585e144a29ba4f0a96537a73b4f176c5527075f66e38e8858";

    // 1) 种子展开(keyGen 案例)
    let d: [u8; 32] = hex(D_HEX).try_into().expect("32 bytes");
    let z: [u8; 32] = hex(Z_HEX).try_into().expect("32 bytes");
    let (ek, _dk) = k512::keypair_from_seed(&d, &z);
    if ek.as_bytes() != hex(EK_KEYGEN_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem512"));
    }
    // 2) 封装(encapDecap 案例自带 ek)
    let ek2 = k512::EncapsKey::from_bytes(hex(EK_HEX).as_slice())?;
    let m: [u8; 32] = hex(M_HEX).try_into().expect("32 bytes");
    let (c, ss) = k512::encapsulate_with_seed(&ek2, &m)?;
    if c.as_bytes() != hex(C_HEX).as_slice() || ss.expose_bytes() != hex(K_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem512"));
    }
    // 3) 解封装(同案例 dk)
    let dk2 = k512::DecapsKey::from_bytes(hex(DK_HEX).as_slice())?;
    let ss2 = k512::decapsulate(&dk2, &c);
    if ss2.expose_bytes() != hex(K_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem512"));
    }
    Ok(())
}

fn kat_mlkem1024() -> Result<(), crate::Error> {
    use crate::mlkem::k1024;
    // NIST ACVP(2026-09 代):步骤 1 用 keyGen tcId 51,
    // 步骤 2/3 用 encapDecap encapsulation tcId 51 的完整五元组(ek/dk/m/c/k 同案例)
    const D_HEX: &str = "f3a706faf090c03db506863ab0b20bd8a1627956318e88c67eb875e8e7266009";
    const Z_HEX: &str = "35d2bc43dd1cc879f765bf2a0c5e297889dde910e57e2bb0eae417b90ab7a275";
    const EK_KEYGEN_HEX: &str = "
        8d0923ca8a2da2b4146ec25321122b8a5aa8afe0c03415273008a46ee83031e9
        8aaaa125abc75d3b30322560c197e75dd0e48a348099f7b2144d7b8a8660a4a9
        7bcf19c0583bd9bb2123033cd7bb5a14b08b817831a673a28170f5f6443c0551
        913a327cba18c3a053c4040250403b70ab9588832403aa0fc37665e04980fe16
        02e7d2715d9cbc00515df432a4f5b32b3bc92ae3f31700166d498123e9457650
        9b712b18491b1435ee7ab7aeb1ad30d72348c3cc083abe24a8b12097bf32f792
        476288eecc3bf630adcdac6aca7950d9839501a448500742bae37f109203a809
        b2b960a307e25347a32c3eab79288173a878789b296e9e8c1c28c5bb3ac47260
        1c9765f7b77225a810c7b85370bef4a5b079d2015ada54236b8f33840675f9b2
        eb427a1b5974cd5c61b24010886c5a7bda5bbed974af7217f3338ad719cb308a
        8bcb1b6d6ed2a1643736c29095e8a8452a3a36c7bb5ae58cbfdc61529466a90f
        454ed6895b0861083dd1371999b2f559a3a487cf59a074fb49215ea6a6be656f
        9af17b121a2447cb7985590e9738842b899ba57ac311810ae2d9794f37483dd6
        bccb64af6d56588ae94665961c025c3aa2861974c236bca4bd8ff5509f7ab774
        593e7c5549e57c2f18d15c0515094ad9a0dfaa0601e524f8231156b627bb25a0
        dae04dacd0a66c041cef400583fc13bae640291a39a5c5ca8bca1ad5c683cdd8
        290891a76940817dd8c9f52678780548e37a05806600801426dbb950c3b2ba34
        e24cc77864dd91b39f1408c716a69df63342854e50a245fb50977b9410ded2c9
        3f86b1f9d5a78b87bf81e51ca620a7e8566b19ab700964a40e3266415228d432
        156e5cbdf52364a90483a55c39b3fb16fc7465a3f8ac801b70b9fb28b583444b
        a5c1a73722d417a9d6d9b7deb08bc6b330ff27cf61ab8831e27758c64af3b121
        50cb7b33abc29858106d63686d8762459abf9413850ae53ed6313f76f83d0fb8
        ab34374e7df693e4a1b3e5a8ad0ce820afe1cf401acde650a8101b0946022d52
        178e19613c42b88b07cc04eaa81dfb28ac9dc076236b67219a30f8f945dd57bd
        2f335c52d59372308d38993467db53da3382b74867b616481bd0091a2232c111
        6dc88a589db9107224a681008c67c589186a6929549beef92253db02b0c8aa9f
        9c875a670266c72bcbdb4f5625043703c1a0457395832e4c335180462ed2220c
        59e7361903c107d85457f6cd82eb820d0855d97675c2e0151cdb73c2885ddb78
        49d74541580124e890116a65bc068093b57914e20c937c60a3eb25576f1a976a
        9583839b672144cd4a45c3477a45c29b4e0bc2bdbd206585c9b7a7741c8b6b57
        93a92797a15ae7a5b73a74b2971463634ba52aa792af05530730b6d0a89a3461
        56b733677932bd36593a7496130cc458dcc5ca987c21960604ec8a8c53960566
        80cbf3f1aac4f401aa5029fb2150434bb4706c31a2d54e4297939fa7c9c6f857
        00613ceb65c7f03ac56eb86e2d27ccc6dcb7b9394dcdb942ff222d86958a996c
        0cb6a8a44f97a70441c95fa71250116eec20863c0b5a643458788ab001f8869d
        909922f51ee547a1e889255b3a0599c65842e5ab8d73872f053bc62392ea5389
        6d328102d460bf1609583c22c3b43780ec6dad0319eb4a5a65b4756c3cb40eaa
        935183bf8bd46abe76ba46e199103a5313c3235f49c915e097bca804de680781
        d8365731beac6789a9203fb8787c4c070e00a13a6722a66a28236db179825653
        d33ccf898b72c6b8450d97afd3276bb13340519cbeda708d12a858f54c49f454
        7195b7788a9150b2649e36aa394121926d568a488b16d3557b2a32af57d11fc3
        373f80a28c0723273d362502e7c428ab44d3cbabf9ea585fd1bd0c9846556a1e
        196b78cf951592984a0a8487a78c2317d7aca4118e1049750a0788f0d66ad9e4
        8e34731130aba0b427360a856d96d80b3f028fdd3aba9035c10106ba1c0934be
        d36c6d7c7434249654ea89fc22137f4ab903653b75fb25b6f01635e6cc7d39cf
        1508690562826b49b6ffc59e0dd35022e541f8ba0d304aa5b4e20606907c4243
        95666c54abc2b8fb009847c86317685000c231215c8c15945860f6a85ddb98a8
        c3a527f2749d3c027e694e8f0b0f0fa454913aadb635aadd452f7128bf775256
        9669a8b93290eb92e78f6adff23e89f57f3890753b51f12f3f3a8a654e677847";
    const EK_HEX: &str = "
        2191abb6d6beee29c5780758a970349879b61a028deea5404731292346c81eeb
        1d17766afbcaa68c867d91132f34a494e28caa767241b50902f4825771865fc8
        d633736248963a253dd52c1a07c7177cb6df74c43c3c74d7133da9dc915fe14b
        5bc30b86153e7b86be04189f4ce8ceb6e5cb69808d35640ac50335b1633162e2
        8260300646153be7b26a84c1c565956f8577c95cfa80b68978cd7a79e49c7e80
        8b21cdb460ed8388c4859670333b69980b2f9a0f78d7a5b9d8554893a0b5c524
        47b15318828989816f43406abfa4950607a5a57cba62f5c4340ac3d294191687
        c7d2b60ef2d83f6bec15b1618129b78577447bd1e36a87142b9c552c8cd2304f
        35b81b3bc5196c3840c0346d513ec8862ba14a172c051402f7398707a6133c8e
        88e5b57f9b9eec18aed1404ea1c47be5169e5d558e262a231a75925d25c49965
        0c8ac031be580cfb185a0e0362ce2194c53713a630cb59600c1942908ff5142b
        02364b74b3f5474301ab2da4188dca640961142f28584ebe810683c046b59cc2
        cc07025da63a5e8c5e99e24fbae183329886279c2d58d0b8cac1c7b3033f05e7
        65c75414748687ff03c00b36709d41c5968528f178cd03b813b69054d3e20fb6
        a04a713b1b169ab5f0369810fc5cd967a866b70a2d87514605794930be9b7b5d
        49d08f5cb08d84fc5beecb994dd2a5fc10aeeb4cce1ed251fd4815a9d06eea6a
        77f18c56acabb1f7c478dd52218b7bbf9770a51c3ac96402548a1bb22dd0c29f
        422330057179ec2d91e1aee27bbcb1eb4671324265e28676090896e332535ba2
        e9169c54113f19a45229c2448078ae08f7213f347244527ef726855ca054ab7a
        1ed1a40a285a60d04c00ccfa8fd318cf182705ddb888df652669b3224cf54ed6
        d00db5121331b40c7e35b1945925e5ac1503a999899317a2370020d818733303
        27db7cc1d430e32c6570fccffeea90a871a4b04ca9a8065ab20a6459f1806221
        a5482802f14a68893b111607c4e085a5a6410c027c1bf5fa33adeccea576211f
        272cc942acacc879de398477780a26e7545ed693edd66ad24772b0c0c4b033a7
        3ed94e1d02c384d1a288a78f5d8838b5ac0b37855dc59b987ec9b0f5c6868b03
        19f3372024fc15b247144f925d3edc453fb8551ec1ca734a37a3972b8cc9b84a
        217a4d0bc0d08c815359aba59a07eed998133587e062cf735a8521a18ac2059e
        3f142f12ccc5ae024ca33b8e4a285be5a3ced460c2c3675b956294ef2c61c96a
        88441665d56b2029688efe4540dc35abf5957bf9477f58277851719547510f22
        d01a13d0047c5b88372a654c732aff395dbf825829a06e6ae6923296218ab9ad
        33d0b07a0b6a3f3b3dd8428a1f82b72a921853d94c5d400494494f0d168148b5
        392f386b16c6056d6b8d767b7f4d918a9384c855c8b4de8450eb6ccc2df18528
        cba0a3552d5704683aa3aaf52751fd5a17801a76a58a75038533c1576e8668b5
        d290ceb20cb1ff91317d9b418311c11e617d10b4b0e9055aef5cc6ebf8b91f02
        44ed9854fde463e55112a238038750cd016735613b9c461996b90a3b4d4b0d8b
        8825ec087fb138ab868a04cb3c9249b5b118f81ed06b99e6b521f206a32a4013
        e876c6c80b36af39ac197a21b78b5f2713aa45c74335f32676c2ac2902710ec4
        2af5984687a672e0466831c7b436300b30351795b22b3a939347a7b744831baf
        e78c3bf09a8224a958568f75d71f822941dfd64d8969ada5b73c847272b59144
        91bbce5f0c09ef501babd95190f93201ab413e833f7924b696d5c2b622722459
        32a6d9c35e1670aa3bc57cd41b8d61a52aaa6d80bb774f922ecb41cd58469c4d
        cac698f6730890237602064b148650c17e6edcb3713382fe755e46f3abe0c6c4
        5184ae5c5c861e35a0e2bab2b4e8a4775584351170152512c7e99e96f285b916
        605b53b91d6c501f51a93129483046cb2dfa16d4a540ed2b6ab18bbdfba2b201
        baa20297ab1fe7133757c07dc041971aa4219157d77628f31aaf520650a2250a
        dad1524e58268d21cc62e2469c8425e86359dbcaa066acb466e06947a1c144e5
        3ee0dc930432c806c493b2bb25757b1e2c775a27a896ec25b58e8a78d4dac918
        15a1f3d90635ea25ae13cefa77877dc0c2f0678ade307243b438c54aa69dda53
        9b8a41c1b3c98c77fceb2c0bc2025a272d927943ea338cdf32a0dec8b187df5c";
    const DK_HEX: &str = "
        e4b509441c4df0443db6bc96f707be70d0b8af557ef228c78050699779b16550
        2ba1a600230c849225552250b78c10c2a5e244b5364383d29bd8a75889da089d
        c4485dc4289a559931c6cce9c8b95df482e5496ffde9b526f0055e241a19a7cb
        15a2a85ea042cae75eaae29de68ca3cc1c1de7c1a0c187897f5a45c6a9225171
        6d801c36f315b3738a489bc4ccaa2094b5f9af9524252fdb1f822b5ea1c63e66
        96b0dbc1177c9124cdf9cdbd8529f78088d232693b67ad1fc08362c3c6b44213
        e559a8a48562505565bc01cc2fa62acf736e3e5040b10b621b606b57598548ea
        7f040203dc777580c2326dbb36dbc3befe89b977179d32001414d1100a70025b
        652e8517ccd0b4c44012bb38d99599f27866a222f96b347483369a496627e47e
        5f8a6f0aa899c7427822c25bce4205ccd22e1a00ae6b30cd190243d51519a4c5
        1602568f883ba4bf8885998c0a0eabcb14e7a9d801376101a0e94282ca82492f
        ba69d301920282b5aaba6940442bd7703855390f5db42d7f9794e7bb192e628d
        119561a2597ae0e7301e7668a8a3aca6db1716465c48400fb1e841751235f641
        77854295bc7a9c060c7f5e3b9da8eba23b9199bdb69f524b8be03b6096f2ab36
        b377251888ac728657b66e5f54c71ae311e7a0528baac222da7c5b519d4c04ce
        fba133423666663a9154640a6e767c14575d910a80a8a71f0936433be8ce46a5
        a8e5a70f279bc3cee79665cc9835a3c295fc325285650bd0b1046669ab775a26
        49259d4903bc2554bd741a29027b6c65a99a16c97f79accf2818b90800a5fc29
        b401c1705892500b2899d1863390aa7b4884f2fc2593d437a1232777e6466f52
        83c5b05d8b06bed8860f35129d92fc57e0e32f504611835b6083c6383ed99124
        f282cac39d8fb17b5e079d25dc4756f30185653fcc408d4ec3c6845020a2073b
        b2569113017ce9121bdf0a9b43d850b03510f5b10b5a92b072f6b26a38a21536
        4d526a44176c0869c4895aa248f3d108e4dc5959ea2adf5a4ebf90b6cf05b9bc
        8444d118547967957ff9a81e31912eb20158836d3b63c45a628aa1d35790c512
        c0b7b0788b390bd687d5aa2dc6d63331a7152fcb36af850234d98d0f22304f50
        a4767c9293a185fac14a11d124a9d4ce53972de8eb3f57e471d5a3414d752bac
        832f12eca919d84d3832180ce6cf3b975da2e544958ca3a96c1e549067ff498e
        accc3eb475c5fee3b85017c087663d0792324280c111d83105d67929aa64056b
        ae2e5823bd7762750a051c24a466fb003e2b61e0b675f62c4764038fa2489983
        f21602240edf812e0a557ac8e6a812b4108141937d9a053f052c218b69e3169e
        4cea78602172f7000d6631b09a42b1efab6771d01ca58535ebb63d8924444419
        a26ea3bb60e96abea8c203271fa41a008e889f141b2af3f4bef78228cc00632d
        f9285a2624ba71118015ca2f0c4f2a641f753b257b90a26a016bcdc666fa065e
        cf4257c0db1eee815e5a7285d944a1d9e18986a84f5980955960b79a957a893c
        a206e2161e037498c896ed49659e32893e84c992cb3ea814a636d87a77f3479b
        ab07bb9a6af00a71c49a22fad59a818b26513c27aaf389b1f5737176a46809a2
        9337aec0c8569df87402a56327dcce27e5088959bdbc39c8d324adaef1060146
        ad3085cd737007fd550ae78135b98301f0667164375a83ec2e28a47255f1c994
        9cccbad619186825664aad07c35ea7241d29072b9bab1ffac5b4427c2b7c026b
        3dcaaf765a67d0e1630dab279376439b98141dbb7e54f104a4a00a64d743b6d2
        598e757fae50c9811c7d0c0864a144899bbc923a6722a49827a0c6aa3d62c558
        133331537dda0cc2d2f8702d6282aa4220b0d31be6eb2bb5147bd2822f587945
        b7444de151925a03c3e9969624155c21e205ec75cc5957a2268962a0f5861036
        a55bc50e1aeaa0022664c761b530c9790b3591f7d587ce40ba48d43f390a89f3
        660c98ca9c011ccd1dc1616469c008e2974fe128e802c96a9271aed831797c85
        7c345427791d6f3625c2dc3931733675d37165ab4e50174587679cf3319386b9
        7df1685aba1ab2caa00a1378cd577511ade21ec94236a9944e07e885dc12602e
        762fa5b284d73c8f9cd8395da0c3b075bd9eec465b6a4ff6e125546a3903ea84
        2191abb6d6beee29c5780758a970349879b61a028deea5404731292346c81eeb
        1d17766afbcaa68c867d91132f34a494e28caa767241b50902f4825771865fc8
        d633736248963a253dd52c1a07c7177cb6df74c43c3c74d7133da9dc915fe14b
        5bc30b86153e7b86be04189f4ce8ceb6e5cb69808d35640ac50335b1633162e2
        8260300646153be7b26a84c1c565956f8577c95cfa80b68978cd7a79e49c7e80
        8b21cdb460ed8388c4859670333b69980b2f9a0f78d7a5b9d8554893a0b5c524
        47b15318828989816f43406abfa4950607a5a57cba62f5c4340ac3d294191687
        c7d2b60ef2d83f6bec15b1618129b78577447bd1e36a87142b9c552c8cd2304f
        35b81b3bc5196c3840c0346d513ec8862ba14a172c051402f7398707a6133c8e
        88e5b57f9b9eec18aed1404ea1c47be5169e5d558e262a231a75925d25c49965
        0c8ac031be580cfb185a0e0362ce2194c53713a630cb59600c1942908ff5142b
        02364b74b3f5474301ab2da4188dca640961142f28584ebe810683c046b59cc2
        cc07025da63a5e8c5e99e24fbae183329886279c2d58d0b8cac1c7b3033f05e7
        65c75414748687ff03c00b36709d41c5968528f178cd03b813b69054d3e20fb6
        a04a713b1b169ab5f0369810fc5cd967a866b70a2d87514605794930be9b7b5d
        49d08f5cb08d84fc5beecb994dd2a5fc10aeeb4cce1ed251fd4815a9d06eea6a
        77f18c56acabb1f7c478dd52218b7bbf9770a51c3ac96402548a1bb22dd0c29f
        422330057179ec2d91e1aee27bbcb1eb4671324265e28676090896e332535ba2
        e9169c54113f19a45229c2448078ae08f7213f347244527ef726855ca054ab7a
        1ed1a40a285a60d04c00ccfa8fd318cf182705ddb888df652669b3224cf54ed6
        d00db5121331b40c7e35b1945925e5ac1503a999899317a2370020d818733303
        27db7cc1d430e32c6570fccffeea90a871a4b04ca9a8065ab20a6459f1806221
        a5482802f14a68893b111607c4e085a5a6410c027c1bf5fa33adeccea576211f
        272cc942acacc879de398477780a26e7545ed693edd66ad24772b0c0c4b033a7
        3ed94e1d02c384d1a288a78f5d8838b5ac0b37855dc59b987ec9b0f5c6868b03
        19f3372024fc15b247144f925d3edc453fb8551ec1ca734a37a3972b8cc9b84a
        217a4d0bc0d08c815359aba59a07eed998133587e062cf735a8521a18ac2059e
        3f142f12ccc5ae024ca33b8e4a285be5a3ced460c2c3675b956294ef2c61c96a
        88441665d56b2029688efe4540dc35abf5957bf9477f58277851719547510f22
        d01a13d0047c5b88372a654c732aff395dbf825829a06e6ae6923296218ab9ad
        33d0b07a0b6a3f3b3dd8428a1f82b72a921853d94c5d400494494f0d168148b5
        392f386b16c6056d6b8d767b7f4d918a9384c855c8b4de8450eb6ccc2df18528
        cba0a3552d5704683aa3aaf52751fd5a17801a76a58a75038533c1576e8668b5
        d290ceb20cb1ff91317d9b418311c11e617d10b4b0e9055aef5cc6ebf8b91f02
        44ed9854fde463e55112a238038750cd016735613b9c461996b90a3b4d4b0d8b
        8825ec087fb138ab868a04cb3c9249b5b118f81ed06b99e6b521f206a32a4013
        e876c6c80b36af39ac197a21b78b5f2713aa45c74335f32676c2ac2902710ec4
        2af5984687a672e0466831c7b436300b30351795b22b3a939347a7b744831baf
        e78c3bf09a8224a958568f75d71f822941dfd64d8969ada5b73c847272b59144
        91bbce5f0c09ef501babd95190f93201ab413e833f7924b696d5c2b622722459
        32a6d9c35e1670aa3bc57cd41b8d61a52aaa6d80bb774f922ecb41cd58469c4d
        cac698f6730890237602064b148650c17e6edcb3713382fe755e46f3abe0c6c4
        5184ae5c5c861e35a0e2bab2b4e8a4775584351170152512c7e99e96f285b916
        605b53b91d6c501f51a93129483046cb2dfa16d4a540ed2b6ab18bbdfba2b201
        baa20297ab1fe7133757c07dc041971aa4219157d77628f31aaf520650a2250a
        dad1524e58268d21cc62e2469c8425e86359dbcaa066acb466e06947a1c144e5
        3ee0dc930432c806c493b2bb25757b1e2c775a27a896ec25b58e8a78d4dac918
        15a1f3d90635ea25ae13cefa77877dc0c2f0678ade307243b438c54aa69dda53
        9b8a41c1b3c98c77fceb2c0bc2025a272d927943ea338cdf32a0dec8b187df5c
        f84e555ebe890141df38cd7d478b75c658d3f5e9d8a114494478289a74a042fd
        59434802516a1a73500c7ae6875f2df34a48054af2d4d19b60db6e7432acf2e7";
    const M_HEX: &str = "2f1e2ca7bd72af847cac38cdefc4d345909d7517543edf32e2fc491ba05eb5c3";
    const C_HEX: &str = "
        d892e0948544d020ece56f00c6495b9bd1c469ac0f134002c864d10cf61c6c78
        87890276a7546310ea077741d83428f22b60e2a40ed8bbf5d9227893bb0b7417
        df4380323426ebc9744ef35a1bb6dab1181ff0b677e8c9b6574360994f96eb87
        c3524e15e468283169d90d8a994bed0da9cd778ca239ca6c225390221fd408a3
        eae541a032d714f3d078dd1ec722ca83fffc92a416f46fc1e8710c3f8e9cd452
        c016f483985a5c1d951fe2b03c4ac2c9a0ba71f6ffa4b29ede76df69cad594d1
        0618b94e783fddaf11cc801ed5036feb4a70d071079944b7d9f1ffbb98f123dc
        34a52a40de57e079a1f8b180da9e6ecaa47a111f3c054dd9563d7e74b95ac65a
        b453afe0bf0dcec5578fe6fbc2d9eb91932799ea64b2dfa95b2c9c6e931ffb0a
        5a499ddc42c6d6b563d5a6c1f34dbcb36f57e6c10c69feadc70a3d01f5f3715f
        6bea0d3e2e7ac2b04e01ca80a2e187bb7d9efa5ee257b9c3caea3947d785ca56
        a76d0b6692a69418935dfc36fd9b26758f5e1043d5c2a6d5cc1556aed592bbcd
        652f4024bf324910f8caa2415171f3ec8b1cc494f2b519cbd2b13d317672de60
        f0a3d400e6079f85c3cdad0da5a17f2efaccec03d3c2e85b063ac3cb29f2fc48
        50705b8d472e35dc12d06a7d021db302ca883dc7d4a57b7a9e1d023960d4ea5e
        39c9d5bf328b8e4ac0afc3a6c990f598f373059ad28edbeb90412472c637877e
        a82e7105caf467a15ce961695f2ff04c750e277e78c1ce9e490a5c45168936bc
        f79261b95fb20afccdce790e0aedde204da4f6734700fc1ce05239f4ead0a496
        fd8f733049ef9b6ecc54d5c635b56d6fcac7aba1ba32d707d888b820ce3e794c
        2562798210d726c6c5db3d22a214f3f2a1f2477fcb77377af41d0f3f0ac635e7
        bf0674cebb672ea93b9504efa8c5aafb7458e41a1d85c28841877ff2f444005c
        d1c1e4227573e8151184786489c7cc39549f8a29b9c4d68be38f24081fe239c5
        d0e14ad5e361720836a72a99ce88e47bf3e6a1a9eb57cdc3724cb4789fb88c13
        5ca9ccd94ccc9bd73ee8a796bfea36e894ed27b0791e8ad23fcda5ae7c167d57
        74aa00468d97bd0fd2b9bd78d01e1e39a66994a369212d95d7886be00f2b7a1f
        187c85b873f33cb7d3e4c23993f7bc9ff7abbc6fd9d5c54f06e5f31b79da992b
        c0f2aa79a36bd00483ca0add2dcb2be36c27dc913668f030128e82281e5e66e8
        7c75dce3fc60431ce6a19c6c45e19a2d84430e945034b7da6c506ef009493af2
        2f701088e01c20acb21972ff28d284d2a57a69d108161938f028011ba985e2b9
        25532b52eed878646579183eb0ef3fb57be1e05c1bd2b68d0e4a9ce104e0ecdf
        b30268e6c007812fd88498d97795447e7f88ce8a3bcd161b1e34dcf77413b778
        f88a3ad66f247f7664d0f6af31ec1dadc00fec09d6ac63fc8a802c3752484b5b
        43555381043fdd30ef5d7dcf4f5b68eb89f08a0fe5ecb65d6d9418ca5283ca39
        8d3129cd3b44edf5e3568c953ce2b66a28b473cab0c43918a05c5a4358d13926
        04495d06b395187caa6b36050b436b218485466cb6643eda7ac66aaeea758715
        a22a8849066879cc966da7e0f7e843a1e234920aecf2a4fed2f78c69a6c103a5
        a535d77976b1e40bb0d75d6e370212017734f2b0be9f3f87c5583634ea6a998d
        8fe9ce5e5905f2a6abb9d635425d06731c1227eb634a3603d081cb1a7c2b0c16
        85942efd6f65992dd39aa67cd954fa0310a0f5866ac6121e27e349d5c2adf37a
        672c1dfb021a855000f0c0c29489926b997930b20df641120ef8605cfd9482ee
        a9344918aec689f580a94508f318e63eadc3eb7486b9fcd7a92ba16f5e02cf78
        ef73f528fae3a43d451c58261a82b3735e09d4f4679ce112803505006cbae6ea
        f641a6f66b0c51ee90095735d87ad88c3d19cd7888248fcc3872939605f20597
        6ef6c6aa52f8316e9def841594373fcd177f0e24d8975653a29f738fa8f0d457
        f2d72b7c00a4b7a66fb080f705a8bd599314354e65842af598b8a2a40f6cb320
        8762ac6cd467d06a2e987a7af72e355bea297da87bc9250ebe8d8f85cf292200
        a21d93475ba46be78c17c1605c10b97659859f08f980114955e68361e180c980
        15aa46a776070c4f2b328a903a70ad226742f0022279179fd2530390f190e300
        8f7202c83a6a15866df848c8a150d12287451dec8ee7f04c1c3121e4bba687d7";
    const K_HEX: &str = "5087e3b0c90bf601dd6501e071270eff8683621e9f5d67a7a668e50c4f460a75";

    // 1) 种子展开(keyGen 案例)
    let d: [u8; 32] = hex(D_HEX).try_into().expect("32 bytes");
    let z: [u8; 32] = hex(Z_HEX).try_into().expect("32 bytes");
    let (ek, _dk) = k1024::keypair_from_seed(&d, &z);
    if ek.as_bytes() != hex(EK_KEYGEN_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem1024"));
    }
    // 2) 封装(encapDecap 案例自带 ek)
    let ek2 = k1024::EncapsKey::from_bytes(hex(EK_HEX).as_slice())?;
    let m: [u8; 32] = hex(M_HEX).try_into().expect("32 bytes");
    let (c, ss) = k1024::encapsulate_with_seed(&ek2, &m)?;
    if c.as_bytes() != hex(C_HEX).as_slice() || ss.expose_bytes() != hex(K_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem1024"));
    }
    // 3) 解封装(同案例 dk)
    let dk2 = k1024::DecapsKey::from_bytes(hex(DK_HEX).as_slice())?;
    let ss2 = k1024::decapsulate(&dk2, &c);
    if ss2.expose_bytes() != hex(K_HEX).as_slice() {
        return Err(crate::Error::SelfTestFailed("mlkem1024"));
    }
    Ok(())
}

// ---------------------------------------------------------------------------
// KAT:RSA PKCS#1 v1.5(内建 KAT 密钥签名/验证;openssl 交叉锚定)
// ---------------------------------------------------------------------------

const RSA_KAT_PKCS8_HEX: &str = include_str!("kat_rsa_key.hex");
const RSA_KAT_SIG_HEX: &str = include_str!("kat_rsa_sig.hex");

fn hex(s: &str) -> Vec<u8> {
    let clean: String = s.chars().filter(|c| !c.is_whitespace()).collect();
    (0..clean.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&clean[i..i + 2], 16).expect("valid hex"))
        .collect()
}

fn kat_rsa_pkcs1v15() -> Result<(), crate::Error> {
    use crate::sign::rsa;
    let sk = rsa::SigningKey::from_pkcs8_der(&hex(RSA_KAT_PKCS8_HEX))?;
    let sig = sk.sign_pkcs1v15(256, b"sample")?;
    if sig != hex(RSA_KAT_SIG_HEX) {
        return Err(crate::Error::SelfTestFailed("rsa-pkcs1v15"));
    }
    let pk = rsa::VerifyKey::from_spki_der(&hex(RSA_KAT_PUB_SPKI))?;
    pk.verify_pkcs1v15(256, b"sample", &sig)?;
    Ok(())
}

const RSA_KAT_PUB_SPKI: &str = include_str!("kat_rsa_spki.hex");

// ---------------------------------------------------------------------------
// KAT:CTR-DRBG(CAVP [AES-256 no df] 流程:Instantiate → Reseed →
// Generate → Generate,第二次输出逐字节比对)
// ---------------------------------------------------------------------------

fn kat_drbg() -> Result<(), crate::Error> {
    const EI_HEX: &str = "e4bc23c5089a19d86f4119cb3fa08c0a4991e0a1def17e101e4c14d9c323460a7c2fb58e0b086c6c57b55f56cae25bad";
    const EIR_HEX: &str = "fd85a836bba85019881e8c6bad23c9061adc75477659acaea8e4a01dfe07a1832dad1c136f59d70f8653a5dc118663d6";
    const EXPECT_HEX: &str = "b2cb8905c05e5950ca31895096be29ea3d5a3b82b269495554eb80fe07de43e193b9e7c3ece73b80e062b1c1f68202fbb1c52a040ea2478864295282234aaada";
    let mut d = CtrDrbg::new(&hex(EI_HEX), b"")?;
    d.reseed(&hex(EIR_HEX), b"")?;
    let mut discard = [0u8; 64];
    d.generate(&mut discard)?;
    let mut out = [0u8; 64];
    d.generate(&mut out)?;
    if out == hex(EXPECT_HEX)[..] {
        Ok(())
    } else {
        Err(crate::Error::SelfTestFailed("ctr-drbg"))
    }
}

use crate::drbg::CtrDrbg;

#[cfg(test)]
mod failure_injection {
    use super::*;

    #[test]
    fn failed_state_gates_and_recovers() {
        reset_for_tests();
        // 正常自检通过
        assert_eq!(run_power_on_self_tests(), SelfTestStatus::Passed);
        // 注入失败:模块进入错误状态
        force_fail_for_tests("injected");
        assert_eq!(status(), SelfTestStatus::Failed("injected"));
        assert_eq!(
            ensure_passed(),
            Err(crate::Error::SelfTestFailed("injected"))
        );
        // 复位后恢复
        reset_for_tests();
        assert_eq!(run_power_on_self_tests(), SelfTestStatus::Passed);
    }
}

#[cfg(test)]
mod kat_debug {
    use super::*;

    #[test]
    fn debug_ecdsa_kat() {
        let d = hex("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721");
        let sk = crate::sign::ecdsa::p256::SigningKey::from_seed(d.as_slice().try_into().unwrap());
        let sig = sk.sign(b"sample").unwrap();
        println!(
            "sig  = {}",
            sig.iter().map(|b| format!("{b:02x}")).collect::<String>()
        );
        println!("kat  = {}", hex("3046022100efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716022100f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8").iter().map(|b| format!("{b:02x}")).collect::<String>());
        println!(
            "eq   = {}",
            sig == hex(
                "3046022100efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716022100f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8"
            )
        );
        let q = sk.public_key_sec1();
        let vk = crate::sign::ecdsa::p256::VerifyKey::from_sec1_point(&q).unwrap();
        println!("verify = {:?}", vk.verify(b"sample", &sig));
    }
}