Source code for arxpy.primitives.simon

"""Simon family of block ciphers."""
import enum
import math

from arxpy.bitvector.core import Constant
from arxpy.bitvector.operation import (
    Operation, RotateLeft, RotateRight, BvComp, Ite, ZeroExtend
)
from arxpy.bitvector.extraop import PopCount

from arxpy.differential.derivative import Derivative
from arxpy.differential.difference import XorDiff

from arxpy.primitives.primitives import KeySchedule, Encryption, Cipher


[docs]class SimonRF(Operation): """The non-linear part of the round function of Simon. This corresponds to ((x <<< a) & (x <<< b)) ^ (x <<< c), where (a, b, c) = (8, 1, 2). """ a = 8 b = 1 c = 2 arity = [1, 0] is_symmetric = False
[docs] @classmethod def output_width(cls, x): return x.width
[docs] @classmethod def eval(cls, x): if isinstance(x, Constant): return (RotateLeft(x, cls.a) & RotateLeft(x, cls.b)) ^ RotateLeft(x, cls.c)
@classmethod def xor_derivative(cls, input_diff): return XDSimonRF(input_diff)
[docs]class XDSimonRF(Derivative): """Represent the derivative of SimonRF w.r.t XOR differences. >>> from arxpy.bitvector.core import Constant, Variable >>> from arxpy.differential.difference import XorDiff >>> alpha = XorDiff(Constant(0, 16)) >>> f = XDSimonRF(alpha) >>> x = Constant(0, 16) >>> f.eval(x) # f(x + alpha) - f(x) XorDiff(0x0000) >>> f.max_weight(), f.error(), f.num_frac_bits() (15, 0, 0) """ diff_type = XorDiff op = SimonRF
[docs] def is_possible(self, output_diff): """Return whether the given output `XorDiff` is possible. >>> from arxpy.bitvector.core import Constant, Variable >>> from arxpy.bitvector.context import NotEvaluation >>> from arxpy.bitvector.printing import BvWrapPrinter >>> from arxpy.differential.difference import XorDiff >>> alpha = XorDiff(Constant(0, 16)) >>> f = XDSimonRF(alpha) >>> f.is_possible(XorDiff(Constant(0, 16))) 0b1 >>> u, v = Variable("u", 16), Variable("v", 16) >>> f = XDSimonRF(XorDiff(u)) >>> with NotEvaluation([PopCount]): ... result = f.is_possible(XorDiff(v)) >>> print(BvWrapPrinter().doprint(result)) Ite(0xffff == u, 0b0 == (PopCount(v ^ (u <<< 2))[0]), ==(0x0000, |(~(u <<< 8) & (u <<< 1) & (u <<< 15) & (v ^ (u <<< 2) ^ ((v ^ (u <<< 2)) <<< 7)), ~((u <<< 1) | (u <<< 8)) & (v ^ (u <<< 2)) >>> result.xreplace({u: Constant(0, 16), v: Constant(0, 16)}) 0b1 See `Derivative.is_possible` for more information. """ a, b, c = self.op.a, self.op.b, self.op.c n = self.input_diff[0].val.width assert math.gcd(n, a - b) == 1 and a > b and n % 2 == 0 alpha = self.input_diff[0].val Rol = RotateLeft varibits = Rol(alpha, a) | Rol(alpha, b) r = (2 * a - b) % n doublebits = (Rol(alpha, b) & (~Rol(alpha, a)) & Rol(alpha, r)) beta = output_diff.val gamma = beta ^ Rol(alpha, c) def is_even(x): return BvComp(x[0], Constant(0, 1)) case2 = BvComp( Constant(0, n), (gamma & (~varibits)) | ((gamma ^ Rol(gamma, a - b)) & doublebits) ) condition = Ite( BvComp(alpha, ~Constant(0, n)), is_even(PopCount(gamma)), case2 ) return condition
[docs] 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 >>> alpha = XorDiff(Constant(0, 16)) >>> f = XDSimonRF(alpha) >>> f.has_probability_one(XorDiff(Constant(0, 16))) 0b1 """ a, b, c = self.op.a, self.op.b, self.op.c n = self.input_diff[0].val.width assert math.gcd(n, a - b) == 1 and a > b and n % 2 == 0 alpha = self.input_diff[0].val Rol = RotateLeft varibits = Rol(alpha, a) | Rol(alpha, b) r = (2 * a - b) % n doublebits = (Rol(alpha, b) & (~Rol(alpha, a)) & Rol(alpha, r)) beta = output_diff.val gamma = beta ^ Rol(alpha, c) case2 = BvComp( Constant(0, n), (gamma & (~varibits)) | ((gamma ^ Rol(gamma, a - b)) & doublebits) ) hw = varibits ^ doublebits # no need to PopCount condition = ~BvComp(alpha, ~Constant(0, n)) condition &= case2 condition &= BvComp(hw, Constant(0, hw.width)) return condition
[docs] def weight(self, output_diff): """Return the weight of a possible output `XorDiff`. >>> from arxpy.bitvector.core import Constant, Variable >>> from arxpy.bitvector.context import NotEvaluation >>> from arxpy.bitvector.printing import BvWrapPrinter >>> from arxpy.bitvector.extraop import PopCount >>> from arxpy.differential.difference import XorDiff >>> alpha = XorDiff(Constant(0, 16)) >>> f = XDSimonRF(alpha) >>> f.weight(XorDiff(Constant(0, 16))) 0b00000 >>> alpha = XorDiff(Variable("u", 16)) >>> f = XDSimonRF(alpha) >>> beta = XorDiff(Variable("v", 16)) >>> with NotEvaluation([PopCount]): ... print(BvWrapPrinter().doprint(f.weight(beta))) Ite(0xffff == u, 0b01111, PopCount((~(u <<< 8) & (u <<< 1) & (u <<< 15)) ^ ((u <<< 1) | (u <<< 8))) See `Derivative.weight` for more information. """ a, b = self.op.a, self.op.b n = self.input_diff[0].val.width alpha = self.input_diff[0].val Rol = RotateLeft varibits = Rol(alpha, a) | Rol(alpha, b) r = (2 * a - b) % n doublebits = (Rol(alpha, b) & (~Rol(alpha, a)) & Rol(alpha, r)) hw = PopCount(varibits ^ doublebits) width = max((n - 1).bit_length(), hw.width) value = Ite( BvComp(alpha, ~Constant(0, n)), Constant(n - 1, width), ZeroExtend(hw, width - hw.width) ) return value
[docs] def max_weight(self): width = self.input_diff[0].val.width - 1 # n - 1 return width # as an integer
[docs] def exact_weight(self, output_diff): return int(self.weight(output_diff))
[docs] def num_frac_bits(self): return 0
[docs] def error(self): return 0
[docs]class SimonInstance(enum.Enum): simon_32_64 = enum.auto() simon_48_96 = enum.auto() simon_64_128 = enum.auto()
[docs]def get_Simon_instance(simon_instance): """Return an instance of the Simon family.""" if simon_instance == SimonInstance.simon_32_64: default_rounds = 32 n = 16 m = 4 z = "11111010001001010110000111001101111101000100101011000011100110" elif simon_instance == SimonInstance.simon_48_96: default_rounds = 36 n = 24 m = 4 z = "10001110111110010011000010110101000111011111001001100001011010" elif simon_instance == SimonInstance.simon_64_128: default_rounds = 44 n = 32 m = 4 z = "11011011101011000110010111100000010010001010011100110100001111" else: raise ValueError("invalid instance of Simon") class SimonKeySchedule(KeySchedule): """Key schedule function.""" rounds = default_rounds input_widths = [n for _ in range(m)] output_widths = [n for _ in range(default_rounds)] @classmethod def set_rounds(cls, new_rounds): cls.rounds = new_rounds cls.input_widths = [n for _ in range(min(m, new_rounds))] cls.output_widths = [n for _ in range(new_rounds)] @classmethod def eval(cls, *master_key): if cls.rounds <= m: return list(reversed(master_key))[:cls.rounds] k = [None for i in range(cls.rounds)] k[:m] = list(reversed(master_key)) for i in range(m, cls.rounds): tmp = RotateRight(k[i - 1], 3) if m == 4: tmp ^= k[i - 3] tmp ^= RotateRight(tmp, 1) k[i] = ~k[i - m] ^ tmp ^ int(z[(i - m) % 62]) ^ 3 return k class SimonEncryption(Encryption): """Encryption function.""" rounds = default_rounds input_widths = [n, n] output_widths = [n, n] round_keys = None @classmethod def set_rounds(cls, new_rounds): cls.rounds = new_rounds @classmethod def eval(cls, x, y): for i in range(cls.rounds): x, y = (y ^ SimonRF(x) ^ cls.round_keys[i], x) return x, y class SimonCipher(Cipher): key_schedule = SimonKeySchedule encryption = SimonEncryption rounds = default_rounds _simon_instance = simon_instance @classmethod def set_rounds(cls, new_rounds): # assert new_rounds >= 2 cls.rounds = new_rounds cls.key_schedule.set_rounds(new_rounds) cls.encryption.set_rounds(new_rounds) @classmethod def test(cls): old_rounds = cls.rounds cls.set_rounds(default_rounds) if cls._simon_instance == SimonInstance.simon_32_64: plaintext = (0x6565, 0x6877) key = (0x1918, 0x1110, 0x0908, 0x0100) assert cls(plaintext, key) == (0xc69b, 0xe9bb) elif cls._simon_instance == SimonInstance.simon_48_96: plaintext = (0x726963, 0x20646e) key = (0x1a1918, 0x121110, 0x0a0908, 0x020100) assert cls(plaintext, key) == (0x6e06a5, 0xacf156) elif cls._simon_instance == SimonInstance.simon_64_128: plaintext = (0x656b696c, 0x20646e75) key = (0x1b1a1918, 0x13121110, 0x0b0a0908, 0x03020100) assert cls(plaintext, key) == (0x44c8fc20, 0xb9dfa07a) else: raise ValueError("invalid instance of Simon") cls.set_rounds(old_rounds) return SimonCipher