Source code for cascada.primitives.rectangle

"""RECTANGLE cipher."""
import functools
from decimal import Decimal
from math import inf

from cascada.bitvector.core import Constant, Term
from cascada.bitvector.operation import RotateLeft, Concat, BvIdentity
from cascada.bitvector.secondaryop import LutOperation
from cascada.abstractproperty.opmodel import log2_decimal
from cascada.differential.difference import XorDiff
from cascada.differential.opmodel import get_wdt_model as get_differential_wdt_model
from cascada.linear.opmodel import get_wdt_model as get_linear_wdt_model
from cascada.bitvector.ssa import RoundBasedFunction
from cascada.primitives.blockcipher import Encryption, Cipher


# if False, represent the plaintext and the encryption state
# as a 16 × 4 rectangular array of bits (instead of 4 x 16)
# (masterkey and key-scheduling state remains the same in both representations)
DEFAULT_REPR = True


[docs]class SboxLut(LutOperation): """The 4-bit S-box of RECTANGLE.""" lut = [Constant(x, 4) for x in (6, 5, 12, 10, 1, 14, 7, 9, 11, 0, 3, 13, 8, 15, 4, 2)]
ddt = ( (16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 2, 0, 0, 4, 2, 0, 0, 0, 2, 0, 0, 4, 2), (0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2, 0, 2, 4, 0, 2), (0, 0, 0, 2, 0, 0, 2, 0, 2, 4, 2, 2, 2, 0, 0, 0), (0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4), (0, 2, 0, 0, 4, 2, 0, 0, 4, 2, 0, 0, 0, 2, 0, 0), (0, 2, 4, 0, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 2), (0, 0, 4, 0, 2, 2, 0, 0, 0, 2, 0, 2, 2, 0, 0, 2), (0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2), (0, 2, 0, 0, 0, 2, 4, 0, 0, 2, 0, 0, 0, 2, 4, 0), (0, 0, 0, 0, 0, 4, 2, 2, 2, 0, 2, 0, 2, 0, 0, 2), (0, 4, 0, 2, 0, 0, 2, 0, 2, 0, 2, 2, 2, 0, 0, 0), (0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 4, 0, 0, 0, 4, 0), (0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 4, 0, 0, 2, 4, 0), (0, 0, 4, 2, 2, 2, 0, 2, 0, 2, 0, 0, 2, 0, 0, 0), (0, 2, 4, 2, 2, 0, 0, 2, 0, 0, 0, 0, 2, 2, 0, 0) ) lat = ( (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0.25, 0, -0.25, 0, 0, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5), (0, 0, 0, 0, 0, 0, 0.25, 0.25, 0, 0, 0.25, -0.25, 0, 0, 0, 0), (0, 0, 0, -0.25, 0.25, 0, 0, 0, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5), (0, 0, 0, 0, 0, 0, -0.25, 0.25, 0, 0, 0, 0, 0, 0, 0.25, 0.25), (0, 0, -0.25, 0, 0, -0.25, 0, 0, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5), (0, 0, 0, 0, 0, 0, 0, 0, 0.25, 0.25, 0, 0, -0.25, 0.25, 0, 0), (0, 0, -0.25, 0, -0.25, 0, 0, 0, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5), (0, 0, 0, -0.25, -0.5, -0.5, 0.5, -0.5, 0, -0.25, 0, 0, -0.5, 0.5, 0.5, 0.5), (0, 0, 0, 0, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.25, 0, 0.25, 0), (0, 0, 0, -0.25, -0.5, -0.5, -0.5, 0.5, 0.25, 0, 0, 0, 0.5, -0.5, -0.5, -0.5), (0, 0, 0, 0, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0, -0.25, 0, 0.25), (0, 0.25, 0, 0, -0.5, 0.5, -0.5, -0.5, 0, 0, 0, -0.25, -0.5, -0.5, -0.5, 0.5), (0, 0.25, 0.25, 0, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0, 0, 0, 0), (0, -0.25, 0, 0, -0.5, 0.5, 0.5, 0.5, 0, 0, -0.25, 0, -0.5, -0.5, -0.5, 0.5), (0, 0.25, -0.25, 0, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0, 0, 0, 0) ) SboxLut.xor_model = get_differential_wdt_model( SboxLut, XorDiff, tuple([tuple(inf if x == 0 else -log2_decimal(x / Decimal(2 ** 4)) for x in row) for row in ddt])) SboxLut.linear_model = get_linear_wdt_model( SboxLut, tuple([tuple(inf if x == 0 else -log2_decimal(Decimal(abs(x))) for x in row) for row in lat])) def sub_column(x): if DEFAULT_REPR: bits = x[0].width for i in range(bits): s = Concat(Concat(Concat(x[3][i], x[2][i]), x[1][i]), x[0][i]) s = SboxLut(s) for j in range(4): if i == 0: x[j] = Concat(x[j][:1], s[j]) elif i == bits - 1: x[j] = Concat(s[j], x[j][bits - 2:]) else: x[j] = Concat(Concat(x[j][:i + 1], s[j]), x[j][i - 1:]) x[j] = BvIdentity(x[j]) else: for i in range(len(x)): x[i] = SboxLut(x[i]) def shift_row(x): if DEFAULT_REPR: x[1] = RotateLeft(x[1], 1) x[2] = RotateLeft(x[2], 12) x[3] = RotateLeft(x[3], 13) else: y = [[None for _ in range(4)] for _ in range(16)] for i in range(16): y[i][0] = x[i][0] y[i][1] = x[(i - 1) % 16][1] y[i][2] = x[(i - 12) % 16][2] y[i][3] = x[(i - 13) % 16][3] for i in range(16): x[i] = functools.reduce(Concat, reversed(y[i]))
[docs]def transpose(list_bv): """Transpose a list of bit-vectors. Given a list ``x`` of bit-vectors, return another list of bit-vectors ``y`` such as the bit-matrix representation of ``y`` is the transpose of that of ``x`` (``x[i][j] == y[j][i]``). """ assert all(isinstance(bv, Term) for bv in list_bv) bit_matrix = [[bv[i] for i in range(bv.width)] for bv in list_bv] num_rows = len(bit_matrix) num_columns = len(bit_matrix[0]) assert all(num_columns == len(row) for row in bit_matrix) bit_matrix_t = [[bit_matrix[i][j] for i in range(num_rows)] for j in range(num_columns)] num_rows, num_columns = num_columns, num_rows assert num_rows == len(bit_matrix_t) and num_columns == len(bit_matrix_t[0]) list_bv_t = [functools.reduce(Concat, reversed(bit_list)) for bit_list in bit_matrix_t] return list_bv_t
def add_round_key(x, k, i): if DEFAULT_REPR: x[0] ^= k[i] x[1] ^= k[i + 1] x[2] ^= k[i + 2] x[3] ^= k[i + 3] else: k = transpose(k[i:i + 4]) for col in range(16): x[col] ^= BvIdentity(k[col]) RC = (0x01, 0x02, 0x04, 0x09, 0x12, 0x05, 0x0B, 0x16, 0x0C, 0x19, 0x13, 0x07, 0x0F, 0x1F, 0x1E, 0x1C, 0x18, 0x11, 0x03, 0x06, 0x0D, 0x1B, 0x17, 0x0E, 0x1D)
[docs]class RECTANGLEEncryption(Encryption, RoundBasedFunction): """RECTANGLE encryption function.""" num_rounds = 25 input_widths = [16, 16, 16, 16] if DEFAULT_REPR else [4 for _ in range(16)] output_widths = [16, 16, 16, 16] if DEFAULT_REPR else [4 for _ in range(16)] round_keys = None
[docs] @classmethod def set_num_rounds(cls, new_num_rounds): cls.num_rounds = new_num_rounds
[docs] @classmethod def eval(cls, *x): x = list(x) for i in range(cls.num_rounds): add_round_key(x, cls.round_keys, i * 4) sub_column(x) shift_row(x) cls.add_round_outputs(x) add_round_key(x, cls.round_keys, cls.num_rounds * 4) return x
[docs]class RECTANGLE80KeySchedule(RoundBasedFunction): """RECTANGLE-80 key schedule function.""" num_rounds = 25 input_widths = [16, 16, 16, 16, 16] output_widths = [16 for _ in range(4 * num_rounds + 4)]
[docs] @classmethod def set_num_rounds(cls, new_num_rounds): assert new_num_rounds <= len(RC) cls.num_rounds = new_num_rounds cls.output_widths = [16 for _ in range(4 * new_num_rounds + 4)]
[docs] @classmethod def eval(cls, *master_key): x = list(master_key) round_keys = x[:4] for i in range(cls.num_rounds): t = [x[0][3:], x[1][3:], x[2][3:], x[3][3:]] if DEFAULT_REPR: sub_column(t) else: t = [BvIdentity(bv) for bv in transpose(t)] sub_column(t) t = transpose(t) for j in range(4): x[j] = Concat(x[j][:4], t[j]) t = x[0] x[0] = RotateLeft(x[0], 8) ^ x[1] x[1] = x[2] x[2] = x[3] x[3] = RotateLeft(x[3], 12) ^ x[4] x[4] = t x[0] ^= Constant(RC[i], 16) round_keys.extend(x[:4]) return round_keys
[docs]class RECTANGLE128KeySchedule(RoundBasedFunction): """RECTANGLE-128 key schedule function.""" num_rounds = 25 input_widths = [32, 32, 32, 32] output_widths = [16 for _ in range(4 * num_rounds + 4)]
[docs] @classmethod def set_num_rounds(cls, new_num_rounds): assert new_num_rounds <= len(RC) cls.num_rounds = new_num_rounds cls.output_widths = [16 for _ in range(4 * new_num_rounds + 4)]
[docs] @classmethod def eval(cls, *master_key): x = list(master_key) round_keys = [w[15:] for w in x] for i in range(cls.num_rounds): t = [w[7:] for w in x] if DEFAULT_REPR: sub_column(t) else: t = [BvIdentity(bv) for bv in transpose(t)] sub_column(t) t = transpose(t) for j in range(4): x[j] = Concat(x[j][:8], t[j]) t = x[0] x[0] = RotateLeft(x[0], 8) ^ x[1] x[1] = x[2] x[2] = RotateLeft(x[2], 16) ^ x[3] x[3] = t x[0] ^= Constant(RC[i], 32) round_keys.extend([w[15:] for w in x]) return round_keys
[docs]class RECTANGLE80Cipher(Cipher): """RECTANGLE-80 cipher.""" key_schedule = RECTANGLE80KeySchedule encryption = RECTANGLEEncryption
[docs] @classmethod def set_num_rounds(cls, new_num_rounds): cls.key_schedule.set_num_rounds(new_num_rounds) cls.encryption.set_num_rounds(new_num_rounds)
[docs] @classmethod def test(cls): """Test RECTANGLE-80 test vectors.""" old_num_rounds = cls.num_rounds cls.set_num_rounds(25) key = (0x0000, 0x0000, 0x0000, 0x0000, 0x0000) if DEFAULT_REPR: plaintext = (0x0000, 0x0000, 0x0000, 0x0000) assert cls(plaintext, key) == (0x2D96, 0xE354, 0xE8B1, 0x0874) else: plaintext = transpose([Constant(x, 16) for x in (0x0000, 0x0000, 0x0000, 0x0000)]) ciphertext = transpose([Constant(x, 16) for x in (0x2D96, 0xE354, 0xE8B1, 0x0874)]) assert cls(plaintext, key) == tuple(ciphertext) key = (0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF) if DEFAULT_REPR: plaintext = (0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF) assert cls(plaintext, key) == (0x9945, 0xAA34, 0xAE3D, 0x0112) else: plaintext = transpose([Constant(x, 16) for x in (0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF)]) ciphertext = transpose([Constant(x, 16) for x in (0x9945, 0xAA34, 0xAE3D, 0x0112)]) assert cls(plaintext, key) == tuple(ciphertext) cls.set_num_rounds(old_num_rounds)
[docs]class RECTANGLE128Cipher(Cipher): """RECTANGLE-128 cipher.""" key_schedule = RECTANGLE128KeySchedule encryption = RECTANGLEEncryption
[docs] @classmethod def set_num_rounds(cls, new_num_rounds): cls.key_schedule.set_num_rounds(new_num_rounds) cls.encryption.set_num_rounds(new_num_rounds)
[docs] @classmethod def test(cls): """Test RECTANGLE-128 test vectors.""" old_num_rounds = cls.num_rounds cls.set_num_rounds(25) key = (0x00000000, 0x00000000, 0x00000000, 0x00000000) if DEFAULT_REPR: plaintext = (0x0000, 0x0000, 0x0000, 0x0000) assert cls(plaintext, key) == (0xAEE6, 0x3613, 0x44A4, 0x99EE) else: plaintext = transpose([Constant(x, 16) for x in (0x0000, 0x0000, 0x0000, 0x0000)]) ciphertext = transpose([Constant(x, 16) for x in (0xAEE6, 0x3613, 0x44A4, 0x99EE)]) assert cls(plaintext, key) == tuple(ciphertext) key = (0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF) if DEFAULT_REPR: plaintext = (0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF) assert cls(plaintext, key) == (0xE83E, 0xEFEE, 0x4A15, 0x7A46) else: plaintext = transpose([Constant(x, 16) for x in (0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF)]) ciphertext = transpose([Constant(x, 16) for x in (0xE83E, 0xEFEE, 0x4A15, 0x7A46)]) assert cls(plaintext, key) == tuple(ciphertext) cls.set_num_rounds(old_num_rounds)