Adding a primitive

To implement a new primitive, the easiest way is to take a similar primitive already implemented and adapt it to the new primitive (see also BvFunction and blockcipher). If the new primitive contains a bit-vector operation with no associated property model (see abstractproperty.opmodel.OpModel), you need to implement the property model (as in simon and simon_rf) or use a WeakModel, BranchNumberModel or WDTModel (as in aes or skinny64).

We will now describe step-by-step how to add a new primitive from scratch using Speck32/64 as example.

Description of Speck

Speck is a family of lightweight block ciphers. A member of the family is denoted by Speck \(2n/m·n\), where the block size is \(2n\) bits for \(n\in \{16,24,32,48,64\}\), and the key size is \(m·n\) bits for \(m \in \{2,3,4\}\), depending on the desired security level.

The round function of Speck \(f(x, y, k) = (x', y')\) receives two words \(x\) and \(y\), and a round key \(k\), all of size \(n\), and outputs two words of bitsize \(n\), \(x'\) and \(y'\), such that

  • \(x' = (x \ggg \alpha)\boxplus y)\oplus k\)

  • \(y' = x' \oplus (y \lll \beta))\)

The Speck key-schedule algorithm uses the same round function to generate the round keys. Let \(K = (l_{m-2},...,l_0,k_0)\) be a master key for Speck \(2n/m·n\), where \(l_i, k_0\) are \(n\)-bit words. The sequence of round keys \(k_i\) (with \(i\) starting from 0) is generated as

  • \(l_{i+m-1} = (l_i \ggg \alpha)\boxplus k_i)\oplus i\)

  • \(k_{i+1} = l_{i+m-1} \oplus (k_i\lll \beta)\)

The rotation offset \((\alpha, \beta)\) is \((7,2)\) for Speck32/64, and \((8,3)\) for the larger versions.

Implementing the round function

First, we will implement the round function \(f(x, y, k) = (x', y')\) using the bitvector module. To implement \(f\), we search in bitvector.operation and bitvector.secondaryop the classes corresponding to the bit-vector operations used in \(f\). In particular, \(f\) uses the modular addition (corresponding to the class BvAdd), the left and right rotations (RotateLeft and RotateRight), and the XOR (BvXor). Thus, we can implement \(f\) with the following Python function

from cascada.bitvector.operation import BvXor, BvAdd, RotateRight, RotateLeft
def f(x, y, k):
    x = BvXor(BvAdd(RotateRight(x, 7), y), k)
    y = BvXor(RotateLeft(y, 2), x)
    return x, y

In the unlikely case that \(f\) would contain an operation not included in the bitvector module or in the case that we would want to group some operations together to treat them as an atomic operation, new operations can be defined as subclasses of SecondaryOperation (see for example simon_rf).

We can test \(f\) by evaluating it with symbolic and constant bit-vectors (corresponding to the classes Variable and Constant).

>>> from cascada.bitvector.core import Constant, Variable
>>> f(Variable("x", 16), Variable("y", 16), Variable("z", 16))
(((x >>> 7) + y) ^ z, (y <<< 2) ^ ((x >>> 7) + y) ^ z)
>>> f(Constant(0, 16), Constant(0, 16), Constant(0, 16))
(0x0000, 0x0000)

Since \(f\) is only meant to be used with bit-vector inputs (Term objects), we can use the Python operators +, ^ which are overloaded for Term objects with the classes BvAdd and BvXor respectively. In other words, we can also implement \(f\) with the following Python function

def f(x, y, k):
    x = RotateRight(x, 7) + y ^ k
    y = RotateLeft(y, 2) ^ x
    return x, y

Implementing the key-schedule and the encryption functions

We will now implement the key-schedule and the encryption functions of Speck32/64 using two Python functions and bit-vector operations. To do so, we will implement the key-schedule as a function taking the key words and returning the list of round keys, and the encryption as a function taking the plaintext words and the round keys and returning the ciphertext words.

from cascada.bitvector.core import Constant
from cascada.bitvector.operation import RotateRight, RotateLeft

num_rounds = 22

def f(x, y, k):
    x = RotateRight(x, 7) + y ^ k
    y = RotateLeft(y, 2) ^ x
    return x, y

def key_schedule(l2, l1, l0, k0):
    l = [None for i in range(num_rounds + 3)]
    round_keys = [None for _ in range(num_rounds)]
    round_keys[0] = k0
    l[0:3] = [l0, l1, l2]
    for i in range(num_rounds - 1):
        l[i+3], round_keys[i+1] = f(l[i], round_keys[i], Constant(i, 16))
    return round_keys

def encryption(x, y, round_keys):
    for i in range(num_rounds):
        x, y = f(x, y, round_keys[i])
    return x, y

There are many ways to implement these key-schedule and encryption functions; our only restriction is that the output of these two functions are list of bit-vectors (Term objects) assuming that the inputs are Term objects. We can test these two Python functions with the following Speck32/64 test vector

  • plaintext = (0x6574, 0x694c)

  • key = (0x1918, 0x1110, 0x0908, 0x0100)

  • ciphertext = (0xa868, 0x42f2)

