Files
mlkem-sync/.claude/plans/decaps_plan.md
FallenSigh 940946f30c feat(dec): Decaps D2 - s_hat=byteDecode12(dk_pke) + u_hat=NTT(u')
K-PKE.Decrypt steps 2-3 (FIPS 203 Alg 15), both by reusing Encaps machines:

- ST_DEC_SDEC reuses the Encaps TDEC (byteDecode12) machine: only the byte
  source changes (td_byte mux -> dkp_rd_data; dkp_rd_addr driven by td_ekaddr
  in SDEC). Decodes dk_pke -> s_hat[j] into bank_a slot j*K, the same layout
  t_hat uses, so the D3 MAC can read s_hat[j] with the existing addressing.
- ST_DEC_NTT reuses the forward-NTT machine (n_slot_max=k_r) to transform
  u'[i] in place in bank_se rel slots 0..K-1 -> u_hat[i]. Added ST_DEC_NTT to
  the bank_se read/write muxes and the NTT load/process/arm blocks alongside
  ST_N/ST_ENC_N.
- FSM: DECOMP -> SDEC -> NTT -> DONE.

TB verify_d2 checks s_hat[i] (bank_a slot i*K) and u_hat[i] (bank_se rel i)
against golden. verify_d1 narrowed to v' only: D2's in-place NTT overwrites u'
in bank_se, so u' correctness is now proven transitively via u_hat==NTT(u').

Verified: dec D2 K=2/3/4 all cases PASS; KeyGen + Encaps unregressed.
2026-06-29 18:00:44 +08:00

