Files
mlkem-sync/test_framework/modules/sha3/gen_vectors.py
FallenSigh 453bc899fc feat(sha3): implement synchronous Keccak-f[1600] core with G/H/J modes
Phase 1.1 of ML-KEM sync rewrite.
- keccak_round.v: combinational theta/rho/pi/chi/iota
- keccak_core.v: 24-round pipeline, valid/ready
- sha3_top.v: sponge FSM, modes G(SHA3-512)/H(SHA3-256)/J(SHAKE-256)
- Verilator C++ TB + Python vector gen against reference
- Verified: 25/25 vectors bit-exact vs Python G()/H()/J()
2026-06-24 20:33:44 +08:00

196 lines
6.0 KiB
Python

"""gen_vectors.py - Test vector generator for sha3 module.
Generates vectors for G (SHA3-512), H (SHA3-256), J (SHAKE-256) modes
and computes expected outputs using the Python reference implementation.
"""
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")
_REF_PATH = os.path.expanduser(_REF_PATH)
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 _hex_to_bits(hex_str, num_bits):
"""Convert hex string (MSB-first) to binary string (LSB-first).
Returns a binary string where result[0] = bit 0 (LSB).
"""
hex_val = int(hex_str, 16)
bits = ''
for i in range(num_bits):
bits += '1' if (hex_val & (1 << i)) else '0'
return bits
def _random_bits(length):
"""Generate a random binary string of given length."""
val = random.getrandbits(length)
bits = ''
for i in range(length):
bits += '1' if (val & (1 << i)) else '0'
return bits
class Sha3VectorGenerator(VectorGenerator):
"""Generates test vectors for the sha3_top module."""
def generate_one(self, params: dict) -> dict:
"""Generate a single test vector.
For G mode:
- d: 256-bit random binary string
- k: 8-bit random binary string
- Input to RTL: d||k packed into 512-bit value (data_i[263:0])
- Expected: G(d, k) = 512-bit binary string
For H mode:
- ek: 256-bit random binary string (single-block)
- Input to RTL: ek in data_i[255:0]
- Expected: H(ek) = 256-bit binary string
For J mode:
- z: 256-bit random binary string
- c: 256-bit random binary string
- Input to RTL: z||c in data_i[511:0]
- Expected: J(z, c) = 256-bit binary string
Args:
params: dict with 'mode' key ('G', 'H', 'J').
Returns:
dict with 'input' and 'expected' keys.
"""
mode = params.get('mode', 'G')
if mode == 'G':
# d: 256 bits, k: 8 bits
d = _random_bits(256)
k = _random_bits(8)
msg = d + k # 264 bits, LSB first
# Pack into 512-bit hex value: data_i[511:0]
# pad msg to 512 bits with zeros (upper bits = 0)
msg_512 = msg + '0' * (512 - len(msg))
input_hex = _bits_to_hex(msg_512, 512)
# Expected: G(d, k) returns binary string
expected_bits = SHA_3.G(d, k)
expected_hex = _bits_to_hex(expected_bits, 512)
return {
'input': {
'mode': 0,
'data': input_hex
},
'expected': {
'hash': expected_hex
}
}
elif mode == 'H':
# ek: 256-bit random message (single-block test)
ek = _random_bits(256)
# Pack into 512-bit hex: data_i[511:0]
msg_512 = ek + '0' * (512 - len(ek))
input_hex = _bits_to_hex(msg_512, 512)
# Expected: H(ek) returns 256-bit binary string
expected_bits = SHA_3.H(ek)
expected_hex = _bits_to_hex(expected_bits, 256)
return {
'input': {
'mode': 1,
'data': input_hex
},
'expected': {
'hash': expected_hex
}
}
elif mode == 'J':
# z: 256 bits, c: 256 bits
z = _random_bits(256)
c = _random_bits(256)
msg = z + c # 512 bits
# Pack into 512-bit hex
input_hex = _bits_to_hex(msg, 512)
# Expected: J(z, c) returns 256-bit binary string
expected_bits = SHA_3.J(z, c)
expected_hex = _bits_to_hex(expected_bits, 256)
return {
'input': {
'mode': 2,
'data': input_hex
},
'expected': {
'hash': expected_hex
}
}
else:
raise ValueError(f"Unknown mode: {mode}")
def write_hex_file(self, vectors: list[dict], filepath: str) -> None:
"""Write input vectors as "MM DDDD..." hex format (mode, data).
Each line: "MM DDDD..." where MM is hex mode (00/01/10) and DDDD...
is the hex-encoded 512-bit message.
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:
mode_val = v['input']['mode']
data_hex = v['input']['data']
f.write(f'{mode_val:02X} {data_hex}\n')
def write_expected_file(self, vectors: list[dict], filepath: str) -> None:
"""Write expected output as hex strings, one per line.
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:
expected_hex = v['expected']['hash']
f.write(f'{expected_hex}\n')