>>> from cascada.bitvector.core import Constant
>>> l2, l1 = Constant(0x1918, 16), Constant(0x1110, 16)
>>> l0, k0 = Constant(0x0908, 16), Constant(0x0100, 16)
>>> round_keys = key_schedule(l2, l1, l0, k0)
>>> x, y = Constant(0x6574, 16), Constant(0x694c, 16)
>>> ciphertext = encryption(x, y, round_keys)
>>> ciphertext
(0xa868, 0x42f2)

Wrapping the key-schedule function

To implement Speck32/64 as a primitive supported by CASCADA (i.e., as a Cipher subclass), we need to wrap the key-schedule into a RoundBasedFunction subclass.

To do so, we need to set some attributes (input_widths, output_widths, num_rounds) and implement the methods eval and set_num_rounds (see BvFunction and RoundBasedFunction) as follows

from cascada.bitvector.core import Constant
from cascada.bitvector.ssa import RoundBasedFunction
from cascada.bitvector.operation import RotateRight, RotateLeft

def f(x, y, k):
    x = RotateRight(x, 7) + y ^ k
    y = RotateLeft(y, 2) ^ x
    return x, y

class SpeckKeySchedule(RoundBasedFunction):
    input_widths = [16, 16, 16, 16]
    output_widths = [16 for _ in range(22)]
    num_rounds = 22

    @classmethod
    def eval(cls, l2, l1, l0, k0):
        l = [None for i in range(cls.num_rounds + 3)]
        round_keys = [None for _ in range(cls.num_rounds)]
        round_keys[0] = k0
        l[0:3] = [l0, l1, l2]
        for i in range(cls.num_rounds - 1):
            l[i+3], round_keys[i+1] = f(l[i], round_keys[i], Constant(i, 16))
        return round_keys

    @classmethod
    def set_num_rounds(cls, new_num_rounds):
        cls.num_rounds = new_num_rounds
        cls.output_widths = [16 for _ in range(new_num_rounds)]

To implement the eval method, we took the previous Python function key_schedule working on bit-vectors, but using the class attribute cls.num_rounds so that we can change the current number of rounds with the method set_num_rounds.

Similar as before, we can test SpeckKeySchedule by evaluating it with symbolic and constant bit-vectors. To avoid long outputs, we will reduce the number of rounds for the test:

>>> from cascada.bitvector.core import Constant, Variable
>>> SpeckKeySchedule.set_num_rounds(3)
>>> masterkey = Variable("l2", 16), Variable("l1", 16), Variable("l0", 16), Variable("k0", 16)
>>> SpeckKeySchedule(*masterkey, symbolic_inputs=True)
(k0, (k0 <<< 2) ^ ((l0 >>> 7) + k0), (((k0 <<< 2) ^ ((l0 >>> 7) + k0)) <<< 2) ^ ((l1 >>> 7) + ((k0 <<< 2) ^ ((l0 >>> 7) + k0))) ^ 0x0001)
>>> masterkey = Constant(0, 16), Constant(0, 16), Constant(0, 16), Constant(0, 16)
>>> SpeckKeySchedule(*masterkey)
(0x0000, 0x0000, 0x0001)

By default evaluating RoundBasedFunction with symbolic inputs is not allowed, but this can be enabled by passing the optional argument symbolic_inputs=True.

Note that we can also use the method add_round_outputs within the eval to delimit the end of the rounds (as in speck), but this is optional and we will not use it in this tutorial.

Wrapping the encryption function

To implement Speck32/64 as a Cipher subclass, we also need to wrap the encryption function into a Encryption/RoundBasedFunction subclass.

To do so, we need to set some attributes (input_widths, output_widths, num_rounds) and implement the methods eval and set_num_rounds:

from cascada.bitvector.ssa import RoundBasedFunction
from cascada.bitvector.operation import RotateRight, RotateLeft
from cascada.primitives.blockcipher import Encryption

def f(x, y, k):
    x = RotateRight(x, 7) + y ^ k
    y = RotateLeft(y, 2) ^ x
    return x, y

class SpeckEncryption(Encryption, RoundBasedFunction):
    input_widths = [16, 16]
    output_widths = [16, 16]
    num_rounds = 22
    round_keys = None

    @classmethod
    def eval(cls, x, y):
        for i in range(cls.num_rounds):
            x, y = f(x, y, cls.round_keys[i])
        return x, y

    @classmethod
    def set_num_rounds(cls, new_num_rounds):
        cls.num_rounds = new_num_rounds

Similar as before, we took the previous Python function encryption working on bit-vectors, but using the class attribute cls.num_rounds so that we can change the current number of rounds with the method set_num_rounds.

For the eval method of the encryption in particular, the round keys are obtained from the class attribute cls.round_keys (later after we link SpeckKeySchedule and SpeckEncryption, the class attribute cls.round_keys will be automatically set to the output of SpeckKeySchedule).

