feat(dec): Decaps D5 - (K',r')=G(m'||h) + K-bar=J(z||c)

FO transform hash derivations (FIPS 203 Alg 17 steps 6-7), reusing the
shared SHA3 core:

- ST_DEC_G: (K',r') = G(m'||h) via the single-block SHA3-512 path (mode 11,
  dec_g_data = {hek_r, mprime_r}; h was captured into hek_r at D0 load).
  K' -> ss_r (candidate shared secret, ss_o), r' -> r_r (PRF seed for D6).
- ST_DEC_J: K-bar = J(z||c) via the multi-block absorb port (mb_en=1),
  modeled on the H(ek) machine: assemble 136-byte blocks, byte source is
  z_r (g<32), c_in_bram (32<=g<msglen), or SHAKE256 pad (0x1F suffix, last
  byte |=0x80 -- the only difference from H's 0x06). mb_* inputs muxed
  between H and J by state. K-bar -> kbar_r (dbg_kbar_o).
- FSM: MENC -> G -> J -> DONE.

Bring-up note: c_in_bram read through cin_rd_addr_r (a register) plus the
registered BRAM is 2-cycle latency, but the assemble/writeback pipeline only
budgets 1 -- so the first c byte at the z->c boundary read X and poisoned the
whole sponge. Fixed by driving cin_rd_addr combinationally from dj_c_idx
during ST_DEC_J (dropping the register stage) so data lands the next cycle.

Verified: dec D5 K=2/3/4 all cases PASS (ct 768/1088/1568B -> 6/9/12 J blocks);
K' matches the KAT shared secret for valid ciphertexts; KeyGen + Encaps
unregressed.
This commit is contained in:
2026-06-29 20:37:03 +08:00
parent 7f519fe826
commit 189411e8d1
39 changed files with 11352 additions and 21 deletions

View File

@@ -88,15 +88,16 @@ module tb_mlkem_dec_katK_xsim;
start_i=1; @(posedge clk); start_i=0;
c=0; while(!done_o && c<2000000) begin @(posedge clk); c=c+1; end
if(!done_o) begin $display("FAIL K=%0d case %0d: timeout", KP, casenum); $finish; end
$display("=== Decaps D4 done in %0d cyc ===", c);
$display("=== Decaps D5 done in %0d cyc ===", c);
verify_d0;
verify_d1;
verify_d2;
verify_d3;
verify_d4;
if (errors == 0) $display("K=%0d CASE %0d PASS (D4): w + m' recovery OK", KP, casenum);
else $display("K=%0d CASE %0d FAIL (D4): %0d total errors", KP, casenum, errors);
verify_d5;
if (errors == 0) $display("K=%0d CASE %0d PASS (D5): K'/r' = G(m'||h), K-bar = J(z||c) OK", KP, casenum);
else $display("K=%0d CASE %0d FAIL (D5): %0d total errors", KP, casenum, errors);
$finish;
end
@@ -286,4 +287,51 @@ module tb_mlkem_dec_katK_xsim;
errors = errors + be;
end
endtask
// D5: verify K' (ss_o), r' (dbg_r_o), K-bar (dbg_kbar_o) against golden.
reg [7:0] kp_b [0:31];
reg [7:0] rp_b [0:31];
reg [7:0] kb_b [0:31];
task verify_d5;
integer j, be;
reg [8*100-1:0] fn;
begin
// K' = G(m'||h) low half -> ss_o
$sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_kprime.hex", KP, casenum);
$readmemh(fn, kp_b);
be = 0;
for (j = 0; j < 32; j = j + 1)
if (ss_o[8*j +: 8] !== kp_b[j]) begin
if (be < 4) $display(" K'[%0d] got=%02x exp=%02x", j, ss_o[8*j +: 8], kp_b[j]);
be = be + 1;
end
if (be == 0) $display(" PASS: K' == golden (32 bytes)");
else $display(" FAIL: K' %0d byte mismatches", be);
errors = errors + be;
// r' = G(m'||h) high half -> dbg_r_o
$sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_rprime.hex", KP, casenum);
$readmemh(fn, rp_b);
be = 0;
for (j = 0; j < 32; j = j + 1)
if (dbg_r_o[8*j +: 8] !== rp_b[j]) begin
if (be < 4) $display(" r'[%0d] got=%02x exp=%02x", j, dbg_r_o[8*j +: 8], rp_b[j]);
be = be + 1;
end
if (be == 0) $display(" PASS: r' == golden (32 bytes)");
else $display(" FAIL: r' %0d byte mismatches", be);
errors = errors + be;
// K-bar = J(z||c) -> dbg_kbar_o
$sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_kbar.hex", KP, casenum);
$readmemh(fn, kb_b);
be = 0;
for (j = 0; j < 32; j = j + 1)
if (dbg_kbar_o[8*j +: 8] !== kb_b[j]) begin
if (be < 4) $display(" K-bar[%0d] got=%02x exp=%02x", j, dbg_kbar_o[8*j +: 8], kb_b[j]);
be = be + 1;
end
if (be == 0) $display(" PASS: K-bar == golden (32 bytes)");
else $display(" FAIL: K-bar %0d byte mismatches", be);
errors = errors + be;
end
endtask
endmodule