arxpy/differential/tests/preimageXDA.py

arxpy/differential/tests/preimageXDAC.py

arxpy/differential/derivative.py
class XDS(XDA):
    """Represent the `XorDiff` `Derivative` of `BvSub`.

        >>> from arxpy.bitvector.core import Constant, Variable
        >>> from arxpy.differential.difference import XorDiff
        >>> from arxpy.differential.derivative import XDS
        >>> alpha = XorDiff(Constant(0, 4)), XorDiff(Constant(0, 4))
        >>> f = XDS(alpha)
        >>> x = Constant(0, 4), Constant(0, 4)
        >>> f.eval(*x)  # f(x + alpha) - f(x)
        XorDiff(0x0)
        >>> f.max_weight(), f.error(), f.num_frac_bits()
        (3, 0, 0)

    The differential model of the modular substraction
    is the same as the model for the modular addition
    (see `XDA`).
    """

    diff_type = difference.XorDiff
    op = operation.BvSub
arxpy/differential/derivative.py
class XDA(Derivative):
    """Represent the `XorDiff` `Derivative` of `BvAdd`.

        >>> 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)
        >>> x = Constant(0, 4), Constant(0, 4)
        >>> f.eval(*x)  # f(x + alpha) - f(x)
        XorDiff(0x0)
        >>> f.max_weight(), f.error(), f.num_frac_bits()
        (3, 0, 0)

    """

    diff_type = difference.XorDiff
    op = operation.BvAdd

    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))

        # # https://doi.org/10.1007/3-540-36231-2_5
        # dx1, dx2 = [d.val for d in self.input_diff]
        # dy = output_diff.val
        # one = core.Constant(1, dx1.width)
        #
        # impossible = ~( ((dx1^dy) << one) | ((dx2^dy) << one) ) & (dx1^dx2^dy^(dx2<<one))
        #
        # return operation.BvComp(impossible, core.Constant(0, dx1.width))

    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 weight(self, output_diff):
        """Return the weight of a possible output `XorDiff`.

        For XDA, the probability of a valid differential is :math:`2^{-i}`
        for some :math:`i`, and the weight is defined as the exponent
        :math:`i`.

            >>> from arxpy.bitvector.core import Constant, Variable
            >>> from arxpy.differential.difference import XorDiff
            >>> from arxpy.differential.derivative import XDA
            >>> n = 4
            >>> alpha = XorDiff(Constant(0, n)), XorDiff(Constant(0, n))
            >>> f = XDA(alpha)
            >>> f.weight(XorDiff(Constant(0, n)))
            0b00
            >>> a0, a1, b = Variable("a0", n), Variable("a1", n), Variable("b", n)
            >>> alpha = XorDiff(a0), XorDiff(a1)
            >>> f = XDA(alpha)
            >>> result = f.weight(XorDiff(b))
            >>> result  # doctest:+NORMALIZE_WHITESPACE
            (0b0 :: (~((a1 ^ ~a0) & (b ^ ~a0))[0])) + (0b0 :: (~((a1 ^ ~a0) & (b ^ ~a0))[1])) +
            (0b0 :: (~((a1 ^ ~a0) & (b ^ ~a0))[2]))
            >>> result.xreplace({a0: Constant(0, n), a1: Constant(0, n), b: Constant(0, n)})
            0b00

        See `Derivative.weight` for more information.
        """
        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)

        return extraop.PopCount((~eq(a, b, c))[n-2:])  # ignore MSB

        # # https://doi.org/10.1007/3-540-36231-2_5
        # dx1, dx2 = [d.val for d in self.input_diff]
        # dy = output_diff.val
        # one = core.Constant(1, dx1.width)
        #
        # w = ((dx1^dy) << one) | ((dx2^dy) << one)
        #
        # return extraop.PopCount(w)

    def max_weight(self):
        width = self.input_diff[0].val.width - 1  # MSB is ignored
        return width  # as an integer

    def exact_weight(self, output_diff):
        return int(self.weight(output_diff))

    def num_frac_bits(self):
        return 0

    def error(self):
        return 0
arxpy/differential/tests/preimageRXDA.py

