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.
This commit is contained in:
80
sync_rtl/top/TB/gen_decaps_vectors.py
Normal file
80
sync_rtl/top/TB/gen_decaps_vectors.py
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gen_decaps_vectors.py - Emit per-byte Decaps KAT vectors for the XSIM TB.
|
||||
|
||||
For ML-KEM-512/768/1024, parse the NIST .rsp and write, per case:
|
||||
dec_k{K}_c{N}_dk.hex : dk (=sk) bytes, one hex byte per line, byte 0 first
|
||||
dec_k{K}_c{N}_ct.hex : ct bytes (valid ciphertext), one per line, byte 0 first
|
||||
dec_k{K}_c{N}_ss.hex : ss bytes (shared secret for valid ct), byte 0 first
|
||||
dec_k{K}_c{N}_ctn.hex : ct_n bytes (corrupted ct -> implicit reject)
|
||||
dec_k{K}_c{N}_ssn.hex : ss_n bytes (shared secret K-bar for the reject path)
|
||||
|
||||
The TB streams dk into the design via dk_in_* (routed to dk_pke/ek_pke/h/z by
|
||||
region) and ct into c_in_*, runs Decaps, and checks ss:
|
||||
valid ct -> ss == ss (c'==c, K' kept)
|
||||
ct_n -> ss == ss_n (c'!=c, K-bar via J(z||c_n))
|
||||
|
||||
Run: python3 gen_decaps_vectors.py [num_cases]
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
ML_KEM_R = os.environ.get("ML_KEM_R", os.path.expanduser("~/Dev/ml-kem-r"))
|
||||
OUT_DIR = os.path.join(os.path.dirname(__file__), "vectors")
|
||||
KATS = {2: "kat_MLKEM_512.rsp", 3: "kat_MLKEM_768.rsp", 4: "kat_MLKEM_1024.rsp"}
|
||||
|
||||
|
||||
def parse_kat(path, n):
|
||||
"""Return list of dicts {count, sk, ct, ss, ct_n, ss_n} (hex) for first n."""
|
||||
vecs, cur = [], {}
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
m = re.match(r"^count\s*=\s*(\d+)$", line)
|
||||
if m:
|
||||
if cur:
|
||||
vecs.append(cur)
|
||||
if len(vecs) >= n:
|
||||
break
|
||||
cur = {"count": int(m.group(1))}
|
||||
continue
|
||||
# exact-key match: 'ct =' vs 'ct_n =', 'ss =' vs 'ss_n ='
|
||||
m = re.match(r"^(sk|ct|ss|ct_n|ss_n)\s*=\s*([0-9a-fA-F]+)$", line)
|
||||
if m and cur:
|
||||
cur[m.group(1)] = m.group(2).lower()
|
||||
if cur and len(vecs) < n:
|
||||
vecs.append(cur)
|
||||
return vecs
|
||||
|
||||
|
||||
def write_bytes(path, hexstr):
|
||||
"""Write hex string as one byte per line (byte 0 = first 2 hex chars)."""
|
||||
with open(path, "w") as f:
|
||||
for i in range(0, len(hexstr), 2):
|
||||
f.write(hexstr[i:i + 2] + "\n")
|
||||
|
||||
|
||||
def main():
|
||||
ncases = int(sys.argv[1]) if len(sys.argv) > 1 else 3
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
for k, fname in KATS.items():
|
||||
path = os.path.join(ML_KEM_R, "test_data", fname)
|
||||
if not os.path.exists(path):
|
||||
print(f"skip K={k}: {path} not found", file=sys.stderr)
|
||||
continue
|
||||
vecs = parse_kat(path, ncases)
|
||||
for v in vecs:
|
||||
n = v["count"]
|
||||
base = os.path.join(OUT_DIR, f"dec_k{k}_c{n}")
|
||||
write_bytes(f"{base}_dk.hex", v["sk"])
|
||||
write_bytes(f"{base}_ct.hex", v["ct"])
|
||||
write_bytes(f"{base}_ss.hex", v["ss"])
|
||||
write_bytes(f"{base}_ctn.hex", v["ct_n"])
|
||||
write_bytes(f"{base}_ssn.hex", v["ss_n"])
|
||||
print(f"K={k}: wrote {len(vecs)} cases "
|
||||
f"(dk={len(vecs[0]['sk'])//2}B ct={len(vecs[0]['ct'])//2}B)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user