feat(sha3_chain): add simple SHA3_G integration test
Phase 3.2: Verifies module chaining works. - sha3_chain_top.v: 3-state FSM (IDLE→BUSY→DONE), feeds d_in→sha3_top(G) - Captures rho[255:0] and sigma[511:256] from SHA3-512 output - Verified: 3/3 bit-exact vs Python G(d||k=2) reference KG full-path FSM (~11 module chain) deferred — too complex for single dispatch.
This commit is contained in:
126
test_framework/modules/sha3_chain/gen_vectors.py
Normal file
126
test_framework/modules/sha3_chain/gen_vectors.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""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')
|
||||
Reference in New Issue
Block a user