import random from .polynomial import polynomialsOver from .modp import * # isIrreducible: Polynomial, int -> bool # determine if the given monic polynomial with coefficients in Z/p is # irreducible over Z/p where p is the given integer # Algorithm 4.69 in the Handbook of Applied Cryptography def isIrreducible(polynomial, p): ZmodP = IntegersModP(p) if polynomial.field is not ZmodP: raise TypeError("Given a polynomial that's not over %s, but instead %r" % (ZmodP.__name__, polynomial.field.__name__)) poly = polynomialsOver(ZmodP).factory x = poly([0,1]) powerTerm = x isUnit = lambda p: p.degree() == 0 for _ in range(int(polynomial.degree() / 2)): powerTerm = powerTerm.powmod(p, polynomial) gcdOverZmodp = gcd(polynomial, powerTerm - x) if not isUnit(gcdOverZmodp): return False return True # generateIrreduciblePolynomial: int, int -> Polynomial # 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 irreducilibity tests. By Chernoff bounds the probability # it deviates from this by very much is exponentially small. def generateIrreduciblePolynomial(modulus, degree): Zp = IntegersModP(modulus) Polynomial = polynomialsOver(Zp) while True: coefficients = [Zp(random.randint(0, modulus-1)) for _ in range(degree)] randomMonicPolynomial = Polynomial(coefficients + [Zp(1)]) print(randomMonicPolynomial) if isIrreducible(randomMonicPolynomial, modulus): return randomMonicPolynomial # create a type constructor for the finite field of order p^m for p prime, m >= 1 @memoize def FiniteField(p, m, polynomialModulus=None): Zp = IntegersModP(p) if m == 1: return Zp Polynomial = polynomialsOver(Zp) if polynomialModulus is None: polynomialModulus = generateIrreduciblePolynomial(modulus=p, degree=m) class Fq(FieldElement): fieldSize = int(p ** m) primeSubfield = Zp idealGenerator = polynomialModulus operatorPrecedence = 3 def __init__(self, poly): if 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): 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 = extendedEuclideanAlgorithm(self.poly, self.idealGenerator) 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()) Fq.__name__ = 'F_{%d^%d}' % (p,m) return Fq if __name__ == "__main__": F23 = FiniteField(2,3) x = F23([1,1]) F35 = FiniteField(3,5) y = F35([1,1,2])