def ldl_fft(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: FFT """ 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 for j in range(deg)] 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_fft( L[i][j], mul_fft(mul_fft(L[i][k], adj_fft(L[j][k])), D[k][k])) L[i][j] = div_fft(L[i][j], D[j][j]) D[i][i] = sub_fft( D[i][i], mul_fft(mul_fft(L[i][j], adj_fft(L[i][j])), D[j][j])) return [L, D]
def ldl_fft(G): """Compute the LDL decomposition of G. Args: G: a Gram matrix Format: FFT Corresponds to algorithm 14 (LDL) of Falcon's documentation. """ 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 for j in range(deg)] 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_fft( L[i][j], mul_fft(mul_fft(L[i][k], adj_fft(L[j][k])), D[k][k])) L[i][j] = div_fft(L[i][j], D[j][j]) D[i][i] = sub_fft( D[i][i], mul_fft(mul_fft(L[i][j], adj_fft(L[i][j])), D[j][j])) return [L, D]
def reduce(f, g, F, G): """ Reduce (F, G) relatively to (f, g). This is done via Babai's reduction. (F, G) <-- (F, G) - k * (f, g), where k = round((F f* + G g*) / (f f* + g g*)). Corresponds to algorithm 7 (Reduce) of Falcon's documentation. """ n = len(f) size = max(53, bitsize(min(f)), bitsize(max(f)), bitsize(min(g)), bitsize(max(g))) f_adjust = [elt >> (size - 53) for elt in f] g_adjust = [elt >> (size - 53) for elt in g] fa_fft = fft(f_adjust) ga_fft = fft(g_adjust) while (1): # Because we work in finite precision to reduce very large polynomials, # we may need to perform the reduction several times. Size = max(53, bitsize(min(F)), bitsize(max(F)), bitsize(min(G)), bitsize(max(G))) if Size < size: break F_adjust = [elt >> (Size - 53) for elt in F] G_adjust = [elt >> (Size - 53) for elt in G] Fa_fft = fft(F_adjust) Ga_fft = fft(G_adjust) den_fft = add_fft(mul_fft(fa_fft, adj_fft(fa_fft)), mul_fft(ga_fft, adj_fft(ga_fft))) num_fft = add_fft(mul_fft(Fa_fft, adj_fft(fa_fft)), mul_fft(Ga_fft, adj_fft(ga_fft))) k_fft = div_fft(num_fft, den_fft) k = ifft(k_fft) k = [int(round(elt)) for elt in k] if all(elt == 0 for elt in k): break # The two next lines are the costliest operations in ntru_gen # (more than 75% of the total cost in dimension n = 1024). # There are at least two ways to make them faster: # - replace Karatsuba with Toom-Cook # - mutualized Karatsuba, see ia.cr/2020/268 # For simplicity reasons, we didn't implement these optimisations here. fk = karamul(f, k) gk = karamul(g, k) for i in range(n): F[i] -= fk[i] << (Size - size) G[i] -= gk[i] << (Size - size) return F, G
def reduce(f, g, F, G): """ Reduce (F, G) relatively to (f, g). This is done via Babai's reduction. (F, G) <-- (F, G) - k * (f, g), where k = round((F f* + G g*) / (f f* + g g*)). Similar to algorithm Reduce of Falcon's documentation. Input: f, g, F, G Four polynomials mod (x ** n + 1) Output: None The inputs are reduced as detailed above. Format: Coefficient """ n = len(f) size = max(53, bitsize(min(f)), bitsize(max(f)), bitsize(min(g)), bitsize(max(g))) f_adjust = [elt >> (size - 53) for elt in f] g_adjust = [elt >> (size - 53) for elt in g] fa_fft = fft(f_adjust) ga_fft = fft(g_adjust) while(1): # Because we are working in finite precision to reduce very large polynomials, # we may need to perform the reduction several times. Size = max(53, bitsize(min(F)), bitsize(max(F)), bitsize(min(G)), bitsize(max(G))) if Size < size: break F_adjust = [elt >> (Size - 53) for elt in F] G_adjust = [elt >> (Size - 53) for elt in G] Fa_fft = fft(F_adjust) Ga_fft = fft(G_adjust) den_fft = add_fft(mul_fft(fa_fft, adj_fft(fa_fft)), mul_fft(ga_fft, adj_fft(ga_fft))) num_fft = add_fft(mul_fft(Fa_fft, adj_fft(fa_fft)), mul_fft(Ga_fft, adj_fft(ga_fft))) k_fft = div_fft(num_fft, den_fft) k = ifft(k_fft) k = [int(round(elt)) for elt in k] if all(elt == 0 for elt in k): break fk = karamul(f, k) gk = karamul(g, k) for i in range(n): F[i] -= fk[i] << (Size - size) G[i] -= gk[i] << (Size - size) return F, G
def ldl_fft(G): """ Compute the LDL decomposition of G. Only works with 2 * 2 matrices. Args: G: a Gram matrix Format: FFT Corresponds to algorithm 8 (LDL*) of Falcon's documentation. """ deg = len(G[0][0]) dim = len(G) assert (dim == 2) assert (dim == len(G[0])) zero = [0] * deg one = [1] * deg D00 = G[0][0][:] L10 = div_fft(G[1][0], G[0][0]) D11 = sub_fft(G[1][1], mul_fft(mul_fft(L10, adj_fft(L10)), G[0][0])) L = [[one, zero], [L10, one]] D = [[D00, zero], [zero, D11]] return [L, D]
def ffldl_fft(G): """ Compute the ffLDL decomposition tree of G. Input: G A Gram matrix Output: T The ffLDL decomposition tree of G Format: FFT Similar to algorithm ffLDL of Falcon's documentation. """ m = len(G) - 1 d = len(G[0][0]) * fft_ratio # LDL decomposition L, D = ldl_fft(G) # General case if (d > 2): rep = [L] for i in range(m + 1): # Split the output d0, d1 = split_fft(D[i][i]) Gi = [[d0, d1], [adj_fft(d1), d0]] # Recursive call on the split parts rep += [ffldl_fft(Gi)] return rep # Bottom case elif (d == 2): # Each element is real return [L, D[0][0], D[1][1]]
def ffldl_fft(G): """Compute the ffLDL decomposition tree of G. Args: G: a Gram matrix Format: FFT Corresponds to algorithm 9 (ffLDL) of Falcon's documentation. """ n = len(G[0][0]) * fft_ratio L, D = ldl_fft(G) # Coefficients of L, D are elements of R[x]/(x^n - x^(n/2) + 1), in FFT representation if (n > 2): # A bisection is done on elements of a 2*2 diagonal matrix. d00, d01 = split_fft(D[0][0]) d10, d11 = split_fft(D[1][1]) G0 = [[d00, d01], [adj_fft(d01), d00]] G1 = [[d10, d11], [adj_fft(d11), d10]] return [L[1][0], ffldl_fft(G0), ffldl_fft(G1)] elif (n == 2): # End of the recursion (each element is real). return [L[1][0], D[0][0], D[1][1]]
def gram_fft(B): """ Compute the Gram matrix of B. Input: B A matrix Output: G The Gram matrix of B: G = B * (B*) Format: FFT """ 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_fft(G[i][j], mul_fft(B[i][k], adj_fft(B[j][k]))) return G