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 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. 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 ffsampling_fft(t, T, sigmin, randombytes): """Compute the ffsampling of t, using T as auxilary information. Args: t: a vector T: a ldl decomposition tree Format: FFT Corresponds to algorithm 11 (ffSampling) of Falcon's documentation. """ n = len(t[0]) * fft_ratio z = [0, 0] if (n > 1): l10, T0, T1 = T z[1] = merge_fft( ffsampling_fft(split_fft(t[1]), T1, sigmin, randombytes)) t0b = add_fft(t[0], mul_fft(sub_fft(t[1], z[1]), l10)) z[0] = merge_fft( ffsampling_fft(split_fft(t0b), T0, sigmin, randombytes)) return z elif (n == 1): z[0] = [samplerz(t[0][0].real, T[0], sigmin, randombytes)] z[1] = [samplerz(t[1][0].real, T[0], sigmin, randombytes)] return z
def ffnp_fft(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: FFT """ m = len(t) n = len(t[0]) * fft_ratio 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_fft(tib, mul_fft(sub_fft(t[j], z[j]), L[j][i])) # Recursive call z[i] = merge_fft(ffnp_fft(split_fft(tib), T[i + 1])) return z # Bottom case: round each coefficient in parallel elif (n == 1): z[0] = [round(t[0][0].real)] z[1] = [round(t[1][0].real)] return z
def ffnp_fft(t, T): """Compute the ffnp reduction of t, using T as auxilary information. Args: t: a vector T: a ldl decomposition tree Format: FFT """ n = len(t[0]) * fft_ratio z = [0, 0] if (n > 1): l10, T0, T1 = T z[1] = merge_fft(ffnp_fft(split_fft(t[1]), T1)) t0b = add_fft(t[0], mul_fft(sub_fft(t[1], z[1]), l10)) z[0] = merge_fft(ffnp_fft(split_fft(t0b), T0)) return z elif (n == 1): z[0] = [round(t[0][0].real)] z[1] = [round(t[1][0].real)] return z
def ffsampling_fft(t, T): """ Compute the fast Fourier sampling 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: FFT This algorithim is a randomized version of ffnp_fft, such that z * B is distributed as a spherical Gaussian centered around t * B. """ m = len(t) n = len(t[0]) * fft_ratio 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_fft(tib, mul_fft(sub_fft(t[j], z[j]), L[j][i])) # Recursive call z[i] = merge_fft(ffsampling_fft(split_fft(tib), T[i + 1])) return z # Bottom case: round each coefficient in parallel elif (n == 1): z[0] = [sampler_z(T[0], t[0][0].real)] z[1] = [sampler_z(T[0], t[1][0].real)] return z