FallenSigh ecc00d6dd5 feat(dec): Decaps D1 - byteDecode_d + Decompress -> u'/v'
K-PKE.Decrypt step 1 (FIPS 203 Alg 15): decode+decompress the ciphertext.

- comp_decomp_sync instance made mode-selectable: Encaps C1/C2 still compress
  (mode 0), Decaps ST_DEC_DECOMP decompresses (mode 1) with d=du/dv.
- New ST_DEC_DECOMP state with an inline byteDecode_d walker (reverse of the
  C1/C2 bit-packer): walks c_in_bram bytes, accumulates LSB-first into a bit
  buffer, extracts d-bit symbols, feeds comp_decomp, writes each decompressed
  coeff (mod q) to a bank.
    c1 = K polys, d=du -> u'[i] in bank_se rel slot i (0..K-1)
    c2 = 1 poly,  d=dv -> v'   in bank_t rel slot DEC_VSLOT=2 (avoids UPSUM=1)
- dbg_slot_i widened 4->6 bits so the TB can read v' (abs slot 26 at K=4).
- bse/bt write muxes gain the DECOMP writeback paths (dec_u_we / dec_v_we).

Verification: examples/dump_decaps.rs (ml-kem-r worktree) emits per-stage
golden (u'/v'/s_hat/u_hat/w/m') into vectors/decgold/. TB verify_d1 reads back
u'[i] and v' and compares all 256 coeffs each.

Bring-up note: dbg coeff readback latency is bank(1)+dbg_coeff_r(1); the TB's
rdcoeff initially waited 2 cyc and saw data shifted by one index -> fixed to 3.

Verified: dec D1 K=2/3/4 all cases PASS; KeyGen + Encaps unregressed.
2026-06-29 17:32:03 +08:00
2026-06-27 03:53:53 +08:00
2026-06-27 03:53:53 +08:00

ML-KEM 硬件实现FIPS 203

基于 SystemVerilog 的 ML-KEMModule-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 的完整密钥生成数据通路:给定种子 dz,输出封装密钥 ek(公钥)和解封装密钥 dk(私钥)。模块运行于 100 MHz10 ns 周期),低电平复位。

运行时参数选择

ML-KEM 的安全等级 K 在运行时通过输入端口 k_i 选择而非编译期参数。存储按最坏情况ML-KEM-1024KMAX=4静态分配k_istart_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_topsample_ntt_syncsample_cbd_syncntt_corepoly_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_topSHA3-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_coremode=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

存储布局

所有多项式存于 polymemNUM_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_memKMAX 最大 1568Bdkp_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-1dbg_dk_o(读完整 dk0..DKB-1逐字节回读与黄金向量比对
  • 全部相符打印 K=<K> CASE <n> PASS,否则打印前 8 个不匹配字节并报 FAIL

3. 运行测试

测试经由统一脚本 run_tb.sh 分发(自动 source Vivado 环境并设置 LD_PRELOAD

./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_k2KP=3→mlkem_kg_k3KP=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 == pkdk == 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 命令

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

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

参考

Description
No description provided
Readme 23 MiB
Languages
Verilog 81.7%
Python 10.8%
Tcl 5.1%
Shell 2.4%