Skip to content

Commit

Permalink
Merge pull request #261 from Crypto-TII/aradi
Browse files Browse the repository at this point in the history
Aradi
  • Loading branch information
peacker authored Aug 10, 2024
2 parents 9b37e6c + a0fb2f5 commit 35ed390
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 0 deletions.
Binary file not shown.
168 changes: 168 additions & 0 deletions claasp/ciphers/block_ciphers/aradi_block_cipher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# ****************************************************************************
# Copyright 2023 Technology Innovation Institute
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ****************************************************************************


from claasp.cipher import Cipher
from claasp.utils.utils import get_ith_word
from claasp.DTOs.component_state import ComponentState
from claasp.name_mappings import INPUT_PLAINTEXT, INPUT_KEY

input_types = [INPUT_KEY, INPUT_PLAINTEXT]
PARAMETERS_CONFIGURATION_LIST = [{'block_bit_size': 128, 'key_bit_size': 256, 'number_of_rounds': 16}]


class AradiBlockCipher(Cipher):
"""
Construct an instance of the AradiBlockCipher class.
This class is used to store compact representations of a cipher,
used to generate the corresponding cipher.
INPUT:
- ``block_bit_size`` -- **integer** (default: `64`); cipher input and output block bit size of the cipher
- ``key_bit_size`` -- **integer** (default: `128`); cipher key bit size of the cipher
- ``number_of_rounds`` -- **integer** (default: `0`); number of rounds of the cipher. The cipher uses the
corresponding amount given the other parameters (if available) when number_of_rounds is 0
- ``sub_keys_zero`` -- **boolean** (default: `False`)
- ``transformations_flag`` -- **boolean** (default: `True`)
EXAMPLES::
sage: from claasp.ciphers.block_ciphers.aradi_block_cipher import AradiBlockCipher
sage: aradi = AradiBlockCipher(number_of_rounds=16)
sage: aradi.evaluate([0, 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100])
83791582030165712186104466959690447122
"""

def __init__(self, number_of_rounds=16):
self.block_bit_size = 128
self.key_bit_size = 256
self.WORD_SIZE = 32

super().__init__(family_name="aradi",
cipher_type="block_cipher",
cipher_inputs=[INPUT_PLAINTEXT, INPUT_KEY],
cipher_inputs_bit_size=[self.block_bit_size, self.key_bit_size],
cipher_output_bit_size=self.block_bit_size)
self.A = [11,10,9,8]
self.B = [8,9,4,9]
self.C = [14,11,14,7]
state = INPUT_PLAINTEXT
key = INPUT_KEY

for round_i in range(number_of_rounds):
self.add_round()
round_key = self.get_round_key_id(key, round_i)
state = self.round_function(state, round_key, round_i)
key = self.update_key(key, round_i)

round_key = self.get_round_key_id(key, 0)
w = self.add_XOR_component([round_key, state], [list(range(32)), list(range(32))], 32).id
x = self.add_XOR_component([round_key, state], [list(range(32, 64)), list(range(32, 64))], 32).id
y = self.add_XOR_component([round_key, state], [list(range(64, 96)), list(range(64, 96))], 32).id
z = self.add_XOR_component([round_key, state], [list(range(96, 128)), list(range(96, 128))], 32).id
self.add_cipher_output_component([w, x, y, z], [list(range(32)) for _ in range(4)], 128)


def get_key_word_bit_indexes(self, word_index):
return list(range(32*(8-word_index-1), 32*(8-word_index)))

def get_round_key_id(self, key, round_i):
j = round_i % 2
return self.add_round_key_output_component([key, key, key, key], [self.get_key_word_bit_indexes(4*j + i) for i in range(4)], 128).id

def l_function(self, xy_id_links, x_bits, y_bits, round_index):
j = round_index % 4
rot_x_a = self.add_rotate_component([xy_id_links], [x_bits], 16, -self.A[j]).id
rot_x_b = self.add_rotate_component([xy_id_links], [x_bits], 16, -self.B[j]).id
rot_y_a = self.add_rotate_component([xy_id_links], [y_bits], 16, -self.A[j]).id
rot_y_c = self.add_rotate_component([xy_id_links], [y_bits], 16, -self.C[j]).id

left_part = self.add_XOR_component([xy_id_links] + [rot_x_a, rot_y_c], [x_bits] + [list(range(16)), list(range(16))], 16).id
right_part = self.add_XOR_component([xy_id_links] + [rot_y_a, rot_x_b], [y_bits] + [list(range(16)), list(range(16))], 16).id
return left_part, right_part

