Skip to content

Commit

Permalink
add the modeling of linear_layer component
Browse files Browse the repository at this point in the history
  • Loading branch information
SiMohamedRachidi committed Nov 14, 2024
1 parent 98aeca1 commit 2033b35
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 12 deletions.
49 changes: 49 additions & 0 deletions claasp/cipher_modules/division_trail_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from collections import Counter
from sage.rings.polynomial.pbori.pbori import BooleanPolynomialRing
from claasp.cipher_modules.graph_generator import create_networkx_graph_from_input_ids, _get_predecessors_subgraph
from claasp.cipher_modules.component_analysis_tests import binary_matrix_of_linear_component
from gurobipy import Model, GRB
import os

Expand Down Expand Up @@ -221,6 +222,52 @@ def add_sbox_constraints(self, component):
self._model.addConstr(output_vars[index] >= constr)
self._model.update()

def create_copies_for_linear_layer(self, binary_matrix, input_vars_concat):
copies = {}
for index, var in enumerate(input_vars_concat):
column = [row[index] for row in binary_matrix]
number_of_1s = list(column).count(1)
if number_of_1s > 1:
current = 1
else:
current = 0
copies[index] = {}
copies[index][0] = var
copies[index]["current"] = current
self.set_as_used_variables([var])
new_vars = self._model.addVars(list(range(number_of_1s)), vtype=GRB.BINARY,
name="copy_" + var.VarName)
self._model.update()
for i in range(number_of_1s):
self._model.addConstr(var >= new_vars[i])
self._model.addConstr(
sum(new_vars[i] for i in range(number_of_1s)) >= var)
self._model.update()
for i in range(1, number_of_1s + 1):
copies[index][i] = new_vars[i - 1]
return copies

def add_linear_layer_constraints(self, component):
output_vars = self.get_output_vars(component)
input_vars_concat = self.get_input_vars(component)

if component.type == "linear_layer":
binary_matrix = component.description
else:
binary_matrix = binary_matrix_of_linear_component(component)

copies = self.create_copies_for_linear_layer(binary_matrix, input_vars_concat)
for index_row, row in enumerate(binary_matrix):
constr = 0
for index_bit, bit in enumerate(row):
if bit:
current = copies[index_bit]["current"]
constr += copies[index_bit][current]
copies[index_bit]["current"] += 1
self.set_as_used_variables([copies[index_bit][current]])
self._model.addConstr(output_vars[index_row] == constr)
self._model.update()

def add_xor_constraints(self, component):
output_vars = self.get_output_vars(component)
# print(output_vars)
Expand Down Expand Up @@ -388,6 +435,8 @@ def add_constraints(self, predecessors, input_id_link_needed, block_needed):
# print(f"---------> {component.id}")
if component.type == "sbox":
self.add_sbox_constraints(component)
elif component.type in ["linear_layer", "mix_column"]:
self.add_linear_layer_constraints(component)
elif component.type in ["cipher_output", "constant", "intermediate_output"]:
continue
elif component.type == "word_operation":
Expand Down
41 changes: 29 additions & 12 deletions tests/unit/cipher_modules/division_trail_search_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,41 @@
from claasp.ciphers.permutations.gaston_sbox_permutation import GastonSboxPermutation
from claasp.ciphers.block_ciphers.aradi_block_cipher import AradiBlockCipher
from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher
from claasp.ciphers.block_ciphers.midori_block_cipher import MidoriBlockCipher
from claasp.cipher_modules.division_trail_search import *

def test_get_where_component_is_used():
cipher = SimonBlockCipher(number_of_rounds=1)
"""
Given a number of rounds of a chosen cipher and a chosen output bit, this module produces a model that can either:
- obtain the ANF of this chosen output bit,
- find the degree of this ANF,
- or check the presence or absence of a specified monomial.
This module can only be used if the user possesses a Gurobi license.
"""

def test_find_anf_of_specific_output_bit():
# Return the monomials of the anf of the chosen output bit
cipher = SimonBlockCipher(number_of_rounds=2)
milp = MilpDivisionTrailModel(cipher)
predecessors = ['intermediate_output_0_0', 'rot_0_1', 'rot_0_2', 'rot_0_3', 'and_0_4', 'xor_0_5', 'xor_0_6', 'intermediate_output_0_7', 'cipher_output_0_8']
input_id_link_needed = 'xor_0_6'
block_needed = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
occurences = milp.get_where_component_is_used(predecessors, input_id_link_needed, block_needed)
assert list(occurences.keys()) == ['plaintext', 'key', 'rot_0_1', 'rot_0_2', 'rot_0_3', 'and_0_4', 'xor_0_5', 'xor_0_6']
monomials = milp.find_anf_of_specific_output_bit(0)
assert monomials == ['p18','k32','p0','p3p24','p0p3p9','p2p9p24','p0p2p9','p10p17','p2p9p10','p10k49','p3k56','p17p24','p2p9k56','p0p9p17','k50','p24k49','p0p9k49','p4','k49k56','p17k56']

def test_get_monomial_occurences():
cipher = GastonSboxPermutation(number_of_rounds=1)
# Return the monomials of degree 2 of the anf of the chosen output bit
cipher = SimonBlockCipher(number_of_rounds=2)
milp = MilpDivisionTrailModel(cipher)
component = cipher.get_component_from_id('sbox_0_30')
anfs = milp.get_anfs_from_sbox(component)
assert len(anfs) == 5
monomials = milp.find_anf_of_specific_output_bit(0, fixed_degree=2)
assert monomials ==['p17p24', 'p0p9k49', 'p3p24', 'p2p9k56', 'p10p17']

def test_find_degree_of_specific_output_bit():
# Return the degree of the anf of the chosen output bit of the ciphertext
cipher = AradiBlockCipher(number_of_rounds=1)
milp = MilpDivisionTrailModel(cipher)
degree = milp.find_degree_of_specific_output_bit(0)
assert degree == 3

# Return the degree of the anf of the chosen output bit of the component xor_0_12
cipher = AradiBlockCipher(number_of_rounds=1)
milp = MilpDivisionTrailModel(cipher)
degree = milp.find_degree_of_specific_output_bit(0, chosen_cipher_output='xor_0_12')
Expand All @@ -41,7 +52,13 @@ def test_find_degree_of_specific_output_bit():
degree = milp.find_degree_of_specific_output_bit(0)
assert degree == 2

cipher = MidoriBlockCipher(number_of_rounds=2)
milp = MilpDivisionTrailModel(cipher)
degree = milp.find_degree_of_specific_output_bit(0)
assert degree == 8

def test_check_presence_of_particular_monomial_in_specific_anf():
# Return the all monomials that contains p230 of the anf of the chosen output bit
cipher = GastonSboxPermutation(number_of_rounds=1)
milp = MilpDivisionTrailModel(cipher)
monomials = milp.check_presence_of_particular_monomial_in_specific_anf([("plaintext", 230)], 0)
Expand Down

0 comments on commit 2033b35

Please sign in to comment.