Commit Graph

12 Commits

Author SHA1 Message Date
030931d4e5 feat(dec): Decaps D0 - op_i widen + dk/c load + parse
Scaffolding for ML-KEM Decaps (FIPS 203 Alg 18):
- op_i widened to 2-bit: 00=KeyGen, 01=Encaps, 10=Decaps (op_r too).
- New ST_DEC_LOAD state (D0: settles to DONE so load/parse is dbg-checkable).
- dk (=sk) streamed via dk_in_*; load logic routes each byte by region:
  [0,384K)->dk_pke (dkp_bram), [384K,768K+32)->ek_pke (ek_bram),
  [768K+32,+32)->H(ek) (hek_r), [768K+64,+32)->z (z_r). Routing uses the
  LIVE k_i input, not start-captured k_r (dk is streamed before start_i).
- c (=ct) streamed via c_in_* into a SEPARATE c_in_bram, so the computed c'
  (ct_bram) can later be compared against original c and J(z||c) can read c.
- New dbg taps: dbg_mprime_o/dbg_kbar_o/dbg_decz_o/dbg_dech_o.

TB: tb_mlkem_dec_katK_xsim verifies dk parse (H(ek), z, ek_pke/dk_pke BRAM
round-trip). gen_decaps_vectors.py emits dec_k{K}_c{N}_{dk,ct,ss,ctn,ssn}.hex
from the NIST KAT. run_tb.sh gains a 'dec' module (mirrors 'enc').

Regression fix: old KeyGen/Encaps TBs didn't connect the new input ports,
floating them to X and corrupting the ek/dkp write muxes -> tied off
dk_in_*/c_in_*/new dbg taps in both.

Verified: dec D0 K=2/3/4 PASS; KeyGen K=2 + Encaps K=2 unregressed.
2026-06-29 15:22:34 +08:00
cdc5ce25b1 feat(enc): Encaps E2 - sample y/e1/e2 (CBD eta1/eta2, r seed)
ST_ENC_C reuses the ST_C CBD datapath with Encaps muxes: seed=r (not sigma),
eta=eta1 for y[0..K-1] then eta2 for e1[0..K-1]/e2, nonce 0..2K. 2K+1 polys
(vs KeyGen 2K). y/e1 -> bank_se rel slots 0..2K-1; e2 -> bank_t rel slot 0
(free during C/N/U since TDEC is deferred to V-prep so the 28-slot banks hold
all of A+y_hat+e1+e2 at K=4 without resizing).

Bring-up golden via ml-kem-r dump_encaps_full (working-tree example):
vectors/encgold/ec_k2_c0_{y,e1,e2}.hex. Verified (K=2 c0) y[0..1],e1[0..1],e2
== ml-kem-r; A_hat (E1) and ss (E0) still pass; K=3/4 no timeout.
2026-06-29 01:55:47 +08:00
0a8b3dae69 feat(enc): Encaps E0 - op_i/msg_i/ek-load scaffold + H(ek)+G(m||H(ek))
Extend mlkem_top with a runtime op_i select (0=KeyGen, 1=Encaps) and the
first Encaps stages, reusing the shared keccak_core and the ST_H multi-block
SHA3-256 machinery:
  ST_ENC_H: H(ek) over preloaded ek_bram (same FSM as KeyGen ST_H)
  ST_ENC_G: (K,r) = G(m||H(ek)) via new 64-byte single-block SHA3-512

- sha3_top_shared: add mode=2'b11 = SHA3-512 over a full 512-bit message
  (g512_pad). Standalone tb_sha3_g512 confirms it byte-exact.
- mlkem_top: new ports op_i, msg_i, ek_in_{we,addr,byte} (ek preload), ss_o,
  dbg_ct_*, dbg_r_o/dbg_hek_o. st widened 4->5 bits; ST_ENC_* states added.
  Renamed message port to msg_i to avoid collision with ST_M counter m_i.
- TB tb_mlkem_enc_katK + gen_encaps_vectors.py (per-byte ek/m/ct/ss vectors).