def m_function(self, i, j, xy_id_links, xy_input_bits):
y_inds = xy_input_bits[32:64]
x_inds = xy_input_bits[:32]
rot_i_y = self.add_rotate_component(xy_id_links, [y_inds], 32, -i).id
rot_j_x = self.add_rotate_component(xy_id_links, [x_inds], 32, -j).id
left_part = self.add_XOR_component(xy_id_links + [rot_i_y, rot_j_x], [x_inds] + [list(range(32)) for _ in range(2)], 32).id
right_part = self.add_XOR_component(xy_id_links + [rot_i_y], [x_inds] + [list(range(32))], 32).id
return left_part, right_part

def update_key(self, key, round_i):
round_constant = self.add_constant_component(32, round_i).id
k1, k0 = self.m_function(1, 3, [key], self.get_key_word_bit_indexes(1) + self.get_key_word_bit_indexes(0))
k3, k2 = self.m_function(9, 28, [key], self.get_key_word_bit_indexes(3) + self.get_key_word_bit_indexes(2))
k5, k4 = self.m_function(1, 3, [key], self.get_key_word_bit_indexes(5) + self.get_key_word_bit_indexes(4))
k7, k6 = self.m_function(9, 28, [key], self.get_key_word_bit_indexes(7) + self.get_key_word_bit_indexes(6))
k7 = self.add_XOR_component([k7, round_constant], [list(range(32)) for _ in range(2)], 32).id
if round_i % 2 == 0:
updated_key = self.add_intermediate_output_component([k7, k5, k6, k4, k3, k1, k2, k0], [list(range(32)) for _ in range(8)], 256, f"key_{round_i}")
else:
updated_key = self.add_intermediate_output_component([k7, k3, k5, k1, k6, k2, k4, k0], [list(range(32)) for _ in range(8)], 256, f"key_{round_i}")
return updated_key.id

def round_function(self, state, round_key, round_i):
w_ind = list(range(32))
x_ind = list(range(32, 64))
y_ind = list(range(64, 96))
z_ind = list(range(96, 128))
w = self.add_XOR_component([round_key, state], [list(range(32)), list(range(32))], 32).id
x = self.add_XOR_component([round_key, state], [list(range(32, 64)), list(range(32, 64))], 32).id
y = self.add_XOR_component([round_key, state], [list(range(64, 96)), list(range(64, 96))], 32).id
z = self.add_XOR_component([round_key, state], [list(range(96, 128)), list(range(96, 128))], 32).id
# SBOX
wy = self.add_AND_component([w, y], [list(range(32)) for _ in range(2)], 32).id
x = self.add_XOR_component([x, wy], [list(range(32)) for _ in range(2)], 32).id

xy = self.add_AND_component([x, y], [list(range(32)) for _ in range(2)], 32).id
z = self.add_XOR_component([z, xy], [list(range(32)) for _ in range(2)], 32).id

wz = self.add_AND_component([w,z], [list(range(32)) for _ in range(2)], 32).id
y = self.add_XOR_component([y, wz], [list(range(32)) for _ in range(2)], 32).id

xz = self.add_AND_component([x,z], [list(range(32)) for _ in range(2)], 32).id
w = self.add_XOR_component([w, xz], [list(range(32)) for _ in range(2)], 32).id

# L Function
#w_id_links = [sb_id for sb_id in sb_outputs]
#w_input_bits = [[0] for _ in sb_outputs]
x_bits = list(range(16))
y_bits = list(range(16, 32))
wl, wr = self.l_function(
w, x_bits, y_bits, round_i
)

xl, xr = self.l_function(
x, x_bits, y_bits, round_i
)

yl, yr = self.l_function(
y, x_bits, y_bits, round_i
)

zl, zr = self.l_function(
z, x_bits, y_bits, round_i
)

# Round output
state = self.add_round_output_component([wl, wr, xl, xr, yl, yr, zl, zr], [list(range(16)) for _ in range(8)],
128)
return state.id
164 changes: 164 additions & 0 deletions claasp/ciphers/block_ciphers/aradi_block_cipher_sbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# ****************************************************************************
# Copyright 2023 Technology Innovation Institute
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ****************************************************************************


from claasp.cipher import Cipher
from claasp.utils.utils import get_ith_word
from claasp.DTOs.component_state import ComponentState
from claasp.name_mappings import INPUT_PLAINTEXT, INPUT_KEY

