def gs_norm(f, g, q): """Compute the squared Gram-Schmidt norm of the NTRU matrix generated by f, g. This matrix is [[g, - f], [G, - F]]. This algorithm is equivalent to line 9 of algorithm 5 (NTRUGen). """ sqnorm_fg = sqnorm([f, g]) ffgg = add(mul(f, adj(f)), mul(g, adj(g))) Ft = div(adj(g), ffgg) Gt = div(adj(f), ffgg) sqnorm_FG = (q**2) * sqnorm([Ft, Gt]) return max(sqnorm_fg, sqnorm_FG)
def ldl(G): """ Compute the LDL decomposition of G. Input: G A self-adjoint matrix (i.e. G is equal to its conjugate transpose) Output: L, D The LDL decomposition of G, that is G = L * D * (L*), where: - L is lower triangular with a diagonal of 1's - D is diagonal Format: Coefficient """ deg = len(G[0][0]) dim = len(G) L = [[[0 for k in range(deg)] for j in range(dim)] for i in range(dim)] D = [[[0 for k in range(deg)] for j in range(dim)] for i in range(dim)] for i in range(dim): L[i][i] = [1] + [0 for j in range(deg - 1)] D[i][i] = G[i][i] for j in range(i): L[i][j] = G[i][j] for k in range(j): L[i][j] = sub(L[i][j], mul(mul(L[i][k], adj(L[j][k])), D[k][k])) L[i][j] = div(L[i][j], D[j][j]) D[i][i] = sub(D[i][i], mul(mul(L[i][j], adj(L[i][j])), D[j][j])) return [L, D]
def ldl(G): """ Compute the LDL decomposition of G. Only works with 2 * 2 matrices. Args: G: a Gram matrix Format: coefficient Corresponds to algorithm 8 (LDL*) of Falcon's documentation, except it's in polynomial representation. """ deg = len(G[0][0]) dim = len(G) assert (dim == 2) assert (dim == len(G[0])) zero = [0] * deg one = [1] + [0] * (deg - 1) D00 = G[0][0][:] L10 = div(G[1][0], G[0][0]) D11 = sub(G[1][1], mul(mul(L10, adj(L10)), G[0][0])) L = [[one, zero], [L10, one]] D = [[D00, zero], [zero, D11]] return [L, D]
def ldl(G): """Compute the LDL decomposition of G. Args: G: a Gram matrix Format: coefficient Corresponds to algorithm 14 (LDL) of Falcon's documentation, except it's in polynomial representation. """ deg = len(G[0][0]) dim = len(G) L = [[[0 for k in range(deg)] for j in range(dim)] for i in range(dim)] D = [[[0 for k in range(deg)] for j in range(dim)] for i in range(dim)] for i in range(dim): L[i][i] = [1] + [0 for j in range(deg - 1)] D[i][i] = G[i][i] for j in range(i): L[i][j] = G[i][j] for k in range(j): L[i][j] = sub(L[i][j], mul(mul(L[i][k], adj(L[j][k])), D[k][k])) L[i][j] = div(L[i][j], D[j][j]) D[i][i] = sub(D[i][i], mul(mul(L[i][j], adj(L[i][j])), D[j][j])) return [L, D]
def test_fft(n, iterations=10): """Test the FFT.""" for i in range(iterations): f = [randint(-3, 4) for j in range(n)] g = [randint(-3, 4) for j in range(n)] h = mul(f, g) k = div(h, f) k = [int(round(elt)) for elt in k] if k != g: print("(f * g) / f =", k) print("g =", g) print("mismatch") return False return True
def module_ntru_gen(d, q, m): """ Take as input system parameters, and output two "module-NTRU" matrices A and B such that: - B * A = 0 [mod q] - B has small polynomials - A is in Hermite normal form Also compute the inverse of B (over the field K = Q[x] / (x ** d + 1)). Input: d The degree of the underlying ring R = Z[x] / (x ** d + 1) q An integer m An integer Output: A A matrix in R ^ ((m + 1) x 1) B A matrix in R ^ ((m + 1) x (m + 1)) inv_B A matrix in K ^ ((m + 1) x (m + 1)) sq_gs_norm A real number, the square of the Gram-Schmidt norm of B Format: Coefficient """ if m == 1: magic_constant = [1.15] gs_slack = 1.17 elif m == 2: magic_constant = [1.07, 1.14] gs_slack = 1.17 elif m == 3: magic_constant = [1.21, 1.10, 1.06] gs_slack = 1.24 else: print("No parameters implemented yet for m = {m}".format(m=m)) return max_gs_norm = gs_slack * (q ** (1 / (m + 1))) while True: # We generate all rows of B except the last B = [[None for j in range(m + 1)] for i in range(m + 1)] for i in range(m): for j in range(m + 1): # Each coefficient B[i][j] is a polynomial sigma = magic_constant[i] * (q ** (1 / (m + 1))) # ==> ||bi~|| = gs_slack * q^(1/(m+1)) sig = sqrt(1 / (d * (m + 1 - i))) * sigma # sig = stdv. dev. des coef de bi B[i][j] = [int(round(gauss(0, sig))) for k in range(d)] # We check that the GS norm is not larger than tolerated Bp_fft = [[fft(poly) for poly in row] for row in B[:-1]] Gp = gram_fft(Bp_fft) [Lp_fft, Dp_fft] = ldl_fft(Gp) Dp = [[[0] * d for col in range(m)] for row in range(m)] for i in range(m): Dp[i][i] = ifft(Dp_fft[i][i]) prod_di = [1] + [0] * (d - 1) for i in range(m): prod_di = mul(prod_di, Dp[i][i]) last = div([q ** 2] + [0] * (d - 1), prod_di) norms = [Dp[i][i][0] for i in range(m)] + [last[0]] # If the GS norm is too large, restart if sqrt(max(norms)) > max_gs_norm: continue # Try to solve the module-NTRU equation f = submatrix(B, m, 0) f = [[neg(elt) for elt in row] for row in f] g = [B[j][0] for j in range(m)] fp = my_det(f) adjf = my_adjugate(f) gp = [0] * d for i in range(m): gp = add(gp, karamul(adjf[0][i], g[i])) try: # Compute f^(-1) mod q fp_q = [elt % q for elt in fp] inv_f = [[elt[:] for elt in row] for row in adjf] for i in range(m): for j in range(m): inv_f[i][j] = [elt % q for elt in inv_f[i][j]] inv_f[i][j] = div_zq(inv_f[i][j], fp_q, q) # Compute h = f^(-1) * g mod q and A = [1 | h] h = [None] * m for i in range(m): elt = [0] * d for j in range(m): elt = add_zq(elt, mul_zq(inv_f[i][j], g[j], q), q) h[i] = elt one = [1] + [0 for _ in range(1, d)] A = [one] + h Fp, Gp = ntru_solve(fp, gp, q) B[m][0] = Gp B[m][1] = [- coef for coef in Fp] for i in range(2, m + 1): B[m][i] = [0 for _ in range(d)] # Compute the inverse of B det_B = my_det(B) inv_B = my_adjugate(B) inv_B = [[div(poly, det_B) for poly in row] for row in inv_B] return A, B, inv_B, max(norms) # If any step failed, restart except (ZeroDivisionError, ValueError): continue