Verified ss==KAT.ss for K=2/3/4, cases 0-2 (all PASS). KeyGen unaffected
(K=2 c0 still ek==pk, dk==sk byte-exact).
2026-06-29 01:00:47 +08:00
b7e4fd9323 test(top): add kat_k2_* vectors with uniform prefix for parametric TB
The parametric KAT TB (KP generic) builds filenames kat_k<K>_c<n>_*.hex, but
the K=2 vectors were committed earlier as kat_c<n>_*.hex (no k2 prefix), so
run_tb.sh top emitted 'file cannot be opened' warnings for K=2 and the data
read as X. Add kat_k2_* copies so all three parameter sets load cleanly.

./run_tb.sh top now: 0 file warnings, all 11 cases PASS (K=2:0..4, K=3:0..2,
K=4:0..2), exit 0.
2026-06-28 03:06:41 +08:00
b2bf798454 feat(mlkem_top): parameterize K in {2,3,4} (ML-KEM 512/768/1024)
Generalize KeyGen from K=2-hardcoded to compile-time parameter K:
- eta1 derived (3 for K=2, else 2); slot layout SLOT_S/E/T = K*K+{0,K,2K},
  NUM_SLOTS = K*K+3K; SAW=5 slot-addr width.
- A-stage: explicit a_i/a_j row-major counters (slot = i*K+j) instead of
  K=2 bit-tricks. C/N stages: parametric slot bases, 2K polys.
- M-stage: m_i/m_j widened to 3-bit (must reach K=4); slots i*K+j etc.
- E-stage: 2K polys, e_is_dk split, rho offset 384*K.
- H(ek): H_NBLK=ceil((EK_BYTES+1)/136), H_LAST padding generalized;
  h_blk 4-bit. Byte mems sized EK_BYTES/DK_BYTES.
- Widen dbg_byte_idx_i to [10:0] (ek up to 1568B for K=4).

Parametric TB (tb_mlkem_kg_katK, KP generic + CASE plusarg). Verified
byte-exact vs NIST KAT:
  K=2 (512):  cases 0..4  ek 800B  / dk 1632B
  K=3 (768):  cases 0..2  ek 1184B / dk 2400B  (~36k cyc)
  K=4 (1024): cases 0..2  ek 1568B / dk 3168B  (~54k cyc)
run_tb.sh top runs all three parameter sets.
2026-06-28 02:59:58 +08:00
42d3748ab6 test(mlkem_top): KeyGen verified vs NIST KAT count=0..4
Parameterized end-to-end TB (tb_mlkem_kg_katN, +CASE=n) loads d/z/ek/dk from
per-case hex files. All 5 cases pass byte-exact:
  ek == KAT pk (800B), dk == KAT sk (1632B), ~21350 cyc each.
Five independent seeds exercise distinct rejection-sampling paths.
Vectors derived from kat_MLKEM_512.rsp with sk-layout sanity checks
(sk = dk_pke || ek || H(ek) || z).
2026-06-28 02:23:18 +08:00
9824ed8f2c feat(mlkem_top): KeyGen stage 4 - H(ek) + full dk, end-to-end KAT pass
Add ST_H stage: second sha3_top (mb_en=1) computes H(ek) over 800B ek as 6
pre-padded SHA3-256 rate blocks. Per block: assemble 136 bytes (h_padbyte
applies 0x06...0x80 padding on final block) into h_block_r, feed (hold valid
until mb_ready drops), wait permute; capture digest on last block into hek_r.

Full dk readback tap: dk = dk_pke(768) || ek(800) || H(ek)(32) || z(32) = 1632B.

End-to-end TB (tb_mlkem_kg_kat_xsim, no force/release): drive KAT count=0 d/z,
run full KeyGen FSM (IDLE->G->A->C->N->M->E->H->DONE), verify:
  ek  == KAT pk (800B)  byte-exact
  dk  == KAT sk (1632B) byte-exact
