feat(dec): Decaps D7 - implicit-reject compare + end-to-end KAT
Final FO step (FIPS 203 Alg 17 steps 9-11): compare c' to c and select the shared secret with implicit rejection. Completes full ML-KEM Decaps. - ST_DEC_CMP walks all ct bytes (no early-out: constant work, matching the constant-time spec intent), reading c' (ct_bram) and c (c_in_bram) in parallel, OR-ing every byte difference into cmp_neq; the final byte latches dec_reject. - ss_o = dec_reject ? kbar_r (K-bar) : ss_r (K'). KeyGen/Encaps leave dec_reject=0 so ss_r passes through unchanged. - ST_ENC_C2 terminal branches on op_r: Decaps -> ST_DEC_CMP, Encaps -> DONE. The dec TB now runs end-to-end twice per case (dk loaded once): - valid ct : ss_o == KAT ss (c'==c -> K'), dec_reject==0 - corrupt ct: ss_o == KAT ss_n (c'!=c -> K-bar=J(z||ct_n)), dec_reject==1 exercising both the accept and implicit-reject paths against the KAT's own ct_n/ss_n reject vectors. Verified: dec D7 K=2/3/4 all cases PASS (accept + reject); KeyGen + Encaps unregressed. Full ML-KEM (KeyGen + Encaps + Decaps w/ implicit rejection) now works in hardware across all three parameter sets.
This commit is contained in:
@@ -134,7 +134,10 @@ module mlkem_top #(
|
||||
wire [11:0] c2_bytes_rt = 12'd32 * {7'b0, dv_rt}; // 128/128/160
|
||||
wire [11:0] ct_bytes_rt = c1_bytes_rt + c2_bytes_rt; // 768/1088/1568
|
||||
wire [11:0] ct_bytes_rt = c1_bytes_rt + c2_bytes_rt; // 768/1088/1568
|
||||
assign ss_o = ss_r;
|
||||
// Shared secret out: Encaps drives ss_r = K directly. Decaps implicit reject:
|
||||
// ss = (c' == c) ? K' : K-bar. K' is in ss_r, K-bar in kbar_r; dec_reject is
|
||||
// the latched c' != c flag from D7 (0 for KeyGen/Encaps, so ss_r passes through).
|
||||
assign ss_o = dec_reject ? kbar_r : ss_r;
|
||||
|
||||
// ---- E1: rho-load + byteDecode12 bookkeeping ----
|
||||
// rho load: read 32 bytes from ek_bram at offset 384*k into rho_r.
|
||||
@@ -180,7 +183,8 @@ module mlkem_top #(
|
||||
.rd_addr(ct_rd_addr), .rd_data(ct_rd_data),
|
||||
.wr_en(ct_we), .wr_addr(ct_wa), .wr_data(ct_wd)
|
||||
);
|
||||
assign ct_rd_addr = dbg_ct_idx_i;
|
||||
// ct_bram read addr: D7 compare walks c' via cmp_idx; else debug readback.
|
||||
assign ct_rd_addr = (st == ST_DEC_CMP) ? cmp_idx : dbg_ct_idx_i;
|
||||
assign dbg_ct_o = ct_rd_data;
|
||||
|
||||
// ---- c_in_bram: Decaps input ciphertext c (<=1568 B). Preloaded via
|
||||
@@ -199,7 +203,8 @@ module mlkem_top #(
|
||||
// stage) so the registered BRAM read yields the byte 1 cycle later, matching
|
||||
// the assemble/writeback pipeline. Otherwise the registered cin_rd_addr_r
|
||||
// (D1 walker / idle) drives it.
|
||||
assign cin_rd_addr = (st == ST_DEC_J) ? dj_c_idx[10:0] : cin_rd_addr_r;
|
||||
assign cin_rd_addr = (st == ST_DEC_J) ? dj_c_idx[10:0] :
|
||||
(st == ST_DEC_CMP) ? cmp_idx : cin_rd_addr_r;
|
||||
|
||||
// ================================================================
|
||||
// Polynomial storage, sized for KMAX (worst case). Runtime k uses a
|
||||
@@ -488,6 +493,7 @@ module mlkem_top #(
|
||||
localparam ST_DEC_MENC = 5'd25; // D4: m' = byteEncode_1(Compress_1(w))
|
||||
localparam ST_DEC_G = 5'd26; // D5: (K',r') = G(m' || h), SHA3-512 single block
|
||||
localparam ST_DEC_J = 5'd27; // D5: K-bar = J(z || c), multi-block SHAKE256
|
||||
localparam ST_DEC_CMP = 5'd28; // D7: c' == c compare + implicit-reject ss select
|
||||
localparam ST_DONE = 5'd31;
|
||||
|
||||
reg [4:0] st, st_next;
|
||||
@@ -970,6 +976,20 @@ module mlkem_top #(
|
||||
reg men_done;
|
||||
// D4 packs the recovered message bits directly into m_r (reused by D6's
|
||||
// V/mu re-encrypt and exposed on dbg_mprime_o).
|
||||
|
||||
// ================================================================
|
||||
// D7: implicit-reject compare (Decaps ST_DEC_CMP).
|
||||
// Walk all ct bytes, reading c' (ct_bram) and c (c_in_bram) in parallel;
|
||||
// OR every byte difference into cmp_neq. After the last byte, ss = cmp_neq
|
||||
// ? K-bar : K'. The walk visits every byte regardless of early differences
|
||||
// (constant work / no early-out), matching the constant-time spec intent.
|
||||
// ph0: present both addrs; ph1: compare registered reads, accumulate.
|
||||
reg [10:0] cmp_idx; // byte index 0..ct_bytes
|
||||
reg cmp_ph; // 0=present addr, 1=compare
|
||||
reg cmp_neq; // sticky: any byte differed
|
||||
reg cmp_done;
|
||||
reg dec_reject; // latched cmp_neq at end (drives ss select)
|
||||
wire cmp_byte_neq = (ct_rd_data != cin_rd_data);
|
||||
wire [13:0] men_rd = UPSUM*256 + men_idx; // bank_t w read addr
|
||||
wire men_w_bit = (bt_rd_data > 12'd832) && (bt_rd_data <= 12'd2496); // Compress_1
|
||||
|
||||
@@ -1151,7 +1171,10 @@ module mlkem_top #(
|
||||
ST_ENC_TDEC: if (td_done) st_next = ST_ENC_E2MV; // t_hat decoded -> relocate e2
|
||||
ST_ENC_E2MV: if (em_done) st_next = ST_ENC_V; // e2 relocated -> compute v
|
||||
ST_ENC_V: if (u_row >= 3'd1) st_next = ST_ENC_C2; // E6: v done -> E7
|
||||
ST_ENC_C2: if (cp_done) st_next = ST_DONE; // E7: c2 packed -> done
|
||||
ST_ENC_C2: if (cp_done) st_next = (op_r == 2'd2) ? ST_DEC_CMP : ST_DONE;
|
||||
// D7: walk ct bytes comparing c' (ct_bram) vs c (c_in_bram); when the
|
||||
// last byte is compared, select ss = (c'==c) ? K' : K-bar and finish.
|
||||
ST_DEC_CMP: if (cmp_done) st_next = ST_DONE;
|
||||
ST_DONE: st_next = ST_IDLE;
|
||||
default: st_next = ST_IDLE;
|
||||
endcase
|
||||
@@ -1275,6 +1298,11 @@ module mlkem_top #(
|
||||
men_idx <= 8'd0;
|
||||
men_ph <= 2'd0;
|
||||
men_done <= 1'b0;
|
||||
cmp_idx <= 11'd0;
|
||||
cmp_ph <= 1'b0;
|
||||
cmp_neq <= 1'b0;
|
||||
cmp_done <= 1'b0;
|
||||
dec_reject <= 1'b0;
|
||||
dj_blk <= 4'd0;
|
||||
dj_byte <= 8'd0;
|
||||
dj_phase <= 2'd0;
|
||||
@@ -2036,8 +2064,34 @@ module mlkem_top #(
|
||||
endcase
|
||||
end
|
||||
|
||||
// Arm rho-load when entering ST_ENC_LOAD. rho = ek[384k .. 384k+31].
|
||||
// Entered from Encaps G (ST_ENC_G) or Decaps D6 re-encrypt (ST_DEC_J).
|
||||
// Arm D7 compare when re-encrypt finishes (ST_ENC_C2 -> ST_DEC_CMP).
|
||||
if (st == ST_ENC_C2 && st_next == ST_DEC_CMP) begin
|
||||
cmp_idx <= 11'd0;
|
||||
cmp_ph <= 1'b0;
|
||||
cmp_neq <= 1'b0;
|
||||
cmp_done <= 1'b0;
|
||||
end
|
||||
|
||||
// ---- ST_DEC_CMP (D7): constant-time c' == c compare + ss select ----
|
||||
// ph0: present cmp_idx to both ct_bram (c') and c_in_bram (c).
|
||||
// ph1: registered reads valid -> OR any difference into cmp_neq,
|
||||
// advance. After the last byte, latch dec_reject and ss stays
|
||||
// K' (ss_r) or switches to K-bar (kbar_r) via the ss_o mux.
|
||||
if (st == ST_DEC_CMP && !cmp_done) begin
|
||||
if (!cmp_ph) begin
|
||||
cmp_ph <= 1'b1; // addr presented; wait for read
|
||||
end else begin
|
||||
cmp_neq <= cmp_neq | cmp_byte_neq;
|
||||
if (cmp_idx == ct_bytes_rt - 12'd1) begin
|
||||
dec_reject <= cmp_neq | cmp_byte_neq; // include final byte
|
||||
cmp_done <= 1'b1;
|
||||
end else begin
|
||||
cmp_idx <= cmp_idx + 11'd1;
|
||||
end
|
||||
cmp_ph <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
if (st_next == ST_ENC_LOAD &&
|
||||
(st == ST_ENC_G || st == ST_DEC_J)) begin
|
||||
rl_idx <= 6'd0;
|
||||
|
||||
Reference in New Issue
Block a user