# Primitive Root of Unity def get_primitive_root_of_unity(F, n): # using the method described by Thomas Pornin in # https://crypto.stackexchange.com/a/63616 q = F.order() for k in range(q): if k==0: continue g = F(k) # g = F.random_element() if g==0: continue w = g ^ ((q-1)/n) if w^(n/2) != 1: return g, w # Roots of Unity def get_nth_roots_of_unity(n, primitive_w): w = [0]*n for i in range(n): w[i] = primitive_w^i return w # fft (Fast Fourier Transform) returns: # - nth roots of unity # - Vandermonde matrix for the nth roots of unity # - Inverse Vandermonde matrix def fft(F, n): g, primitive_w = get_primitive_root_of_unity(F, n) w = get_nth_roots_of_unity(n, primitive_w) ft = matrix(F, n) for j in range(n): row = [] for k in range(n): row.append(primitive_w^(j*k)) ft.set_row(j, row) ft_inv = ft^-1 return w, ft, ft_inv # Fast polynomial multiplication using FFT def poly_mul(fa, fb, F, n): w, ft, ft_inv = fft(F, n) # compute evaluation points from polynomials fa & fb at the roots of unity a_evals = [] b_evals = [] for i in range(n): a_evals.append(fa(w[i])) b_evals.append(fb(w[i])) # multiply elements in a_evals by b_evals c_evals = map(operator.mul, a_evals, b_evals) c_evals = vector(c_evals) # using FFT, convert the c_evals into fc(x) fc_coef = c_evals*ft_inv fc2=P(fc_coef.list()) return fc2, c_evals # Tests ##### # Roots of Unity test: q = 17 F = GF(q) n = 4 g, primitive_w = get_primitive_root_of_unity(F, n) print("generator:", g) print("primitive_w:", primitive_w) w = get_nth_roots_of_unity(n, primitive_w) print(f"{n}th roots of unity: {w}") assert w == [1, 13, 16, 4] ##### # FFT test: def isprime(num): for n in range(2,int(num^1/2)+1): if num%n==0: return False return True # list valid values for q for i in range(20): if isprime(8*i+1): print("q =", 8*i+1) q = 41 F = GF(q) n = 4 # q needs to be a prime, s.t. q-1 is divisible by n assert (q-1)%n==0 print("q =", q, "n = ", n) # ft: Vandermonde matrix for the nth roots of unity w, ft, ft_inv = fft(F, n) print("nth roots of unity:", w) print("Vandermonde matrix:") print(ft) fa_eval = vector([3,4,5,9]) print("fa_eval:", fa_eval) # interpolate f_a(x) fa_coef = ft_inv * fa_eval print("fa_coef:", fa_coef) P. = PolynomialRing(F) fa = P(list(fa_coef)) print("f_a(x):", fa) # check that evaluating fa(x) at the roots of unity returns the expected values of fa_eval for i in range(len(fa_eval)): assert fa(w[i]) == fa_eval[i] # go from coefficient form to evaluation form fa_eval2 = ft * fa_coef print("fa_eval'", fa_eval) assert fa_eval2 == fa_eval # Fast polynomial multiplication using FFT print("\n---------") print("---Fast polynomial multiplication using FFT") n = 8 # q needs to be a prime, s.t. q-1 is divisible by n assert (q-1)%n==0 print("q =", q, "n = ", n) fa=P([1,2,3,4]) fb=P([1,2,3,4]) fc_expected = fa*fb print("fc expected result:", fc_expected) # expected result print("fc expected coef", fc_expected.coefficients()) fc, c_evals = poly_mul(fa, fb, F, n) print("c_evals=(a_evals*b_evals)=", c_evals) print("fc:", fc) assert fc_expected == fc