def sample_preimage(self, point): """ Sample a short vector s such that s[0] + s[1] * h = point. """ [[a, b], [c, d]] = self.B0_fft # We compute a vector t_fft such that: # (fft(point), fft(0)) * B0_fft = t_fft # Because fft(0) = 0 and the inverse of B has a very specific form, # we can do several optimizations. point_fft = fft(point) t0_fft = [(point_fft[i] * d[i]) / q for i in range(self.n)] t1_fft = [(-point_fft[i] * b[i]) / q for i in range(self.n)] t_fft = [t0_fft, t1_fft] # We now compute v such that: # v = z * B0 for an integral vector z # v is close to (point, 0) z_fft = ffsampling_fft(t_fft, self.T_fft, self.sigmin) v0_fft = add_fft(mul_fft(z_fft[0], a), mul_fft(z_fft[1], c)) v1_fft = add_fft(mul_fft(z_fft[0], b), mul_fft(z_fft[1], d)) v0 = [int(round(elt)) for elt in ifft(v0_fft)] v1 = [int(round(elt)) for elt in ifft(v1_fft)] # The difference s = (point, 0) - v is such that: # s is short # s[0] + s[1] * h = point s = [sub(point, v0), neg(v1)] return s
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 sample_preimage_fft(self, point): """Sample preimage.""" B = self.B0_fft c = point, [0] * self.n t_fft = self.get_coord_in_fft(c) z_fft = ffsampling_fft(t_fft, self.T_fft) v0_fft = add_fft(mul_fft(z_fft[0], B[0][0]), mul_fft(z_fft[1], B[1][0])) v1_fft = add_fft(mul_fft(z_fft[0], B[0][1]), mul_fft(z_fft[1], B[1][1])) v0 = [int(round(elt)) for elt in ifft(v0_fft)] v1 = [int(round(elt)) for elt in ifft(v1_fft)] v = v0, v1 s = [sub(c[0], v[0]), sub(c[1], v[1])] return s
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 sample_preimage(self, point, seed=None): """ Sample a short vector s such that s[0] + s[1] * h = point. """ [[a, b], [c, d]] = self.B0_fft # We compute a vector t_fft such that: # (fft(point), fft(0)) * B0_fft = t_fft # Because fft(0) = 0 and the inverse of B has a very specific form, # we can do several optimizations. point_fft = fft(point) t0_fft = [(point_fft[i] * d[i]) / q for i in range(self.n)] t1_fft = [(-point_fft[i] * b[i]) / q for i in range(self.n)] t_fft = [t0_fft, t1_fft] # We now compute v such that: # v = z * B0 for an integral vector z # v is close to (point, 0) if seed is None: # If no seed is defined, use urandom as the pseudo-random source. z_fft = ffsampling_fft(t_fft, self.T_fft, self.sigmin, urandom) else: # If a seed is defined, initialize a ChaCha20 PRG # that is used to generate pseudo-randomness. chacha_prng = ChaCha20(seed) z_fft = ffsampling_fft(t_fft, self.T_fft, self.sigmin, chacha_prng.randombytes) v0_fft = add_fft(mul_fft(z_fft[0], a), mul_fft(z_fft[1], c)) v1_fft = add_fft(mul_fft(z_fft[0], b), mul_fft(z_fft[1], d)) v0 = [int(round(elt)) for elt in ifft(v0_fft)] v1 = [int(round(elt)) for elt in ifft(v1_fft)] # The difference s = (point, 0) - v is such that: # s is short # s[0] + s[1] * h = point s = [sub(point, v0), neg(v1)] return s
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
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 vecmatmul_fft(t, B): """ Compute the product t * B, where t is a vector and B is a square matrix. Input: t A row vector B A matrix Output: v The row vector t * B Format: FFT """ 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_fft(v[j], mul_fft(t[i], B[i][j])) return v
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