hardware-rust-crypto 1.0.1

Hardware-only AES-256-GCM and AES-256-GCM-SIV AEAD plus AES-256-CTR key/nonce generation (AES-NI/PCLMULQDQ, ARMv8 AES/PMULL), with compact, caller-placeable, zeroized key state.
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
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
# Security Assessment - hardware-rust-crypto

**Multi-Model Agentic Cryptographic Review**

---

## Document control

| Field | Value |
| --- | --- |
| **Title** | Security Assessment of the `hardware-rust-crypto` AES-256-GCM and key-generation primitives |
| **Target** | `hardware-rust-crypto` workspace - `hardware-aes-gcm`, `hardware-random` |
| **Version reviewed** | Initial revision |
| **Assessment type** | Multi-model agentic cryptographic source review with differential, statistical, and assembly-level verification |
| **Assessment date** | 2026-06-11 |
| **Toolchain** | rustc 1.96.0; crate MSRV 1.88; edition 2021 |
| **Platforms** | `x86_64` (AES-NI, PCLMULQDQ, SSSE3, RDSEED); `aarch64` (ARMv8 AES, PMULL, FEAT_RNG) |
| **Classification** | Internal / pre-integration |
| **Status** | Initial |

---

## Table of contents

1. [Executive summary]#1-executive-summary
2. [Scope and limitations]#2-scope-and-limitations
3. [Methodology]#3-methodology
4. [Threat model]#4-threat-model
5. [System overview]#5-system-overview
6. [Findings]#6-findings
7. [Cryptographic construction analysis]#7-cryptographic-construction-analysis
8. [Constant-time and side-channel analysis]#8-constant-time-and-side-channel-analysis
9. [Memory safety and `unsafe` review]#9-memory-safety-and-unsafe-review
10. [Dependency and supply-chain assessment]#10-dependency-and-supply-chain-assessment
11. [Test coverage assessment]#11-test-coverage-assessment
12. [Standards conformance matrix]#12-standards-conformance-matrix
13. [Residual risk register]#13-residual-risk-register
14. [Recommendations]#14-recommendations
15. [Conclusion]#15-conclusion
- [Appendix A - Rating methodology]#appendix-a---rating-methodology
- [Appendix B - Verification performed and captured evidence]#appendix-b---verification-performed-and-captured-evidence
- [Appendix C - References]#appendix-c---references
- [Appendix D - Glossary]#appendix-d---glossary

---

## 1. Executive summary

`hardware-rust-crypto` is a small, hardware-only cryptographic primitive set
intended to replace `ring`/RustCrypto in a cached-key tier that holds many keys resident. It
provides AES-256-GCM authenticated encryption (`hardware-aes-gcm`) and
AES-256-CTR key/nonce generation (`hardware-random`). Its defining design
decision is the deliberate removal of all software cipher fallback: AES rounds
and GF(2^128) multiplication execute exclusively as CPU instructions (AES-NI /
PCLMULQDQ on `x86_64`; ARMv8 AES / PMULL on `aarch64`), and the implementation
fails closed with a typed error where the hardware is absent.

The codebase comprises approximately **3,961 lines of Rust** across two
production crates, a large fraction of it necessarily `unsafe` (SIMD intrinsics
and in-place initialization). The cryptographic cores are adapted from audited
upstreams (`ghash`/`polyval` from RustCrypto; AES backends from `rand_aes`)
with provenance recorded in `NOTICE`.

### 1.1 Overall assessment

Within the threat model of section 4 and the scope of section 2, the implementation is
**well-constructed and the cryptographic constructions are sound as reviewed.**
AES-256-GCM output was verified byte-identical to two independent
implementations - RustCrypto `aes-gcm` 0.10 and `ring` 0.17 - across a dense
differential corpus, and against NIST Special Publication 800-38D known-answer
vectors. The `unsafe` surface is uniformly documented with `SAFETY:`
justifications and the memory-safety arguments hold under review. **No critical
or high-severity vulnerability was identified.** Constant-time behavior of the
authentication path was confirmed both by emitted-assembly inspection and by a
statistical (dudect) timing test.

The remaining findings are characteristic of any AES-GCM deployment and of an
un-certified library, and are dominated by integrator responsibilities and
assurance residuals rather than implementation defects.

### 1.2 Findings summary

| ID | Title | Risk | Status |
| --- | --- | --- | --- |
| [HRC-2026-01]#hrc-2026-01---aes-gcm-nonce-uniqueness-and-invocation-limits-are-not-enforced | AES-GCM nonce uniqueness and invocation limits are not enforced | **Medium** | Mitigation available |
| [HRC-2026-02]#hrc-2026-02---cpu-rng-only-reseed-narrows-post-compromise-recovery-to-the-cpu-vendor | CPU-RNG-only reseed narrows post-compromise recovery to the CPU vendor | **Low** | Open (residual) |
| [HRC-2026-03]#hrc-2026-03---rng-health-test-is-not-sp-800-90b-conformant | RNG health test is not SP 800-90B-conformant | **Low** | Open |
| [HRC-2026-04]#hrc-2026-04---unsafeffi-soundness-is-not-tool-verified | Unsafe/FFI soundness is not tool-verified | **Low** | Substantially addressed (Miri + Valgrind + sanitizers in CI) |
| [HRC-2026-05]#hrc-2026-05---constant-time-assurance-is-empirical-not-formal | Constant-time assurance is empirical, not formal | **Low** | Open (residual) |
| [HRC-2026-06]#hrc-2026-06---ephemeral-stack-and-register-copies-of-key-material-are-not-wiped | Ephemeral stack/register copies of key material are not wiped | Info | Open (inherent) |
| [HRC-2026-07]#hrc-2026-07---drop-based-zeroization-is-defeated-by-memforget-and-leaks | Drop-based zeroization is defeated by `mem::forget`/leaks | Info | Open (inherent) |
| [HRC-2026-08]#hrc-2026-08---no-wycheproof-or-formal-coverage-of-ghash-aggregation-edge-cases | No Wycheproof or formal coverage of GHASH aggregation edge cases | **Low** | Resolved |
| [HRC-2026-09]#hrc-2026-09---independent-review-and-cavp-validation-not-yet-performed | Independent review and CAVP validation not yet performed | Info | Open |

**Risk distribution:** 0 Critical / 0 High / 1 Medium / 4 Low / 4 Informational.

All workspace status checks (format, Clippy `-D warnings`, tests, docs,
dependency audit) are green at the reviewed commit on both CI architectures
(`x86_64` Linux, `aarch64` macOS).

---

## 2. Scope and limitations

