feat(sha3): multi-block SHA3-256 absorb for H(ek); KeyGen golden vectors
Stage 0+1 of mlkem_top KeyGen integration: - sha3_top: add multi-block SHA3-256 absorb FSM (mb_en/mb_block_i/mb_valid_i/ mb_last_i/mb_ready_o). Caller pre-pads final block; module does pure absorb loop (state^=block; Keccak-p). Single-block G/H/J paths bit-identical when mb_en=0. Sticky digest register holds output until consumer acks. - tb_sha3_mb_xsim: self-checking TB streams 800B ek (6 blocks) -> H(ek), verified == hashlib.sha3_256. Proper valid/ready handshake (no force). - Existing G/H/J TBs (xsim + Verilator) tie off mb_* ports; both frameworks regress clean (Verilator 25/25, XSIM G/H/J + keccak + 7-vec + multiblock). - test_framework/modules/mlkem_keygen/golden: full 256-coeff per-stage intermediates (rho/sigma, A_hat, s/e, s_hat/e_hat, t_hat, ek, dk_pke) for KAT count=0..4, dumped by ml-kem-r and self-verified against NIST KAT.
This commit is contained in:
@@ -116,6 +116,10 @@ int main(int argc, char** argv) {
|
||||
for (int w = 0; w < 16; w++) dut->data_i[w] = 0;
|
||||
dut->valid_i = 0;
|
||||
dut->ready_i = 0;
|
||||
// multi-block absorb path disabled for single-block G/H/J tests
|
||||
dut->mb_en = 0;
|
||||
dut->mb_valid_i = 0;
|
||||
dut->mb_last_i = 0;
|
||||
|
||||
// Reset: 2 full cycles
|
||||
for (int i = 0; i < 2; i++) posedge(dut);
|
||||
|
||||
124
sync_rtl/sha3/TB/tb_sha3_mb_xsim.v
Normal file
124
sync_rtl/sha3/TB/tb_sha3_mb_xsim.v
Normal file
@@ -0,0 +1,124 @@
|
||||
// tb_sha3_mb_xsim.v - Multi-block SHA3-256 absorb self-check for sha3_top.
|
||||
//
|
||||
// Streams pre-padded 1088-bit rate blocks (mb_h_blocks.hex) into sha3_top
|
||||
// with mb_en=1, then compares the 256-bit digest against mb_h_expected.hex
|
||||
// (= hashlib.sha3_256(ek) for KAT count=0, ek = 800 bytes -> 6 blocks).
|
||||
//
|
||||
// Run (from project root):
|
||||
// xvlog -sv sync_rtl/sha3/keccak_round.v sync_rtl/sha3/keccak_core.v \
|
||||
// sync_rtl/sha3/sha3_top.v sync_rtl/sha3/TB/tb_sha3_mb_xsim.v
|
||||
// xelab tb_sha3_mb_xsim -s mb_sim --timescale 1ns/1ps
|
||||
// xsim mb_sim -R
|
||||
|
||||
`timescale 1ns/1ps
|
||||
module tb_sha3_mb_xsim;
|
||||
|
||||
parameter BLOCK_FILE = "sync_rtl/sha3/TB/vectors/mb_h_blocks.hex";
|
||||
parameter EXPECTED_FILE = "sync_rtl/sha3/TB/vectors/mb_h_expected.hex";
|
||||
parameter MAX_BLOCKS = 16;
|
||||
|
||||
reg clk = 0;
|
||||
reg rst_n = 0;
|
||||
// single-block ports (unused here, tie off)
|
||||
reg [1:0] mode = 2'b01;
|
||||
reg [511:0] data_i = 0;
|
||||
reg valid_i = 0;
|
||||
wire ready_o;
|
||||
wire [511:0] hash_o;
|
||||
wire valid_o;
|
||||
reg ready_i = 0;
|
||||
// multi-block ports
|
||||
reg mb_en = 1'b1;
|
||||
reg [1087:0] mb_block_i = 0;
|
||||
reg mb_valid_i = 0;
|
||||
reg mb_last_i = 0;
|
||||
wire mb_ready_o;
|
||||
|
||||
sha3_top dut (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.mode(mode), .data_i(data_i), .valid_i(valid_i),
|
||||
.ready_o(ready_o), .hash_o(hash_o), .valid_o(valid_o), .ready_i(ready_i),
|
||||
.mb_en(mb_en), .mb_block_i(mb_block_i),
|
||||
.mb_valid_i(mb_valid_i), .mb_last_i(mb_last_i), .mb_ready_o(mb_ready_o)
|
||||
);
|
||||
|
||||
always #5 clk = ~clk;
|
||||
|
||||
reg [1087:0] blocks [0:MAX_BLOCKS-1];
|
||||
reg [255:0] expected_mem [0:0];
|
||||
reg [255:0] expected;
|
||||
integer nblocks;
|
||||
integer i;
|
||||
|
||||
// count valid (non-x) lines loaded
|
||||
function integer count_blocks;
|
||||
integer k;
|
||||
begin
|
||||
count_blocks = 0;
|
||||
for (k = 0; k < MAX_BLOCKS; k = k + 1)
|
||||
if (blocks[k] !== {1088{1'bx}}) count_blocks = k + 1;
|
||||
end
|
||||
endfunction
|
||||
|
||||
initial begin
|
||||
for (i = 0; i < MAX_BLOCKS; i = i + 1) blocks[i] = {1088{1'bx}};
|
||||
$readmemh(BLOCK_FILE, blocks);
|
||||
$readmemh(EXPECTED_FILE, expected_mem);
|
||||
expected = expected_mem[0];
|
||||
nblocks = count_blocks();
|
||||
$display("=== Multi-block SHA3-256 absorb TB ===");
|
||||
$display("Loaded %0d blocks; expected digest = %064x", nblocks, expected);
|
||||
|
||||
// reset
|
||||
rst_n = 0; ready_i = 0;
|
||||
repeat (4) @(posedge clk);
|
||||
rst_n = 1;
|
||||
@(posedge clk);
|
||||
|
||||
// stream blocks — hold valid/last stable across the accept edge
|
||||
// (deassert only after mb_ready_o drops) to avoid sampling races.
|
||||
for (i = 0; i < nblocks; i = i + 1) begin
|
||||
mb_block_i = blocks[i];
|
||||
mb_last_i = (i == nblocks - 1);
|
||||
mb_valid_i = 1'b1;
|
||||
// wait until module is ready, then one more edge is the accept
|
||||
while (!mb_ready_o) @(posedge clk);
|
||||
@(posedge clk); // accept edge (valid & ready both high)
|
||||
while (mb_ready_o) @(posedge clk); // ready drops => accepted, now safe
|
||||
mb_valid_i = 1'b0;
|
||||
mb_last_i = 1'b0;
|
||||
// wait for this block's permutation to finish (ready again or DONE)
|
||||
while (!mb_ready_o && !valid_o) @(posedge clk);
|
||||
$display(" after block %0d: state[255:0] = %064x", i, dut.mb_state_r[255:0]);
|
||||
end
|
||||
|
||||
// wait for digest
|
||||
i = 0;
|
||||
while (!valid_o && i < 1000) begin @(posedge clk); i = i + 1; end
|
||||
|
||||
if (!valid_o) begin
|
||||
$display("FAIL: digest never became valid (timeout)");
|
||||
$finish;
|
||||
end
|
||||
|
||||
if (hash_o[255:0] === expected) begin
|
||||
$display("PASS: H(ek) = %064x", hash_o[255:0]);
|
||||
$display("ALL TESTS PASSED");
|
||||
end else begin
|
||||
$display("FAIL: digest mismatch");
|
||||
$display(" got = %064x", hash_o[255:0]);
|
||||
$display(" expected = %064x", expected);
|
||||
end
|
||||
ready_i = 1; // ack the digest
|
||||
@(posedge clk);
|
||||
$finish;
|
||||
end
|
||||
|
||||
// global safety timeout
|
||||
initial begin
|
||||
#100000;
|
||||
$display("FAIL: global timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -56,7 +56,13 @@ module tb_sha3_xsim;
|
||||
.ready_o (ready_o),
|
||||
.hash_o (hash_o),
|
||||
.valid_o (valid_o),
|
||||
.ready_i (ready_i)
|
||||
.ready_i (ready_i),
|
||||
// multi-block absorb path disabled for single-block G/H/J tests
|
||||
.mb_en (1'b0),
|
||||
.mb_block_i (1088'b0),
|
||||
.mb_valid_i (1'b0),
|
||||
.mb_last_i (1'b0),
|
||||
.mb_ready_o ()
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
|
||||
@@ -44,7 +44,13 @@ module tb_sha3_xsim_simple;
|
||||
.ready_o (ready_o),
|
||||
.hash_o (hash_o),
|
||||
.valid_o (valid_o),
|
||||
.ready_i (ready_i)
|
||||
.ready_i (ready_i),
|
||||
// multi-block absorb path disabled for single-block G/H/J tests
|
||||
.mb_en (1'b0),
|
||||
.mb_block_i (1088'b0),
|
||||
.mb_valid_i (1'b0),
|
||||
.mb_last_i (1'b0),
|
||||
.mb_ready_o ()
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
|
||||
6
sync_rtl/sha3/TB/vectors/mb_h_blocks.hex
Normal file
6
sync_rtl/sha3/TB/vectors/mb_h_blocks.hex
Normal file
@@ -0,0 +1,6 @@
|
||||
537be01216c3a897d6923257af0713f9291cc342993653d55120a2df51673d65baf45fc64c24f26614842c8793a2b9b7307dc5eacb82e0877c091f738748194d78e1beacfbf69960c21c77c581e7bcb952ab54536298dc6836195672bc9043571d2897da5ec8f47af5f5006b872857d3125360bcb0ec6338f91931489273f468e17e1d830001b780
|
||||
02859394b52a4cda4d3910a3548462a50fc8926778b2877c50832dd243654015beb34328e6365aa6803769743c00eacfb08d5258e83bcc101d5ca99de98897e6d8a85a446009f85b92aa57f23150bc211731e486b20c63c83d8b61dbb4063860e525156b692f2848439296c629929e5319391cbac5099d3bb0c4b08c0a20322abf46d29f3208cf35
|
||||
bd65bd35a545a1865862b4d1c1b86c6011c642ac120c32bd7faa4292383e21f2212ff1587c78710cbb5ac948718a9cd648a7fb29b397c88987ca299070f60a04e62a9de92650426993d5008a239c2cb4ca77873ac5a822c288cd08ea85075037bb666265b15e8589b19be5d23e24aa09c44e1ad363b7fc101a1ae854f3072706d9867110bcd3d657
|
||||
408e5bae7f3143a020ef13c8d065c2cd60868c51b7390e5c314c731796028925591a5ee636af48417d6686198910a8751a32e102cfe192c72c6129ca5fc1268fc006203bc32ca00cf9162a3d85b76841f112a0124078e148aad5c33676faa9760aa3a722ad2bd13c17ae9a229311f1da5c50ecbba32098752532001704e9871ad657160c336b990d
|
||||
3b8c5da23a8f510e7e28cc0180136829e04664534d446a3d49d1bc9216c95c2153b2c3608c1886cb39164746079c4a8243fbc607e9cbc0d5440278b9348d22ba80cdc036309a6f4e83a4bca35c27158a00d246ced1a380c967739aad3a4849023c946523ef7b844c1539086fb2894610a95d7519551ad50ff3be091340496ba64f056c1df7ec6e54
|
||||
8000000000000000000000000000000615f74355ca862c3cdf3dab780c35cf24b88bf144706090a1c17e41205f9f13794c1a9033ace032aa0f0c85ff94c8d3c9bca46fd7461ff5dab8403a2af8b1c242f08be4763fd5d68d198e9016b80f1c77505b2b56480c37bb4cc084f08993c2cdb2771f266b33353c265af09a027649489d81f4595cc3b395
|
||||
1
sync_rtl/sha3/TB/vectors/mb_h_expected.hex
Normal file
1
sync_rtl/sha3/TB/vectors/mb_h_expected.hex
Normal file
@@ -0,0 +1 @@
|
||||
c6aa5100ed06d0c2db8cc3d1a36a62055b182f8d51ea71602501857e7c5d87ca
|
||||
@@ -1,22 +1,32 @@
|
||||
// sha3_top.v - SHA3/SHAKE top wrapper with valid/ready interface
|
||||
//
|
||||
// Implements SHA3-512 (G), SHA3-256 (H), SHAKE-256 (J) over a single
|
||||
// Keccak-f[1600] core. Supports single-block absorption (Phase 1.1).
|
||||
// Keccak-f[1600] core. Supports single-block absorption, plus a separate
|
||||
// multi-block SHA3-256 streaming absorb path for long inputs (e.g. H(ek)).
|
||||
//
|
||||
// Modes:
|
||||
// Single-block modes (mb_en=0):
|
||||
// 00 = G (SHA3-512): rate=576, suffix=01, msg_len=264, out=512
|
||||
// 01 = H (SHA3-256): rate=1088, suffix=01, msg_len=256, out=256
|
||||
// 10 = J (SHAKE-256): rate=1088, suffix=1111,msg_len=512,out=256
|
||||
//
|
||||
// Interface:
|
||||
// clk, rst_n - clock, active-low reset
|
||||
// Multi-block SHA3-256 (mb_en=1):
|
||||
// Streams pre-padded 1088-bit rate blocks. The CALLER applies SHA3-256
|
||||
// padding (suffix 0x06 ... 0x80) to the final block so this module only
|
||||
// does the absorb loop: state ^= block; state = Keccak-p(state); repeat.
|
||||
// The single-block paths above are bit-identical when mb_en=0.
|
||||
//
|
||||
// Interface (single-block):
|
||||
// mode[1:0] - 00=G, 01=H, 10=J
|
||||
// data_i - 512-bit message input
|
||||
// valid_i - input valid
|
||||
// ready_o - can accept new input
|
||||
// hash_o - 512-bit hash output (lower 256 for H/J)
|
||||
// valid_o - output valid
|
||||
// ready_i - consumer accepts output
|
||||
// valid_i / ready_o / hash_o[511:0] / valid_o / ready_i
|
||||
//
|
||||
// Interface (multi-block SHA3-256, active when mb_en=1):
|
||||
// mb_en - 1 selects the multi-block absorb path
|
||||
// mb_block_i - one pre-padded 1088-bit (136-byte) rate block, byte 0 in [7:0]
|
||||
// mb_valid_i - block valid
|
||||
// mb_ready_o - module can accept a block
|
||||
// mb_last_i - asserted with the final (already-padded) block
|
||||
// result reuses hash_o[255:0] / valid_o / ready_i
|
||||
|
||||
module sha3_top (
|
||||
input clk,
|
||||
@@ -27,7 +37,13 @@ module sha3_top (
|
||||
output ready_o,
|
||||
output [511:0] hash_o,
|
||||
output valid_o,
|
||||
input ready_i
|
||||
input ready_i,
|
||||
// --- multi-block SHA3-256 absorb (tie mb_en=0 to disable) ---
|
||||
input mb_en,
|
||||
input [1087:0] mb_block_i,
|
||||
input mb_valid_i,
|
||||
input mb_last_i,
|
||||
output mb_ready_o
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
@@ -67,6 +83,42 @@ module sha3_top (
|
||||
(mode == 2'b10) ? {{(1600-1088){1'b0}}, j_pad} :
|
||||
1600'd0;
|
||||
|
||||
// ================================================================
|
||||
// Multi-block SHA3-256 absorb FSM (active only when mb_en=1)
|
||||
//
|
||||
// Running sponge state mb_state_r (init 0). For each pre-padded
|
||||
// 1088-bit rate block: mb_state_r ^= block; mb_state_r = Keccak-p(...).
|
||||
// After the last block, squeeze 256 bits. The caller pads the final
|
||||
// block (SHA3-256 suffix 0x06 ... 0x80), so this FSM is pure absorb.
|
||||
// ================================================================
|
||||
localparam MB_IDLE = 2'd0; // ready for a block (or first block)
|
||||
localparam MB_PERMUTE = 2'd1; // keccak running on xored state
|
||||
localparam MB_DONE = 2'd2; // squeeze: present 256-bit digest
|
||||
|
||||
reg [1:0] mb_state, mb_state_next;
|
||||
reg [1599:0] mb_state_r; // running sponge state
|
||||
reg mb_last_r; // captured last-block flag
|
||||
reg [255:0] mb_digest_r; // latched 256-bit digest (sticky in MB_DONE)
|
||||
|
||||
// XOR the incoming block into the low 1088 bits (rate) of the state.
|
||||
wire [1599:0] mb_xored;
|
||||
assign mb_xored = mb_state_r ^ {{(1600-1088){1'b0}}, mb_block_i};
|
||||
|
||||
// Accept a block only in MB_IDLE while enabled.
|
||||
assign mb_ready_o = mb_en && (mb_state == MB_IDLE);
|
||||
wire mb_accept = mb_en && (mb_state == MB_IDLE) && mb_valid_i;
|
||||
wire mb_kc_valid = mb_accept; // start keccak on the accept cycle
|
||||
|
||||
always @(*) begin
|
||||
mb_state_next = mb_state;
|
||||
case (mb_state)
|
||||
MB_IDLE: if (mb_accept) mb_state_next = MB_PERMUTE;
|
||||
MB_PERMUTE: if (kc_valid_o) mb_state_next = mb_last_r ? MB_DONE : MB_IDLE;
|
||||
MB_DONE: if (ready_i) mb_state_next = MB_IDLE;
|
||||
default: mb_state_next = MB_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// Keccak core
|
||||
// ================================================================
|
||||
@@ -77,10 +129,14 @@ module sha3_top (
|
||||
/* verilator lint_on UNUSEDSIGNAL */
|
||||
wire kc_valid_o;
|
||||
|
||||
// Keccak input: multi-block xored state when mb_en, else single-block absorb.
|
||||
wire [1599:0] kc_state_i_mux;
|
||||
assign kc_state_i_mux = mb_en ? mb_xored : absorb_state;
|
||||
|
||||
keccak_core #(.ROUNDS(24)) u_keccak (
|
||||
.clk (clk),
|
||||
.rst_n (rst_n),
|
||||
.state_i (absorb_state),
|
||||
.state_i (kc_state_i_mux),
|
||||
.valid_i (kc_valid_i),
|
||||
.ready_o (kc_ready_o), // unused but must connect
|
||||
.state_o (kc_state_o),
|
||||
@@ -91,14 +147,10 @@ module sha3_top (
|
||||
// ================================================================
|
||||
// FSM combinational logic
|
||||
// ================================================================
|
||||
assign ready_o = (state_r == ST_IDLE);
|
||||
assign ready_o = !mb_en && (state_r == ST_IDLE);
|
||||
|
||||
// kc_valid_i: start keccak_core during IDLE when input accepted.
|
||||
// Driven from state_next to avoid NBA latency: when state_r==IDLE
|
||||
// and valid_i→1, state_next=PERMUTE immediately (combinational).
|
||||
// keccak_core sees valid_i=1 on the stable cycle before the posedge.
|
||||
// On subsequent PERMUTE cycles, busy_r blocks re-start.
|
||||
assign kc_valid_i = (state_next == ST_PERMUTE);
|
||||
// kc_valid_i: single-block start (state_next==PERMUTE) OR multi-block accept.
|
||||
assign kc_valid_i = mb_en ? mb_kc_valid : (state_next == ST_PERMUTE);
|
||||
|
||||
always @(*) begin
|
||||
state_next = state_r;
|
||||
@@ -116,8 +168,9 @@ module sha3_top (
|
||||
// Register for squeezed output (only 512 bits needed)
|
||||
reg [511:0] squeezed_state_r;
|
||||
|
||||
assign valid_o = (state_r == ST_SQUEEZE);
|
||||
assign hash_o = squeezed_state_r;
|
||||
// valid_o / hash_o serve both paths, selected by mb_en.
|
||||
assign valid_o = mb_en ? (mb_state == MB_DONE) : (state_r == ST_SQUEEZE);
|
||||
assign hash_o = mb_en ? {256'b0, mb_digest_r} : squeezed_state_r;
|
||||
|
||||
// ================================================================
|
||||
// Sequential logic
|
||||
@@ -127,13 +180,35 @@ module sha3_top (
|
||||
if (!rst_n) begin
|
||||
state_r <= ST_IDLE;
|
||||
squeezed_state_r <= 512'd0;
|
||||
mb_state <= MB_IDLE;
|
||||
mb_state_r <= 1600'd0;
|
||||
mb_last_r <= 1'b0;
|
||||
mb_digest_r <= 256'd0;
|
||||
end else begin
|
||||
state_r <= state_next;
|
||||
state_r <= state_next;
|
||||
mb_state <= mb_state_next;
|
||||
|
||||
// Latch squeezed output when keccak_core finishes
|
||||
// --- single-block: latch squeezed output ---
|
||||
if (state_r == ST_PERMUTE && kc_valid_o) begin
|
||||
squeezed_state_r <= kc_state_o[511:0];
|
||||
end
|
||||
|
||||
// --- multi-block: capture last flag on accept ---
|
||||
if (mb_accept) begin
|
||||
mb_last_r <= mb_last_i;
|
||||
end
|
||||
|
||||
// --- multi-block: latch permuted state when keccak finishes ---
|
||||
if (mb_state == MB_PERMUTE && kc_valid_o) begin
|
||||
mb_state_r <= kc_state_o;
|
||||
// On the final block, latch the 256-bit digest (sticky for MB_DONE).
|
||||
if (mb_last_r) mb_digest_r <= kc_state_o[255:0];
|
||||
end
|
||||
|
||||
// --- multi-block: clear running state after digest consumed ---
|
||||
if (mb_state == MB_DONE && ready_i) begin
|
||||
mb_state_r <= 1600'd0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user