input_types = [INPUT_KEY, INPUT_PLAINTEXT]
PARAMETERS_CONFIGURATION_LIST = [{'block_bit_size': 128, 'key_bit_size': 256, 'number_of_rounds': 16}]


class AradiBlockCipherSBox(Cipher):
"""
Construct an instance of the AradiBlockCipher class using an SBox component.
This class is used to store compact representations of a cipher,
used to generate the corresponding cipher.
INPUT:
- ``block_bit_size`` -- **integer** (default: `64`); cipher input and output block bit size of the cipher
- ``key_bit_size`` -- **integer** (default: `128`); cipher key bit size of the cipher
- ``number_of_rounds`` -- **integer** (default: `0`); number of rounds of the cipher. The cipher uses the
corresponding amount given the other parameters (if available) when number_of_rounds is 0
- ``sub_keys_zero`` -- **boolean** (default: `False`)
- ``transformations_flag`` -- **boolean** (default: `True`)
EXAMPLES::
sage: from claasp.ciphers.block_ciphers.aradi_block_cipher_sbox import AradiBlockCipherSBox
sage: aradi = AradiBlockCipherSBox(number_of_rounds=16)
sage: aradi.evaluate([0, 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100])
83791582030165712186104466959690447122
"""

def __init__(self, number_of_rounds=16):
self.block_bit_size = 128
self.key_bit_size = 256
self.WORD_SIZE = 32
self.SBOX = [0, 1, 2, 3, 4, 13, 15, 6, 8, 11, 5, 14, 12, 7, 10, 9]

super().__init__(family_name="aradi",
cipher_type="block_cipher",
cipher_inputs=[INPUT_PLAINTEXT, INPUT_KEY],
cipher_inputs_bit_size=[self.block_bit_size, self.key_bit_size],
cipher_output_bit_size=self.block_bit_size)
self.A = [11,10,9,8]
self.B = [8,9,4,9]
self.C = [14,11,14,7]
state = INPUT_PLAINTEXT
key = INPUT_KEY

for round_i in range(number_of_rounds):
self.add_round()
round_key = self.get_round_key_id(key, round_i)
state = self.round_function(state, round_key, round_i)
key = self.update_key(key, round_i)

round_key = self.get_round_key_id(key, 0)
w = self.add_XOR_component([round_key, state], [list(range(32)), list(range(32))], 32).id
x = self.add_XOR_component([round_key, state], [list(range(32, 64)), list(range(32, 64))], 32).id
y = self.add_XOR_component([round_key, state], [list(range(64, 96)), list(range(64, 96))], 32).id
z = self.add_XOR_component([round_key, state], [list(range(96, 128)), list(range(96, 128))], 32).id
self.add_cipher_output_component([w, x, y, z], [list(range(32)) for _ in range(4)], 128)


def get_key_word_bit_indexes(self, word_index):
return list(range(32*(8-word_index-1), 32*(8-word_index)))

def get_round_key_id(self, key, round_i):
j = round_i % 2
return self.add_round_key_output_component([key, key, key, key], [self.get_key_word_bit_indexes(4*j + i) for i in range(4)], 128).id

def l_function(self, xy_id_links, xy_input_bits, round_index):
j = round_index % 4

rot_x_a = self.add_rotate_component(xy_id_links[:16], xy_input_bits[:16], 16, -self.A[j]).id
rot_x_b = self.add_rotate_component(xy_id_links[:16], xy_input_bits[:16], 16, -self.B[j]).id
rot_y_a = self.add_rotate_component(xy_id_links[16:], xy_input_bits[16:], 16, -self.A[j]).id
rot_y_c = self.add_rotate_component(xy_id_links[16:], xy_input_bits[16:], 16, -self.C[j]).id

left_part = self.add_XOR_component(xy_id_links[:16] + [rot_x_a, rot_y_c], xy_input_bits[:16] + [list(range(16)), list(range(16))], 16).id
right_part = self.add_XOR_component(xy_id_links[16:] + [rot_y_a, rot_x_b], xy_input_bits[16:] + [list(range(16)), list(range(16))], 16).id
return left_part, right_part

def m_function(self, i, j, xy_id_links, xy_input_bits):
y_inds = xy_input_bits[32:64]
x_inds = xy_input_bits[:32]
rot_i_y = self.add_rotate_component(xy_id_links, [y_inds], 32, -i).id
rot_j_x = self.add_rotate_component(xy_id_links, [x_inds], 32, -j).id
left_part = self.add_XOR_component(xy_id_links + [rot_i_y, rot_j_x], [x_inds] + [list(range(32)) for _ in range(2)], 32).id
right_part = self.add_XOR_component(xy_id_links + [rot_i_y], [x_inds] + [list(range(32))], 32).id
return left_part, right_part

