def are_CCZ_equivalent(s1, s2): s1, s2 = convert_sboxes(s1, s2) assert_equal_sizes(s1, s2) lin1 = s1.is_linear() lin2 = s2.is_linear() if lin1 ^ lin2: return False if lin1 and lin2: return True inp, out = s1.n, s1.m M1 = matrix(GF(2), 1 + inp + out, 2**inp) M2 = matrix(GF(2), 1 + inp + out, 2**inp) for x in range(2**inp): M1.set_column( x, Bin.concat(Bin(1, 1) + Bin(x, inp) + Bin(s1[x], out)).tuple) M2.set_column( x, Bin.concat(Bin(1, 1) + Bin(x, inp) + Bin(s2[x], out)).tuple) L1 = LinearCode(M1) L2 = LinearCode(M2) # Annoying: this is not checked in "is_permutation_equivalent" code! # raises a TypeError instead if L1.generator_matrix().nrows() != L2.generator_matrix().nrows(): return False return L1.is_permutation_equivalent(L2)
def as_matrix(self): assert self.is_linear() m = matrix(GF(2), self.output_size(), self.input_size()) for e in range(self.input_size()): vec = Bin(self[2**e], self.output_size()).tuple m.set_column(self.input_size() - 1 - e, vec) return m
def __init__(self, *args): self.matrix = matrix(*args) assert self.matrix.base_ring() in (ZZ, QQ) self.n = self.matrix.nrows() self.m = self.matrix.ncols() # bounds are relative self.set_bounds((1, ) * self.matrix.ncols())
def difference_distribution_table(self, zero_zero=True): ddt = matrix(ZZ, 2**self.output_size(), 2**self.input_size()) for x in self.input_range(): for dx in self.input_range()[1:]: dy = self[x] ^ self[x ^ dx] ddt[dx, dy] += 1 if zero_zero: ddt[0, 0] = 0 return ddt
def mat_field_mul_const(field, c): assert field.base_ring() == GF(2) d = field.degree() m = matrix(GF(2), d, d) for i, e in enumerate(reversed(range(d))): x = 1 << e res = field.fetch_int(x) * field.fetch_int(c) res = res.integer_representation() m.set_column(i, Bin(res, d).tuple) return m
def xor_add_ddt(self): axddt = matrix(ZZ, self.insize, self.outsize) for x in range(self.insize): for dx in range(1, self.insize): x2 = x ^ dx y = self[x] y2 = self[x2] dy = (y2 - y) % self.outsize axddt[dx, dy] += 1 return axddt
def add_add_ddt(self): addt = matrix(ZZ, self.insize, self.outsize) for x in range(self.insize): for dx in range(1, self.insize): x2 = (x + dx) % self.insize y = self[x] y2 = self[x2] dy = (y2 - y) % self.outsize addt[dx, dy] += 1 return addt
def add_xor_ddt(self): axddt = matrix(ZZ, self.insize, self.outsize) for x in range(self.insize): for dx in range(1, self.insize): x2 = (x + dx) % self.insize y = self[x] y2 = self[x2] dy = y ^ y2 axddt[dx, dy] += 1 return axddt
def linear_approximation_table(self, zero_zero=True): lat = matrix(ZZ, 2**self.input_size(), 2**self.output_size(), [ self.component(mask).to_sage_BF().walsh_hadamard_transform() for mask in self.output_range() ]).transpose() if zero_zero: lat[0, 0] = 0 if abs: lat = lat.apply_map(abs) return lat
def kddt(self, k=3, zero_zero=False): kddt = matrix(ZZ, self.insize, self.outsize) for xs in Combinations(range(self.insize), k): ys = map(self, xs) dx = reduce(lambda a, b: a ^ b, xs) dy = reduce(lambda a, b: a ^ b, ys) kddt[dx, dy] += 1 if zero_zero: kddt[0, 0] = 0 return kddt
def as_table(self, height=None, width=None): assert (width is None) ^ (height is None) if width is not None: height = self.input_size() // width else: width = self.input_size() // height assert height * width == self.input_size() m = matrix(ZZ, height, width) for x, y in self.graph(): m[divmod(x, width)] = y return m
def hdim(self, right_to_left=False): """ hdim[i,j] = i-th output bit contains monomial x1...xn/xj """ res = matrix(GF(2), self.in_bits, self.out_bits) anf = mobius(tuple(self)) for j in range(self.in_bits): mask = (1 << self.in_bits) - 1 mask ^= 1 << (self.in_bits - 1 - j) res.set_column(j, Bin(anf[mask], self.out_bits).tuple) if right_to_left: res = res[::-1, ::-1] return res
def cmul_xor_ddt(self, F=None): if F is None: F = GF(self.insize, name='a') cxddt = matrix(ZZ, self.insize, self.outsize) for x in range(1, self.insize): for dx in range(2, self.insize): x2 = (F.fetch_int(x) * F.fetch_int(dx)).integer_representation() y = self[x] y2 = self[x2] dy = y2 ^ y cxddt[dx, dy] += 1 return cxddt
def minilat(self, abs=False): """LAT taken on a basis points""" res = matrix(ZZ, self.m, self.n) for eu in range(self.m): for ev in range(self.n): u = Integer(2**eu) v = Integer(2**ev) for x in range(2**self.m): res[eu, ev] += ((u & x).popcount() & 1 == (v & self[x]).popcount() & 1) if abs: res = res.apply_map(_abs) return res
def xor_cmul_ddt(self, F=None): if F is None: F = GF(self.insize, name='a') xcddt = matrix(ZZ, self.insize, self.outsize) for x in range(self.insize): for dx in range(1, self.insize): x2 = x ^ dx y = self[x] y2 = self[x2] dy = (F.fetch_int(y2) * F.fetch_int(y) **(self.outsize - 2)).integer_representation() xcddt[dx, dy] += 1 return xcddt
def matrix_is_mds(mat): """@mat is considered as operator M*x""" n = mat.ncols() m = mat.nrows() mat2 = matrix(mat.base_ring(), m * 2, n) mat2[:m, :n] = identity_matrix(mat.base_ring(), m, n) mat2[m:, :n] = mat # linear code: x -> (x || M*x) C = LinearCode(mat2.transpose()) D = C.minimum_distance() K = n N = 2 * m # Singleton bound # MDS on equality assert D <= N - K + 1 return D == N - K + 1
def __init__(self, oracle, n_in, field=F2, ntests=10): self.oracle = oracle self.field = field self.n_in = int(n_in) self.zero = vector(field, oracle([0] * n_in)) self.n_out = len(self.zero) cols = [] for i in range(n_in): x = [0] * n_in x[i] = 1 y = vector(field, oracle(x)) - self.zero cols.append(y) self.matrix = matrix(field, cols).transpose() self._kernel = None self.rank = self.matrix.rank() self.kernel_dimension = self.matrix.ncols() - self.rank self.test_oracle(ntests)
def mat_from_linear_func(m, n, func): mat = matrix(GF(2), n, m) for i, e in enumerate(reversed(range(m))): x = 1 << e mat.set_column(i, Bin(func(x), n).tuple) return mat
def random_permutation_matrix(field, n): rows = list(identity_matrix(n)) shuffle(rows) return matrix(field, rows)
def to_matrix(self, field=F2): m = matrix(Permutation([v + 1 for v in self.p])) m = m.transpose().change_ring(field) return m