We can test SpeckEncryption by evaluating it with symbolic and constant bit-vectors, but we need to set the round keys manually for testing it individually.

>>> from cascada.bitvector.core import Constant, Variable
>>> num_rounds = 2
>>> round_keys = [Variable("k" + str(i), 16) for i in range(num_rounds)]
>>> SpeckEncryption.set_num_rounds(num_rounds)
>>> SpeckEncryption.round_keys = round_keys
>>> plaintext = [Variable("x", 16), Variable("y", 16)]
>>> SpeckEncryption(*plaintext, symbolic_inputs=True)
((((((x >>> 7) + y) ^ k0) >>> 7) + ((y <<< 2) ^ ((x >>> 7) + y) ^ k0)) ^ k1,
(((y <<< 2) ^ ((x >>> 7) + y) ^ k0) <<< 2) ^ (((((x >>> 7) + y) ^ k0) >>> 7) + ((y <<< 2) ^ ((x >>> 7) + y) ^ k0)) ^ k1)

Alternatively, we can also check it by computing its SSA (a list of simple instructions representing the bit-vector function)

>>> num_rounds = 2
>>> round_keys = [Variable("k" + str(i), 16) for i in range(num_rounds)]
>>> SpeckEncryption.set_num_rounds(num_rounds)
>>> SpeckEncryption.round_keys = round_keys
>>> SpeckEncryption.to_ssa(["x", "y"], "z")
SSA(input_vars=[x, y], output_vars=[z7_out, z9_out], external_vars=[k0, k1],
    assignments=[
        (z0, x >>> 7), (z1, z0 + y), (z2, z1 ^ k0), (z3, y <<< 2), (z4, z3 ^ z2),
        (z5, z2 >>> 7), (z6, z5 + z4), (z7, z6 ^ k1), (z8, z4 <<< 2), (z9, z8 ^ z7),
        (z7_out, Id(z7)), (z9_out, Id(z9))])

Wrapping everything as a Cipher subclass

Finally, we can implement Speck32/64 as a Cipher subclass using SpeckKeySchedule, SpeckEncryption and setting some attributes:

from cascada.bitvector.core import Constant
from cascada.bitvector.ssa import RoundBasedFunction
from cascada.bitvector.operation import RotateRight, RotateLeft
from cascada.primitives.blockcipher import Encryption, Cipher

def f(x, y, k):
    x = RotateRight(x, 7) + y ^ k
    y = RotateLeft(y, 2) ^ x
    return x, y

class SpeckKeySchedule(RoundBasedFunction):
    input_widths = [16, 16, 16, 16]
    output_widths = [16 for _ in range(22)]
    num_rounds = 22

    @classmethod
    def eval(cls, l2, l1, l0, k0):
        l = [None for i in range(cls.num_rounds + 3)]
        round_keys = [None for _ in range(cls.num_rounds)]
        round_keys[0] = k0
        l[0:3] = [l0, l1, l2]
        for i in range(cls.num_rounds - 1):
            l[i+3], round_keys[i+1] = f(l[i], round_keys[i], Constant(i, 16))
        return round_keys

    @classmethod
    def set_num_rounds(cls, new_num_rounds):
        cls.num_rounds = new_num_rounds
        cls.output_widths = [16 for _ in range(new_num_rounds)]

class SpeckEncryption(Encryption, RoundBasedFunction):
    input_widths = [16, 16]
    output_widths = [16, 16]
    num_rounds = 22
    round_keys = None

    @classmethod
    def eval(cls, x, y):
        for i in range(cls.num_rounds):
            x, y = f(x, y, cls.round_keys[i])
        return x, y

    @classmethod
    def set_num_rounds(cls, new_num_rounds):
        cls.num_rounds = new_num_rounds

class SpeckCipher(Cipher):
    key_schedule = SpeckKeySchedule
    encryption = SpeckEncryption

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

The subclass SpeckCipher is now a primitive that can be used in the automated methods implemented by CASCADA (see Usage).

Since the differential, linear and algebraic models are implemented for BvAdd, BvXor, RotateLeft and RotateRight (the operations of Speck), SpeckCipher can be used directly in the automated methods of CASCADA (e.g., round_based_ch_search).

However, if we implement a primitive containing an operation whose property model is not implemented, then we need to implement it in order to search for characteristics over this primitive. For example, if the primitive contains S-boxes (LutOperation) or binary matrix-vector operations (MatrixOperation), no default property models are assigned for these operations, but the generic models WeakModel, BranchNumberModel and WDTModel can be used (as in aes or skinny64).

Note that the previous implementation of Speck32/64 is slightly different to the implementations of Speck in speck. In particular, speck includes add_round_outputs calls to delimit the rounds and considers that a Speck cipher instance with \(r\) rounds contains \(r\) encryption rounds but \((r-1)\) key-schedule rounds (since the key-schedule does not perform any operation for the 1-round cipher).