def update_key(self, key, round_i):
round_constant = self.add_constant_component(32, round_i).id
k1, k0 = self.m_function(1, 3, [key], self.get_key_word_bit_indexes(1) + self.get_key_word_bit_indexes(0))
k3, k2 = self.m_function(9, 28, [key], self.get_key_word_bit_indexes(3) + self.get_key_word_bit_indexes(2))
k5, k4 = self.m_function(1, 3, [key], self.get_key_word_bit_indexes(5) + self.get_key_word_bit_indexes(4))
k7, k6 = self.m_function(9, 28, [key], self.get_key_word_bit_indexes(7) + self.get_key_word_bit_indexes(6))
k7 = self.add_XOR_component([k7, round_constant], [list(range(32)) for _ in range(2)], 32).id
if round_i % 2 == 0:
updated_key = self.add_intermediate_output_component([k7, k5, k6, k4, k3, k1, k2, k0], [list(range(32)) for _ in range(8)], 256, f"key_{round_i}")
else:
updated_key = self.add_intermediate_output_component([k7, k3, k5, k1, k6, k2, k4, k0], [list(range(32)) for _ in range(8)], 256, f"key_{round_i}")
return updated_key.id

def round_function(self, state, round_key, round_i):
w = self.add_XOR_component([round_key, state], [list(range(32)), list(range(32))], 32).id
x = self.add_XOR_component([round_key, state], [list(range(32, 64)), list(range(32, 64))], 32).id
y = self.add_XOR_component([round_key, state], [list(range(64, 96)), list(range(64, 96))], 32).id
z = self.add_XOR_component([round_key, state], [list(range(96, 128)), list(range(96, 128))], 32).id
# SBOX
sb_outputs = []
for ind in range(32):
sb_outputs.append(self.add_SBOX_component([w, x, y, z], [[ind], [ind], [ind], [ind]], 4, self.SBOX).id)

# L Function
w_id_links = [sb_id for sb_id in sb_outputs]
w_input_bits = [[0] for _ in sb_outputs]
wl, wr = self.l_function(
w_id_links, w_input_bits, round_i
)

x_id_links = [sb_id for sb_id in sb_outputs]
x_input_bits = [[1] for _ in sb_outputs]
xl, xr = self.l_function(
x_id_links, x_input_bits, round_i
)

y_id_links = [sb_id for sb_id in sb_outputs]
y_input_bits = [[2] for _ in sb_outputs]
yl, yr = self.l_function(
y_id_links, y_input_bits, round_i
)

z_id_links = [sb_id for sb_id in sb_outputs]
z_input_bits = [[3] for _ in sb_outputs]
zl, zr = self.l_function(
z_id_links, z_input_bits, round_i
)

# Round output
state = self.add_round_output_component([wl, wr, xl, xr, yl, yr, zl, zr], [list(range(16)) for _ in range(8)],
128)
return state.id


1 change: 1 addition & 0 deletions claasp/components/intermediate_output_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def get_byte_based_vectorized_python_code(self, params):
f' else:',
f' intermediateOutputs["{self.description[0]}"].append({self.id}.transpose())']


def milp_xor_linear_mask_propagation_constraints(self, model):
"""
Return a list of variables and a list of constraints for OUTPUT component for MILP xor linear.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from claasp.ciphers.block_ciphers.aradi_block_cipher_sbox import AradiBlockCipherSBox

def test_aradi_block_cipher_sbox():
aradi = AradiBlockCipherSBox()
plaintext = 0
key = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100
ciphertext = 0x3f09abf400e3bd7403260defb7c53912
assert aradi.evaluate([plaintext, key]) == ciphertext
assert aradi.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext
10 changes: 10 additions & 0 deletions tests/unit/ciphers/block_ciphers/aradi_block_cipher_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from claasp.ciphers.block_ciphers.aradi_block_cipher import AradiBlockCipher


def test_aradi_block_cipher():
aradi = AradiBlockCipher()
plaintext = 0
key = 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100
ciphertext = 0x3f09abf400e3bd7403260defb7c53912
assert aradi.evaluate([plaintext, key]) == ciphertext
assert aradi.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext

0 comments on commit 35ed390

Please sign in to comment.