Done in 21403 cycles. ML-KEM-512 KeyGen complete and KAT-verified.
Prior stage TBs (2c/2e/2f) still pass (no regression).
2026-06-28 02:18:52 +08:00
17914911c3 feat(mlkem_top): KeyGen stage 2f (byteEncode12 -> ek, dk_pke)
Add ST_E stage: serialize t_hat[0..1] -> ek_mem[0..767], s_hat[0..1] ->
dkp_mem[0..767] via byteEncode12 (2 coeffs -> 3 bytes, LSB-first 12-bit:
b0=c0[7:0], b1={c1[3:0],c0[11:8]}, b2=c1[11:4]), then copy rho into
ek_mem[768..799]. Byte readback tap (dbg_byte_sel/idx -> dbg_byte_o).

Verified vs KAT-derived golden: ek 800B (== KAT pk) + dk_pke 768B
(== KAT sk prefix) byte-exact (20430 cyc). Completes Stage 2 datapath.
2026-06-28 02:03:03 +08:00
a9e50ebc0c feat(mlkem_top): KeyGen stage 2e (matrix accumulate t_hat)
Add ST_M stage: t_hat[i] = e_hat[i] + sum_j A_hat[i][j] o s_hat[j] via
poly_mul_sync + inline mod-add accumulation. Per (i,j): stream 256 (A,shat)
pairs into poly_mul, then accumulate 256 products into T_i (seeded from E_i
when j==0, else running T_i). m_pending waits for poly_mul IDLE between terms.

Verified vs ml-kem-r golden: 512/512 t_hat coeffs exact (19885 cyc).
2026-06-28 01:53:23 +08:00
4c692e570a feat(mlkem_top): KeyGen stage 2d (forward NTT of s/e)
Add ST_N stage: forward NTT (ntt_core mode=0, no scaling) of s[0],s[1],
e[0],e[1] in place (slots S0,S1,E0,E1). Per slot: stream 256 coeffs into
ntt_core during LOAD, collect 256 outputs back to same slot. n_pending
waits for core IDLE between slots.

Verified vs ml-kem-r golden: 1024/1024 shat/ehat coeffs exact (17318 cyc).
2026-06-28 01:47:54 +08:00
2f206a6bc5 feat(mlkem_top): KeyGen stages 2a-2c (G, SampleNTT A_hat, CBD s/e)
Fresh valid/ready KeyGen FSM for ML-KEM-512 (K=2, eta1=3). Independent
keccak per consumer (no arbiter). Verified stage-by-stage vs ml-kem-r golden:
- 2a G(d||K): rho/sigma exact (d byte0-low, K at byte32, no reversal).
- 2b SampleNTT: A_hat[i][j] from seed rho||j||i, 1024/1024 coeffs exact.
- 2c CBD: s[i]=CBD3(PRF(sigma,i)), e[i]=CBD3(PRF(sigma,K+i)); signed->mod-q
  (+Q when negative); 2048/2048 (A+s+e) coeffs exact.

polymem register array (10 slots x 256), debug readback tap (dbg_slot/idx ->
coeff, rho/sigma taps) for stage TBs. a_busy/c_busy guards (defensive after
sample_ntt fix). FSM: IDLE->G->A->C->DONE (datapath extended in later stages).

Plan + progress doc in .claude/plans/keygen_plan.md.
2026-06-28 01:41:44 +08:00
0e6798beb5 feat(tb): add KAT testbench for mlkem_top (ML-KEM-512)
- gen_vectors.py: parse kat_MLKEM_512.rsp, generate hex vectors
- tb_mlkem_top_xsim.v: force-inject d/msg/z for KAT testing
- mlkem_top_input.hex: 5 vectors (d + msg + z)
- mlkem_top_expected.hex: 5 vectors (pk + sk + ct + ss)
- xsim_run.tcl: full dependency chain compilation

Known issue: mlkem_top FSM has combinational race on rng_valid_i
- rng_valid_i driven by state_r (registered) causes rng_sync
  to miss valid_i pulse when state transitions at posedge
- Fix: change rng_valid_i to use state_next pattern
  (same as sha3_top uses state_next for kc_valid_i)
2026-06-27 01:07:34 +08:00