diff --git a/claasp/cipher.py b/claasp/cipher.py index 96b54a39..2a6f5635 100644 --- a/claasp/cipher.py +++ b/claasp/cipher.py @@ -39,6 +39,8 @@ class Cipher: + + def __init__(self, family_name, cipher_type, cipher_inputs, cipher_inputs_bit_size, cipher_output_bit_size, cipher_reference_code=None): @@ -146,6 +148,8 @@ def __init__(self, family_name, cipher_type, cipher_inputs, self._id = self.make_cipher_id() self._file_name = self.make_file_name() + def __repr__(self): + return self.id def _are_there_not_forbidden_components(self, forbidden_types, forbidden_descriptions): return self._rounds.are_there_not_forbidden_components(forbidden_types, forbidden_descriptions) diff --git a/claasp/cipher_modules/algebraic_tests.py b/claasp/cipher_modules/algebraic_tests.py index d8a25c81..5362c386 100644 --- a/claasp/cipher_modules/algebraic_tests.py +++ b/claasp/cipher_modules/algebraic_tests.py @@ -30,22 +30,36 @@ class AlgebraicTests: sage: from claasp.ciphers.toys.toyspn1 import ToySPN1 sage: toyspn = ToySPN1(number_of_rounds=2) sage: alg_test = AlgebraicTests(toyspn) - sage: alg_test.algebraic_tests(30) # timeout=30 seconds + sage: alg_test.algebraic_tests(timeout_in_seconds=10) + {'input_parameters': {'cipher.id': 'toyspn1_p6_k6_o6_r2', + 'timeout_in_seconds': 10, + 'test_name': 'algebraic_tests'}, + 'test_results': {'number_of_variables': [66, 126], + 'number_of_equations': [76, 158], + 'number_of_monomials': [96, 186], + 'max_degree_of_equations': [2, 2], + 'test_passed': [False, True]}} + sage: from claasp.cipher_modules.algebraic_tests import AlgebraicTests sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: speck = SpeckBlockCipher(number_of_rounds=1) sage: alg_test = AlgebraicTests(speck) - sage: alg_test.algebraic_tests(60) # timeout=60 seconds + sage: alg_test.algebraic_tests(timeout_in_seconds=30) + {'input_parameters': {'cipher.id': 'speck_p32_k64_o32_r1', + 'timeout_in_seconds': 30, + 'test_name': 'algebraic_tests'}, + 'test_results': {'number_of_variables': [320], + 'number_of_equations': [272], + 'number_of_monomials': [365], + 'max_degree_of_equations': [2], + 'test_passed': [True]}} - sage: speck = SpeckBlockCipher(number_of_rounds=2) - sage: alg_test = AlgebraicTests(speck) - sage: alg_test.algebraic_tests(60) """ def __init__(self, cipher): self._cipher = cipher - def algebraic_tests(self, timeout=60): + def algebraic_tests(self, timeout_in_seconds=60): from sage.structure.sequence import Sequence nvars_up_to_round = [] @@ -66,23 +80,20 @@ def algebraic_tests(self, timeout=60): nmonomials_up_to_round.append(Fseq.nmonomials()) max_deg_of_equations_up_to_round.append(Fseq.maximal_degree()) - if tests_up_to_round and tests_up_to_round[-1] is True: - tests_up_to_round.append(True) - else: - from cysignals.alarm import alarm, cancel_alarm - - try: - alarm(timeout) - cancel_alarm() - result = False - except InterruptedError: - result = True + from cysignals.alarm import alarm, cancel_alarm, AlarmInterrupt + try: + alarm(timeout_in_seconds) + Fseq.groebner_basis() + cancel_alarm() + result = False + except AlarmInterrupt: + result = True - tests_up_to_round.append(result) + tests_up_to_round.append(result) input_parameters = { - "cipher.id": self._cipher.id, - "timeout": timeout, + "cipher": self._cipher, + "timeout_in_seconds": timeout_in_seconds, "test_name": "algebraic_tests" } test_results = { diff --git a/claasp/cipher_modules/avalanche_tests.py b/claasp/cipher_modules/avalanche_tests.py index b6e85e8b..48ac4f79 100644 --- a/claasp/cipher_modules/avalanche_tests.py +++ b/claasp/cipher_modules/avalanche_tests.py @@ -87,6 +87,7 @@ def avalanche_tests(self, number_of_samples=5, avalanche_dependence_uniform_bias avalanche_dependence_uniform_bias) intermediate_output_names = self._add_intermediate_output_components_id_to_dictionary(self._cipher.get_all_components()) diffusion_tests = {"input_parameters": { + "cipher": self._cipher, "test_name": "avalanche_tests", "number_of_samples": number_of_samples, "avalanche_dependence_uniform_bias": avalanche_dependence_uniform_bias, diff --git a/claasp/cipher_modules/component_analysis_tests.py b/claasp/cipher_modules/component_analysis_tests.py index e525cc0c..24db9c39 100644 --- a/claasp/cipher_modules/component_analysis_tests.py +++ b/claasp/cipher_modules/component_analysis_tests.py @@ -69,7 +69,8 @@ def component_analysis_tests(self): output_dictionary = { 'input_parameters': { - 'test_name': 'component_analysis' + 'test_name': 'component_analysis', + 'cipher': self._cipher }, 'test_results': components_analysis } @@ -153,15 +154,19 @@ def print_component_analysis_as_radar_charts(self, results=None): plt.rcParams['figure.figsize'] = [20, 20] # remove XOR from results - results_without_xor = [results[i] for i in range(len(results)) if results[i]["description"][0] != "XOR"] - results = self._remove_components_with_strings_as_values(results_without_xor) + # results_without_xor = [results[i] for i in range(len(results)) if results[i]["description"][0] != "XOR"] + results_without_fsr = [results[i] for i in range(len(results)) if results[i]["type"] != "fsr"] + # removed for now because the fsr dictionary does not follow the standard structure as the other components: + # the keys properties, values, etc are not present. + # results = self._remove_components_with_strings_as_values(results_without_xor) + results = self._remove_components_with_strings_as_values(results_without_fsr) nb_plots = len(results) col = 2 row = nb_plots // col if nb_plots % col != 0: row += nb_plots % col - positions = {8: -0.7, 3: -0.4} + positions = {8: -0.7, 3: -0.4} # positions of the text according to the numbers of properties for plot_number in range(nb_plots): categories = list(results[plot_number]["properties"].keys()) @@ -211,10 +216,12 @@ def print_component_analysis_as_radar_charts(self, results=None): self._fill_area(ax, categories, plot_number, positions, results) # Show the graph - plt.subplots_adjust(left=0.25, bottom=0.1, right=0.7, top=0.95, wspace=0, hspace=0.96) + if nb_plots >= 5: + plt.subplots_adjust(left=0.02, bottom=0.1, right=0.7, top=0.95, wspace=1, hspace=0.96) + else: + plt.subplots_adjust(left=0.09, bottom=0.3, right=0.7, top=0.7, wspace=1, hspace=0.96) plt.show() #print("The radar chart can be plot with the build-in method plt.show()") - #return plt @@ -274,7 +281,7 @@ def _select_boolean_function(self, component, boolean_polynomial_ring): elif component.description[0] == "MODADD": return self._MODADD_as_boolean_function(component, boolean_polynomial_ring) else: - return "TODO(...)" + return f"TODO: {component.id} not implemented yet" def _MODADD_as_boolean_function(self, component, boolean_polynomial_ring): @@ -425,11 +432,8 @@ def _select_properties_function(self, boolean_polynomial_ring, operation): if component.type == 'fsr': return self._fsr_properties(operation) - if component.type == WORD_OPERATION: - print(f"TODO : {component.description[0]}") - return {} else: - print(f"TODO : {component.type}") + # print(f"TODO: not implemented yet") return {} def _is_mds(self, component): @@ -485,7 +489,6 @@ def _word_operation_properties(self, operation, boolean_polynomial_ring): INPUT: - ``operation`` -- **list**; a list containing: - * a component with the operation under study * number of occurrences of the operation * list of ids of all the components with the same underlying operation @@ -642,7 +645,7 @@ def _linear_layer_properties(self, operation): "min_possible_value": 1, "max_possible_value": pow(2, component.input_bit_size) - 1 } - if component.input_bit_size <= 32: + if component.input_bit_size <= 64: dictio["properties"]["differential_branch_number"] = {"value": branch_number(component, 'differential', 'bit'), "min_possible_value": 0, "max_possible_value": component.input_bit_size} @@ -883,7 +886,8 @@ def _fill_area(self, ax, categories, plot_number, positions, results): text += f"{category} = {int(results[plot_number]['properties'][category]['value'])} " \ f"(best is {results[plot_number]['properties'][category]['max_possible_value']}, " \ f"worst is {results[plot_number]['properties'][category]['min_possible_value']})\n" - plt.text(0, positions[len(categories)], text, transform=ax.transAxes, size="small") + # plt.text(0, positions[len(categories)], text, transform=ax.transAxes, size="small") + plt.text(2, 0, text, transform=ax.transAxes, size="small") def _initialise_spider_plot(self, plot_number, results): is_component_word_operation = results[plot_number]["type"] == "word_operation" diff --git a/claasp/cipher_modules/continuous_diffusion_analysis.py b/claasp/cipher_modules/continuous_diffusion_analysis.py index aed05b60..74f9926a 100644 --- a/claasp/cipher_modules/continuous_diffusion_analysis.py +++ b/claasp/cipher_modules/continuous_diffusion_analysis.py @@ -440,6 +440,7 @@ def continuous_diffusion_tests(self, """ continuous_diffusion_tests = {"input_parameters": { 'test_name': 'continuous_diffusion_tests', + 'cipher': self.cipher, 'continuous_avalanche_factor_number_of_samples': continuous_avalanche_factor_number_of_samples, 'threshold_for_avalanche_factor': threshold_for_avalanche_factor, 'continuous_neutral_measure_beta_number_of_samples': continuous_neutral_measure_beta_number_of_samples, diff --git a/claasp/cipher_modules/models/algebraic/algebraic_model.py b/claasp/cipher_modules/models/algebraic/algebraic_model.py index 97d97e19..efdf4826 100644 --- a/claasp/cipher_modules/models/algebraic/algebraic_model.py +++ b/claasp/cipher_modules/models/algebraic/algebraic_model.py @@ -200,11 +200,15 @@ def polynomial_system_at_round(self, r): component_type = component.type operation = component.description[0] component_types = ["sbox", "linear_layer", "mix_column", "constant"] - operations = ["XOR", "AND", "OR", "SHIFT", "ROTATE", "NOT"] + operations = ["XOR", "AND", "OR", "SHIFT", "ROTATE", "NOT", "MODADD", "MODSUB"] if component_type in component_types or (component_type == "word_operation" and operation in operations): polynomials += component.algebraic_polynomials(self) + elif component_type == "word_operation" and \ + operation in ['ROTATE_BY_VARIABLE_AMOUNT', 'SHIFT_BY_VARIABLE_AMOUNT']: + raise ValueError(f"polynomial generation of {operation} operation is not supported at present") + return Sequence(polynomials) def ring(self): @@ -275,6 +279,17 @@ def var_names(self): # aux output variables var_names += \ [component_id + "_" + "o" + str(n) + "_" + str(i) for i in range(output_size)] + elif component.type == "word_operation" and component.description[0].lower() == "modsub": + ninput_words = component.description[1] + nadditions = ninput_words - 1 + + for n in range(nadditions): + # borrow variables + var_names += [component_id + "_" + "b" + str(n) + "_" + str(i) for i in range(output_size)] + if n < nadditions - 1: + # aux output variables + var_names += \ + [component_id + "_" + "o" + str(n) + "_" + str(i) for i in range(output_size)] for i in range(len(self._cipher.inputs)): var_names += [self._cipher.inputs[i] + "_" + diff --git a/claasp/cipher_modules/models/cp/cp_model.py b/claasp/cipher_modules/models/cp/cp_model.py index de8ed203..10dfec59 100644 --- a/claasp/cipher_modules/models/cp/cp_model.py +++ b/claasp/cipher_modules/models/cp/cp_model.py @@ -25,6 +25,7 @@ from sage.crypto.sbox import SBox from claasp.cipher_modules.component_analysis_tests import branch_number +from claasp.cipher_modules.models.cp.minizinc_utils import usefulfunctions from claasp.cipher_modules.models.utils import write_model_to_file, convert_solver_solution_to_dictionary from claasp.name_mappings import SBOX @@ -57,15 +58,13 @@ def initialise_model(self): self.list_of_xor_components = [] self.list_of_xor_all_inputs = [] self.component_and_probability = {} - self._model_prefix = [ - 'include "globals.mzn";', - f"include \"{os.path.join(os.path.dirname(__file__), 'Minizinc_functions', 'Usefulfunctions.mzn')}\";"] + self._model_prefix = ['include "globals.mzn";', f'{usefulfunctions.MINIZINC_USEFUL_FUNCTIONS}'] def add_solutions_from_components_values(self, components_values, memory, model_type, solutions, solve_time, solver_name, solver_output, total_weight): for i in range(len(total_weight)): solution = convert_solver_solution_to_dictionary( - self.cipher_id, + self._cipher, model_type, solver_name, solve_time, @@ -287,7 +286,7 @@ def format_component_value(self, component_id, string): return value - def get_command_for_solver_process(self, input_file_path, model_type, solver_name): + def get_command_for_solver_process(self, input_file_path, model_type, solver_name, num_of_processors, timelimit): solvers = ['xor_differential_one_solution', 'xor_linear_one_solution', 'deterministic_truncated_xor_differential_one_solution', @@ -296,9 +295,11 @@ def get_command_for_solver_process(self, input_file_path, model_type, solver_nam 'evaluate_cipher'] write_model_to_file(self._model_constraints, input_file_path) if model_type in solvers: - command = ['minizinc', '--solver-statistics', '--solver', solver_name, input_file_path] + command = ['minizinc', f'-p {num_of_processors}', '--solver-statistics', '--time-limit', str(timelimit), + '--solver', solver_name, input_file_path] else: - command = ['minizinc', '-a', '--solver-statistics', '--solver', solver_name, input_file_path] + command = ['minizinc', f'-p {num_of_processors}', '-a', '--solver-statistics', + '--time-limit', str(timelimit), '--solver', solver_name, input_file_path] return command @@ -409,7 +410,7 @@ def set_component_solution_value(self, component_solution, truncated, value): else: component_solution['value'] = value - def solve(self, model_type, solver_name=None): + def solve(self, model_type, solver_name=None, num_of_processors=1, timelimit=60000): """ Return the solution of the model. @@ -430,6 +431,8 @@ def solve(self, model_type, solver_name=None): * ``'Chuffed'`` * ``'Gecode'`` * ``'COIN-BC'`` + - ``num_of_processors`` -- **integer**; the number of processors to be used + - ``timelimit`` -- **integer**; time limit to output a result EXAMPLES:: @@ -450,7 +453,9 @@ def solve(self, model_type, solver_name=None): """ cipher_name = self.cipher_id input_file_path = f'{cipher_name}_Cp_{model_type}_{solver_name}.mzn' - command = self.get_command_for_solver_process(input_file_path, model_type, solver_name) + command = self.get_command_for_solver_process( + input_file_path, model_type, solver_name, num_of_processors, timelimit + ) solver_process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8") os.remove(input_file_path) if solver_process.returncode >= 0: @@ -458,7 +463,7 @@ def solve(self, model_type, solver_name=None): solver_output = solver_process.stdout.splitlines() solve_time, memory, components_values, total_weight = self._parse_solver_output(solver_output) if components_values == {}: - solution = convert_solver_solution_to_dictionary(self.cipher_id, model_type, solver_name, + solution = convert_solver_solution_to_dictionary(self._cipher, model_type, solver_name, solve_time, memory, components_values, total_weight) if 'UNSATISFIABLE' in solver_output[0]: diff --git a/claasp/cipher_modules/models/cp/cp_models/cp_deterministic_truncated_xor_differential_model.py b/claasp/cipher_modules/models/cp/cp_models/cp_deterministic_truncated_xor_differential_model.py index 2217de17..3b78f4ba 100644 --- a/claasp/cipher_modules/models/cp/cp_models/cp_deterministic_truncated_xor_differential_model.py +++ b/claasp/cipher_modules/models/cp/cp_models/cp_deterministic_truncated_xor_differential_model.py @@ -610,7 +610,7 @@ def _parse_solver_output(self, output_to_parse, model_type): return time, memory, components_values - def solve(self, model_type, solver_name=None): + def solve(self, model_type, solver_name=None, num_of_processors=1, timelimit=60000): """ Return the solution of the model. @@ -632,6 +632,9 @@ def solve(self, model_type, solver_name=None): * ``'Gecode'`` * ``'COIN-BC'`` + - ``num_of_processors`` -- **integer**; the number of processors to be used + - ``timelimit`` -- **integer**; time limit to output a result + EXAMPLES:: sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import CpXorDifferentialTrailSearchModel @@ -652,7 +655,9 @@ def solve(self, model_type, solver_name=None): cipher_name = self.cipher_id input_file_path = f'{cipher_name}_Cp_{model_type}_{solver_name}.mzn' - command = self.get_command_for_solver_process(input_file_path, model_type, solver_name) + command = self.get_command_for_solver_process( + input_file_path, model_type, solver_name, num_of_processors, timelimit + ) solver_process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8") os.remove(input_file_path) if solver_process.returncode >= 0: diff --git a/claasp/cipher_modules/models/cp/cp_models/cp_xor_differential_model.py b/claasp/cipher_modules/models/cp/cp_models/cp_xor_differential_model.py index e9228ce1..48f8d6d6 100644 --- a/claasp/cipher_modules/models/cp/cp_models/cp_xor_differential_model.py +++ b/claasp/cipher_modules/models/cp/cp_models/cp_xor_differential_model.py @@ -22,6 +22,7 @@ from sage.crypto.sbox import SBox from claasp.cipher_modules.models.cp.cp_model import CpModel, solve_satisfy +from claasp.cipher_modules.models.utils import get_single_key_scenario_format_for_fixed_values from claasp.name_mappings import (CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, SBOX, MIX_COLUMN, WORD_OPERATION, XOR_DIFFERENTIAL, LINEAR_LAYER) @@ -36,7 +37,7 @@ def and_xor_differential_probability_ddt(numadd): EXAMPLES:: - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import ( ....: and_xor_differential_probability_ddt) sage: from claasp.ciphers.block_ciphers.simon_block_cipher import SimonBlockCipher sage: simon = SimonBlockCipher() @@ -110,16 +111,10 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): EXAMPLES:: - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( - ....: CpXorDifferentialTrailSearchModel) + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - sage: cp = CpXorDifferentialTrailSearchModel(speck) - sage: fixed_variables = [set_fixed_variables('key', 'equal', range(64), - ....: integer_to_bit_list(0, 64, 'little'))] - sage: fixed_variables.append(set_fixed_variables('plaintext', 'equal', range(32), - ....: integer_to_bit_list(0, 32, 'little'))) + sage: cp = CpXorDifferentialModel(speck) sage: cp.build_xor_differential_trail_model(-1, fixed_variables) """ self.initialise_model() @@ -138,6 +133,8 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): def build_xor_differential_trail_model_template(self, weight, fixed_variables): variables = [] self._variables_list = [] + if fixed_variables == []: + fixed_variables = get_single_key_scenario_format_for_fixed_values(self._cipher) constraints = self.fix_variables_value_constraints(fixed_variables) component_types = [CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, MIX_COLUMN, WORD_OPERATION] operation_types = ['AND', 'MODADD', 'MODSUB', 'NOT', 'OR', 'ROTATE', 'SHIFT', 'XOR'] @@ -169,17 +166,11 @@ def final_xor_differential_constraints(self, weight): EXAMPLES:: - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( - ....: CpXorDifferentialTrailSearchModel) + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - sage: cp = CpXorDifferentialTrailSearchModel(speck) - sage: fixed_variables = [set_fixed_variables('key', 'equal', range(64), - ....: integer_to_bit_list(0, 64, 'little'))] - sage: fixed_variables.append(set_fixed_variables('plaintext', 'equal', range(32), - ....: integer_to_bit_list(0, 32, 'little'))) - sage: cp.build_xor_differential_trail_model(-1, fixed_variables) + sage: cp = CpXorDifferentialModel(speck) + sage: cp.build_xor_differential_trail_model(-1) sage: cp.final_xor_differential_constraints(-1)[:-1] ['solve:: int_search(p, smallest, indomain_min, complete) minimize weight;'] """ @@ -210,6 +201,7 @@ def final_xor_differential_constraints(self, weight): def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='Chuffed'): """ Return a list of solutions containing all the differential trails having the ``fixed_weight`` weight. + By default, the search is set in the single-key setting. INPUT: @@ -223,21 +215,25 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed EXAMPLES:: - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( - ....: CpXorDifferentialTrailSearchModel) + # single-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: cp = CpXorDifferentialModel(speck) + sage: trails = cp.find_all_xor_differential_trails_with_fixed_weight(9, solver_name='Chuffed') + sage: len(trails) + 2 + + # related-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list - sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) - sage: cp = CpXorDifferentialTrailSearchModel(speck) - sage: fixed_values = [] - sage: fixed_values.append(set_fixed_variables('key', 'equal', list(range(16)), - ....: integer_to_bit_list(0, 16, 'big'))) - sage: fixed_values.append(set_fixed_variables('plaintext', 'not_equal', list(range(8)), - ....: integer_to_bit_list(0, 8, 'big'))) - sage: trails = cp.find_all_xor_differential_trails_with_fixed_weight(1, fixed_values, 'Chuffed') # long - ... - sage: len(trails) # long - 6 + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher( number_of_rounds=5) + sage: cp = CpXorDifferentialModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trails = cp.find_all_xor_differential_trails_with_fixed_weight(2, fixed_values=[key], solver_name='Chuffed') + sage: len(trails) + 2 """ start = tm.time() self.build_xor_differential_trail_model(fixed_weight, fixed_values) @@ -253,7 +249,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w solver_name='Chuffed'): """ Return a list of solutions containing all the differential trails. - + By default, the search is set in the single-key setting. The differential trails having the weight of correlation lying in the interval ``[min_weight, max_weight]``. INPUT: @@ -269,21 +265,26 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w EXAMPLES:: - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( - ....: CpXorDifferentialTrailSearchModel) + # single-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: cp = CpXorDifferentialModel(speck) + sage: trails = cp.find_all_xor_differential_trails_with_weight_at_most(9,10, solver_name='Chuffed') + sage: len(trails) + 28 + + # related-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list - sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) - sage: cp = CpXorDifferentialTrailSearchModel(speck) - sage: fixed_values = [] - sage: fixed_values.append(set_fixed_variables('key', 'equal', list(range(16)), - ....: integer_to_bit_list(0, 16, 'big'))) - sage: fixed_values.append(set_fixed_variables('plaintext', 'not_equal', list(range(8)), - ....: integer_to_bit_list(0, 8, 'big'))) - sage: trails = cp.find_all_xor_differential_trails_with_weight_at_most(0,1, fixed_values, 'Chuffed') - ... - sage: len(trails) # long - 7 + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: cp = CpXorDifferentialModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trails = cp.find_all_xor_differential_trails_with_weight_at_most(2,3, fixed_values=[key], solver_name='Chuffed') # long + sage: len(trails) + 9 + """ start = tm.time() self.build_xor_differential_trail_model(0, fixed_values) @@ -311,7 +312,8 @@ def find_differential_weight(self, fixed_values=[], solver_name='Chuffed'): def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name='Chuffed'): """ - Return the solution representing a differential trail with the lowest weight of correlation. + Return the solution representing a differential trail with the lowest probability weight. + By default, the search is set in the single-key setting. .. NOTE:: @@ -329,24 +331,29 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name EXAMPLES:: - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( - ....: CpXorDifferentialTrailSearchModel) + # single-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=5) - sage: cp = CpXorDifferentialTrailSearchModel(speck) - sage: fixed_values = [] - sage: fixed_values.append(set_fixed_variables('key', 'equal', list(range(64)), - ....: integer_to_bit_list(0, 64, 'big'))) - sage: fixed_values.append(set_fixed_variables('plaintext', 'not_equal', list(range(32)), - ....: integer_to_bit_list(0, 32, 'big'))) - sage: cp.find_lowest_weight_xor_differential_trail(fixed_values,'Chuffed') # random + sage: cp = CpXorDifferentialModel(speck) + sage: cp.find_lowest_weight_xor_differential_trail(solver_name='Chuffed') # random {'building_time': 0.007165431976318359, 'cipher_id': 'speck_p32_k64_o32_r4', 'components_values': {'cipher_output_4_12': {'value': '850a9520', 'weight': 0}, ... 'total_weight': '9.0'} + + # related-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: cp = CpXorDifferentialModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) + sage: trail = cp.find_lowest_weight_xor_differential_trail(fixed_values=[key], solver_name='Chuffed') + sage: trail['total_weight'] + '1.0' """ start = tm.time() self.build_xor_differential_trail_model(-1, fixed_values) @@ -355,12 +362,12 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name solution = self.solve('xor_differential_one_solution', solver_name) solution['building_time_seconds'] = build_time solution['test_name'] = "find_lowest_weight_xor_differential_trail" - return solution def find_one_xor_differential_trail(self, fixed_values=[], solver_name='Chuffed'): """ Return the solution representing a differential trail with any weight. + By default, the search is set in the single-key setting. INPUT: @@ -373,23 +380,27 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='Chuffed' EXAMPLES:: - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( - ....: CpXorDifferentialTrailSearchModel) - sage: from claasp.cipher_modules.models.utils import set_fixed_variables + # single-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: speck = SpeckBlockCipher(number_of_rounds=2) - sage: cp = CpXorDifferentialTrailSearchModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=[0]*32) - sage: cp.find_one_xor_differential_trail([plaintext], 'Chuffed') # random + sage: cp = CpXorDifferentialModel(speck) + sage: cp.find_one_xor_differential_trail(solver_name=Chuffed') # random {'cipher_id': 'speck_p32_k64_o32_r2', 'model_type': 'xor_differential_one_solution', ... 'cipher_output_1_12': {'value': 'ffff0000', 'weight': 0}}, 'total_weight': '18.0'} + + # related-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=2) + sage: cp = CpXorDifferentialModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) + sage: trail = cp.find_one_xor_differential_trail(fixed_values=[key], solver_name='Chuffed') # random + """ start = tm.time() self.build_xor_differential_trail_model(0, fixed_values) @@ -403,7 +414,8 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='Chuffed' def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight=-1, fixed_values=[], solver_name='Chuffed'): """ - Return the solution representing a differential trail with the weight of correlation equal to ``fixed_weight``. + Return the solution representing a differential trail with the weight of probability equal to ``fixed_weight``. + By default, the search is set in the single-key setting. INPUT: @@ -417,23 +429,25 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight=-1, fix EXAMPLES:: - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( - ....: CpXorDifferentialTrailSearchModel) - sage: from claasp.cipher_modules.models.utils import set_fixed_variables + # single-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: speck = SpeckBlockCipher(number_of_rounds=5) - sage: cp = CpXorDifferentialTrailSearchModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=[0]*32) - sage: cp.find_one_xor_differential_trail_with_fixed_weight(9, [plaintext], 'Chuffed') # random - {'cipher_id': 'speck_p32_k64_o32_r5', - 'model_type': 'xor_differential_one_solution', - ... - 'total_weight': '9.0', - 'building_time_seconds': 0.0013153553009033203} + sage: speck = SpeckBlockCipher(number_of_rounds=3) + sage: cp = CpXorDifferentialModel(speck) + sage: trail = cp.find_one_xor_differential_trail_with_fixed_weight(3, solver_name='Chuffed') # random + sage: trail['total_weight'] + '3.0' + + # related-key setting + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=3) + sage: cp = CpXorDifferentialModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trail = cp.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[key], solver_name='Chuffed') + sage: trail['total_weight'] + '3.0' """ start = tm.time() self.build_xor_differential_trail_model(fixed_weight, fixed_values) @@ -468,10 +482,9 @@ def input_xor_differential_constraints(self): EXAMPLES:: sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_trail_search_model import ( - ....: CpXorDifferentialTrailSearchModel) + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import (CpXorDifferentialModel) sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - sage: cp = CpXorDifferentialTrailSearchModel(speck) + sage: cp = CpXorDifferentialModel(speck) sage: cp.input_xor_differential_constraints() (['array[0..31] of var 0..1: plaintext;', 'array[0..63] of var 0..1: key;', diff --git a/claasp/cipher_modules/models/cp/cp_models/cp_xor_linear_model.py b/claasp/cipher_modules/models/cp/cp_models/cp_xor_linear_model.py index 11f57843..3b33c7d3 100644 --- a/claasp/cipher_modules/models/cp/cp_models/cp_xor_linear_model.py +++ b/claasp/cipher_modules/models/cp/cp_models/cp_xor_linear_model.py @@ -23,9 +23,10 @@ from sage.crypto.sbox import SBox from claasp.cipher_modules.models.cp.cp_model import CpModel, solve_satisfy, constraint_type_error -from claasp.cipher_modules.models.utils import get_bit_bindings +from claasp.cipher_modules.models.utils import get_bit_bindings, \ + get_single_key_scenario_format_for_fixed_values from claasp.name_mappings import INTERMEDIATE_OUTPUT, XOR_LINEAR, CONSTANT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, \ - MIX_COLUMN, WORD_OPERATION + MIX_COLUMN, WORD_OPERATION, INPUT_KEY class CpXorLinearModel(CpModel): @@ -131,6 +132,13 @@ def build_xor_linear_trail_model(self, weight=-1, fixed_variables=[]): self.component_and_probability = {} self._variables_list = [] variables = [] + if INPUT_KEY not in [variable["component_id"] for variable in fixed_variables]: + cipher_without_key_schedule = self._cipher.remove_key_schedule() + self._cipher = cipher_without_key_schedule + self.bit_bindings, self.bit_bindings_for_intermediate_output = get_bit_bindings( + self._cipher, lambda record: f'{record[0]}_{record[2]}[{record[1]}]') + if fixed_variables == []: + fixed_variables = get_single_key_scenario_format_for_fixed_values(self._cipher) constraints = self.fix_variables_value_xor_linear_constraints(fixed_variables) self._model_constraints = constraints @@ -210,6 +218,7 @@ def final_xor_linear_constraints(self, weight): def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='Chuffed'): """ Return a list of solutions containing all the linear trails having the ``fixed_weight`` weight of correlation. + By default, the search removes the key schedule, if any. INPUT: @@ -225,14 +234,22 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=3) - sage: speck = speck.remove_key_schedule() sage: cp = CpXorLinearModel(speck) - sage: fixed_variables = [set_fixed_variables('plaintext', 'not_equal', list(range(8)), integer_to_bit_list(0, 8, 'little'))] - sage: trails = cp.find_all_xor_linear_trails_with_fixed_weight(1, fixed_variables) # long - sage: len(trails) # long + sage: trails = cp.find_all_xor_linear_trails_with_fixed_weight(1) # long + sage: len(trails) 12 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: cp = CpXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trails = cp.find_all_xor_linear_trails_with_fixed_weight(2, fixed_values=[key]) + sage: len(trails) + 8 """ start = tm.time() self.build_xor_linear_trail_model(fixed_weight, fixed_values) @@ -248,6 +265,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight= fixed_values=[], solver_name='Chuffed'): """ Return a list of solutions containing all the linear trails having the weight of correlation lying in the interval ``[min_weight, max_weight]``. + By default, the search removes the key schedule, if any. INPUT: @@ -264,14 +282,22 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight= sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=3) - sage: speck = speck.remove_key_schedule() sage: cp = CpXorLinearModel(speck) - sage: fixed_variables = [set_fixed_variables('plaintext', 'not_equal', list(range(8)), integer_to_bit_list(0, 8, 'little'))] - sage: trails = cp.find_all_xor_linear_trails_with_weight_at_most(0, 1, fixed_variables) # long time - sage: len(trails) # long time + sage: trails = cp.find_all_xor_linear_trails_with_weight_at_most(0, 1) + sage: len(trails) 13 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: cp = CpXorLinearModel(speck) + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trails = cp.find_all_xor_linear_trails_with_weight_at_most(0, 3, fixed_values=[key]) + sage: len(trails) + 73 """ start = tm.time() self.build_xor_linear_trail_model(0, fixed_values) @@ -288,6 +314,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight= def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='Chuffed'): """ Return the solution representing a linear trail with the lowest weight of correlation. + By default, the search removes the key schedule, if any. .. NOTE:: @@ -307,19 +334,22 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='Chuf sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list - sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - sage: speck = speck.remove_key_schedule() + sage: speck = SpeckBlockCipher(number_of_rounds=4) sage: cp= CpXorLinearModel(speck) - sage: fixed_variables = [set_fixed_variables('plaintext', 'not_equal', list(range(32)), integer_to_bit_list(0, 32, 'little'))] - sage: cp.find_lowest_weight_xor_linear_trail(fixed_variables) # random - {'building_time': 0.007994651794433594, - 'cipher_id': 'speck_p32_k64_o32_r4', - 'components_values': {'cipher_output_3_12_o': {'value': '38103010', - 'weight': 0}, - ... - 'total_weight': 3.0 - 'building_time_seconds': 0.009123563766479492} + sage: trail = cp.find_lowest_weight_xor_linear_trail() + sage: trail['total_weight'] + '3.0' + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=16, key_bit_size=32, number_of_rounds=4) + sage: cp = CpXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) + sage: trail = cp.find_lowest_weight_xor_linear_trail(fixed_values=[key]) + sage: trail['total_weight'] + '3.0' """ start = tm.time() self.build_xor_linear_trail_model(-1, fixed_values) @@ -334,6 +364,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='Chuf def find_one_xor_linear_trail(self, fixed_values=[], solver_name='Chuffed'): """ Return the solution representing a linear trail with any weight of correlation. + By default, the search removes the key schedule, if any. INPUT: @@ -348,20 +379,18 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='Chuffed'): sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - sage: speck = speck.remove_key_schedule() sage: cp = CpXorLinearModel(speck) - sage: fixed_variables = [set_fixed_variables('plaintext', 'not_equal', list(range(32)), integer_to_bit_list(0, 32, 'little'))] - sage: cp.find_one_xor_linear_trail(fixed_variables) # random - {'cipher_id': 'speck_p32_k64_o32_r4', - ... - 'memory': '0.0MB', - 'components_values': {'plaintext': {'weight': 0, 'value': '0xffff'}, - ... - 'cipher_output_3_12': {'weight': 0, 'value': '0xffffffff'}}, - 'total_weight': 16.0 - 'building_time_seconds': 0.00975656509399414} + sage: cp.find_one_xor_linear_trail() # random + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) + sage: cp = CpXorLinearModel(speck) + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: cp.find_one_xor_linear_trail(fixed_values=[key]) # random """ start = tm.time() self.build_xor_linear_trail_model(0, fixed_values) @@ -376,6 +405,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='Chuffed'): def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight=-1, fixed_values=[], solver_name='Chuffed'): """ Return the solution representing a linear trail with the weight of correlation equal to ``fixed_weight``. + By default, the search removes the key schedule, if any. INPUT: @@ -391,18 +421,22 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight=-1, fixed_val sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - sage: speck = speck.remove_key_schedule() sage: cp = CpXorLinearModel(speck) - sage: fixed_variables = [set_fixed_variables('plaintext', 'not_equal', list(range(32)), integer_to_bit_list(0, 32, 'little'))] - sage: cp.find_one_xor_linear_trail_with_fixed_weight(3, fixed_variables) # random - {'cipher_id': 'speck_p32_k64_o32_r4', - 'model_type': 'xor_linear_one_solution', - ... - 'total_weight': 3.0, - 'building_time_seconds': 0.005683183670043945} + sage: trail = cp.find_one_xor_linear_trail_with_fixed_weight(3) + sage: trail['total_weight'] + '3.0' + # including the key schedule in the model + sage: from claasp.cipher_modules.models.cp.cp_models.cp_xor_linear_model import CpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: cp = CpXorLinearModel(speck) + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trail = cp.find_one_xor_linear_trail_with_fixed_weight(3, fixed_values=[key]) + sage: trail['total_weight'] + '3.0' """ start = tm.time() self.build_xor_linear_trail_model(fixed_weight, fixed_values) diff --git a/claasp/cipher_modules/models/cp/minizinc_utils/__init__.py b/claasp/cipher_modules/models/cp/minizinc_utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/claasp/cipher_modules/models/cp/Minizinc_functions/Usefulfunctions.mzn b/claasp/cipher_modules/models/cp/minizinc_utils/usefulfunctions.py similarity index 96% rename from claasp/cipher_modules/models/cp/Minizinc_functions/Usefulfunctions.mzn rename to claasp/cipher_modules/models/cp/minizinc_utils/usefulfunctions.py index 0c7b1de1..bb0b0665 100644 --- a/claasp/cipher_modules/models/cp/Minizinc_functions/Usefulfunctions.mzn +++ b/claasp/cipher_modules/models/cp/minizinc_utils/usefulfunctions.py @@ -1,3 +1,4 @@ +MINIZINC_USEFUL_FUNCTIONS = """ include "globals.mzn"; % XOR of 2 arrays @@ -81,11 +82,11 @@ array [0..length(a)-1] of var 0..1: prob, array [0..length(a)-1] of var 0..1: X=Xor3(a,b,c) } in - state[0]=0 /\ + state[0]=0 /\\ forall (i in 0..length(a)-1)( - if state[i]==0 then all_equal([a[i],b[i],c[i]]) else true endif /\ - state[i+1]=((X[i]+state[i]) mod 2) /\ - if state[i]==1 then prob[i]=1 else prob[i]=0 endif) /\ + if state[i]==0 then all_equal([a[i],b[i],c[i]]) else true endif /\\ + state[i+1]=((X[i]+state[i]) mod 2) /\\ + if state[i]==1 then prob[i]=1 else prob[i]=0 endif) /\\ p=100 * sum(prob) ); @@ -107,11 +108,11 @@ } in forall (i in 0..length(a)-1) ( if i0 then a[pivot]+b[pivot]>0 else true endif ); %Hamming weight of an array function var 0..512: Ham_weight(array[int] of var int: x) = sum(i in index_set(x))(x[i] != 0); - +""" diff --git a/claasp/cipher_modules/models/milp/milp_model.py b/claasp/cipher_modules/models/milp/milp_model.py index 467aed6a..f43e35c6 100644 --- a/claasp/cipher_modules/models/milp/milp_model.py +++ b/claasp/cipher_modules/models/milp/milp_model.py @@ -344,7 +344,7 @@ def solve(self, model_type, solver_name=SOLVER_DEFAULT, external_solver_name=Non status, milp_time, milp_memory = self._solve_with_internal_solver() objective_value, components_values = self._parse_solver_output() - solution = convert_solver_solution_to_dictionary(self.cipher_id, model_type, solver_name_in_solution, milp_time, + solution = convert_solver_solution_to_dictionary(self._cipher, model_type, solver_name_in_solution, milp_time, milp_memory, components_values, objective_value) solution['status'] = status return solution diff --git a/claasp/cipher_modules/models/milp/milp_models/milp_xor_differential_model.py b/claasp/cipher_modules/models/milp/milp_models/milp_xor_differential_model.py index e34c9edf..2c9e186e 100644 --- a/claasp/cipher_modules/models/milp/milp_models/milp_xor_differential_model.py +++ b/claasp/cipher_modules/models/milp/milp_models/milp_xor_differential_model.py @@ -14,8 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # **************************************************************************** - - +import os +import sys import time from bitstring import BitArray @@ -24,10 +24,11 @@ from claasp.cipher_modules.models.milp.milp_model import MilpModel, verbose_print from claasp.cipher_modules.models.milp.utils.milp_name_mappings import MILP_XOR_DIFFERENTIAL, MILP_PROBABILITY_SUFFIX, \ MILP_BUILDING_MESSAGE, MILP_XOR_DIFFERENTIAL_OBJECTIVE -from claasp.cipher_modules.models.milp.utils.utils import _string_to_hex, _get_variables_values_as_string -from claasp.cipher_modules.models.utils import integer_to_bit_list, set_component_solution +from claasp.cipher_modules.models.milp.utils.utils import _string_to_hex, _get_variables_values_as_string, _filter_fixed_variables +from claasp.cipher_modules.models.utils import integer_to_bit_list, set_component_solution, \ + get_single_key_scenario_format_for_fixed_values from claasp.name_mappings import (CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, - WORD_OPERATION, LINEAR_LAYER, SBOX, MIX_COLUMN) + WORD_OPERATION, LINEAR_LAYER, SBOX, MIX_COLUMN, INPUT_KEY) class MilpXorDifferentialModel(MilpModel): @@ -99,6 +100,8 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): """ variables = [] self._variables_list = [] + if fixed_variables == []: + fixed_variables = get_single_key_scenario_format_for_fixed_values(self._cipher) constraints = self.fix_variables_value_constraints(fixed_variables) component_types = [CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, MIX_COLUMN, WORD_OPERATION] operation_types = ['AND', 'MODADD', 'MODSUB', 'NOT', 'OR', 'ROTATE', 'SHIFT', 'XOR'] @@ -124,6 +127,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return all the XOR differential trails with weight equal to ``fixed_weight`` as a list in standard format. + By default, the search is set in the single-key setting. .. SEEALSO:: @@ -142,15 +146,28 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) EXAMPLES:: - sage: from claasp.cipher_modules.models.utils import get_single_key_scenario_format_for_fixed_values + + # single-key setting sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: milp = MilpXorDifferentialModel(speck) + sage: trails = milp.find_all_xor_differential_trails_with_fixed_weight(9) # long + ... + sage: len(trails) + 2 + + # related-key setting + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: milp = MilpXorDifferentialModel(speck) - sage: trails = milp.find_all_xor_differential_trails_with_fixed_weight(1, get_single_key_scenario_format_for_fixed_values(speck)) # long + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trails = milp.find_all_xor_differential_trails_with_fixed_weight(2, fixed_values=[key]) # long ... - sage: len(trails) # long - 6 + sage: len(trails) + 2 """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -167,8 +184,10 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed end = time.time() building_time = end - start + if fixed_values == []: + fixed_values = get_single_key_scenario_format_for_fixed_values(self._cipher) if self.is_single_key(fixed_values): - inputs_ids = [i for i in self._cipher.inputs if "key" not in i] + inputs_ids = [i for i in self._cipher.inputs if INPUT_KEY not in i] else: inputs_ids = self._cipher.inputs @@ -176,33 +195,16 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed looking_for_other_solutions = 1 while looking_for_other_solutions: try: + f = open(os.devnull, 'w') + sys.stdout = f solution = self.solve(MILP_XOR_DIFFERENTIAL, solver_name, external_solver_name) + sys.stdout = sys.__stdout__ solution['building_time'] = building_time solution['test_name'] = "find_all_xor_differential_trails_with_fixed_weight" self._number_of_trails_found += 1 verbose_print(f"trails found : {self._number_of_trails_found}") list_trails.append(solution) - fixed_variables = [] - for index, input in enumerate(inputs_ids): - fixed_variable = {} - fixed_variable["component_id"] = input - input_bit_size = self._cipher.inputs_bit_size[index] - fixed_variable["bit_positions"] = list(range(input_bit_size)) - fixed_variable["constraint_type"] = "not_equal" - fixed_variable["bit_values"] = integer_to_bit_list( - BitArray(solution["components_values"][input]["value"]).int, input_bit_size, 'big') - fixed_variables.append(fixed_variable) - - for cipher_round in self._cipher.rounds_as_list: - for component in cipher_round.components: - fixed_variable = {} - fixed_variable["component_id"] = component.id - output_bit_size = component.output_bit_size - fixed_variable["bit_positions"] = list(range(output_bit_size)) - fixed_variable["constraint_type"] = "not_equal" - fixed_variable["bit_values"] = integer_to_bit_list( - BitArray(solution["components_values"][component.id]["value"]).int, output_bit_size, 'big') - fixed_variables.append(fixed_variable) + fixed_variables = self._get_fixed_variables_from_solution(fixed_values, inputs_ids, solution) fix_var_constraints = self.exclude_variables_value_constraints(fixed_variables) number_new_constraints += len(fix_var_constraints) @@ -210,6 +212,8 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed mip.add_constraint(constraint) except Exception: looking_for_other_solutions = 0 + finally: + sys.stdout = sys.__stdout__ number_constraints = mip.number_of_constraints() mip.remove_constraints(range(number_constraints - number_new_constraints, number_constraints)) @@ -301,12 +305,11 @@ def is_single_key(self, fixed_values): be fixed EXAMPLES:: - sage: from claasp.cipher_modules.models.utils import get_single_key_scenario_format_for_fixed_values - sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) sage: milp = MilpXorDifferentialModel(speck) - sage: milp.is_single_key(get_single_key_scenario_format_for_fixed_values(speck)) + sage: milp.is_single_key(speck) True """ cipher_inputs = self._cipher.inputs @@ -324,7 +327,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return all XOR differential trails with weight greater than ``min_weight`` and lower/equal to ``max_weight``. - + By default, the search is set in the single-key setting. The value returned is a list of solutions in standard format. .. SEEALSO:: @@ -347,15 +350,27 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w EXAMPLES:: - sage: from claasp.cipher_modules.models.utils import get_single_key_scenario_format_for_fixed_values + # single-key setting sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) + sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: milp = MilpXorDifferentialModel(speck) - sage: trails = milp.find_all_xor_differential_trails_with_weight_at_most(0, 1, get_single_key_scenario_format_for_fixed_values(speck)) # long + sage: trails = milp.find_all_xor_differential_trails_with_weight_at_most(9, 10) # long ... - sage: len(trails) # long - 7 + sage: len(trails) + 28 + + # related-key setting + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: milp = MilpXorDifferentialModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trails = milp.find_all_xor_differential_trails_with_weight_at_most(2, 3, fixed_values=[key]) # long + ... + sage: len(trails) + 9 """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -365,9 +380,12 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) end = time.time() building_time = end - start + + if fixed_values == []: + fixed_values = get_single_key_scenario_format_for_fixed_values(self._cipher) inputs_ids = self._cipher.inputs if self.is_single_key(fixed_values): - inputs_ids = [i for i in self._cipher.inputs if "key" not in i] + inputs_ids = [i for i in self._cipher.inputs if INPUT_KEY not in i] list_trails = [] for weight in range(min_weight, max_weight + 1): @@ -378,14 +396,16 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w number_new_constraints = len(weight_constraints) while looking_for_other_solutions: try: + f = open(os.devnull, 'w') + sys.stdout = f solution = self.solve(MILP_XOR_DIFFERENTIAL, solver_name, external_solver_name) + sys.stdout = sys.__stdout__ solution['building_time'] = building_time solution['test_name'] = "find_all_xor_differential_trails_with_weight_at_most" self._number_of_trails_found += 1 verbose_print(f"trails found : {self._number_of_trails_found}") list_trails.append(solution) - fixed_variables = self.get_fixed_variables_for_all_xor_differential_trails_with_weight_at_most( - fixed_values, inputs_ids, solution) + fixed_variables = self._get_fixed_variables_from_solution(fixed_values, inputs_ids, solution) fix_var_constraints = self.exclude_variables_value_constraints(fixed_variables) for constraint in fix_var_constraints: @@ -393,6 +413,8 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w number_new_constraints += len(fix_var_constraints) except Exception: looking_for_other_solutions = 0 + finally: + sys.stdout = sys.__stdout__ number_constraints = mip.number_of_constraints() mip.remove_constraints(range(number_constraints - number_new_constraints, number_constraints)) self._number_of_trails_found = 0 @@ -403,6 +425,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name external_solver_name=False): """ Return a XOR differential trail with the lowest weight in standard format, i.e. the solver solution. + By default, the search is set in the single-key setting. .. SEEALSO:: @@ -416,12 +439,24 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name EXAMPLES:: - sage: from claasp.cipher_modules.models.utils import get_single_key_scenario_format_for_fixed_values + # single-key setting + from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: milp = MilpXorDifferentialModel(speck) + sage: trail = milp.find_lowest_weight_xor_differential_trail() + ... + sage: trail["total_weight"] + 9.0 + + # related-key setting + sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel - sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: milp = MilpXorDifferentialModel(speck) - sage: trail = milp.find_lowest_weight_xor_differential_trail(get_single_key_scenario_format_for_fixed_values(speck)) + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trail = milp.find_lowest_weight_xor_differential_trail(fixed_values=[key]) ... sage: trail["total_weight"] 1.0 @@ -445,6 +480,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name def find_one_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return a XOR differential trail, not necessarily the one with the lowest weight. + By default, the search is set in the single-key setting. INPUT: @@ -459,12 +495,21 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DE EXAMPLES:: - sage: from claasp.cipher_modules.models.utils import get_single_key_scenario_format_for_fixed_values + # single-key setting + from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: milp = MilpXorDifferentialModel(speck) + sage: trail = milp.find_one_xor_differential_trail() # random + + # related-key setting + sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel - sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: milp = MilpXorDifferentialModel(speck) - sage: trail = milp.find_one_xor_differential_trail(get_single_key_scenario_format_for_fixed_values(speck)) # random + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trail = milp.find_one_xor_differential_trail(fixed_values=[key]) # random """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -484,6 +529,7 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return one XOR differential trail with weight equal to ``fixed_weight`` as a list in standard format. + By default, the search is set in the single-key setting. INPUT: @@ -499,13 +545,25 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ EXAMPLES:: - sage: from claasp.cipher_modules.models.utils import get_single_key_scenario_format_for_fixed_values + # single-key setting + from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel + sage: speck = SpeckBlockCipher(number_of_rounds=3) + sage: milp = MilpXorDifferentialModel(speck) + sage: trail = milp.find_one_xor_differential_trail_with_fixed_weight(3) # random + sage: trail['total_weight'] + 3.0 + + # related-key setting + sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel - sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: milp = MilpXorDifferentialModel(speck) - sage: trail = milp.find_one_xor_differential_trail_with_fixed_weight(5, get_single_key_scenario_format_for_fixed_values(speck)) - ... + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trail = milp.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[key]) # random + sage: trail['total_weight'] + 3.0 """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -524,31 +582,29 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ return solution - def get_fixed_variables_for_all_xor_differential_trails_with_weight_at_most(self, fixed_values, inputs_ids, - solution): + def _get_fixed_variables_from_solution(self, fixed_values, inputs_ids, solution): fixed_variables = [] - for index, input in enumerate(inputs_ids): - input_bit_size = self._cipher.inputs_bit_size[index] + for input in inputs_ids: + input_bit_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(input)] fixed_variable = {"component_id": input, "bit_positions": list(range(input_bit_size)), "constraint_type": "not_equal", "bit_values": (integer_to_bit_list( BitArray(solution["components_values"][input]["value"]).int, input_bit_size, 'big'))} + _filter_fixed_variables(fixed_values, fixed_variable, input) + fixed_variables.append(fixed_variable) - fixed_variables += [fixed_variable for dictio in fixed_values - if dictio["component_id"] == input and - dictio["bit_values"] != fixed_variable["bit_values"]] - - for component in self._cipher.get_all_components(): - output_bit_size = component.output_bit_size - fixed_variable = {"component_id": component.id, - "bit_positions": list(range(output_bit_size)), - "constraint_type": "not_equal", - "bit_values": integer_to_bit_list( - BitArray(solution["components_values"][component.id]["value"]).int, - output_bit_size, 'big')} - fixed_variables.append(fixed_variable) + for component in self._cipher.get_all_components(): + output_bit_size = component.output_bit_size + fixed_variable = {"component_id": component.id, + "bit_positions": list(range(output_bit_size)), + "constraint_type": "not_equal", + "bit_values": integer_to_bit_list( + BitArray(solution["components_values"][component.id]["value"]).int, + output_bit_size, 'big')} + _filter_fixed_variables(fixed_values, fixed_variable, component.id) + fixed_variables.append(fixed_variable) return fixed_variables diff --git a/claasp/cipher_modules/models/milp/milp_models/milp_xor_linear_model.py b/claasp/cipher_modules/models/milp/milp_models/milp_xor_linear_model.py index c7ce46cd..f2b96186 100644 --- a/claasp/cipher_modules/models/milp/milp_models/milp_xor_linear_model.py +++ b/claasp/cipher_modules/models/milp/milp_models/milp_xor_linear_model.py @@ -1,4 +1,3 @@ - # **************************************************************************** # Copyright 2023 Technology Innovation Institute # @@ -18,6 +17,8 @@ import time +import os +import sys from bitstring import BitArray @@ -28,11 +29,12 @@ from claasp.cipher_modules.models.milp.milp_model import MilpModel, verbose_print from claasp.cipher_modules.models.milp.utils.milp_name_mappings import MILP_XOR_LINEAR, MILP_PROBABILITY_SUFFIX, \ MILP_BUILDING_MESSAGE, MILP_XOR_LINEAR_OBJECTIVE -from claasp.cipher_modules.models.milp.utils.utils import _get_variables_values_as_string, _string_to_hex +from claasp.cipher_modules.models.milp.utils.utils import _get_variables_values_as_string, _string_to_hex, \ + _filter_fixed_variables from claasp.cipher_modules.models.utils import get_bit_bindings, set_fixed_variables, integer_to_bit_list, \ - set_component_solution + set_component_solution, get_single_key_scenario_format_for_fixed_values from claasp.name_mappings import (INTERMEDIATE_OUTPUT, CONSTANT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, MIX_COLUMN, - WORD_OPERATION) + WORD_OPERATION, INPUT_KEY) class MilpXorLinearModel(MilpModel): @@ -156,6 +158,11 @@ def build_xor_linear_trail_model(self, weight=-1, fixed_variables=[]): """ self._variables_list = [] variables = [] + if INPUT_KEY not in [variable["component_id"] for variable in fixed_variables]: + self._cipher = self._cipher.remove_key_schedule() + self.bit_bindings, self.bit_bindings_for_intermediate_output = get_bit_bindings(self.cipher, '_'.join) + if fixed_variables == []: + fixed_variables = get_single_key_scenario_format_for_fixed_values(self._cipher) constraints = self.fix_variables_value_xor_linear_constraints(fixed_variables) self._model_constraints = constraints @@ -245,9 +252,10 @@ def exclude_variables_value_xor_linear_constraints(self, fixed_variables=[]): return constraints - def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=None, solver_name=SOLVER_DEFAULT, external_solver_name=None): + def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return all the XOR linear trails with weight equal to ``fixed_weight`` as a solutions list in standard format. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. .. SEEALSO:: @@ -262,34 +270,39 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value INPUT: - ``fixed_weight`` -- **integer**; the weight found using :py:meth:`~find_lowest_weight_xor_linear_trail` - - ``fixed_values`` -- **list** (default: `None`); each dictionary contains variables values whose output need + - ``fixed_values`` -- **list** (default: `[]`); each dictionary contains variables values whose output need to be fixed - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) EXAMPLES:: sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel - sage: from claasp.cipher_modules.models.utils import integer_to_bit_list, set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=3) - sage: milp = MilpXorLinearModel(speck.remove_key_schedule()) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', constraint_type='not equal', - ....: bit_positions=range(8), bit_values=integer_to_bit_list(0x0, 8, 'big')) - sage: trails = milp.find_all_xor_linear_trails_with_fixed_weight(1, fixed_values = [plaintext]) # long + sage: milp = MilpXorLinearModel(speck) + sage: trails = milp.find_all_xor_linear_trails_with_fixed_weight(1) ... - sage: len(trails) # long + sage: len(trails) 12 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: milp = MilpXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trails = milp.find_all_xor_linear_trails_with_fixed_weight(2, fixed_values=[key]) # long + ... + sage: len(trails) + 8 """ start = time.time() self.init_model_in_sage_milp_class(solver_name) verbose_print(f"Solver used : {solver_name} (Choose Gurobi for Better performance)") mip = self._model mip.set_objective(None) - if not fixed_values: - input_size = self._cipher.inputs_bit_size[self._cipher.inputs.index("plaintext")] - list_of_0s = [0] * input_size - fixed_values.append(set_fixed_variables("plaintext", "not_equal", list(range(input_size)), list_of_0s)) + self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) _, constraints = self.weight_xor_linear_constraints(fixed_weight) for constraint in constraints: @@ -303,32 +316,16 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value looking_for_other_solutions = 1 while looking_for_other_solutions: try: + f = open(os.devnull, 'w') + sys.stdout = f solution = self.solve(MILP_XOR_LINEAR, solver_name, external_solver_name) + sys.stdout = sys.__stdout__ solution['building_time'] = building_time solution['test_name'] = "find_all_xor_linear_trails_with_fixed_weight" self._number_of_trails_found += 1 verbose_print(f"trails found : {self._number_of_trails_found}") list_trails.append(solution) - fixed_variables = [] - for index, input in enumerate(inputs_ids): - fixed_variable = {"component_id": input} - input_bit_size = self._cipher.inputs_bit_size[index] - fixed_variable["bit_positions"] = list(range(input_bit_size)) - fixed_variable["constraint_type"] = "not_equal" - fixed_variable["bit_values"] = integer_to_bit_list( - BitArray(solution["components_values"][input]["value"]).int, input_bit_size, 'big') - fixed_variables.append(fixed_variable) - - for cipher_round in self._cipher.rounds_as_list: - for component in cipher_round.components: - fixed_variable = {"component_id": component.id} - output_bit_size = component.output_bit_size - fixed_variable["bit_positions"] = list(range(output_bit_size)) - fixed_variable["constraint_type"] = "not_equal" - fixed_variable["bit_values"] = integer_to_bit_list( - BitArray(solution["components_values"][component.id + "_o"]["value"]).int, - output_bit_size, 'big') - fixed_variables.append(fixed_variable) + fixed_variables = self._get_fixed_variables_from_solution(fixed_values, inputs_ids, solution) fix_var_constraints = self.exclude_variables_value_xor_linear_constraints(fixed_variables) @@ -337,6 +334,8 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value mip.add_constraint(constraint) except Exception: looking_for_other_solutions = 0 + finally: + sys.stdout = sys.__stdout__ number_constraints = mip.number_of_constraints() mip.remove_constraints(range(number_constraints - number_new_constraints, number_constraints)) @@ -349,6 +348,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return all XOR linear trails with weight greater than ``min_weight`` and lower than or equal to ``max_weight``. + By default, the search removes the key schedule, if any. The value returned is a list of solutions in standard format. @@ -373,14 +373,24 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import integer_to_bit_list, set_fixed_variables sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=3) - sage: milp = MilpXorLinearModel(speck.remove_key_schedule()) - sage: plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not equal', bit_positions=range(8), bit_values=integer_to_bit_list(0x0, 8, 'big')) - sage: trails = milp.find_all_xor_linear_trails_with_weight_at_most(0,1,[plaintext]) # long + sage: milp = MilpXorLinearModel(speck) + sage: trails = milp.find_all_xor_linear_trails_with_weight_at_most(0,1) ... - sage: len(trails) # long + sage: len(trails) 13 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: milp = MilpXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trails = milp.find_all_xor_linear_trails_with_weight_at_most(0, 3, fixed_values=[key]) # long + ... + sage: len(trails) + 73 """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -401,14 +411,16 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, number_new_constraints = len(weight_constraints) while looking_for_other_solutions: try: + f = open(os.devnull, 'w') + sys.stdout = f solution = self.solve(MILP_XOR_LINEAR, solver_name, external_solver_name) + sys.stdout = sys.__stdout__ solution['building_time'] = building_time solution['test_name'] = "find_all_xor_linear_trails_with_weight_at_most" self._number_of_trails_found += 1 verbose_print(f"trails found : {self._number_of_trails_found}") list_trails.append(solution) - fixed_variables = self.get_fixed_variables_for_all_xor_linear_trails_with_weight_at_most( - fixed_values, inputs_ids, solution) + fixed_variables = self._get_fixed_variables_from_solution(fixed_values, inputs_ids, solution) fix_var_constraints = self.exclude_variables_value_xor_linear_constraints(fixed_variables) for constraint in fix_var_constraints: @@ -416,6 +428,8 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, number_new_constraints += len(fix_var_constraints) except Exception: looking_for_other_solutions = 0 + finally: + sys.stdout = sys.__stdout__ number_constraints = mip.number_of_constraints() mip.remove_constraints(range(number_constraints - number_new_constraints, number_constraints)) self._number_of_trails_found = 0 @@ -425,6 +439,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return a XOR linear trail with the lowest weight in standard format, i.e. the solver solution. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. .. SEEALSO:: @@ -447,7 +462,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVE sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: from claasp.cipher_modules.models.utils import integer_to_bit_list, set_fixed_variables sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=9) - sage: milp = MilpXorLinearModel(speck.remove_key_schedule()) + sage: milp = MilpXorLinearModel(speck) sage: plaintext = set_fixed_variables(component_id='plaintext', constraint_type='equal', bit_positions=range(32), bit_values=integer_to_bit_list(0x03805224, 32, 'big')) sage: trail = milp.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) # doctest: +SKIP ... @@ -459,12 +474,23 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVE sage: from claasp.ciphers.block_ciphers.simon_block_cipher import SimonBlockCipher sage: from claasp.cipher_modules.models.utils import integer_to_bit_list, set_fixed_variables sage: simon = SimonBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=13) - sage: milp = MilpXorLinearModel(simon.remove_key_schedule()) + sage: milp = MilpXorLinearModel(simon) sage: plaintext = set_fixed_variables(component_id='plaintext', constraint_type='equal', bit_positions=range(32), bit_values=integer_to_bit_list(0x00200000, 32, 'big')) sage: trail = milp.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) # doctest: +SKIP ... sage: trail["total_weight"] # doctest: +SKIP 18.0 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=16, key_bit_size=32, number_of_rounds=4) + sage: milp = MilpXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) + sage: trail = milp.find_lowest_weight_xor_linear_trail(fixed_values=[key]) + sage: trail["total_weight"] + 3.0 """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -484,6 +510,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVE def find_one_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return a XOR linear trail, not necessarily the one with the lowest weight. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. INPUT: @@ -500,11 +527,18 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import integer_to_bit_list, set_fixed_variables sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - sage: milp = MilpXorLinearModel(speck.remove_key_schedule()) - sage: plaintext = set_fixed_variables(component_id='plaintext', constraint_type='equal', bit_positions=range(32), bit_values=integer_to_bit_list(0x03805224, 32, 'big')) - sage: trail = milp.find_one_xor_linear_trail(fixed_values=[plaintext]) # random + sage: milp = MilpXorLinearModel(speck) + sage: trail = milp.find_one_xor_linear_trail() # random + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: milp = MilpXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) + sage: trail = milp.find_one_xor_linear_trail(fixed_values=[key]) # random """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -524,6 +558,7 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values solver_name=SOLVER_DEFAULT, external_solver_name=None): """ Return one XOR linear trail with weight equal to ``fixed_weight`` as a list in standard format. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. INPUT: @@ -542,9 +577,21 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - sage: milp = MilpXorLinearModel(speck.remove_key_schedule()) - sage: trail = milp.find_one_xor_linear_trail_with_fixed_weight(6) + sage: milp = MilpXorLinearModel(speck) + sage: trail = milp.find_one_xor_linear_trail_with_fixed_weight(6) # random ... + sage: trail['total_weight'] + 6.0 + + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: milp = MilpXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trail = milp.find_one_xor_linear_trail_with_fixed_weight(3, fixed_values=[key]) # random + sage: trail["total_weight"] + 3.0 """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -627,20 +674,18 @@ def fix_variables_value_xor_linear_constraints(self, fixed_variables=[]): return constraints - def get_fixed_variables_for_all_xor_linear_trails_with_weight_at_most(self, fixed_values, inputs_ids, solution): + def _get_fixed_variables_from_solution(self, fixed_values, inputs_ids, solution): fixed_variables = [] - for index, input in enumerate(inputs_ids): - input_bit_size = self._cipher.inputs_bit_size[index] + for input in inputs_ids: + input_bit_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(input)] fixed_variable = {"component_id": input, "bit_positions": list(range(input_bit_size)), "constraint_type": "not_equal", "bit_values": integer_to_bit_list( BitArray(solution["components_values"][input]["value"]).int, input_bit_size, 'big')} - - fixed_variables += [fixed_variable for dictio in fixed_values - if dictio["component_id"] == input and - dictio["bit_values"] != fixed_variable["bit_values"]] + _filter_fixed_variables(fixed_values, fixed_variable, input) + fixed_variables.append(fixed_variable) for component in self._cipher.get_all_components(): output_bit_size = component.output_bit_size fixed_variable = {"component_id": component.id, @@ -649,6 +694,7 @@ def get_fixed_variables_for_all_xor_linear_trails_with_weight_at_most(self, fixe "bit_values": integer_to_bit_list( BitArray(solution["components_values"][component.id + "_o"]["value"]).int, output_bit_size, 'big')} + _filter_fixed_variables(fixed_values, fixed_variable, component.id) fixed_variables.append(fixed_variable) return fixed_variables diff --git a/claasp/cipher_modules/models/milp/utils/utils.py b/claasp/cipher_modules/models/milp/utils/utils.py index 5f435f51..3101c25e 100644 --- a/claasp/cipher_modules/models/milp/utils/utils.py +++ b/claasp/cipher_modules/models/milp/utils/utils.py @@ -710,4 +710,13 @@ def _string_to_hex( string): value = "0b" + value.bin except Exception: value = string - return value \ No newline at end of file + return value + +def _filter_fixed_variables(fixed_values, fixed_variable, id): + fixed_values_to_keep = [variable for variable in fixed_values if variable["constraint_type"] == "equal"] + if id in [value["component_id"] for value in fixed_values_to_keep]: + input_index = [value["component_id"] for value in fixed_values_to_keep].index(id) + for bit in fixed_values_to_keep[input_index]["bit_positions"]: + bit_index = fixed_variable["bit_positions"].index(bit) + del fixed_variable["bit_values"][bit_index] + del fixed_variable["bit_positions"][bit_index] \ No newline at end of file diff --git a/claasp/cipher_modules/models/sat/sat_model.py b/claasp/cipher_modules/models/sat/sat_model.py index c32fbd2e..e642ac16 100644 --- a/claasp/cipher_modules/models/sat/sat_model.py +++ b/claasp/cipher_modules/models/sat/sat_model.py @@ -335,7 +335,7 @@ def _solve_with_external_sat_solver(self, model_type, solver_name, options, host component2fields, total_weight = {}, None if total_weight is not None: total_weight = float(total_weight) - solution = convert_solver_solution_to_dictionary(self.cipher_id, model_type, solver_name, sat_time, + solution = convert_solver_solution_to_dictionary(self._cipher, model_type, solver_name, sat_time, sat_memory, component2fields, total_weight) solution['status'] = status @@ -361,7 +361,7 @@ def _solve_with_sage_sat_solver(self, model_type, solver_name): status = 'UNSATISFIABLE' if total_weight is not None: total_weight = float(total_weight) - solution = convert_solver_solution_to_dictionary(self.cipher_id, model_type, solver_name, sat_time, + solution = convert_solver_solution_to_dictionary(self._cipher, model_type, solver_name, sat_time, sat_memory, component2fields, total_weight) solution['status'] = status diff --git a/claasp/cipher_modules/models/sat/sat_models/sat_xor_differential_model.py b/claasp/cipher_modules/models/sat/sat_models/sat_xor_differential_model.py index 20b0a923..0934784e 100644 --- a/claasp/cipher_modules/models/sat/sat_models/sat_xor_differential_model.py +++ b/claasp/cipher_modules/models/sat/sat_models/sat_xor_differential_model.py @@ -21,7 +21,7 @@ from claasp.cipher_modules.models.sat.sat_model import SatModel from claasp.cipher_modules.models.sat.sat_models.sat_cipher_model import SatCipherModel -from claasp.cipher_modules.models.utils import set_component_solution +from claasp.cipher_modules.models.utils import set_component_solution, get_single_key_scenario_format_for_fixed_values from claasp.name_mappings import (CIPHER_OUTPUT, CONSTANT, INTERMEDIATE_OUTPUT, LINEAR_LAYER, MIX_COLUMN, SBOX, WORD_OPERATION, XOR_DIFFERENTIAL) @@ -57,6 +57,8 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): """ variables = [] self._variables_list = [] + if fixed_variables == []: + fixed_variables = get_single_key_scenario_format_for_fixed_values(self._cipher) constraints = self.fix_variables_value_constraints(fixed_variables) self._model_constraints = constraints component_types = (CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, MIX_COLUMN, WORD_OPERATION) @@ -115,6 +117,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed solver_name='cryptominisat'): """ Return a list of solutions containing all the XOR differential trails having the ``fixed_weight`` weight. + By default, the search is set in the single-key setting. INPUT: @@ -128,22 +131,27 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed EXAMPLES:: + # single-key setting sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: sat = SatXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) + sage: trails = sat.find_all_xor_differential_trails_with_fixed_weight(9) + sage: len(trails) == 2 + True + + # related-key setting + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: sat = SatXorDifferentialModel(speck) sage: key = set_fixed_variables( ....: component_id='key', - ....: constraint_type='equal', + ....: constraint_type='not_equal', ....: bit_positions=range(64), - ....: bit_values=integer_to_bit_list(0, 64, 'big')) - sage: trails = sat.find_all_xor_differential_trails_with_fixed_weight(9, fixed_values=[plaintext, key]) + ....: bit_values=[0]*64) + sage: trails = sat.find_all_xor_differential_trails_with_fixed_weight(2, fixed_values=[key]) sage: len(trails) == 2 True """ @@ -180,6 +188,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w solver_name='cryptominisat'): """ Return a list of solutions. + By default, the search is set in the single-key setting. The list contain all the XOR differential trails having the weight lying in the interval ``[min_weight, max_weight]``. @@ -197,23 +206,28 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w EXAMPLES:: + # single-key setting sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: sat = SatXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) + sage: trails = sat.find_all_xor_differential_trails_with_weight_at_most(9, 10) + sage: len(trails) == 28 + True + + # related-key setting + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: sat = SatXorDifferentialModel(speck) sage: key = set_fixed_variables( ....: component_id='key', - ....: constraint_type='equal', + ....: constraint_type='not_equal', ....: bit_positions=range(64), - ....: bit_values=integer_to_bit_list(0, 64, 'big')) - sage: trails = sat.find_all_xor_differential_trails_with_weight_at_most(9, 10, fixed_values=[plaintext, key]) - sage: len(trails) == 28 + ....: bit_values=[0]*64) + sage: trails = sat.find_all_xor_differential_trails_with_weight_at_most(2, 3, fixed_values=[key]) + sage: len(trails) == 9 True """ solutions_list = [] @@ -231,6 +245,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name='cryptominisat'): """ Return the solution representing a trail with the lowest weight. + By default, the search is set in the single-key setting. .. NOTE:: @@ -248,24 +263,29 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name EXAMPLES:: + # single-key setting + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: sat = SatXorDifferentialModel(speck) + sage: trail = sat.find_lowest_weight_xor_differential_trail() + sage: trail['total_weight'] + 9.0 + + # related-key setting sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: sat = SatXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=(0,)*32) sage: key = set_fixed_variables( ....: component_id='key', - ....: constraint_type='equal', + ....: constraint_type='not_equal', ....: bit_positions=range(64), ....: bit_values=(0,)*64) - sage: trail = sat.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) + sage: trail = sat.find_lowest_weight_xor_differential_trail(fixed_values=[key]) sage: trail['total_weight'] - 9.0 + 1.0 """ current_weight = 0 start_building_time = time.time() @@ -293,7 +313,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name def find_one_xor_differential_trail(self, fixed_values=[], solver_name='cryptominisat'): """ Return the solution representing a XOR differential trail. - + By default, the search is set in the single-key setting. The solution probability is almost always lower than the one of a random guess of the longest input. INPUT: @@ -307,25 +327,26 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='cryptomi EXAMPLES:: + # single-key setting + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: sat = SatXorDifferentialModel(speck) + sage: sat.find_one_xor_differential_trail() # random + + # related-key setting sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list + sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: sat = SatXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', + sage: key = set_fixed_variables( + ....: component_id='key', ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: sat.find_one_xor_differential_trail(fixed_values=[plaintext]) # random - {'cipher_id': 'speck_p32_k64_o32_r5', - 'model_type': 'xor_differential', - 'solver_name': 'cryptominisat', - 'solving_time_seconds': 0.0, - 'memory_megabytes': 7.09, - ... - 'status': 'SATISFIABLE', - 'building_time_seconds': 0.004874706268310547} + ....: bit_positions=range(64), + ....: bit_values=[0]*64) + sage: sat.find_one_xor_differential_trail(fixed_values=[key]) """ start_building_time = time.time() self.build_xor_differential_trail_model(fixed_variables=fixed_values) @@ -340,7 +361,7 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ solver_name='cryptominisat'): """ Return the solution representing a XOR differential trail whose probability is ``2 ** fixed_weight``. - + By default, the search is set in the single-key setting. INPUT: - ``fixed_weight`` -- **integer**; the weight to be fixed @@ -353,23 +374,28 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ EXAMPLES:: + # single-key setting sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel - sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: sat = SatXorDifferentialModel(speck, window_size_by_round=[0, 0, 0]) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=(0,)*32) + sage: trail = sat.find_one_xor_differential_trail_with_fixed_weight(3) + sage: trail['total_weight'] + 3.0 + + # related-key setting + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=3) + sage: sat = SatXorDifferentialModel(speck) sage: key = set_fixed_variables( ....: component_id='key', - ....: constraint_type='equal', + ....: constraint_type='not_equal', ....: bit_positions=range(64), - ....: bit_values=(0,)*64) - sage: result = sat.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[plaintext, key]) - sage: result['total_weight'] + ....: bit_values=[0]*64) + sage: trail = sat.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[key]) + sage: trail['total_weight'] 3.0 """ start_building_time = time.time() diff --git a/claasp/cipher_modules/models/sat/sat_models/sat_xor_linear_model.py b/claasp/cipher_modules/models/sat/sat_models/sat_xor_linear_model.py index d723efc0..db80122d 100644 --- a/claasp/cipher_modules/models/sat/sat_models/sat_xor_linear_model.py +++ b/claasp/cipher_modules/models/sat/sat_models/sat_xor_linear_model.py @@ -21,9 +21,11 @@ from claasp.cipher_modules.models.sat.utils import constants, utils from claasp.cipher_modules.models.sat.sat_model import SatModel -from claasp.cipher_modules.models.utils import get_bit_bindings, set_component_solution +from claasp.cipher_modules.models.sat.utils.constants import OUTPUT_BIT_ID_SUFFIX, INPUT_BIT_ID_SUFFIX +from claasp.cipher_modules.models.utils import get_bit_bindings, set_component_solution, \ + get_single_key_scenario_format_for_fixed_values from claasp.name_mappings import (CIPHER_OUTPUT, CONSTANT, INTERMEDIATE_OUTPUT, LINEAR_LAYER, - MIX_COLUMN, SBOX, WORD_OPERATION, XOR_LINEAR) + MIX_COLUMN, SBOX, WORD_OPERATION, XOR_LINEAR, INPUT_KEY) class SatXorLinearModel(SatModel): @@ -84,6 +86,11 @@ def build_xor_linear_trail_model(self, weight=-1, fixed_variables=[]): """ self._variables_list = [] variables = [] + if INPUT_KEY not in [variable["component_id"] for variable in fixed_variables]: + self._cipher = self._cipher.remove_key_schedule() + self.bit_bindings, self.bit_bindings_for_intermediate_output = get_bit_bindings(self._cipher, '_'.join) + if fixed_variables == []: + fixed_variables = get_single_key_scenario_format_for_fixed_values(self._cipher) constraints = self.fix_variables_value_xor_linear_constraints(fixed_variables) self._model_constraints = constraints component_types = (CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, MIX_COLUMN, WORD_OPERATION) @@ -110,6 +117,7 @@ def build_xor_linear_trail_model(self, weight=-1, fixed_variables=[]): def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='cryptominisat'): """ Return a list of solutions containing all the XOR linear trails having weight equal to ``fixed_weight``. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail INPUT: @@ -126,17 +134,22 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=3) - sage: sat = SatXorLinearModel(speck.remove_key_schedule()) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: trails = sat.find_all_xor_linear_trails_with_fixed_weight(1, fixed_values=[plaintext]) + sage: sat = SatXorLinearModel(speck) + sage: trails = sat.find_all_xor_linear_trails_with_fixed_weight(1) sage: len(trails) == 4 True + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: sat = SatXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trails = sat.find_all_xor_linear_trails_with_fixed_weight(2, fixed_values=[key]) # long + sage: len(trails) == 8 + True """ start_building_time = time.time() self.build_xor_linear_trail_model(weight=fixed_weight, fixed_variables=fixed_values) @@ -154,12 +167,14 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value value_to_avoid = int(value_as_hex_string, base=16) bit_len = len(value_as_hex_string) * 4 minus = ['-' * (value_to_avoid >> i & 1) for i in reversed(range(bit_len))] - if component.endswith('_i') or component.endswith('_o'): + if CONSTANT in component and component.endswith(INPUT_BIT_ID_SUFFIX): + continue + elif component.endswith(INPUT_BIT_ID_SUFFIX) or component.endswith(OUTPUT_BIT_ID_SUFFIX): component_id = component[:-2] suffix = component[-2:] else: component_id = component - suffix = '_o' + suffix = OUTPUT_BIT_ID_SUFFIX literals.extend([f'{minus[i]}{component_id}_{i}{suffix}' for i in range(bit_len)]) self._model_constraints.append(' '.join(literals)) solution = self.solve(XOR_LINEAR, solver_name=solver_name) @@ -171,6 +186,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, solver_name='cryptominisat'): """ Return a list of solutions. + By default, the search removes the key schedule, if any. The list contains all the XOR linear trails having the weight lying in the interval ``[min_weight, max_weight]``. @@ -190,17 +206,22 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=3) - sage: sat = SatXorLinearModel(speck.remove_key_schedule()) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: trails = sat.find_all_xor_linear_trails_with_weight_at_most(0, 2, fixed_values=[plaintext]) + sage: sat = SatXorLinearModel(speck) + sage: trails = sat.find_all_xor_linear_trails_with_weight_at_most(0, 2) # long sage: len(trails) == 187 True + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: sat = SatXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trails = sat.find_all_xor_linear_trails_with_weight_at_most(0, 3, fixed_values=[key]) # long + sage: len(trails) == 73 + True """ solutions_list = [] for weight in range(min_weight, max_weight + 1): @@ -216,6 +237,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='cryptominisat'): """ Return the solution representing a XOR LINEAR trail with the lowest possible weight. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. .. NOTE:: @@ -236,17 +258,22 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='cryp sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: sat = SatXorLinearModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: trail = sat.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) + sage: trail = sat.find_lowest_weight_xor_linear_trail() + sage: trail['total_weight'] + 1.0 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=16, key_bit_size=32, number_of_rounds=4) + sage: sat = SatXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) + sage: trail = sat.find_lowest_weight_xor_linear_trail(fixed_values=[key]) sage: trail['total_weight'] - 2.0 + 3.0 """ current_weight = 0 start_building_time = time.time() @@ -274,6 +301,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='cryp def find_one_xor_linear_trail(self, fixed_values=[], solver_name='cryptominisat'): """ Return the solution representing a XOR linear trail. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. The solution probability is almost always lower than the one of a random guess of the longest input. @@ -291,15 +319,9 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='cryptominisat' sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=4) sage: sat = SatXorLinearModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: sat.find_one_xor_linear_trail(fixed_values=[plaintext]) # random + sage: sat.find_one_xor_linear_trail() # random {'cipher_id': 'speck_p32_k64_o32_r4', 'model_type': 'xor_linear', 'solver_name': 'cryptominisat', @@ -308,6 +330,15 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='cryptominisat' ... 'status': 'SATISFIABLE', 'building_time_seconds': 0.010079622268676758} + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=4) + sage: sat = SatXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: sat.find_one_xor_linear_trail(fixed_values=[key]) # random """ start_building_time = time.time() self.build_xor_linear_trail_model(fixed_variables=fixed_values) @@ -322,6 +353,7 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values solver_name='cryptominisat'): """ Return the solution representing a XOR linear trail whose weight is ``fixed_weight``. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. INPUT: @@ -337,18 +369,23 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values EXAMPLES:: sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel - sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: sat = SatXorLinearModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=(0,)*32) - sage: result = sat.find_one_xor_linear_trail_with_fixed_weight(7, fixed_values=[plaintext]) - sage: result['total_weight'] + sage: trail = sat.find_one_xor_linear_trail_with_fixed_weight(7) + sage: trail['total_weight'] 7.0 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.sat.sat_models.sat_xor_linear_model import SatXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: sat = SatXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trail = sat.find_one_xor_linear_trail_with_fixed_weight(3, fixed_values=[key]) + sage: trail['total_weight'] + 3.0 """ start_building_time = time.time() self.build_xor_linear_trail_model(weight=fixed_weight, fixed_variables=fixed_values) diff --git a/claasp/cipher_modules/models/smt/smt_model.py b/claasp/cipher_modules/models/smt/smt_model.py index 17c7831b..4c898b63 100644 --- a/claasp/cipher_modules/models/smt/smt_model.py +++ b/claasp/cipher_modules/models/smt/smt_model.py @@ -434,7 +434,7 @@ def _get_data(data_string, lines): if total_weight is not None: total_weight = float(total_weight) - solution = convert_solver_solution_to_dictionary(self.cipher_id, model_type, solver_name, solve_time, + solution = convert_solver_solution_to_dictionary(self._cipher, model_type, solver_name, solve_time, memory, component2attributes, total_weight) solution['status'] = status diff --git a/claasp/cipher_modules/models/smt/smt_models/smt_xor_differential_model.py b/claasp/cipher_modules/models/smt/smt_models/smt_xor_differential_model.py index ca743a1d..7b65f33c 100644 --- a/claasp/cipher_modules/models/smt/smt_models/smt_xor_differential_model.py +++ b/claasp/cipher_modules/models/smt/smt_models/smt_xor_differential_model.py @@ -21,7 +21,7 @@ from claasp.cipher_modules.models.smt.smt_model import SmtModel from claasp.cipher_modules.models.smt.utils import constants, utils -from claasp.cipher_modules.models.utils import set_component_solution +from claasp.cipher_modules.models.utils import set_component_solution, get_single_key_scenario_format_for_fixed_values from claasp.name_mappings import (CIPHER_OUTPUT, CONSTANT, INTERMEDIATE_OUTPUT, LINEAR_LAYER, MIX_COLUMN, SBOX, WORD_OPERATION, XOR_DIFFERENTIAL) @@ -59,6 +59,8 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): """ variables = [] self._variables_list = [] + if fixed_variables == []: + fixed_variables = get_single_key_scenario_format_for_fixed_values(self._cipher) constraints = self.fix_variables_value_constraints(fixed_variables) component_types = (CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, MIX_COLUMN, WORD_OPERATION) operation_types = ('AND', 'MODADD', 'MODSUB', 'NOT', 'OR', 'ROTATE', 'SHIFT', 'XOR') @@ -88,6 +90,7 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='z3'): """ Return a list of solutions containing all the XOR differential trails having the ``fixed_weight`` weight. + By default, the search is set in the single-key setting. INPUT: @@ -101,22 +104,27 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed EXAMPLES:: + # single-key setting sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: smt = SmtXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) + sage: trails = smt.find_all_xor_differential_trails_with_fixed_weight(9) + sage: len(trails) + 2 + + # related-key setting + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: smt = SmtXorDifferentialModel(speck) sage: key = set_fixed_variables( ....: component_id='key', - ....: constraint_type='equal', + ....: constraint_type='not_equal', ....: bit_positions=range(64), - ....: bit_values=integer_to_bit_list(0, 64, 'big')) - sage: trails = smt.find_all_xor_differential_trails_with_fixed_weight(9, fixed_values=[plaintext, key]) + ....: bit_values=[0]*64) + sage: trails = smt.find_all_xor_differential_trails_with_fixed_weight(2, fixed_values=[key]) sage: len(trails) 2 """ @@ -153,6 +161,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w solver_name='z3'): """ Return a list of solutions. + By default, the search is set in the single-key setting. The list contains all the XOR differential trails having the weight lying in the interval ``[min_weight, max_weight]``. @@ -170,24 +179,29 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w EXAMPLES:: + # single-key setting sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: smt = SmtXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) + sage: trails = smt.find_all_xor_differential_trails_with_weight_at_most(9, 10) + sage: len(trails) + 28 + + # related-key setting + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: smt = SmtXorDifferentialModel(speck) sage: key = set_fixed_variables( ....: component_id='key', - ....: constraint_type='equal', + ....: constraint_type='not_equal', ....: bit_positions=range(64), - ....: bit_values=integer_to_bit_list(0, 64, 'big')) - sage: trails = smt.find_all_xor_differential_trails_with_weight_at_most(9, 10, fixed_values=[plaintext, key]) + ....: bit_values=[0]*64) + sage: trails = smt.find_all_xor_differential_trails_with_weight_at_most(2, 3, fixed_values=[key]) sage: len(trails) - 28 + 9 """ solutions_list = [] for weight in range(min_weight, max_weight + 1): @@ -203,6 +217,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name='z3'): """ Return the solution representing a trail with the lowest weight. + By default, the search is set in the single-key setting. .. NOTE:: @@ -220,24 +235,29 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name EXAMPLES:: + # single-key setting sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: smt = SmtXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) + sage: trail = smt.find_lowest_weight_xor_differential_trail() + sage: trail['total_weight'] + 9.0 + + # related-key setting + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: smt = SmtXorDifferentialModel(speck) sage: key = set_fixed_variables( ....: component_id='key', - ....: constraint_type='equal', + ....: constraint_type='not_equal', ....: bit_positions=range(64), - ....: bit_values=integer_to_bit_list(0, 64, 'big')) - sage: trail = smt.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) + ....: bit_values=[0]*64) + sage: trail = smt.find_lowest_weight_xor_differential_trail(fixed_values=[key]) sage: trail['total_weight'] - 9.0 + 1.0 """ current_weight = 0 start_building_time = time.time() @@ -265,7 +285,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name def find_one_xor_differential_trail(self, fixed_values=[], solver_name='z3'): """ Return the solution representing a XOR differential trail. - + By default, the search is set in the single-key setting. The solution probability is almost always lower than the one of a random guess of the longest input. INPUT: @@ -279,17 +299,12 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='z3'): EXAMPLES:: + # single-key setting sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: smt = SmtXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: smt.find_one_xor_differential_trail(fixed_values=[plaintext]) # random + sage: smt.find_one_xor_differential_trail() # random {'cipher_id': 'speck_p32_k64_o32_r5', 'model_type': 'xor_differential', 'solver_name': 'z3', @@ -298,6 +313,19 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='z3'): ... 'total_weight': 93, 'building_time_seconds': 0.002946615219116211} + + # related-key setting + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: smt = SmtXorDifferentialModel(speck) + sage: key = set_fixed_variables( + ....: component_id='key', + ....: constraint_type='not_equal', + ....: bit_positions=range(64), + ....: bit_values=[0]*64) + sage: smt.find_one_xor_differential_trail(fixed_values=[key]) # random """ start_building_time = time.time() self.build_xor_differential_trail_model(fixed_variables=fixed_values) @@ -311,6 +339,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='z3'): def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='z3'): """ Return the solution representing a XOR differential trail whose probability is ``2 ** fixed_weight``. + By default, the search is set in the single-key setting. INPUT: @@ -324,24 +353,29 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ EXAMPLES:: + # single-key setting sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel - sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: smt = SmtXorDifferentialModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=(0,)*32) + sage: trail = smt.find_one_xor_differential_trail_with_fixed_weight(3) + sage: trail['total_weight'] + 3.0 + + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(number_of_rounds=5) + sage: smt = SmtXorDifferentialModel(speck) sage: key = set_fixed_variables( ....: component_id='key', - ....: constraint_type='equal', + ....: constraint_type='not_equal', ....: bit_positions=range(64), - ....: bit_values=(0,)*64) - sage: result = smt.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[plaintext, key]) - sage: result['total_weight'] + ....: bit_values=[0]*64) + sage: trail = smt.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[key]) + sage: trail['total_weight'] 3.0 + """ start_building_time = time.time() self.build_xor_differential_trail_model(weight=fixed_weight, fixed_variables=fixed_values) diff --git a/claasp/cipher_modules/models/smt/smt_models/smt_xor_linear_model.py b/claasp/cipher_modules/models/smt/smt_models/smt_xor_linear_model.py index 26b59827..4c023ba5 100644 --- a/claasp/cipher_modules/models/smt/smt_models/smt_xor_linear_model.py +++ b/claasp/cipher_modules/models/smt/smt_models/smt_xor_linear_model.py @@ -21,9 +21,11 @@ from claasp.cipher_modules.models.smt.utils import constants, utils from claasp.cipher_modules.models.smt.smt_model import SmtModel -from claasp.cipher_modules.models.utils import get_bit_bindings, set_component_solution +from claasp.cipher_modules.models.smt.utils.constants import INPUT_BIT_ID_SUFFIX, OUTPUT_BIT_ID_SUFFIX +from claasp.cipher_modules.models.utils import get_bit_bindings, set_component_solution, \ + get_single_key_scenario_format_for_fixed_values from claasp.name_mappings import (CIPHER_OUTPUT, CONSTANT, INTERMEDIATE_OUTPUT, LINEAR_LAYER, - MIX_COLUMN, SBOX, WORD_OPERATION, XOR_LINEAR) + MIX_COLUMN, SBOX, WORD_OPERATION, XOR_LINEAR, INPUT_KEY) class SmtXorLinearModel(SmtModel): @@ -84,6 +86,11 @@ def build_xor_linear_trail_model(self, weight=-1, fixed_variables=[]): """ self._variables_list = [] variables = [] + if INPUT_KEY not in [variable["component_id"] for variable in fixed_variables]: + self._cipher = self._cipher.remove_key_schedule() + self.bit_bindings, self.bit_bindings_for_intermediate_output = get_bit_bindings(self._cipher, '_'.join) + if fixed_variables == []: + fixed_variables = get_single_key_scenario_format_for_fixed_values(self._cipher) constraints = self.fix_variables_value_xor_linear_constraints(fixed_variables) self._model_constraints = constraints @@ -144,6 +151,7 @@ def cipher_input_xor_linear_variables(self): def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='z3'): """ Return a list of solutions containing all the XOR linear trails having weight equal to ``fixed_weight``. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. INPUT: @@ -160,17 +168,22 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: smt = SmtXorLinearModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: trails = smt.find_all_xor_linear_trails_with_fixed_weight(2, fixed_values=[plaintext]) + sage: trails = smt.find_all_xor_linear_trails_with_fixed_weight(1) sage: len(trails) - 2 + 4 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: smt = SmtXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trails = smt.find_all_xor_linear_trails_with_fixed_weight(2, fixed_values=[key]) # long + sage: len(trails) + 8 """ start_building_time = time.time() self.build_xor_linear_trail_model(weight=fixed_weight, fixed_variables=fixed_values) @@ -180,19 +193,24 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value solution = self.solve(XOR_LINEAR, solver_name=solver_name) solution['building_time_seconds'] = end_building_time - start_building_time solutions_list = [] - out_suffix = constants.OUTPUT_BIT_ID_SUFFIX while solution['total_weight'] is not None: solutions_list.append(solution) operands = [] - for component in self._cipher.get_all_components(): - bit_len = component.output_bit_size - if component.type == SBOX or \ - (component.type == WORD_OPERATION and - component.description[0] in ('AND', 'MODADD', 'MODSUB', 'OR', 'SHIFT_BY_VARIABLE_AMOUNT')): - value_to_avoid = int(solution['components_values'][f'{component.id}{out_suffix}']['value'], base=16) - operands.extend([utils.smt_not(f'{component.id}_{j}{out_suffix}') + for component in solution['components_values']: + value_as_hex_string = solution['components_values'][component]['value'] + value_to_avoid = int(value_as_hex_string, base=16) + bit_len = len(value_as_hex_string) * 4 + if CONSTANT in component and component.endswith(INPUT_BIT_ID_SUFFIX): + continue + elif component.endswith(INPUT_BIT_ID_SUFFIX) or component.endswith(OUTPUT_BIT_ID_SUFFIX): + component_id = component[:-2] + suffix = component[-2:] + else: + component_id = component + suffix = OUTPUT_BIT_ID_SUFFIX + operands.extend([utils.smt_not(f'{component_id}_{j}{suffix}') if value_to_avoid >> (bit_len - 1 - j) & 1 - else f'{component.id}_{j}{out_suffix}' + else f'{component_id}_{j}{suffix}' for j in range(bit_len)]) clause = utils.smt_or(operands) self._model_constraints = self._model_constraints[:-len(constants.MODEL_SUFFIX)] \ @@ -206,6 +224,7 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, fixed_values=[], solver_name='z3'): """ Return a list of solutions. + By default, the search removes the key schedule, if any. The list contains all the XOR linear trails having the weight lying in the interval ``[min_weight, max_weight]``. @@ -225,17 +244,22 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: smt = SmtXorLinearModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: trails = smt.find_all_xor_linear_trails_with_weight_at_most(2, 3, fixed_values=[plaintext]) + sage: trails = smt.find_all_xor_linear_trails_with_weight_at_most(0, 2) # long sage: len(trails) - 11 + 187 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: smt = SmtXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trails = smt.find_all_xor_linear_trails_with_weight_at_most(0, 3, fixed_values=[key]) + sage: len(trails) + 73 """ solutions_list = [] for weight in range(min_weight, max_weight + 1): @@ -251,6 +275,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='z3'): """ Return the solution representing a XOR LINEAR trail with the lowest possible weight. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. .. NOTE:: @@ -271,17 +296,22 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='z3') sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: smt = SmtXorLinearModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: trail = smt.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) + sage: trail = smt.find_lowest_weight_xor_linear_trail() sage: trail['total_weight'] 2.0 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=16, key_bit_size=32, number_of_rounds=4) + sage: smt = SmtXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) + sage: trail = smt.find_lowest_weight_xor_linear_trail(fixed_values=[key]) + sage: trail['total_weight'] + 3.0 """ current_weight = 0 start_building_time = time.time() @@ -309,6 +339,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='z3') def find_one_xor_linear_trail(self, fixed_values=[], solver_name='z3'): """ Return the solution representing a XOR linear trail. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. The solution probability is almost always lower than the one of a random guess of the longest input. @@ -326,15 +357,9 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='z3'): sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list sage: speck = SpeckBlockCipher(number_of_rounds=4) sage: smt = SmtXorLinearModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=integer_to_bit_list(0, 32, 'big')) - sage: smt.find_one_xor_linear_trail(fixed_values=[plaintext]) #random + sage: smt.find_one_xor_linear_trail() #random {'cipher_id': 'speck_p32_k64_o32_r4', 'model_type': 'xor_linear', 'solver_name': 'z3', @@ -343,6 +368,14 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='z3'): ... 'total_weight': 67, 'building_time_seconds': 0.003168344497680664} + + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) + sage: smt = SmtXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: smt.find_one_xor_linear_trail(fixed_values=[key]) #random """ start_building_time = time.time() self.build_xor_linear_trail_model(fixed_variables=fixed_values) @@ -357,6 +390,7 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values solver_name='z3'): """ Return the solution representing a XOR linear trail whose weight is ``fixed_weight``. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. INPUT: @@ -372,18 +406,23 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values EXAMPLES:: sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel - sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher sage: speck = SpeckBlockCipher(number_of_rounds=3) sage: smt = SmtXorLinearModel(speck) - sage: plaintext = set_fixed_variables( - ....: component_id='plaintext', - ....: constraint_type='not_equal', - ....: bit_positions=range(32), - ....: bit_values=(0,)*32) - sage: result = smt.find_one_xor_linear_trail_with_fixed_weight(7, fixed_values=[plaintext]) - sage: result['total_weight'] + sage: trail = smt.find_one_xor_linear_trail_with_fixed_weight(7) + sage: trail['total_weight'] 7.0 + + # including the key schedule in the model + sage: from claasp.cipher_modules.models.smt.smt_models.smt_xor_linear_model import SmtXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables + sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sage: smt = SmtXorLinearModel(speck) + sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + sage: trail = smt.find_one_xor_linear_trail_with_fixed_weight(3, fixed_values=[key]) + sage: trail['total_weight'] + 3.0 """ start_building_time = time.time() self.build_xor_linear_trail_model(weight=fixed_weight, fixed_variables=fixed_values) diff --git a/claasp/cipher_modules/models/utils.py b/claasp/cipher_modules/models/utils.py index 9b57ce12..3c1dedfa 100644 --- a/claasp/cipher_modules/models/utils.py +++ b/claasp/cipher_modules/models/utils.py @@ -22,7 +22,8 @@ import math from copy import deepcopy -from claasp.name_mappings import CONSTANT, CIPHER_OUTPUT, INTERMEDIATE_OUTPUT +from claasp.name_mappings import CONSTANT, CIPHER_OUTPUT, INTERMEDIATE_OUTPUT, INPUT_KEY, INPUT_PLAINTEXT, \ + INPUT_MESSAGE, INPUT_STATE def add_arcs(arcs, component, curr_input_bit_ids, input_bit_size, intermediate_output_arcs, previous_output_bit_ids): @@ -37,7 +38,7 @@ def add_arcs(arcs, component, curr_input_bit_ids, input_bit_size, intermediate_o arcs[previous_output_bit_ids[i]].append(curr_input_bit_ids[i]) -def convert_solver_solution_to_dictionary(cipher_id, model_type, solver_name, solve_time, memory, +def convert_solver_solution_to_dictionary(cipher, model_type, solver_name, solve_time, memory, components_values, total_weight): """ Return a dictionary that represents the solution obtained from the solver. @@ -72,7 +73,7 @@ def convert_solver_solution_to_dictionary(cipher_id, model_type, solver_name, so 'total_weight': 0} """ return { - 'cipher_id': cipher_id, + 'cipher': cipher, 'model_type': model_type, 'solver_name': solver_name, 'solving_time_seconds': solve_time, @@ -737,15 +738,17 @@ def get_single_key_scenario_format_for_fixed_values(_cipher): 'not_equal' """ fixed_variables = [] - if 'key' in _cipher.inputs: - input_size = _cipher.inputs_bit_size[_cipher.inputs.index("key")] + if INPUT_KEY in _cipher.inputs: + input_size = _cipher.inputs_bit_size[_cipher.inputs.index(INPUT_KEY)] list_of_0s = [0] * input_size - fixed_variable = set_fixed_variables("key", "equal", list(range(input_size)), list_of_0s) + fixed_variable = set_fixed_variables(INPUT_KEY, "equal", list(range(input_size)), list_of_0s) + fixed_variables.append(fixed_variable) + possible_inputs = {INPUT_PLAINTEXT, INPUT_MESSAGE, INPUT_STATE} + for input in set(_cipher.inputs).intersection(possible_inputs): + input_size = _cipher.inputs_bit_size[_cipher.inputs.index(input)] + list_of_0s = [0] * input_size + fixed_variable = set_fixed_variables(input, "not_equal", list(range(input_size)), list_of_0s) fixed_variables.append(fixed_variable) - input_size = _cipher.inputs_bit_size[_cipher.inputs.index("plaintext")] - list_of_0s = [0] * input_size - fixed_variable = set_fixed_variables("plaintext", "not_equal", list(range(input_size)), list_of_0s) - fixed_variables.append(fixed_variable) return fixed_variables diff --git a/claasp/cipher_modules/neural_network_tests.py b/claasp/cipher_modules/neural_network_tests.py index 61a4d9e8..e5793ee4 100644 --- a/claasp/cipher_modules/neural_network_tests.py +++ b/claasp/cipher_modules/neural_network_tests.py @@ -64,6 +64,7 @@ def neural_network_blackbox_distinguisher_tests(self, nb_samples=10000, """ results = {"input_parameters": { "test_name": "neural_network_blackbox_distinguisher_tests", + "cipher": self.cipher, "number_of_samples": nb_samples, "hidden_layers": hidden_layers, "number_of_epochs": number_of_epochs}, "test_results": {}} @@ -146,7 +147,7 @@ def _update_blackbox_distinguisher_vectorized_tests_ds(self, base_inputs, base_o # cipher_output = base_output base_inputs_np = [np.broadcast_to( - np.array([b for b in x.to_bytes(input_lengths[i] // 8, byteorder='big')], dtype=np.uint8), + np.array([b for b in int(x).to_bytes(input_lengths[i] // 8, byteorder='big')], dtype=np.uint8), (nb_samples, input_lengths[i] // 8) ).transpose().copy() for i, x in enumerate(base_inputs)] random_inputs_for_index = np.frombuffer(os.urandom(nb_samples * input_lengths[index] // 8), @@ -255,6 +256,7 @@ def neural_network_differential_distinguisher_tests(self, nb_samples=10000, hidd """ results = {"input_parameters": { "test_name": "neural_network_differential_distinguisher_tests", + "cipher":self.cipher, "number_of_samples": nb_samples, "input_differences": diff, "hidden_layers": hidden_layers, @@ -313,7 +315,7 @@ def _update_distinguisher_vectorized_tests_ds(self, base_inputs, d, ds, index, l random_labels_size = nb_samples - np.count_nonzero(np.array(labels)) base_inputs_np = [np.broadcast_to( - np.array([b for b in x.to_bytes(input_lengths[i] // 8, byteorder='big')], dtype=np.uint8), + np.array([b for b in int(x).to_bytes(input_lengths[i] // 8, byteorder='big')], dtype=np.uint8), (nb_samples, input_lengths[i] // 8) ).transpose().copy() for i, x in enumerate(base_inputs)] random_inputs_for_index = np.frombuffer(os.urandom(nb_samples * input_lengths[index] // 8), @@ -323,7 +325,7 @@ def _update_distinguisher_vectorized_tests_ds(self, base_inputs, d, ds, index, l other_inputs_np = list(base_inputs_np) - d_array = np.array([b for b in d.to_bytes(input_lengths[index] // 8, byteorder='big')]) + d_array = np.array([b for b in int(d).to_bytes(input_lengths[index] // 8, byteorder='big')]) other_inputs_np[index] = other_inputs_np[index] ^ np.broadcast_to(d_array, ( nb_samples, input_lengths[index] // 8)).transpose() @@ -598,6 +600,7 @@ def run_autond_pipeline(self, difference_positions=None, optimizer_samples=10 ** neural_distinguisher_test_results = { 'input_parameters': { 'test_name': 'neural_distinguisher_test', + 'cipher': self.cipher, 'optimizer_samples': optimizer_samples, 'optimizer_generations': optimizer_generations, 'training_samples': training_samples, @@ -636,6 +639,9 @@ def data_generator(nr, samples): input_size = self.cipher.output_bit_size * 2 neural_network = self.get_neural_network(neural_net, input_size = input_size) nr = max(1, highest_round-3) + + neural_distinguisher_test_results['test_results']['round_start']=nr + print(f'Training {neural_net} on input difference {[hex(x) for x in input_difference]} ({self.cipher.inputs}), from round {nr}...') neural_results = self.train_neural_distinguisher(data_generator, nr, neural_network, training_samples, testing_samples, number_of_epochs) diff --git a/claasp/cipher_modules/report.py b/claasp/cipher_modules/report.py index 3e7cbdca..345dad89 100644 --- a/claasp/cipher_modules/report.py +++ b/claasp/cipher_modules/report.py @@ -7,11 +7,11 @@ import json import shutil from claasp.cipher_modules.statistical_tests.dieharder_statistical_tests import DieharderTests -from claasp.cipher_modules.statistical_tests.nist_statistical_tests import StatisticalTests +from claasp.cipher_modules.statistical_tests.nist_statistical_tests import NISTStatisticalTests from claasp.cipher_modules.component_analysis_tests import CipherComponentsAnalysis -def _print_colored_state(state, verbose, file): +def _print_colored_state(state, verbose, file): for line in state: print('', end='', file=file) for x in line: @@ -100,7 +100,7 @@ def _latex_heatmap(table, table_string, bit_count): class Report: - def __init__(self, cipher, test_report): + def __init__(self, test_report): """ Construct an instance of the Report class. @@ -134,7 +134,10 @@ def __init__(self, cipher, test_report): """ - self.cipher = cipher + try: + self.cipher = test_report['input_parameters']['cipher'] + except KeyError: + self.cipher = test_report['cipher'] self.test_report = test_report if 'test_name' in test_report.keys(): @@ -148,7 +151,7 @@ def __init__(self, cipher, test_report): self.input_parameters = {} self.test_name = test_report['test_name'] if type(test_report) is dict else test_report[0]['test_name'] - def show(self, test_name='trail_search', fixed_input='plaintext', fixed_output='round_output', + def show(self, test_name=None, fixed_input='plaintext', fixed_output='round_output', fixed_input_difference='average', word_size=1, state_size=1, key_state_size=1, verbose=False, show_word_permutation=False, show_var_shift=False, show_var_rotate=False, show_theta_xoodoo=False, @@ -172,26 +175,41 @@ def show(self, test_name='trail_search', fixed_input='plaintext', fixed_output=' show_and, show_or, show_not, show_plaintext, show_key, show_intermediate_output, show_cipher_output, show_input, show_output, save_fig=False) - - else: - - test_list = [] - if 'statistical' in self.test_name: - test_list.append(self.test_name) - elif 'component_analysis' in self.test_name: - Component_Analysis=CipherComponentsAnalysis(self.cipher) - Component_Analysis.print_component_analysis_as_radar_charts(results=self.test_report['test_results']) - elif 'algebraic' not in self.test_name and self.test_name !='neural_distinguisher_test': - test_list = list(self.test_report['test_results'][fixed_input][fixed_output].keys()) - if test_name not in test_list and 'algebraic' not in self.test_name and self.test_name !='neural_distinguisher_test': - print('Error! Invalid test name. Please choose a valid test name') - print('The test name has to be one of the following : ',end='') + return + elif 'component_analysis' in self.test_name: + Component_Analysis = CipherComponentsAnalysis(self.cipher) + Component_Analysis.print_component_analysis_as_radar_charts(results=self.test_report['test_results']) + return + elif 'avalanche_tests' == self.test_name: + test_list = self.test_report['test_results']['plaintext']['round_output'].keys() + if test_name not in test_list: + print('Error! Invalid test name. The report.show function requires a test_name input') + print('test_name has to be one of the following : ', end='') print(test_list) return - self._produce_graph(show_graph=True, fixed_input=fixed_input, fixed_output=fixed_output, - fixed_input_difference=fixed_input_difference, test_name=test_name) + input_diff_values = [x['input_difference_value'] for x in + self.test_report['test_results']['plaintext']['round_output'][test_name] if + 'input_difference_value' in x.keys()] + if fixed_input_difference not in input_diff_values: + print( + 'Error! Invalid input difference value. The report.show() function requires an input_difference_value input') + print('input_difference_value has to be one of the following :', end='') + print(input_diff_values) + return + elif 'neural_network_differential_distinguisher' in self.test_name: + input_diff_values = [x['input_difference_value'] for x in + self.test_report['test_results']['plaintext']['round_output'][ + 'neural_network_differential_distinguisher']] + if fixed_input_difference not in input_diff_values: + print( + 'Error! Invalid input difference value. The report.show() function requires an input_difference_value input') + print('The input difference value has to be one of the following :', end='') + print(input_diff_values) + return + self._produce_graph(show_graph=True, fixed_input=fixed_input, fixed_output=fixed_output, + fixed_input_difference=fixed_input_difference, test_name=test_name) - def _export(self, file_format, output_dir): + def _export(self, file_format, output_dir, fixed_input = None, fixed_output=None, fixed_test = None): if not os.path.exists(output_dir): os.makedirs(output_dir) @@ -202,16 +220,16 @@ def _export(self, file_format, output_dir): if 'statistical' in self.test_name: if file_format == '.csv': - df = pd.DataFrame.from_dict(self.test_report["randomness_test"]) + df = pd.DataFrame.from_dict(self.test_report['test_results']) df.to_csv(output_dir + '/' + self.cipher.id + '/' + self.test_name + file_format) if file_format == '.json': with open(output_dir + '/' + self.cipher.id + '/' + self.test_name + file_format, 'w') as fp: - json.dump(self.test_report["randomness_test"], fp, default=lambda x: float(x)) + json.dump(self.test_report['test_results'], fp, default=lambda x: float(x)) if file_format == '.tex': with open(output_dir + '/' + self.cipher.id + '/' + self.test_name + file_format, 'w') as fp: - fp.write(pd.DataFrame(self.test_report["randomness_test"]).style.to_latex()) + fp.write(pd.DataFrame(self.test_report['test_results']).style.to_latex()) elif 'component_analysis' in self.test_name: print('This method is not implemented yet for the component analysis test.') @@ -259,22 +277,20 @@ def _export(self, file_format, output_dir): if not os.path.exists(output_dir + '/' + self.cipher.id): os.makedirs(output_dir + '/' + self.cipher.id) - for it in self.test_report["test_results"].keys(): + for it in self.test_report["test_results"].keys() if fixed_input == None else [fixed_input]: if not os.path.exists(output_dir + '/' + self.cipher.id + '/' + self.test_report["input_parameters"][ "test_name"] + '_tables/' + it): os.makedirs(output_dir + '/' + self.cipher.id + '/' + self.test_report["input_parameters"][ "test_name"] + '_tables/' + it) - for out in self.test_report["test_results"][it].keys(): - + for out in self.test_report["test_results"][it].keys() if fixed_output == None else [fixed_output]: if not os.path.exists( output_dir + '/' + self.cipher.id + '/' + self.test_report["input_parameters"][ "test_name"] + '_tables/' + it + '/' + out): os.makedirs(output_dir + '/' + self.cipher.id + '/' + self.test_report["input_parameters"][ "test_name"] + '_tables/' + it + '/' + out) - for test in self.test_report["test_results"][it][out].keys(): - + for test in self.test_report["test_results"][it][out].keys() if fixed_test == None else [fixed_test]: if not os.path.exists( output_dir + '/' + self.cipher.id + '/' + self.test_report["input_parameters"][ "test_name"] + '_tables/' + it + '/' + out + '/' + test): @@ -379,14 +395,14 @@ def _export(self, file_format, output_dir): print("Report saved in " + output_dir + '/' + self.cipher.id) - def save_as_DataFrame(self, output_dir=os.getcwd() + '/test_reports'): - self._export(file_format='.csv', output_dir=output_dir) + def save_as_DataFrame(self, output_dir=os.getcwd() + '/test_reports', fixed_input = None, fixed_output=None, fixed_test=None): + self._export(file_format='.csv', output_dir=output_dir, fixed_input=fixed_input, fixed_output=fixed_output, fixed_test = fixed_test) - def save_as_latex_table(self, output_dir=os.getcwd() + '/test_reports'): - self._export(file_format='.tex', output_dir=output_dir) + def save_as_latex_table(self, output_dir=os.getcwd() + '/test_reports', fixed_input = None, fixed_output=None, fixed_test=None): + self._export(file_format='.tex', output_dir=output_dir, fixed_input=fixed_input, fixed_output=fixed_output, fixed_test = fixed_test) - def save_as_json(self, output_dir=os.getcwd() + '/test_reports'): - self._export(file_format='.json', output_dir=output_dir) + def save_as_json(self, output_dir=os.getcwd() + '/test_reports', fixed_input = None, fixed_output=None, fixed_test=None): + self._export(file_format='.json', output_dir=output_dir, fixed_input=fixed_input, fixed_output=fixed_output, fixed_test = fixed_test) def _print_trail(self, word_size, state_size, key_state_size, verbose, show_word_permutation, show_var_shift, show_var_rotate, show_theta_xoodoo, @@ -447,7 +463,7 @@ def _print_trail(self, word_size, state_size, key_state_size, verbose, show_word for comp_id in self.test_report['components_values'].keys(): - if (comp_id != "plaintext" and comp_id != "key") and "key" not in comp_id: + if (comp_id != "plaintext" and comp_id != "key") and "key" not in comp_id: rel_prob = self.test_report['components_values'][comp_id]['weight'] abs_prob += rel_prob @@ -461,11 +477,13 @@ def _print_trail(self, word_size, state_size, key_state_size, verbose, show_word input_links = self.cipher.get_component_from_id(comp_id).input_id_links comp_value = '_'.join(comp_id.split('_')[:-2]) - if (all((id_link in key_flow or 'constant' in id_link or id_link+'_o' in key_flow or id_link+'_i' in key_flow) for id_link in input_links) or ('key' in comp_id and 'comp_id' != 'key')): + if (all(( + id_link in key_flow or 'constant' in id_link or id_link + '_o' in key_flow or id_link + '_i' in key_flow) + for id_link in input_links) or ('key' in comp_id and 'comp_id' != 'key')): key_flow.append(comp_id) if 'linear' in self.test_name: - constants_i = [constant_id+'_i' for constant_id in input_links if 'constant' in constant_id] - constants_o = [constant_id+'_o' for constant_id in input_links if 'constant' in constant_id] + constants_i = [constant_id + '_i' for constant_id in input_links if 'constant' in constant_id] + constants_o = [constant_id + '_o' for constant_id in input_links if 'constant' in constant_id] key_flow = key_flow + constants_i + constants_o else: constants = [constant_id for constant_id in input_links if 'constant' in constant_id] @@ -473,18 +491,19 @@ def _print_trail(self, word_size, state_size, key_state_size, verbose, show_word if show_components[ comp_value if (comp_id not in ['plaintext', 'cipher_output', 'cipher_output_o', 'cipher_output_i', - 'intermediate_output', 'intermediate_output_o', - 'intermediate_output_i'] and 'key' not in comp_id) else comp_id]: + 'intermediate_output', 'intermediate_output_o', + 'intermediate_output_i'] and 'key' not in comp_id) else comp_id]: value = self.test_report['components_values'][comp_id]['value'] - bin_list = list(format(int(value, 16), 'b').zfill(4 * len(value)) if value[:2] != '0x' else 4 * len(value[2:])) if '*' not in value else list( + bin_list = list(format(int(value, 16), 'b').zfill( + 4 * len(value) if value[:2] != '0x' else 4 * len(value[2:]))) if '*' not in value else list( value[2:]) - word_list = ['*' if '*' in ''.join(bin_list[x:x + word_size]) else word_denominator if ''.join(bin_list[x:x + word_size]).count('1') > 0 else '_' for x in + word_list = ['*' if '*' in ''.join(bin_list[x:x + word_size]) else word_denominator if ''.join( + bin_list[x:x + word_size]).count('1') > 0 else '_' for x in range(0, len(bin_list), word_size)] - if ('intermediate' in comp_id or 'cipher' in comp_id) and comp_id not in key_flow: size = (state_size, len(word_list) // state_size) @@ -525,21 +544,21 @@ def _print_trail(self, word_size, state_size, key_state_size, verbose, show_word print('total weight = ' + str(self.test_report['total_weight']), file=file) show_key_flow = False - - for key_comp in key_flow: if key_comp != 'key' and self.test_report['components_values'][key_comp]['weight'] != 0: show_key_flow = True break - if show_key_flow: print('', file=file) print("KEY FLOW", file=file) print('', file=file) for comp_id in key_flow: - if comp_id not in ['plaintext', 'key', 'cipher_output','intermediate_output'] and 'key' not in comp_id and 'intermediate_output' not in comp_id and comp_id[-2:] not in ['_i','_o']: + if comp_id not in ['plaintext', 'key', 'cipher_output', + 'intermediate_output'] and 'key' not in comp_id and 'intermediate_output' not in comp_id and comp_id[ + -2:] not in [ + '_i', '_o']: identifier = '_'.join(comp_id.split('_')[:-2]) elif 'intermediate_output' in comp_id: identifier = 'intermediate_output' + comp_id[-2:] @@ -572,13 +591,17 @@ def _produce_graph(self, output_directory=os.getcwd(), show_graph=False, fixed_i df_scores = pd.DataFrame( self.test_report['test_results']['plaintext']['cipher_output']['differences_scores'], index=['scores']).T + nr = self.test_report['test_results']['round_start'] df_result = pd.DataFrame( self.test_report['test_results']['plaintext']['cipher_output']['neural_distinguisher_test'][0][ - 'accuracies'], index=['accuracy_round' + str(i) for i in range(len( + 'accuracies'], index=['accuracy_round' + str(i) for i in range(nr, nr + len( self.test_report['test_results']['plaintext']['cipher_output']['neural_distinguisher_test'][0][ 'accuracies']))]) if show_graph: + print() + print() + print() print('RESULTS') print('plaintext_input_diff : ' + str( self.test_report['test_results']['plaintext']['cipher_output']['neural_distinguisher_test'][0][ @@ -600,35 +623,44 @@ def _produce_graph(self, output_directory=os.getcwd(), show_graph=False, fixed_i elif 'statistical' in self.test_name: if 'dieharder' in self.test_name: for dict in self.test_report['test_results']: - DieharderTests.generate_chart_round(dict, - output_directory + '/' + self.cipher.id + '/' + self.test_name, show_graph=True) - DieharderTests.generate_chart_all(self.test_report['test_results'], - output_directory + '/' + self.cipher.id + '/' + self.test_name, show_graph=True) + DieharderTests._generate_chart_round(dict, + output_directory + '/' + self.cipher.id + '/' + self.test_name, + show_graph=True) + DieharderTests._generate_chart_all(self.test_report['test_results'], + output_directory + '/' + self.cipher.id + '/' + self.test_name, + show_graph=True) elif 'nist' in self.test_name: for dict in self.test_report['test_results']: - StatisticalTests.generate_chart_round(dict, - output_directory + '/' + self.cipher.id + '/' + self.test_name, show_graph=True) - StatisticalTests.generate_chart_all(self.test_report['test_results'], - output_directory + '/' + self.cipher.id + '/' + self.test_name, show_graph=True) + NISTStatisticalTests._generate_chart_round(dict, + output_directory + '/' + self.cipher.id + '/' + self.test_name, + show_graph=True) + NISTStatisticalTests._generate_chart_all(self.test_report['test_results'], + output_directory + '/' + self.cipher.id + '/' + self.test_name, + show_graph=True) elif 'algebraic' in self.test_name: - y = list(self.test_report['test_results'].keys()) - num_rounds = len(self.test_report['test_results']['number_of_equations']) - x = [i+1 for i in range(num_rounds)] - z = [[1]*num_rounds]*len(self.test_report['test_results'].keys()) - z_text = [] - for test in self.test_report['test_results'].keys(): - z_text.append([str(x) for x in self.test_report['test_results'][test]]) - fig = px.imshow(z, x=x, y=y, color_continuous_scale='Viridis', aspect="auto") - fig.update_traces(text=z_text, texttemplate="%{text}") - fig.update_xaxes(side="top") - if show_graph==False: - fig.write_image(output_directory + '/test_results.png') - if show_graph: - fig.show(renderer='png') - return + y = list(self.test_report['test_results'].keys()) + num_rounds = len(self.test_report['test_results']['number_of_equations']) + x = [i + 1 for i in range(num_rounds)] + z = [[1] * num_rounds] * len(self.test_report['test_results'].keys()) + z_text = [] + for test in self.test_report['test_results'].keys(): + z_text.append([str(x) for x in self.test_report['test_results'][test]]) + fig = px.imshow(z, x=x, y=y, color_continuous_scale='Viridis', aspect="auto") + fig.update_traces(text=z_text, texttemplate="%{text}") + fig.update(layout_coloraxis_showscale=False) + fig.update_xaxes(side="top") + print(output_directory) + + if show_graph == False: + print('saving image') + fig.write_image(output_directory + '/' + self.cipher.id + '/' + self.test_name + '/test_results.png') + print('image saved') + if show_graph: + fig.show(renderer='png') + return else: inputs = list(self.test_report['test_results'].keys()) @@ -714,7 +746,6 @@ def _produce_graph(self, output_directory=os.getcwd(), show_graph=False, fixed_i self.cipher.id + '/' + self.test_name + '/' + it + '/' + out + '/' + res + '/' + str( res) + '_' + str(case['input_difference_value']) + '.png', scale=4) else: - fig.show(renderer='png') return fig.data = [] @@ -723,7 +754,7 @@ def _produce_graph(self, output_directory=os.getcwd(), show_graph=False, fixed_i else: fig = px.line(df, range_x=[1, self.cipher.number_of_rounds], - range_y=[min(df[0]) - 1, max(df[0]) + 1]) + range_y=[0, 1]) fig.update_layout(xaxis_title="round", yaxis_title=res_key, showlegend=False) @@ -738,8 +769,8 @@ def _produce_graph(self, output_directory=os.getcwd(), show_graph=False, fixed_i fig.data = [] fig.layout = {} - - def save_as_image(self, word_size=1, state_size=1, key_state_size=1, output_directory=os.getcwd() + '/test_reports', + def save_as_image(self, test_name=None, fixed_input=None, fixed_output=None, + fixed_input_difference=None, word_size=1, state_size=1, key_state_size=1, output_directory=os.getcwd() + '/test_reports', verbose=False, show_word_permutation=False, show_var_shift=False, show_var_rotate=False, show_theta_xoodoo=False, show_theta_keccak=False, show_shift_rows=False, show_sigma=False, show_reverse=False, @@ -797,15 +828,13 @@ def save_as_image(self, word_size=1, state_size=1, key_state_size=1, output_dire if not os.path.exists(output_directory + '/' + self.cipher.id + '/' + self.test_name): os.mkdir(output_directory + '/' + self.cipher.id + '/' + self.test_name) - self._produce_graph(output_directory) + self._produce_graph(output_directory, test_name=test_name, fixed_output=fixed_output, fixed_input_difference=fixed_input_difference, fixed_input=fixed_input) print('Report saved in ' + output_directory) - def clean_reports(self, output_dir=os.getcwd() + '/test_reports/reports'): + def clean_reports(self, output_dir=os.getcwd() + '/test_reports'): if os.path.exists(output_dir): shutil.rmtree(output_dir) else: print("Directory " + output_dir + " not found") return - - diff --git a/claasp/cipher_modules/statistical_tests/dieharder_statistical_tests.py b/claasp/cipher_modules/statistical_tests/dieharder_statistical_tests.py index f54333d6..433fe010 100644 --- a/claasp/cipher_modules/statistical_tests/dieharder_statistical_tests.py +++ b/claasp/cipher_modules/statistical_tests/dieharder_statistical_tests.py @@ -1,4 +1,3 @@ - # **************************************************************************** # Copyright 2023 Technology Innovation Institute # @@ -25,7 +24,6 @@ from claasp.cipher_modules.statistical_tests.dataset_generator import DatasetGenerator, DatasetType - TEST_ID_TABLE = { 'Frequency': 1, @@ -58,12 +56,13 @@ def __init__(self, cipher): self._cipher_primitive = cipher.id + "_" + "_".join(str_of_inputs_bit_size) def dieharder_statistical_tests(self, test_type, - bits_in_one_line='default', - number_of_lines='default', + bits_in_one_sequence_dieharder='default', + number_of_sequences_dieharder='default', input_index=0, round_start=0, round_end=0, dieharder_report_folder_prefix="dieharder_statistics_report", + dieharder_test_option=None ): """ @@ -73,8 +72,8 @@ def dieharder_statistical_tests(self, test_type, INPUT: - ``test_type`` -- string describing which test to run - - ``bits_in_one_line`` -- integer parameter used to run the dieharder tests - - ``number_of_lines`` -- integer parameter used to run the dieharder tests + - ``bits_in_one_sequence_dieharder`` -- integer parameter used to run the dieharder tests + - ``number_of_sequences_dieharder`` -- integer parameter used to run the dieharder tests - ``input_index`` -- cipher input index - ``round_start`` -- first round to be considered in the cipher - ``round_end`` -- last round to be considered in the cipher @@ -93,10 +92,12 @@ def dieharder_statistical_tests(self, test_type, dieharder_avalanche_test_results = dieharder_tests.dieharder_statistical_tests('avalanche') """ + dieharder_test = { 'input_parameters': { 'test_name': 'dieharder_statistical_tests', + 'cipher': self.cipher, 'test_type': test_type, 'round_start': round_start, 'round_end': round_end, @@ -113,146 +114,137 @@ def dieharder_statistical_tests(self, test_type, if test_type == 'avalanche': - self.dataset_type = DatasetType.avalanche + self.dataset_type_dieharder = DatasetType.avalanche self.input_index = input_index - if bits_in_one_line == 'default': - bits_in_one_line = 1048576 - if number_of_lines == 'default': - number_of_lines = 384 + if bits_in_one_sequence_dieharder == 'default': + bits_in_one_sequence_dieharder = 1048576 + if number_of_sequences_dieharder == 'default': + number_of_sequences_dieharder = 384 sample_size = self.cipher.inputs_bit_size[input_index] * self.cipher.output_bit_size - number_of_samples_in_one_line = math.ceil(bits_in_one_line / sample_size) - self.number_of_lines = number_of_lines - self.number_of_samples_in_one_line = number_of_samples_in_one_line - self.number_of_samples = self.number_of_samples_in_one_line * (self.number_of_lines + 1) - self.bits_in_one_line = sample_size * self.number_of_samples_in_one_line + number_of_samples_dieharder = math.ceil(bits_in_one_sequence_dieharder / sample_size) + self.number_of_sequences_dieharder = number_of_sequences_dieharder + self.number_of_samples_dieharder = number_of_samples_dieharder + self.number_of_samples_dieharder = self.number_of_samples_dieharder * ( + self.number_of_sequences_dieharder + 1) + self.bits_in_one_sequence_dieharder = sample_size * self.number_of_samples_dieharder self._create_report_folder() dataset = self.data_generator.generate_avalanche_dataset(input_index=self.input_index, - number_of_samples=self.number_of_samples) + number_of_samples=self.number_of_samples_dieharder) elif test_type == 'correlation': - self.dataset_type = DatasetType.correlation + self.dataset_type_dieharder = DatasetType.correlation self.input_index = input_index - if bits_in_one_line == 'default': - bits_in_one_line = 1048576 - if number_of_lines == 'default': - number_of_lines = 384 + if bits_in_one_sequence_dieharder == 'default': + bits_in_one_sequence_dieharder = 1048576 + if number_of_sequences_dieharder == 'default': + number_of_sequences_dieharder = 384 - number_of_blocks_in_one_sample = math.ceil(bits_in_one_line / self.cipher.output_bit_size) - self.number_of_lines = number_of_lines - self.number_of_samples = self.number_of_lines + 1 - self.bits_in_one_line = number_of_blocks_in_one_sample * self.cipher.output_bit_size + number_of_blocks_in_one_sample_dieharder = math.ceil( + bits_in_one_sequence_dieharder / self.cipher.output_bit_size) + self.number_of_sequences_dieharder = number_of_sequences_dieharder + self.number_of_samples_dieharder = self.number_of_sequences_dieharder + 1 + self.bits_in_one_sequence_dieharder = number_of_blocks_in_one_sample_dieharder * self.cipher.output_bit_size self._create_report_folder() dataset = self.data_generator.generate_correlation_dataset(input_index=self.input_index, - number_of_samples=self.number_of_samples, - number_of_blocks_in_one_sample=number_of_blocks_in_one_sample) + number_of_samples=self.number_of_samples_dieharder, + number_of_blocks_in_one_sample=number_of_blocks_in_one_sample_dieharder) elif test_type == 'cbc': - self.dataset_type = DatasetType.cbc + self.dataset_type_dieharder = DatasetType.cbc self.input_index = input_index - if bits_in_one_line == 'default': - bits_in_one_line = 1048576 - if number_of_lines == 'default': - number_of_lines = 384 + if bits_in_one_sequence_dieharder == 'default': + bits_in_one_sequence_dieharder = 1048576 + if number_of_sequences_dieharder == 'default': + number_of_sequences_dieharder = 384 - number_of_blocks_in_one_sample = math.ceil(bits_in_one_line / self.cipher.output_bit_size) - self.number_of_lines = number_of_lines - self.number_of_samples = self.number_of_lines + 1 - self.bits_in_one_line = number_of_blocks_in_one_sample * self.cipher.output_bit_size + number_of_blocks_in_one_sample_dieharder = math.ceil( + bits_in_one_sequence_dieharder / self.cipher.output_bit_size) + self.number_of_sequences_dieharder = number_of_sequences_dieharder + self.number_of_samples_dieharder = self.number_of_sequences_dieharder + 1 + self.bits_in_one_sequence_dieharder = number_of_blocks_in_one_sample_dieharder * self.cipher.output_bit_size self._create_report_folder() dataset = self.data_generator.generate_cbc_dataset(input_index=self.input_index, - number_of_samples=self.number_of_samples, - number_of_blocks_in_one_sample=number_of_blocks_in_one_sample) + number_of_samples=self.number_of_samples_dieharder, + number_of_blocks_in_one_sample=number_of_blocks_in_one_sample_dieharder) elif test_type == 'random': - self.dataset_type = DatasetType.random + self.dataset_type_dieharder = DatasetType.random self.input_index = input_index - if bits_in_one_line == 'default': - bits_in_one_line = 1040384 - if number_of_lines == 'default': - number_of_lines = 128 + if bits_in_one_sequence_dieharder == 'default': + bits_in_one_sequence_dieharder = 1040384 + if number_of_sequences_dieharder == 'default': + number_of_sequences_dieharder = 128 - number_of_blocks_in_one_sample = math.ceil(bits_in_one_line / self.cipher.output_bit_size) - self.number_of_lines = number_of_lines - self.number_of_samples = self.number_of_lines + 1 - self.bits_in_one_line = number_of_blocks_in_one_sample * self.cipher.output_bit_size + number_of_blocks_in_one_sample_dieharder = math.ceil( + bits_in_one_sequence_dieharder / self.cipher.output_bit_size) + self.number_of_sequences_dieharder = number_of_sequences_dieharder + self.number_of_samples_dieharder = self.number_of_sequences_dieharder + 1 + self.bits_in_one_sequence_dieharder = number_of_blocks_in_one_sample_dieharder * self.cipher.output_bit_size self._create_report_folder() dataset = self.data_generator.generate_random_dataset(input_index=self.input_index, - number_of_samples=self.number_of_samples, - number_of_blocks_in_one_sample=self.number_of_blocks_in_one_sample) + number_of_samples=self.number_of_samples_dieharder, + number_of_blocks_in_one_sample=number_of_blocks_in_one_sample_dieharder) - elif test_type == 'low_density': - self.dataset_type = DatasetType.low_density + elif 'density' in test_type: + self.dataset_type_dieharder = DatasetType.low_density self.input_index = input_index - if bits_in_one_line == 'default': - bits_in_one_line = 1056896 - if number_of_lines == 'default': - number_of_lines = 1 - - number_of_blocks_in_one_sample = math.ceil(bits_in_one_line / self.cipher.output_bit_size) - self.number_of_lines = number_of_lines - self.number_of_samples = self.number_of_lines + 1 + if bits_in_one_sequence_dieharder == 'default': + bits_in_one_sequence_dieharder = 1056896 + if number_of_sequences_dieharder == 'default': + number_of_sequences_dieharder = 1 + + number_of_blocks_in_one_sample_dieharder = math.ceil( + bits_in_one_sequence_dieharder / self.cipher.output_bit_size) + self.number_of_sequences_dieharder = number_of_sequences_dieharder + self.number_of_samples_dieharder = self.number_of_sequences_dieharder + 1 n = self.cipher.inputs_bit_size[self.input_index] - ratio = min(1, (number_of_blocks_in_one_sample - 1 - n) / math.comb(n, 2)) - self.number_of_blocks_in_one_sample = int(1 + n + math.ceil(math.comb(n, 2) * ratio)) - self.bits_in_one_line = self.number_of_blocks_in_one_sample * self.cipher.output_bit_size + ratio = min(1, (number_of_blocks_in_one_sample_dieharder - 1 - n) / math.comb(n, 2)) + self.number_of_blocks_in_one_sample_dieharder = int(1 + n + math.ceil(math.comb(n, 2) * ratio)) + self.bits_in_one_sequence_dieharder = self.number_of_blocks_in_one_sample_dieharder * self.cipher.output_bit_size self._create_report_folder() - dataset = self.data_generator.generate_low_density_dataset(input_index=self.input_index, - number_of_samples=self.number_of_samples, + if test_type == 'low_density': + dataset = self.data_generator.generate_low_density_dataset(input_index=self.input_index, + number_of_samples=self.number_of_samples_dieharder, ratio=ratio) - elif test_type == 'high_density': - self.dataset_type = DatasetType.high_density - self.input_index = input_index - if bits_in_one_line == 'default': - bits_in_one_line = 1056896 - if number_of_lines == 'default': - number_of_lines = 1 - - number_of_blocks_in_one_sample = math.ceil(bits_in_one_line / self.cipher.output_bit_size) - self.number_of_lines = number_of_lines - self.number_of_samples = self.number_of_lines + 1 - n = self.cipher.inputs_bit_size[self.input_index] - ratio = min(1, (number_of_blocks_in_one_sample - 1 - n) / math.comb(n, 2)) - self.number_of_blocks_in_one_sample = int(1 + n + math.ceil(math.comb(n, 2) * ratio)) - self.bits_in_one_line = self.number_of_blocks_in_one_sample * self.cipher.output_bit_size - - self._create_report_folder() - - dataset = self.data_generator.generate_high_density_dataset(input_index=self.input_index, - number_of_samples=self.number_of_samples, + elif test_type == 'high_density': + dataset = self.data_generator.generate_high_density_dataset(input_index=self.input_index, + number_of_samples=self.number_of_samples_dieharder, ratio=ratio) else: # maybe print the enum value of Dataset.type print( - 'Invalid test_type choice. Choose among the following: avalanche, correlation, cbc, random, low_density, high_density') + 'Invalid test_type choice. Choose among the following: avalanche, correlation, cbc, random, ' + 'low_density, high_density') return dataset_generate_time = time.time() - dataset_generate_time if not dataset: return - self._write_execution_time(f'Compute {self.dataset_type.value}', dataset_generate_time) - dieharder_test['test_results'] = self._generate_dieharder_dicts(dataset, round_start, round_end, FLAG_CHART=False) - dieharder_test['input_parameters']['bits_in_one_line'] = bits_in_one_line - dieharder_test['input_parameters']['number_of_lines'] = number_of_lines + self._write_execution_time(f'Compute {self.dataset_type_dieharder.value}', dataset_generate_time) + dieharder_test['test_results'] = self._generate_dieharder_dicts(dataset, round_start, round_end, + FLAG_CHART=False, + dieharder_test_option=dieharder_test_option) + dieharder_test['input_parameters']['bits_in_one_sequence_dieharder'] = bits_in_one_sequence_dieharder + dieharder_test['input_parameters']['number_of_sequences_dieharder'] = number_of_sequences_dieharder return dieharder_test - @staticmethod - def _run_dieharder_statistical_tests_tool(input_file): + def _run_dieharder_statistical_tests_tool(input_file, dieharder_test_option): """ Run dieharder tests using the Dieharder library [1]. The result will be in dieharder_test_output.txt. @@ -281,7 +273,10 @@ def _run_dieharder_statistical_tests_tool(input_file): Dieharder Tests Finished!!! """ print("Dieharder Tests Started...") - os.system(f'dieharder -g 201 -f {input_file} -a > {__class__._DIEHARDER_OUTPUT}') + if dieharder_test_option is None: + os.system(f'dieharder -g 201 -f {input_file} -a > {__class__._DIEHARDER_OUTPUT}') + else: + os.system(f'dieharder -g 201 -f {input_file} -d {dieharder_test_option} > {__class__._DIEHARDER_OUTPUT}') print(f'Dieharder Tests Finished!!!') @staticmethod @@ -365,7 +360,7 @@ def _parse_report(report_filename): return report_dict @staticmethod - def generate_chart_round(report_dict, output_dir='', show_graph=False): + def _generate_chart_round(report_dict, output_dir='', show_graph=False): """ Generate the corresponding chart based on the parsed report dictionary. @@ -378,30 +373,8 @@ def generate_chart_round(report_dict, output_dir='', show_graph=False): - save the chart with filename f'dieharder_{report_dict["data_type"]}_{report_dict["cipher_name"]}_round_{report_dict["round"]}.png' - EXAMPLES:: - - sage: from claasp.cipher_modules.statistical_tests.dieharder_statistical_tests import DieharderTests - sage: result = DieharderTests._run_dieharder_statistical_tests_tool( # doctest: +SKIP - ....: f'claasp/cipher_modules/statistical_tests/input_data_example', # doctest: +SKIP - ....: ) # long time # doctest: +SKIP - ... - Dieharder Tests Finished!!! - - sage: from claasp.cipher_modules.statistical_tests.dieharder_statistical_tests import DieharderTests - sage: dict = DieharderTests.parse_report(f'dieharder_test_output.txt') # doctest: +SKIP - Parsing dieharder_test_output.txt is in progress. - Parsing dieharder_test_output.txt is finished. - - sage: dict['data_type'] = 'random' # doctest: +SKIP - sage: dict['data_type'] = 'random' # doctest: +SKIP - sage: dict['cipher_name'] = 'toy_cipher' # doctest: +SKIP - sage: dict['round'] = 1 # doctest: +SKIP - sage: dict['rounds'] = 1 # doctest: +SKIP - sage: DieharderTests.generate_chart_round(dict) # doctest: +SKIP - Drawing round 1 is in progress. - Drawing round 1 is finished. Please find the chart in file dieharder_random_toy_cipher_round_1.png. """ - print(f'Drawing round {report_dict["round"]} is in progress.') + print(f'Drawing round {report_dict["round"]} is in progress') x = [i for i in range(len(report_dict['randomness_test']))] y = [0 for _ in range(len(report_dict['randomness_test']))] label_y = { @@ -414,25 +387,30 @@ def generate_chart_round(report_dict, output_dir='', show_graph=False): plt.clf() plt.scatter(x, y, color="cadetblue") - plt.title( - f'{report_dict["cipher_name"]}: {report_dict["data_type"]}, Round {report_dict["round"]}|{report_dict["rounds"]}') + if len(report_dict['randomness_test'])==1: + plt.title( + f'{report_dict["cipher_name"]}: {report_dict["data_type"]}, Round {report_dict["round"]}|{report_dict["rounds"]}|{report_dict["randomness_test"][0]["test_name"]}') + else: + plt.title( + f'{report_dict["cipher_name"]}: {report_dict["data_type"]}, Round {report_dict["round"]}|{report_dict["rounds"]}') plt.xlabel('Tests') plt.yticks([-1, 0, 1], ['FAILED', 'WEAK', 'PASSED']) - if show_graph==False: - if output_dir =='': + if show_graph == False: + if output_dir == '': output_dir = f'dieharder_{report_dict["data_type"]}_{report_dict["cipher_name"]}_round_{report_dict["round"]}.png' plt.savefig(output_dir) else: - plt.savefig(output_dir+'/'+f'dieharder_{report_dict["data_type"]}_{report_dict["cipher_name"]}_round_{report_dict["round"]}.png') + plt.savefig( + output_dir + '/' + f'dieharder_{report_dict["data_type"]}_{report_dict["cipher_name"]}_round_{report_dict["round"]}.png') else: plt.show() plt.clf() plt.close() - print(f'Drawing round {report_dict["round"]} is finished. Please find the chart in file {output_dir}.') + print(f'Drawing round {report_dict["round"]} is finished') @staticmethod - def generate_chart_all(report_dict_list, output_dir='', show_graph=False): + def _generate_chart_all(report_dict_list, output_dir='', show_graph=False): """ Generate the corresponding chart based on the parsed report dictionary. @@ -445,34 +423,12 @@ def generate_chart_all(report_dict_list, output_dir='', show_graph=False): - save the chart with filename f'dieharder_{report_dict["data_type"]}_{report_dict["cipher_name"]}_round_{report_dict["round"]}.png' - EXAMPLES:: - - sage: from claasp.cipher_modules.statistical_tests.dieharder_statistical_tests import DieharderTests - sage: result = DieharderTests.run_dieharder_statistical_tests_tool( # doctest: +SKIP - ....: f'claasp/cipher_modules/statistical_tests/input_data_example', # doctest: +SKIP - ....: ) # long time # doctest: +SKIP - ... - Dieharder Tests Finished!!! - - sage: from claasp.cipher_modules.statistical_tests.dieharder_statistical_tests import DieharderTests - sage: dict = DieharderTests.parse_report(f'dieharder_test_output.txt') # doctest: +SKIP - Parsing dieharder_test_output.txt is in progress. - Parsing dieharder_test_output.txt is finished. - - sage: dict['data_type'] = 'random' # doctest: +SKIP - sage: dict['cipher_name'] = 'toy_cipher' # doctest: +SKIP - sage: dict['round'] = 1 # doctest: +SKIP - sage: dict['rounds'] = 1 # doctest: +SKIP - sage: dict_list = [dict] # doctest: +SKIP - sage: DieharderTests.generate_chart_all(dict_list) # doctest: +SKIP - Drawing chart for all rounds is in progress. - Drawing chart for all rounds is in finished. Please find the chart in file dieharder_random_toy_cipher.png. """ print("Drawing chart for all rounds is in progress.") - x = [i + 1 for i in range(report_dict_list[0]["rounds"])] - y = [0 for _ in range(report_dict_list[0]["rounds"])] + x = [i + 1 for i in range(report_dict_list[0]["round"], report_dict_list[-1]["round"]+1)] + y = [0 for _ in range(len(x))] for i in range(len(report_dict_list)): - y[report_dict_list[i]["round"] - 1] = report_dict_list[i]["passed_tests_proportion"] + y[i] = report_dict_list[i]["passed_tests_proportion"] plt.clf() plt.scatter(x, y, color="cadetblue") @@ -485,21 +441,22 @@ def generate_chart_all(report_dict_list, output_dir='', show_graph=False): # plt.grid(True) chart_filename = f'dieharder_{report_dict_list[0]["data_type"]}_{report_dict_list[0]["cipher_name"]}.png' - if show_graph==False: - if output_dir =='': + if show_graph == False: + if output_dir == '': output_dir = f'dieharder_{report_dict_list[0]["data_type"]}_{report_dict_list[0]["cipher_name"]}.png' plt.savefig(output_dir) else: - plt.savefig(output_dir+'/'+f'dieharder_{report_dict_list[0]["data_type"]}_{report_dict_list[0]["cipher_name"]}.png') + plt.savefig( + output_dir + '/' + f'dieharder_{report_dict_list[0]["data_type"]}_{report_dict_list[0]["cipher_name"]}.png') else: plt.show() plt.clf() plt.close() - print(f'Drawing chart for all rounds is in finished. Please find the chart in file {chart_filename}.') + print(f'Drawing chart for all rounds is in finished.') def _create_report_folder(self): self.report_folder = os.path.join(self.folder_prefix, - f'{self._cipher_primitive}_{self.dataset_type.name}_index{self.input_index}_{self.number_of_lines}lines_{self.bits_in_one_line}bits') + f'{self._cipher_primitive}_{self.dataset_type_dieharder.name}_index{self.input_index}_{self.number_of_sequences_dieharder}lines_{self.bits_in_one_sequence_dieharder}bits') try: os.makedirs(self.report_folder) except OSError: @@ -513,7 +470,7 @@ def _write_execution_time(self, execution_description, execution_time): except Exception as e: print(f'Error: {e.strerror}') - def _generate_dieharder_dicts(self, dataset, round_start, round_end, FLAG_CHART=False): + def _generate_dieharder_dicts(self, dataset, round_start, round_end, dieharder_test_option, FLAG_CHART=False): dataset_folder = os.getcwd() + '/dataset' dataset_filename = 'dieharder_input_' + self._cipher_primitive dataset_filename = os.path.join(dataset_folder, dataset_filename) @@ -531,7 +488,7 @@ def _generate_dieharder_dicts(self, dataset, round_start, round_end, FLAG_CHART= dataset[round_number].tofile(dataset_filename) dieharder_execution_time = time.time() - self._run_dieharder_statistical_tests_tool(dataset_filename) + self._run_dieharder_statistical_tests_tool(dataset_filename, dieharder_test_option) dieharder_execution_time = time.time() - dieharder_execution_time try: os.rename(self._DIEHARDER_OUTPUT, report_round) @@ -548,22 +505,22 @@ def _generate_dieharder_dicts(self, dataset, round_start, round_end, FLAG_CHART= # generate report dieharder_report_dict = self._parse_report(report_round) dieharder_report_dict[ - 'data_type'] = f'{self.cipher.inputs[self.input_index]}_{self.dataset_type.value}' + 'data_type'] = f'{self.cipher.inputs[self.input_index]}_{self.dataset_type_dieharder.value}' dieharder_report_dict["cipher_name"] = self.cipher.id dieharder_report_dict["round"] = round_number dieharder_report_dict["rounds"] = self.cipher.number_of_rounds dieharder_report_dicts.append(dieharder_report_dict) # generate round chart if FLAG_CHART: - self.generate_chart_round(dieharder_report_dict) + self._generate_chart_round(dieharder_report_dict) except OSError: print(f'Error in parsing report for round {round_number}.') # generate chart for all rounds if FLAG_CHART: try: - self.generate_chart_all(dieharder_report_dicts) + self._generate_chart_all(dieharder_report_dicts) except OSError: print(f'Error in generating all round chart.') - return dieharder_report_dicts \ No newline at end of file + return dieharder_report_dicts diff --git a/claasp/cipher_modules/statistical_tests/nist_statistical_tests.py b/claasp/cipher_modules/statistical_tests/nist_statistical_tests.py index 63947e7e..9b6d42e5 100644 --- a/claasp/cipher_modules/statistical_tests/nist_statistical_tests.py +++ b/claasp/cipher_modules/statistical_tests/nist_statistical_tests.py @@ -49,7 +49,7 @@ } -class StatisticalTests: +class NISTStatisticalTests: def __init__(self, cipher): cipher.sort_cipher() @@ -99,6 +99,7 @@ def nist_statistical_tests(self, test_type, 'input_parameters': { 'test_name': 'nist_statistical_tests', + 'cipher': self.cipher, 'test_type': test_type, 'round_start': round_start, 'round_end': round_end, @@ -131,7 +132,8 @@ def nist_statistical_tests(self, test_type, self.number_of_samples = self.number_of_samples_in_one_sequence * (self.number_of_sequences + 1) self.bits_in_one_sequence = sample_size * self.number_of_samples_in_one_sequence - self._create_report_folder() + self._create_report_folder(statistical_test_option_list) + dataset = self.data_generator.generate_avalanche_dataset(input_index=self.input_index, number_of_samples=self.number_of_samples) @@ -149,8 +151,7 @@ def nist_statistical_tests(self, test_type, self.number_of_sequences = number_of_sequences self.number_of_samples = self.number_of_sequences + 1 self.bits_in_one_sequence = number_of_blocks_in_one_sample * self.cipher.output_bit_size - - self._create_report_folder() + self._create_report_folder(statistical_test_option_list) dataset = self.data_generator.generate_correlation_dataset(input_index=self.input_index, number_of_samples=self.number_of_samples, @@ -169,8 +170,7 @@ def nist_statistical_tests(self, test_type, self.number_of_sequences = number_of_sequences self.number_of_samples = self.number_of_sequences + 1 self.bits_in_one_sequence = number_of_blocks_in_one_sample * self.cipher.output_bit_size - - self._create_report_folder() + self._create_report_folder(statistical_test_option_list) dataset = self.data_generator.generate_cbc_dataset(input_index=self.input_index, number_of_samples=self.number_of_samples, @@ -188,8 +188,7 @@ def nist_statistical_tests(self, test_type, self.number_of_sequences = number_of_sequences self.number_of_samples = self.number_of_sequences + 1 self.bits_in_one_sequence = self.number_of_blocks_in_one_sample * self.cipher.output_bit_size - - self._create_report_folder() + self._create_report_folder(statistical_test_option_list) dataset = self.data_generator.generate_random_dataset(input_index=self.input_index, number_of_samples=self.number_of_samples, @@ -210,8 +209,7 @@ def nist_statistical_tests(self, test_type, ratio = min(1, (number_of_blocks_in_one_sample - 1 - n) / math.comb(n, 2)) self.number_of_blocks_in_one_sample = int(1 + n + math.ceil(math.comb(n, 2) * ratio)) self.bits_in_one_sequence = self.number_of_blocks_in_one_sample * self.cipher.output_bit_size - - self._create_report_folder() + self._create_report_folder(statistical_test_option_list) dataset = self.data_generator.generate_low_density_dataset(input_index=self.input_index, number_of_samples=self.number_of_samples, @@ -231,8 +229,7 @@ def nist_statistical_tests(self, test_type, ratio = min(1, (number_of_blocks_in_one_sample - 1 - n) / math.comb(n, 2)) self.number_of_blocks_in_one_sample = int(1 + n + math.ceil(math.comb(n, 2) * ratio)) self.bits_in_one_sequence = self.number_of_blocks_in_one_sample * self.cipher.output_bit_size - - self._create_report_folder() + self._create_report_folder(statistical_test_option_list) dataset = self.data_generator.generate_high_density_dataset(input_index=self.input_index, number_of_samples=self.number_of_samples, @@ -450,7 +447,7 @@ def _parse_report(report_filename, statistical_test_option_list='1' + 14 * '0'): return report_dict @staticmethod - def generate_chart_round(report_dict, output_dir='', show_graph=False): + def _generate_chart_round(report_dict, output_dir='', show_graph=False): """ Generate the corresponding chart based on the parsed report dictionary. @@ -463,21 +460,10 @@ def generate_chart_round(report_dict, output_dir='', show_graph=False): - save the chart with filename f'nist_{report_dict["data_type"]}_{report_dict["cipher_name"]}_round_{report_dict["round"]}.png' - EXAMPLES:: - - sage: from claasp.cipher_modules.statistical_tests.nist_statistical_tests import StatisticalTests - sage: dict = StatisticalTests.parse_report(f'claasp/cipher_modules/statistical_tests/finalAnalysisReportExample.txt') - Parsing claasp/cipher_modules/statistical_tests/finalAnalysisReportExample.txt is in progress. - Parsing claasp/cipher_modules/statistical_tests/finalAnalysisReportExample.txt is finished. - - sage: dict['data_type'] = 'random' - sage: dict['cipher_name'] = 'toy_cipher' - sage: dict['round'] = 1 - sage: dict['rounds'] = 1 - sage: StatisticalTests.generate_chart_round(dict) - Drawing round 1 is in progress. - Drawing round 1 is finished. """ + + if len(report_dict['randomness_test']) == 1: + return print(f'Drawing round {report_dict["round"]} is in progress.') x = [test['test_id'] for test in report_dict['randomness_test']] y = [0 for _ in range(len(x))] @@ -514,7 +500,7 @@ def generate_chart_round(report_dict, output_dir='', show_graph=False): print(f'Drawing round {report_dict["round"]} is finished.') @staticmethod - def generate_chart_all(report_dict_list, report_folder="", show_graph=False): + def _generate_chart_all(report_dict_list, report_folder="", show_graph=False): """ Generate the corresponding chart based on the list of parsed report dictionary for all rounds. @@ -526,41 +512,24 @@ def generate_chart_all(report_dict_list, report_folder="", show_graph=False): - save the chart with filename f'nist_{data_type}_{cipher_name}.png' - EXAMPLES:: - - sage: from claasp.cipher_modules.statistical_tests.nist_statistical_tests import StatisticalTests - sage: dict = StatisticalTests.parse_report(f'claasp/cipher_modules/statistical_tests/finalAnalysisReportExample.txt') - Parsing claasp/cipher_modules/statistical_tests/finalAnalysisReportExample.txt is in progress. - Parsing claasp/cipher_modules/statistical_tests/finalAnalysisReportExample.txt is finished. - - sage: dict['data_type'] = 'random' - sage: dict['cipher_name'] = 'toy_cipher' - sage: dict['round'] = 1 - sage: dict['rounds'] = 1 - sage: dict_list = [dict] - sage: StatisticalTests.generate_chart_all(dict_list) - Drawing chart for all rounds is in progress. - Drawing chart for all rounds is in finished. """ print("Drawing chart for all rounds is in progress.") - x = [i + 1 for i in range(report_dict_list[0]["rounds"])] - y = [0 for _ in range(report_dict_list[0]["rounds"])] + x = [i + 1 for i in range(report_dict_list[0]["round"], report_dict_list[-1]["round"]+1)] + y = [0 for _ in range(len(x))] for i in range(len(report_dict_list)): - print(report_dict_list[i]["round"]) - print(len(y)) - print() - y[report_dict_list[i]["round"]-1] = report_dict_list[i]["passed_tests"] + y[i] = report_dict_list[i]["passed_tests"] + random_round = -1 for r in range(report_dict_list[0]["rounds"]): - if report_dict_list[r]["passed_tests"] > 188*0.98: + if report_dict_list[r]["passed_tests"] > len(report_dict_list[0]['randomness_test'])*0.98: random_round = report_dict_list[r]["round"] break plt.clf() plt.scatter(x, y, color="cadetblue") - plt.hlines(186, 1, report_dict_list[0]["rounds"], color="darkorange", linestyle="dotted", linewidth=2, - label="186") + plt.hlines(len(report_dict_list[0]['randomness_test'])*0.98, 1, report_dict_list[0]["rounds"], color="darkorange", linestyle="dotted", linewidth=2, + label=str(math.ceil(len(report_dict_list[0]['randomness_test'])*0.98))) plt.plot(x, y, 'o--', color='olive', alpha=0.4) if random_round > -1: plt.title( @@ -571,7 +540,7 @@ def generate_chart_all(report_dict_list, report_folder="", show_graph=False): plt.ylabel('Tests passed') plt.xticks([i * 2 + 1 for i in range(int(report_dict_list[0]["rounds"] / 2) + 1)], [i * 2 + 1 for i in range(int(report_dict_list[0]["rounds"] / 2 + 1))]) - plt.yticks([i * 20 for i in range(1, 11)], [i * 20 for i in range(1, 11)]) + plt.yticks(list(range(math.ceil(len(report_dict_list[0]['randomness_test'])*0.98)))) chart_filename = f'nist_{report_dict_list[0]["data_type"]}_{report_dict_list[0]["cipher_name"]}.png' if show_graph == False: @@ -582,9 +551,9 @@ def generate_chart_all(report_dict_list, report_folder="", show_graph=False): plt.close() print(f'Drawing chart for all rounds is in finished.') - def _create_report_folder(self): + def _create_report_folder(self,statistical_test_option_list): self.report_folder = os.path.join(self.folder_prefix, - f'{self._cipher_primitive}_{self.dataset_type.name}_index{self.input_index}_{self.number_of_sequences}lines_{self.bits_in_one_sequence}bits') + f'{self._cipher_primitive}_{self.dataset_type.name}_index{self.input_index}_{self.number_of_sequences}lines_{self.bits_in_one_sequence}bits_{statistical_test_option_list}test_option_list') try: os.makedirs(self.report_folder) except OSError: @@ -632,12 +601,10 @@ def _generate_nist_dicts(self, dataset, round_start, round_end, statistical_test sts_execution_time = time.time() - sts_execution_time try: shutil.move(nist_local_experiment_folder, report_folder_round) - except OSError as e: - print(f'Error: {e.strerror}') - print( - f'Please remove the existed folder {report_folder_round} ' - f'or indicate another folder for saving the NIST STS reports.') - continue + except OSError: + shutil.rmtree(report_folder_round) + shutil.move(nist_local_experiment_folder, report_folder_round) + self._write_execution_time(f'Compute round {round_number}', sts_execution_time) try: @@ -655,9 +622,9 @@ def _generate_nist_dicts(self, dataset, round_start, round_end, statistical_test print("Finished.") return sts_report_dicts - def generate_chart_for_all_rounds(self, flag_chart, sts_report_dicts): + def _generate_chart_for_all_rounds(self, flag_chart, sts_report_dicts): if flag_chart: try: - self.generate_chart_all(sts_report_dicts, self.report_folder) + self._generate_chart_all(sts_report_dicts, self.report_folder) except OSError: print("Error in generating all round chart.") diff --git a/claasp/components/constant_component.py b/claasp/components/constant_component.py index caa9f7db..11306a57 100644 --- a/claasp/components/constant_component.py +++ b/claasp/components/constant_component.py @@ -21,7 +21,6 @@ from claasp.component import Component from claasp.cipher_modules.models.sat.utils import constants from claasp.cipher_modules.models.smt.utils import utils as smt_utils -from claasp.cipher_modules.models.milp.utils import utils as milp_utils from claasp.cipher_modules.code_generator import constant_to_bitstring diff --git a/claasp/components/modsub_component.py b/claasp/components/modsub_component.py index 2b033c86..f5efc3ff 100644 --- a/claasp/components/modsub_component.py +++ b/claasp/components/modsub_component.py @@ -1,4 +1,3 @@ - # **************************************************************************** # Copyright 2023 Technology Innovation Institute # @@ -39,6 +38,80 @@ def __init__(self, current_round_number, current_round_number_of_components, super().__init__(current_round_number, current_round_number_of_components, input_id_links, input_bit_positions, output_bit_size, 'modsub', modulus) + def algebraic_polynomials(self, model): + """ + Return a list of polynomials representing Modular subtraction operation + + INPUT: + + - ``model`` -- **model object**; a model instance + + EXAMPLES:: + + sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel + sage: from claasp.cipher import Cipher + sage: cipher = Cipher("cipher_name", "permutation", ["input"], [8], 8) + sage: cipher.add_round() + sage: modsub_0_0 = cipher.add_MODSUB_component(["input","input"], [[0,1,2,3],[4,5,6,7]], 4) + sage: modsub_component = cipher.get_component_from_id('modsub_0_0') + sage: algebraic = AlgebraicModel(cipher) + sage: modsub_component.algebraic_polynomials(algebraic) + [modsub_0_0_b0_0, + modsub_0_0_b0_0 + modsub_0_0_y0 + modsub_0_0_x4 + modsub_0_0_x0, + modsub_0_0_x4*modsub_0_0_b0_0 + modsub_0_0_x0*modsub_0_0_b0_0 + modsub_0_0_x0*modsub_0_0_x4 + modsub_0_0_b0_1 + modsub_0_0_b0_0 + modsub_0_0_x4, + modsub_0_0_b0_1 + modsub_0_0_y1 + modsub_0_0_x5 + modsub_0_0_x1, + modsub_0_0_x5*modsub_0_0_b0_1 + modsub_0_0_x1*modsub_0_0_b0_1 + modsub_0_0_x1*modsub_0_0_x5 + modsub_0_0_b0_2 + modsub_0_0_b0_1 + modsub_0_0_x5, + modsub_0_0_b0_2 + modsub_0_0_y2 + modsub_0_0_x6 + modsub_0_0_x2, + modsub_0_0_x6*modsub_0_0_b0_2 + modsub_0_0_x2*modsub_0_0_b0_2 + modsub_0_0_x2*modsub_0_0_x6 + modsub_0_0_b0_3 + modsub_0_0_b0_2 + modsub_0_0_x6, + modsub_0_0_b0_3 + modsub_0_0_y3 + modsub_0_0_x7 + modsub_0_0_x3] + + """ + component_id = self.id + ninput_words = self.description[1] + nsubtractions = ninput_words - 1 + ninput_bits = self.input_bit_size + noutput_bits = word_size = self.output_bit_size + + input_vars = [component_id + "_" + model.input_postfix + str(i) for i in range(ninput_bits)] + output_vars = [component_id + "_" + model.output_postfix + str(i) for i in range(noutput_bits)] + borrows_vars = [[component_id + "_" + "b" + str(n) + "_" + str(i) for i in range(word_size)] for n in + range(nsubtractions)] + aux_outputs_vars = [[component_id + "_" + "o" + str(n) + "_" + str(i) for i in range(word_size)] for n in + range(nsubtractions - 1)] + + ring_R = model.ring() + input_vars = list(map(ring_R, input_vars)) + output_vars = list(map(ring_R, output_vars)) + borrows_vars = [list(map(ring_R, borrow_vars)) for borrow_vars in borrows_vars] + aux_outputs_vars = [list(map(ring_R, aux_output_vars)) for aux_output_vars in aux_outputs_vars] + + def borrow_polynomial(xi, yi, bi): + return xi * yi + yi + bi * (xi + yi + 1) + + polynomials = [] + for n in range(nsubtractions): # z = (x - y) % 2^word_size + if n == 0: + x = input_vars[:word_size] + else: + x = aux_outputs_vars[n - 1] + + if n == nsubtractions - 1: + z = output_vars + else: + z = aux_outputs_vars[n] + + y = input_vars[(n + 1) * word_size: (n + 2) * word_size] + b = borrows_vars[n] + + polynomials += [b[0] + 0] + polynomials += [x[0] + y[0] + z[0] + b[0]] + + for i in range(1, word_size): + polynomials += [b[i] + borrow_polynomial(x[i - 1], y[i - 1], b[i - 1])] + polynomials += [x[i] + y[i] + z[i] + b[i]] + + return polynomials + def cms_constraints(self): """ Return a list of variables and a list of clauses for Modular Subtraction in CMS CIPHER model. @@ -197,7 +270,7 @@ def sat_constraints(self): input_bit_ids[output_bit_len + i], temp_carry_bit_ids[i])) constraints.extend(sat_utils.cnf_equivalent([temp_input_bit_ids[output_bit_len - 1], - input_bit_ids[2 * output_bit_len - 1]])) + input_bit_ids[2 * output_bit_len - 1]])) # carries for i in range(output_bit_len - 2): constraints.extend(sat_utils.cnf_carry(carry_bit_ids[i], @@ -260,7 +333,7 @@ def smt_constraints(self): # carries complement 2 for i in range(output_bit_len - 2): operation = smt_utils.smt_and((smt_utils.smt_not(input_bit_ids[output_bit_len + i + 1]), - temp_carry_bit_ids[i + 1])) + temp_carry_bit_ids[i + 1])) equation = smt_utils.smt_equivalent((temp_carry_bit_ids[i], operation)) constraints.append(smt_utils.smt_assert(equation)) distinction = smt_utils.smt_distinct(temp_carry_bit_ids[output_bit_len - 2], @@ -270,11 +343,11 @@ def smt_constraints(self): # results complement 2 for i in range(output_bit_len - 1): operation = smt_utils.smt_xor((smt_utils.smt_not(input_bit_ids[output_bit_len + i]), - temp_carry_bit_ids[i])) + temp_carry_bit_ids[i])) equation = smt_utils.smt_equivalent((temp_input_bit_ids[i], operation)) constraints.append(smt_utils.smt_assert(equation)) equation = smt_utils.smt_equivalent((temp_input_bit_ids[output_bit_len - 1], - input_bit_ids[2 * output_bit_len - 1])) + input_bit_ids[2 * output_bit_len - 1])) constraints.append(smt_utils.smt_assert(equation)) # carries @@ -285,7 +358,7 @@ def smt_constraints(self): equation = smt_utils.smt_equivalent((carry_bit_ids[i], operation)) constraints.append(smt_utils.smt_assert(equation)) operation = smt_utils.smt_and((input_bit_ids[output_bit_len - 1], - temp_input_bit_ids[output_bit_len - 1])) + temp_input_bit_ids[output_bit_len - 1])) equation = smt_utils.smt_equivalent((carry_bit_ids[output_bit_len - 2], operation)) constraints.append(smt_utils.smt_assert(equation)) @@ -295,7 +368,7 @@ def smt_constraints(self): equation = smt_utils.smt_equivalent((output_bit_ids[i], operation)) constraints.append(smt_utils.smt_assert(equation)) operation = smt_utils.smt_xor((input_bit_ids[output_bit_len - 1], - temp_input_bit_ids[output_bit_len - 1])) + temp_input_bit_ids[output_bit_len - 1])) equation = smt_utils.smt_equivalent((output_bit_ids[output_bit_len - 1], operation)) constraints.append(smt_utils.smt_assert(equation)) diff --git a/claasp/components/modular_component.py b/claasp/components/modular_component.py index 05a9a4eb..ae55aab4 100644 --- a/claasp/components/modular_component.py +++ b/claasp/components/modular_component.py @@ -1178,7 +1178,7 @@ def twoterms_milp_probability_xor_linear_constraints(self, binary_variable, inte constraints.append(x[f"{self.id}_chunk_{chunk_number}_dummy_{i}"] + x[input_vars[output_bit_size + i]] + x[input_vars[i]] + x[output_vars[i]] + - x[f"{self.id}_chunk_{chunk_number}_dummy_{i + 1}"] >= - 4) + x[f"{self.id}_chunk_{chunk_number}_dummy_{i + 1}"] <= 4) constraints.append(correlation[f"{self.id}_modadd_probability{chunk_number}"] == sum( x[f"{self.id}_chunk_{chunk_number}_dummy_{i}"] for i in range(output_bit_size))) diff --git a/claasp/components/xor_component.py b/claasp/components/xor_component.py index 30f6bcb3..a4218d60 100644 --- a/claasp/components/xor_component.py +++ b/claasp/components/xor_component.py @@ -601,13 +601,7 @@ def milp_xor_linear_constraints(self, model): for i in range(number_of_inputs): for j in range(output_bit_size): - input_component_id = input_vars[output_bit_size * i + j].rsplit('_', 1)[0] - if input_component_id in model.cipher.inputs: - constraints.append(x[ind_output_vars[j]] == x[ind_input_vars[output_bit_size * i + j]]) - else: - input_component = model.cipher.get_component_from_id(input_component_id) - if CONSTANT not in input_component.type: - constraints.append(x[ind_output_vars[j]] == x[ind_input_vars[output_bit_size * i + j]]) + constraints.append(x[ind_output_vars[j]] == x[ind_input_vars[output_bit_size * i + j]]) return variables, constraints diff --git a/claasp/utils/utils.py b/claasp/utils/utils.py index 86191020..005a9517 100644 --- a/claasp/utils/utils.py +++ b/claasp/utils/utils.py @@ -369,6 +369,9 @@ def pprint_dictionary_to_file(dictionary, name_file): sage: import os sage: os.remove(f"{tii_dir_path}/test_json") """ + + if 'cipher' in dictionary.keys(): + dictionary['cipher'] = dictionary['cipher'].id dictionary_json = json.loads(str(dictionary).replace("'", '"')) source_file = open(name_file, 'w') print(json.dumps(dictionary_json, indent=4), file=source_file) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 0ea75287..2cc1bf73 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -161,9 +161,11 @@ Configuring PyCharm to use Python from within a container involves setting up a 4. **Configure Project Interpreter**: - Go to **Preferences** > **Project: [Your Project Name]** > **Python Interpreter**. - - Click on the gear icon and select **Add**. + - In the right corner click **Add Interpreter**. - In the left-hand pane of the Add Python Interpreter dialog, select **On Docker**. - - Specify the docker image. You can choose your local image CLAASP or the public one from `hub.docker.com` called `tiicrc/claasp-lib` + - Specify the docker image. You can choose one of "build" or "Pull or use existing": + - If your option is "build" then you need to fill the field "Dockerfile" with `docker/Dockerfile` + - If your option is "Pull or use existing" then you need to fill the field "image tag" with `claasp:latest` - PyCharm will attempt to find the Python interpreter in the created image. You may need to specify the path to the Python executable if PyCharm cannot locate it automatically (commonly `/usr/bin/python3` or similar). 5. **Apply and Save Changes**: Click **OK** to save your new interpreter settings. diff --git a/tests/benchmark/cipher_test.py b/tests/benchmark/cipher_test.py index ef8604d0..a5f16870 100644 --- a/tests/benchmark/cipher_test.py +++ b/tests/benchmark/cipher_test.py @@ -4,18 +4,13 @@ from claasp.ciphers.block_ciphers.aes_block_cipher import AESBlockCipher from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher -speck = SpeckBlockCipher() -aes = AESBlockCipher() - - -@pytest.mark.parametrize("number_of_samples", [10, 100, 1000, 10000]) -def test_diffusion_tests_with_speck_cipher(benchmark, number_of_samples): - benchmark(speck.diffusion_tests, number_of_samples=number_of_samples) +from claasp.cipher_modules.continuous_diffusion_analysis import ContinuousDiffusionAnalysis +from claasp.cipher_modules.avalanche_tests import AvalancheTests +from claasp.cipher_modules.neural_network_tests import NeuralNetworkTests -@pytest.mark.parametrize("number_of_samples", [10, 100, 1000, 10000]) -def test_diffusion_tests_with_aes_cipher(benchmark, number_of_samples): - benchmark(aes.diffusion_tests, number_of_samples=number_of_samples) +speck = SpeckBlockCipher() +aes = AESBlockCipher() def test_evaluate_with_speck_cipher(benchmark): @@ -50,18 +45,11 @@ def test_evaluate_vectorized_with_aes_cipher(benchmark, cipher_input): benchmark(aes.evaluate_vectorized, cipher_input) -@pytest.mark.parametrize("nb_samples", [10, 100]) -@pytest.mark.parametrize("hidden_layers", [[32, 32, 32], [64, 64, 64]]) -@pytest.mark.parametrize("number_of_epochs", [1, 10, 100]) -def test_neural_network_blackbox_distinguisher_tests_with_speck_cipher(benchmark, nb_samples, - hidden_layers, number_of_epochs): - benchmark(speck.neural_network_blackbox_distinguisher_tests, nb_samples, hidden_layers, - number_of_epochs) +@pytest.mark.parametrize("number_of_samples", [10, 100, 1000, 10000]) +def test_avalanche_tests_with_speck_cipher(benchmark, number_of_samples): + benchmark(AvalancheTests(speck).avalanche_tests, number_of_samples=number_of_samples) -@pytest.mark.parametrize("nb_samples", [10, 100]) -@pytest.mark.parametrize("hidden_layers", [[32, 32, 32], [64, 64, 64]]) -@pytest.mark.parametrize("number_of_epochs", [1, 10, 100]) -def test_neural_network_blackbox_distinguisher_tests_with_aes_cipher(benchmark, nb_samples, - hidden_layers, number_of_epochs): - benchmark(aes.neural_network_blackbox_distinguisher_tests, nb_samples, hidden_layers, number_of_epochs) +@pytest.mark.parametrize("number_of_samples", [10, 100, 1000, 10000]) +def test_avalanche_tests_with_aes_cipher(benchmark, number_of_samples): + benchmark(AvalancheTests(aes).avalanche_tests, number_of_samples=number_of_samples) diff --git a/tests/benchmark/statistical_tests_test.py b/tests/benchmark/statistical_tests_test.py index 1a88805b..787a0a7b 100644 --- a/tests/benchmark/statistical_tests_test.py +++ b/tests/benchmark/statistical_tests_test.py @@ -2,17 +2,17 @@ from claasp.ciphers.block_ciphers.aes_block_cipher import AESBlockCipher from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher -from claasp.cipher_modules.statistical_tests.nist_statistical_tests import StatisticalTests +from claasp.cipher_modules.statistical_tests.nist_statistical_tests import NISTStatisticalTests speck = SpeckBlockCipher() aes = AESBlockCipher() def test_run_avalanche_nist_statistics_test_with_speck_cipher(benchmark): - tests = StatisticalTests(speck) - benchmark(tests.run_avalanche_nist_statistics_test, 0, 10, 10) + tests = NISTStatisticalTests(speck) + benchmark(tests.nist_statistical_tests, test_type='avalanche') def test_run_avalanche_nist_statistics_test_with_aes_cipher(benchmark): - tests = StatisticalTests(aes) - benchmark(tests.run_avalanche_nist_statistics_test, 0, 10, 10) + tests = NISTStatisticalTests(aes) + benchmark(tests.nist_statistical_tests, test_type='avalanche') diff --git a/tests/unit/cipher_modules/continuous_diffusion_analysis_test.py b/tests/unit/cipher_modules/continuous_diffusion_analysis_test.py index c226768e..b7d69b9a 100644 --- a/tests/unit/cipher_modules/continuous_diffusion_analysis_test.py +++ b/tests/unit/cipher_modules/continuous_diffusion_analysis_test.py @@ -16,7 +16,7 @@ def test_continuous_tests_report(): speck = SpeckBlockCipher(number_of_rounds=2) cda = ContinuousDiffusionAnalysis(speck) cda_for_repo = cda.continuous_diffusion_tests() - cda_repo = Report(speck, cda_for_repo) + cda_repo = Report(cda_for_repo) cda_repo.save_as_image() diff --git a/tests/unit/cipher_modules/models/cp/cp_models/cp_deterministic_truncated_xor_differential_model_test.py b/tests/unit/cipher_modules/models/cp/cp_models/cp_deterministic_truncated_xor_differential_model_test.py index a376ade8..ccd604aa 100644 --- a/tests/unit/cipher_modules/models/cp/cp_models/cp_deterministic_truncated_xor_differential_model_test.py +++ b/tests/unit/cipher_modules/models/cp/cp_models/cp_deterministic_truncated_xor_differential_model_test.py @@ -40,7 +40,7 @@ def test_find_all_deterministic_truncated_xor_differential_trail(): assert len(trail) == 4 for i in range(len(trail)): - assert trail[i]['cipher_id'] == 'speck_p32_k64_o32_r3' + assert str(trail[i]['cipher']) == 'speck_p32_k64_o32_r3' assert trail[i]['model_type'] == 'deterministic_truncated_xor_differential' assert trail[i]['model_type'] == 'deterministic_truncated_xor_differential' assert trail[i]['solver_name'] == 'Chuffed' @@ -54,7 +54,7 @@ def test_find_one_deterministic_truncated_xor_differential_trail(): key = set_fixed_variables(component_id='key', constraint_type='equal', bit_positions=range(64), bit_values=[0] * 64) trail = cp.find_one_deterministic_truncated_xor_differential_trail(1, [plaintext, key], 'Chuffed') - assert trail[0]['cipher_id'] == 'speck_p32_k64_o32_r1' + assert str(trail[0]['cipher']) == 'speck_p32_k64_o32_r1' assert trail[0]['components_values']['key']['value'] == '000000000000000000000000000000000000000000000000000000' \ '0000000000' diff --git a/tests/unit/cipher_modules/models/cp/cp_models/cp_impossible_xor_differential_model_test.py b/tests/unit/cipher_modules/models/cp/cp_models/cp_impossible_xor_differential_model_test.py index ef37121e..69efd9bd 100644 --- a/tests/unit/cipher_modules/models/cp/cp_models/cp_impossible_xor_differential_model_test.py +++ b/tests/unit/cipher_modules/models/cp/cp_models/cp_impossible_xor_differential_model_test.py @@ -41,7 +41,7 @@ def find_one_impossible_xor_differential_trail(): bit_positions=range(64), bit_values=[0] * 64) trail = cp.find_one_impossible_xor_differential_trail(6, [plaintext, ciphertext, key], 'Chuffed', 3) - assert trail['cipher_id'] == 'speck_p32_k64_o32_r6' + assert str(trail['cipher']) == 'speck_p32_k64_o32_r6' assert trail['model_type'] == 'impossible_xor_differential_one_solution' assert trail['solver_name'] == 'Chuffed' diff --git a/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_fixing_number_of_active_sboxes_model_test.py b/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_fixing_number_of_active_sboxes_model_test.py index 3d8028cf..c51bc28e 100644 --- a/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_fixing_number_of_active_sboxes_model_test.py +++ b/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_fixing_number_of_active_sboxes_model_test.py @@ -24,7 +24,7 @@ def test_find_lowest_weight_xor_differential_trail(): set_fixed_variables('plaintext', 'not_equal', range(128), integer_to_bit_list(0, 128, 'little'))] solution = cp.find_lowest_weight_xor_differential_trail(fixed_variables, 'Chuffed', 'Chuffed') - assert solution['cipher_id'] == 'aes_block_cipher_k128_p128_o128_r2' + assert str(solution['cipher']) == 'aes_block_cipher_k128_p128_o128_r2' assert solution['model_type'] == 'xor_differential' assert solution['solver_name'] == 'Chuffed' assert solution['total_weight'] == '30.0' @@ -42,7 +42,7 @@ def test_find_one_xor_differential_trail(): set_fixed_variables('plaintext', 'not_equal', range(128), integer_to_bit_list(0, 128, 'little'))] solution = cp.find_one_xor_differential_trail(fixed_variables, 'Chuffed', 'Chuffed') - assert solution['cipher_id'] == 'aes_block_cipher_k128_p128_o128_r2' + assert str(solution['cipher']) == 'aes_block_cipher_k128_p128_o128_r2' assert solution['model_type'] == 'xor_differential' assert solution['solver_name'] == 'Chuffed' assert eval(solution['total_weight']) >= 0.0 @@ -57,7 +57,7 @@ def test_find_one_xor_differential_trail_with_fixed_weight(): set_fixed_variables('plaintext', 'not_equal', range(128), integer_to_bit_list(0, 128, 'little'))] solution = cp.find_one_xor_differential_trail_with_fixed_weight(224, fixed_variables, 'Chuffed', 'Chuffed') - assert solution['cipher_id'] == 'aes_block_cipher_k128_p128_o128_r2' + assert str(solution['cipher']) == 'aes_block_cipher_k128_p128_o128_r2' assert solution['model_type'] == 'xor_differential' assert solution['solver_name'] == 'Chuffed' assert eval(solution['total_weight']) == 224.0 @@ -74,7 +74,7 @@ def test_solve_full_two_steps_xor_differential_model(): set_fixed_variables('key', 'not_equal', list(range(128)), integer_to_bit_list(0, 128, 'little'))] constraints = cp.solve_full_two_steps_xor_differential_model('xor_differential_one_solution', -1, fixed_variables, 'Chuffed', 'Chuffed') - assert constraints['cipher_id'] == 'aes_block_cipher_k128_p128_o128_r2' + assert str(constraints['cipher']) == 'aes_block_cipher_k128_p128_o128_r2' assert eval('0x' + constraints['components_values']['intermediate_output_0_35']['value']) >= 0 assert constraints['components_values']['intermediate_output_0_35']['weight'] == 0 assert eval('0x' + constraints['components_values']['xor_0_36']['value']) >= 0 diff --git a/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_model_test.py b/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_model_test.py index 3a135b7c..16007bf1 100644 --- a/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_model_test.py +++ b/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_model_test.py @@ -1,5 +1,5 @@ from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher -from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list +from claasp.cipher_modules.models.utils import set_fixed_variables from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_model import ( CpXorDifferentialModel, and_xor_differential_probability_ddt) @@ -11,18 +11,14 @@ def test_and_xor_differential_probability_ddt(): def test_find_all_xor_differential_trails_with_fixed_weight(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) cp = CpXorDifferentialModel(speck) - fixed_values = [set_fixed_variables('key', 'equal', list(range(16)), integer_to_bit_list(0, 16, 'big')), - set_fixed_variables('plaintext', 'not_equal', list(range(8)), integer_to_bit_list(0, 8, 'big'))] - trails = cp.find_all_xor_differential_trails_with_fixed_weight(1, fixed_values, 'Chuffed') + trails = cp.find_all_xor_differential_trails_with_fixed_weight(1, solver_name='Chuffed') assert len(trails) == 6 def test_solving_unsatisfiability(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) cp = CpXorDifferentialModel(speck) - fixed_values = [set_fixed_variables('key', 'equal', list(range(16)), integer_to_bit_list(0, 16, 'big')), - set_fixed_variables('plaintext', 'not_equal', list(range(8)), integer_to_bit_list(0, 8, 'big'))] - trails = cp.find_one_xor_differential_trail_with_fixed_weight(1, fixed_values, 'Chuffed') + trails = cp.find_one_xor_differential_trail_with_fixed_weight(1, solver_name='Chuffed') assert trails['status'] == 'UNSATISFIABLE' @@ -30,9 +26,7 @@ def test_solving_unsatisfiability(): def test_find_all_xor_differential_trails_with_weight_at_most(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) cp = CpXorDifferentialModel(speck) - fixed_values = [set_fixed_variables('key', 'equal', list(range(16)), integer_to_bit_list(0, 16, 'big')), - set_fixed_variables('plaintext', 'not_equal', list(range(8)), integer_to_bit_list(0, 8, 'big'))] - trails = cp.find_all_xor_differential_trails_with_weight_at_most(0, 1, fixed_values, 'Chuffed') + trails = cp.find_all_xor_differential_trails_with_weight_at_most(0, 1, solver_name='Chuffed') assert len(trails) == 7 @@ -40,11 +34,9 @@ def test_find_all_xor_differential_trails_with_weight_at_most(): def test_find_lowest_weight_xor_differential_trail(): speck = SpeckBlockCipher(number_of_rounds=5) cp = CpXorDifferentialModel(speck) - fixed_values = [set_fixed_variables('key', 'equal', list(range(64)), integer_to_bit_list(0, 64, 'big')), - set_fixed_variables('plaintext', 'not_equal', list(range(32)), integer_to_bit_list(0, 32, 'big'))] - trail = cp.find_lowest_weight_xor_differential_trail(fixed_values, 'Chuffed') + trail = cp.find_lowest_weight_xor_differential_trail(solver_name='Chuffed') - assert trail['cipher_id'] == 'speck_p32_k64_o32_r5' + assert str(trail['cipher']) == 'speck_p32_k64_o32_r5' assert trail['total_weight'] == '9.0' assert eval('0x' + trail['components_values']['cipher_output_4_12']['value']) >= 0 assert trail['components_values']['cipher_output_4_12']['weight'] == 0 @@ -57,7 +49,7 @@ def test_find_one_xor_differential_trail(): bit_positions=range(32), bit_values=[0] * 32) trail = cp.find_one_xor_differential_trail([plaintext], 'Chuffed') - assert trail['cipher_id'] == 'speck_p32_k64_o32_r2' + assert str(trail['cipher']) == 'speck_p32_k64_o32_r2' assert trail['model_type'] == 'xor_differential_one_solution' assert eval('0x' + trail['components_values']['cipher_output_1_12']['value']) >= 0 assert trail['components_values']['cipher_output_1_12']['weight'] == 0 @@ -71,7 +63,7 @@ def test_find_one_xor_differential_trail_with_fixed_weight(): bit_positions=range(32), bit_values=[0] * 32) trail = cp.find_one_xor_differential_trail_with_fixed_weight(9, [plaintext], 'Chuffed') - assert trail['cipher_id'] == 'speck_p32_k64_o32_r5' + assert str(trail['cipher']) == 'speck_p32_k64_o32_r5' assert trail['model_type'] == 'xor_differential_one_solution' assert eval('0x' + trail['components_values']['intermediate_output_0_5']['value']) >= 0 assert trail['components_values']['intermediate_output_0_5']['weight'] == 0 diff --git a/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_linear_model_test.py b/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_linear_model_test.py index 4206afe5..04a25dd0 100644 --- a/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_linear_model_test.py +++ b/tests/unit/cipher_modules/models/cp/cp_models/cp_xor_linear_model_test.py @@ -14,11 +14,8 @@ def test_and_xor_linear_probability_lat(): def test_final_xor_linear_constraints(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - speck = speck.remove_key_schedule() cp = CpXorLinearModel(speck) - fixed_variables = [ - set_fixed_variables('plaintext', 'not_equal', list(range(32)), integer_to_bit_list(0, 32, 'little'))] - cp.build_xor_linear_trail_model(-1, fixed_variables) + cp.build_xor_linear_trail_model(-1) assert cp.final_xor_linear_constraints(-1)[:-1] == \ ['solve:: int_search(p, smallest, indomain_min, complete) minimize sum(p);'] @@ -26,35 +23,26 @@ def test_final_xor_linear_constraints(): def test_find_all_xor_linear_trails_with_fixed_weight(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=3) - speck = speck.remove_key_schedule() cp = CpXorLinearModel(speck) - fixed_variables = [ - set_fixed_variables('plaintext', 'not_equal', list(range(8)), integer_to_bit_list(0, 8, 'little'))] - trails = cp.find_all_xor_linear_trails_with_fixed_weight(1, fixed_variables) + trails = cp.find_all_xor_linear_trails_with_fixed_weight(1) assert len(trails) == 12 def test_find_all_xor_linear_trails_with_weight_at_most(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=3) - speck = speck.remove_key_schedule() cp = CpXorLinearModel(speck) - fixed_variables = [ - set_fixed_variables('plaintext', 'not_equal', list(range(8)), integer_to_bit_list(0, 8, 'little'))] - trails = cp.find_all_xor_linear_trails_with_weight_at_most(0, 1, fixed_variables) + trails = cp.find_all_xor_linear_trails_with_weight_at_most(0, 1) assert len(trails) == 13 def test_find_lowest_weight_xor_linear_trail(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - speck = speck.remove_key_schedule() cp = CpXorLinearModel(speck) - fixed_variables = [ - set_fixed_variables('plaintext', 'not_equal', list(range(32)), integer_to_bit_list(0, 32, 'little'))] - trail = cp.find_lowest_weight_xor_linear_trail(fixed_variables) + trail = cp.find_lowest_weight_xor_linear_trail() - assert trail['cipher_id'] == 'speck_p32_k64_o32_r4' + assert str(trail['cipher']) == 'speck_p32_k64_o32_r4' assert eval('0x' + trail['components_values']['cipher_output_3_12_o']['value']) >= 0 assert trail['components_values']['cipher_output_3_12_o']['weight'] == 0 assert trail['total_weight'] == '3.0' @@ -62,13 +50,10 @@ def test_find_lowest_weight_xor_linear_trail(): def test_find_one_xor_linear_trail(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - speck = speck.remove_key_schedule() cp = CpXorLinearModel(speck) - fixed_variables = [ - set_fixed_variables('plaintext', 'not_equal', list(range(32)), integer_to_bit_list(0, 32, 'little'))] - trail = cp.find_one_xor_linear_trail(fixed_variables) + trail = cp.find_one_xor_linear_trail() - assert trail['cipher_id'] == 'speck_p32_k64_o32_r4' + assert str(trail['cipher']) == 'speck_p32_k64_o32_r4' assert trail['components_values']['plaintext']['weight'] == 0 assert eval('0x' + trail['components_values']['plaintext']['value']) > 0 assert trail['components_values']['cipher_output_3_12_o']['weight'] == 0 @@ -78,12 +63,9 @@ def test_find_one_xor_linear_trail(): def test_find_one_xor_linear_trail_with_fixed_weight(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - speck = speck.remove_key_schedule() cp = CpXorLinearModel(speck) - fixed_variables = [ - set_fixed_variables('plaintext', 'not_equal', list(range(32)), integer_to_bit_list(0, 32, 'little'))] - trail = cp.find_one_xor_linear_trail_with_fixed_weight(3, fixed_variables) - assert trail['cipher_id'] == 'speck_p32_k64_o32_r4' + trail = cp.find_one_xor_linear_trail_with_fixed_weight(3) + assert str(trail['cipher']) == 'speck_p32_k64_o32_r4' assert trail['model_type'] == 'xor_linear_one_solution' assert trail['total_weight'] == '3.0' diff --git a/tests/unit/cipher_modules/models/milp/milp_model_test.py b/tests/unit/cipher_modules/models/milp/milp_model_test.py index 802a1672..e4548e21 100644 --- a/tests/unit/cipher_modules/models/milp/milp_model_test.py +++ b/tests/unit/cipher_modules/models/milp/milp_model_test.py @@ -87,7 +87,7 @@ def test_solve(): milp.add_constraints_to_build_in_sage_milp_class() differential_solution = milp.solve("xor_differential") - assert differential_solution['cipher_id'] == 'speck_p32_k64_o32_r4' + assert str(differential_solution['cipher']) == 'speck_p32_k64_o32_r4' assert differential_solution['model_type'] == 'xor_differential' assert differential_solution['components_values']['key']['weight'] == 0 assert differential_solution['components_values']['modadd_0_1']['weight'] >= 0 @@ -99,7 +99,7 @@ def test_solve(): milp.add_constraints_to_build_in_sage_milp_class() linear_solution = milp.solve("xor_linear") - assert linear_solution['cipher_id'] == 'speck_p32_k64_o32_r4' + assert str(linear_solution['cipher']) == 'speck_p32_k64_o32_r4' assert linear_solution['model_type'] == 'xor_linear' assert differential_solution['components_values']['key']['weight'] == 0 assert linear_solution['components_values']['modadd_1_7_i']['weight'] >= 0 diff --git a/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_differential_model_test.py b/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_differential_model_test.py index 7f15e831..3f23a827 100644 --- a/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_differential_model_test.py +++ b/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_differential_model_test.py @@ -1,4 +1,4 @@ -from claasp.cipher_modules.models.utils import get_single_key_scenario_format_for_fixed_values +from claasp.cipher_modules.models.utils import set_fixed_variables from claasp.ciphers.block_ciphers.present_block_cipher import PresentBlockCipher from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel @@ -8,12 +8,11 @@ def test_find_all_xor_differential_trails_with_fixed_weight(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) milp = MilpXorDifferentialModel(speck) - trail = milp.find_all_xor_differential_trails_with_fixed_weight( - 1, get_single_key_scenario_format_for_fixed_values(speck)) + trail = milp.find_all_xor_differential_trails_with_fixed_weight(1) assert len(trail) == 6 for i in range(len(trail)): - assert trail[i]['cipher_id'] == 'speck_p8_k16_o8_r2' + assert str(trail[i]['cipher']) == 'speck_p8_k16_o8_r2' assert trail[i]['total_weight'] == 1.0 assert eval(trail[i]['components_values']['plaintext']['value']) > 0 assert eval(trail[i]['components_values']['key']['value']) == 0 @@ -26,8 +25,7 @@ def test_find_all_xor_differential_trails_with_fixed_weight(): def test_find_all_xor_differential_trails_with_weight_at_most(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=2) milp = MilpXorDifferentialModel(speck) - trails = milp.find_all_xor_differential_trails_with_weight_at_most( - 0, 1, get_single_key_scenario_format_for_fixed_values(speck)) + trails = milp.find_all_xor_differential_trails_with_weight_at_most(0, 1) assert len(trails) == 7 for i in range(len(trails)): assert trails[i]['total_weight'] <= 1.0 @@ -37,27 +35,24 @@ def test_find_all_xor_differential_trails_with_weight_at_most(): def test_find_lowest_weight_xor_differential_trail(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) milp = MilpXorDifferentialModel(speck) - trail = milp.find_lowest_weight_xor_differential_trail( - get_single_key_scenario_format_for_fixed_values(speck)) + trail = milp.find_lowest_weight_xor_differential_trail() assert trail["total_weight"] == 1.0 speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) milp = MilpXorDifferentialModel(speck, n_window_heuristic=3) - trail = milp.find_lowest_weight_xor_differential_trail( - get_single_key_scenario_format_for_fixed_values(speck)) + trail = milp.find_lowest_weight_xor_differential_trail() assert trail["total_weight"] == 1.0 present = PresentBlockCipher(number_of_rounds=2) milp = MilpXorDifferentialModel(present) - trail = milp.find_lowest_weight_xor_differential_trail( - get_single_key_scenario_format_for_fixed_values(present)) + trail = milp.find_lowest_weight_xor_differential_trail() assert trail["total_weight"] == 4.0 def test_find_one_xor_differential_trail(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) milp = MilpXorDifferentialModel(speck) - trail = milp.find_one_xor_differential_trail(get_single_key_scenario_format_for_fixed_values(speck)) + trail = milp.find_one_xor_differential_trail() assert trail["total_weight"] >= 1.0 tea = TeaBlockCipher(block_bit_size=16, key_bit_size=32, number_of_rounds=2) @@ -74,8 +69,7 @@ def test_find_one_xor_differential_trail(): def test_find_one_xor_differential_trail_with_fixed_weight(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) milp = MilpXorDifferentialModel(speck) - fixed_values = get_single_key_scenario_format_for_fixed_values(speck) - trail = milp.find_one_xor_differential_trail_with_fixed_weight(5, fixed_values) + trail = milp.find_one_xor_differential_trail_with_fixed_weight(5) assert trail["total_weight"] == 5.0 tea = TeaBlockCipher(block_bit_size=16, key_bit_size=32, number_of_rounds=2) @@ -85,5 +79,7 @@ def test_find_one_xor_differential_trail_with_fixed_weight(): # speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) milp = MilpXorDifferentialModel(speck) - trail = milp.find_one_xor_differential_trail_with_fixed_weight(5) + key = set_fixed_variables(component_id='key', constraint_type='not_equal', + bit_positions=range(64), bit_values=[0] * 64) + trail = milp.find_one_xor_differential_trail_with_fixed_weight(5, fixed_values=[key]) assert trail["total_weight"] == 5.0 diff --git a/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_linear_model_test.py b/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_linear_model_test.py index 3e280932..9b4875cb 100644 --- a/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_linear_model_test.py +++ b/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_linear_model_test.py @@ -10,23 +10,21 @@ def test_build_xor_linear_trail_model(): milp.init_model_in_sage_milp_class() milp.build_xor_linear_trail_model() - assert str(milp.model_constraints[0]) == 'x_16 == x_9' - assert str(milp.model_constraints[1]) == 'x_17 == x_10' - assert str(milp.model_constraints[2]) == 'x_18 == x_11' - assert str(milp.model_constraints[23759]) == 'x_12127 == x_12191' - assert str(milp.model_constraints[23760]) == 'x_12128 == x_12192' + assert str(milp.model_constraints[0]) == 'x_0 == x_1' + assert str(milp.model_constraints[1]) == 'x_2 == x_3' + assert str(milp.model_constraints[2]) == 'x_4 == x_5' + assert str(milp.model_constraints[12369]) == 'x_6400 == x_6432' + assert str(milp.model_constraints[12370]) == 'x_6401 == x_6433' def test_find_all_xor_linear_trails_with_fixed_weight(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=3) - milp = MilpXorLinearModel(speck.remove_key_schedule()) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not equal', - bit_positions=range(8), bit_values=integer_to_bit_list(0x0, 8, 'big')) - trails = milp.find_all_xor_linear_trails_with_fixed_weight(1, fixed_values=[plaintext]) + milp = MilpXorLinearModel(speck) + trails = milp.find_all_xor_linear_trails_with_fixed_weight(1) assert len(trails) == 12 for i in range(len(trails)): - assert trails[i]['cipher_id'] == 'speck_p8_k16_o8_r3' + assert str(trails[i]['cipher']) == 'speck_p8_k16_o8_r3' assert trails[i]['total_weight'] == 1.0 assert eval(trails[i]['components_values']['plaintext']['value']) > 0 assert eval(trails[i]['components_values']['key_0_2']['value']) >= 0 @@ -39,14 +37,12 @@ def test_find_all_xor_linear_trails_with_fixed_weight(): def test_find_all_xor_linear_trails_with_weight_at_most(): speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=3) - milp = MilpXorLinearModel(speck.remove_key_schedule()) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not equal', - bit_positions=range(8), bit_values=integer_to_bit_list(0x0, 8, 'big')) - trails = milp.find_all_xor_linear_trails_with_weight_at_most(0, 1, [plaintext]) + milp = MilpXorLinearModel(speck) + trails = milp.find_all_xor_linear_trails_with_weight_at_most(0, 1) assert len(trails) == 13 for i in range(len(trails)): - assert trails[i]['cipher_id'] == 'speck_p8_k16_o8_r3' + assert str(trails[i]['cipher']) == 'speck_p8_k16_o8_r3' assert trails[i]['total_weight'] <= 1.0 assert trails[i]['total_weight'] >= 0.0 assert eval(trails[i]['components_values']['plaintext']['value']) > 0 @@ -60,28 +56,26 @@ def test_find_all_xor_linear_trails_with_weight_at_most(): def test_find_lowest_weight_xor_linear_trail(): # speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=9) - # milp = MilpXorLinearModel(speck.remove_key_schedule()) + # milp = MilpXorLinearModel(speck) # plaintext = set_fixed_variables(component_id='plaintext', constraint_type='equal', bit_positions=range(32), # bit_values=integer_to_bit_list(0x03805224, 32, 'big')) # trail = milp.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) # assert trail["total_weight"] == 14.0 # simon = SimonBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=13) - # milp = MilpXorLinearModel(simon.remove_key_schedule()) + # milp = MilpXorLinearModel(simon) # plaintext = set_fixed_variables(component_id='plaintext', constraint_type='equal', bit_positions=range(32), # bit_values=integer_to_bit_list(0x00200000, 32, 'big')) # trail = milp.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) # assert trail["total_weight"] == 18.0 # - speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=3) - milp = MilpXorLinearModel(speck.remove_key_schedule()) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not equal', bit_positions=range(32), - bit_values=integer_to_bit_list(0x0, 32, 'big')) - trail = milp.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) - assert trail["total_weight"] == 1.0 + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) + milp = MilpXorLinearModel(speck) + trail = milp.find_lowest_weight_xor_linear_trail() + assert trail["total_weight"] == 3.0 # # present = PresentBlockCipher(number_of_rounds=3) - # milp = MilpXorLinearModel(present.remove_key_schedule()) + # milp = MilpXorLinearModel(present) # plaintext = set_fixed_variables(component_id='plaintext', constraint_type='equal', bit_positions=range(64), # bit_values=integer_to_bit_list(0x0d00000000000000, 64, 'big')) # trail = milp.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) @@ -90,7 +84,7 @@ def test_find_lowest_weight_xor_linear_trail(): def test_find_one_xor_linear_trail(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck.remove_key_schedule()) + milp = MilpXorLinearModel(speck) plaintext = set_fixed_variables(component_id='plaintext', constraint_type='equal', bit_positions=range(32), bit_values=integer_to_bit_list(0x03805224, 32, 'big')) trail = milp.find_one_xor_linear_trail(fixed_values=[plaintext]) @@ -99,19 +93,19 @@ def test_find_one_xor_linear_trail(): def test_find_one_xor_linear_trail_with_fixed_weight(): # speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - # milp = MilpXorLinearModel(speck.remove_key_schedule()) + # milp = MilpXorLinearModel(speck) # trail = milp.find_one_xor_linear_trail_with_fixed_weight(6) # assert len(trail) == 9 # assert trail["total_weight"] == 6.0 speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck.remove_key_schedule()) + milp = MilpXorLinearModel(speck) trail = milp.find_one_xor_linear_trail_with_fixed_weight(1) assert len(trail) == 10 assert trail["total_weight"] == 1.0 # # speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) - # milp = MilpXorLinearModel(speck.remove_key_schedule()) + # milp = MilpXorLinearModel(speck) # trail = milp.find_one_xor_linear_trail_with_fixed_weight(10) # assert len(trail) == 9 # assert trail["total_weight"] == 10.0 @@ -119,7 +113,7 @@ def test_find_one_xor_linear_trail_with_fixed_weight(): def test_find_one_xor_linear_trail_with_fixed_weight_with_external_solver(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck.remove_key_schedule()) + milp = MilpXorLinearModel(speck) trail = milp.find_one_xor_linear_trail_with_fixed_weight(1, external_solver_name="glpk") assert len(trail) == 10 assert trail["total_weight"] == 1.0 @@ -128,20 +122,20 @@ def test_find_one_xor_linear_trail_with_fixed_weight_with_external_solver(): def test_find_one_xor_linear_trail_with_fixed_weight_with_supported_but_not_installed_external_solver(): with pytest.raises(Exception) as e_info: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck.remove_key_schedule()) + milp = MilpXorLinearModel(speck) trail = milp.find_one_xor_linear_trail_with_fixed_weight(1, external_solver_name="cplex") def test_find_one_xor_linear_trail_with_fixed_weight_with_installed_external_solver_but_missing_license(): with pytest.raises(Exception) as e_info: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck.remove_key_schedule()) + milp = MilpXorLinearModel(speck) trail = milp.find_one_xor_linear_trail_with_fixed_weight(1, external_solver_name="Gurobi") def test_find_one_xor_linear_trail_with_fixed_weight_with_unsupported_external_solver(): with pytest.raises(Exception) as e_info: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck.remove_key_schedule()) + milp = MilpXorLinearModel(speck) trail = milp.find_one_xor_linear_trail_with_fixed_weight(1, external_solver_name="unsupported_solver") diff --git a/tests/unit/cipher_modules/models/models_utils_test.py b/tests/unit/cipher_modules/models/models_utils_test.py index eaf88158..0a7330e7 100644 --- a/tests/unit/cipher_modules/models/models_utils_test.py +++ b/tests/unit/cipher_modules/models/models_utils_test.py @@ -55,7 +55,7 @@ def test_to_bias_for_xor_linear_trail(): trail = milp.find_lowest_weight_xor_linear_trail([plaintext]) solution = to_bias_for_xor_linear_trail(speck, trail) - assert solution['cipher_id'] == 'speck_p32_k64_o32_r4' + assert str(solution['cipher']) == 'speck_p32_k64_o32_r4' assert solution['total_weight'] == 4.0 assert solution['measure'] == 'bias' @@ -68,7 +68,7 @@ def test_to_probability_for_xor_linear_trail(): trail = milp.find_lowest_weight_xor_linear_trail([plaintext]) solution = to_probability_for_xor_linear_trail(speck, trail) - assert solution['cipher_id'] == 'speck_p32_k64_o32_r4' + assert str(solution['cipher']) == 'speck_p32_k64_o32_r4' assert solution['measure'] == 'probability' assert solution['total_weight'] == 0.83 @@ -81,7 +81,7 @@ def test_to_correlation_for_xor_linear_trail(): trail = milp.find_lowest_weight_xor_linear_trail([plaintext]) solution = to_correlation_for_xor_linear_trail(speck, trail) - assert solution['cipher_id'] == 'speck_p32_k64_o32_r4' + assert str(solution['cipher']) == 'speck_p32_k64_o32_r4' assert solution['measure'] == 'correlation' assert solution['total_weight'] == 3.0 diff --git a/tests/unit/cipher_modules/models/sat/sat_model_test.py b/tests/unit/cipher_modules/models/sat/sat_model_test.py index 40c4b06f..221761ed 100644 --- a/tests/unit/cipher_modules/models/sat/sat_model_test.py +++ b/tests/unit/cipher_modules/models/sat/sat_model_test.py @@ -14,7 +14,7 @@ def test_solve(): sat = SatCipherModel(tea) sat.build_cipher_model() solution = sat.solve('cipher', solver_name='cryptominisat') - assert solution['cipher_id'] == 'tea_p64_k128_o64_r32' + assert str(solution['cipher']) == 'tea_p64_k128_o64_r32' assert solution['solver_name'] == 'cryptominisat' assert eval('0x' + solution['components_values']['modadd_0_3']['value']) >= 0 assert eval('0x' + solution['components_values']['cipher_output_31_16']['value']) >= 0 @@ -23,7 +23,7 @@ def test_solve(): sat = SatCipherModel(simon) sat.build_cipher_model() solution = sat.solve('cipher', solver_name='cryptominisat_sage') - assert solution['cipher_id'] == 'simon_p32_k64_o32_r32' + assert str(solution['cipher']) == 'simon_p32_k64_o32_r32' assert solution['solver_name'] == 'cryptominisat' assert eval('0x' + solution['components_values']['rot_0_3']['value']) >= 0 assert eval('0x' + solution['components_values']['cipher_output_31_13']['value']) >= 0 diff --git a/tests/unit/cipher_modules/models/sat/sat_models/sat_cipher_model_test.py b/tests/unit/cipher_modules/models/sat/sat_models/sat_cipher_model_test.py index 8af3bbd3..48808515 100644 --- a/tests/unit/cipher_modules/models/sat/sat_models/sat_cipher_model_test.py +++ b/tests/unit/cipher_modules/models/sat/sat_models/sat_cipher_model_test.py @@ -14,7 +14,7 @@ def test_find_missing_bits(): missing_bits = sat.find_missing_bits(fixed_values=[ciphertext]) - assert missing_bits['cipher_id'] == 'speck_p32_k64_o32_r22' + assert str(missing_bits['cipher']) == 'speck_p32_k64_o32_r22' assert missing_bits['model_type'] == 'cipher' assert missing_bits['solver_name'] == 'cryptominisat' assert missing_bits['components_values'][cipher_output_id] == {'value': '1234abcd'} diff --git a/tests/unit/cipher_modules/models/sat/sat_models/sat_xor_differential_model_test.py b/tests/unit/cipher_modules/models/sat/sat_models/sat_xor_differential_model_test.py index 323c63ce..a0b9b763 100644 --- a/tests/unit/cipher_modules/models/sat/sat_models/sat_xor_differential_model_test.py +++ b/tests/unit/cipher_modules/models/sat/sat_models/sat_xor_differential_model_test.py @@ -4,50 +4,39 @@ from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list from claasp.cipher_modules.models.sat.sat_models.sat_xor_differential_model import SatXorDifferentialModel +speck_5rounds = SpeckBlockCipher(number_of_rounds=5) def test_find_all_xor_differential_trails_with_fixed_weight(): speck = SpeckBlockCipher(number_of_rounds=5) sat = SatXorDifferentialModel(speck, window_size_weight_pr_vars=1) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=(0,) * 32) - key = set_fixed_variables(component_id='key', constraint_type='equal', - bit_positions=range(64), bit_values=(0,) * 64) assert int(sat.find_all_xor_differential_trails_with_fixed_weight( - 9, fixed_values=[plaintext, key])[0]['total_weight']) == int(9.0) + 9)[0]['total_weight']) == int(9.0) def test_find_all_xor_differential_trails_with_weight_at_most(): - speck = SpeckBlockCipher(number_of_rounds=5) + speck = speck_5rounds sat = SatXorDifferentialModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=integer_to_bit_list(0, 32, 'big')) - key = set_fixed_variables(component_id='key', constraint_type='equal', bit_positions=range(64), - bit_values=integer_to_bit_list(0, 64, 'big')) - trails = sat.find_all_xor_differential_trails_with_weight_at_most(9, 10, fixed_values=[plaintext, key]) + trails = sat.find_all_xor_differential_trails_with_weight_at_most(9, 10) assert len(trails) == 28 def test_find_lowest_weight_xor_differential_trail(): - speck = SpeckBlockCipher(number_of_rounds=5) + speck = speck_5rounds sat = SatXorDifferentialModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=(0,) * 32) - key = set_fixed_variables(component_id='key', constraint_type='equal', - bit_positions=range(64), bit_values=(0,) * 64) - trail = sat.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) + trail = sat.find_lowest_weight_xor_differential_trail() assert int(trail['total_weight']) == int(9.0) def test_find_one_xor_differential_trail(): - speck = SpeckBlockCipher(number_of_rounds=5) + speck = speck_5rounds sat = SatXorDifferentialModel(speck) plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', bit_positions=range(32), bit_values=integer_to_bit_list(0, 32, 'big')) trail = sat.find_one_xor_differential_trail(fixed_values=[plaintext]) - assert trail['cipher_id'] == 'speck_p32_k64_o32_r5' + assert str(trail['cipher']) == 'speck_p32_k64_o32_r5' assert trail['model_type'] == 'xor_differential' assert trail['solver_name'] == 'cryptominisat' assert trail['status'] == 'SATISFIABLE' @@ -60,11 +49,7 @@ def test_find_one_xor_differential_trail(): def test_find_one_xor_differential_trail_with_fixed_weight(): speck = SpeckBlockCipher(number_of_rounds=3) sat = SatXorDifferentialModel(speck, window_size_by_round=[0, 0, 0]) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=(0,) * 32) - key = set_fixed_variables(component_id='key', constraint_type='equal', - bit_positions=range(64), bit_values=(0,) * 64) - result = sat.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[plaintext, key]) + result = sat.find_one_xor_differential_trail_with_fixed_weight(3) assert int(result['total_weight']) == int(3.0) @@ -76,11 +61,7 @@ def test_find_one_xor_differential_trail_with_fixed_weight_and_window_heuristic_ for component_id in filtered_objects: dict_of_window_heuristic_per_component[component_id] = 0 sat = SatXorDifferentialModel(speck, window_size_by_component_id=dict_of_window_heuristic_per_component) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=(0,) * 32) - key = set_fixed_variables(component_id='key', constraint_type='equal', - bit_positions=range(64), bit_values=(0,) * 64) - result = sat.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[plaintext, key]) + result = sat.find_one_xor_differential_trail_with_fixed_weight(3) assert int(result['total_weight']) == int(3.0) @@ -88,68 +69,51 @@ def test_build_xor_differential_trail_model_fixed_weight_and_parkissat(): number_of_cores = 2 speck = SpeckBlockCipher(number_of_rounds=3) sat = SatXorDifferentialModel(speck) - plaintext = set_fixed_variables( - component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=integer_to_bit_list(0, 32, 'big')) - key = set_fixed_variables( - component_id='key', - constraint_type='equal', - bit_positions=range(64), - bit_values=(0,) * 64) - sat.build_xor_differential_trail_model(3, fixed_variables=[plaintext, key]) + sat.build_xor_differential_trail_model(3) result = sat._solve_with_external_sat_solver("xor_differential", "parkissat", [f'-c={number_of_cores}']) assert int(result['total_weight']) == int(3.0) -def test_differential_in_related_key_scenario_speck3264(): - def repeat_input_difference(input_difference_, number_of_samples_, number_of_bytes_): - bytes_array = input_difference_.to_bytes(number_of_bytes_, 'big') - np_array = np.array(list(bytes_array), dtype=np.uint8) - column_array = np_array.reshape(-1, 1) - return np.tile(column_array, (1, number_of_samples_)) +def repeat_input_difference(input_difference_, number_of_samples_, number_of_bytes_): + bytes_array = input_difference_.to_bytes(number_of_bytes_, 'big') + np_array = np.array(list(bytes_array), dtype=np.uint8) + column_array = np_array.reshape(-1, 1) + return np.tile(column_array, (1, number_of_samples_)) +def test_differential_in_related_key_scenario_speck3264(): rng = np.random.default_rng(seed=42) - number_of_samples = 2**22 - input_difference = 0x2a14001 - output_difference = 0x850a810a - key_difference = 0x2800020000800001 + number_of_samples = 2**23 + input_difference = 0x00402000 + output_difference = 0x0 + key_difference = 0x000afa3d0030a000 input_difference_data = repeat_input_difference(input_difference, number_of_samples, 4) output_difference_data = repeat_input_difference(output_difference, number_of_samples, 4) key_difference_data = repeat_input_difference(key_difference, number_of_samples, 8) - speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=7) key_data = rng.integers(low=0, high=256, size=(8, number_of_samples), dtype=np.uint8) - plaintext_data1 = rng.integers(low=0, high=256, size=(4, number_of_samples), dtype=np.uint8) plaintext_data2 = plaintext_data1 ^ input_difference_data - ciphertext1 = speck.evaluate_vectorized([plaintext_data1, key_data]) - ciphertext2 = speck.evaluate_vectorized([plaintext_data2, key_data ^ key_difference_data]) - total = np.sum(ciphertext1[0] ^ ciphertext2[0] == output_difference_data.T) + ciphertext1 = speck_5rounds.evaluate_vectorized([plaintext_data1, key_data]) + ciphertext2 = speck_5rounds.evaluate_vectorized([plaintext_data2, key_data ^ key_difference_data]) + rows_all_true = np.all((ciphertext1[0] ^ ciphertext2[0] == output_difference_data.T), axis=1) + total = np.count_nonzero(rows_all_true) import math - total_prob_weight = math.log(total, 2) - assert 18 > total_prob_weight > 12 + total_prob_weight = math.log(total/number_of_samples, 2) + assert 21 > abs(total_prob_weight) > 12 def test_differential_in_single_key_scenario_speck3264(): - def repeat_input_difference(input_difference_, number_of_samples_, number_of_bytes_): - bytes_array = input_difference_.to_bytes(number_of_bytes_, 'big') - np_array = np.array(list(bytes_array), dtype=np.uint8) - column_array = np_array.reshape(-1, 1) - return np.tile(column_array, (1, number_of_samples_)) - rng = np.random.default_rng(seed=42) - number_of_samples = 2**22 + number_of_samples = 2**23 input_difference = 0x20400040 output_difference = 0x106040E0 input_difference_data = repeat_input_difference(input_difference, number_of_samples, 4) output_difference_data = repeat_input_difference(output_difference, number_of_samples, 4) - speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=5) key_data = rng.integers(low=0, high=256, size=(8, number_of_samples), dtype=np.uint8) plaintext_data1 = rng.integers(low=0, high=256, size=(4, number_of_samples), dtype=np.uint8) plaintext_data2 = plaintext_data1 ^ input_difference_data - ciphertext1 = speck.evaluate_vectorized([plaintext_data1, key_data]) - ciphertext2 = speck.evaluate_vectorized([plaintext_data2, key_data]) - total = np.sum(ciphertext1[0] ^ ciphertext2[0] == output_difference_data.T) + ciphertext1 = speck_5rounds.evaluate_vectorized([plaintext_data1, key_data]) + ciphertext2 = speck_5rounds.evaluate_vectorized([plaintext_data2, key_data]) + rows_all_true = np.all((ciphertext1[0] ^ ciphertext2[0] == output_difference_data.T), axis=1) + total = np.count_nonzero(rows_all_true) import math - total_prob_weight = math.log(total, 2) - assert 21 > total_prob_weight > 13 \ No newline at end of file + total_prob_weight = math.log(total/number_of_samples, 2) + assert 21 > abs(total_prob_weight) > 13 \ No newline at end of file diff --git a/tests/unit/cipher_modules/models/sat/sat_models/sat_xor_linear_model_test.py b/tests/unit/cipher_modules/models/sat/sat_models/sat_xor_linear_model_test.py index 6501182d..aa589a85 100644 --- a/tests/unit/cipher_modules/models/sat/sat_models/sat_xor_linear_model_test.py +++ b/tests/unit/cipher_modules/models/sat/sat_models/sat_xor_linear_model_test.py @@ -15,35 +15,29 @@ def test_branch_xor_linear_constraints(): assert constraints[-2] == '-xor_2_10_15_o cipher_output_2_12_31_i' assert constraints[-1] == 'xor_2_10_15_o -cipher_output_2_12_31_i' - def test_find_all_xor_linear_trails_with_weight_at_most(): - speck = SpeckBlockCipher(number_of_rounds=3) - sat = SatXorLinearModel(speck.remove_key_schedule()) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=integer_to_bit_list(0, 32, 'big')) - trails = sat.find_all_xor_linear_trails_with_weight_at_most(0, 2, fixed_values=[plaintext]) + speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) + sat = SatXorLinearModel(speck) + key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + trails = sat.find_all_xor_linear_trails_with_weight_at_most(0, 3, fixed_values=[key]) - assert len(trails) == 187 + assert len(trails) == 73 def test_find_lowest_weight_xor_linear_trail(): - speck = SpeckBlockCipher(number_of_rounds=3) + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) sat = SatXorLinearModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=integer_to_bit_list(0, 32, 'big')) - trail = sat.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) + trail = sat.find_lowest_weight_xor_linear_trail() - assert trail['total_weight'] == 2.0 + assert trail['total_weight'] == 3.0 def test_find_one_xor_linear_trail(): speck = SpeckBlockCipher(number_of_rounds=4) sat = SatXorLinearModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=integer_to_bit_list(0, 32, 'big')) - trail = sat.find_one_xor_linear_trail(fixed_values=[plaintext]) + trail = sat.find_one_xor_linear_trail() - assert trail['cipher_id'] == 'speck_p32_k64_o32_r4' + assert str(trail['cipher']) == 'speck_p32_k64_o32_r4' assert trail['model_type'] == 'xor_linear' assert trail['solver_name'] == 'cryptominisat' assert trail['status'] == 'SATISFIABLE' @@ -52,9 +46,7 @@ def test_find_one_xor_linear_trail(): def test_find_one_xor_linear_trail_with_fixed_weight(): speck = SpeckBlockCipher(number_of_rounds=3) sat = SatXorLinearModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', constraint_type='not_equal', - bit_positions=range(32), bit_values=(0,) * 32) - result = sat.find_one_xor_linear_trail_with_fixed_weight(7, fixed_values=[plaintext]) + result = sat.find_one_xor_linear_trail_with_fixed_weight(7) assert result['total_weight'] == 7.0 diff --git a/tests/unit/cipher_modules/models/smt/smt_models/smt_cipher_model_test.py b/tests/unit/cipher_modules/models/smt/smt_models/smt_cipher_model_test.py index 193abf64..2514a004 100644 --- a/tests/unit/cipher_modules/models/smt/smt_models/smt_cipher_model_test.py +++ b/tests/unit/cipher_modules/models/smt/smt_models/smt_cipher_model_test.py @@ -14,7 +14,7 @@ def test_find_missing_bits(): missing_bits = smt.find_missing_bits(fixed_values=[ciphertext]) - assert missing_bits['cipher_id'] == 'speck_p32_k64_o32_r22' + assert str(missing_bits['cipher']) == 'speck_p32_k64_o32_r22' assert missing_bits['model_type'] == 'cipher' assert missing_bits['solver_name'] == 'z3' assert missing_bits['components_values'][cipher_output_id] == {'value': '1234abcd'} diff --git a/tests/unit/cipher_modules/models/smt/smt_models/smt_xor_differential_model_test.py b/tests/unit/cipher_modules/models/smt/smt_models/smt_xor_differential_model_test.py index 8dee7fe9..311e3c14 100644 --- a/tests/unit/cipher_modules/models/smt/smt_models/smt_xor_differential_model_test.py +++ b/tests/unit/cipher_modules/models/smt/smt_models/smt_xor_differential_model_test.py @@ -1,47 +1,26 @@ from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher -from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list from claasp.cipher_modules.models.smt.smt_models.smt_xor_differential_model import SmtXorDifferentialModel def test_find_all_xor_differential_trails_with_weight_at_most(): speck = SpeckBlockCipher(number_of_rounds=5) smt = SmtXorDifferentialModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=integer_to_bit_list(0, 32, 'big')) - key = set_fixed_variables(component_id='key', - constraint_type='equal', - bit_positions=range(64), - bit_values=integer_to_bit_list(0, 64, 'big')) - trails = smt.find_all_xor_differential_trails_with_weight_at_most(9, 10, fixed_values=[plaintext, key]) + trails = smt.find_all_xor_differential_trails_with_weight_at_most(9, 10) assert len(trails) == 28 def test_find_lowest_weight_xor_differential_trail(): speck = SpeckBlockCipher(number_of_rounds=5) smt = SmtXorDifferentialModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=integer_to_bit_list(0, 32, 'big')) - key = set_fixed_variables(component_id='key', - constraint_type='equal', - bit_positions=range(64), - bit_values=integer_to_bit_list(0, 64, 'big')) - trail = smt.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) + trail = smt.find_lowest_weight_xor_differential_trail() assert trail['total_weight'] == 9.0 def test_find_one_xor_differential_trail(): speck = SpeckBlockCipher(number_of_rounds=5) smt = SmtXorDifferentialModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=integer_to_bit_list(0, 32, 'big')) - solution = smt.find_one_xor_differential_trail(fixed_values=[plaintext]) - assert solution['cipher_id'] == 'speck_p32_k64_o32_r5' + solution = smt.find_one_xor_differential_trail() + assert str(solution['cipher']) == 'speck_p32_k64_o32_r5' assert solution['solver_name'] == 'z3' assert eval('0x' + solution['components_values']['intermediate_output_0_6']['value']) >= 0 assert solution['components_values']['intermediate_output_0_6']['weight'] == 0 @@ -52,13 +31,5 @@ def test_find_one_xor_differential_trail(): def test_find_one_xor_differential_trail_with_fixed_weight(): speck = SpeckBlockCipher(number_of_rounds=3) smt = SmtXorDifferentialModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=(0,) * 32) - key = set_fixed_variables(component_id='key', - constraint_type='equal', - bit_positions=range(64), - bit_values=(0,) * 64) - result = smt.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[plaintext, key]) + result = smt.find_one_xor_differential_trail_with_fixed_weight(3) assert result['total_weight'] == 3.0 diff --git a/tests/unit/cipher_modules/models/smt/smt_models/smt_xor_linear_model_test.py b/tests/unit/cipher_modules/models/smt/smt_models/smt_xor_linear_model_test.py index 99c0ffb6..d706f129 100644 --- a/tests/unit/cipher_modules/models/smt/smt_models/smt_xor_linear_model_test.py +++ b/tests/unit/cipher_modules/models/smt/smt_models/smt_xor_linear_model_test.py @@ -4,36 +4,26 @@ def test_find_all_xor_linear_trails_with_weight_at_most(): - speck = SpeckBlockCipher(number_of_rounds=3) + speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) smt = SmtXorLinearModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=integer_to_bit_list(0, 32, 'big')) - trails = smt.find_all_xor_linear_trails_with_weight_at_most(2, 3, fixed_values=[plaintext]) - assert len(trails) == 11 + key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) + trails = smt.find_all_xor_linear_trails_with_weight_at_most(0, 3, fixed_values=[key]) + + assert len(trails) == 73 def test_find_lowest_weight_xor_linear_trail(): speck = SpeckBlockCipher(number_of_rounds=3) smt = SmtXorLinearModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=integer_to_bit_list(0, 32, 'big')) - trail = smt.find_lowest_weight_xor_linear_trail(fixed_values=[plaintext]) - assert trail['total_weight'] == 2.0 + trail = smt.find_lowest_weight_xor_linear_trail() + assert trail['total_weight'] == 1.0 def test_find_one_xor_linear_trail(): speck = SpeckBlockCipher(number_of_rounds=4) smt = SmtXorLinearModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=integer_to_bit_list(0, 32, 'big')) - solution = smt.find_one_xor_linear_trail(fixed_values=[plaintext]) - assert solution['cipher_id'] == 'speck_p32_k64_o32_r4' + solution = smt.find_one_xor_linear_trail() + assert str(solution['cipher']) == 'speck_p32_k64_o32_r4' assert solution['solver_name'] == 'z3' assert eval('0x' + solution['components_values']['modadd_0_1_i']['value']) >= 0 assert solution['components_values']['modadd_0_1_i']['weight'] == 0 @@ -46,11 +36,7 @@ def test_find_one_xor_linear_trail(): def test_find_one_xor_linear_trail_with_fixed_weight(): speck = SpeckBlockCipher(number_of_rounds=3) smt = SmtXorLinearModel(speck) - plaintext = set_fixed_variables(component_id='plaintext', - constraint_type='not_equal', - bit_positions=range(32), - bit_values=(0,) * 32) - result = smt.find_one_xor_linear_trail_with_fixed_weight(7, fixed_values=[plaintext]) + result = smt.find_one_xor_linear_trail_with_fixed_weight(7) assert result['total_weight'] == 7.0 diff --git a/tests/unit/cipher_modules/neural_network_tests_test.py b/tests/unit/cipher_modules/neural_network_tests_test.py index b2d0840b..696cc291 100644 --- a/tests/unit/cipher_modules/neural_network_tests_test.py +++ b/tests/unit/cipher_modules/neural_network_tests_test.py @@ -47,13 +47,14 @@ def test_get_differential_dataset(): def test_neural_network_blackbox_distinguisher_tests(): cipher = SpeckBlockCipher(number_of_rounds=5) results = NeuralNetworkTests(cipher).neural_network_blackbox_distinguisher_tests(nb_samples=10) - assert results['input_parameters'] == {'number_of_samples': 10, 'hidden_layers': [32, 32, 32], 'number_of_epochs': 10, 'test_name': 'neural_network_blackbox_distinguisher_tests'} + assert results['input_parameters'] == {'cipher': cipher, 'number_of_samples': 10, 'hidden_layers': [32, 32, 32], 'number_of_epochs': 10, 'test_name': 'neural_network_blackbox_distinguisher_tests'} def test_neural_network_differential_distinguisher_tests(): cipher = SpeckBlockCipher(number_of_rounds=5) results = NeuralNetworkTests(cipher).neural_network_differential_distinguisher_tests(nb_samples=10) - assert results['input_parameters'] == {'test_name': 'neural_network_differential_distinguisher_tests', + assert results['input_parameters'] == {'cipher': cipher, + 'test_name': 'neural_network_differential_distinguisher_tests', 'number_of_samples': 10, 'input_differences': [[4194304], [10]], 'hidden_layers': [32, 32, 32], diff --git a/tests/unit/cipher_modules/report_test.py b/tests/unit/cipher_modules/report_test.py index 325ab5c8..f2bb9060 100644 --- a/tests/unit/cipher_modules/report_test.py +++ b/tests/unit/cipher_modules/report_test.py @@ -7,12 +7,13 @@ from claasp.ciphers.block_ciphers.simon_block_cipher import SimonBlockCipher from claasp.cipher_modules.report import Report from claasp.cipher_modules.statistical_tests.dieharder_statistical_tests import DieharderTests -from claasp.cipher_modules.statistical_tests.nist_statistical_tests import StatisticalTests +from claasp.cipher_modules.statistical_tests.nist_statistical_tests import NISTStatisticalTests from claasp.cipher_modules.neural_network_tests import NeuralNetworkTests from claasp.cipher_modules.algebraic_tests import AlgebraicTests from claasp.cipher_modules.avalanche_tests import AvalancheTests from claasp.cipher_modules.component_analysis_tests import CipherComponentsAnalysis + def test_save_as_image(): speck = SpeckBlockCipher(number_of_rounds=2) sat = SatXorDifferentialModel(speck) @@ -28,21 +29,26 @@ def test_save_as_image(): bit_values=(0,) * 64) trail = sat.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) - trail_report = Report(speck, trail) + trail_report = Report(trail) trail_report.save_as_image() avalanche_results = AvalancheTests(speck).avalanche_tests() - avalanche_report = Report(speck, avalanche_results) - avalanche_report.save_as_image() + avalanche_report = Report(avalanche_results) + avalanche_report.save_as_image(test_name='avalanche_weight_vectors', fixed_input='plaintext', fixed_output='round_output', + fixed_input_difference='average') blackbox_results = NeuralNetworkTests(speck).neural_network_blackbox_distinguisher_tests() - blackbox_report = Report(speck, blackbox_results) + blackbox_report = Report(blackbox_results) blackbox_report.save_as_image() - algebraic_results = AlgebraicTests(speck).algebraic_tests(timeout=1) - algebraic_report = Report(speck, algebraic_results) + algebraic_results = AlgebraicTests(speck).algebraic_tests(timeout_in_seconds=1) + algebraic_report = Report(algebraic_results) algebraic_report.save_as_image() + component_analysis = CipherComponentsAnalysis(speck).component_analysis_tests() + report_cca = Report(component_analysis) + report_cca.save_as_image() + def test_save_as_latex_table(): simon = SimonBlockCipher(number_of_rounds=2) @@ -62,16 +68,19 @@ def test_save_as_latex_table(): trail = smt.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) avalanche_test_results = AvalancheTests(simon).avalanche_tests() - avalanche_report = Report(simon, avalanche_test_results) - avalanche_report.save_as_latex_table() + avalanche_report = Report(avalanche_test_results) + avalanche_report.save_as_latex_table(fixed_input='plaintext',fixed_output='round_output',fixed_test='avalanche_weight_vectors') - trail_report = Report(simon, trail) + trail_report = Report(trail) trail_report.save_as_latex_table() + nist = NISTStatisticalTests(simon) + report_sts = Report(nist.nist_statistical_tests('avalanche')) + report_sts.save_as_latex_table() def test_save_as_DataFrame(): speck = SpeckBlockCipher(number_of_rounds=2) - smt = CpXorDifferentialModel(speck) + cp = CpXorDifferentialModel(speck) plaintext = set_fixed_variables( component_id='plaintext', constraint_type='not_equal', @@ -82,26 +91,30 @@ def test_save_as_DataFrame(): constraint_type='equal', bit_positions=range(64), bit_values=(0,) * 64) - trail = smt.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) - - algebraic_results = AlgebraicTests(speck).algebraic_tests(timeout=1) - algebraic_report = Report(speck, algebraic_results) - algebraic_report.save_as_DataFrame() + trail = cp.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) avalanche_results = AvalancheTests(speck).avalanche_tests() - avalanche_report = Report(speck,avalanche_results) - avalanche_report.save_as_DataFrame() + avalanche_report = Report(avalanche_results) + avalanche_report.save_as_DataFrame(fixed_input='plaintext',fixed_output='round_output',fixed_test='avalanche_weight_vectors') - trail_report = Report(speck, trail) + trail_report = Report(trail) trail_report.save_as_DataFrame() + nist = NISTStatisticalTests(speck) + report_sts = Report(nist.nist_statistical_tests('avalanche')) + report_sts.save_as_DataFrame() + def test_save_as_json(): - simon = SimonBlockCipher(number_of_rounds=3) + simon = SimonBlockCipher(number_of_rounds=2) + neural_network_blackbox_distinguisher_tests_results = NeuralNetworkTests( simon).neural_network_blackbox_distinguisher_tests() - blackbox_report = Report(simon, neural_network_blackbox_distinguisher_tests_results) - + blackbox_report = Report(neural_network_blackbox_distinguisher_tests_results) + blackbox_report.save_as_json(fixed_input='plaintext',fixed_output='round_output') + nist = NISTStatisticalTests(simon) + report_sts = Report(nist.nist_statistical_tests('avalanche')) + report_sts.save_as_json() milp = MilpXorDifferentialModel(simon) plaintext = set_fixed_variables( component_id='plaintext', @@ -115,44 +128,56 @@ def test_save_as_json(): bit_values=(0,) * 64) trail = milp.find_lowest_weight_xor_differential_trail(fixed_values=[plaintext, key]) - - trail_report = Report(simon, trail) - - algebraic_results = AlgebraicTests(simon).algebraic_tests(timeout=1) - algebraic_report = Report(simon, algebraic_results) - algebraic_report.save_as_json() - - avalanche_results = AvalancheTests(simon).avalanche_tests() - avalanche_report = Report(simon,avalanche_results) - avalanche_report.save_as_json() - + trail_report = Report(trail) trail_report.save_as_json() - blackbox_report.save_as_json() - + avalanche_results = AvalancheTests(simon).avalanche_tests() + avalanche_report = Report(avalanche_results) + avalanche_report.save_as_json(fixed_input='plaintext',fixed_output='round_output',fixed_test='avalanche_weight_vectors') def test_clean_reports(): simon = SimonBlockCipher(number_of_rounds=2) neural_network_blackbox_distinguisher_tests_results = NeuralNetworkTests( simon).neural_network_blackbox_distinguisher_tests() - blackbox_report = Report(simon, neural_network_blackbox_distinguisher_tests_results) + blackbox_report = Report(neural_network_blackbox_distinguisher_tests_results) blackbox_report.save_as_json() blackbox_report.clean_reports() -def test_show(): +def test_show(): speck = SpeckBlockCipher(number_of_rounds=3) + component_analysis = CipherComponentsAnalysis(speck).component_analysis_tests() - report_cca = Report(speck,component_analysis) + report_cca = Report(component_analysis) report_cca.show() + avalanche_results = AvalancheTests(speck).avalanche_tests() + avalanche_report = Report(avalanche_results) + avalanche_report.show(test_name=None) + avalanche_report.show(test_name='avalanche_weight_vectors', fixed_input_difference=None) + avalanche_report.show(test_name='avalanche_weight_vectors', fixed_input_difference='average') - result = NeuralNetworkTests(speck).run_autond_pipeline(optimizer_samples=10 ** 3, optimizer_generations=1, - training_samples=10 ** 2, testing_samples=10 ** 2, - number_of_epochs=1, verbose=False) - report_autond = Report(speck,result) - report_autond.show() + milp = MilpXorDifferentialModel(speck) + plaintext = set_fixed_variables( + component_id='plaintext', + constraint_type='not_equal', + bit_positions=range(32), + bit_values=(0,) * 32) + key = set_fixed_variables( + component_id='key', + constraint_type='equal', + bit_positions=range(64), + bit_values=(0,) * 64) - avalanche_results = AvalancheTests(speck).avalanche_tests() - avalanche_report = Report(speck,avalanche_results) - avalanche_report.show() \ No newline at end of file + trail = milp.find_one_xor_differential_trail(fixed_values=[plaintext, key]) + trail_report = Report(trail) + trail_report.show() + + nist = NISTStatisticalTests(speck) + report_sts = Report(nist.nist_statistical_tests('avalanche')) + report_sts.show() + + neural_network_tests = NeuralNetworkTests(speck).neural_network_differential_distinguisher_tests() + neural_network_tests_report = Report(neural_network_tests) + neural_network_tests_report.show(fixed_input_difference=None) + neural_network_tests_report.show(fixed_input_difference='0xa') \ No newline at end of file diff --git a/tests/unit/cipher_modules/statistical_tests/dieharder_statistical_tests_test.py b/tests/unit/cipher_modules/statistical_tests/dieharder_statistical_tests_test.py index 5917a65d..e166f02c 100644 --- a/tests/unit/cipher_modules/statistical_tests/dieharder_statistical_tests_test.py +++ b/tests/unit/cipher_modules/statistical_tests/dieharder_statistical_tests_test.py @@ -10,65 +10,48 @@ "Parsing dieharder_test_output.txt is finished." -@pytest.mark.skip("Takes to long") def test_run_dieharder_statistical_tests_tool(): - result = DieharderTests._run_dieharder_statistical_tests_tool(INPUT_DATA_EXAMPLE) + result = DieharderTests._run_dieharder_statistical_tests_tool(INPUT_DATA_EXAMPLE, dieharder_test_option='100') - assert result == TESTS_FINISHED - -@pytest.mark.skip("Takes to long") def test_dieharder_statistical_tests(): - speck = SimonBlockCipher(number_of_rounds=1) + speck = SimonBlockCipher(number_of_rounds=3) dieharder_tests = DieharderTests(speck) - dieharder_avalanche_test_results = dieharder_tests.dieharder_statistical_tests('avalanche') - dieharder_correlation_test_results = dieharder_tests.dieharder_statistical_tests('correlation') - dieharder_random_test_results = dieharder_tests.dieharder_statistical_tests('random') - dieharder_high_density_test_results = dieharder_tests.dieharder_statistical_tests('high_density') - dieharder_low_density_test_results = dieharder_tests.dieharder_statistical_tests('low_density') + dieharder_avalanche_test_results = dieharder_tests.dieharder_statistical_tests('avalanche', dieharder_test_option='100') + dieharder_correlation_test_results = dieharder_tests.dieharder_statistical_tests('correlation', dieharder_test_option='100') + dieharder_random_test_results = dieharder_tests.dieharder_statistical_tests('random', dieharder_test_option='100') + dieharder_high_density_test_results = dieharder_tests.dieharder_statistical_tests('high_density', dieharder_test_option='100') + dieharder_low_density_test_results = dieharder_tests.dieharder_statistical_tests('low_density', dieharder_test_option='100') -@pytest.mark.skip("Takes too long") def test_parse_report(): - result = DieharderTests._run_dieharder_statistical_tests_tool(INPUT_DATA_EXAMPLE) + result = DieharderTests._run_dieharder_statistical_tests_tool(INPUT_DATA_EXAMPLE, dieharder_test_option='100') - assert result == TESTS_FINISHED dictio = DieharderTests._parse_report(OUTPUT_TXT) - assert dictio == OUTPUT_TXT_IS_FINISHED -@pytest.mark.skip("Takes too long") def test_generate_chart_round(): - result = DieharderTests._run_dieharder_statistical_tests_tool(INPUT_DATA_EXAMPLE) + result = DieharderTests._run_dieharder_statistical_tests_tool(INPUT_DATA_EXAMPLE, dieharder_test_option='100') - assert result == TESTS_FINISHED dictio = DieharderTests._parse_report(OUTPUT_TXT) - assert dictio == OUTPUT_TXT_IS_FINISHED - dictio['data_type'] = 'random' dictio['cipher_name'] = 'toy_cipher' dictio['round'] = 1 dictio['rounds'] = 1 chart = DieharderTests._generate_chart_round(dictio) - assert chart == "Drawing round 1 is in progress.\n" \ - "Drawing round 1 is finished. Please find the chart in file " \ - "dieharder_random_toy_cipher_round_1.png." -@pytest.mark.skip("Takes too long") def test_generate_chart_all(): - result = DieharderTests._run_dieharder_statistical_tests_tool(INPUT_DATA_EXAMPLE) + result = DieharderTests._run_dieharder_statistical_tests_tool(INPUT_DATA_EXAMPLE, dieharder_test_option='100') - assert result == TESTS_FINISHED dictio = DieharderTests._parse_report(OUTPUT_TXT) - assert dictio == OUTPUT_TXT_IS_FINISHED dictio['data_type'] = 'random' dictio['cipher_name'] = 'toy_cipher' @@ -77,7 +60,4 @@ def test_generate_chart_all(): dict_list = [dictio] chart = DieharderTests._generate_chart_all(dict_list) - assert chart == "Drawing chart for all rounds is in progress.\n" \ - "Drawing chart for all rounds is in finished. Please find the chart in file " \ - "dieharder_random_toy_cipher.png." diff --git a/tests/unit/cipher_modules/statistical_tests/nist_statistical_tests_test.py b/tests/unit/cipher_modules/statistical_tests/nist_statistical_tests_test.py index e75ad710..36c7e779 100644 --- a/tests/unit/cipher_modules/statistical_tests/nist_statistical_tests_test.py +++ b/tests/unit/cipher_modules/statistical_tests/nist_statistical_tests_test.py @@ -3,7 +3,7 @@ from io import StringIO import pytest from claasp.ciphers.block_ciphers.simon_block_cipher import SimonBlockCipher -from claasp.cipher_modules.statistical_tests.nist_statistical_tests import StatisticalTests +from claasp.cipher_modules.statistical_tests.nist_statistical_tests import NISTStatisticalTests REPORT_EXAMPLE_TXT = 'claasp/cipher_modules/statistical_tests/finalAnalysisReportExample.txt' @@ -12,14 +12,14 @@ def test_run_nist_statistical_tests_tool(): if os.path.exists('test_reports/statistical_tests/experiments'): os.removedirs('test_reports/statistical_tests/experiments') os.makedirs('test_reports/statistical_tests/experiments') - result = StatisticalTests._run_nist_statistical_tests_tool( + result = NISTStatisticalTests._run_nist_statistical_tests_tool( 'claasp/cipher_modules/statistical_tests/input_data_example', 10000, 10, 1) assert result is True def test_parse_report(): - dictio = StatisticalTests._parse_report(REPORT_EXAMPLE_TXT) + dictio = NISTStatisticalTests._parse_report(REPORT_EXAMPLE_TXT) assert dictio['number_of_sequences_threshold'] == [{'total': 10, 'passed': 8}, {'total': 8, 'passed': 7}] assert dictio['randomness_test'][0]['test_id'] == 1 @@ -27,7 +27,7 @@ def test_parse_report(): def test_generate_chart_round(): - dictio = StatisticalTests._parse_report(REPORT_EXAMPLE_TXT) + dictio = NISTStatisticalTests._parse_report(REPORT_EXAMPLE_TXT) dictio['data_type'] = 'random' dictio['cipher_name'] = 'toy_cipher' dictio['round'] = 1 @@ -36,7 +36,7 @@ def test_generate_chart_round(): old_stdout = sys.stdout result = StringIO() sys.stdout = result - StatisticalTests.generate_chart_round(dictio) + NISTStatisticalTests._generate_chart_round(dictio) sys.stdout = old_stdout assert result.getvalue() == \ @@ -45,7 +45,7 @@ def test_generate_chart_round(): def test_generate_chart_all(): - dictio = StatisticalTests._parse_report(REPORT_EXAMPLE_TXT) + dictio = NISTStatisticalTests._parse_report(REPORT_EXAMPLE_TXT) dictio['data_type'] = 'random' dictio['cipher_name'] = 'toy_cipher' dictio['round'] = 1 @@ -55,12 +55,12 @@ def test_generate_chart_all(): old_stdout = sys.stdout result = StringIO() sys.stdout = result - StatisticalTests.generate_chart_all(dict_list) + NISTStatisticalTests._generate_chart_all(dict_list) sys.stdout = old_stdout def test_run_avalanche_nist_statistics_test(): - tests = StatisticalTests(SimonBlockCipher(number_of_rounds=1)) + tests = NISTStatisticalTests(SimonBlockCipher(number_of_rounds=1)) old_stdout = sys.stdout result = StringIO() sys.stdout = result @@ -70,7 +70,7 @@ def test_run_avalanche_nist_statistics_test(): assert return_str.find('Finished.') == len(return_str) - 10 def test_run_correlation_nist_statistics_test(): - tests = StatisticalTests(SimonBlockCipher(number_of_rounds=1)) + tests = NISTStatisticalTests(SimonBlockCipher(number_of_rounds=1)) old_stdout = sys.stdout result = StringIO() sys.stdout = result @@ -80,9 +80,8 @@ def test_run_correlation_nist_statistics_test(): assert return_str.find('Finished.') == len(return_str) - 10 @pytest.mark.skip("Takes too long") - def test_run_CBC_nist_statistics_test(): - tests = StatisticalTests(SimonBlockCipher(number_of_rounds=1)) + tests = NISTStatisticalTests(SimonBlockCipher(number_of_rounds=1)) old_stdout = sys.stdout result = StringIO() sys.stdout = result @@ -93,7 +92,7 @@ def test_run_CBC_nist_statistics_test(): def test_run_random_nist_statistics_test(): - tests = StatisticalTests(SimonBlockCipher(number_of_rounds=1)) + tests = NISTStatisticalTests(SimonBlockCipher(number_of_rounds=1)) old_stdout = sys.stdout result = StringIO() sys.stdout = result @@ -103,7 +102,7 @@ def test_run_random_nist_statistics_test(): assert return_str.find('Finished.') == len(return_str) - 10 def test_run_low_density_nist_statistics_test(): - tests = StatisticalTests(SimonBlockCipher(number_of_rounds=1)) + tests = NISTStatisticalTests(SimonBlockCipher(number_of_rounds=1)) old_stdout = sys.stdout result = StringIO() sys.stdout = result @@ -113,7 +112,7 @@ def test_run_low_density_nist_statistics_test(): assert return_str.find('Finished.') == len(return_str) - 10 def test_run_high_density_nist_statistics_test(): - tests = StatisticalTests(SimonBlockCipher(number_of_rounds=1)) + tests = NISTStatisticalTests(SimonBlockCipher(number_of_rounds=1)) old_stdout = sys.stdout result = StringIO() sys.stdout = result diff --git a/tests/unit/cipher_test.py b/tests/unit/cipher_test.py index c6d3123a..06d03617 100644 --- a/tests/unit/cipher_test.py +++ b/tests/unit/cipher_test.py @@ -49,40 +49,41 @@ def test_algebraic_tests(): - toyspn = ToySPN1(number_of_rounds=2) - d = AlgebraicTests(toyspn).algebraic_tests(30) + d = AlgebraicTests(toyspn).algebraic_tests(10) assert d == { - 'input_parameters': {'cipher.id': 'toyspn1_p6_k6_o6_r2', 'timeout': 30, 'test_name': 'algebraic_tests'}, + 'input_parameters': {'cipher': toyspn, 'timeout_in_seconds': 10, 'test_name': 'algebraic_tests'}, 'test_results': {'number_of_variables': [66, 126], 'number_of_equations': [76, 158], 'number_of_monomials': [96, 186], 'max_degree_of_equations': [2, 2], - 'test_passed': [False, False]}} - - speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - d = AlgebraicTests(speck).algebraic_tests(5) - assert d == { - 'input_parameters': {'cipher.id': 'speck_p32_k64_o32_r2', 'timeout': 5, 'test_name': 'algebraic_tests'}, - 'test_results': {'number_of_variables': [304, 800], - 'number_of_equations': [240, 688], - 'number_of_monomials': [304, 800], - 'max_degree_of_equations': [1, 1], - 'test_passed': [False, False]}} - - aes = AESBlockCipher(word_size=4, state_size=2, number_of_rounds=2) + 'test_passed': [False, True]}} + + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=1) + d = AlgebraicTests(speck).algebraic_tests(1) + assert d == {'input_parameters': {'cipher': speck, + 'timeout_in_seconds': 1, + 'test_name': 'algebraic_tests'}, + 'test_results': {'number_of_variables': [320], + 'number_of_equations': [272], + 'number_of_monomials': [365], + 'max_degree_of_equations': [2], + 'test_passed': [True]}} + + aes = AESBlockCipher(word_size=4, state_size=2, number_of_rounds=1) d = AlgebraicTests(aes).algebraic_tests(5) - compare_result = {'input_parameters': {'cipher.id': 'aes_block_cipher_k16_p16_o16_r2', - 'timeout': 5, - 'test_name': 'algebraic_tests'}, - 'test_results': {'number_of_variables': [352, 592], - 'number_of_equations': [454, 796], - 'number_of_monomials': [520, 928], - 'max_degree_of_equations': [2, 2], - 'test_passed': [False, False]}} + compare_result = {'input_parameters': {'cipher': aes, + 'timeout_in_seconds': 5, + 'test_name': 'algebraic_tests'}, + 'test_results': {'number_of_variables': [320], + 'number_of_equations': [390], + 'number_of_monomials': [488], + 'max_degree_of_equations': [2], + 'test_passed': [False]}} assert d == compare_result + def test_delete_generated_evaluate_c_shared_library(): file_c = open(FANCY_EVALUATE_C_FILE, 'a') file_o = open(FANCY_EVALUATE_O_FILE, 'a') @@ -160,6 +161,7 @@ def test_generate_bit_based_c_code(): bit_based_c_code = SpeckBlockCipher().generate_bit_based_c_code(True, True) assert '\tprintf("\\nROUND 0\\n\\n");\n' in bit_based_c_code + def test_generate_word_based_c_code(): word_based_c_code = SpeckBlockCipher().generate_word_based_c_code(20) assert word_based_c_code[:8] == '#include' @@ -229,6 +231,7 @@ def test_is_spn(): aes = AESBlockCipher(number_of_rounds=2) assert aes.is_spn() is True + def test_polynomial_system(): assert str(IdentityBlockCipher().polynomial_system()) == 'Polynomial Sequence with 128 Polynomials in 256 Variables' @@ -365,12 +368,14 @@ def test_print_as_python_dictionary(): } """ + def test_inputs_size_to_dict(): speck = SpeckBlockCipher(number_of_rounds=1, key_bit_size=64, block_bit_size=32) input_sizes = speck.inputs_size_to_dict() assert input_sizes['key'] == 64 assert input_sizes['plaintext'] == 32 + def test_vector_check(): speck = SpeckBlockCipher(number_of_rounds=22) key1 = 0x1918111009080100 diff --git a/tests/unit/components/modsub_component_test.py b/tests/unit/components/modsub_component_test.py index e2098707..e38a5356 100644 --- a/tests/unit/components/modsub_component_test.py +++ b/tests/unit/components/modsub_component_test.py @@ -1,5 +1,26 @@ from claasp.ciphers.block_ciphers.raiden_block_cipher import RaidenBlockCipher from claasp.cipher_modules.models.cp.cp_model import CpModel +from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel +from claasp.cipher import Cipher + + +def test_algebraic_polynomials(): + cipher = Cipher("cipher_name", "permutation", ["input"], [8], 8) + cipher.add_round() + cipher.add_MODSUB_component(["input", "input"], [[0, 1, 2, 3], [4, 5, 6, 7]], 4) + modsub_component = cipher.get_component_from_id('modsub_0_0') + algebraic = AlgebraicModel(cipher) + algebraic_polynomials = modsub_component.algebraic_polynomials(algebraic) + + assert str(algebraic_polynomials[0]) == "modsub_0_0_b0_0" + assert str(algebraic_polynomials[1]) == "modsub_0_0_b0_0 + modsub_0_0_y0 + modsub_0_0_x4 + modsub_0_0_x0" + assert str(algebraic_polynomials[2]) == "modsub_0_0_x4*modsub_0_0_b0_0 + modsub_0_0_x0*modsub_0_0_b0_0 + " \ + "modsub_0_0_x0*modsub_0_0_x4 + modsub_0_0_b0_1 + modsub_0_0_b0_0 + " \ + "modsub_0_0_x4" + assert str(algebraic_polynomials[-2]) == "modsub_0_0_x6*modsub_0_0_b0_2 + modsub_0_0_x2*modsub_0_0_b0_2 + " \ + "modsub_0_0_x2*modsub_0_0_x6 + modsub_0_0_b0_3 + modsub_0_0_b0_2 + " \ + "modsub_0_0_x6" + assert str(algebraic_polynomials[-1]) == "modsub_0_0_b0_3 + modsub_0_0_y3 + modsub_0_0_x7 + modsub_0_0_x3" def test_cms_constraints(): @@ -39,9 +60,10 @@ def test_cp_xor_differential_propagation_constraints(): modsub_component = raiden.component_from(0, 7) cp_model = CpModel(raiden) output_bit_ids, constraints = modsub_component.cp_xor_differential_propagation_constraints(cp_model) - + assert output_bit_ids[0] == 'array[0..31] of var 0..1: pre_modsub_0_7_0;' - assert output_bit_ids[-1] == 'array[0..31] of var 0..1: eq_modsub_0_7 = Eq(Shi_pre_modsub_0_7_1, Shi_pre_modsub_0_7_0, Shi_modsub_0_7);' + assert output_bit_ids[ + -1] == 'array[0..31] of var 0..1: eq_modsub_0_7 = Eq(Shi_pre_modsub_0_7_1, Shi_pre_modsub_0_7_0, Shi_modsub_0_7);' assert constraints[0] == 'constraint pre_modsub_0_7_0[0] = modadd_0_4[0];' assert constraints[1] == 'constraint pre_modsub_0_7_0[1] = modadd_0_4[1];'