def generate_irreducible_polynomial(modulus: int, degree: int) -> Poly: """ Generate a random irreducible polynomial of a given degree over Z/p, where p is given by the integer 'modulus'. This algorithm is expected to terminate after 'degree' many irreducibility tests. By Chernoff bounds the probability it deviates from this by very much is exponentially small. """ Zp = IntegersModP(modulus) Polynomial = polynomials_over(Zp) while True: coefficients = [ Zp(random.randint(0, modulus - 1)) for _ in range(degree) ] random_monic_polynomial = Polynomial(coefficients + [Zp(1)]) if is_irreducible(random_monic_polynomial, modulus): return random_monic_polynomial
def test_zpoly(self): """Test construction of polynomials with specified root""" modulus = 7 mod7 = IntegersModP(modulus) polysMod7 = polynomials_over(mod7).factory # Test 1 root roots = [3] poly = zpoly(mod7, roots) assert poly(mod7(3)) == 0 # Check equals x - 3 assert poly == polysMod7([-3, 1]) # Test 2 roots roots = [1, 2] poly = zpoly(mod7, roots) assert poly(mod7(1)) == 0 assert poly(mod7(2)) == 0 # Check equals x^2 - 3x + 2 assert poly == polysMod7([2, -3, 1])
def test_Taylor_Expansion(self): p = 2 m = 4 Zp = IntegersModP(p) polysOver = polynomials_over(Zp) coefficients = [Zp(0)] * 5 coefficients[0] = Zp(1) coefficients[1] = Zp(1) coefficients[4] = Zp(1) poly = polysOver(coefficients) field = FiniteField(p, m, polynomialModulus=poly) # 1 + x + x^3 f = field(polysOver([1, 1, 0, 1])) # 1 + x^2 + x^3 fp = field(polysOver([1, 0, 1, 1])) obj = Additive_FFT(field) V1, V2 = obj.Taylor_Expansion(f, f.poly.degree()) V1p, V2p = obj.Taylor_Expansion(fp, fp.poly.degree()) assert V1.poly.degree() <= 3 and V2.poly.degree() <= 3 assert V1p.poly.degree() <= 3 and V2p.poly.degree() <= 3
def test_finitefield(self): """Test multivariates over finite fields.""" # This finite field is of size 2^17 p = 2 m = 17 Zp = IntegersModP(p) polysOver = polynomials_over(Zp) #field = FiniteField(p, m) #x^17 + x^3 + 1 is primitive coefficients = [Zp(0)] * 18 coefficients[0] = Zp(1) coefficients[3] = Zp(1) coefficients[17] = Zp(1) poly = polysOver(coefficients) field = FiniteField(p, m, polynomialModulus=poly) width = 2 inp = [field(0), field(1)] polysOver = multivariates_over(field, width).factory [X_1, X_2] = generate_Xi_s(field, width) step_poly = X_2 poly = step_poly([X_1, X_2]) assert poly == X_2
def multi_interp_4(field, xsets, ysets): """Optimized version of the above restricted to deg-4 polynomials""" polysOver = polynomials_over(field).factory data = [] invtargets = [] for xs, ys in zip(xsets, ysets): x01, x02, x03, x12, x13, x23 = \ xs[0] * xs[1], xs[0] * xs[2], xs[0] * xs[3], xs[1] * xs[2], xs[1] * xs[3], xs[2] * xs[3] eq0 = polysOver( [-x12 * xs[3], (x12 + x13 + x23), -xs[1] - xs[2] - xs[3], 1]) eq1 = polysOver( [-x02 * xs[3], (x02 + x03 + x23), -xs[0] - xs[2] - xs[3], 1]) eq2 = polysOver( [-x01 * xs[3], (x01 + x03 + x13), -xs[0] - xs[1] - xs[3], 1]) eq3 = polysOver( [-x01 * xs[2], (x01 + x02 + x12), -xs[0] - xs[1] - xs[2], 1]) e0 = eq0(xs[0]) e1 = eq1(xs[1]) e2 = eq2(xs[2]) e3 = eq3(xs[3]) data.append([ys, eq0, eq1, eq2, eq3]) invtargets.extend([e0, e1, e2, e3]) invalls = multi_inv(field, invtargets) o = [] for (i, (ys, eq0, eq1, eq2, eq3)) in enumerate(data): invallz = invalls[i * 4:i * 4 + 4] inv_y0 = ys[0] * invallz[0] inv_y1 = ys[1] * invallz[1] inv_y2 = ys[2] * invallz[2] inv_y3 = ys[3] * invallz[3] o.append( polysOver([ (eq0.coefficients[i] * inv_y0 + eq1.coefficients[i] * inv_y1 + eq2.coefficients[i] * inv_y2 + eq3.coefficients[i] * inv_y3) for i in range(4) ])) return o
def gauss(M, row, field): for i in range(row): # Search for maximum in this column maxEl = M[i][i] maxRow = i for k in range(i + 1, row): if max(M[k][i], maxEl) == 1: maxEl = M[k][i] maxRow = k # Swap maximum row with current row (column by column) for k in range(i, row + 1): tmp = M[maxRow][k] M[maxRow][k] = M[i][k] M[i][k] = tmp polysOver = polynomials_over(field).factory zero = polysOver([0]) # Make all rows below this one 0 in current column for k in range(i + 1, row): c = -M[k][i] / M[i][i] for j in range(i, row + 1): if i == j: M[k][j] = 0 else: M[k][j] = M[k][j] + c * M[i][j] # Solve equation Mx=b for an upper triangular matrix M x = [0 for i in range(row)] for i in range(row - 1, -1, -1): x[i] = M[i][row] / M[i][i] for k in range(i - 1, -1, -1): M[k][row] = M[k][row] - M[k][i] * x[i] return x
def is_primitive(irred_poly: Poly, modulus: int, degree: int) -> bool: """Returns true if given polynomial is primitve. Follows algorithm 4.78 in the Handbook of Applied Cryptography (http://math.fau.edu/bkhadka/Syllabi/A%20handbook%20of%20applied%20cryptography.pdf). """ # All primitive polynomials are irreducible if not is_irreducible(irred_poly, modulus): return False # factorize p^m - 1 prime_factors = factorint(modulus**degree - 1) # This is returned as dictionary with multiplicities. Turn into list prime_factors = [int(factor) for factor in prime_factors.keys()] Zp = IntegersModP(modulus) polysOver = polynomials_over(Zp) x = polysOver([0, 1]) # This is 1 right? one = polysOver([1]) for i, factor in enumerate(prime_factors): power = (modulus**degree - 1) // factor l_x = (x**power) % irred_poly if l_x == one: return False return True
def test_exponentiation(self): """Basic test of floating point exponentiation.""" p = 2 m = 4 Zp = IntegersModP(p) polysOver = polynomials_over(Zp) coefficients = [Zp(0)] * 5 coefficients[0] = Zp(1) coefficients[1] = Zp(1) coefficients[4] = Zp(1) poly = polysOver(coefficients) field = FiniteField(p, m, polynomialModulus=poly) floating_point = FloatingPoint(field) assert floating_point(field(polysOver([1, 1, 1])), field(polysOver([1])), 0, 0) == floating_point( field(polysOver([1, 1, 1])), field(polysOver([1])), 0, 0)**floating_point(field(polysOver([1])), field(polysOver([0])), 0, 0) assert floating_point(field(polysOver([1])), field(polysOver([0])), 0, 0) == floating_point( field(polysOver([1, 1, 1])), field(polysOver([1])), 0, 0)**floating_point(field(polysOver([0])), field(polysOver([0])), 0, 0) assert floating_point(field(polysOver([1, 1, 1])), field(polysOver([0])), 0, 0) == floating_point( field(polysOver([1, 1, 1])), field(polysOver([1])), 0, 0)**floating_point(field(polysOver([1])), field(polysOver([0, 1])), 0, 0)
def test_adfft(self): p = 2 m = 4 Zp = IntegersModP(p) polysOver = polynomials_over(Zp) coefficients = [Zp(0)] * 5 coefficients[0] = Zp(1) coefficients[1] = Zp(1) coefficients[4] = Zp(1) poly = polysOver(coefficients) field = FiniteField(p, m, polynomialModulus=poly) # 1 + x + x^3 f = field(polysOver([1, 1, 0, 1])) obj = Additive_FFT(field) mp = 2 beta = [] beta.append(field(polysOver([1, 0, 1]))) beta.append(field(polysOver([0, 1, 1]))) shift = field(polysOver([1, 0, 1])) obj = Additive_FFT(field) V1, V2 = obj.Taylor_Expansion(f, f.poly.degree()) W = obj.adfft(f, mp, beta) assert V1.poly.degree() <= 3 and V2.poly.degree() <= 3 print(W)
def select_random(field: Field, x) -> FieldElement: result = hashlib.sha384(x.encode()).hexdigest() num = HextoBin(str(result)) polysOver = polynomials_over(IntegersModP(field.p)) return field(polysOver(num))
def query(self, f, coeff): seed(1) # rate parameter R = -math.log(self.rho, 2) # localization parameter eta = 2 # Perhaps an option to set in the constructor k_0 = math.log(len(self.affine_space), 2) r = int(math.floor((k_0 - R)/eta)) # verifier can compute sequences of L and L_0 L = [] L.append(self.affine_space) L_0 = [] L_0.append(select_subspace(L[-1], eta)) q = [] for i in range(r-1): q.append(construct_affine_vanishing_polynomial(self.field, L_0[-1])) L.append(eval_on_subspace(q[-1], L[-1])) L_0.append(select_subspace(L[-1], eta)) q.append(construct_affine_vanishing_polynomial(self.field, L_0[-1])) # compute tha last f based on the proof polysOver = polynomials_over(self.field).factory P_prime = polysOver(coeff) L_r = eval_on_subspace(q[-1], L[-1]) x_ply = [val for val in L_r] y_ply = [P_prime(val) for val in x_ply] f.append(lagrange_interp(self.field, x_ply, y_ply)) # a sequence of x can be computed as well x = [] for i in range(r): x_str = str(R)+str(eta)+str(r)+str(k_0)+str(L[i])+str(L_0[i]) x.append(select_random(self.field, x_str)) # we need to assign l, it can be a function of the security parameter l = 2 for j in range(l): # randomly select a ellement of L[0] random_n = randint(0, len(L[0])) #constructing s s_i = [] count = 1 for elm in L[0]: if count == random_n: s_i.append(elm) count += 1 for i in range(r): s_i.append(q[i](s_i[-1])) S_i = [] for i in range(r): temp_coset = [s_i[i] + l_i for l_i in L_0[i]] S_i.append(temp_coset) # for all S_is, compute P P = [] for i in range(r): coset_eval = [f[i](x_c) for x_c in S_i[i]] P.append(lagrange_interp(self.field, S_i[i], coset_eval)) for i in range(r): #if f[i+1](s_i[i+1]) == f[i+1](s_i[i+1]): #is not P[i](temp): if f[i+1](s_i[i+1]) == P[i](x[i]): return False return True
def __add__(self, other): ''' if we are adding A = (v_a, p_a, z_a, s_a) and B = (v_b, p_b, z_b, s_b), then: 1- If z_a = 0, then the output is B, and if z_b= 0, then the output is A 2- find v_max, v_min, p_max, and p_min 3- delta = p_max - p_min 4- s_output = s_a XOR s_b 5- if delta > 1, then v_output = v_max and p_output = p_max, also z_output will be equal to z of the maximum element 6- if 0 <= delta <= l, then v_output = v_max * 2^delta + v_min and p_output = p_min ''' polysOver = polynomials_over(IntegersModP(field.p)) if self.z == 1: v_c = other.v p_c = other.p z_c = other.z s_c = other.s output = Fp(v_c, p_c, z_c, s_c) return output if other.z == 1: v_c = self.v p_c = self.p z_c = self.z s_c = self.s output = Fp(v_c, p_c, z_c, s_c) return output a = self.v.LT(other.v) b = self.p.LT(other.p) if self.p == other.p: if a == 1: v_min = self.v v_max = other.v s_c = other.s else: v_min = other.v v_max = self.v s_c = self.s else: if b == 1: v_min = self.v v_max = other.v s_c = other.s else: v_min = other.v v_max = self.v s_c = self.s if b == 1: p_min = self.p p_max = other.p else: p_min = other.p p_max = self.p Delta = p_max - p_min c = Delta.LT(field(polysOver(num_to_binary_list(field.m)))) if c == 0: v_c = v_max p_c = p_max else: v_c = v_max * Delta.two_pow() v_c = v_c + v_min p_c = p_min if v_c == field(polysOver([0])): z_c = 1 else: z_c = 0 output = Fp(v_c, p_c, z_c, s_c) return output
def __init__(self, field, root_of_unity): self.field = field self.root_of_unity = root_of_unity self.polysOver = polynomials_over(self.field).factory
def adfft_inverse(self, x, y, m): # this is th exit condition where the size of x and y is 2 and we need to interpolate a finction that has these two points on it if m == 1: if x[0] == x[1]: return x[0] else: return ((y[1] - y[0]) / (x[1] - x[0])) * (self.field(polysOver([0, 1])) - x[0]) + y[0] # based on the value of x we can build all beta where beta_i = x_{2^i} polysOver = polynomials_over(IntegersModP(2)) beta = [] for i in range(m): beta.append(x[2**i]) """ gamma_i = beta_i * beta_m ^-1 and delta_i = gamma_i^2 - gamma_i for i = 1, ..., m-1 G = <gamma_1, ..., gamma_{m-1}> and D = <delta_1, ..., delta_{m-1}> """ gamma = [] Ibeta = beta[-1].inverse() for i in range(m - 1): gamma.append(beta[i] * Ibeta) delta = [] for i in range(m - 1): delta.append(gamma[i] * gamma[i] - gamma[i]) G = [] for i in range(2**(m - 1)): binary = int_to_bin_string(i) temp = self.field(polysOver([0])) for j in range(len(binary)): if binary[j] == '1': temp = temp + gamma[j] G.append(temp) D = delta # comput u and v where v_i = y_{i+2^{m-1} - y_i and u_i = y_i - G_i * v_i v = [] u = [] for i in range(2**(m - 1)): v.append(y[i + 2**(m - 1)] - y[i]) u.append(y[i] - G[i] * v[i]) x1 = [] for i in range(2**(m - 1)): binary = int_to_bin_string(i + 1) temp = self.field(polysOver([0])) for j in range(len(binary)): if binary[j] == '1': temp = temp + D[j] x1.append(temp) # the recursion parts of the approach to get g0 and g1 g_0 = self.adfft_inverse(x1, u, m - 1) g_1 = self.adfft_inverse(x1, v, m - 1) # computing g based on g0 and g1 by using taylor expansion and then changing the variables to return it as the output g = self.field(polysOver([0])) g_right_tempp = self.field(polysOver([0, 1])) * Ibeta g_right_temp = g_right_tempp * g_right_tempp - g_right_tempp g_right = [] g_right.append(self.field(polysOver([1]))) multiplier = [] multiplier.append(self.field(polysOver([0]))) multiplier.append(g_right_tempp) multiplier.append(self.field(polysOver([1]))) multiplier.append(self.field(polysOver([1])) + g_right_tempp) for i in range(2**(m - 1)): g_right.append(g_right[-1] * g_right_temp) for i in range(2**(m - 1)): if i <= g_0.poly.degree(): g0I = int(g_0.poly.coefficients[i]) else: g0I = 0 if i <= g_1.poly.degree(): g1I = int(g_1.poly.coefficients[i]) else: g1I = 0 g = g + multiplier[g0I + 2**g1I] * g_right[i] return g
def adfft(self, Polys, m, affine_beta): # evaluation f in 0 and beta_1 polysOver = polynomials_over(IntegersModP(2)) f1 = self.field(polysOver([0])) for i in range(Polys.poly.degree() + 1): if str(Polys.poly.coefficients[i])[0] == '1': f1 = f1 + (affine_beta[0])**i f2 = self.field(polysOver([0])) for i in range(Polys.poly.degree() + 1): if str(Polys.poly.coefficients[i])[0] == '1': f2 = f2 # if m == 1 return f1 and f2 if m == 1: return f1, f2 # g(x) = f(beta_m * x) g = self.field(polysOver([0])) x = self.field(polysOver([0, 1])) x = x * affine_beta[m - 1] for i in range(Polys.poly.degree() + 1): if str(Polys.poly.coefficients[i])[0] == '1': g = g + x**i # g_0 and g_1 are output of taylor expansion over g g0, g1 = self.Taylor_Expansion(g, 2**m) """ gamma_i = beta_i * beta_m ^-1 and delta_i = gamma_i^2 - gamma_i for i = 1, ..., m-1 G = <gamma_1, ..., gamma_{m-1}> and D = <delta_1, ..., delta_{m-1}> """ gamma = [] beta_m_I = affine_beta[m - 1].inverse() for i in range(m - 1): gamma.append(affine_beta[i] * beta_m_I) delta = [] for i in range(m - 1): delta.append(gamma[i]**2 - gamma[i]) G = [] for i in range(2**(m - 1)): binary = int_to_bin_string(i) temp = self.field(polysOver([0])) for j in range(len(binary)): if binary[j] == '1': temp = temp + gamma[j] G.append(temp) D = delta # recursively call additive FFT u = self.adfft(g0, m - 1, D) v = self.adfft(g1, m - 1, D) w1 = [] w2 = [] for i in range(2**(m - 1)): w1.append(u[i] + G[i] * v[i]) w2.append(w1[i] + v[i]) # outputs are w_0, ..., w_{n-1} where w_i = u_i + G[i] * v_i and w_[i+2^{m-1}] = w_i + v_i where i = 0, ..., 2 ^{m-1} w = [] for i in range(len(w1)): w.append(w1[i]) for i in range(len(w2)): w.append(w2[i]) return w
def p(L, q): f = IntegersModP(q) Polynomial = polynomials_over(f).factory return Polynomial(L)
def FiniteField(p, m, polynomialModulus=None): """Create a type constructor for the finite field of order p^m for p prime, m >= 1""" if polynomialModulus is not None: if not is_irreducible(polynomialModulus, p): raise ValueError( "Must provide an irreducible polynomial as modulus.") Zp = IntegersModP(p) if m == 1: return Zp Polynomial = polynomials_over(Zp) if polynomialModulus is None: polynomialModulus = generate_primitive_polynomial(modulus=p, degree=m) class Fq(FieldElement): field_size = int(p**m) primeSubfield = Zp ideal_generator = polynomialModulus operatorPrecedence = 3 def __init__(self, poly): if isinstance(poly, bytes): self.poly = Polynomial(poly) elif type(poly) is Fq: self.poly = poly.poly elif type(poly) is int or type(poly) is Zp: self.poly = Polynomial([Zp(poly)]) elif isinstance(poly, Polynomial): self.poly = poly % polynomialModulus else: self.poly = Polynomial([Zp(x) for x in poly]) % polynomialModulus self.field = Fq @typecheck def __add__(self, other): return Fq(self.poly + other.poly) @typecheck def __sub__(self, other): return Fq(self.poly - other.poly) @typecheck def __mul__(self, other): return Fq(self.poly * other.poly) @typecheck def __eq__(self, other): return isinstance(other, Fq) and self.poly == other.poly @typecheck def __ne__(self, other): return not self == other def __pow__(self, n): if n == 0: return Fq([1]) if n == 1: return self if n % 2 == 0: sqrut = self**(n // 2) return sqrut * sqrut if n % 2 == 1: return (self**(n - 1)) * self #def __pow__(self, n): return Fq(pow(self.poly, n)) def __neg__(self): return Fq(-self.poly) def __abs__(self): return abs(self.poly) def __repr__(self): # The code \u2208 prints \in in pretty symbols return repr(self.poly) + ' \u2208 ' + self.__class__.__name__ @typecheck def __divmod__(self, divisor): q, r = divmod(self.poly, divisor.poly) return (Fq(q), Fq(r)) def inverse(self): if self == Fq(0): raise ZeroDivisionError x, y, d = extended_euclidean_algorithm(self.poly, self.ideal_generator) if d.degree() != 0: raise Exception( 'Somehow, this element has no inverse! Maybe intialized with a non-prime?' ) return Fq(x) * Fq(d.coefficients[0].inverse()) # TODO(rbharath): This function is broken!! def to_bytes(self): return self.poly.to_bytes() #this function computes 2^x where x is a number in binary finite field representation and output is in in binary finite field representation def two_pow(self): num = Fq([1]) for i in range(self.poly.degree() + 1): if str(self.poly.coefficients[i])[0] == '1': l = num_to_binary_list_pow(i + 1) num = num * Fq(l) return num #less than and equality operations between numbers in binary finite field representation def LT(self, other): if self.poly.degree() > other.poly.degree(): return 0 elif other.poly.degree() > self.poly.degree(): return 1 xor_o = self + other if xor_o == Fq(0): return 1 if str(self.poly.coefficients[xor_o.poly.degree()])[0] > str( other.poly.coefficients[xor_o.poly.degree()])[0]: return 0 else: return 1 #this function computes regular binary addition def Regular_Binary_Addition(self, other): x = '' y = '' for i in range(self.poly.degree() + 1): x = str(self.poly.coefficients[i])[0] + x for i in range(other.poly.degree() + 1): y = str(other.poly.coefficients[i])[0] + y list = add_binary_nums(x, y) return Fq(list) Fq.__name__ = 'F_{%d^%d}' % (p, m) Fq.p = p Fq.m = m Fq.base_field = Zp return Fq