def has_probability_one(self, output_diff): """Return whether the input diff propagates to the output diff with probability one. >>> from arxpy.bitvector.core import Constant, Variable >>> from arxpy.differential.difference import XorDiff >>> from arxpy.differential.derivative import XDA >>> alpha = XorDiff(Constant(0, 4)), XorDiff(Constant(0, 4)) >>> f = XDA(alpha) >>> f.has_probability_one(XorDiff(Constant(0, 4))) 0b1 """ is_possible = self.is_possible(output_diff) a, b = [d.val for d in self.input_diff] c = output_diff.val n = a.width def eq(x, y, z): if not isinstance(x, core.Constant): if isinstance(y, core.Constant): return eq(y, x, z) elif isinstance(z, core.Constant): return eq(z, x, y) return (~x ^ y) & (~x ^ z) # not optimized return is_possible & operation.BvComp( (~eq(a, b, c))[n-2:], core.Constant(0, n - 1) )
def is_valid(self): """Return the bv expression for non-zero propagation probability. >>> from arxpy.bitvector.core import Constant >>> from arxpy.diffcrypt.difference import DiffVar >>> from arxpy.diffcrypt.differential import RXDBvAdd >>> a, b, c = DiffVar("a", 8), DiffVar("b", 8), DiffVar("c", 8) >>> rxda = RXDBvAdd([a, b], c) >>> rxda.is_valid() # doctest: +ELLIPSIS 0b111111 == (~(((((c >> 0x01) ^ ((a >> 0x01) ^ (b >> 0x01))) ... >>> zero = Constant(0, 8) >>> rxda.is_valid().xreplace({a: zero, b: zero, c: zero}) 0b1 """ # (I ^ SHL)(da ^ db ^ dc)[1:] <= SHL((da ^ dc) OR (db ^ dc))[1:] alpha, beta = self.input_diff gamma = self.output_diff # da, db, dc = alpha[:1], beta[:1], gamma[:1] # boolector error da, db, dc = alpha >> 1, beta >> 1, gamma >> 1 # ignore LSB lhs = (da ^ db ^ dc) ^ ((da ^ db ^ dc) << 1) rhs = ((da ^ dc) | (db ^ dc)) << 1 def bitwise_implication(x, y): return (~x) | y n = lhs.width return operation.BvComp( bitwise_implication(lhs[n - 2:1], rhs[n - 2:1]), # ignore MSB, LSB ~core.Constant(0, n - 2))
def lz(x): if are_cte_differences: return extraop.LeadingZeros(x) else: aux = core.Variable("{}_{}lz".format(prefix, self._i_auxvar), x.width) self._i_auxvar += 1 assertions.append(operation.BvComp(aux, extraop.LeadingZeros(x))) return aux
def rev(x): if are_cte_differences: return extraop.Reverse(x) else: aux = core.Variable("{}_{}rev".format(prefix, self._i_auxvar), x.width) self._i_auxvar += 1 assertions.append(operation.BvComp(aux, extraop.Reverse(x))) return aux
def is_possible(self, output_diff): """Return whether the given output `RXDiff` is possible. >>> from arxpy.bitvector.core import Constant, Variable >>> from arxpy.differential.difference import RXDiff >>> from arxpy.differential.derivative import RXDA >>> alpha = RXDiff(Constant(0, 4)), RXDiff(Constant(0, 4)) >>> f = RXDA(alpha) >>> beta = RXDiff(Constant(0, 4)) >>> f.is_possible(beta) 0b1 >>> a0, a1, b = Variable("a0", 4), Variable("a1", 4), Variable("b", 4) >>> alpha = RXDiff(a0), RXDiff(a1) >>> f = RXDA(alpha) >>> beta = RXDiff(b) >>> result = f.is_possible(beta) >>> result # doctest:+NORMALIZE_WHITESPACE 0b11 == (~(((((a0[:1]) ^ (a1[:1]) ^ (b[:1])) << 0b001) ^ (a0[:1]) ^ (a1[:1]) ^ (b[:1]))[:1]) | ((((a0[:1]) ^ (b[:1])) | ((a1[:1]) ^ (b[:1])))[1:])) >>> result.xreplace({a0: Constant(0, 4), a1: Constant(0, 4), b: Constant(0, 4)}) 0b1 >>> a1 = Constant(0, 4) >>> alpha = RXDiff(a0), RXDiff(a1) >>> f = RXDA(alpha) >>> beta = RXDiff(b) >>> result = f.is_possible(beta) >>> result # doctest:+NORMALIZE_WHITESPACE 0b11 == (~(((((a0[:1]) ^ (b[:1])) << 0b001) ^ (a0[:1]) ^ (b[:1]))[:1]) | ((((a0[:1]) ^ (b[:1])) | (b[:1]))[1:])) See `Derivative.is_possible` for more information. """ # (I ^ SHL)(da ^ db ^ dc)[1:] <= SHL((da ^ dc) OR (db ^ dc))[1:] # one = core.Constant(1, self.input_diff[0].val.width) # alt v1 alpha, beta = [d.val for d in self.input_diff] gamma = output_diff.val # da, db, dc = alpha >> one, beta >> one, gamma >> one # alt v1 da, db, dc = alpha[:1], beta[:1], gamma[:1] # ignore LSB one = core.Constant(1, da.width) # lhs = (da ^ db ^ dc) ^ ((da ^ db ^ dc) << one) # alt v1 # rhs = ((da ^ dc) | (db ^ dc)) << one lhs = ((da ^ db ^ dc) ^ ((da ^ db ^ dc) << one))[:1] rhs = (((da ^ dc) | (db ^ dc)) << one)[:1] def bitwise_implication(x, y): return (~x) | y # alt v1 # n = lhs.width # return operation.BvComp( # bitwise_implication(lhs[n - 2:1], rhs[n - 2:1]), # ignore MSB, LSB # ~ core.Constant(0, n - 2)) return operation.BvComp(bitwise_implication(lhs, rhs), ~core.Constant(0, lhs.width)) # alt v1
def is_possible(self, output_diff): """Return whether the given output `XorDiff` is possible. >>> from arxpy.bitvector.core import Constant, Variable >>> from arxpy.differential.difference import XorDiff >>> from arxpy.differential.derivative import XDA >>> alpha = XorDiff(Constant(0, 4)), XorDiff(Constant(0, 4)) >>> f = XDA(alpha) >>> beta = XorDiff(Constant(0, 4)) >>> f.is_possible(beta) 0b1 >>> a0, a1, b = Variable("a0", 4), Variable("a1", 4), Variable("b", 4) >>> alpha = XorDiff(a0), XorDiff(a1) >>> f = XDA(alpha) >>> beta = XorDiff(b) >>> result = f.is_possible(beta) >>> result 0x0 == ((~(a0 << 0x1) ^ (a1 << 0x1)) & (~(a0 << 0x1) ^ (b << 0x1)) & ((a0 << 0x1) ^ b ^ a0 ^ a1)) >>> result.xreplace({a0: Constant(0, 4), a1: Constant(0, 4), b: Constant(0, 4)}) 0b1 >>> a1 = Constant(0, 4) >>> alpha = XorDiff(a0), XorDiff(a1) >>> f = XDA(alpha) >>> beta = XorDiff(b) >>> result = f.is_possible(beta) >>> result 0x0 == (~(a0 << 0x1) & ~(b << 0x1) & (a0 ^ b)) See `Derivative.is_possible` for more information. """ a, b = [d.val for d in self.input_diff] c = output_diff.val one = core.Constant(1, a.width) def eq(x, y, z): if not isinstance(x, core.Constant): if isinstance(y, core.Constant): return eq(y, x, z) elif isinstance(z, core.Constant): return eq(z, x, y) return (~x ^ y) & (~x ^ z) def xor_shift(x, y, z): if not isinstance(x, core.Constant): if isinstance(y, core.Constant): return xor_shift(y, x, z) elif isinstance(z, core.Constant): return xor_shift(z, x, y) return (x ^ y ^ z ^ (x << one)) return operation.BvComp( eq(a << one, b << one, c << one) & xor_shift(a, b, c), core.Constant(0, a.width))
def is_possible(self, output_diff): """Return whether the given output `XorDiff` is possible. >>> from arxpy.bitvector.core import Constant, Variable >>> from arxpy.differential.difference import XorDiff >>> from arxpy.differential.derivative import XDCA >>> alpha, cte = XorDiff(Constant(0, 4)), Constant(1, 4) >>> f = XDCA(alpha, cte) >>> f.is_possible(XorDiff(Constant(0, 4))) 0b1 >>> u, v = Variable("u", 4), Variable("v", 4) >>> f = XDCA(XorDiff(u), cte) >>> result = f.is_possible(XorDiff(v)) >>> result # doctest: +NORMALIZE_WHITESPACE 0x0 == (((u << 0x1) & (v << 0x1) & ~(0x9 ^ u ^ v) & (~((~(u << 0x1) & ~(v << 0x1)) << 0x1) | (~((~(u << 0x1) & ~(v << 0x1)) << 0x1) ^ ((0x9 & ~(u << 0x1) & ~(v << 0x1)) + ~((~(u << 0x1) & ~(v << 0x1)) << 0x1)) ^ (0x9 & ~(u << 0x1) & ~(v << 0x1))))) | (~(u << 0x1) & ~(v << 0x1) & (u ^ v))) >>> result.xreplace({u: Constant(0, 4), v: Constant(0, 4)}) 0b1 See `Derivative.is_possible` for more information. """ u = self.input_diff[0].val v = output_diff.val a = self.op.constant effective_width = self._effective_width index_one = self._index_first_one if effective_width == 0: return operation.BvComp(u, v) elif effective_width == 1: return operation.BvComp(u[index_one:], v[index_one:]) & \ operation.BvComp(~u[index_one] ^ (u[index_one+1] ^ v[index_one+1]), core.Constant(1, 1)) else: if index_one == 0: c = core.Constant(1, 1) else: c = operation.BvComp(u[index_one-1:], v[index_one-1:]) u = difference.XorDiff(u[:index_one]) v = difference.XorDiff(v[:index_one]) der = type(self)([u], a[:index_one]) return c & der._is_possible(v)
def has_probability_one(self, output_diff): """Return whether the input diff propagates to the output diff with probability one. >>> from arxpy.bitvector.core import Constant, Variable >>> from arxpy.differential.difference import XorDiff >>> from arxpy.differential.derivative import XDCA >>> alpha, cte = XorDiff(Constant(0, 4)), Constant(1, 4) >>> f = XDCA(alpha, cte) >>> f.has_probability_one(XorDiff(Constant(0, 4))) 0b1 >>> u, v = Variable("u", 4), Variable("v", 4) >>> f = XDCA(XorDiff(u), cte) >>> result = f.has_probability_one(XorDiff(v)) >>> result 0xf == (~(u << 0x1) & ~(v << 0x1) & ~(u ^ v)) >>> result.xreplace({u: Constant(0, 4), v: Constant(0, 4)}) 0b1 """ u = self.input_diff[0].val v = output_diff.val a = self.op.constant effective_width = self._effective_width index_one = self._index_first_one if effective_width == 0: return operation.BvComp(u, v) elif effective_width == 1: return operation.BvComp(u[index_one:], v[index_one:]) & \ operation.BvComp(~u[index_one] ^ (u[index_one+1] ^ v[index_one+1]), core.Constant(1, 1)) else: if index_one == 0: c = core.Constant(1, 1) else: c = operation.BvComp(u[index_one-1:], v[index_one-1:]) u = difference.XorDiff(u[:index_one]) v = difference.XorDiff(v[:index_one]) der = type(self)([u], a[:index_one]) return c & der._has_probability_one(v)
def _is_possible(self, output_diff, debug=False): u = self.input_diff[0].val v = output_diff.val a = self.op.constant n = a.width one = core.Constant(1, n) assert self._effective_width == n - 1 def carry(x, y): # carry[i] = Maj(x[i-1], y[i-1], carry[i-1]) return (x + y) ^ x ^ y # # S_0 well defined case11_ = (u << one) & (v << one) # i-bit is True if S_i = 11* case00_ = (~(u << one)) & (~(v << one)) # i-bit is True if S_i = 00* case__1 = u ^ v a = a << one # i-bit holds a[i-1] local_eq_a = ~(a ^ (a << one)) # i-bit is True if a[i-1] == a[i-2] case00_prev = case00_ << one # i-bit is True if S_{i-1} = 00* c = carry(local_eq_a & case00_, (~case00_prev)) case001 = case00_ & case__1 bad_case11_ = case11_ & ~(case__1 ^ local_eq_a) & (c | ~case00_prev) bad_events = (case001 | bad_case11_) is_valid = operation.BvComp(bad_events, core.Constant(0, bad_events.width)) if debug: print("\n\n ~~ ") print("u: ", u.bin()) print("v: ", v.bin()) print("a: ", a.bin()) print("case11_: ", case11_.bin()) print("case00_: ", case00_.bin()) print("case__1: ", case__1.bin()) print("case001: ", case001.bin()) print("case00_prev: ", case00_prev.bin()) print("local_eq_a: ", local_eq_a.bin()) print("c: ", c.bin()) print("bad_case11_: ", bad_case11_.bin()) print("bad_events: ", bad_events.bin()) print("is_valid :", is_valid.bin()) print("\n\n") return is_valid
def _has_probability_one(self, output_diff): u = self.input_diff[0].val v = output_diff.val a = self.op.constant n = a.width assert self._effective_width == n - 1 one = core.Constant(1, n) def all_ones(width): return ~ core.Constant(0, width) case00_ = (~(u << one)) & (~(v << one)) # i-bit is True if S_i = 00* case__1 = u ^ v case000 = case00_ & (~case__1) return operation.BvComp(case000, all_ones(n))
def is_valid(self): """Return the bv expression for non-zero propagation probability. >>> from arxpy.bitvector.core import Constant >>> from arxpy.diffcrypt.difference import DiffVar >>> from arxpy.diffcrypt.differential import XDBvAdd >>> a, b, c = DiffVar("a", 8), DiffVar("b", 8), DiffVar("c", 8) >>> xda = XDBvAdd([a, b], c) >>> xda.is_valid() # doctest: +ELLIPSIS 0x00 == (((~(a << 0x01) ^ (b << 0x01)) & (~(a << 0x01) ... >>> zero = Constant(0, 8) >>> xda.is_valid().xreplace({a: zero, b: zero, c: zero}) 0b1 """ a, b = self.input_diff c = self.output_diff def eq(x, y, z): return (~x ^ y) & (~x ^ z) return operation.BvComp( eq(a << 1, b << 1, c << 1) & (a ^ b ^ c ^ (b << 1)), core.Constant(0, a.width))
def _generate(self): """Generate the SMT problem.""" self.assertions = [] # Forbid zero input difference with XOR difference if self.ch.diff_type == difference.XorDiff: if self.parent_ch is not None and self.parent_ch.outer_ch == self.ch: inner_noutputs = len(self.parent_ch.inner_ch.output_diff) non_zero_input_diff = self.ch.input_diff[:-inner_noutputs] else: non_zero_input_diff = self.ch.input_diff non_zero_input_diff = functools.reduce(operation.Concat, non_zero_input_diff) zero = core.Constant(0, non_zero_input_diff.width) self.assertions.append( operation.BvNot(operation.BvComp(non_zero_input_diff, zero))) # Assertions of the weights of the non-deterministic steps self.op_weights = [] for var, propagation in self.ch.items(): if isinstance(propagation, differential.Differential): self.assertions.append(propagation.is_valid()) weight_value = propagation.weight() weight_var = core.Variable(propagation._weight_var_name(), weight_value.width) self.assertions.append( operation.BvComp(weight_var, weight_value)) self.op_weights.append(weight_var) else: self.assertions.append(operation.BvComp(var, propagation)) # Characteristic weight assignment max_value = 0 for ow in self.op_weights: max_value += (2**ow.width) - 1 width = max(max_value.bit_length(), 1) # for trivial characteristic ext_op_weights = [] for ow in self.op_weights: ext_op_weights.append(operation.ZeroExtend(ow, width - ow.width)) name_ch_weight = "w_{}_{}".format( ''.join([str(i) for i in self.ch.input_diff]), ''.join([str(i) for i in self.ch.output_diff])) ch_weight = core.Variable(name_ch_weight, width) self.assertions.append(operation.BvComp(ch_weight, sum(ext_op_weights))) # Condition between the weight and the target weight weight_function = self.ch.get_weight_function() target_weight = int(weight_function(self.target_weight)) width = max(ch_weight.width, target_weight.bit_length()) self.ch_weight = operation.ZeroExtend(ch_weight, width - ch_weight.width) if self.equality: self.assertions.append( operation.BvComp(self.ch_weight, target_weight)) else: self.assertions.append( operation.BvUlt(self.ch_weight, target_weight)) self.assertions = tuple(self.assertions)