def test_covariance(d, m, q, iterations=100): """ Compute the covariance matrix of the signatures distribution. For an isotropic Gaussian, the covariance matrix is proportional to the identity matrix. """ sk = SecretKey(d, m, q) dim = (m + 1) * d liste_sig = [] mean = [0] * dim Cov = [[0 for _ in range(dim)] for _ in range(dim)] for i in range(iterations): message = "0" r, t = sk.sign(message) s = decompress(t, sk.d, sk.rate) s = sum([elt for elt in s], []) mean = add(mean, s) liste_sig += [s] # print("mean = {mean}".format(mean=mean)) for s in liste_sig: s = [iterations * elt for elt in s] s = [(s[i] - mean[i]) for i in range(dim)] M = make_matrix(s) for i in range(dim): Cov[i] = add(Cov[i], M[i]) # We normalize only at the end to work with integers as long as possible for i in range(dim): for j in range(dim): Cov[i][j] /= (iterations**3) Cov[i][j] = int(round(Cov[i][j]))
def test_covariance(n, iterations=100): """ Compute the covariance matrix of the signatures distribution. For an isotropic Gaussian, the covariance matrix is proportional to the identity matrix. """ sk = SecretKey(n) liste_sig = [] mean = [0] * (2 * n) Cov = [[0 for _ in range(2 * n)] for _ in range(2 * n)] for i in range(iterations): message = "0" r, s = sk.sign(message) s = s[0] + s[1] mean = add(mean, s) liste_sig += [s] # mean = [elt / iterations for elt in mean] # print(liste_sig) print("mean = {mean}".format(mean=mean)) for s in liste_sig: s = [iterations * elt for elt in s] s = [(s[i] - mean[i]) for i in range(2 * n)] M = make_matrix(s) for i in range(2 * n): Cov[i] = add(Cov[i], M[i]) for i in range(2 * n): for j in range(2 * n): Cov[i][j] /= (iterations**3) print(Cov) return Cov
def vecvecmul(u, v, integer=False, modulus=None): """ Compute the product u * (v^t), u and v are row vectors, and v^t denotes the transose of v. Input: u A row vector v A row vector integer This flag should be set (to True) iff the elements are in Z[x] / (x ** n + 1) modulus This flag should be set (to q) iff the elements are in Z_q[x] / (x ** n + 1) Output: rep The product u * (v^t) = sum(u[i] * v[i] for i in range(len(u))) Format: Coefficient """ m = len(u) deg = len(u[0]) assert (len(u) == len(v)) rep = [0 for k in range(deg)] if modulus is not None: for i in range(m): rep = add_zq(rep, mul_zq(u[i], v[i], modulus), modulus) return rep else: for i in range(m): rep = add(rep, mul(u[i], v[i])) if integer is True: rep = [int(round(elt)) for elt in rep] return rep
def vecmatmul(t, B, integer=False, modulus=None): """ Compute the product t * B, where t is a vector and B is a square matrix. Input: t A row vector B A matrix integer This flag should be set (to True) iff the elements are in Z[x] / (x ** n + 1) modulus This flag should be set (to q) iff the elements are in Z_q[x] / (x ** n + 1) Output: v The row vector t * B Format: Coefficient """ nrows = len(B) ncols = len(B[0]) deg = len(B[0][0]) assert (len(t) == nrows) v = [[0 for k in range(deg)] for j in range(ncols)] if modulus is not None: for j in range(ncols): for i in range(nrows): v[j] = add_zq(v[j], mul_zq(t[i], B[i][j], modulus), modulus) return v else: for j in range(ncols): for i in range(nrows): v[j] = add(v[j], mul(t[i], B[i][j])) if integer is True: v = [[int(round(elt)) for elt in poly] for poly in v] return v
def my_det(M): """ Compute the determinant of M. If M has coefficients in a ring R, then the determinant is in R. This determinant is computed using Cramer's rule: this is less efficient than other methods but allows to do all operations in R. Input: M A matrix Output: determinant The determinant of M Format: Coefficient """ nrow = len(M) ncol = len(M[0]) d = len(M[0][0]) assert (nrow == ncol) if (nrow == 1): return M[0][0] else: determinant = [0] * d for i in range(nrow): Mp = submatrix(M, i, 0) det_Mp = my_det(Mp) if (i & 1): det_Mp = [- elt for elt in det_Mp] determinant = add(determinant, karamul(M[i][0], det_Mp)) return determinant
def ffnp(t, T): """ Compute the FFNP reduction of t, using T as auxilary information. Input: t A vector T The LDL decomposition tree of an (implicit) matrix G Output: z An integer vector such that (t - z) * B is short Format: Coefficient """ m = len(t) n = len(t[0]) z = [None] * m # General case if (n > 1): L = T[0] for i in range(m - 1, -1, -1): # t[i] is "corrected", taking into accounts the t[j], z[j] (j > i) tib = t[i][:] for j in range(m - 1, i, -1): tib = add(tib, mul(sub(t[j], z[j]), L[j][i])) # Recursive call z[i] = merge(ffnp(split(tib), T[i + 1])) return z # Bottom case: round each coefficient in parallel elif (n == 1): z[0] = [round(t[0][0])] z[1] = [round(t[1][0])] return z
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 gram(B): """Compute the Gram matrix of B. Args: B: a matrix Format: coefficient """ rows = range(len(B)) ncols = len(B[0]) deg = len(B[0][0]) G = [[[0 for coef in range(deg)] for j in rows] for i in rows] for i in rows: for j in rows: for k in range(ncols): G[i][j] = add(G[i][j], mul(B[i][k], adj(B[j][k]))) return G
def vecmatmul(t, B): """Compute the product t * B, where t is a vector and B is a square matrix. Args: B: a matrix Format: coefficient """ nrows = len(B) ncols = len(B[0]) deg = len(B[0][0]) assert(len(t) == nrows) v = [[0 for k in range(deg)] for j in range(ncols)] for j in range(ncols): for i in range(nrows): v[j] = add(v[j], mul(t[i], B[i][j])) return v
def gram(B): """ Compute the Gram matrix of B. Input: B A matrix Output: G The Gram matrix of B: G = B * (B*) Format: Coefficient """ rows = range(len(B)) ncols = len(B[0]) deg = len(B[0][0]) G = [[[0 for coef in range(deg)] for j in rows] for i in rows] for i in rows: for j in rows: for k in range(ncols): G[i][j] = add(G[i][j], mul(B[i][k], adj(B[j][k]))) return G
def ffnp(t, T): """Compute the ffnp reduction of t, using T as auxilary information. Args: t: a vector T: a ldl decomposition tree Format: coefficient """ n = len(t[0]) z = [None, None] if (n > 1): l10, T0, T1 = T z[1] = merge(ffnp(split(t[1]), T1)) t0b = add(t[0], mul(sub(t[1], z[1]), l10)) z[0] = merge(ffnp(split(t0b), T0)) return z elif (n == 1): z[0] = [round(t[0][0])] z[1] = [round(t[1][0])] return z
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