def polynomials(self, X=None, Y=None, degree=2, groebner=False): """ Return a list of polynomials satisfying this S-box. First, a simple linear fitting is performed for the given ``degree`` (cf. for example [BC2003]_). If ``groebner=True`` a Groebner basis is also computed for the result of that process. INPUT: - ``X`` - input variables - ``Y`` - output variables - ``degree`` - integer > 0 (default: ``2``) - ``groebner`` - calculate a reduced Groebner basis of the spanning polynomials to obtain more polynomials (default: ``False``) EXAMPLES:: sage: from sage.crypto.sbox import SBox sage: S = SBox(7,6,0,4,2,5,1,3) sage: P = S.ring() By default, this method returns an indirect representation:: sage: S.polynomials() [x0*x2 + x1 + y1 + 1, x0*x1 + x1 + x2 + y0 + y1 + y2 + 1, x0*y1 + x0 + x2 + y0 + y2, x0*y0 + x0*y2 + x1 + x2 + y0 + y1 + y2 + 1, x1*x2 + x0 + x1 + x2 + y2 + 1, x0*y0 + x1*y0 + x0 + x2 + y1 + y2, x0*y0 + x1*y1 + x1 + y1 + 1, x1*y2 + x1 + x2 + y0 + y1 + y2 + 1, x0*y0 + x2*y0 + x1 + x2 + y1 + 1, x2*y1 + x0 + y1 + y2, x2*y2 + x1 + y1 + 1, y0*y1 + x0 + x2 + y0 + y1 + y2, y0*y2 + x1 + x2 + y0 + y1 + 1, y1*y2 + x2 + y0] We can get a direct representation by computing a lexicographical Groebner basis with respect to the right variable ordering, i.e. a variable ordering where the output bits are greater than the input bits:: sage: P.<y0,y1,y2,x0,x1,x2> = PolynomialRing(GF(2),6,order='lex') sage: S.polynomials([x0,x1,x2],[y0,y1,y2], groebner=True) [y0 + x0*x1 + x0*x2 + x0 + x1*x2 + x1 + 1, y1 + x0*x2 + x1 + 1, y2 + x0 + x1*x2 + x1 + x2 + 1] """ def nterms(nvars, deg): """ Return the number of monomials possible up to a given degree. INPUT: - ``nvars`` - number of variables - ``deg`` - degree TESTS:: sage: from sage.crypto.sbox import SBox sage: S = SBox(7,6,0,4,2,5,1,3) sage: F = S.polynomials(degree=3) # indirect doctest """ total = 1 divisor = 1 var_choices = 1 for d in range(1, deg+1): var_choices *= (nvars - d + 1) divisor *= d total += var_choices/divisor return total m = self.m n = self.n F = self._F if X is None and Y is None: P = self.ring() X = P.gens()[:m] Y = P.gens()[m:] else: P = X[0].parent() gens = X+Y bits = [] for i in range(1<<m): bits.append( self.to_bits(i,m) + self(self.to_bits(i,m)) ) ncols = (1<<m)+1 A = Matrix(P, nterms(m+n, degree), ncols) exponents = [] for d in range(degree+1): exponents += IntegerVectors(d, max_length=m+n, min_length=m+n, min_part=0, max_part=1).list() row = 0 for exponent in exponents: A[row,ncols-1] = mul([gens[i]**exponent[i] for i in range(len(exponent))]) for col in range(1<<m): A[row,col] = mul([bits[col][i] for i in range(len(exponent)) if exponent[i]]) row +=1 for c in range(ncols): A[0,c] = 1 RR = A.echelon_form(algorithm='row_reduction') # extract spanning stet gens = (RR.column(ncols-1)[1<<m:]).list() if not groebner: return gens FI = set(FieldIdeal(P).gens()) I = Ideal(gens + list(FI)) gb = I.groebner_basis() gens = [] for f in gb: if f not in FI: # filter out field equations gens.append(f) return gens
def polynomials(self, X=None, Y=None, degree=2, groebner=False): """ Return a list of polynomials satisfying this S-box. First, a simple linear fitting is performed for the given ``degree`` (cf. for example [BC03]_). If ``groebner=True`` a Groebner basis is also computed for the result of that process. INPUT: - ``X`` - input variables - ``Y`` - output variables - ``degree`` - integer > 0 (default: ``2``) - ``groebner`` - calculate a reduced Groebner basis of the spanning polynomials to obtain more polynomials (default: ``False``) EXAMPLES:: sage: S = mq.SBox(7,6,0,4,2,5,1,3) sage: P = S.ring() By default, this method returns an indirect representation:: sage: S.polynomials() [x0*x2 + x1 + y1 + 1, x0*x1 + x1 + x2 + y0 + y1 + y2 + 1, x0*y1 + x0 + x2 + y0 + y2, x0*y0 + x0*y2 + x1 + x2 + y0 + y1 + y2 + 1, x1*x2 + x0 + x1 + x2 + y2 + 1, x0*y0 + x1*y0 + x0 + x2 + y1 + y2, x0*y0 + x1*y1 + x1 + y1 + 1, x1*y2 + x1 + x2 + y0 + y1 + y2 + 1, x0*y0 + x2*y0 + x1 + x2 + y1 + 1, x2*y1 + x0 + y1 + y2, x2*y2 + x1 + y1 + 1, y0*y1 + x0 + x2 + y0 + y1 + y2, y0*y2 + x1 + x2 + y0 + y1 + 1, y1*y2 + x2 + y0] We can get a direct representation by computing a lexicographical Groebner basis with respect to the right variable ordering, i.e. a variable ordering where the output bits are greater than the input bits:: sage: P.<y0,y1,y2,x0,x1,x2> = PolynomialRing(GF(2),6,order='lex') sage: S.polynomials([x0,x1,x2],[y0,y1,y2], groebner=True) [y0 + x0*x1 + x0*x2 + x0 + x1*x2 + x1 + 1, y1 + x0*x2 + x1 + 1, y2 + x0 + x1*x2 + x1 + x2 + 1] REFERENCES: .. [BC03] \A. Biryukov and C. D. Canniere *Block Ciphers and Systems of Quadratic Equations*; in Proceedings of Fast Software Encryption 2003; LNCS 2887; pp. 274-289, Springer-Verlag 2003. """ def nterms(nvars, deg): """ Return the number of monomials possible up to a given degree. INPUT: - ``nvars`` - number of variables - ``deg`` - degree TESTS:: sage: S = mq.SBox(7,6,0,4,2,5,1,3) sage: F = S.polynomials(degree=3) # indirect doctest """ total = 1 divisor = 1 var_choices = 1 for d in range(1, deg+1): var_choices *= (nvars - d + 1) divisor *= d total += var_choices/divisor return total m = self.m n = self.n F = self._F if X is None and Y is None: P = self.ring() X = P.gens()[:m] Y = P.gens()[m:] else: P = X[0].parent() gens = X+Y bits = [] for i in range(1<<m): bits.append( self.to_bits(i,m) + self(self.to_bits(i,m)) ) ncols = (1<<m)+1 A = Matrix(P, nterms(m+n, degree), ncols) exponents = [] for d in range(degree+1): exponents += IntegerVectors(d, max_length=m+n, min_length=m+n, min_part=0, max_part=1).list() row = 0 for exponent in exponents: A[row,ncols-1] = mul([gens[i]**exponent[i] for i in range(len(exponent))]) for col in range(1<<m): A[row,col] = mul([bits[col][i] for i in range(len(exponent)) if exponent[i]]) row +=1 for c in range(ncols): A[0,c] = 1 RR = A.echelon_form(algorithm='row_reduction') # extract spanning stet gens = (RR.column(ncols-1)[1<<m:]).list() if not groebner: return gens FI = set(FieldIdeal(P).gens()) I = Ideal(gens + list(FI)) gb = I.groebner_basis() gens = [] for f in gb: if f not in FI: # filter out field equations gens.append(f) return gens