def concat(funcs): assert len(set(f.input_size() for f in funcs)) == 1 sizes = tuple(f.output_size() for f in funcs) return SBox2( [Bin.concat(*Bin.array(ys, ns=sizes)) for ys in zip(*funcs)], m=sum(sizes) )
def swap_halves(self, input=True, output=True): if input: fx = lambda x: Bin(x, self.input_size()).swap_halves().int else: fx = lambda x: x if output: fy = lambda y: Bin(y, self.output_size()).swap_halves().int else: fy = lambda y: y return self.transform_graph(lambda x, y: (fx(x), fy(y)))
def int_tuple_list_vector(v): if isinstance(v, int): return v if isinstance(v, Integer): return int(v) if isinstance(v, tuple): return Bin(v).int if isinstance(v, list): return Bin(v).int if is_Vector(v): return Bin(tuple(v)).int raise TypeError("%r is not tuple, list, vector or integer")
def matrix_mult_int_rev(mat, x): """ LSB to MSB vector >>> matrix_mult_int_rev( \ matrix(GF(2), [[1, 0, 1], [1, 0, 0]]), \ 0b110) # read as 6 -> 0,1,1 -> 1,0 -> 1 1 """ assert mat.base_ring() == GF(2) n = mat.ncols() x = vector(GF(2), Bin(x, n).tuple[::-1]) y = mat * x return Bin(y[::-1]).int
def matrix_mult_int(mat, x): """ MSB to LSB vector >>> matrix_mult_int( \ matrix(GF(2), [[1, 0, 1], [1, 0, 0]]), \ 0b110) # read as 6 -> 1,1,0 -> 1,1 -> 3 3 """ assert mat.base_ring() == GF(2) n = mat.ncols() x = vector(GF(2), Bin(x, n).tuple) y = mat * x return Bin(y).int
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 propagate_single_permutation(self, pos): outs_by_consts = [defaultdict(dict) for _ in range(self.num_out)] for x, y in self.s.graph(): xs = Bin(x, self.num_in * self.width_in).split(parts=self.num_in) ys = Bin(y, self.num_out * self.width_out).split(parts=self.num_out) key = tuple(xs[i] for i in range(self.num_in) if i != pos) for j, y in enumerate(ys): d = outs_by_consts[j][key] d.setdefault(y, 0) d[y] += 1 out_prop = [] for j in range(self.num_out): has_const = False has_perm = False has_balanced = False has_unknown = False for key, d in outs_by_consts[j].items(): if self.width_in == self.width_out and\ len(d) == 2**self.width_in: has_perm = True continue if len(d) == 1: has_const = True continue xorsum = 0 for y, cnt in d.items(): if cnt & 1: xorsum ^= y if xorsum == 0: has_balanced = True else: has_unknown = True break if has_unknown: result = UNKNOWN elif has_balanced: result = BALANCED elif has_const and not has_perm: result = CONST elif has_perm and not has_const: result = PERM else: assert False out_prop.append(result) return "".join(out_prop)
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 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 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 anfs(self): names = ["x%d" % e for e in range(self.input_size())] bpr = BooleanPolynomialRing(names=names) vs = list((bpr.gens())) res = [] for f in self.mobius().coordinates(): anf = bpr(0) for mask, take in f.graph(): if not take: continue clause = bpr(1) for b, v in zip(Bin(mask, self.input_size()).tuple, vs): if b: clause *= v anf += clause res.append(anf) return res
def decrypt(self, msg): msg = Bin(msg).int msg = power_mod(msg, self.d, self.n) return Bin(msg).bytes
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 component(self, mask): mask = int_tuple_list_vector(mask) tt = [Bin(y & mask).parity() for y in self] yield type(self)(tt, m=1)
def squeeze_by_mask(self, mask): mask = int_tuple_list_vector(mask) assert mask in self.output_range() m = self.output_size() return type(self)([Bin(y, m).squeeze_by_mask(mask).int for y in self], m=Bin(mask).hw)
def __getitem__(self, lst): assert len(lst) == self.num_in x = Bin.concat(Bin.array(lst, n=self.width_in)) y = self.s[x] return Bin(y, self.width_out).split(parts=self.num_out)