diff --git a/claasp/cipher_modules/division_trail_search.py b/claasp/cipher_modules/division_trail_search.py index d2066510..fe892331 100644 --- a/claasp/cipher_modules/division_trail_search.py +++ b/claasp/cipher_modules/division_trail_search.py @@ -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 @@ -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) @@ -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": diff --git a/tests/unit/cipher_modules/division_trail_search_test.py b/tests/unit/cipher_modules/division_trail_search_test.py index 9dd11d97..9ec64434 100644 --- a/tests/unit/cipher_modules/division_trail_search_test.py +++ b/tests/unit/cipher_modules/division_trail_search_test.py @@ -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') @@ -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)