Files
mlkem-sync/README.md
FallenSigh 8774e03a0e build(vivado): rewrite create_project.tcl for current KeyGen flow
The old script referenced 5 non-existent files (keccak_arbiter,
sha3_chain_top_shared, tb_mlkem_top_xsim, tb_kg/en/de) and stale
vectors, so read_verilog/elaborate failed outright.

Rewrite to mirror the verified XSIM flow (sync_rtl/top/TB/xsim_run.tcl):
  - load exactly the 14 sources mlkem_top depends on
  - sim top = tb_mlkem_kg_katK_xsim, runtime K via generic KP, case via
    -testplusarg CASE
  - copy KAT vectors into the xsim working dir via xsim.compile.tcl.pre
    (the only hook in 2019.2 that runs before $readmemh; an appended
    -tclbatch runs after Vivado's own 'run all', too late)
  - drop duplicate --relax (XSim adds it; passing again is an error)

Verified through the actual Vivado batch project flow:
  K=2 CASE 0 -> PASS (21403 cyc), K=4 CASE 2 -> PASS (54059 cyc),
  0 file-not-found warnings. gitignore the generated vivado_prj/.

Also rewrite README.md in Chinese documenting the mlkem_top workflow
and test flow.
2026-06-28 03:43:56 +08:00

246 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ML-KEM 硬件实现FIPS 203
基于 SystemVerilog 的 **ML-KEM**Module-Lattice-based Key Encapsulation MechanismNIST 后量子密码标准,源自 Kyber同步流水线硬件实现。面向 FPGA使用 Vivado XSIM 与 Verilator 进行仿真验证。
本文档重点介绍顶层密钥生成模块 **`mlkem_top`** 的工作流程及其测试流程。各底层算子SHA-3、NTT、CBD 采样等)已独立验证,本文不再展开。
## 概述
ML-KEM 是 NIST 在 FIPS 203 中标准化的后量子密钥封装机制,其安全性基于多项式环 Z_q[x]/(x²⁵⁶+1) 上的 Module-LWE 难题。
`mlkem_top` 实现 **FIPS 203 算法 16KeyGen_internal** 的完整密钥生成数据通路:给定种子 `d``z`,输出封装密钥 `ek`(公钥)和解封装密钥 `dk`(私钥)。模块运行于 **100 MHz**10 ns 周期),低电平复位。
### 运行时参数选择
**ML-KEM 的安全等级 K 在运行时通过输入端口 `k_i` 选择**而非编译期参数。存储按最坏情况ML-KEM-1024KMAX=4静态分配`k_i``start_i` 时被采样到内部寄存器 `k_r`,据此选取激活的子区间。
| k_i | 方案 | 模块秩 K | η₁ | ek 字节数 | dk 字节数 | KeyGen 周期数 |
|:---:|:---|:---:|:---:|:---:|:---:|:---:|
| 2 | ML-KEM-512 | 2 | 3 | 800 | 1632 | 21 403 |
| 3 | ML-KEM-768 | 3 | 2 | 1184 | 2400 | 36 207 |
| 4 | ML-KEM-1024 | 4 | 2 | 1568 | 3168 | 54 005 |
> 注:`k_i` 仅在 `start_i` 时采样,且假定取值 ∈ {2,3,4}越界值0/1/57当前不做保护会产生错误的尺寸计算。
固定参数:**q = 3329**(素数模)、**n = 256**多项式次数。du/dv 仅用于封装/解封装KeyGen 不涉及。
## `mlkem_top` 接口
```
module mlkem_top #(parameter KMAX = 4) (
input clk, rst_n,
input [2:0] k_i, // 运行时方案选择2/3/4
input [255:0] d_i, // KeyGen 种子 dbyte0 在 d_i[7:0]
input [255:0] z_i, // 隐式拒绝种子 z
input start_i, // 启动脉冲
output busy_o, // 运行中拉高
output done_o, // ek/dk 就绪时拉高
// 调试回读端口(供 TB 逐字节核对,无需宽总线)
input [3:0] dbg_slot_i, input [7:0] dbg_idx_i, output [11:0] dbg_coeff_o,
input dbg_byte_sel_i, input [10:0] dbg_byte_idx_i, output [7:0] dbg_byte_o,
input [11:0] dbg_dk_idx_i, output [7:0] dbg_dk_o,
output [255:0] dbg_rho_o, dbg_sigma_o
);
```
`busy_o`/`done_o` 提供握手;`dbg_*` 端口为只读调试抽头,让 TB 可以逐系数 / 逐字节读出中间结果与最终的 ek/dk而无需引出整条数据总线。
## 工作流程
`mlkem_top` 复用了已独立验证的叶子模块(每个模块自带 keccak_core无共享仲裁器`sha3_top``sample_ntt_sync``sample_cbd_sync``ntt_core``poly_mul_sync`。顶层是一个 8 状态 FSM串行驱动这些算子并把所有中间多项式存放在统一的系数寄存器阵列 `polymem` 中。
### KeyGen 算法FIPS 203 算法 16
```
(ρ, σ) = G(d ‖ K) // SHA3-512
Â[i][j] = SampleNTT(ρ ‖ j ‖ i) i,j ∈ 0..K-1 // SHAKE-128 拒绝采样
s[i] = CBD_η1(PRF(σ, i)) i ∈ 0..K-1 // SHAKE-256
e[i] = CBD_η1(PRF(σ, K+i)) i ∈ 0..K-1
ŝ[i] = NTT(s[i]), ê[i] = NTT(e[i])
t̂[i] = ê[i] + Σⱼ Â[i][j] ∘ ŝ[j] // NTT 域逐点乘 + 累加
ek = byteEncode₁₂(t̂[0..K-1]) ‖ ρ
dk = byteEncode₁₂(ŝ[0..K-1]) ‖ ek ‖ H(ek) ‖ z
```
### FSM 状态机
```
start_i
ST_IDLE ─────────────────► ST_G
▲ │ G(d‖K),捕获 ρ/σ
│ ▼
│ ST_A 生成 Â[i][j]K² 个多项式SampleNTT
│ │
│ ▼
│ ST_C 采样 s[i], e[i]2K 个多项式CBD
│ │
│ ▼
│ ST_N 原地前向 NTTŝ[i], ê[i]2K 次)
│ │
│ ▼
│ ST_M t̂[i] = ê[i] + Σⱼ Â[i][j]∘ŝ[j]
│ │
│ ▼
│ ST_E byteEncode₁₂ → ek_mem / dkp_memek 尾接 ρ
│ │
│ ▼
│ ST_H H(ek):多块 SHA3-256
│ done_o │
└────────── ST_DONE ◄───────┘
```
| 状态 | 名称 | 动作 | 使用的算子 |
|:---:|:---|:---|:---|
| ST_G | 哈希 G | `(ρ,σ)=G(d‖K)``data_i={K_byte, d}` | `sha3_top`SHA3-512 |
| ST_A | 矩阵采样 | 逐个生成 Â[i][j],行主序写入 slot `i*K+j`,每个 256 系数 | `sample_ntt_sync` |
| ST_C | CBD 采样 | s[0..K-1]nonce 0..K-1、e[0..K-1]nonce K..2K-1有符号→模 q | `sample_cbd_sync` |
| ST_N | 前向 NTT | 对 ŝ、ê 共 2K 个多项式逐个原地变换 | `ntt_core`mode=0 |
| ST_M | 矩阵乘累加 | 对每个 (i,j):流入 256 对 (Â,ŝ) 做逐点乘,累加进 t̂[i]j=0 时以 ê[i] 初始化) | `poly_mul_sync` |
| ST_E | 字节编码 | byteEncode₁₂t̂→ek_memŝ→dkp_mem末尾拷入 ρ 的 32 字节 | — |
| ST_H | 哈希 H | 对 ek 做多块 SHA3-256得到 H(ek)调用方预填充末块0x06…0x80 | `sha3_top`(多块模式) |
各状态之间以 valid/ready 握手串接FSM 拉高对应算子的 `valid_i`,在 `ready_o` 时认为请求被接收,再逐拍收集 `valid_o` 输出,直到 `last_o`/`done_o`
### 存储布局
所有多项式存于 `polymem``NUM_SLOTS×256` 个 12-bit 系数,`NUM_SLOTS = KMAX²+3·KMAX = 28`)。每个 slot 256 系数,槽基址在运行时由 `k_r` 推导:
```
slot 0 .. K²-1 : Â[i][j] (下标 i*K + j
slot_s_rt = K² : ŝ[i] ST_N 原地覆盖 s[i]
slot_e_rt = K² + K : ê[i]
slot_t_rt = K² + 2K : t̂[i]
```
字节输出存于 `ek_mem`KMAX 最大 1568B`dkp_mem`(最大 1536B。byteEncode₁₂ 规则:每 2 个系数打包成 3 字节LSB 优先 12-bit
```
b0 = c0[7:0]
b1 = {c1[3:0], c0[11:8]}
b2 = c1[11:4]
```
完整私钥 dk 的字节布局(与 NIST KAT 的 sk 对齐):
```
dk = dk_pke(384·K) ‖ ek(384·K+32) ‖ H(ek)(32) ‖ z(32)
```
H(ek) 阶段采用预填充多块吸收:调用方逐块组装 136 字节速率块,在 `ek_bytes` 位置填 `0x06`、末块最后字节或上 `0x80`;分块数 `h_nblk_rt` 为 6/9/12对应 K=2/3/4
## 测试流程
`mlkem_top` 的验证策略是:**对全部三种安全等级,把硬件产出的 `ek`/`dk` 与 NIST KAT 标准答案逐字节比对**。验证素材、参数化 TB、运行脚本三者配合完成。
### 1. 黄金向量NIST KAT
测试向量来自 NIST FIPS 203 的 KAT 响应文件,经 `sync_rtl/top/TB/gen_vectors.py` 解析后生成每个用例的独立 hex 文件,存于 `sync_rtl/top/TB/vectors/`
| 文件 | 内容 | 字节长度(按 K |
|:---|:---|:---|
| `kat_k<K>_c<n>_d.hex` | KeyGen 种子 d | 32 |
| `kat_k<K>_c<n>_z.hex` | 隐式拒绝种子 z | 32 |
| `kat_k<K>_c<n>_ek.hex` | 期望公钥 pk== ek | 384·K+32 |
| `kat_k<K>_c<n>_dk.hex` | 期望私钥 sk== dk | 768·K+96 |
其中 `K ∈ {2,3,4}``n` 为用例号。当前覆盖:**K=2 共 5 个用例c0c4K=3 / K=4 各 3 个用例c0c2**,合计 11 个用例。
向量采用 “byte0 在低位” 约定256-bit 值满足 `bit[8m+:8] = byte m`
### 2. 参数化测试平台
`sync_rtl/top/TB/tb_mlkem_kg_katK_xsim.v` 是参数化自检 TB
- 通过 `parameter KP`(由 `xelab -generic_top KP=2|3|4` 设定)选择安全等级;
- 通过 `+CASE=n` plusarg 选择用例号,据此加载对应的 `kat_k<KP>_c<n>_*.hex`
- **将 `KP` 驱动到 DUT 的运行时输入 `k_i`**不再用参数覆盖KMAX 取默认 4
- 复位 → 加载 d/z 与 k_i → 拉 `start_i` 一拍 → 轮询 `done_o`(超时上限 200 万周期);
- 完成后通过 `dbg_byte_o`(读 ek0..EKB-1`dbg_dk_o`(读完整 dk0..DKB-1逐字节回读与黄金向量比对
- 全部相符打印 `K=<K> CASE <n> PASS`,否则打印前 8 个不匹配字节并报 `FAIL`
### 3. 运行测试
测试经由统一脚本 `run_tb.sh` 分发(自动 source Vivado 环境并设置 `LD_PRELOAD`
```bash
./run_tb.sh top
```
该命令执行 `sync_rtl/top/TB/xsim_run.tcl`,其流程为:
1. **编译**`xvlog`):全部叶子算子 RTL + `mlkem_top.v` + 参数化 TB
2. **细化**`xelab`):为每种 K 生成一个快照 —— `KP=2→mlkem_kg_k2``KP=3→mlkem_kg_k3``KP=4→mlkem_kg_k4`
3. **仿真**`xsim -R -testplusarg CASE=n`):依次跑完每种 K 的全部用例。
整体测试矩阵:
```
K=2 (ML-KEM-512) : CASE 0,1,2,3,4 → ek 800B, dk 1632B
K=3 (ML-KEM-768) : CASE 0,1,2 → ek 1184B, dk 2400B
K=4 (ML-KEM-1024): CASE 0,1,2 → ek 1568B, dk 3168B
```
### 4. 预期结果
11 个用例全部 PASS每个用例确认 `ek == pk``dk == sk` 逐字节相等:
```
=== ML-KEM K=2 KAT case 0: KeyGen done in 21403 cyc ===
K=2 CASE 0 PASS: ek (800B)==pk, dk (1632B)==sk
...
K=4 CASE 2 PASS: ek (1568B)==pk, dk (3168B)==sk
```
### 验证注意事项
- **干净重跑**:每轮仿真前清理 `xsim.dir``.Xil`,避免旧快照污染(`rm -rf xsim.dir .Xil`)。
- **`$readmemh` 缺文件只是 WARNING**:文件名拼错时数据读为 X不会报错极易造成假 PASS。务必确认日志中无 `cannot be opened` 警告。
- **以日志文件为准**:将每个 `xsim` 调用重定向到独立日志后再 grep `PASS|FAIL|cannot be opened`,不要只看终端滚屏的模糊输出。
## 手动 XSIM 命令
```bash
source /opt/Xilinx/Vivado/2019.2/settings64.sh
export LD_PRELOAD=/usr/lib64/libtinfo.so.5 # Vivado 2019.2 的 ncurses 兼容修复
rm -rf xsim.dir .Xil
# 1) 编译(叶子算子 + 顶层 + TB
xvlog -sv --relax -i . \
sync_rtl/sha3/keccak_round.v sync_rtl/sha3/keccak_core.v sync_rtl/sha3/sha3_top.v \
sync_rtl/sample_ntt/sample_ntt_sync.v sync_rtl/sample_cbd/sample_cbd_sync.v \
sync_rtl/ntt/barrett_mul.v sync_rtl/ntt/zeta_rom.v sync_rtl/ntt/butterfly_unit.v sync_rtl/ntt/ntt_core.v \
sync_rtl/poly_mul/basecase_mul.v sync_rtl/poly_mul/poly_mul_zeta_rom.v sync_rtl/poly_mul/poly_mul_sync.v \
sync_rtl/top/mlkem_top.v \
sync_rtl/top/TB/tb_mlkem_kg_katK_xsim.v
# 2) 细化某一种 K
xelab tb_mlkem_kg_katK_xsim -generic_top KP=4 -s mlkem_kg_k4 --timescale 1ns/1ps
# 3) 跑某个用例
xsim mlkem_kg_k4 -R -testplusarg CASE=0
```
## 先决条件
- **Vivado 2019.2+**XSIM 仿真):`/opt/Xilinx/Vivado/2019.2/`
- **Verilator 5.046**(底层算子 C++ 验证)
- **Python 3.10+**(向量生成,仅用标准库)
## Vivado 2019.2 兼容性说明
在 Fedora 44 上经实测的必要 workaround
```bash
export LD_PRELOAD=/usr/lib64/libtinfo.so.5 # 必需ncurses 兼容库
xvlog -sv --relax -i . <file>.v # 用 -i非 -include_dirs指定包含目录--relax 放宽严格 SV 检查
xelab <top> -s <snap> --timescale 1ns/1ps # xelab 需显式 --timescale
```
## 参考
- [FIPS 203: ML-KEM](https://csrc.nist.gov/pubs/fips/203/final) —— NIST 标准(算法 16 KeyGen_internal
- [FIPS 202: SHA-3 / SHAKE](https://csrc.nist.gov/pubs/fips/202/final) —— Keccak 哈希族
- [CRYSTALS-Kyber](https://pq-crystals.org/kyber/) —— 原始提案