90 lines
8.8 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 Decaps 顶层集成 — 实现计划
> 在 `mlkem_top`(KeyGen + Encaps 均 KAT 通过)基础上扩展 Decaps(FIPS 203 Alg 18 + K-PKE.Decrypt Alg 15)。
> 决策(已与用户确认):**(1) 实现完整隐式拒绝路径(J + 常量时间 c'==c 比较 + K̄/K' mux),并用 corrupted-ct(KAT ct_n/ss_n)验证拒绝路径;(2) 逐级 dbg tap 对拍 ml-kem-r golden(m'/w/u_hat 等)+ 端到端 ss==KAT.ss。**
## 算法(Decaps_internal,全 K)
输入:dk(=KAT.sk,768K+96 B)、c(=KAT.ct,32(du·K+dv) B)。输出:K'=ss(32 B)。
1. 解析 dk:`dk_pke = dk[0:384K]`(s_hat)、`ek_pke = dk[384K:768K+32]``h = dk[768K+32:768K+64]``z = dk[768K+64:768K+96]`
2. `m' = K-PKE.Decrypt(dk_pke, c)`(**唯一全新数据通路**,见下)。
3. `(K', r') = G(m' ‖ h)` = SHA3-512(64 B 单块)。**与 Encaps G 完全相同**(mode=11)。
4. `K̄ = J(z ‖ c)` = SHAKE-256(z‖c, 32 B 输出)。**多块,块数 6/9/12 = 与 H(ek) 相同**。
5. `c' = K-PKE.Encrypt(ek_pke, m', r')`。**这就是 Encaps E1E7**(A 再生 + 采样 y/e1/e2(seed=r') + NTT + u + v + 压缩成 ct')。**已 KAT 通过,零改动复用**。
6. 常量时间比较 `c' == c`(逐字节 XOR 累加)。
7. `K' = (c'==c) ? K' : K̄`(隐式拒绝 mux)。返回 K'=ss。
### K-PKE.Decrypt(Alg 15,全新)
- 解析 c:`c1 = c[0:32·du·K]``c2 = c[32·du·K : +32·dv]`
- `u'[i] = Decompress_du(byteDecode_du(c1[i]))`,i=0..K-1(**新:通用 byteDecode_d 解包器 + comp_decomp mode=1 解压**)。
- `v' = Decompress_dv(byteDecode_dv(c2))`(1 poly)。
- `s_hat[i] = byteDecode12(dk_pke[i·384..])`,i=0..K-1(**复用 Encaps TDEC 机器**,5-cyc/triple)。
- `u_hat[i] = NTT(u'[i])`(mode=0,K polys,**复用 ST_ENC_N 的 ntt_core**)。
- `w = v' INTT(Σⱼ s_hat[j]∘u_hat[j])` mod Q(**复用 Encaps V 机器(MAC+INTT),ADD 改 SUB,加数 v' 替 e2+mu**)。
- `m' = byteEncode₁(Compress₁(w))` = 32 B(**复用 C1/C2 打包器,d=1**)。
## 复用与新增
### 直接复用(零或极小改动)
- **整个 Encaps E1E7**(c' = Encrypt):A 再生、CBD(seed=r')、NTT、U、V、C1、C2 → ct_bram。完全复用,只是 seed 来自 r'(已是 r_r 路径)、ek 来自 ek_pke(已在 ek_bram)、m 来自 m'(新:m' 寄存器替 m_r)。
- `u_sha3` G(mode=11,m'‖h):与 Encaps G 同,只是 enc_g_data 高半改 h(来自 dk 而非 H(ek) 重算)。
- `u_keccak` 共享核 + J 多块:复用 H 的 mb 路径,仅末块 pad 常量 0x06→0x1F(SHAKE)。
- `ntt_core`:u_hat fwd NTT(mode=0,复用 ST_ENC_N);decrypt 的 INTT(mode=1,复用 V 的 u_intt 路径)。
- `poly_mul` / 3 银行 / comp_decomp:加 Decaps phase mux。
- Encaps TDEC(byteDecode12 → bank_a):decrypt 的 s_hat 解码复用(改落 dk_pke 源 + 目标 bank)。
### 新增 RTL
- **`byteDecode_d` 通用解包器**(d∈{4,5,10,11}):c 字节流 → d-bit 系数,LSB-first(byteEncode_d 的逆)。新写,流式读 ct/c_bram,写银行。
- **comp_decomp mode=1 解压**:实例已有(E5/E7 用 mode=0);Decaps decode-decompress 用 mode=1,d=du/dv。加 phase。
- **dk 载入路径**:`dk_in_*`(或复用 ek_in_* 加宽地址),Decaps 前流入 dk_bram。顶层解析:dk_pke→bank/解码、ek_pke→ek_bram、h/z→寄存器。
- **c 载入路径**:`c_in_*` 流入 ct_bram 的「输入 c」区(注意 c' 也写 ct_bram → 需独立 c_in_bram 或分区,见存储编排)。
- **J 多块组装**:z(reg)‖c(c_in_bram)→ 136B 块,末块 0x1F pad。新地址逻辑(类 H 的 h_g_addr)。
- **w = v' INTT(...)**:V 机器 ADD 子相改 SUB(`(v' psum) mod Q`,负则 +Q),加数源 v'(银行)替 e2(bank_a)+mu。
- **m' Compress₁+byteEncode₁**:打包器加 d=1 路径(每系数 1 bit,256 bit = 32 B)。m' 落 32-bit 寄存器(供 G 与 c'-Encrypt)。
- **常量时间比较 c'==c**:逐字节读 ct_bram(c')与 c_in_bram(c),XOR 累加进 1-bit `ct_ne_r`(全程扫完,不早退)。
- **隐式拒绝 mux**:`ss_r = ct_ne_r ? k_bar_r : kprime_r`
- **op_i 加宽 2-bit**:00=KeyGen,01=Encaps,10=Decaps。新增 ST_DEC_* 状态。
## 存储编排(关键)
- **c 输入 vs c' 输出冲突**:Decaps 既要保留输入 c(给 J 和最终比较),又要算 c'(写 ct_bram)。**解法:c 输入存独立 `c_in_bram`(sd_bram W=8 D=2048);c' 仍写 ct_bram。** 比较阶段两个 bram 各一读口,无冲突。J 从 c_in_bram 读。
- **Decrypt 阶段银行**:s_hat(K)、u'(K)、u_hat(=NTT(u') 就地 K)、v'(1)、psum/w(1)。
- u' → bank_se rel 0..K-1;NTT 就地得 u_hat。
- s_hat → bank_a slot j·K(复用 TDEC 落点 + V-MAC 寻址)。
- v' → bank_t rel 0(或 bank_a 空 slot)。
- psum(Σ s∘u_hat)→ bank_t[UPSUM];INTT 就地;SUB 读 v'(bank_t rel 0)+ psum(bank_t UPSUM)→ 单口冲突 → v' 改存 bank_a 某 slot(类 e2 搬迁)或 bank_se 空区。**bring-up 定稿,dbg 验证。**
- m' 算完 → 32-bit 寄存器。Decrypt 阶段结束,银行清空。
- **Encrypt(c')阶段**:Decrypt 已出 m'(reg),银行重新被 Encaps E1E7 占用,无并发。
## 顶层接口新增
- `op_i [1:0]`:00/01/10。start_i 锁存 op_r[1:0]。
- `dk_in_*`(we/addr/byte):dk 流入。`c_in_*`(we/addr/byte):c 流入。
- `ss_o`(复用):Decaps 输出 K'。
- dbg taps:m'(`dbg_mprime_o[255:0]`)、w/u_hat 经现有 dbg_coeff_o、k_bar(`dbg_kbar_o`)。
## 实现阶段(逐阶段 dbg/KAT 对拍)
- **D0 — 脚手架 + dk/c 载入 + 解析** ✅:op_i 加宽 2-bit(00 KG/01 Enc/10 Dec),ST_DEC_LOAD(D0 暂直接→DONE)。dk 流入按 region 路由:dk_pke→dkp_bram、ek_pke→ek_bram、h→hek_r、z→z_r;ct→c_in_bram(独立于 ct_bram)。dbg 验证 h/z/ek_pke/dk_pke。**踩坑1:载入路由用 k_r 但 k_r 在 start_i 才锁存 → 预载期 region 边界全 0,路由全错。改用 LIVE k_i 边界(dkp_bytes_ld 等)。踩坑2:旧 KG/Enc TB 未接新端口(dk_in_*/c_in_*/dbg_*)→ X 漂入 write mux,KeyGen/Encaps 超时回归。补 tie-off 0。** runner = `./run_tb.sh dec [K] [CASE]`。K=2/3/4 D0 全过,KG/Enc 回归通过。
- **D1 — byteDecode_d + Decompress → u'/v'** ✅:复用 comp_decomp(改 mode 可选:Encaps C1/C2 compress mode=0,Decaps DECOMP mode=1)。ST_DEC_DECOMP 内联 byteDecode 走子机:逐字节读 c_in_bram,LSB-first 累进 bit buffer,凑够 d 位抽符号→comp_decomp 解压→写 bank。c1(K 多项式,d=du)→u'[i] bank_se rel i;c2(1 多项式,d=dv)→v' bank_t rel DEC_VSLOT=2(避开 UPSUM=1)。dbg_slot_i 加宽 4→6 bit(K=4 v' 在 slot 26)。dump_decaps.rs(ml-kem-r 工作树)产 u'/v'/s_hat/u_hat/w/m' golden 到 vectors/decgold/。**踩坑:dbg coeff 读回延迟 = bank(1)+dbg_coeff_r(1),TB rdcoeff 等 2 拍少一拍 → 数据整体错位一格;改 3 拍修正。** K=2/3/4 全过,KG/Enc 回归通过。
- **D2 — s_hat 解码 + u_hat = NTT(u')** ✅:复用 Encaps TDEC 机(ST_DEC_SDEC),字节源从 ek 切到 dkp_bram(td_byte mux + dkp_rd_addr 在 SDEC 走 td_ekaddr),s_hat 写 bank_a slot j*K(与 t_hat 同布局,D3 MAC 可直接读)。复用前向 NTT 机(ST_DEC_NTT,n_slot_max=k_r)对 bank_se rel 0..K-1 的 u' 原地变换成 u_hat。**踩坑:NTT 原地覆盖 u' → verify_d1 复查 u' 必失败;改为 verify_d1 只查 v'(bank_t 未动),u' 正确性由 u_hat==NTT(u') golden 传递性证明。** K=2/3/4 全过,KG/Enc 回归通过。
- **D3 — w = v' INTT(Σ s∘u_hat)**:V 机器 SUB 变体。dbg 对 w。
- **D4 — m' = byteEncode₁(Compress₁(w))**:打包器 d=1。dbg 对 m'(== KAT 解密的 m')。
- **D5 — G(m'‖h) → (K',r') + J(z‖c) → K̄**:G 复用、J 多块(0x1F pad)。dbg 对 K'/r'/K̄。
- **D6 — c' = Encrypt(ek_pke,m',r')**:复用 Encaps E1E7 写 ct_bram。dbg 对 c'==KAT.ct(有效 ct 时)。
- **D7 — 比较 + 拒绝 mux + 端到端 KAT**:c'==c 常量时间比较,ss=mux。干净 TB:
- 有效 ct(KAT.ct):ss==KAT.ss(c'==c → K')。
- 损坏 ct(KAT ct_n / ss_n):ss==KAT.ss_n(c'≠c → K̄)。
- K=2/3/4 各 count=0..N。
## 验证
-`dump_decaps.rs`(ml-kem-r examples,工作树):出 D1D6 中间量(u'/v'/s_hat/u_hat/w/m'/K'/K̄)256-coeff / 32B golden。
- 新 TB `tb_mlkem_dec_katK_xsim.v`:从 KAT 取 sk(→dk)、ct、ss、ct_n、ss_n,载入 dk/c,跑 Decaps,比 ss。
- runner:`./run_tb.sh dec [K] [CASE]`(并入 run_tb,复用 top tcl + dec TB)。
- XSIM 环境同前:`source settings64.sh; export LD_PRELOAD=libtinfo.so.5; rm -rf xsim.dir .Xil`
## 风险 / 注意
- **c 输入 / c' 输出共存**:必须分两个 bram(c_in_bram + ct_bram),否则 c' 覆盖 c 后无法比较 / 算 J。最易错,D0 定。
- **w 的 SUB**:`(v' psum) mod Q`,结果可能负 → +Q 修正(类 mod_sub)。v'/psum 读口冲突 → v' 搬到不同 bank。D3 dbg 验证。
- **J SHAKE pad**:末块 0x1F(非 0x06);单字节差异,复用 H mb 机器加 phase 选择 pad 常量。
- **op_i 加宽**:1-bit→2-bit,改 IDLE 分发、reset、所有 op_r 判断。回归 KeyGen/Encaps 不破。
- **m' 双用**:既喂 G(m'‖h)又喂 c'-Encrypt(替 m_r)。确保 Encrypt 路径读 m'_r 而非 m_r(加 mux 或 Decaps 时把 m'_r 写入 m_r)。
- **隐式拒绝常量时间**:比较全程扫完不早退(XOR 累加),硬件天然如此;但 mux 不可短路。
- **ek_pke 来自 dk**:Decaps 的 Encrypt 用 dk 内嵌的 ek_pke(解析到 ek_bram),不是外部 ek。