feat(poly_mul): implement synchronous PolyMul with base-case multiply
Phase 2.2: NTT-domain polynomial pointwise multiplication. - basecase_mul.v: degree-1 base-case multiply (c0,c1) with Barrett - poly_mul_zeta_rom.v: 128-entry zeta ROM for PolyMul - poly_mul_sync.v: FSM (IDLE→LOAD 256 cycles→COMPUTE 256 cycles→DONE) Verified: 5/5 vectors bit-exact vs Python PolyMul reference
This commit is contained in:
128
test_framework/modules/poly_mul/gen_vectors.py
Normal file
128
test_framework/modules/poly_mul/gen_vectors.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""gen_vectors.py - Test vector generator for poly_mul module.
|
||||
|
||||
Generates random NTT-domain polynomial pairs and computes expected
|
||||
pointwise base-case multiplication results using embedded Python reference.
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))
|
||||
from vector_gen import VectorGenerator
|
||||
|
||||
Q = 3329
|
||||
N = 256
|
||||
|
||||
# Bit-reversed zeta values (same as NTT zeta_bitRev)
|
||||
zeta_bitRev = [1, 1729, 2580, 3289, 2642, 630, 1897, 848,
|
||||
1062, 1919, 193, 797, 2786, 3260, 569, 1746,
|
||||
296, 2447, 1339, 1476, 3046, 56, 2240, 1333,
|
||||
1426, 2094, 535, 2882, 2393, 2879, 1974, 821,
|
||||
289, 331, 3253, 1756, 1197, 2304, 2277, 2055,
|
||||
650, 1977, 2513, 632, 2865, 33, 1320, 1915,
|
||||
2319, 1435, 807, 452, 1438, 2868, 1534, 2402,
|
||||
2647, 2617, 1481, 648, 2474, 3110, 1227, 910,
|
||||
17, 2761, 583, 2649, 1637, 723, 2288, 1100,
|
||||
1409, 2662, 3281, 233, 756, 2156, 3015, 3050,
|
||||
1703, 1651, 2789, 1789, 1847, 952, 1461, 2687,
|
||||
939, 2308, 2437, 2388, 733, 2337, 268, 641,
|
||||
1584, 2298, 2037, 3220, 375, 2549, 2090, 1645,
|
||||
1063, 319, 2773, 757, 2099, 561, 2466, 2594,
|
||||
2804, 1092, 403, 1026, 1143, 2150, 2775, 886,
|
||||
1722, 1212, 1874, 1029, 2110, 2935, 885, 2154]
|
||||
|
||||
# Precomputed PolyMul zeta values: zeta_sq_mul[i] = (zeta_bitRev[i]^2 * 17) % Q
|
||||
zeta_sq_mul = [((z * z) * 17) % Q for z in zeta_bitRev]
|
||||
|
||||
|
||||
def basecase_multiply(a0, a1, b0, b1, zeta):
|
||||
"""Degree-1 NTT-domain base-case multiply.
|
||||
|
||||
Computes:
|
||||
c0 = (a0*b0 + a1*b1*zeta) mod Q
|
||||
c1 = (a0*b1 + a1*b0) mod Q
|
||||
|
||||
Uses modular arithmetic to match Barrett reduction semantics.
|
||||
"""
|
||||
c0 = ((a0 * b0) % Q + (((a1 * b1) % Q) * zeta) % Q) % Q
|
||||
c1 = ((a0 * b1) % Q + (a1 * b0) % Q) % Q
|
||||
return c0, c1
|
||||
|
||||
|
||||
def poly_mul(f_hat, g_hat):
|
||||
"""Full NTT-domain polynomial multiplication.
|
||||
|
||||
Given two 256-coefficient NTT-domain polynomials f_hat and g_hat,
|
||||
compute pointwise product using 128 degree-1 base-case multiplies.
|
||||
|
||||
Args:
|
||||
f_hat: list of 256 coefficients in [0, Q-1]
|
||||
g_hat: list of 256 coefficients in [0, Q-1]
|
||||
|
||||
Returns:
|
||||
list of 256 result coefficients in [0, Q-1]
|
||||
"""
|
||||
h_hat = []
|
||||
for i in range(128):
|
||||
zeta = zeta_sq_mul[i]
|
||||
h1, h2 = basecase_multiply(
|
||||
f_hat[2 * i], f_hat[2 * i + 1],
|
||||
g_hat[2 * i], g_hat[2 * i + 1],
|
||||
zeta
|
||||
)
|
||||
h_hat.append(h1)
|
||||
h_hat.append(h2)
|
||||
return h_hat
|
||||
|
||||
|
||||
class PolyMulVectorGenerator(VectorGenerator):
|
||||
"""Generates test vectors for the poly_mul_sync module."""
|
||||
|
||||
def generate_one(self, params: dict) -> dict:
|
||||
"""Generate a random polynomial pair with expected result.
|
||||
|
||||
Args:
|
||||
params: Unused for basic case.
|
||||
|
||||
Returns:
|
||||
dict with 'input' (A coeffs, B coeffs) and 'expected' (C coeffs).
|
||||
"""
|
||||
# Generate random NTT-domain polynomials
|
||||
A = [random.randint(0, Q - 1) for _ in range(N)]
|
||||
B = [random.randint(0, Q - 1) for _ in range(N)]
|
||||
|
||||
# Compute expected result via Python reference
|
||||
C = poly_mul(A, B)
|
||||
|
||||
return {
|
||||
'input': {'A': A, 'B': B},
|
||||
'expected': {'C': C}
|
||||
}
|
||||
|
||||
def _format_coeffs(self, coeffs: list[int]) -> str:
|
||||
"""Format a coefficient list as space-separated 3-digit hex."""
|
||||
return ' '.join(f'{c:03X}' for c in coeffs)
|
||||
|
||||
def write_hex_file(self, vectors: list[dict], filepath: str) -> None:
|
||||
"""Write input vectors: one line per vector with 512 hex values.
|
||||
|
||||
Format: A[0] A[1] ... A[255] B[0] B[1] ... B[255]
|
||||
"""
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, 'w') as f:
|
||||
for v in vectors:
|
||||
all_coeffs = v['input']['A'] + v['input']['B']
|
||||
hex_str = self._format_coeffs(all_coeffs)
|
||||
f.write(f'{hex_str}\n')
|
||||
|
||||
def write_expected_file(self, vectors: list[dict], filepath: str) -> None:
|
||||
"""Write expected output: one line per vector with 256 hex values.
|
||||
|
||||
Format: C[0] C[1] ... C[255]
|
||||
"""
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, 'w') as f:
|
||||
for v in vectors:
|
||||
hex_str = self._format_coeffs(v['expected']['C'])
|
||||
f.write(f'{hex_str}\n')
|
||||
Reference in New Issue
Block a user