arxpy/differential/derivative.py
def _tuplify(seq):
    def __init__(self, input_diff):  # noqa: 102
    def __str__(self):
    def eval(self, *x):
    def is_possible(self, output_diff):
    def has_probability_one(self, output_diff):
    def weight(self, output_diff):
    def max_weight(self):
    def exact_weight(self, output_diff):
    def num_frac_bits(self):
    def error(self):
    def _replace_input_diff(self, new_input_diff, **kwargs):
    def is_possible(self, output_diff):
        def eq(x, y, z):
        def xor_shift(x, y, z):
    def has_probability_one(self, output_diff):
        def eq(x, y, z):
    def weight(self, output_diff):
        def eq(x, y, z):
    def max_weight(self):
    def exact_weight(self, output_diff):
    def num_frac_bits(self):
    def error(self):
    def is_possible(self, output_diff):
        def bitwise_implication(x, y):
    def has_probability_one(self, output_diff):
    def decimal2bin(number, precision):
    def weight(self, output_diff):
        def bitwise_implication(x, y):
    def max_weight(self):
    def exact_weight(self, output_diff):
        def bitwise_implication(x, y):
    def num_frac_bits(self):
    def error(self):
    def __init__(self, input_diff, cte):  # noqa: 102
    def __str__(self):
    def _is_possible(self, output_diff, debug=False):
        def carry(x, y):
    def is_possible(self, output_diff):
    def _has_probability_one(self, output_diff):
        def all_ones(width):
    def has_probability_one(self, output_diff):
    def weight(self, output_diff, prefix=None, debug=False, version=2):
    def _weight(self, output_diff, prefix=None, debug=False, version=2):
        def rev(x):
        def lz(x):
        def carry(x, y):
        def rev_carry(x, y):
        def smart_add(x, y):
        def smart_sub(x, y):
    def max_weight(self):
    def exact_weight(self, output_diff):
        def probability2weight(pr):
    def num_frac_bits(self):
    def _len2error(cls, n, precision, verbose=False):
        def get_chainlen2biterror():
            def bit_error(chainlen):
        def error_chainlen(chainlen):
        def error_decomposition(decomposition):
        def get_decompositions(diff_len):
    def error(self):
    def _replace_input_diff(self, new_input_diff, **kwargs):
