fix(sample_ntt,sha3): FIPS-203 SHAKE-128 squeeze + self-checking sha3 TBs
sample_ntt was non-conformant: both RTL and the test reference re-ran
keccak_p after every 3-byte squeeze instead of consuming the full
1344-bit SHAKE-128 rate. Only coeff[0] matched a standard sampler, so
the generated A matrix would not interoperate with any compliant ML-KEM.
- sample_ntt_sync{,_shared}.v: walk all 56 groups of the rate block via
grp_ptr_r; re-permute only when the block is exhausted. Verified
256/256 against ml-kem-r Rust sample_ntt on two seeds, and 1536/1536
in the Verilator framework (runtime ~128x faster per poly).
- gen_vectors.py: use a self-contained hashlib.shake_128 oracle.
sha3 testbench fixes (all now self-check hash_o against verified vectors,
cross-checked with hashlib and ml-kem-r mlkem_G):
- tb_sha3_xsim_simple.v: test G/H/J modes, not just G.
- tb_keccak_core_xsim.v: correct the wrong EXPECTED_STATE constant
(RTL was correct; lane0 = 0xf1258f7940e1dde7 per FIPS 202).
- tb_sha3_xsim.v: read expected file and self-check per vector; add
vectors/g_basic_{input,expected}.hex (3 G / 2 H / 2 J).
Remove stale sha3_chain test (its RTL was deleted in 1cace51) and its
README references. Extend .gitignore for XSIM artifacts and result dumps.
This commit is contained in:
17
.gitignore
vendored
17
.gitignore
vendored
@@ -10,6 +10,23 @@ __pycache__/
|
||||
test_framework/reports/report_*.html
|
||||
test_framework/modules/*/vectors/*.hex
|
||||
|
||||
# XSIM testbench result dumps (regenerated by simulation)
|
||||
sync_rtl/**/TB/vectors/*_result.hex
|
||||
|
||||
# Vivado XSIM simulation artifacts
|
||||
xsim.dir/
|
||||
*.jou
|
||||
*.log
|
||||
*.pb
|
||||
*.wdb
|
||||
*.backup.jou
|
||||
*.backup.log
|
||||
.Xil/
|
||||
webtalk*.jou
|
||||
webtalk*.log
|
||||
xelab.pb
|
||||
xvlog.pb
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -33,8 +33,6 @@ mlkem/
|
||||
│ │ ├── keccak_round.v # Single Keccak-f round (θ,ρ,π,χ,ι)
|
||||
│ │ ├── keccak_core.v # 24-round sequential Keccak-f[1600] core
|
||||
│ │ └── sha3_top.v # SHA3-512(G)/SHA3-256(H)/SHAKE-256(J) wrapper
|
||||
│ ├── sha3_chain/ # G function for key generation
|
||||
│ │ └── sha3_chain_top.v # SHA3-512 chain: G(d||k=2) → rho, sigma
|
||||
│ ├── rng/ # Pseudorandom number generator
|
||||
│ │ └── rng_sync.v # 256-bit Galois LFSR (taps: 255,253,252,247,0)
|
||||
│ ├── ntt/ # Number Theoretic Transform
|
||||
@@ -157,7 +155,7 @@ export LD_PRELOAD=/usr/lib64/libtinfo.so.5 # ncurses fix for 2019.2 on modern L
|
||||
|
||||
# Run all modules
|
||||
for m in mod_add rng poly_arith comp_decomp storage \
|
||||
sha3_chain ntt poly_mul sample_cbd sample_ntt; do
|
||||
ntt poly_mul sample_cbd sample_ntt; do
|
||||
./run_tb.sh "$m"
|
||||
done
|
||||
```
|
||||
@@ -236,7 +234,6 @@ A single Keccak-f[1600] permutation engine is shared across all SHA-3/SHAKE modu
|
||||
| `poly_mul_sync` | 512×coeff → 256×coeff | ~300 cycles | NTT-domain poly multiply |
|
||||
| `comp_decomp_sync` | coeff_in[11:0], d[4:0] → coeff_out | 1 cycle | Compress/Decompress |
|
||||
| `sha3_top` | data_i[511:0], mode → hash_o[511:0] | ~24 cycles | SHA3/SHAKE |
|
||||
| `sha3_chain_top` | d_in[255:0], start → rho, sigma | ~24 cycles | G function |
|
||||
| `sample_cbd_sync` | seed[255:0], nonce, eta → 256×coeff | ~300 cycles | CBD sampling |
|
||||
| `sample_ntt_sync` | rho[255:0], k,i,j → 256×coeff | ~4000 cycles | SampleNTT |
|
||||
| `s_bram` | rd/wr addr, data | 1 cycle | Single-port BRAM |
|
||||
@@ -248,7 +245,6 @@ A single Keccak-f[1600] permutation engine is shared across all SHA-3/SHAKE modu
|
||||
|--------|:---:|:---:|:---:|
|
||||
| sha3_top | ✅ | ✅ | PASS |
|
||||
| keccak_core | — | ✅ | PASS |
|
||||
| sha3_chain_top | ✅ | ✅ | PASS |
|
||||
| rng_sync | ✅ | ✅ | PASS |
|
||||
| mod_add_sync | ✅ | ✅ | PASS |
|
||||
| ntt_core | ✅ | ✅ | PASS |
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
// Generates one k×k polynomial (256 coefficients) via SHAKE-128 XOF
|
||||
// rejection sampling from seed rho || j || i.
|
||||
//
|
||||
// Matches Python reference (sample.py/SHA_3.py) bit-exactly:
|
||||
// FIPS 202/203 conformant SHAKE-128 squeeze:
|
||||
// - Absorb: S = Keccak-p(padded(rho || j || i))
|
||||
// - For each squeeze: take S[23:0] (3 bytes), extract d1[11:0], d2[23:12]
|
||||
// - Squeeze the full 1344-bit (168-byte) rate as 56 groups of 3 bytes,
|
||||
// each group g read from S[24*g +: 24]; extract d1[11:0], d2[23:12]
|
||||
// - Accept d if d < Q=3329
|
||||
// - S = Keccak-p(S) (permute between every 3-byte squeeze)
|
||||
// - Repeat until 256 coefficients collected
|
||||
// - Only after all 56 groups of the block are consumed: S = Keccak-p(S)
|
||||
// (re-permute once per rate block, NOT per 3-byte group)
|
||||
// - Repeat until 256 coefficients collected (~3 blocks)
|
||||
//
|
||||
// Parameters:
|
||||
// K = 4 (ML-KEM parameter)
|
||||
@@ -52,6 +54,10 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
// ================================================================
|
||||
localparam Q = `Q; // 3329
|
||||
|
||||
// SHAKE-128 rate = 1344 bits = 168 bytes = 56 groups of 3 bytes.
|
||||
// After consuming all 56 groups of a block, re-permute the state.
|
||||
localparam GRP_MAX = 6'd55;
|
||||
|
||||
// ================================================================
|
||||
// FSM state encoding
|
||||
// ================================================================
|
||||
@@ -84,6 +90,13 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
// ================================================================
|
||||
reg [1599:0] squeeze_state_r;
|
||||
|
||||
// ================================================================
|
||||
// Group pointer: which 3-byte group within the 1344-bit rate
|
||||
// block is currently being consumed (0..GRP_MAX). Re-permute when
|
||||
// it would exceed GRP_MAX.
|
||||
// ================================================================
|
||||
reg [5:0] grp_ptr_r;
|
||||
|
||||
// ================================================================
|
||||
// Registered d1, d2 and acceptance flags
|
||||
// ================================================================
|
||||
@@ -121,15 +134,21 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
// Comb: extract d1,d2 from squeeze state
|
||||
// Comb: extract d1,d2 from the current 3-byte group of squeeze state
|
||||
// ================================================================
|
||||
// squeeze_state_r[7:0]=c0, [15:8]=c1, [23:16]=c2
|
||||
// group g occupies bits [24*g +: 24]: c0=byte0, c1=byte1, c2=byte2
|
||||
// d1 = {c1[3:0], c0}
|
||||
// d2 = {c2, c1[7:4]}
|
||||
wire [10:0] grp_bit_off;
|
||||
assign grp_bit_off = grp_ptr_r * 11'd24; // 0..1320, +24 ≤ 1344 (rate)
|
||||
|
||||
wire [23:0] grp_bits;
|
||||
assign grp_bits = squeeze_state_r[ grp_bit_off +: 24 ];
|
||||
|
||||
wire [7:0] c0, c1, c2;
|
||||
assign c0 = squeeze_state_r[7:0];
|
||||
assign c1 = squeeze_state_r[15:8];
|
||||
assign c2 = squeeze_state_r[23:16];
|
||||
assign c0 = grp_bits[7:0];
|
||||
assign c1 = grp_bits[15:8];
|
||||
assign c2 = grp_bits[23:16];
|
||||
|
||||
wire [11:0] d1_comb, d2_comb;
|
||||
assign d1_comb = {c1[3:0], c0};
|
||||
@@ -161,10 +180,10 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
.ready_i (1'b1)
|
||||
);
|
||||
|
||||
// kc_valid_i: asserted during ABSORB and first phase of SQUEEZE.
|
||||
// Keccak captures it on the transition (when ready_o=1).
|
||||
// kc_valid_i: asserted on the ABSORB load, and for one cycle when the
|
||||
// squeeze block is exhausted (SQUEEZE → WAIT) to re-permute the state.
|
||||
assign kc_valid_i = (state_next == ST_ABSORB) ||
|
||||
(state_r == ST_SQUEEZE && sq_phase_r == 2'd0);
|
||||
(state_r == ST_SQUEEZE && state_next == ST_WAIT);
|
||||
|
||||
// kc_state_i: absorb_state in ABSORB, squeeze_state_r otherwise
|
||||
assign kc_state_i = (state_next == ST_ABSORB) ? absorb_state : squeeze_state_r;
|
||||
@@ -186,6 +205,11 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
assign ready_o = (state_r == ST_IDLE);
|
||||
wire need_more = (coeff_cnt_r < 9'd256);
|
||||
|
||||
// grp_done: current 3-byte group fully consumed (phase 2 resolved):
|
||||
// d2 rejected, no longer need coefficients, or d2 was just accepted out.
|
||||
wire grp_done = (state_r == ST_SQUEEZE) && (sq_phase_r == 2'd2) &&
|
||||
(!d2_acc_r || !need_more || (valid_o_r && ready_i));
|
||||
|
||||
// ================================================================
|
||||
// FSM: state_next (combinational)
|
||||
// ================================================================
|
||||
@@ -205,11 +229,18 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
end
|
||||
|
||||
ST_SQUEEZE: begin
|
||||
// Sub-phase transitions managed in sequential logic.
|
||||
// Only transitions to ST_WAIT from phase 2 when done.
|
||||
if (sq_phase_r == 2'd2 &&
|
||||
(!d2_acc_r || !need_more || (valid_o_r && ready_i)))
|
||||
state_next = ST_WAIT;
|
||||
// A group is fully consumed once phase 2 resolves (d2 output,
|
||||
// rejected, or no longer needed). Then either advance to the
|
||||
// next group in this block, re-permute (block exhausted), or
|
||||
// finish.
|
||||
if (grp_done) begin
|
||||
if (!need_more)
|
||||
state_next = ST_DONE;
|
||||
else if (grp_ptr_r < GRP_MAX)
|
||||
state_next = ST_SQUEEZE; // next group, no re-permute
|
||||
else
|
||||
state_next = ST_WAIT; // block exhausted: re-permute
|
||||
end
|
||||
end
|
||||
|
||||
ST_WAIT: begin
|
||||
@@ -239,6 +270,7 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
sq_phase_r <= 2'd0;
|
||||
coeff_cnt_r <= 9'd0;
|
||||
squeeze_state_r <= 1600'd0;
|
||||
grp_ptr_r <= 6'd0;
|
||||
d1_r <= 12'd0;
|
||||
d2_r <= 12'd0;
|
||||
d1_acc_r <= 1'b0;
|
||||
@@ -265,10 +297,12 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
end
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Latch keccak output when valid_o fires
|
||||
// Latch keccak output when valid_o fires. A fresh block
|
||||
// starts at group 0 (ABSORB load or WAIT re-permute result).
|
||||
// ---------------------------------------------------------
|
||||
if (kc_valid_o) begin
|
||||
squeeze_state_r <= kc_state_o;
|
||||
grp_ptr_r <= 6'd0;
|
||||
end
|
||||
|
||||
// ---------------------------------------------------------
|
||||
@@ -347,6 +381,13 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
valid_o_r <= 1'b0;
|
||||
end
|
||||
endcase
|
||||
|
||||
// Group consumed but block not exhausted: advance to the
|
||||
// next 3-byte group within the same block (no re-permute).
|
||||
if (grp_done && need_more && grp_ptr_r < GRP_MAX) begin
|
||||
grp_ptr_r <= grp_ptr_r + 6'd1;
|
||||
sq_phase_r <= 2'd0;
|
||||
end
|
||||
end else if (state_r != ST_SQUEEZE && state_next == ST_SQUEEZE) begin
|
||||
// About to enter SQUEEZE: reset phase and output
|
||||
sq_phase_r <= 2'd0;
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
// Generates one k×k polynomial (256 coefficients) via SHAKE-128 XOF
|
||||
// rejection sampling from seed rho || j || i.
|
||||
//
|
||||
// Matches Python reference (sample.py/SHA_3.py) bit-exactly:
|
||||
// FIPS 202/203 conformant SHAKE-128 squeeze:
|
||||
// - Absorb: S = Keccak-p(padded(rho || j || i))
|
||||
// - For each squeeze: take S[23:0] (3 bytes), extract d1[11:0], d2[23:12]
|
||||
// - Squeeze the full 1344-bit (168-byte) rate as 56 groups of 3 bytes,
|
||||
// each group g read from S[24*g +: 24]; extract d1[11:0], d2[23:12]
|
||||
// - Accept d if d < Q=3329
|
||||
// - S = Keccak-p(S) (permute between every 3-byte squeeze)
|
||||
// - Repeat until 256 coefficients collected
|
||||
// - Only after all 56 groups of the block are consumed: S = Keccak-p(S)
|
||||
// (re-permute once per rate block, NOT per 3-byte group)
|
||||
// - Repeat until 256 coefficients collected (~3 blocks)
|
||||
//
|
||||
// Parameters:
|
||||
// K = 4 (ML-KEM parameter)
|
||||
@@ -75,6 +77,10 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
// ================================================================
|
||||
localparam Q = `Q; // 3329
|
||||
|
||||
// SHAKE-128 rate = 1344 bits = 168 bytes = 56 groups of 3 bytes.
|
||||
// After consuming all 56 groups of a block, re-permute the state.
|
||||
localparam GRP_MAX = 6'd55;
|
||||
|
||||
// ================================================================
|
||||
// FSM state encoding
|
||||
// ================================================================
|
||||
@@ -107,6 +113,12 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
// ================================================================
|
||||
reg [1599:0] squeeze_state_r;
|
||||
|
||||
// ================================================================
|
||||
// Group pointer: which 3-byte group within the 1344-bit rate
|
||||
// block is currently being consumed (0..GRP_MAX).
|
||||
// ================================================================
|
||||
reg [5:0] grp_ptr_r;
|
||||
|
||||
// ================================================================
|
||||
// Registered d1, d2 and acceptance flags
|
||||
// ================================================================
|
||||
@@ -144,15 +156,21 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
// Comb: extract d1,d2 from squeeze state
|
||||
// Comb: extract d1,d2 from the current 3-byte group of squeeze state
|
||||
// ================================================================
|
||||
// squeeze_state_r[7:0]=c0, [15:8]=c1, [23:16]=c2
|
||||
// group g occupies bits [24*g +: 24]: c0=byte0, c1=byte1, c2=byte2
|
||||
// d1 = {c1[3:0], c0}
|
||||
// d2 = {c2, c1[7:4]}
|
||||
wire [10:0] grp_bit_off;
|
||||
assign grp_bit_off = grp_ptr_r * 11'd24; // 0..1320, +24 ≤ 1344 (rate)
|
||||
|
||||
wire [23:0] grp_bits;
|
||||
assign grp_bits = squeeze_state_r[ grp_bit_off +: 24 ];
|
||||
|
||||
wire [7:0] c0, c1, c2;
|
||||
assign c0 = squeeze_state_r[7:0];
|
||||
assign c1 = squeeze_state_r[15:8];
|
||||
assign c2 = squeeze_state_r[23:16];
|
||||
assign c0 = grp_bits[7:0];
|
||||
assign c1 = grp_bits[15:8];
|
||||
assign c2 = grp_bits[23:16];
|
||||
|
||||
wire [11:0] d1_comb, d2_comb;
|
||||
assign d1_comb = {c1[3:0], c0};
|
||||
@@ -176,9 +194,10 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
// kc_ready_i: always ready to accept keccak output
|
||||
assign kc_ready_i = 1'b1;
|
||||
|
||||
// kc_valid_i: asserted during ABSORB and first phase of SQUEEZE.
|
||||
// kc_valid_i: asserted on the ABSORB load, and for one cycle when the
|
||||
// squeeze block is exhausted (SQUEEZE → WAIT) to re-permute the state.
|
||||
assign kc_valid_i = (state_next == ST_ABSORB) ||
|
||||
(state_r == ST_SQUEEZE && sq_phase_r == 2'd0);
|
||||
(state_r == ST_SQUEEZE && state_next == ST_WAIT);
|
||||
|
||||
// kc_state_i: absorb_state in ABSORB, squeeze_state_r otherwise
|
||||
assign kc_state_i = (state_next == ST_ABSORB) ? absorb_state : squeeze_state_r;
|
||||
@@ -200,6 +219,11 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
assign ready_o = (state_r == ST_IDLE);
|
||||
wire need_more = (coeff_cnt_r < 9'd256);
|
||||
|
||||
// grp_done: current 3-byte group fully consumed (phase 2 resolved):
|
||||
// d2 rejected, no longer need coefficients, or d2 was just accepted out.
|
||||
wire grp_done = (state_r == ST_SQUEEZE) && (sq_phase_r == 2'd2) &&
|
||||
(!d2_acc_r || !need_more || (valid_o_r && ready_i));
|
||||
|
||||
// ================================================================
|
||||
// FSM: state_next (combinational)
|
||||
// ================================================================
|
||||
@@ -219,11 +243,18 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
end
|
||||
|
||||
ST_SQUEEZE: begin
|
||||
// Sub-phase transitions managed in sequential logic.
|
||||
// Only transitions to ST_WAIT from phase 2 when done.
|
||||
if (sq_phase_r == 2'd2 &&
|
||||
(!d2_acc_r || !need_more || (valid_o_r && ready_i)))
|
||||
state_next = ST_WAIT;
|
||||
// A group is fully consumed once phase 2 resolves (d2 output,
|
||||
// rejected, or no longer needed). Then either advance to the
|
||||
// next group in this block, re-permute (block exhausted), or
|
||||
// finish.
|
||||
if (grp_done) begin
|
||||
if (!need_more)
|
||||
state_next = ST_DONE;
|
||||
else if (grp_ptr_r < GRP_MAX)
|
||||
state_next = ST_SQUEEZE; // next group, no re-permute
|
||||
else
|
||||
state_next = ST_WAIT; // block exhausted: re-permute
|
||||
end
|
||||
end
|
||||
|
||||
ST_WAIT: begin
|
||||
@@ -253,6 +284,7 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
sq_phase_r <= 2'd0;
|
||||
coeff_cnt_r <= 9'd0;
|
||||
squeeze_state_r <= 1600'd0;
|
||||
grp_ptr_r <= 6'd0;
|
||||
d1_r <= 12'd0;
|
||||
d2_r <= 12'd0;
|
||||
d1_acc_r <= 1'b0;
|
||||
@@ -279,10 +311,12 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
end
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// Latch keccak output when valid_o fires
|
||||
// Latch keccak output when valid_o fires. A fresh block
|
||||
// starts at group 0 (ABSORB load or WAIT re-permute result).
|
||||
// ---------------------------------------------------------
|
||||
if (kc_valid_o) begin
|
||||
squeeze_state_r <= kc_state_o;
|
||||
grp_ptr_r <= 6'd0;
|
||||
end
|
||||
|
||||
// ---------------------------------------------------------
|
||||
@@ -361,6 +395,13 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
valid_o_r <= 1'b0;
|
||||
end
|
||||
endcase
|
||||
|
||||
// Group consumed but block not exhausted: advance to the
|
||||
// next 3-byte group within the same block (no re-permute).
|
||||
if (grp_done && need_more && grp_ptr_r < GRP_MAX) begin
|
||||
grp_ptr_r <= grp_ptr_r + 6'd1;
|
||||
sq_phase_r <= 2'd0;
|
||||
end
|
||||
end else if (state_r != ST_SQUEEZE && state_next == ST_SQUEEZE) begin
|
||||
// About to enter SQUEEZE: reset phase and output
|
||||
sq_phase_r <= 2'd0;
|
||||
|
||||
@@ -53,9 +53,11 @@ module tb_keccak_core_xsim;
|
||||
//
|
||||
// Keccak-f[1600](0) after 24 rounds, flat 1600-bit output.
|
||||
// Verilog packing: {lane24, lane23, ..., lane1, lane0}
|
||||
// where lane[i] = A[i%5][i/5] at bits [i*64+63 : i*64]
|
||||
// where lane[i] = A[i%5][i/5] at bits [i*64+63 : i*64].
|
||||
// lane0 = 0xf1258f7940e1dde7 is the well-known FIPS 202 test value;
|
||||
// independently recomputed and confirmed against the RTL output.
|
||||
// ================================================================
|
||||
parameter [1599:0] EXPECTED_STATE = 1600'hbfb7e1b9bd5d5750a1ad89d6b16dd89e76c82a7b8b784ff1a8f71cbe511a4b37593f83e2476e446a9607dab51d2284543912af66d5169a42b000c95e3f38a1d14df070f6937de8028965308c22a1d7ed39beddcf42fc7c09f566afaa29ffc221e0bedc7fe0a51684906ebd59a992e4c2e88479b3c8e88c45bfe624f5737b96a5d0656897dda87cafe0f3909e35059e7a83831c8c135d1f2ac3c03f088c216b4c4a445d8c512ddea81c7cc6d86579ec7d3ca0d28f00c7d66020fb5a92a1a94488625f47811fa2dc9d;
|
||||
parameter [1599:0] EXPECTED_STATE = 1600'heaf1ff7b5ceca24975f644e97f30a13b16f53526e70465c21841f924a2c509e4940c7922ae3a26148c3ee88a1ccf32c8b87c5a554fd00ecb613670957bc4661164befef28cc970f205e5635a21d9ae6101f22f1a11a5569f43b831cd0347c82681a57c16dbcf555fa9a6e6260d712103eb5aa93f2317d63530935ab7d08ffc64ad30a6f71b19059c8c5bda0cd6192e7690fee5a0a44647c4ff97a42d7f8e6fd48b284e056253d057bd1547306f80494dd598261ea65aa9ee84d5ccf933c0478af1258f7940e1dde7;
|
||||
|
||||
// ================================================================
|
||||
// Test sequence
|
||||
|
||||
@@ -26,6 +26,7 @@ module tb_sha3_xsim;
|
||||
// Parameters
|
||||
// ================================================================
|
||||
parameter VECTOR_FILE = "sync_rtl/sha3/TB/vectors/g_basic_input.hex";
|
||||
parameter EXPECTED_FILE = "sync_rtl/sha3/TB/vectors/g_basic_expected.hex";
|
||||
parameter RESULT_FILE = "sync_rtl/sha3/TB/vectors/g_basic_result.hex";
|
||||
parameter MAX_VECTORS = 256;
|
||||
parameter TIMEOUT_CYCLES = 1000;
|
||||
@@ -69,6 +70,7 @@ module tb_sha3_xsim;
|
||||
// 520 bits per word: bits[519:512]=padding+mode, bits[511:0]=data_i
|
||||
// ================================================================
|
||||
reg [519:0] vector_mem [0:MAX_VECTORS-1];
|
||||
reg [511:0] expected_mem [0:MAX_VECTORS-1]; // expected hash per vector
|
||||
integer vec_count;
|
||||
integer idx;
|
||||
integer cycle_count;
|
||||
@@ -100,6 +102,8 @@ module tb_sha3_xsim;
|
||||
|
||||
// Load vectors from hex file
|
||||
$readmemh(VECTOR_FILE, vector_mem);
|
||||
// Load expected hashes (one 512-bit hex per line, MSB-first)
|
||||
$readmemh(EXPECTED_FILE, expected_mem);
|
||||
|
||||
// Count non-zero entries to determine actual vector count
|
||||
// (XSim leaves unloaded entries as 520'hX)
|
||||
@@ -180,9 +184,29 @@ module tb_sha3_xsim;
|
||||
$display("ERROR: Timeout waiting for valid_o on vector %0d", idx);
|
||||
fail_count = fail_count + 1;
|
||||
end else begin
|
||||
// Capture hash output
|
||||
// Capture hash output and self-check against expected.
|
||||
// G (mode 0) uses all 512 bits; H/J use the low 256 bits.
|
||||
captured_hash = hash_o;
|
||||
pass_count = pass_count + 1;
|
||||
|
||||
begin
|
||||
reg [511:0] exp_hash;
|
||||
reg match;
|
||||
exp_hash = expected_mem[idx];
|
||||
if (vec_mode == 2'd0)
|
||||
match = (captured_hash === exp_hash);
|
||||
else
|
||||
match = (captured_hash[255:0] === exp_hash[255:0]);
|
||||
|
||||
if (match) begin
|
||||
pass_count = pass_count + 1;
|
||||
$display("PASS: Vector %0d (mode=%0d)", idx, vec_mode);
|
||||
end else begin
|
||||
fail_count = fail_count + 1;
|
||||
$display("FAIL: Vector %0d (mode=%0d) hash mismatch", idx, vec_mode);
|
||||
$display(" got = %0h", (vec_mode==2'd0) ? captured_hash : {256'd0, captured_hash[255:0]});
|
||||
$display(" exp = %0h", (vec_mode==2'd0) ? exp_hash : {256'd0, exp_hash[255:0]});
|
||||
end
|
||||
end
|
||||
|
||||
// Write result to output file
|
||||
// Format: "RESULT: MODE HASH_HEX"
|
||||
@@ -216,6 +240,11 @@ module tb_sha3_xsim;
|
||||
$display(" Failed: %0d", fail_count);
|
||||
$display(" Results written to: %s", RESULT_FILE);
|
||||
$display("========================================");
|
||||
if (fail_count == 0)
|
||||
$display("ALL TESTS PASSED (%0d/%0d)", pass_count, vec_count);
|
||||
else
|
||||
$display("TESTS FAILED: %0d of %0d", fail_count, vec_count);
|
||||
$display("========================================");
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// tb_sha3_xsim_simple.v - Simple self-checking testbench for sha3_top
|
||||
// tb_sha3_xsim_simple.v - Self-checking testbench for sha3_top (G/H/J modes)
|
||||
//
|
||||
// Tests sha3_top in G mode (SHA3-512) with a hardcoded all-zero input.
|
||||
// Verifies the output hash against an expected value.
|
||||
// Uses $display for output and $error for mismatches.
|
||||
// Self-checking: pass/fail determined by $error count at $finish.
|
||||
// Drives sha3_top in all three modes and checks hash_o against expected
|
||||
// values computed by the verified Python reference (server_code SHA_3,
|
||||
// KAT-validated). Vectors are deterministic (gen_vectors seed=20260627).
|
||||
//
|
||||
// NOTE: This testbench uses the RTL's actual padding (suffix "10").
|
||||
// The expected hash was pre-computed using the same algorithm as the RTL.
|
||||
// G (mode=00, SHA3-512): data_i[263:0] = d||k, output 512 bits
|
||||
// H (mode=01, SHA3-256): data_i[255:0] = ek, output low 256 bits
|
||||
// J (mode=10, SHAKE-256): data_i[511:0] = z||c, output low 256 bits
|
||||
//
|
||||
// data_i / hash_o use MSB-first hex packing (hex[511:0] literal == data_i).
|
||||
//
|
||||
// Usage:
|
||||
// xvlog -sv sha3_top.v tb_sha3_xsim_simple.v
|
||||
@@ -52,111 +54,119 @@ module tb_sha3_xsim_simple;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
// ================================================================
|
||||
// Expected hash value for G mode with all-zero input
|
||||
//
|
||||
// Input: data_i[263:0] = 264'd0 (all zeros)
|
||||
// mode = 2'b00 (G mode, SHA3-512)
|
||||
//
|
||||
// RTL g_pad = {1'b1, 308'b0, 1'b1, 2'b10, data_i[263:0]}
|
||||
// absorb_state = {1024'b0, g_pad}
|
||||
//
|
||||
// Expected hash_o = Keccak-f[1600](absorb_state) lower 512 bits
|
||||
// Expected vectors (verified Python reference, seed=20260627)
|
||||
// ================================================================
|
||||
parameter [511:0] G_EXPECTED_HASH = 512'h93d50514dbf28b7f2b6aa4f34bc6bd53368a9a20c6568940dc8eb3ce0a8e357f8608c63ce7b579f6916c69ca3f196527ccc92b87c515edc12e159e0f3092e1d9;
|
||||
// G: data_i[263:0] = {k=2, d}; output 512-bit SHA3-512
|
||||
localparam [511:0] G_DATA = 512'h000000000000000000000000000000000000000000000000000000000000007DBC2AC0D13D719B37B4E2D4691951FF890A97854EF5D3A8957EF67A54978E26C9;
|
||||
localparam [511:0] G_EXP = 512'h615E530C77D5D834311E922DB99D5B7F1D57C7B08F029FD829914D3F4035FB730350FD00A852A5CCE9CFC79CF8C61384FEA115030E41750A6AE2CFE055D7976D;
|
||||
|
||||
// H: data_i[255:0] = ek; output 256-bit SHA3-256 (in hash_o[255:0])
|
||||
localparam [511:0] H_DATA = 512'h000000000000000000000000000000000000000000000000000000000000000047885BF4E257CF39645D34C593047B7570D6ABBA300599D96171A950BB5027D5;
|
||||
localparam [255:0] H_EXP = 256'h6B44867E24B29A9231570DF5E1D6D14DB0C29EBD7A40AE98606EF66B8244C308;
|
||||
|
||||
// J: data_i[511:0] = {c, z}; output 256-bit SHAKE-256 (in hash_o[255:0])
|
||||
localparam [511:0] J_DATA = 512'hB73DCA0F437D2334320494E5D0F728D73F5275E342572FF9B0219DC338CB3C2F0F7398474A3D68C4C90B777F42FA4C12B1FC8F70E1DAADF20755473CC2653D3C;
|
||||
localparam [255:0] J_EXP = 256'hECEC4DAD11DFF42D911925DB83F13D119209AB4EE182E9E9BA0F29F5524B240E;
|
||||
|
||||
// ================================================================
|
||||
// Test sequence
|
||||
// ================================================================
|
||||
reg [511:0] captured_hash;
|
||||
integer error_count;
|
||||
integer cycle_count;
|
||||
parameter TIMEOUT = 200;
|
||||
|
||||
// ================================================================
|
||||
// Drive one mode and self-check against expected output
|
||||
// ================================================================
|
||||
task run_mode(input [1:0] m, input [511:0] din,
|
||||
input [511:0] exp, input integer out_bits,
|
||||
input [127:0] label);
|
||||
reg [511:0] got;
|
||||
begin
|
||||
@(posedge clk);
|
||||
mode <= m;
|
||||
data_i <= din;
|
||||
valid_i <= 1'b1;
|
||||
@(posedge clk);
|
||||
valid_i <= 1'b0;
|
||||
|
||||
cycle_count = 0;
|
||||
while (!valid_o && cycle_count < TIMEOUT) begin
|
||||
@(posedge clk);
|
||||
cycle_count = cycle_count + 1;
|
||||
end
|
||||
|
||||
if (cycle_count >= TIMEOUT) begin
|
||||
$display("FAIL [%0s]: TIMEOUT, valid_o not asserted", label);
|
||||
error_count = error_count + 1;
|
||||
end else begin
|
||||
got = hash_o;
|
||||
if (out_bits == 512) begin
|
||||
if (got !== exp) begin
|
||||
$display("FAIL [%0s]: hash mismatch", label);
|
||||
$display(" got = 512'h%0h", got);
|
||||
$display(" exp = 512'h%0h", exp);
|
||||
error_count = error_count + 1;
|
||||
end else begin
|
||||
$display("PASS [%0s]: hash_o = 512'h%0h", label, got);
|
||||
end
|
||||
end else begin
|
||||
// Compare low 256 bits only
|
||||
if (got[255:0] !== exp[255:0]) begin
|
||||
$display("FAIL [%0s]: hash mismatch", label);
|
||||
$display(" got = 256'h%0h", got[255:0]);
|
||||
$display(" exp = 256'h%0h", exp[255:0]);
|
||||
error_count = error_count + 1;
|
||||
end else begin
|
||||
$display("PASS [%0s]: hash_o = 256'h%0h", label, got[255:0]);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Wait for DUT to return to IDLE
|
||||
@(posedge clk);
|
||||
while (!ready_o) @(posedge clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
// ================================================================
|
||||
// Test sequence
|
||||
// ================================================================
|
||||
initial begin
|
||||
error_count = 0;
|
||||
|
||||
$display("========================================");
|
||||
$display(" SHA3 Top Simple Self-Checking Testbench");
|
||||
$display(" Mode: G (SHA3-512)");
|
||||
$display(" Input: data_i = 512'd0");
|
||||
$display(" SHA3 Top Testbench (G / H / J modes)");
|
||||
$display("========================================");
|
||||
|
||||
// Initialize
|
||||
mode = 2'd0; // G mode
|
||||
data_i = 512'd0;
|
||||
valid_i = 1'b0;
|
||||
ready_i = 1'b1; // always ready
|
||||
mode <= 2'd0;
|
||||
data_i <= 512'd0;
|
||||
valid_i <= 1'b0;
|
||||
ready_i <= 1'b1;
|
||||
|
||||
// Reset: rst_n low for 3 cycles
|
||||
rst_n = 1'b0;
|
||||
rst_n <= 1'b0;
|
||||
repeat (3) @(posedge clk);
|
||||
rst_n = 1'b1;
|
||||
rst_n <= 1'b1;
|
||||
@(posedge clk);
|
||||
|
||||
$display("INFO: Reset complete. Starting test...");
|
||||
$display("INFO: Reset complete. Running G/H/J...");
|
||||
|
||||
// Drive test vector
|
||||
mode = 2'd0;
|
||||
data_i = 512'd0;
|
||||
valid_i = 1'b1;
|
||||
@(posedge clk);
|
||||
valid_i = 1'b0;
|
||||
run_mode(2'd0, G_DATA, G_EXP, 512, "G SHA3-512");
|
||||
run_mode(2'd1, H_DATA, {256'd0,H_EXP},256, "H SHA3-256");
|
||||
run_mode(2'd2, J_DATA, {256'd0,J_EXP},256, "J SHAKE-256");
|
||||
|
||||
$display("INFO: Vector driven (mode=G, data=0). Waiting for valid_o...");
|
||||
|
||||
// Wait for valid_o
|
||||
cycle_count = 0;
|
||||
while (!valid_o && cycle_count < TIMEOUT) begin
|
||||
@(posedge clk);
|
||||
cycle_count = cycle_count + 1;
|
||||
end
|
||||
|
||||
if (cycle_count >= TIMEOUT) begin
|
||||
$error("TIMEOUT: valid_o not asserted within %0d cycles", TIMEOUT);
|
||||
error_count = error_count + 1;
|
||||
end else begin
|
||||
captured_hash = hash_o;
|
||||
|
||||
$display("INFO: valid_o asserted after %0d cycles", cycle_count + 1);
|
||||
$display("INFO: hash_o = 512'h%0h", captured_hash);
|
||||
|
||||
// Check against expected
|
||||
if (captured_hash !== G_EXPECTED_HASH) begin
|
||||
$error("MISMATCH!");
|
||||
$display(" Expected: 512'h%0h", G_EXPECTED_HASH);
|
||||
$display(" Got: 512'h%0h", captured_hash);
|
||||
error_count = error_count + 1;
|
||||
end else begin
|
||||
$display("PASS: hash_o matches expected value.");
|
||||
end
|
||||
end
|
||||
|
||||
// One extra cycle
|
||||
@(posedge clk);
|
||||
|
||||
// ============================================================
|
||||
// Summary
|
||||
// ============================================================
|
||||
$display("========================================");
|
||||
if (error_count == 0) begin
|
||||
$display("ALL TESTS PASSED");
|
||||
end else begin
|
||||
$display("TESTS FAILED: %0d error(s)", error_count);
|
||||
end
|
||||
$display("========================================");
|
||||
|
||||
// Vivado xsim: $finish with error code
|
||||
if (error_count > 0)
|
||||
$finish;
|
||||
if (error_count == 0)
|
||||
$display("ALL TESTS PASSED (3/3 modes)");
|
||||
else
|
||||
$finish;
|
||||
$display("TESTS FAILED: %0d error(s)", error_count);
|
||||
$display("========================================");
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// Timeout watchdog
|
||||
// ================================================================
|
||||
initial begin
|
||||
#(TIMEOUT * 10 * 10); // TIMEOUT * 10ns * extra margin
|
||||
#(TIMEOUT * 10 * 100);
|
||||
$display("FATAL: Global simulation timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
7
sync_rtl/sha3/TB/vectors/g_basic_expected.hex
Normal file
7
sync_rtl/sha3/TB/vectors/g_basic_expected.hex
Normal file
@@ -0,0 +1,7 @@
|
||||
615E530C77D5D834311E922DB99D5B7F1D57C7B08F029FD829914D3F4035FB730350FD00A852A5CCE9CFC79CF8C61384FEA115030E41750A6AE2CFE055D7976D
|
||||
5A7DEC7ECB7A1217993059A6CBAF7754BF774311A936963A10568900EEE6DB3BDAB89C84A5650189B0E8B2526BE916FDA070A0AB2E33399590B87D315C55D5CB
|
||||
BE71BF8A954846FB70D52DE11DCC189E25B6A931E63BD2A3BB546E4C02A8929500F1AE56BFAF3D61ACF85ECFA2B5D8E02778CBBED34D22552A371B4DE938ED6B
|
||||
0000000000000000000000000000000000000000000000000000000000000000A63A98E0A2FB57548A6906708813766DA0F1E9ACF3831A6E77C4A56227E9473E
|
||||
0000000000000000000000000000000000000000000000000000000000000000CFB62681BD3A993605FB6C80D93E8A308C5494EF0641F92C18F83002453A8FA2
|
||||
0000000000000000000000000000000000000000000000000000000000000000B09665ECDF8E2934BC244DF46E36AF3B3DB2C18524300A896BE8837558BEE367
|
||||
0000000000000000000000000000000000000000000000000000000000000000EB253D3B38BBF839B448BBC0149ED56E12E4EE72D31DE12D881994CA16AA0C2A
|
||||
7
sync_rtl/sha3/TB/vectors/g_basic_input.hex
Normal file
7
sync_rtl/sha3/TB/vectors/g_basic_input.hex
Normal file
@@ -0,0 +1,7 @@
|
||||
00000000000000000000000000000000000000000000000000000000000000007DBC2AC0D13D719B37B4E2D4691951FF890A97854EF5D3A8957EF67A54978E26C9
|
||||
0000000000000000000000000000000000000000000000000000000000000000C247885BF4E257CF39645D34C593047B7570D6ABBA300599D96171A950BB5027D5
|
||||
0000000000000000000000000000000000000000000000000000000000000000B038CB3C2F0F7398474A3D68C4C90B777F42FA4C12B1FC8F70E1DAADF20755473C
|
||||
010000000000000000000000000000000000000000000000000000000000000000F9D94DE0F1EF4D2CB73DCA0F437D2334320494E5D0F728D73F5275E342572FF9
|
||||
0100000000000000000000000000000000000000000000000000000000000000008A1733CED3BC820A5D410AD00B0E4ACB0C260A8EBCD1B70FECF85B59256609CB
|
||||
02CC2F7B1D60813526FB623E09F10D22635605046A66888277667DACCA02B420BBE795DC9A6DA171DA1DE082111568D55A8A27FE9B009275761098B44B484B3684
|
||||
02E94BA9958BE80EDB47A9C83C07DBE09EA32CF4B1A59AD014938117DA74B87E9840E880E1A6F0C9E69C5E78D61A357D30ECC92FE5890DF046D93D94E87EA4ECB0
|
||||
@@ -1,9 +1,11 @@
|
||||
"""gen_vectors.py - Test vector generator for sample_ntt module.
|
||||
|
||||
Generates random rho seeds, calls the Python reference sampleNTT to compute
|
||||
expected coefficients, and writes hex files for Verilator simulation.
|
||||
Generates random rho seeds, computes expected coefficients with a standard
|
||||
FIPS 202 SHAKE-128 rejection sampler (hashlib), and writes hex files for
|
||||
Verilator simulation.
|
||||
|
||||
Matches the Python reference (sample.py / SHA_3.py) bit-exactly.
|
||||
The expected coefficients are FIPS-203-conformant (Algorithm 7 SampleNTT):
|
||||
SHAKE-128(rho || j || i), consume the full rate, 12-bit rejection sampling.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -11,21 +13,36 @@ import random
|
||||
import sys
|
||||
import hashlib
|
||||
|
||||
# Add the Python reference implementation to path
|
||||
_REF_DIR = os.path.expanduser(
|
||||
"~/Dev/server_code/python_project/PQC_2025/A_ML_KEM_v0"
|
||||
)
|
||||
if _REF_DIR not in sys.path:
|
||||
sys.path.insert(0, _REF_DIR)
|
||||
|
||||
import utils
|
||||
import sample as sample_ref
|
||||
|
||||
# Add test_framework/lib to path for VectorGenerator base class
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "lib"))
|
||||
from vector_gen import VectorGenerator
|
||||
|
||||
|
||||
def _sample_ntt_ref(seed: bytes) -> list[int]:
|
||||
"""FIPS 203 Algorithm 7 SampleNTT using standard SHAKE-128.
|
||||
|
||||
Args:
|
||||
seed: 34-byte input = rho(32) || j(1) || i(1).
|
||||
|
||||
Returns:
|
||||
List of 256 coefficients in [0, 3329).
|
||||
"""
|
||||
# 1344 bytes = 8 full SHAKE-128 rate blocks; ample for 256 accepts.
|
||||
stream = hashlib.shake_128(seed).digest(1344)
|
||||
coeffs = []
|
||||
off = 0
|
||||
while len(coeffs) < 256:
|
||||
c0, c1, c2 = stream[off], stream[off + 1], stream[off + 2]
|
||||
off += 3
|
||||
d1 = c0 | ((c1 & 0x0F) << 8)
|
||||
d2 = (c1 >> 4) | (c2 << 4)
|
||||
if d1 < 3329:
|
||||
coeffs.append(d1)
|
||||
if d2 < 3329 and len(coeffs) < 256:
|
||||
coeffs.append(d2)
|
||||
return coeffs
|
||||
|
||||
|
||||
class SampleNTTVectorGenerator(VectorGenerator):
|
||||
"""Generates test vectors for the sample_ntt_sync module."""
|
||||
|
||||
@@ -40,25 +57,21 @@ class SampleNTTVectorGenerator(VectorGenerator):
|
||||
"""
|
||||
k = params.get("k", 2)
|
||||
|
||||
# Generate random 32-byte rho (as binary string, matching the reference)
|
||||
rho_bin = utils.random_Generator(8 * 32) # 256-bit binary string
|
||||
# Random 32-byte rho
|
||||
rho = bytes(random.randint(0, 255) for _ in range(32))
|
||||
|
||||
# Choose random (i, j) indices within [0, k-1]
|
||||
i = random.randint(0, k - 1)
|
||||
j = random.randint(0, k - 1)
|
||||
|
||||
# Build the 34-byte input for sampleNTT: rho || j || i
|
||||
# Each component is a binary string (LSB at index 0)
|
||||
s_j_bin = utils.dec_to_binary_little_endian(j) # 8-bit binary string
|
||||
s_i_bin = utils.dec_to_binary_little_endian(i) # 8-bit binary string
|
||||
B = rho_bin + s_j_bin + s_i_bin # 272-bit binary string
|
||||
# 34-byte SampleNTT input: rho || j || i (FIPS 203 Alg 13/7)
|
||||
seed = rho + bytes([j]) + bytes([i])
|
||||
|
||||
# Compute expected coefficients using Python reference
|
||||
coeffs = sample_ref.sampleNTT(B) # numpy array of 256 ints
|
||||
# Expected coefficients from a standard SHAKE-128 sampler
|
||||
coeffs = _sample_ntt_ref(seed)
|
||||
|
||||
# Convert rho to hex for the input file
|
||||
# binary_to_hex_little_endian produces MSB-first hex per byte
|
||||
rho_hex = utils.binary_to_hex_little_endian(rho_bin)
|
||||
# rho hex: byte 0 first (matches TB parse_rho byte ordering)
|
||||
rho_hex = rho.hex().upper()
|
||||
|
||||
return {
|
||||
"input": {
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
"""gen_vectors.py - Test vector generator for sha3_chain module.
|
||||
|
||||
Generates random 256-bit d, computes G(d||k=2) using Python reference,
|
||||
and outputs rho (first 256 bits) and sigma (next 256 bits).
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
# Add test_framework/lib to path for VectorGenerator base class
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))
|
||||
|
||||
# Add Python reference path
|
||||
_REF_PATH = os.path.expanduser(
|
||||
"~/Dev/server_code/python_project/PQC_2025/A_ML_KEM_v0")
|
||||
sys.path.insert(0, _REF_PATH)
|
||||
|
||||
import SHA_3
|
||||
|
||||
from vector_gen import VectorGenerator
|
||||
|
||||
|
||||
def _bits_to_hex(bits_str, num_bits):
|
||||
"""Convert binary string (LSB-first: bits_str[0] = bit 0) to hex.
|
||||
|
||||
Returns hex with MSB-nibble first (matching Verilog %X format).
|
||||
Each group of 4 bits forms a nibble: bit[i+3:i] → two hex chars.
|
||||
"""
|
||||
result = []
|
||||
for i in range(0, num_bits, 4):
|
||||
nib = 0
|
||||
for j in range(4):
|
||||
pos = i + j
|
||||
if pos < len(bits_str) and bits_str[pos] == '1':
|
||||
nib |= (1 << j)
|
||||
result.insert(0, '0123456789ABCDEF'[nib])
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
def _random_bits(length):
|
||||
"""Generate a random binary string of given length (LSB-first)."""
|
||||
val = random.getrandbits(length)
|
||||
bits = ''
|
||||
for i in range(length):
|
||||
bits += '1' if (val & (1 << i)) else '0'
|
||||
return bits
|
||||
|
||||
|
||||
def _k_bits(k_val):
|
||||
"""Convert integer k to 8-bit LSB-first binary string.
|
||||
|
||||
k=2 → "01000000" (bit 0=0, bit 1=1)
|
||||
"""
|
||||
bits = ''
|
||||
for i in range(8):
|
||||
bits += '1' if (k_val & (1 << i)) else '0'
|
||||
return bits
|
||||
|
||||
|
||||
class Sha3ChainVectorGenerator(VectorGenerator):
|
||||
"""Generates test vectors for sha3_chain_top module."""
|
||||
|
||||
def generate_one(self, params: dict) -> dict:
|
||||
"""Generate a single test vector.
|
||||
|
||||
Args:
|
||||
params: dict with 'k' key (default 2 for ML-KEM-512).
|
||||
|
||||
Returns:
|
||||
dict with 'input' and 'expected' keys.
|
||||
"""
|
||||
k_val = params.get('k', 2)
|
||||
|
||||
# Generate random 256-bit d (LSB-first binary string)
|
||||
d = _random_bits(256)
|
||||
k_str = _k_bits(k_val)
|
||||
|
||||
# Compute G(d, k): SHA3-512 of d||k (both LSB-first binary)
|
||||
expected_bits = SHA_3.G(d, k_str)
|
||||
|
||||
# rho = first 256 bits, sigma = next 256 bits
|
||||
rho_bits = expected_bits[0:256]
|
||||
sigma_bits = expected_bits[256:512]
|
||||
|
||||
# Convert to hex (MSB-first)
|
||||
d_hex = _bits_to_hex(d, 256)
|
||||
rho_hex = _bits_to_hex(rho_bits, 256)
|
||||
sigma_hex = _bits_to_hex(sigma_bits, 256)
|
||||
|
||||
return {
|
||||
'input': {
|
||||
'd': d_hex
|
||||
},
|
||||
'expected': {
|
||||
'rho': rho_hex,
|
||||
'sigma': sigma_hex
|
||||
}
|
||||
}
|
||||
|
||||
def write_hex_file(self, vectors: list[dict], filepath: str) -> None:
|
||||
"""Write input vectors: one 64-char d-hex per line.
|
||||
|
||||
Args:
|
||||
vectors: List of vector dicts from generate_one().
|
||||
filepath: Path to write the hex file.
|
||||
"""
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, 'w') as f:
|
||||
for v in vectors:
|
||||
d_hex = v['input']['d']
|
||||
f.write(f'{d_hex}\n')
|
||||
|
||||
def write_expected_file(self, vectors: list[dict], filepath: str) -> None:
|
||||
"""Write expected output: "RHO_HEX SIGMA_HEX" per line (128 chars).
|
||||
|
||||
Args:
|
||||
vectors: List of vector dicts from generate_one().
|
||||
filepath: Path to write the expected hex file.
|
||||
"""
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, 'w') as f:
|
||||
for v in vectors:
|
||||
rho_hex = v['expected']['rho']
|
||||
sigma_hex = v['expected']['sigma']
|
||||
f.write(f'{rho_hex} {sigma_hex}\n')
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"module": "sha3_chain",
|
||||
"rtl_top": "sync_rtl/sha3_chain/sha3_chain_top.v",
|
||||
"rtl_deps": ["sync_rtl/sha3/keccak_round.v", "sync_rtl/sha3/keccak_core.v", "sync_rtl/sha3/sha3_top.v"],
|
||||
"tb_cpp": "sync_rtl/sha3_chain/TB/tb_sha3_chain.cpp",
|
||||
"timeout_s": 60,
|
||||
"cases": [{
|
||||
"id": "basic",
|
||||
"description": "d → SHA3_G(d||k=2) → rho, sigma vs Python G()",
|
||||
"params": {"k": 2},
|
||||
"num_vectors": 3,
|
||||
"tolerance": "bit_exact"
|
||||
}]
|
||||
}
|
||||
Reference in New Issue
Block a user