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:
@@ -195,7 +195,11 @@ module mlkem_top #(
|
||||
.rd_addr(cin_rd_addr), .rd_data(cin_rd_data),
|
||||
.wr_en(c_in_we), .wr_addr(c_in_addr), .wr_data(c_in_byte)
|
||||
);
|
||||
assign cin_rd_addr = cin_rd_addr_r;
|
||||
// c_in read addr: D5 J presents dj_c_idx combinationally (no extra register
|
||||
// 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;
|
||||
|
||||
// ================================================================
|
||||
// Polynomial storage, sized for KMAX (worst case). Runtime k uses a
|
||||
@@ -482,6 +486,8 @@ module mlkem_top #(
|
||||
localparam ST_DEC_NTT = 5'd23; // D2: u_hat[i] = NTT(u'[i]) in place (bank_se rel i)
|
||||
localparam ST_DEC_W = 5'd24; // D3: w = v' - INTT(sum_j s_hat[j] o u_hat[j])
|
||||
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_DONE = 5'd31;
|
||||
|
||||
reg [4:0] st, st_next;
|
||||
@@ -524,7 +530,9 @@ module mlkem_top #(
|
||||
reg sha3_ack; // consumer ready for hash
|
||||
wire [511:0] kg_g_data = {248'b0, 5'b0, k_r, d_i}; // KeyGen G: [263:256]=k, [255:0]=d
|
||||
wire [511:0] enc_g_data = {hek_r, m_r}; // Encaps G: m || H(ek), 64 bytes
|
||||
wire [511:0] g_data = (st == ST_ENC_G) ? enc_g_data : kg_g_data;
|
||||
wire [511:0] dec_g_data = {hek_r, mprime_r}; // Decaps D5 G: m' || h, 64 bytes
|
||||
wire [511:0] g_data = (st == ST_ENC_G) ? enc_g_data :
|
||||
(st == ST_DEC_G) ? dec_g_data : kg_g_data;
|
||||
|
||||
// ================================================================
|
||||
// Shared keccak_core + phase mux (3 consumers -> 1 core)
|
||||
@@ -551,7 +559,8 @@ module mlkem_top #(
|
||||
// phase selects (mutually exclusive). Encaps adds ST_ENC_H/G (sha3),
|
||||
// ST_ENC_A (snt), ST_ENC_C (cbd).
|
||||
wire sel_sha3 = (st == ST_G) || (st == ST_H) ||
|
||||
(st == ST_ENC_H) || (st == ST_ENC_G);
|
||||
(st == ST_ENC_H) || (st == ST_ENC_G) ||
|
||||
(st == ST_DEC_G) || (st == ST_DEC_J);
|
||||
wire sel_snt = (st == ST_A) || (st == ST_ENC_A);
|
||||
wire sel_cbd = (st == ST_C) || (st == ST_ENC_C);
|
||||
|
||||
@@ -578,15 +587,17 @@ module mlkem_top #(
|
||||
.ready_i(1'b1) // consumers always accept (kc_ready_i=1)
|
||||
);
|
||||
|
||||
// ---- single shared sha3_top serving BOTH G and H ----
|
||||
// G (ST_G) uses single-block mode (mb_en=0); H(ek) (ST_H) uses the
|
||||
// multi-block absorb path (mb_en=1). These phases are disjoint in the
|
||||
// top FSM, so one sha3_top (one keccak_core) is sufficient. mb_en and
|
||||
// ready_i are muxed by phase; data_i/mode only matter while mb_en=0.
|
||||
wire sha3_mb_en = (st == ST_H) || (st == ST_ENC_H);
|
||||
// sha3 single-block mode: 2'b00 = G(33B) for KeyGen, 2'b11 = G(64B) for
|
||||
// Encaps (m||H(ek)). mode only matters when mb_en=0.
|
||||
wire [1:0] sha3_mode = (st == ST_ENC_G) ? 2'b11 : 2'b00;
|
||||
// G/H/J share one sha3_top. Single-block (mb_en=0): KeyGen G (ST_G, mode 00),
|
||||
// Encaps G (ST_ENC_G, mode 11), Decaps D5 G (ST_DEC_G, mode 11). Multi-block
|
||||
// absorb (mb_en=1): H(ek) (ST_H/ST_ENC_H, SHA3-256 pad) and Decaps D5 J
|
||||
// (ST_DEC_J, SHAKE256 pad over z||c). mb_* inputs are muxed by phase.
|
||||
wire sha3_mb_en = (st == ST_H) || (st == ST_ENC_H) || (st == ST_DEC_J);
|
||||
wire [1:0] sha3_mode = (st == ST_ENC_G || st == ST_DEC_G) ? 2'b11 : 2'b00;
|
||||
// multi-block feed mux: J (ST_DEC_J) drives dj_*, else H drives h_*.
|
||||
wire [1087:0] mb_block_mux = (st == ST_DEC_J) ? dj_block_r : h_block_r;
|
||||
wire mb_valid_mux = (st == ST_DEC_J) ? dj_mbvalid : h_mbvalid;
|
||||
wire mb_last_mux = (st == ST_DEC_J) ? dj_mblast : h_mblast;
|
||||
wire mb_ack_mux = (st == ST_DEC_J) ? dj_ack : h_ack;
|
||||
sha3_top_shared u_sha3 (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.mode(sha3_mode), // G = SHA3-512 (only used when mb_en=0)
|
||||
@@ -595,11 +606,11 @@ module mlkem_top #(
|
||||
.ready_o(sha3_ready),
|
||||
.hash_o(sha3_hash),
|
||||
.valid_o(sha3_vo),
|
||||
.ready_i(sha3_mb_en ? h_ack : sha3_ack),
|
||||
.ready_i(sha3_mb_en ? mb_ack_mux : sha3_ack),
|
||||
.mb_en(sha3_mb_en),
|
||||
.mb_block_i(h_block_r),
|
||||
.mb_valid_i(h_mbvalid),
|
||||
.mb_last_i(h_mblast),
|
||||
.mb_block_i(mb_block_mux),
|
||||
.mb_valid_i(mb_valid_mux),
|
||||
.mb_last_i(mb_last_mux),
|
||||
.mb_ready_o(h_mbready),
|
||||
// shared keccak_core interface (gated by phase in the mux below)
|
||||
.kc_state_o(kc_state_o),
|
||||
@@ -631,6 +642,43 @@ module mlkem_top #(
|
||||
reg [7:0] h_wb_pad; // pad constant to use if g is out of ek range
|
||||
reg h_wb_inek; // 1 if g is within ek range (use BRAM data)
|
||||
|
||||
// ---- Decaps D5 J(z||c) multi-block SHAKE256 state ----
|
||||
// Message = z (32 B, from z_r) || c (ct_bytes_rt B, from c_in_bram). Rate=136.
|
||||
// SHAKE256 pad: byte at msg_len = 0x1F, last byte of last block |= 0x80.
|
||||
// Reuses the shared u_sha3 mb_* port (muxed by ST_DEC_J above).
|
||||
reg [1087:0] dj_block_r; // current pre-padded rate block
|
||||
reg dj_mbvalid;
|
||||
reg dj_mblast;
|
||||
reg dj_ack;
|
||||
reg [3:0] dj_blk; // block index 0..DJ_NBLK-1 (z||c up to ~1600B -> <=12 blks)
|
||||
reg [7:0] dj_byte; // 0..136 byte being assembled
|
||||
reg [1:0] dj_phase; // 0=assemble 1=feed 2=wait-perm 3=done
|
||||
reg dj_done;
|
||||
// writeback pipeline (c_in_bram registered read, 1-cyc latency)
|
||||
reg dj_wb_vld;
|
||||
reg [7:0] dj_wb_idx; // position within 136-byte block
|
||||
reg dj_wb_inc; // 1 if global byte g is within c range (use BRAM)
|
||||
reg dj_wb_inz; // 1 if g < 32 (use z_r byte)
|
||||
reg [7:0] dj_wb_pad; // pad constant if g out of msg range
|
||||
reg [4:0] dj_wb_zidx; // z byte index (g) when inz
|
||||
// J message length = 32 (z) + ct_bytes_rt; total padded blocks.
|
||||
wire [11:0] dj_msglen = 12'd32 + {1'b0, ct_bytes_rt};
|
||||
wire [11:0] dj_last_byte = (dj_msglen / 12'd136) * 12'd136 + 12'd135; // last byte of final block
|
||||
wire [3:0] dj_nblk = (dj_msglen / 12'd136) + 4'd1;
|
||||
// global byte index for the address presented this cycle (assemble)
|
||||
wire [11:0] dj_g = {4'd0, dj_blk} * 12'd136 + {4'd0, dj_byte};
|
||||
// c_in_bram byte index (when g>=32): g-32
|
||||
wire [11:0] dj_c_idx = dj_g - 12'd32;
|
||||
// SHAKE256 pad constant for global byte g
|
||||
function [7:0] dj_padconst(input [11:0] g);
|
||||
begin
|
||||
if (g == dj_last_byte && g == dj_msglen) dj_padconst = 8'h9f; // 0x1F|0x80
|
||||
else if (g == dj_msglen) dj_padconst = 8'h1f;
|
||||
else if (g == dj_last_byte) dj_padconst = 8'h80;
|
||||
else dj_padconst = 8'h00;
|
||||
end
|
||||
endfunction
|
||||
|
||||
// h_hash / h_vo are now served by the shared u_sha3 above (mb_en=1 during
|
||||
// ST_H). They alias the single core's outputs; the H consumer logic below
|
||||
// already gates on st==ST_H, and u_sha3's valid_o/hash_o are mb-selected.
|
||||
@@ -1075,7 +1123,10 @@ module mlkem_top #(
|
||||
// D3: w = v' - INTT(sum_j s_hat[j] o u_hat[j]). Single output poly
|
||||
// (u_row 0..0); MAC->INTT->SUB, then D4 encodes m'.
|
||||
ST_DEC_W: if (u_row >= 3'd1) st_next = ST_DEC_MENC;
|
||||
ST_DEC_MENC: if (men_done) st_next = ST_DONE;
|
||||
ST_DEC_MENC: if (men_done) st_next = ST_DEC_G;
|
||||
// D5: (K',r') = G(m'||h) single-block, then K-bar = J(z||c) multi-block.
|
||||
ST_DEC_G: if (sha3_vo) st_next = ST_DEC_J;
|
||||
ST_DEC_J: if (dj_done) st_next = ST_DONE;
|
||||
ST_G: if (sha3_vo) st_next = ST_A;
|
||||
ST_A: if (a_pair >= kk_rt) st_next = ST_C;
|
||||
ST_C: if (c_poly >= {1'b0, k_r, 1'b0}) st_next = ST_N;
|
||||
@@ -1220,6 +1271,15 @@ module mlkem_top #(
|
||||
men_ph <= 2'd0;
|
||||
men_done <= 1'b0;
|
||||
mprime_r <= 256'd0;
|
||||
dj_blk <= 4'd0;
|
||||
dj_byte <= 8'd0;
|
||||
dj_phase <= 2'd0;
|
||||
dj_mbvalid <= 1'b0;
|
||||
dj_mblast <= 1'b0;
|
||||
dj_ack <= 1'b0;
|
||||
dj_done <= 1'b0;
|
||||
dj_wb_vld <= 1'b0;
|
||||
dj_block_r <= 1088'd0;
|
||||
h_blk <= 3'd0;
|
||||
h_byte <= 8'd0;
|
||||
h_phase <= 2'd0;
|
||||
@@ -1885,6 +1945,93 @@ module mlkem_top #(
|
||||
sha3_ack <= 1'b0;
|
||||
end
|
||||
|
||||
// Arm Decaps D5 G when m' is ready (ST_DEC_MENC -> ST_DEC_G): fire the
|
||||
// 64-byte single-block G(m'||h). dec_g_data = {hek_r, mprime_r}.
|
||||
if (st == ST_DEC_MENC && st_next == ST_DEC_G) begin
|
||||
sha3_valid <= 1'b1;
|
||||
sha3_ack <= 1'b1;
|
||||
end
|
||||
// Capture (K', r') when D5 G completes. K' = low half (candidate ss),
|
||||
// r' = high half (PRF seed for D6 re-encrypt). Reuse ss_r / r_r.
|
||||
if (st == ST_DEC_G && sha3_vo) begin
|
||||
ss_r <= sha3_hash[255:0]; // K' = G bytes 0..31
|
||||
r_r <= sha3_hash[511:256]; // r' = G bytes 32..63
|
||||
sha3_ack <= 1'b0;
|
||||
end
|
||||
|
||||
// Arm D5 J(z||c) when G completes (ST_DEC_G -> ST_DEC_J): assemble the
|
||||
// first 136-byte block. dj_ack high to consume the final digest.
|
||||
if (st == ST_DEC_G && st_next == ST_DEC_J) begin
|
||||
dj_blk <= 4'd0;
|
||||
dj_byte <= 8'd0;
|
||||
dj_phase <= 2'd0;
|
||||
dj_mbvalid<= 1'b0;
|
||||
dj_mblast <= 1'b0;
|
||||
dj_ack <= 1'b1;
|
||||
dj_done <= 1'b0;
|
||||
dj_wb_vld <= 1'b0;
|
||||
cin_rd_addr_r <= 11'd0; // present c byte 0 (g=32 -> c_idx 0)
|
||||
end
|
||||
|
||||
// ---- ST_DEC_J: K-bar = J(z||c) multi-block SHAKE256 ----
|
||||
// Mirror of the H(ek) multi-block machine. Byte source by global g:
|
||||
// g < 32 -> z_r byte g
|
||||
// 32 <= g < msglen -> c_in_bram byte (g-32), registered read
|
||||
// else -> SHAKE256 pad constant
|
||||
// c_in_bram read is registered: present c_idx for dj_byte this cycle,
|
||||
// write back the byte that arrived for the addr presented last cycle.
|
||||
if (st == ST_DEC_J && !dj_done) begin
|
||||
case (dj_phase)
|
||||
2'd0: begin
|
||||
// writeback the byte read for the previous address
|
||||
if (dj_wb_vld) begin
|
||||
if (dj_wb_inz)
|
||||
dj_block_r[dj_wb_idx*8 +: 8] <= z_r[dj_wb_zidx*8 +: 8];
|
||||
else if (dj_wb_inc)
|
||||
dj_block_r[dj_wb_idx*8 +: 8] <= cin_rd_data;
|
||||
else
|
||||
dj_block_r[dj_wb_idx*8 +: 8] <= dj_wb_pad;
|
||||
end
|
||||
if (dj_byte <= 8'd135) begin
|
||||
dj_wb_vld <= 1'b1;
|
||||
dj_wb_idx <= dj_byte;
|
||||
dj_wb_inz <= (dj_g < 12'd32);
|
||||
dj_wb_inc <= (dj_g >= 12'd32) && (dj_g < dj_msglen);
|
||||
dj_wb_zidx <= dj_g[4:0];
|
||||
dj_wb_pad <= dj_padconst(dj_g);
|
||||
// c_in addr is presented combinationally (cin_rd_addr
|
||||
// mux uses dj_c_idx during ST_DEC_J); data lands next
|
||||
// cycle, matching this byte's writeback.
|
||||
dj_byte <= dj_byte + 8'd1;
|
||||
end else begin
|
||||
dj_wb_vld <= 1'b0;
|
||||
dj_byte <= 8'd0;
|
||||
dj_mbvalid <= 1'b1;
|
||||
dj_mblast <= (dj_blk == dj_nblk - 4'd1);
|
||||
dj_phase <= 2'd1;
|
||||
end
|
||||
end
|
||||
2'd1: begin
|
||||
if (dj_mbvalid && !h_mbready) begin
|
||||
dj_mbvalid <= 1'b0;
|
||||
dj_mblast <= 1'b0;
|
||||
dj_phase <= 2'd2;
|
||||
end
|
||||
end
|
||||
2'd2: begin
|
||||
if (sha3_vo) begin
|
||||
kbar_r <= sha3_hash[255:0];
|
||||
dj_done <= 1'b1;
|
||||
dj_phase<= 2'd3;
|
||||
end else if (h_mbready) begin
|
||||
dj_blk <= dj_blk + 4'd1;
|
||||
dj_phase <= 2'd0;
|
||||
end
|
||||
end
|
||||
default: ; // done
|
||||
endcase
|
||||
end
|
||||
|
||||
// Arm rho-load when entering ST_ENC_LOAD. rho = ek[384k .. 384k+31].
|
||||
if (st == ST_ENC_G && st_next == ST_ENC_LOAD) begin
|
||||
rl_idx <= 6'd0;
|
||||
|
||||
Reference in New Issue
Block a user