This is a multi-model agentic cryptographic review: source analysis combined
with execution of the project's verification tooling and independent
differential, statistical, and assembly-level cross-checks (section 3). Findings are
cross-checked across models; reviewers should re-run the verification commands
in [Appendix B](#appendix-b---verification-performed-and-captured-evidence) and
apply their own judgment.

- **It is a source-level cryptographic review**, not an accredited
  certification. Nothing here constitutes FIPS 140-3 validation, CAVP algorithm
  validation, or Common Criteria evaluation. Where conformance to a NIST or
  IETF specification is asserted, it means "the construction follows the cited
  specification as verified by source review and differential/known-answer
  testing," not "validated by an accredited laboratory."
- **Scope is the two production crates and their direct support code.** Out of
  scope: the consuming application, the eventual FFI integration, build/release
  infrastructure beyond the committed CI workflow, and the development host.
- **It is a point-in-time review** of the initial revision.

Ratings follow the methodology in [Appendix A](#appendix-a---rating-methodology).

---

## 3. Methodology

The review combined manual source analysis with execution of the project's
tooling and independent cross-checks.

**Manual review passes** (iterative; each from a distinct angle): cryptographic
construction conformance; memory safety and `unsafe`/FFI soundness;
constant-time / side-channel behavior; key-material lifecycle (placement,
zeroization, fork safety); API contract / type-state / misuse resistance;
dependency and supply-chain posture; adversarial input handling and resource
bounds.

**Dynamic and differential verification** (commands and captured output in
[Appendix B](#appendix-b---verification-performed-and-captured-evidence)):

- Byte-for-byte differential testing against RustCrypto `aes-gcm` 0.10 and
  `ring` 0.17 (encryption equality and cross-decryption both directions).
- NIST SP 800-38D known-answer vectors (zero-key/zero-nonce, empty and 16-byte
  plaintext).
- AES-256-CTR keystream cross-checked against OpenSSL AES-256-ECB.
- Emitted-assembly inspection of secret-handling paths (`cargo-show-asm`).
- A dudect-style statistical timing test (Welch's t-test) over decryption.
- Dependency vulnerability scan (`cargo audit`).

Subsequent assurance work (post-review) extended this with machine-checked
proofs of the GHASH/POLYVAL field core, full-lifecycle Miri, Valgrind/ASan/TSan,
NIST CAVP vectors, Project Wycheproof vectors, property-based and fuzz testing,
and an RNG statistical battery - all CI-reproducible and mapped in
[assurance.md](assurance.md). The per-finding "Update" notes below record where
this changes a finding's status.

**Standards consulted:** FIPS 197; NIST SP 800-38A, 800-38D, 800-90A Rev. 1,
800-90B, 800-133 Rev. 2; FIPS 140-3 (terminology / non-conformance scoping);
RFC 5116, 5288, 8452. Report structure follows publicly available
cryptographic review deliverables, including the NCC Group review of the
RustCrypto AEAD crates (2020, MobileCoin-funded) - the direct lineage of this
code's GHASH/POLYVAL paths - and the general format of NCC Group, Trail of
Bits, and Cure53 reports and OSTIF-coordinated open-source crypto audits.

---

## 4. Threat model

### 4.1 Assets

- **Long-lived key material**: AES-256 keys cached in the consuming
  application's memory.
- **Key-equivalent state**: expanded AES round keys and GHASH key powers.
- **CSPRNG state**: AES-CTR generator seeds and counters.
- **Plaintext**: data-encryption-key material and protected records.

### 4.2 Security goals

1. Confidentiality and integrity of AES-256-GCM-protected data
   (IND-CPA + INT-CTXT).
2. Wire interoperability with existing AES-256-GCM ciphertexts (the nonce-appended layout
   `ciphertext || tag || nonce`).
3. Reduced side-channel surface versus table-based software AES.
4. Minimal, caller-placeable, zeroizable key-state footprint.
5. Generator hygiene: reseeding, fork detection, no duplicate keystreams.

### 4.3 Adversaries

| ID | Adversary | In scope |
| --- | --- | --- |
| A1 | Network / chosen-ciphertext: submits crafted ciphertext/tag/AAD/nonce to decryption seeking forgery or recovery | Yes |
| A2 | Co-resident timing: measures operation timing to recover key material | Yes (software/cache timing); transient-execution attacks **out** |
| A3 | Malicious/defective hardware RNG | Yes (reseed path) |
| A4 | Process-image: reads memory post-compromise or inherits state across `fork`/snapshot | Partial (zeroization, fork detection); live full-memory compromise **out** |

### 4.4 Explicitly out of scope

Spectre/Meltdown and other transient-execution / micro-architectural data
sampling; physical attacks (power/EM, fault injection, cold-boot); the
correctness of the AES/CLMUL/PMULL silicon (trusted vendor primitive - see
[HRC-2026-05](#hrc-2026-05---constant-time-assurance-is-empirical-not-formal));
and nonce-generation *policy* at the integration layer (the library emits uniform
random bytes; uniqueness discipline is the integrator's - see
[HRC-2026-01](#hrc-2026-01---aes-gcm-nonce-uniqueness-and-invocation-limits-are-not-enforced)).

---

## 5. System overview

```
hardware-rust-crypto (workspace facade, src/lib.rs)
|-- hardware-aes-gcm            AES-256-GCM, hardware-only
|   |-- lib.rs    public API; GCM composition (J0, CTR, GHASH glue, tag)
|   |-- aes.rs    AES-256 key expansion + 1-/8-block encryption (NI / ARMv8)
|   |-- ghash.rs  GHASH->POLYVAL, CLMUL/PMULL, 8-block aggregated reduction
|   |-- nonce.rs  generated-nonce sequence (96-bit OS salt + 64-bit counter)
|   `-- fork.rs   pthread_atfork generation counter (nonce re-salt)
`-- hardware-random            key/nonce generation
    |-- lib.rs    AES-CTR generator; reseed, fork detection
    |-- aes_ctr.rs  AES-256-CTR-128 backend (NI / ARMv8)
    |-- entropy.rs  CPU-RNG (RDSEED / RNDRRS) + stuck-output screen
    `-- fork.rs     pthread_atfork generation counter
```

### 5.1 Encryption data flow

```
key (32B) --> KeyState::init_in_place
                 |- aes::Aes256::init_in_place          (expand round keys)
                 `- H = E(K, 0^128) --> GHashKey         (derive POLYVAL H^1..H^8)
nonce(12B),aad,plaintext --> seal()
                 J0 = nonce || 0^31 || 1
                 mask = E(K, J0)
                 for each 128B batch:  C = P XOR AES-CTR(inc32(J0)...)   (8-way)
                                       GHASH <- C                      (8-block agg., stitched)
                 S = GHASH(aad, C, lengths)
                 tag = S XOR mask
                 out = C || tag                          (|| nonce in the nonce-appended layout)
```

### 5.2 Production dependency graph (verified minimal)

- `hardware-aes-gcm` -> `subtle` 2.6.1, `zeroize` 1.8.2, `getrandom` 0.3.4
  (nonce salt), `libc` 0.2 (Unix; fork detection)
- `hardware-random` -> `getrandom` 0.3.4, `zeroize` 1.8.2, `libc` 0.2 (Unix)

No software cipher and no third-party cryptographic *implementation* appears in
either production graph. `ring`, RustCrypto `aes-gcm`, `rand_chacha`, and
`salsa20` are dev-dependencies only, used as benchmark/interop/timing
baselines. A CI step fails the build if a forbidden crate enters a production
graph.

---

## 6. Findings

Each finding states an overall **Risk** plus the **Impact** and
**Exploitability** components ([Appendix A](#appendix-a---rating-methodology)),
a CVSS 3.1 reference where one is meaningful, the affected **Location**,
**Evidence**, an **Impact** analysis, and a **Recommendation**.

---

### HRC-2026-01 - AES-GCM nonce uniqueness residual in hazmat explicit-nonce mode

| | |
| --- | --- |
| **Risk** | Medium |
| **Impact** | High (on misuse) |
| **Exploitability** | Low (requires integrator misuse) |
| **CVSS 3.1** | `AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N` -> 7.4 (conditional impact; see note) |
| **Category** | Cryptography - key/IV management |
| **Status** | Mitigated in the default API; residual risk remains only in the non-default `hazmat-explicit-nonce` feature |
| **Location** | `src/aes_gcm/mod.rs` - default envelope APIs; hazmat caller-supplied helpers gated by `hazmat-explicit-nonce`; nonce construction in `nonce.rs` |

**Description.** AES-GCM is catastrophically fragile to nonce reuse under a
fixed key. Two messages encrypted with the same `(key, nonce)` pair leak the
XOR of their plaintexts and, more seriously, allow recovery of the GHASH
authentication subkey `H`, enabling universal forgery for that key. AES-GCM is
also bounded in the number of invocations per key: for randomly generated
96-bit IVs, NIST SP 800-38D section 8.3 limits a key to roughly 2^32 invocations to
keep the IV-collision probability acceptably low.

The default library API now owns nonce generation: `encrypt` and `encrypt_to`
generate a nonce and return `ciphertext || tag || nonce`; `decrypt` and
`decrypt_to` parse the nonce from that envelope. Low-level helper methods that
accept caller-supplied nonces are gated behind the non-default
`hazmat-explicit-nonce` feature. If that feature is enabled, the helper path
validates nonce *length* but performs no uniqueness tracking and enforces no
per-key invocation counter, matching the contract of `ring` and RustCrypto.

**Evidence.** The default encrypt path takes no nonce parameter and appends the
generated nonce to the envelope. The hazmat caller-supplied-nonce path
validates length only (`nonce_from_slice` in `src/aes_gcm/mod.rs`):

```rust
fn nonce_from_slice(nonce: &[u8]) -> Result<[u8; NONCE_SIZE], Error> {
    nonce.try_into().map_err(|_| Error::InvalidNonceLength)
}
```

When `hazmat-explicit-nonce` is enabled, `encrypt_with_nonce(nonce, aad,
plaintext)` accepts any 12-byte value with no uniqueness or invocation-count
check.

**Impact.** If a caller enables the hazmat API and reuses a `(key, nonce)` pair,
an A1 adversary observing two ciphertexts can recover `H` and forge arbitrary
authenticated ciphertexts under that key, and can recover plaintext XOR. The
CVSS base of 7.4 reflects this *conditional* impact; the assessed Risk is
**Medium**, not High, because the default API removes the nonce parameter and
exploitation requires deliberate use of the hazmat surface plus an integration
defect.

**Mitigation.** The default API owns nonce uniqueness so the caller cannot
reuse one accidentally. The construction is `nonce = (salt + counter) mod
2^96`, with `salt` a 96-bit value drawn from the OS (re-drawn on fork) and
`counter` a per-instance 64-bit value (see section 7.6). Within an instance
uniqueness is guaranteed; across instances the residual is a sub-random 96-bit
base-range overlap. This *prevents* reuse but, being plain AES-GCM, does not
*survive* it - a call site that cannot trust local nonce state still needs a
misuse-resistant mode. The "within an instance uniqueness is guaranteed" claim
is now **machine-checked**: a `cfg(kani)` harness
(`nonce_value_is_injective_in_counter`) proves with Kani/CBMC that the compiled
`nonce_value(base, counter)` is injective in `counter` over the full `2^64`
sequence for every base, so the per-call counter walk cannot reuse a nonce.

**Recommendation.**
1. Keep caller-supplied-nonce helpers out of the default API and public
   examples; require explicit opt-in through `hazmat-explicit-nonce`.
2. Document the nonce-uniqueness contract and the SP 800-38D section 8.3
   per-key invocation limit prominently on the hazmat API, with the failure
   mode stated explicitly.
3. Where a deterministic nonce is required, use the SP 800-38D section 8.2.1
   construction or rotate keys before 2^32 invocations.
4. Consider offering an AES-GCM-SIV (RFC 8452) misuse-resistant mode for call
   sites that cannot trust local nonce uniqueness at all.

---

### HRC-2026-02 - CPU-RNG-only reseed narrows post-compromise recovery to the CPU vendor

| | |
| --- | --- |
| **Risk** | Low |
| **Impact** | Medium |
| **Exploitability** | Low (requires prior state compromise + malicious CPU RNG) |
| **CVSS 3.1** | `AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N` -> ~4.4 (conditional) |
| **Category** | Cryptography - RNG / reseeding |
| **Status** | Open (residual) |
| **Location** | `src/random/mod.rs` - `reseed` (L386), `reseed_blend` (L422); `src/random/entropy.rs` |

**Description.** When `cpu-rng-reseed` is enabled (default) and a CPU hardware
RNG is present, reseeds draw exclusively from the CPU RNG, blended through the
current secret state; the OS entropy source is not consulted on reseed. Because
the blend incorporates prior state, a *weak-but-live* CPU RNG cannot reduce
security below "no reseed" (see section 7.4). However, *recovery from a state
compromise* then depends on the CPU RNG contributing genuine fresh entropy. A
fully malicious CPU RNG combined with a prior state leak (A3 + A4) defeats
recovery - an unavoidable "both sources bad" case - but a CPU-RNG-only reseed
schedule narrows the recovery guarantee to a single vendor's TRNG.

**Evidence.** The reseed path consults the CPU RNG and only falls back to the
OS when it is unavailable or the draw is screened out:

```rust
// src/random/mod.rs:386
fn reseed(&mut self) -> Result<(), Error> {
    let mut entropy_input = Zeroizing::new([0_u8; AES_CTR_SEED_SIZE]);
    if entropy::cpu_rng_fill(entropy_input.as_mut()) {
        self.reseed_blend(&entropy_input);          // CPU RNG path
    } else {
        fill_from_os(entropy_input.as_mut())?;      // OS fallback only
        self.reset_from_seed(&entropy_input)?;
    }
    ...
}
```

**Impact.** Limited. Initial seeding is always from the OS (so every chain is
rooted in a full-entropy seed), and the blend (section 7.4) means a degraded CPU RNG
cannot weaken a non-compromised state. The residual is confined to
post-compromise recovery speed/assurance under a hostile CPU RNG.

**Recommendation.** Add a periodic forced OS reseed (e.g., every Nth reseed or
on a wall-clock interval) so recovery never depends solely on the CPU RNG. At
the default 1 GiB reseed interval the cost is negligible.

---

### HRC-2026-03 - RNG health test is not SP 800-90B-conformant

| | |
| --- | --- |
| **Risk** | Low |
| **Impact** | Low |
| **Exploitability** | Low |
| **CVSS 3.1** | N/A (assurance/compliance gap) |
| **Category** | Cryptography - entropy source health |
| **Status** | Open |
| **Location** | `src/random/entropy.rs` - `stuck_output` (L62), `cpu_rng_fill` (L32) |

**Description.** Each CPU-RNG draw is screened for stuck output by rejecting any
draw containing two identical 64-bit words - the granularity at which
RDSEED/RNDRRS return data. This catches all-zero, all-ones, constant-word, and
short-period sources, and is conceptually a continuous repetition test. NIST SP
800-90B section 4.4 requires both a Repetition Count Test (section 4.4.1) and an Adaptive
Proportion Test (section 4.4.2), with cutoffs derived from the source's assessed
min-entropy, plus start-up testing. The implemented screen has no APT (so it
will not detect a source biased but not stuck), no min-entropy-derived cutoff,
and no startup self-test; it is therefore not an SP 800-90B-conformant
health-test suite.

**Evidence.**

```rust
// src/random/entropy.rs:62
fn stuck_output(buf: &[u8]) -> bool {
    let mut outer = 0_usize;
    while outer + 8 <= buf.len() {
        let word = &buf[outer..outer + 8];
        let mut inner = outer + 8;
        while inner + 8 <= buf.len() {
            if &buf[inner..inner + 8] == word { return true; }
            inner += 8;
        }
        outer += 8;
    }
    false
}
```

**Impact.** Low. The CPU RNG is never the sole entropy source (initial seed is
the OS), and the reseed blend (section 7.4) neutralizes a weak-but-live source, so the
screen only needs to catch gross failure (a dead/stuck source contributing zero
entropy) - which it does. Subtle bias is mitigated architecturally rather than
detected.

**Recommendation.** Document the screen precisely as "continuous stuck-output
detection, not SP 800-90B validation." If SP 800-90B conformance becomes a
requirement, add an APT and startup self-tests with cutoffs matched to the
assessed source min-entropy.

---

### HRC-2026-04 - Unsafe/FFI soundness is not tool-verified

| | |
| --- | --- |
| **Risk** | Low |
| **Impact** | High (if latent UB existed) |
| **Exploitability** | Low (no evidence of UB; differential corpus would surface most corruption) |
| **CVSS 3.1** | N/A (assurance gap) |
| **Category** | Memory safety - assurance |
| **Status** | Open (residual) |
| **Location** | `src/aes_gcm/{aes,ghash,lib}.rs`; `src/random/{aes_ctr,entropy,lib}.rs` |

**Description.** The crates contain a substantial `unsafe` surface
([section 9](#9-memory-safety-and-unsafe-review)). Miri cannot execute AES/PMULL
intrinsics, inline assembly, or `is_*_feature_detected!`, so the `unsafe` paths
are not Miri-checked, and CI runs no sanitizer or Valgrind pass. Soundness is
therefore argued by review and validated only indirectly by the differential
corpus (which would surface most memory-corruption regressions as output
mismatches or crashes).

**Evidence.** Every `unsafe` block carries a `SAFETY:` comment and the
soundness-critical patterns were reviewed (write-before-read in-place init;
raw-pointer-plus-`PhantomData` aliasing model; runtime-feature-gated
intrinsics). No defect was found, but no tool independently proves the absence
of UB.

**Impact.** A latent unsafe-Rust defect (out-of-bounds, aliasing violation,
uninitialized read) could in principle cause memory corruption or information
disclosure. None was identified; this finding records the *assurance gap*, not
a known vulnerability.

**Recommendation.** Add a Miri job covering the architecture-independent logic
(length checks, buffer handling, the stuck-output screen, the fork guard),
optionally behind a cfg that stubs the intrinsics with a scalar reference path
used only under Miri.

**Update (assurance work; see [assurance.md](assurance.md) §1).** The
recommendation is implemented and the gap is substantially closed. On x86 Miri
implements the AES-NI and PCLMULQDQ intrinsics, so `cargo miri test --lib
aes_gcm` now runs the **whole** AES-256-GCM/SIV key-state lifecycle and the real
AES/GHASH code under Miri's UB checker (aliasing, provenance, out-of-bounds,
uninitialized reads) and passes. The one intrinsic Miri lacks
(`aeskeygenassist`) is routed under `cfg(miri)` through a software key schedule
proven byte-identical to hardware by `software_schedule_matches_hardware`. Miri
found a genuine Stacked Borrows violation on its first run - in a *test*, not
production code - which was fixed. CI additionally runs Valgrind memcheck and
ASan over the real AES-NI/PCLMULQDQ binary and TSan over the cross-thread paths -
and **now also over the ARMv8 AES/PMULL binary on Linux arm64 runners**
(`ubuntu-24.04-arm`), so the aarch64 code generation is memory-checked on real
hardware, not by proxy. Residual: the AES-CTR generator's own `aeskeygenassist`
keeps `random::` out of the Miri job (covered by Valgrind/ASan); aarch64 NEON
crypto intrinsics are not yet modeled by Miri, so the Miri job runs on x86 (the
aarch64 binary's memory safety is the Valgrind/ASan/TSan arm64 jobs). In addition, the `kani` CI job
runs Kani/CBMC over the *compiled* intrinsic-free logic - the counter increments,
length validators, nonce parser, and the two attacker-facing envelope splitters -
proving over all inputs (bounded where noted) that they never panic, never index
out of bounds, and match the spec; this is exactly the architecture-independent
buffer/length handling the recommendation named, now verified as compiled code
rather than only reviewed.

---

### HRC-2026-05 - Constant-time assurance is empirical, not formal

| | |
| --- | --- |
| **Risk** | Low |
| **Impact** | High (if a leak existed) |
| **Exploitability** | Low (no leak found; requires co-residency) |
| **CVSS 3.1** | N/A (assurance/residual) |
| **Category** | Cryptography - side channel |
| **Status** | Open (residual) |
| **Location** | `src/aes_gcm/mod.rs` - `constant_time_eq` (L887); `src/aes_gcm/ghash.rs` - `mulx` (L201) |

**Description.** The constant-time guarantee rests on three pillars
(documented in `docs/constant-time.md`): (1) the *accepted* vendor guarantee
that AES and carryless-multiply instructions are data-independent in timing -
the same trust boundary `ring`/RustCrypto rely on; on ARMv8.4+ the formal
guarantee is gated by `PSTATE.DIT`, which this library does not set; (2)
absence of secret-dependent control flow, verified by review and assembly
inspection; (3) compiler non-interference, defended by `subtle` and
`core::hint::black_box` barriers. *As originally assessed*, none of these was a
machine-checked proof and the statistical test was not CI-gated - both gaps are
now closed (see the Updates below): the scalar secret surface is verified
binary-branch-free as a deterministic, CI-gated check on both architectures, and
the dudect timing test is CI-gated.

**Evidence.** Tag comparison is constant-time via `subtle`; the one scalar
secret-dependent operation (`mulx`'s carry fold) is barriered:

```rust
// src/aes_gcm/mod.rs:887
fn constant_time_eq(expected: &[u8; TAG_SIZE], actual: &[u8]) -> bool {
    expected.as_slice().ct_eq(actual).into()
}
// src/aes_gcm/ghash.rs:201
fn mulx(block: &[u8; 16]) -> [u8; 16] {
    let mut v = u128::from_le_bytes(*block);
    let v_hi = core::hint::black_box(v >> 127);   // barrier: no 0/1 specialization
    ...
}
```

Emitted-assembly inspection and a dudect test confirm the property
empirically (section 8, Appendix B): the tag-mismatch-position test is stable at
|t| ~ 0.2, two orders of magnitude below the |t| ~ 267 an early-exit
comparison produces.

**Impact.** If a future compiler or code change introduced a secret-dependent
branch undetected, a co-resident A2 adversary could in principle recover key
or tag bits. No such leak exists at the reviewed commit.

**Recommendation.** Retain the assembly inspection and dudect procedures and
re-run them on compiler upgrades. Consider setting `PSTATE.DIT` around crypto
sections on `aarch64` once stable intrinsics exist. Transient-execution
attacks remain out of scope by design.

**Update (assurance work; see [assurance.md](assurance.md) §2.3).** The
constant-time verification *method* is now documented in full, including why the
Valgrind-secret-poisoning (ctgrind) technique is deliberately not used here:
memcheck's shadow-value propagation through the AES-NI/PCLMULQDQ SIMD
instructions is incomplete on exactly this code, producing false positives. The
"not CI-gated" sub-gap is now closed: the `constant-time` CI job runs the dudect
Welch t-test on both the GCM and SIV decrypt paths (tag-mismatch-position and
content independence) on every build and **fails the build if `|t| ≥ 25`**.
Best-of-three batches with first-pass early-exit absorb shared-runner jitter
without flaking, since a real early-exit leak holds `|t|` in the hundreds across
every batch (~267 measured) versus ~0.4-2.4 for the constant-time code.

**Update 2 (binary-level CT verification).** The guarantee is no longer purely
statistical. `proofs/constant-time/verify.sh` (in the `constant-time` CI job)
disassembles the two scalar secret-handling functions and **fails the build
unless they are branch-free over their secret inputs**: `mulx` has no conditional
branch, and `constant_time_eq` has no conditional branch after its first
secret-byte load (its only branch is the public length check; the 16 byte
comparisons are `cmp`+`cset`/`setcc`+`and`, branchless). A non-vacuity control (a
deliberately leaky comparison) must be rejected. The check runs on **both shipped
architectures** (x86_64 and aarch64; `verify.sh` recognizes both instruction
sets' conditional-branch mnemonics), so the ARMv8 secret-handling code generation
is verified branch-free on real hardware, not only the x86 one. Combined with the
structural argument that these two functions are the *entire* scalar secret
surface (all other secret ops are the vendor-data-oblivious SIMD instructions or
XOR/copy), this is a checkable constant-time property of the *shipped machine
code* for that surface - not a t-test. It is not a whole-program CT prover
(`ct-verif`/`binsec` would taint all secrets and check every path); ARMv8.4
`PSTATE.DIT` is still not set; so the finding stays **Low / residual** - but the
scalar secret surface is now binary-verified on both architectures, not only
measured.

---

### HRC-2026-06 - Ephemeral stack and register copies of key material are not wiped

| | |
| --- | --- |
| **Risk** | Informational |
| **Category** | Cryptography - key hygiene |
| **Status** | Open (inherent) |
| **Location** | `src/aes_gcm/ghash.rs` (`mulx` `u128`); SIMD by-value locals across both crates |

**Description.** Long-lived key state is zeroized on drop, but register and
stack spills of key material created by the compiler (e.g., the `u128` in
`mulx`, by-value SIMD locals) cannot be reliably wiped from safe Rust and are
not. This is an inherent limitation, documented as accepted residual in
`docs/design.md`.

**Recommendation.** No action required; documented residual. A full mitigation
would require assembly-level control the project deliberately avoids
([section 9](#9-memory-safety-and-unsafe-review)).

---

### HRC-2026-07 - Drop-based zeroization is defeated by `mem::forget` and leaks

| | |
| --- | --- |
| **Risk** | Informational |
| **Category** | Cryptography - key hygiene |
| **Status** | Open (inherent) |
| **Location** | `Drop` implementations across both crates |

**Description.** Zeroization is implemented in `Drop`. A handle that is
`mem::forget`-ten, leaked (`Box::leak`), or whose owning allocation is never
dropped is never wiped. This is the standard caveat for all drop-based
zeroization in Rust and is not specific to this library.

**Recommendation.** No action required; ensure integration code does not
`forget`/leak key-state handles.

---

### HRC-2026-08 - No Wycheproof or formal coverage of GHASH aggregation edge cases

| | |
| --- | --- |
| **Risk** | Low |
| **Impact** | Medium (a latent aggregation bug would break authentication) |
| **Exploitability** | Low (dense differential corpus already exercises boundaries) |
| **CVSS 3.1** | N/A (test-coverage gap) |
| **Category** | Testing / assurance |
| **Status** | Resolved - Wycheproof integrated; aggregation identity machine-checked for all inputs; property-based + fuzz coverage added |
| **Location** | `tests/aes_gcm_interop.rs`; `tests/wycheproof_aes_gcm.rs`; `tests/wycheproof_aes_gcm_siv.rs`; `src/aes_gcm/ghash.rs` |

**Description.** The 8-block aggregated GHASH reduction (section 7.2) is novel
relative to the per-block upstream POLYVAL and is the most intricate arithmetic
in the codebase. Its correctness currently rests on differential testing
against RustCrypto/`ring` and NIST KATs - strong evidence, but not a
machine-checked proof, and the well-known Google Project Wycheproof AES-GCM
vectors (which target truncated tags, oversized inputs, and special-value
nonces) are not integrated.

**Evidence.** The dense differential sweep exercises the aggregation/batch
boundaries through multiple full batches and their tails:

```rust
// tests/aes_gcm_interop.rs (dense_length_sweep_matches_rustcrypto)
let lengths = (0..=288_usize).chain([
    511, 512, 513, 1023, 1024, 1025, 4095, 4096, 4097, 8191, 8192, 8193, 16383, 16384, 16385,
]);
```

**Impact.** A latent aggregation defect would manifest as authentication
failure or, worst case, a tag that validates incorrectly. The boundary-dense
differential corpus makes an undetected defect unlikely but does not formally
exclude one.

**Recommendation.** Integrate Project Wycheproof AES-GCM vectors and add
property-based (`proptest`) round-trip and decrypt-parser tests. Consider a
formal or computer-algebra check of the aggregation identity (section 7.2).

**Update (2026-06-18).** Project Wycheproof vectors are now integrated and pass:
`tests/wycheproof_aes_gcm.rs` runs the 66 AES-256 / 96-bit-IV / 128-bit-tag
cases (39 valid, 27 `ModifiedTag` rejection), and `tests/wycheproof_aes_gcm_siv.rs`
runs the 103 AES-256-GCM-SIV cases (69 valid, 34 rejection, 5 `WrappedIv`
counter-wrap). The vector files are vendored verbatim (Apache-2.0, dev-only;
`NOTICE`). The aggregated-reduction boundaries now have authoritative
third-party coverage in addition to the dense differential corpus. Residual:
property-based/fuzz testing of the decrypt parser and a formal/computer-algebra
proof of the aggregation identity are still open.

**Update (assurance work; see [assurance.md](assurance.md) §1-2 and
[proofs/](../proofs)).** The two remaining residuals are now closed, moving this
finding to **Resolved**:

- *Formal proof of the aggregation identity.* The GHASH/POLYVAL field core is
  machine-checked correct **for every input**, faithfully to the exact intrinsic
  sequence. `proofs/field_model.py` first pins a Python model to reality (it
  reproduces, byte-for-byte, reference outputs captured from the running backend
  `imp::mul` and equals the independent RFC 8452 POLYVAL `dot`), then:
  `prove_multiply.py` proves the field multiply equals POLYVAL `dot` exhaustively
  on all 128×128 standard-basis pairs (so, by bilinearity, on all inputs);
  `prove_aggregation.py` proves the exact `mont_reduce ∘ karatsuba2` (aarch64)
  and `reduce` (x86) reductions GF(2)-linear via Z3; `prove_ghash_identity.py`
  proves the per-block Horner recurrence equals the sum-of-powers form the batch
  path computes (sympy). Chained, the 8-/4-block aggregated path computes exactly
  the specified POLYVAL accumulator for every input. `prove_ghash_polyval_mapping.py`
  then proves the byte-reversal + `mulX` bridge that drives **GHASH** on the
  POLYVAL backend computes NIST SP 800-38D GHASH for every subkey and block count
  (single-block mapping exhaustive on all 128×128 basis pairs, lifted to all block
  counts via the Horner induction and `ByteReverse` being a linear involution) -
  so the proof now reaches GHASH itself, not only the POLYVAL field core.
  `src/aes_gcm/ghash.rs` (`aggregation_tests`) also checks the aggregated
  reduction equals per-block evaluation at runtime, and
  `mul_reference_anchor` re-derives the proof's anchor vectors from the real
  backend on each CI architecture (so the x86 model is anchored to actual
  AES-NI/PCLMULQDQ silicon, not only aarch64-captured output). The `formal-proof`
  CI job runs the suite on every build.
- *Property-based and fuzz testing of the decrypt parser.* `tests/proptest_aead.rs`
  adds round-trip, tamper-rejection, and decrypt-parser-never-panics properties
  for both modes (512 cases each); `fuzz/` adds differential and
  parser-robustness fuzz targets on the attacker-controlled decrypt surface.

---

### HRC-2026-09 - Independent review and CAVP validation not yet performed

| | |
| --- | --- |
| **Risk** | Informational |
| **Category** | Process / assurance |
| **Status** | Open |

**Description.** The library has not received an independent third-party
cryptographic review or CAVP/CMVP algorithm validation. This is expected for a
pre-integration primitive but is a precondition many deployments require before
a library protects production data.

**Recommendation.** Obtain independent third-party cryptographic review and,
where the regulatory posture requires it, CAVP/CMVP validation before
production deployment.

**Update (assurance work; see [assurance.md](assurance.md) §3).** This finding
remains **Open**: no independent third-party audit and no CAVP/CMVP accreditation
has been performed, and none is claimed. What has been added is *functional*
known-answer coverage from the official NIST CAVP GCM vectors -
`tests/nist_cavp_gcm.rs` runs the full `Keylen=256 / IVlen=96 / Taglen=128`
subset (375 encrypt + 375 decrypt, of which 191 are `FAIL`/tag-rejection
records), vendored verbatim and public-domain (`NOTICE`). This is functional KAT
coverage that exercises the same vectors an ACVP/CAVP run would, **not** CAVP
accreditation. `assurance.md` §3 records the CAVP/CMVP readiness posture (spec
mapping, vector coverage, fixed parameter set, reproducibility) for when an
accredited engagement is funded.

---

## 7. Cryptographic construction analysis

### 7.1 AES-256-GCM composition (NIST SP 800-38D)

The GCM composition is implemented in `src/aes_gcm/mod.rs` (`KeyState::seal`
and `KeyState::open`) over the AES and GHASH backends.

**Hash subkey.** `H = E(K, 0^128)` is computed at key setup by encrypting a
zero block, then converted into the POLYVAL domain (section 7.2). Conforms to
SP 800-38D section 6.3.

**Pre-counter block J0 (96-bit IV).** Per section 7.1, `J0 = IV || 0^31 || 1`. The code
writes the 12 nonce bytes followed by `0x00000001`:

```rust
// src/aes_gcm/mod.rs:858
fn j0(nonce: &[u8; NONCE_SIZE]) -> [u8; 16] {
    let mut out = [0_u8; 16];
    out[..NONCE_SIZE].copy_from_slice(nonce);
    out[15] = 1;
    out
}
```

**Counter.** GCTR increments the rightmost 32 bits modulo 2^32 (`inc32`,
section 6.5), big-endian; verified branch-free (section 8, HRC-2026-05).

**Tag.** `T = MSB_t(GHASH_H(A,C,lens)) XOR E(K, J0)`. The code XORs the encrypted
J0 mask into the GHASH output. Decrypt writes plaintext into caller-controlled
output while authenticating, then compares the tag in constant time. If the
comparison fails, the plaintext-length prefix of the caller buffer is zeroized
before `Error::Decrypt` is returned.

**Length limits.** `MAX_GCM_DATA_LEN = (2^32-2) * 16 = 2^36-32` bytes = exactly the
SP 800-38D section 5.2.1.1 plaintext bound of `2^39-256` bits. AAD and ciphertext are
each bounded to fit the 64-bit GCM length field:

```rust
// src/aes_gcm/mod.rs:43
const MAX_GCM_DATA_LEN: u64 = ((u32::MAX as u64) - 1) * AES_BLOCK_SIZE as u64;
const MAX_GHASH_INPUT_LEN: u64 = u64::MAX / 8;
```

**Conformance:** the construction conforms to SP 800-38D as reviewed, confirmed
by NIST KATs and byte-for-byte differential equality with two independent
implementations (Appendix B).

### 7.2 GHASH via POLYVAL and the 8-block aggregated reduction

The GHASH backend (`ghash.rs`) vendors the RustCrypto `ghash`->`polyval` mapping
and the CLMUL/PMULL multiply, and adds an aggregated reduction.

**GHASH/POLYVAL mapping.** GHASH operates in GF(2^128) with bit-reflected
ordering; POLYVAL uses the natural ordering. The standard relationship (RFC
8452 Appendix A) is realized by reversing the hash subkey, applying `mulx`
(multiply by `x`) to obtain the POLYVAL key, reversing each input block, and
reversing the final tag. `mulx` is the GF(2^128) doubling with the reduction
polynomial folded in branch-free (HRC-2026-05).

**Aggregated reduction (the novel part).** POLYVAL's per-block update is
`Y_i = (Y_{i-1} XOR X_i) * H`. By Horner's rule over an 8-block batch from
accumulator `Y`:

```
Y' = (Y XOR X1) * H^8  XOR  X2 * H^7  XOR  ...  XOR  X7 * H^2  XOR  X8 * H^1
```

Let `W(a,b)` denote the *unreduced* (wide) carryless product and `R` the
Montgomery reduction. Because `W` is bilinear over GF(2) addition (XOR) and `R`
is GF(2)-linear:

```
Y' = R( W(Y XOR X1, H^8) XOR W(X2, H^7) XOR ... XOR W(X8, H^1) )
```

i.e. the eight wide products are XOR-accumulated and reduced **once**. This is
the Gueron-Kounavis aggregated reduction (Intel CLMUL whitepaper) and is
exactly what `update_blocks8_inner` (and its in-register twin `ghash8_regs`,
used by the stitched encrypt path) implements, using precomputed key powers
`H^1..H^8` stored in the key state. Four- and one-block reductions remain for
the sub-batch tail. The implementation precomputes the powers at setup:

```rust
// src/aes_gcm/ghash.rs (init_in_place)
let mut h2 = unsafe { imp::mul(&h1, &h1) };  // H^2
let mut h3 = unsafe { imp::mul(&h2, &h1) };  // H^3
let mut h4 = unsafe { imp::mul(&h2, &h2) };  // H^4
let mut h5 = unsafe { imp::mul(&h4, &h1) };  // H^5
let mut h6 = unsafe { imp::mul(&h4, &h2) };  // H^6
let mut h7 = unsafe { imp::mul(&h4, &h3) };  // H^7
let mut h8 = unsafe { imp::mul(&h4, &h4) };  // H^8
```

**Assurance.** Correctness rests on the algebraic identity above plus
byte-exact differential equality against the per-block upstream POLYVAL across
the boundary-dense corpus. It is **not** machine-proven (HRC-2026-08).

### 7.3 AES-256 (FIPS 197) and CTR (SP 800-38A)

Both backends implement AES-256 with the FIPS-197 key expansion (`Rcon`
sequence, `RotWord`/`SubWord`) in vector registers; `SubWord` is computed via
the AES instruction itself (`AESE`/`AESENCLAST` with a zero round key),
eliminating any in-memory S-box. GCM uses only the encryption schedule (correct
for counter mode). The 8-way interleaved CTR encrypts eight independent counter
blocks per batch; independence is structural and equivalence to serial CTR is
confirmed by the differential corpus. The AES-256-CTR keystream was
cross-validated against OpenSSL AES-256-ECB (Appendix B).

**Stitched bulk AEAD.** On the bulk encrypt and decrypt paths, the keystream
batch and GHASH of the previous ciphertext batch are emitted as two independent
instruction sequences in one `#[target_feature]` body (`seal_bulk_inner` /
`open_bulk_inner`), software-pipelined so the AES and carryless-multiply
pipelines overlap. The instructions are the data-independent AESE/AESMC
(AESENC/AESENCLAST), PMULL/PMULL2 (PCLMULQDQ), XOR, and in-register byte-reverse
(REV / PSHUFB), the loop bound is the public batch count, and no memory index
depends on a secret, so the constant-time argument of section 6 holds. Decrypt
writes plaintext before the final tag comparison; public wrappers zeroize the
written range on authentication failure.

### 7.4 AES-CTR DRBG and the reseed blend (relationship to SP 800-90A Rev. 1)

`AesCtrKeyGenerator` is a CTR-mode CSPRNG over AES-256. Its reseed-with-input
path is **modeled on** `CTR_DRBG_Update` (SP 800-90A Rev. 1 section 10.2.1.2):

```
new_seed = AES-CTR_keystream(current secret state) XOR external_entropy
(rekey backend from new_seed)
```

```rust
// src/random/mod.rs:422
fn reseed_blend(&mut self, cpu_entropy: &[u8; AES_CTR_SEED_SIZE]) {
    let mut seed = Zeroizing::new([0_u8; AES_CTR_SEED_SIZE]);
    for block in seed.chunks_exact_mut(AES_BLOCK_SIZE) {
        let mut keystream = Zeroizing::new([0_u8; AES_BLOCK_SIZE]);
        self.backend.fill_block(&mut keystream);
        block.copy_from_slice(keystream.as_ref());
    }
    for (s, e) in seed.iter_mut().zip(cpu_entropy.iter()) { *s ^= e; }
    ...
}
```

**Security property.** The keystream block is secret (it depends on the current
key, rooted in the original OS seed). An adversary controlling `cpu_entropy`
(A3) cannot force the new seed without also knowing the secret keystream; an
honest CPU RNG makes the new seed fresh even if the prior state had leaked. The
reseed is therefore safe unless **both** the prior state is compromised **and**
the entropy input is adversarial - strictly stronger than trusting either
source alone. This is the correct construction; the residual is HRC-2026-02.

**Non-conformance note.** This *models*, but is not a validated implementation
of, CTR_DRBG. It uses no derivation function for the input, AES-256 with a
128-bit counter rather than the spec's exact seedlen handling, no SP 800-90A
reseed-counter / `reseed_required` state machine, and no DRBG known-answer
self-tests. SP 800-90A conformance would require reimplementation and
validation. For the current threat model (a performance CSPRNG rooted in an OS
seed) the construction is appropriate. Key/nonce draws are fixed-size with no
rejection sampling, consistent with SP 800-133 Rev. 2 (no modulo bias).

### 7.5 Entropy source and health testing (relationship to SP 800-90B)

Initial seeding is always from the OS (`getrandom`). CPU-RNG entropy
(`RDSEED` on `x86_64`; `RNDRRS`/FEAT_RNG on `aarch64`) enters only on reseed,
runtime-detected with bounded retries and OS fallback, and is screened by
`stuck_output` (section HRC-2026-03). The health screen is a pragmatic continuous
stuck-output detector, not the full SP 800-90B RCT+APT suite (HRC-2026-03).

### 7.6 Generated-nonce construction (`nonce.rs`)

The default `encrypt` and `encrypt_to` APIs produce a unique 96-bit nonce and
append it to the returned envelope as `ciphertext || tag || nonce`. The nonce
is constructed as `nonce = (salt + counter) mod 2^96`:

- `salt` is a 96-bit value drawn from the OS (`getrandom`) only -- never the
  CPU RNG or the AES-CTR generator -- at first use and re-drawn on fork (and on
  the unreachable 2^64 counter wrap).
- `counter` is a per-instance 64-bit value incremented once per nonce.

A fixed random base walked by a sequential counter yields distinct values
within an instance (no collision for up to 2^64 nonces); the random base
differentiates instances across process restart, fork, and hosts. Fork is
detected by the same `pthread_atfork` generation counter used in
`hardware-random` (`fork.rs`), so a forked child re-salts before its next
nonce and never repeats its parent's sequence. The generator state lives on the
handle, not in the placed key state, so the 368-byte key-state footprint is
unchanged (asserted by test).

The residual is the cross-instance base-range overlap: for M instances of n
nonces each it is of order `M^2 * n / 2^96`, below the point-collision rate of
independent random nonces. This construction *prevents* reuse but, being plain
AES-GCM, does not *survive* it; a call site that cannot guarantee unique nonces
at all still requires a misuse-resistant mode (HRC-2026-01).

---

## 8. Constant-time and side-channel analysis

All secret-dependent computation executes either in constant-time hardware
instructions (AES rounds; PMULL/PCLMULQDQ multiply) or in straight-line
XOR/copy loops whose trip counts derive solely from public lengths. The single
secret-derived comparison - tag verification - uses `subtle::ConstantTimeEq`.
Control flow branches only on public values: input/buffer lengths, CPU-feature
availability, and the public accept/reject result.

**Assembly inspection** (Appendix B) of `KeyState::init_in_place` (which
inlines `mulx` and the AES/GHASH setup) confirmed: 213 AES/PMULL instructions
emitted (no scalar AES substitute); the `mulx` carry fold compiles to a
branch-free `eor ... lsl #62/#63/#57` cascade; and the `black_box` barrier is
present (two `InlineAsm` markers). Conditional branches in the function are on
public values only (key length, chunk-length mask).

**Statistical testing** (`tests/timing_constant_time.rs`, dudect / Welch
t-test), captured this session:

- *tag comparison vs. mismatch position*: |t| = 0.178 (means 437.6 ns /
  437.6 ns) - no early-exit leak.
- *decrypt time vs. ciphertext content* (symmetric pools): |t| = 1.537.

Both are far below the |t| ~ 267 a genuine early-exit leak produced during
development, confirming the test's sensitivity. Residual assurance limitations
are recorded as HRC-2026-05; transient-execution attacks are out of scope (section 4.4).

---

## 9. Memory safety and `unsafe` review

`unsafe_code = "deny"` is set at the workspace lint level; each crate
re-permits it locally (`#![allow(unsafe_code)]`), scoping the exception to the
SIMD/intrinsic code. The surface, measured at the reviewed commit:

| File | `unsafe` blocks | `unsafe fn` | `SAFETY:` comments |
| --- | ---: | ---: | ---: |
| `src/aes_gcm/aes.rs` | 35 | 9 | 35 |
| `src/aes_gcm/ghash.rs` | 54 | 28 | 54 |
| `src/aes_gcm/mod.rs` | 11 | 1 | 13 |
| `src/aes_gcm/fork.rs` | 1 | 0 | 1 |
| `src/aes_gcm/nonce.rs` | 0 | 0 | 0 |
| `src/random/aes_ctr.rs` | 18 | 7 | 17 |
| `src/random/mod.rs` | 3 | 0 | 3 |
| `src/random/entropy.rs` | 3 | 2 | 3 |
| `src/random/fork.rs` | 1 | 0 | 1 |

Every `unsafe` block carries a `SAFETY:` justification. Soundness-critical
patterns reviewed:

- **In-place initialization** (`init_in_place` on raw `NonNull`): write-before-
  read ordering verified; the GHASH-init failure path drops the
  already-initialized AES state, so no partial-init-then-error path leaves key
  material in caller storage.
- **Caller-placed aliasing model**: `OpaqueKeyState` stores `NonNull<u8>` +
  `PhantomData<&'a mut [u8]>` rather than a live `&mut` slice, so reads through
  the handle never coexist with a live mutable reference. The single-wipe drop
  path is documented to require revision if `KeyState` ever gains a
  resource-owning field.
- **`Send`/`Sync`**: manually implemented for `OpaqueKeyState` behind a
  compile-time `KeyState: Send + Sync` assertion; access outside drop is
  read-only with no interior mutability. A concurrent-use test exercises shared
  decryption across threads.
- **Target-feature gating**: every intrinsic call is reachable only after a
  runtime feature check at construction.
- **Inline assembly** (`RNDRRS`): uses the encoded system register
  `S3_3_C2_C4_1` with `cset ne` to read the success flag; `options(nostack,
  nomem)`; emission confirmed by inspection.

No `unsafe`-related defect was identified. The original assurance residual was
the lack of tool-based UB checking; per HRC-2026-04 this is now substantially
addressed - CI runs full-lifecycle Miri over the AES-256-GCM/SIV paths on x86
plus Valgrind memcheck, ASan, and TSan over the real intrinsic binary.

---

## 10. Dependency and supply-chain assessment

| Crate | Version | Role | License |
| --- | --- | --- | --- |
| `subtle` | 2.6.1 | constant-time compare | BSD-3-Clause |
| `zeroize` | 1.8.2 | memory wiping | Apache-2.0 / MIT |
| `getrandom` | 0.3.4 | OS entropy | Apache-2.0 / MIT |
| `libc` | 0.2 | `pthread_atfork`, `fork` (Unix) | Apache-2.0 / MIT |
| `cfg-if` | 1.0 | transitive (getrandom) | Apache-2.0 / MIT |

- **`cargo audit`** against the committed `Cargo.lock` (114 dependencies incl.
  dev) reports **no known vulnerabilities**; a `cargo-audit` CI job runs on
  every push.
- **Production graph is minimal and cipher-free** (section 5.2); a CI dependency-tree
  guard fails the build if `rand*`, `chacha20`, `salsa20`, `ring`, `aes*`,
  `ghash`, `polyval`, or related crates enter either production crate's normal
  graph (verified 0 on production; trips on the dev graph - positive control).
- **Vendored provenance**: `NOTICE` records upstream project, version, commit
  SHA, copyright, license, and modifications for `rand_aes` 0.7.0 (Apache-2.0),
  `ghash` 0.5.1, and `polyval` 0.6.2 (Apache-2.0 OR MIT) - satisfying
  Apache-2.0 section 4.
- **GitHub Actions** are pinned to commit SHAs.

**Observation (Informational).** Vendored upstreams are *copied*, not tracked
as dependencies, so upstream security fixes to `ghash`/`polyval`/`rand_aes` do
not flow in automatically. Recommend a documented periodic upstream re-sync
review.

---

## 11. Test coverage assessment

Executed at the reviewed commit (`--workspace --all-targets`):

| Suite | Tests | Coverage |
| --- | ---: | --- |
| `hardware-aes-gcm` unit | 26 | length/limit validation, envelope layout, placement, wipe-on-drop, thread sharing, caller-buffer paths, default/generated-nonce round-trip/uniqueness |
| `hardware-random` unit | 14 | KAT, contiguity, reseed, fork (incl. real `fork()`), blend determinism, stuck-output, state size |
| `aes_gcm_interop` | 9 | default envelope RustCrypto/ring differential + cross-decrypt, NIST KAT decrypt, tamper sweep, dense length sweep |
| `random` integration | 1 | public-API smoke |
| `timing_constant_time` | 2 (ignored) | dudect harness, run manually |

**Strengths:** two independent differential oracles; boundary-dense sweeps
targeting the batch/aggregation seams; KATs cross-validated with OpenSSL; full
single-byte tamper coverage; lifecycle tests (fork, wipe, threading).

**Gaps:** Wycheproof vectors (HRC-2026-08); property/fuzz testing of the
decrypt parser (A1 surface); Miri (HRC-2026-04); `x86_64` hardware-path
execution depends on CI runners exposing AES-NI/RDSEED.

**Update (2026-06-18) - AES-256-GCM-SIV added and coverage expanded.** Since the
reviewed commit the crate gained an AES-256-GCM-SIV (RFC 8452) implementation
(`src/aes_gcm/siv.rs`) with its own test surface, and the GCM suite was expanded
to match. This code post-dates the review and has not been independently
audited; its current automated coverage is:

| Suite | Tests | Coverage |
| --- | ---: | --- |
| `aes_gcm::tests` (unit) | 25 | length/limit, layout, placement, wipe-on-drop, thread sharing, caller-buffer paths, generated-nonce round-trip/uniqueness |
| `aes_gcm::siv::tests` (unit, white-box) | 6 | mod-2^32 CTR counter wrap, CTR vs block-wise reference, key derivation determinism, POLYVAL determinism |
| `aes_gcm_interop` | 11 | RustCrypto/ring differential + cross-decrypt, NIST KAT, multi-size single-bit tamper, wrong key/nonce/AAD, dense plaintext + AAD sweeps |
| `aes_gcm_siv_interop` | 18 | RFC 8452 KAT (dual-asserted), RustCrypto differential + cross-decrypt, determinism, multi-size tamper, wrong key/nonce/AAD, error paths, decrypt-zeroize, dense plaintext + AAD sweeps |
| `wycheproof_aes_gcm` | 66 vectors | Project Wycheproof AES-256-GCM (39 valid, 27 rejection) |
| `wycheproof_aes_gcm_siv` | 103 vectors | Project Wycheproof AES-256-GCM-SIV (69 valid, 34 rejection, 5 counter-wrap) |
| `nist_cavp_gcm` | 750 vectors | NIST CAVP AES-256-GCM 96/128 subset (375 encrypt + 375 decrypt, 191 FAIL/tag-rejection); byte-exact encrypt KATs under `hazmat-explicit-nonce` |
| `ghash::aggregation_tests` (unit) | 3 | 8-/4-block aggregated reduction == per-block Horner; mixed batch/tail |
| `aes::tests` (unit) | 2 | software key schedule (cfg(miri) path) == hardware round keys; and `AES_SBOX` == FIPS-197 `affine(inverse_GF(2^8)(x))` for all 256 inputs (and a bijection) |
| `proptest_aead` | 512×7 cases | round-trip + `*_to` consistency, single-byte tamper rejection, decrypt-parser-never-panics, construction-never-panics (both modes) |
| `rng_statistical` | 3 | monobit / chi-square / serial-correlation sanity (PractRand/dieharder procedure in `docs/randomness-testing.md`) |
| `fuzz/` | 4 targets | differential vs RustCrypto + decrypt-parser robustness (no panic / no UB), both modes |
| `proofs/` | 7 proofs | machine-checked for **all inputs**: basis-exhaustive multiply == POLYVAL; Z3 reduction linearity; sympy Horner identity; ByteReverse+mulX+POLYVAL == NIST SP 800-38D GHASH; (Z3, AES/authenticator as oracles) the composition glue == SP 800-38D / RFC 8452 (J0/counter/SIV-derivation/tag, decrypt-inverts-encrypt); and the GHASH input framing (padding, length block, length limits) == spec |
| `cfg(kani)` harnesses | 10 | **Kani/CBMC over the compiled Rust** (`kani` CI job): GCM/SIV counter increments == spec increments, J0 layout, length validation, nonce parser, both envelope splitters never panic / split correctly, `constant_time_eq` == bytewise equality on equal-length tags, and the generated-nonce arithmetic is injective in the counter, over all inputs (bounded where noted) |
| `timing_constant_time` / `_siv` | 2 + 2 | dudect harnesses for the GCM and SIV decrypt paths, **CI-gated** (`constant-time` job, best-of-3, `\|t\| < 25`) |
| `proofs/saw/` (`saw` CI job) | 2 | **SAW** (Galois) verifies rustc's **LLVM bitcode** of `increment_counter`/`j0` against a Cryptol spec - a third independent prover (own solvers) |
| `proofs/crux-mir/` (`crux-mir` CI job) | 1 | **crux-mir** (Galois) proves `increment_counter` == `inc_32` over the **Rust MIR** - a fourth independent toolchain; a probe documents the unmodeled-intrinsic boundary |
| `proofs/fstar/` (`fstar` CI job) | 1 | **F\*** (via hax) proves `j0`/`increment_counter` over source **extracted from the real Rust**, with a drift guard; runs in a prebuilt GHCR image and gates every PR + release |
| `proofs/constant-time/verify.sh` (`constant-time` job) | 2 + control | **binary branch-freedom**: the two scalar secret ops disassembled and required branch-free over secret inputs, x86_64 **and** aarch64, with a non-vacuity control |

The counter-increment property is now confirmed by **five independent engines**
(Z3, Kani/CBMC, SAW-LLVM, crux-mir, F\*); SAW and crux-mir also established the
negative result that no source/bitcode tool reaches *through* the SIMD
carryless-multiply, which is why the field arithmetic is proven against a model
anchored to real captured output. This resolves the Wycheproof and formal-proof
gap (HRC-2026-08) for both modes and the parser property/fuzz gap. The
Miri/sanitizer gap (HRC-2026-04) is substantially addressed: CI runs
full-lifecycle Miri over the AES-256-GCM/SIV paths on x86 plus Valgrind/ASan/TSan
**on both x86_64 and Linux arm64**, and the proof jobs run the full suite. The
whole battery (~23 checks across five platforms) is **required to merge**. The
remaining gap is independent third-party review / CAVP accreditation
(HRC-2026-09).

---

## 12. Standards conformance matrix

"Conforms (reviewed)" = follows the cited specification as verified by source
review and differential/known-answer testing; **not** accredited validation.

| Standard | Subject | Status |
| --- | --- | --- |
| FIPS 197 | AES-256 cipher and key schedule | Conforms (reviewed; KAT) |
| NIST SP 800-38A | CTR mode | Conforms (reviewed) |
| NIST SP 800-38D | AES-GCM, GHASH, J0, length limits | Conforms (reviewed; NIST CAVP KAT + differential + Wycheproof; GHASH construction machine-checked == SP 800-38D for all inputs) |
| RFC 8452 | AES-256-GCM-SIV, POLYVAL, key derivation | Conforms (KAT + differential + Wycheproof; **added post-audit**, not independently reviewed) |
| RFC 5116 / 5288 | AEAD interface; 96-bit GCM nonce | Conforms (reviewed) |
| NIST SP 800-90A Rev. 1 | CTR_DRBG | Modeled on; **not** conformant/validated (section 7.4) |
| NIST SP 800-90B | Entropy source health tests | Partial; stuck-output only (section 7.5, HRC-2026-03) |
| NIST SP 800-133 Rev. 2 | Key generation from an RBG | Consistent (direct draws, no bias) |
| FIPS 140-3 | Cryptographic module validation | **Not validated** (no CAVP/CMVP) |
| Apache-2.0 section 4 | Vendored-code attribution | Satisfied (`NOTICE`) |

---

## 13. Residual risk register

| Risk | Likelihood | Impact | Mitigation in place | Residual |
| --- | --- | --- | --- | --- |
| Nonce reuse by integrator | Medium | Critical | Random nonce generator; (recommended) docs | HRC-2026-01 - not enforced |
| Per-key invocation limit exceeded | Low-Med | High | None enforced | HRC-2026-01 - caller duty |
| Malicious CPU RNG + prior state leak | Low | High | OS-rooted seed + blend + stuck screen | HRC-2026-02 |
| Compiler reintroduces secret branch | Low | High | `subtle` + `black_box`; **binary branch-freedom CI-gated on x86_64 + aarch64** (+ dudect) | HRC-2026-05 |
| Latent `unsafe` UB | Low | Critical | Review + differential corpus + full-lifecycle Miri (x86) + Valgrind/ASan/TSan in CI **on x86_64 + Linux arm64** | HRC-2026-04 - substantially addressed |
| Transient-execution leak of cache tier | Low-Med | High | Out of scope by design | Documented limitation |
| Upstream vuln in vendored code | Low | Medium | Copied + attributed | Manual re-sync needed |
| Aggregated-GHASH/POLYVAL latent defect | Low | High | Boundary-dense differential corpus + Project Wycheproof vectors (GCM and SIV) + machine-checked field-core proofs (all inputs); composition increment/J0 proven by five engines (Z3, Kani, SAW, crux-mir, F\*) | HRC-2026-08 - resolved |

---

## 14. Recommendations

**Before production integration**

1. **(HRC-2026-01)** Document the GCM nonce-uniqueness contract and SP 800-38D
   section 8.3 invocation limit at the API surface; define the integration's nonce strategy
   (prefer counter-based, or rotate keys before 2^32 invocations).
2. **(HRC-2026-02)** Add a periodic forced OS reseed alongside CPU-RNG
   reseeding.
3. **(HRC-2026-08)** Integrate Project Wycheproof AES-GCM vectors; add
   property/fuzz tests for the decrypt parser.

**Strongly recommended**

4. **(HRC-2026-04)** Add a Miri job over the architecture-independent logic.
5. **(HRC-2026-03)** Label the RNG health screen precisely as non-SP-800-90B,
   or extend it (APT + startup tests) if conformance is targeted.
6. **(HRC-2026-09)** Obtain independent third-party cryptographic review and,
   where required, CAVP/CMVP validation before protecting production data.

**Optional / longer-term**

7. Offer an AES-GCM-SIV (RFC 8452) misuse-resistant mode.
8. Validate benchmark and timing results on target Graviton / x86 Linux
   hardware.
9. Set `PSTATE.DIT` on `aarch64` around crypto sections once stable intrinsics
   exist (HRC-2026-05).

---

## 15. Conclusion

Within the threat model of section 4 and the scope of section 2, `hardware-rust-crypto` is a
carefully engineered, well-tested, hardware-only AES-256-GCM and
key-generation library. Its core cryptographic constructions conform to the
relevant NIST and IETF specifications as reviewed, and were verified
byte-for-byte against two independent implementations and NIST known-answer
vectors. The `unsafe` surface is large but disciplined and documented, and no
critical or high-severity vulnerability was identified.

The principal open finding (HRC-2026-01) is the standard, by-design GCM
nonce-management responsibility that every GCM deployment must address at the
integration layer. The remaining findings are low-severity residuals or
informational items appropriate to an un-certified library.

Recommended next steps before this library protects production data: resolve
HRC-2026-01 at the integration layer, address the prioritized recommendations
in section 14, and obtain independent third-party cryptographic review and (where
required) CAVP/CMVP validation.

---

## Appendix A - Rating methodology

Each finding receives an overall **Risk** derived from **Impact** (the
consequence if the issue is realized) and **Exploitability** (the difficulty
and preconditions of realizing it), in the style of NCC Group / Trail of Bits
deliverables. A CVSS 3.1 vector and base score are provided where the finding
maps cleanly to the CVSS model; for design responsibilities and
process/assurance findings, CVSS is marked N/A and the Impact/Exploitability
pair governs.

| Risk | Meaning |
| --- | --- |
| **Critical** | Directly breaks confidentiality or integrity with low attacker effort in the default configuration. |
| **High** | Breaks a security goal under realistic conditions or a common precondition. |
| **Medium** | Conditional or integrator-dependent weakness, or a by-design responsibility whose mishandling is severe. |
| **Low** | Limited impact, strong mitigations present, or unlikely preconditions; tracked residual. |
| **Informational** | No direct security impact; hygiene, documentation, defense-in-depth, or inherent limitation. |

CVSS scores are reference figures; where a score (e.g. HRC-2026-01 = 7.4)
diverges from the assessed Risk, the text explains the divergence - typically
CVSS over-weighting conditional impact for secure-by-default API gaps.

---

## Appendix B - Verification performed and captured evidence

All commands run at the initial revision, rustc 1.96.0, on a MacBook Pro
(Apple M4 Max) `aarch64` host; the
`x86_64` paths were additionally checked under the `stable-x86_64-apple-darwin`
toolchain. Both CI architectures were green at this commit. Reviewers should
re-run independently.

### B.1 Commands

```sh
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --all-targets
cargo test --release --test aes_gcm_interop
cargo test --release -p hardware-rust-crypto
cargo test --release --test timing_constant_time -- --ignored --nocapture
cargo doc --workspace --no-deps
cargo audit
cargo run --example assert_hardware
cargo run --release --example state_size
cargo asm -p hardware-rust-crypto --lib 'hardware_rust_crypto::aes_gcm::KeyState::init_in_place'
cargo tree -p hardware-rust-crypto -e normal --prefix none

# Machine-checked proofs (each is also its own CI workflow):
./proofs/run_all.sh                          # Z3/sympy: field core + composition, all inputs
cargo kani                                   # Kani/CBMC over the compiled Rust
./proofs/constant-time/verify.sh             # binary branch-freedom (x86_64 + aarch64)
./proofs/saw/run.sh                          # SAW over the LLVM bitcode
./proofs/crux-mir/run.sh                     # crux-mir over the Rust MIR
./proofs/fstar/check.sh                      # F* over hax-extracted source (drift-guarded)
cargo +nightly miri test --lib aes_gcm       # Miri UB checker (x86)
```

### B.2 Test results (captured)

```
hardware-aes-gcm unit ......... 20 passed
hardware-random  unit ......... 14 passed
aes_gcm_interop ............... 7 passed
random (integration) .......... 1 passed
timing_constant_time .......... 2 ignored (run manually)
cargo audit ................... 0 vulnerabilities (114 crates scanned)
```

### B.3 NIST SP 800-38D known-answer vectors (AES-256, K = 0^32, IV = 0^12)

```
P = ""        ->  C||T = 530f8afbc74536b9a963b4f1c4cb738b
P = 0^16       ->  C||T = cea7403d4d606b6e074ec5d3baf39d18 d0d1c8a799996bf0265b98b5d48ab919
```

Both reproduced by `nist_known_answer_vectors`. The AES-256-CTR keystream was
additionally cross-checked against `openssl enc -aes-256-ecb`.

### B.4 Constant-time evidence (captured this session)

dudect (Welch t-test, 300k measurements/class after warm-up):

```
tag-mismatch-position:        |t| = 0.178   (mean0 437.6ns, mean1 437.6ns)
low-vs-high-entropy-content:  |t| = 1.537   (mean0 168.9ns, mean1 168.7ns)
```

Assembly inspection of `KeyState::init_in_place`:

```
AES/PMULL instructions emitted: 213
mulx carry fold (branch-free):  eor x8, x8, x11, lsl #62
                                eor x8, x8, x11, lsl #63
                                eor x8, x8, x11, lsl #57
black_box barrier markers:      2 (InlineAsm)
```

### B.5 Production dependency graphs (captured)

```
hardware-aes-gcm -> subtle 2.6.1, zeroize 1.8.2, getrandom 0.3.4, libc 0.2 (Unix)
hardware-random  -> getrandom 0.3.4, zeroize 1.8.2, libc 0.2 (Unix)
forbidden-crypto guard: 0 hits on both production graphs
```

---

## Appendix C - References

**Standards and specifications**

- FIPS 197, *Advanced Encryption Standard (AES)*, NIST, 2001 (updated 2023).
- NIST SP 800-38A, *Block Cipher Modes of Operation: Methods and Techniques*,
  2001.
- NIST SP 800-38D, *Galois/Counter Mode (GCM) and GMAC*, 2007.
- NIST SP 800-90A Rev. 1, *Random Number Generation Using Deterministic Random
  Bit Generators*, 2015.
- NIST SP 800-90B, *Entropy Sources Used for Random Bit Generation*, 2018.
- NIST SP 800-133 Rev. 2, *Recommendation for Cryptographic Key Generation*,
  2020.
- FIPS 140-3, *Security Requirements for Cryptographic Modules*, 2019.
- RFC 5116, *An Interface and Algorithms for Authenticated Encryption*, 2008.
- RFC 5288, *AES-GCM Cipher Suites for TLS*, 2008.
- RFC 8452, *AES-GCM-SIV: Nonce Misuse-Resistant Authenticated Encryption*,
  2019.

**Technical references**

- S. Gueron, M. Kounavis, *Intel Carry-Less Multiplication Instruction and its
  Usage for Computing the GCM Mode*, Intel white paper (aggregated reduction).
- O. Reparaz, J. Balasch, I. Verbauwhede, *dudect: Dude, is my code constant
  time?* (timing-leak detection methodology).
- Google Project Wycheproof - cryptographic test vectors.

**Prior art and format guidance**

- NCC Group, *Public Report - RustCrypto AES/GCM and ChaCha20+Poly1305
  Implementation Review*, 2020 (MobileCoin-funded) - direct lineage of the
  GHASH/POLYVAL code reviewed here.
- Public cryptographic review deliverables from NCC Group, Trail of Bits, and
  Cure53, and OSTIF-coordinated open-source cryptography audits, for report
  structure and rigor.

---

## Appendix D - Glossary

- **AEAD** - Authenticated Encryption with Associated Data.
- **AES-NI / PCLMULQDQ** - x86 instructions for AES rounds / carryless multiply.
- **ARMv8 AES / PMULL** - aarch64 equivalents.
- **CAVP / CMVP** - NIST Cryptographic Algorithm / Module Validation Programs.
- **CTR_DRBG** - Counter-mode Deterministic Random Bit Generator (SP 800-90A).
- **DIT** - ARM `PSTATE.DIT` (Data-Independent Timing) bit.
- **dudect** - statistical (Welch t-test) constant-time leakage test method.
- **FEAT_RNG / RNDR / RNDRRS** - ARMv8.5 hardware RNG feature and instructions.
- **GHASH / POLYVAL** - the GF(2^128) universal hashes underlying GCM / GCM-SIV.
- **J0** - the GCM pre-counter block.
- **min-entropy** - worst-case entropy measure used by SP 800-90B.
- **RCT / APT** - Repetition Count / Adaptive Proportion health tests.

---

*End of assessment.*