class Derivative(object):
class XDA(Derivative):
class XDS(XDA):
class RXDA(Derivative):
class XDCA(Derivative):
arxpy/bitvector/core.py
class Constant(basic.Atom, Term):
    """Represent bit-vector constants.

    Bit-vector constants are interpreted as unsigned integers in base 2,
    that is, a bit-vector :math:`(x_{n-1}, \dots, x_1, x_0)` represents
    the non-negative integer :math:`x_0 + 2 x_1 + \dots + 2^{n-1} x_{n-1}`.

    Args:
        val: the integer value.
        width: the bit-width.

    ::

        >>> from arxpy.bitvector.core import Constant
        >>> Constant(3, 12)
        0x003
        >>> Constant(0b11, 12)
        0x003
        >>> Constant(0x003, 12)
        0x003
        >>> Constant(3, 12).vrepr()
        'Constant(0b000000000011, width=12)'

    """

    def __int__(self):
        return self.val

    def __hash__(self):
        return super().__hash__()

    def __eq__(self, other):
        """Override == operator."""
        if isinstance(other, int):
            return self.val == other
        elif isinstance(other, Constant) and self.width == other.width:
            return self.val == other.val
        else:
            return False

    # def __index__(self):
    #     """Return an int to be used inside a slice [ : : ]."""
    #     return self.int

    def _hashable_content(self):
        """Return a tuple of information about self to compute its hash."""
        return self.val, self.width

    @classmethod
    def class_key(cls):
        """Return the key (identifier) of the class for sorting."""
        return 1, 0, cls.__name__

    # end Integer

    __slots__ = ["_val"]

    def __new__(cls, val, width):
        assert isinstance(val, int) and 0 <= val < 2 ** width
        obj = Term.__new__(cls, width=width)
        obj._val = val
        return obj

    def __bool__(self):
        if self.width == 1:
            return self == Constant(1, 1)
        else:
            raise AttributeError("only 1-bit constants implement bool()")

    @property
    def val(self):
        """The integer represented by the bit-vector constant."""
        return self._val

    @property
    def formula_size(self):
        """The formula size of the constant."""
        def log2(n):
            return int(math.ceil(math.log(n, 2)))

        def bin_enc(n):
            return 1 + log2(n + 1)

        return 1 + log2(int(self) + 1) + bin_enc(self.width)

    def bin(self):
        """Return the binary representation.

            >>> from arxpy.bitvector.core import Constant
            >>> print(Constant(3, 4).bin())
            0b0011
            >>> print(Constant(4, 6).bin())
            0b000100

        """
        width = self.width + 2  # 2 due to '0b'
        return format(self.val, r'0=#{}b'.format(width))

    def hex(self):
        """Return the hexadecimal representation.

            >>> from arxpy.bitvector.core import Constant
            >>> print(Constant(3, 4).hex())
            0x3

        """
        assert self.width % 4 == 0
        width = (self.width // 4) + 2
        return format(self.val, '0=#{}x'.format(width))

    def oct(self):
        """Return the octal representation.

            >>> from arxpy.bitvector.core import Constant
            >>> print(Constant(4, 6).oct())
            0o04

        """
        assert self.width % 3 == 0
        width = (self.width // 3) + 2
        return format(self.val, '0=#{}o'.format(width))
arxpy/bitvector/context.py
class Simplification(StatefulContext):
    """Control the Simplification context.

    Control whether or not bit-vector expressions are automatically simplified.
    By default, automatic simplification is enabled.

        >>> from arxpy.bitvector.core import Variable
        >>> from arxpy.bitvector.context import Simplification
        >>> x, y = Variable("x", 8), Variable("y", 8)
        >>> (x | y) | x
        x | y
        >>> with Simplification(False):
        ...     expr = (x | y) | x
        >>> expr
        x | x | y

    When the Simplification context is disabled, the `Cache` context is
    also disabled.

    Note:
        Disabling `Simplification` and `Validation` speeds up
        non-symbolic computations with bit-vectors.
    """

    current_context = True

    def __init__(self, new_context):
        """Initialize the context."""
        assert new_context in [True, False]
        super().__init__(new_context)

    def __enter__(self):
        if self.new_context is False:
            self.cache_context = Cache(False)
            self.cache_context.__enter__()
        super().__enter__()

    def __exit__(self, *args):
        if self.new_context is False:
            self.cache_context.__exit__()
        super().__exit__()
arxpy/differential/difference.py
class RXDiff(Difference):
    """Represent rotational-XOR (RX) differences.

    The pair ``(x, (x <<< 1) ^ d)`` has RX difference ``d``.
    In other words,  the RX difference of two `Term` ``x`` and ``y``
    is defined as ``(x <<< 1) ^ y``.

    See `Difference` for more information.

        >>> from arxpy.bitvector.core import Constant, Variable
        >>> from arxpy.differential.difference import RXDiff
        >>> x, y = Constant(0b000, 3), Constant(0b000, 3)
        >>> alpha = RXDiff.from_pair(x, y)
        >>> alpha
        RXDiff(0b000)
        >>> alpha.get_pair_element(x)
        0b000
        >>> x, y = Constant(0b000, 3), Constant(0b001, 3)
        >>> alpha = RXDiff.from_pair(x, y)
        >>> alpha
        RXDiff(0b001)
        >>> alpha.get_pair_element(x)
        0b001
        >>> k = Variable("k", 8)
        >>> alpha = RXDiff.from_pair(k, k)
        >>> alpha
        RXDiff(k ^ (k <<< 1))
        >>> alpha.get_pair_element(k)
        k
    """

    diff_op = RXOp
    inv_diff_op = RXInvOp

    @classmethod
    def derivative(cls, op, input_diff):
        """Return the derivative of ``op`` at the point ``input_diff``.

        See `Difference.derivative` for more information.

            >>> from arxpy.bitvector.core import Variable, Constant
            >>> from arxpy.bitvector.operation import BvAdd, BvXor, RotateLeft
            >>> from arxpy.bitvector.extraop import make_partial_operation
            >>> from arxpy.differential.difference import RXDiff
            >>> d1, d2 = RXDiff(Variable("d1", 8)), RXDiff(Variable("d2", 8))
            >>> RXDiff.derivative(BvXor, [d1, d2])
            RXDiff(d1 ^ d2)
            >>> Xor1 = make_partial_operation(BvXor, tuple([None, Constant(1, 8)]))
            >>> RXDiff.derivative(Xor1, d1)
            RXDiff(0x03 ^ d1)
            >>> Rotate1 = make_partial_operation(RotateLeft, tuple([None, 1]))
            >>> RXDiff.derivative(Rotate1, d1)
            RXDiff(d1 <<< 1)
            >>> RXDiff.derivative(BvAdd, [d1, d2])
            RXDA(RXDiff(d1), RXDiff(d2))

        """
        input_diff = _tuplify(input_diff)
        assert len(input_diff) == sum(op.arity)

        msg = "invalid arguments: op={}, input_diff={}".format(
            op.__name__,
            [d.vrepr() if isinstance(d, core.Term) else d for d in input_diff])

        if not all(isinstance(diff, cls) for diff in input_diff):
            raise ValueError(msg)

        if op == operation.BvNot:
            return input_diff[0]

        if op == operation.BvXor:
            return cls(op(*[d.val for d in input_diff]))

        if op == operation.BvAdd:
            from arxpy.differential import derivative
            return derivative.RXDA(input_diff)

        # Concact, BvSub

        if issubclass(op, extraop.PartialOperation):
            if op.base_op == operation.BvXor:
                assert len(input_diff) == 1
                d1 = input_diff[0]
                val = op.fixed_args[0] if op.fixed_args[0] is not None else op.fixed_args[1]
                d2 = cls.from_pair(val, val)
                input_diff = [d1, d2]
                return cls(op.base_op(*[d.val for d in input_diff]))

            if op.base_op in [operation.RotateLeft, operation.RotateRight]:
                if op.fixed_args[0] is None and op.fixed_args[1] is not None:
                    assert len(input_diff) == 1
                    d = input_diff[0]
                    return cls(op.base_op(d.val, op.fixed_args[1]))
                else:
                    raise ValueError(msg)

            # BvShl, Extract

        if hasattr(op, "rx_derivative"):
            return op.rx_derivative(input_diff)

        raise ValueError(msg)
arxpy/differential/tests/preimageXDS.py

arxpy/differential/difference.py
class Difference(object):
    """Represent differences.

    The *difference* between two `Term` :math:`x` and :math:`y`
    is defined as :math:`\\alpha = y - x`,
    where the *difference operation* :math:`-`
    is a bit-vector `Operation`. In other words, the pair
    :math:`(x, x + \\alpha)` has difference :math:`\\alpha`,
    where :math:`+` is the inverse of the difference operation.

    The most common difference used in differential cryptanalysis
    is the XOR difference `XorDiff` (where the difference operation
    is `BvXor`). Other examples are the additive difference
    (where the difference operation is `BvSub`) or the rotational-XOR
    difference `RXDiff`.

    Note that arithmetic with differences is not supported.
    For example, two `Difference` objects ``d1`` and ``d2``
    cannot be XORed, i.e., ``d1 ^ d2``.
    This can be done instead by performing the arithmetic with
    the difference values and converting the resulting
    `Term` to a difference, that is, ``Difference(d1.val ^ d2.val)``

    This class is not meant to be instantiated but to provide a base
    class for the different types of differences.

    Attributes:
        val: a `Term` representing the value of the difference.
        diff_op: the difference `Operation`.
        inv_diff_op: the inverse of the difference operation.

    """
    diff_op = None
    inv_diff_op = None

    def __init__(self, value):
        assert isinstance(value, core.Term)
        self.val = value

    def __str__(self):
        """Return the non-verbose string representation."""
        return "{}({})".format(type(self).__name__, str(self.val))

    __repr__ = __str__

    def __hash__(self):
        return hash(self.val)

    def __eq__(self, other):
        if isinstance(other, type(self)):
            return self.val == other.val
        else:
            return False

    def xreplace(self, rule):
        """Replace occurrences of differences within the expression.

        The argument ``rule`` is a dict-like object representing
        the replacement rule.

        This method is similar to SymPy `xreplace
        <https://docs.sympy.org/latest/modules/core.html?highlight=xreplace#
        sympy.core.basic.Basic.xreplace>`_ but with the restriction that
        only differences objects are allowed in ``rule``.
        """
        for d in rule:
            assert isinstance(d, type(self)) and isinstance(rule[d], type(self))

        rule = {d.val: rule[d].val for d in rule}
        return type(self)(self.val.xreplace(rule))

    def vrepr(self):
        """Return a verbose string representation."""
        return "{}({})".format(type(self).__name__, self.val.vrepr())

    @classmethod
    def from_pair(cls, x, y):
        """Return the `Difference` :math:`\\alpha = y - x` given two `Term`."""
        assert isinstance(x, core.Term)
        assert isinstance(y, core.Term)
        return cls(cls.diff_op(x, y))  # The order of the operands is important

    def get_pair_element(self, x):
        """Return the `Term` :math:`y` such that :math:`y = \\alpha + x`."""
        assert isinstance(x, core.Term)
        return self.inv_diff_op(x, self.val)

    @classmethod
    def derivative(cls, op, input_diff):
        """Return the derivative of ``op`` at the point ``input_diff``.

        The derivative of an `Operation` :math:`f`
        at the point :math:`\\alpha` (also called the input difference)
        is defined as :math:`f_{\\alpha} (x) = f(x + \\alpha) - f(x)`.
        Note that :math:`f_{\\alpha} (x)` is the difference of
        :math:`(f(x), f(x + \\alpha))`.

        If :math:`f` has multiple operands, :math:`\\alpha` is a list
        containing the `Difference` of each operand and the
        computation :math:`x + \\alpha` is defined component-wise, that is,
        :math:`x = (x_1, \dots, x_n)`, :math:`\\alpha = (\\alpha_1, \dots, \\alpha_n)`,
        and :math:`x + \\alpha = (x_1 + \\alpha_1, \dots, x_n + \\alpha_n)`.

        For some operations, there is a unique output difference :math:`\\beta`
        for every input difference :math:`\\alpha`, that is, :math:`f_{\\alpha}(x) = \\beta`
        is a constant function. In this case, this method returns the `Difference`
        :math:`\\beta`. Otherwise, it returns a `Derivative` object representing
        :math:`f_{\\alpha}`.

        Operations with scalar operands are not supported, but these
        operands can be removed with `make_partial_operation` and
        the derivative of the resulting operator can then be computed.

        Args:
            op:  a bit-vector operator
            input_diff: a list containing the difference of each operand
        """
        raise NotImplementedError("subclasses need to override this method")
arxpy/differential/derivative.py
class RXDA(Derivative):
    """Represent the `RXDiff` `Derivative` of `BvAdd`.

            >>> from arxpy.bitvector.core import Constant, Variable
            >>> from arxpy.differential.difference import RXDiff
            >>> from arxpy.differential.derivative import RXDA
            >>> alpha = RXDiff(Constant(0, 16)), RXDiff(Constant(0, 16))
            >>> f = RXDA(alpha)
            >>> x = Constant(0, 16), Constant(0, 16)
            >>> f.eval(*x)  # f(x + alpha) - f(x)
            RXDiff(0x0000)
            >>> f.max_weight(), f.error(), f.num_frac_bits()
            (136, 0.03999347239201656, 3)

    """

    diff_type = difference.RXDiff
    op = operation.BvAdd
    precision = 3  # 0, 2, 3, 6, 7, 8, 10

    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 has_probability_one(self, output_diff):
        """Return whether the input diff propagates to the output diff with probability one."""
        return core.Constant(0, 1)

    @staticmethod
    def decimal2bin(number, precision):
        """Return a binary representation of a positive real number."""
        assert number > 0
        binary_str = bin(int(number))
        frac = number - int(number)
        for i in range(precision):
            frac *= 2
            frac_bit = int(frac)
            if frac_bit == 1:
                frac -= frac_bit
                binary_str += '1'
            else:
                binary_str += '0'
        return binary_str

    def weight(self, output_diff):
        """Return the weight of a possible output `RXDiff`.

            >>> from arxpy.bitvector.core import Constant, Variable
            >>> from arxpy.differential.difference import RXDiff
            >>> from arxpy.differential.derivative import RXDA
            >>> n = 4
            >>> alpha = RXDiff(Constant(0, n)), RXDiff(Constant(0, n))
            >>> f = RXDA(alpha)
            >>> f.weight(RXDiff(Constant(0, n)))
            0b001001
            >>> a0, a1, b = Variable("a0", n), Variable("a1", n), Variable("b", n)
            >>> alpha = RXDiff(a0), RXDiff(a1)
            >>> f = RXDA(alpha)
            >>> result = f.weight(RXDiff(b))
            >>> result  # doctest:+NORMALIZE_WHITESPACE
            ((0x0 :: ((0b0 :: ((((a0[:1]) ^ (b[:1])) | ((a1[:1]) ^ (b[:1])))[0])) + (0b0 :: ((((a0[:1]) ^ (b[:1])) |
            ((a1[:1]) ^ (b[:1])))[1])))) << 0b000011) + (Ite(~((a0[1]) ^ (a1[1]) ^ (b[1])), 0b001001, 0b011000))
            >>> result.xreplace({a0: Constant(0, n), a1: Constant(0, n), b: Constant(0, n)})
            0b001001

        See `Derivative.weight` for more information.
        """
        # one = core.Constant(1, self.input_diff[0].val.width)  # alt v1
        # two = core.Constant(2, self.input_diff[0].val.width)

        alpha, beta = [d.val for d in self.input_diff]
        gamma = output_diff.val
        da, db, dc = alpha[:1], beta[:1], gamma[:1]  # alt v1
        # da, db, dc = alpha >> one, beta >> one, gamma >> one  # alt v1

        rhs = ((da ^ dc) | (db ^ dc))[da.width-2:]  # equiv to shift left
        # rhs = ((da ^ dc) | (db ^ dc)) << two  # alt v1
        hw = extraop.PopCount(rhs)

        max_hw = rhs.width - 1

        # (max_hw + 3) = maximum integer part
        k = self.__class__.precision  # num fraction bits
        weight_width = (max_hw + 3).bit_length() + k

        # let lhs = LSB(lhs) = da ^ db ^ dc
        #     rhs = LSB(rhs) = 0
        # case A (w=1.415): lhs => rhs
        # case B (w=3):     lhs ^ 1 => rhs
        # 1.415 = -log2(pr propagation of a rotational pair with offset 1)
        # bin(1.415) = 1.01101010001111010111

        n = alpha.width
        w_rotational_pair = -(math.log2((1 + 2**(1 - n) + 0.5 + 2 ** (-n))) - 2)
        w_rotational_pair = int(self.__class__.decimal2bin(w_rotational_pair, k), base=2)

        def bitwise_implication(x, y):
            return (~x) | y

        cte_part = operation.Ite(
            bitwise_implication(da[0] ^ db[0] ^ dc[0], core.Constant(0, 1)),
            core.Constant(w_rotational_pair, weight_width),
            core.Constant(3, weight_width) << k
        )

        hw_extend = operation.ZeroExtend(hw, weight_width - hw.width)

        return (hw_extend << k) + cte_part

    def max_weight(self):
        width = self.input_diff[0].val.width - 2  # LSB, MSB ignored
        return (width + 3) << self.__class__.precision  # as an integer

    def exact_weight(self, output_diff):
        """Return the weight without rounding to the closest integer.

        When the input/output differences have width smaller than 16,
        the exact weight does not coincide with the actual real weight.
        For example, for width=8, the error can be as high as 1.2.
        Moreover, in this case the exact weight is not always smaller
        than the weight.
        """
        alpha, beta = [d.val for d in self.input_diff]
        gamma = output_diff.val
        da, db, dc = alpha[:1], beta[:1], gamma[:1]

        one = core.Constant(1, da.width)

        rhs = ((da ^ dc) | (db ^ dc)) << one
        # rhs = ((da ^ dc) | (db ^ dc))[da.width-2:]  # alt v1
        hw = extraop.PopCount(rhs)

        result = int(hw)

        def bitwise_implication(x, y):
            return (~x) | y

        n = alpha.width
        w_rotational_pair = -(math.log2((1 + 2**(1 - n) + 0.5 + 2**(-n))) - 2)

        if bitwise_implication(da[0] ^ db[0] ^ dc[0], core.Constant(0, 1)) == core.Constant(1, 1):
            result += w_rotational_pair
        else:
            result += 3

        # if debug:
        #     print("\n\n ~~ ")
        #     print("da:           ", da.bin())
        #     print("db:           ", db.bin())
        #     print("dc:           ", dc.bin())
        #     print("rhs:          ", rhs.bin())
        #     print("hw:           ", int(hw))
        #     print("bw_implies:   ", bitwise_implication(da[0] ^ db[0] ^ dc[0], core.Constant(0, 1)).bin())
        #     print("result:       ", result)
        #     print("\n\n")

        return result

    def num_frac_bits(self):
        return self.__class__.precision

    def error(self):
        """"Return the maximum difference between the weight and the exact weight.

            >>> # docstring for debugging purposes
            >>> from arxpy.bitvector.core import Constant, Variable
            >>> from arxpy.differential.difference import RXDiff
            >>> from arxpy.differential.derivative import RXDA
            >>> old_prec = RXDA.precision
            >>> errors = []
            >>> n = 16
            >>> for p in range(0, 12):
            ...     RXDA.precision = p
            ...     alpha = RXDiff(Constant(0, n)), RXDiff(Constant(0, n))
            ...     f = RXDA(alpha)
            ...     errors.append(round(f.error(), 4))
            >>> RXDA.precision = old_prec
            >>> errors
            [0.415, 0.415, 0.165, 0.04, 0.04, 0.0087, 0.0087, 0.0009, 0.0009, 0.0009, 0.0009, 0.0004]

        As opposed to `XDCA`, the exact weight in `RXDA` is bigger.
        """
        n = self.input_diff[0].val.width
        k = self.__class__.precision

        w_rotational_pair = -(math.log2((1 + 2**(1 - n) + 0.5 + 2 ** (-n))) - 2)

        w_rotational_pair_approx = int(core.Constant(
            int(self.__class__.decimal2bin(w_rotational_pair, k), base=2),
            k + 1
        )) * 1.0 / 2**k

        # w_rotational_pair_approx < w_rotational_pair
        theo_error = w_rotational_pair - w_rotational_pair_approx

        if n < 16:
            theo_error += 1.02  # offset found empirically for n=8

        return theo_error
arxpy/bitvector/context.py
class Validation(StatefulContext):
    """Control the Validation context.

    Control whether or not arguments of bit-vector operators are validated.
    By default, validation of arguments is enabled.

    Note that when it is disabled,  Automatic Constant Conversion is no longer
    available (see `Operation`).

        >>> from arxpy.bitvector.core import Constant
        >>> from arxpy.bitvector.context import Validation
        >>> Constant(1, 8) + 1
        0x02
        >>> with Validation(False):
        ...     Constant(1, 5) + 2
        Traceback (most recent call last):
         ...
        AttributeError: 'int' object has no attribute 'width'

    Note:
        Disabling `Simplification` and `Validation` speeds up
        non-symbolic computations with bit-vectors.
    """

    current_context = True

    def __init__(self, new_context):
        """Initialize the context."""
        assert new_context in [True, False]
        super().__init__(new_context)
arxpy/differential/difference.py
class XorDiff(Difference):
    """Represent XOR differences.

    The XOR difference of two `Term` is given by the XOR
    of the terms. In other words, the *difference operation*
    of `XorDiff` is the `BvXor` (see `Difference`).

        >>> from arxpy.bitvector.core import Constant, Variable
        >>> from arxpy.differential.difference import XorDiff
        >>> x, y = Constant(0b000, 3), Constant(0b000, 3)
        >>> alpha = XorDiff.from_pair(x, y)
        >>> alpha
        XorDiff(0b000)
        >>> alpha.get_pair_element(x)
        0b000
        >>> x, y = Constant(0b010, 3), Constant(0b101, 3)
        >>> alpha = XorDiff.from_pair(x, y)
        >>> alpha
        XorDiff(0b111)
        >>> alpha.get_pair_element(x)
        0b101
        >>> k = Variable("k", 8)
        >>> alpha = XorDiff.from_pair(k, k)
        >>> alpha
        XorDiff(0x00)
        >>> alpha.get_pair_element(k)
        k
    """

    diff_op = operation.BvXor
    inv_diff_op = operation.BvXor

    @classmethod
    def derivative(cls, op, input_diff):
        """Return the derivative of ``op`` at the point ``input_diff``.

        See `Difference.derivative` for more information.

            >>> from arxpy.bitvector.core import Variable, Constant
            >>> from arxpy.bitvector.operation import BvAdd, BvXor, RotateLeft, BvSub
            >>> from arxpy.bitvector.extraop import make_partial_operation
            >>> from arxpy.differential.difference import XorDiff
            >>> d1, d2 = XorDiff(Variable("d1", 8)), XorDiff(Variable("d2", 8))
            >>> XorDiff.derivative(BvXor, [d1, d2])
            XorDiff(d1 ^ d2)
            >>> Xor1 = make_partial_operation(BvXor, tuple([None, Constant(1, 8)]))
            >>> XorDiff.derivative(Xor1, d1)
            XorDiff(d1)
            >>> Rotate1 = make_partial_operation(RotateLeft, tuple([None, 1]))
            >>> XorDiff.derivative(Rotate1, d1)
            XorDiff(d1 <<< 1)
            >>> XorDiff.derivative(BvAdd, [d1, d2])
            XDA(XorDiff(d1), XorDiff(d2))
            >>> XorDiff.derivative(BvSub, [d1, d2])
            XDS(XorDiff(d1), XorDiff(d2))
            >>> CteAdd1 = make_partial_operation(BvAdd, tuple([None, Constant(1, 8)]))
            >>> XorDiff.derivative(CteAdd1, d1)
            XDCA_0x01(XorDiff(d1))

        """
        input_diff = _tuplify(input_diff)
        assert len(input_diff) == sum(op.arity)

        msg = "invalid arguments: op={}, input_diff={}".format(
            op.__name__,
            [d.vrepr() if isinstance(d, core.Term) else d for d in input_diff])

        if not all(isinstance(diff, cls) for diff in input_diff):
            raise ValueError(msg)

        if op == operation.BvNot:
            return input_diff[0]

        if op == operation.BvXor:
            return cls(op(*[d.val for d in input_diff]))

        if op == operation.Concat:
            return cls(op(*[d.val for d in input_diff]))

        if op == operation.BvAdd:
            from arxpy.differential import derivative
            return derivative.XDA(input_diff)

        if op == operation.BvSub:
            from arxpy.differential import derivative
            return derivative.XDS(input_diff)

        if issubclass(op, extraop.PartialOperation):
            if op.base_op == operation.BvXor:
                assert len(input_diff) == 1
                d1 = input_diff[0]
                val = op.fixed_args[0] if op.fixed_args[0] is not None else op.fixed_args[1]
                d2 = cls.from_pair(val, val)
                input_diff = [d1, d2]
                return cls(op.base_op(*[d.val for d in input_diff]))

            if op.base_op == operation.BvAnd:
                assert len(input_diff) == 1
                d1 = input_diff[0]
                val = op.fixed_args[0] if op.fixed_args[0] is not None else op.fixed_args[1]
                if isinstance(val, core.Constant):
                    return cls(op.base_op(d1.val, val))

            if op.base_op in [operation.RotateLeft, operation.RotateRight]:
                if op.fixed_args[0] is None and op.fixed_args[1] is not None:
                    assert len(input_diff) == 1
                    d = input_diff[0]
                    return cls(op.base_op(d.val, op.fixed_args[1]))
                else:
                    raise ValueError(msg)

            if op.base_op in [operation.BvShl, operation.BvLshr]:
                if op.fixed_args[0] is None and op.fixed_args[1] is not None:
                    assert len(input_diff) == 1
                    d = input_diff[0]
                    return cls(op.base_op(d.val, op.fixed_args[1]))
                else:
                    raise ValueError(msg)

            if op.base_op == operation.Extract:
                if op.fixed_args[0] is None and op.fixed_args[1] is not None and op.fixed_args[2] is not None:
                    assert len(input_diff) == 1
                    d = input_diff[0]
                    return cls(op.base_op(d.val, op.fixed_args[1], op.fixed_args[2]))
                else:
                    raise ValueError(msg)

            if op.base_op == operation.Concat:
                assert len(input_diff) == 1
                d1 = input_diff[0]
                if op.fixed_args[0] is not None:
                    val = op.fixed_args[0]
                    input_diff = [cls.from_pair(val, val), d1]
                else:
                    val = op.fixed_args[1]
                    input_diff = [d1, cls.from_pair(val, val)]
                return cls(op.base_op(*[d.val for d in input_diff]))

            if op.base_op == operation.BvAdd:
                assert len(input_diff) == 1
                d = input_diff[0]
                cte = op.fixed_args[0] if op.fixed_args[0] is not None else op.fixed_args[1]
                from arxpy.differential import derivative
                return derivative.XDCA(d, cte)
            else:
                raise ValueError(msg)

        if hasattr(op, "xor_derivative"):
            return op.xor_derivative(input_diff)

        raise ValueError(msg)


Question: import collections
import doctest
import importlib
import itertools
import math
import tempfile
import unittest
import cffi
                import random
from hypothesis import given, example, settings  # unlimited, HealthCheck
from hypothesis.strategies import integers
from arxpy.bitvector.core import Constant
from arxpy.bitvector.context import Validation, Simplification
from arxpy.differential.difference import XorDiff, RXDiff, Difference
from arxpy.differential import derivative
from arxpy.differential.derivative import XDA, XDS, RXDA
from arxpy.differential.tests import preimageXDA
from arxpy.differential.tests import preimageRXDA
from arxpy.differential.tests import preimageXDAC
from arxpy.differential.tests import preimageXDS
"""Tests for the derivative module."""

# useful hypothesis decorator
# @example(arg0=..., arg1=..., ...)
# @settings(max_examples=10000, deadline=None)





DP_WIDTH = 8
VERBOSE = False
PRINT_DISTR_ERROR = False
PRINT_MAX_ERROR = False
ERROR_DIGITS = 5  # num fraction digits used when printing error values


class TestDerivativeBvAdd(unittest.TestCase):
    """Tests for the Derivative of BvAdd."""

    @classmethod
    def setUpClass(cls):
        module_name = "_preimageXDA"
        ffibuilderXOR = cffi.FFI()
        ffibuilderXOR.cdef(preimageXDA.header)
        ffibuilderXOR.set_source(module_name, preimageXDA.source)

        cls.tmpdirnameXOR = tempfile.TemporaryDirectory()
        lib_path = ffibuilderXOR.compile(tmpdir=cls.tmpdirnameXOR.name, verbose=VERBOSE)
        spec = importlib.util.spec_from_file_location(module_name, lib_path)
        lib_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(lib_module)
        cls.libXDA = lib_module.lib

        module_name = "_preimageRXDA"
        ffibuilderRX = cffi.FFI()
        ffibuilderRX.cdef(preimageRXDA.header)
        ffibuilderRX.set_source(module_name, preimageRXDA.source)

        cls.tmpdirnameRX = tempfile.TemporaryDirectory()
        lib_path = ffibuilderRX.compile(tmpdir=cls.tmpdirnameRX.name, verbose=VERBOSE)
        spec = importlib.util.spec_from_file_location(module_name, lib_path)
        lib_module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(lib_module)
        cls.libRXDA = lib_module.lib

    @classmethod
    def tearDownClass(cls):
        cls.tmpdirnameXOR.cleanup()
        cls.tmpdirnameRX.cleanup()

    @classmethod
    def _find_preimage(cls, f, beta):
        width = f.input_diff[0].val.width

        if width == 8 and f.diff_type == XorDiff:
            foo = cls.libXDA.find_XOR_preimage_8bit
        elif width == 8 and f.diff_type == RXDiff:
            foo = cls.libRXDA.find_RX_preimage_8bit
        elif width == 16 and f.diff_type == RXDiff:
            foo = cls.libRXDA.find_RX_preimage_16bit
        else:
            foo = None

        if foo is not None:
            result = foo(f.input_diff[0].val, f.input_diff[1].val, beta.val)
            if result.found:
                return result.i, result.j
            else:
                return None
        else:
            for i, j in itertools.product(range(2 ** width), range(2 ** width)):
                output_diff = f.eval(Constant(i, width), Constant(j, width))
                assert isinstance(output_diff, Difference) and isinstance(beta, Difference)
                if output_diff == beta:
                    return i, j
            else:
                return None

    @unittest.skip("skipping test_find_preimage")
    @given(
        integers(min_value=0, max_value=2 ** DP_WIDTH - 1),
        integers(min_value=0, max_value=2 ** DP_WIDTH - 1),
        integers(min_value=0, max_value=2 ** DP_WIDTH - 1),
    )
    def test_find_preimage(self, d1, d2, d3):
        assert DP_WIDTH == 8

        d1 = Constant(d1, DP_WIDTH)
        d2 = Constant(d2, DP_WIDTH)
        d3 = Constant(d3, DP_WIDTH)


Answer:         for diff_type, der_type in zip([XorDiff, RXDiff], [XDA, RXDA]):