diff --git a/.github/workflows/build-claasp-base-image.yaml b/.github/workflows/build-claasp-base-image.yaml index 5e03924f..911891cb 100644 --- a/.github/workflows/build-claasp-base-image.yaml +++ b/.github/workflows/build-claasp-base-image.yaml @@ -1,7 +1,6 @@ name: Build and push image for testing on: - pull_request: - types: [ closed ] + push: branches: - main diff --git a/.github/workflows/build-main-webapp-image.yaml b/.github/workflows/build-main-webapp-image.yaml index b90bc6ca..116e5512 100644 --- a/.github/workflows/build-main-webapp-image.yaml +++ b/.github/workflows/build-main-webapp-image.yaml @@ -1,7 +1,6 @@ name: Build and push image from main on: - pull_request: - types: [ closed ] + push: branches: - main diff --git a/.github/workflows/build-staging-webapp-image.yaml b/.github/workflows/build-staging-webapp-image.yaml index 44234f52..c05a901e 100644 --- a/.github/workflows/build-staging-webapp-image.yaml +++ b/.github/workflows/build-staging-webapp-image.yaml @@ -1,7 +1,6 @@ name: Build and push image from develop on: - pull_request: - types: [ closed ] + push: branches: - develop diff --git a/.github/workflows/generate-and-submit-documentation.yaml b/.github/workflows/generate-and-submit-documentation.yaml index bd6b1638..d1190b26 100644 --- a/.github/workflows/generate-and-submit-documentation.yaml +++ b/.github/workflows/generate-and-submit-documentation.yaml @@ -1,7 +1,6 @@ name: Generate and submit documentation on: - pull_request: - types: [ closed ] + push: branches: - main diff --git a/Makefile b/Makefile index e9df038d..212a12a4 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ rundocker: builddocker sh -c "cd /home/sage/tii-claasp && make install && cd /home/sage/tii-claasp && exec /bin/bash" builddocker-m1: - docker build --build-arg="GUROBI_ARCH=armlinux64" -f docker/Dockerfile --platform linux/aarch64 --target claasp-base -t $(DOCKER_IMG_NAME) . + docker build -f docker/Dockerfile --platform linux/x86_64 --target claasp-base -t $(DOCKER_IMG_NAME) . rundocker-m1: builddocker-m1 docker run -i -p 8888:8888 --mount type=bind,source=`pwd`,target=/home/sage/tii-claasp -t $(DOCKER_IMG_NAME) \ @@ -95,6 +95,3 @@ copyright: install local-installation: ./configure.sh - -local-installation-m1: - ./configure.sh armlinux64 diff --git a/claasp/cipher.py b/claasp/cipher.py index 2a6f5635..66130ea2 100644 --- a/claasp/cipher.py +++ b/claasp/cipher.py @@ -747,7 +747,7 @@ def cipher_partial_inverse(self, start_round=None, end_round=None, keep_key_sche return partial_cipher_inverse - def evaluate_vectorized(self, cipher_input, intermediate_outputs=False, verbosity=False): + def evaluate_vectorized(self, cipher_input, intermediate_output=False, verbosity=False, evaluate_api = False, bit_based = False): """ Return the output of the cipher for multiple inputs. @@ -766,10 +766,12 @@ def evaluate_vectorized(self, cipher_input, intermediate_outputs=False, verbosit - ``cipher_input`` -- **list**; block cipher inputs (ndarray of uint8 representing one byte each, n rows, m columns, with m the number of inputs to evaluate) - - ``intermediate_outputs`` -- **boolean** (default: `False`) + - ``intermediate_output`` -- **boolean** (default: `False`) - ``verbosity`` -- **boolean** (default: `False`); set this flag to True in order to print the input/output of each component - + - ``evaluate_api`` -- **boolean** (default: `False`); if set to True, takes integer inputs (as the evaluate function) + and returns integer inputs; it is expected that cipher.evaluate(x) == cipher.evaluate_vectorized(x, evaluate_api = True) + is True. EXAMPLES:: sage: import numpy as np @@ -789,7 +791,7 @@ def evaluate_vectorized(self, cipher_input, intermediate_outputs=False, verbosit sage: int.from_bytes(result[-1][1].tobytes(), byteorder='big') == C1Lib True """ - return evaluator.evaluate_vectorized(self, cipher_input, intermediate_outputs, verbosity) + return evaluator.evaluate_vectorized(self, cipher_input, intermediate_output, verbosity, evaluate_api, bit_based) def evaluate_with_intermediate_outputs_continuous_diffusion_analysis( self, cipher_input, sbox_precomputations, sbox_precomputations_mix_columns, verbosity=False): @@ -1708,4 +1710,5 @@ def get_descendants_subgraph(G, start_nodes): def update_input_id_links_from_component_id(self, component_id, new_input_id_links): round_number = self.get_round_from_component_id(component_id) - self._rounds.rounds[round_number].update_input_id_links_from_component_id(component_id, new_input_id_links) \ No newline at end of file + self._rounds.rounds[round_number].update_input_id_links_from_component_id(component_id, new_input_id_links) + diff --git a/claasp/cipher_modules/algebraic_tests.py b/claasp/cipher_modules/algebraic_tests.py index c021abc6..22421b02 100644 --- a/claasp/cipher_modules/algebraic_tests.py +++ b/claasp/cipher_modules/algebraic_tests.py @@ -34,9 +34,9 @@ class AlgebraicTests: {'input_parameters': {'cipher': toyspn1_p6_k6_o6_r2, 'timeout_in_seconds': 10, 'test_name': 'algebraic_tests'}, - 'test_results': {'number_of_variables': [30, 48], - 'number_of_equations': [40, 80], - 'number_of_monomials': [60, 108], + 'test_results': {'number_of_variables': [24, 42], + 'number_of_equations': [34, 74], + 'number_of_monomials': [54, 102], 'max_degree_of_equations': [2, 2], 'test_passed': [False, False]}} @@ -48,9 +48,9 @@ class AlgebraicTests: {'input_parameters': {'cipher': speck_p32_k64_o32_r1, 'timeout_in_seconds': 30, 'test_name': 'algebraic_tests'}, - 'test_results': {'number_of_variables': [144], - 'number_of_equations': [96], - 'number_of_monomials': [189], + 'test_results': {'number_of_variables': [112], + 'number_of_equations': [64], + 'number_of_monomials': [157], 'max_degree_of_equations': [2], 'test_passed': [True]}} @@ -69,12 +69,15 @@ def algebraic_tests(self, timeout_in_seconds=60): tests_up_to_round = [] F = [] - constant_vars = {} + dict_vars = {} for round_number in range(self._cipher.number_of_rounds): F += self._algebraic_model.polynomial_system_at_round(round_number, True) - constant_vars.update(self._algebraic_model._dict_constant_component_polynomials(round_number)) - if constant_vars is not None: - F = self._algebraic_model._remove_constant_polynomials(constant_vars, F) + dict_vars.update(self._algebraic_model._dict_const_rot_not_shift_component_polynomials(round_number)) + if round_number == self._cipher.number_of_rounds - 1 and dict_vars: + dict_vars = self._algebraic_model._substitute_cipher_output_vars_dict_vars(dict_vars, round_number) + if dict_vars: + F = self._algebraic_model._eliminate_const_not_shift_rot_components_polynomials(dict_vars, F) + Fseq = Sequence(F) nvars_up_to_round.append(Fseq.nvariables()) npolynomials_up_to_round.append(len(Fseq)) diff --git a/claasp/cipher_modules/avalanche_tests.py b/claasp/cipher_modules/avalanche_tests.py index 48ac4f79..4b5e8740 100644 --- a/claasp/cipher_modules/avalanche_tests.py +++ b/claasp/cipher_modules/avalanche_tests.py @@ -296,7 +296,7 @@ def avalanche_probability_vectors(self, nb_samples): all_avalanche_probability_vectors[cipher_input][intermediate_output_name] = [] inputs = self._generate_random_inputs(nb_samples) - evaluated_inputs = evaluator.evaluate_vectorized(self._cipher, inputs, intermediate_outputs=True, verbosity=False) + evaluated_inputs = evaluator.evaluate_vectorized(self._cipher, inputs, intermediate_output=True, verbosity=False) input_bits_to_analyse = self._cipher.get_all_inputs_bit_positions() for index_of_specific_input, specific_input in enumerate(self._cipher.inputs): # where the diff is injected for input_diff in input_bits_to_analyse[specific_input]: @@ -323,7 +323,7 @@ def _generate_avalanche_probability_vectors(self, dict_intermediate_output_names evaluated_inputs, input_diff, index_of_specific_input): inputs_prime = self._generate_inputs_prime(index_of_specific_input, input_diff, inputs) evaluated_inputs_prime = evaluator.evaluate_vectorized(self._cipher, inputs_prime, - intermediate_outputs=True, verbosity=False) + intermediate_output=True, verbosity=False) intermediate_avalanche_probability_vectors = {} for intermediate_output_name in list(dict_intermediate_output_names.keys()): intermediate_avalanche_probability_vectors[intermediate_output_name] = \ diff --git a/claasp/cipher_modules/code_generator.py b/claasp/cipher_modules/code_generator.py index ca6bbfde..de2bbcfa 100644 --- a/claasp/cipher_modules/code_generator.py +++ b/claasp/cipher_modules/code_generator.py @@ -28,7 +28,7 @@ from claasp.name_mappings import (SBOX, LINEAR_LAYER, MIX_COLUMN, WORD_OPERATION, CONSTANT, CONCATENATE, PADDING, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, FSR, CIPHER_INVERSE_SUFFIX) - +from claasp.cipher_modules.generic_functions_vectorized_byte import get_number_of_bytes_needed_for_bit_size tii_path = inspect.getfile(claasp) tii_dir_path = os.path.dirname(tii_path) @@ -219,7 +219,6 @@ def get_word_operation_component_bit_based_c_code(component, verbosity): return word_operation_code - def generate_bit_based_vectorized_python_code_string(cipher, store_intermediate_outputs=False, verbosity=False, convert_output_to_bytes=False): """ @@ -257,8 +256,62 @@ def generate_bit_based_vectorized_python_code_string(cipher, store_intermediate_ component.description[0] in component_descriptions_allowed): code.extend(component.get_bit_based_vectorized_python_code(params, convert_output_to_bytes)) name = component.id + if True and component.type != 'constant': + code.append(f' bit_vector_print_as_hex_values("{name}_output", {name})') + if store_intermediate_outputs: + code.append(' return intermediateOutputs') + elif CIPHER_INVERSE_SUFFIX in cipher.id: + code.append(' return intermediateOutputs["plaintext"]') + else: + code.append(' return intermediateOutputs["cipher_output"]') + + return '\n'.join(code) + + +def generate_bit_based_vectorized_python_code_string(cipher, store_intermediate_outputs=False, + verbosity=False, convert_output_to_bytes=False): + """ + Return string python code needed to evaluate a cipher using a vectorized implementation bit based oriented. + + INPUT: + + - ``cipher`` -- **Cipher object**; a cipher instance + - ``store_intermediate_outputs`` -- **boolean** (default: `False`); set this flag to True in order to return a list + with each round output + - ``verbosity`` -- **boolean** (default: `False`); set to True to make the Python code print the input/output of + each component + - ``convert_output_to_bytes`` -- **boolean** (default: `False`) + + EXAMPLES:: + + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules import code_generator + sage: speck = SpeckBlockCipher() + sage: string_python_code = code_generator.generate_bit_based_vectorized_python_code_string(speck) + sage: string_python_code.split("\n")[0] + 'from claasp.cipher_modules.generic_functions_vectorized_bit import *' + """ + code = ['from claasp.cipher_modules.generic_functions_vectorized_bit import *\n', + 'from time import time \n' + 'def evaluate(input, store_intermediate_outputs):', ' intermediateOutputs={}'] + code.extend([f' {cipher.inputs[i]}=input[{i}]' for i in range(len(cipher.inputs))]) + for component in cipher.get_all_components(): + params = prepare_input_bit_based_vectorized_python_code_string(component) + component_types_allowed = ['constant', 'linear_layer', 'concatenate', 'mix_column', + 'sbox', 'cipher_output', 'intermediate_output', 'fsr'] + component_descriptions_allowed = ['ROTATE', 'SHIFT', 'SHIFT_BY_VARIABLE_AMOUNT', 'NOT', 'XOR', + 'MODADD', 'MODSUB', 'OR', 'AND'] + if component.type in component_types_allowed or (component.type == 'word_operation' and + component.description[0] in component_descriptions_allowed): + code.append(" t0 = time()") + code.extend(component.get_bit_based_vectorized_python_code(params, convert_output_to_bytes)) + name = component.id + #code.append(f" print('{name}', {name}.dtype)") + code.append(f" print('{name}', time()-t0)") + if verbosity and component.type != 'constant': code.append(f' bit_vector_print_as_hex_values("{name}_output", {name})') + if store_intermediate_outputs: code.append(' return intermediateOutputs') elif CIPHER_INVERSE_SUFFIX in cipher.id: @@ -286,7 +339,7 @@ def constant_to_bitstring(val, output_size): return ret -def generate_byte_based_vectorized_python_code_string(cipher, store_intermediate_outputs=False, verbosity=False): +def generate_byte_based_vectorized_python_code_string(cipher, store_intermediate_outputs=False, verbosity=False, integers_inputs_and_outputs = False): r""" Return string python code needed to evaluate a cipher using a vectorized implementation byte based oriented. @@ -309,34 +362,41 @@ def generate_byte_based_vectorized_python_code_string(cipher, store_intermediate """ cipher.sort_cipher() - code = ['from claasp.cipher_modules.generic_functions_vectorized_byte import *\n', '\n', + code = ['from claasp.cipher_modules.generic_functions_vectorized_byte import *\n', + 'integers_inputs_and_outputs='+str(integers_inputs_and_outputs)+'\n', 'def evaluate(input, store_intermediate_outputs):', ' intermediateOutputs={}'] - bit_sizes = {} + output_bit_sizes = {} + code.append(' if integers_inputs_and_outputs:\n' + ' input = cipher_inputs_to_evaluate_vectorized_inputs(input, ' + str(cipher.inputs_bit_size) + ')') for i in range(len(cipher.inputs)): code.append(f' {cipher.inputs[i]}=input[{i}]') - bit_sizes[cipher.inputs[i]] = cipher.inputs_bit_size[i] + output_bit_sizes[cipher.inputs[i]] = cipher.inputs_bit_size[i] for component in cipher.get_all_components(): - params = prepare_input_byte_based_vectorized_python_code_string(bit_sizes, component) - bit_sizes[component.id] = component.output_bit_size + #code.append(f' print("{component.id}")') + formatted_component_inputs = prepare_input_byte_based_vectorized_python_code_string(output_bit_sizes, component) + output_bit_sizes[component.id] = component.output_bit_size component_types_allowed = ['constant', 'linear_layer', 'concatenate', 'mix_column', 'sbox', 'cipher_output', 'intermediate_output', 'fsr'] component_descriptions_allowed = ['ROTATE', 'SHIFT', 'SHIFT_BY_VARIABLE_AMOUNT', 'NOT', 'XOR', 'MODADD', 'MODSUB', 'OR', 'AND'] if component.type in component_types_allowed or (component.type == 'word_operation' and component.description[0] in component_descriptions_allowed): - code.extend(component.get_byte_based_vectorized_python_code(params)) + code.extend(component.get_byte_based_vectorized_python_code(formatted_component_inputs)) name = component.id if verbosity and component.type != 'constant': - code.append(f' byte_vector_print_as_hex_values("{name}_input", {params})') + code.append(f' byte_vector_print_as_hex_values("{name}_input", {formatted_component_inputs})') code.append(f' byte_vector_print_as_hex_values("{name}_output", {name})') + #code.append(' print("CIPHER OUTPUT : ", cipher_output_15_15)') + if store_intermediate_outputs: code.append(' return intermediateOutputs') elif CIPHER_INVERSE_SUFFIX in cipher.id: code.append(' return intermediateOutputs["plaintext"]') else: code.append(' return intermediateOutputs["cipher_output"]') + # print('\n'.join(code)) return '\n'.join(code) @@ -350,8 +410,10 @@ def prepare_input_byte_based_vectorized_python_code_string(bit_sizes, component) if component.type == 'constant': return params + #assert (input_bit_size % number_of_inputs) == 0, f"The number of inputs does not divide the number of input bits " \ + # f"for component {component.id}. " bits_per_input = input_bit_size // number_of_inputs - words_per_input = math.ceil(bits_per_input / 8) + words_per_input = get_number_of_bytes_needed_for_bit_size(bits_per_input) # Divide inputs real_inputs = [[] for _ in range(number_of_inputs)] real_bits = [[] for _ in range(number_of_inputs)] @@ -407,7 +469,7 @@ def get_number_of_inputs(component): else: number_of_inputs = component.description[1] elif component.type == 'mix_column': - number_of_inputs = len(component.description[0]) + number_of_inputs = len(component.description[0][0]) elif component.type == 'linear_layer': number_of_inputs = len(component.description[0]) elif component.type == 'sbox': @@ -420,17 +482,6 @@ def get_number_of_inputs(component): return number_of_inputs -def constant_to_repr(val, output_size): - _val = int(val, 0) - if output_size % 8 != 0: - s = output_size + (8 - (output_size % 8)) - else: - s = output_size - ret = [(_val >> s - (8 * (i + 1))) & 0xff for i in range(s // 8)] - - return ret - - def generate_evaluate_c_code_shared_library(cipher, intermediate_output, verbosity): name = cipher.id + "_evaluate" cipher_word_size = cipher.is_power_of_2_word_based() diff --git a/claasp/cipher_modules/evaluator.py b/claasp/cipher_modules/evaluator.py index e4e5a1be..79341460 100644 --- a/claasp/cipher_modules/evaluator.py +++ b/claasp/cipher_modules/evaluator.py @@ -1,4 +1,3 @@ - # **************************************************************************** # Copyright 2023 Technology Innovation Institute # @@ -22,11 +21,13 @@ from subprocess import Popen, PIPE from claasp.cipher_modules import code_generator +from claasp.cipher_modules.generic_functions_vectorized_byte import cipher_inputs_to_evaluate_vectorized_inputs, \ + evaluate_vectorized_outputs_to_integers def evaluate(cipher, cipher_input, intermediate_output=False, verbosity=False): python_code_string = code_generator.generate_python_code_string(cipher, verbosity) - + f_module = ModuleType("evaluate") exec(python_code_string, f_module.__dict__) @@ -77,25 +78,16 @@ def evaluate_using_c(cipher, inputs, intermediate_output, verbosity): return function_output -def evaluate_vectorized(cipher, cipher_input, intermediate_outputs=False, verbosity=False): - if np.any(np.array(cipher.inputs_bit_size) % 8 != 0): - python_code_string = code_generator \ - .generate_bit_based_vectorized_python_code_string(cipher, - store_intermediate_outputs=intermediate_outputs, - verbosity=verbosity, - convert_output_to_bytes=True) - cipher_input = [np.unpackbits(cipher_input[i], axis=0)[:x, ] - for i, x in enumerate(cipher.inputs_bit_size)] - else: - python_code_string = code_generator \ - .generate_byte_based_vectorized_python_code_string( - cipher, - store_intermediate_outputs=intermediate_outputs, verbosity=verbosity) - +def evaluate_vectorized(cipher, cipher_input, intermediate_output=False, verbosity=False, evaluate_api=False, + bit_based=False): + python_code_string = code_generator.generate_byte_based_vectorized_python_code_string(cipher, + store_intermediate_outputs=intermediate_output, + verbosity=verbosity, + integers_inputs_and_outputs=evaluate_api) f_module = ModuleType("evaluate") exec(python_code_string, f_module.__dict__) - - return f_module.evaluate(cipher_input, intermediate_outputs) + cipher_output = f_module.evaluate(cipher_input, intermediate_output) + return cipher_output def evaluate_with_intermediate_outputs_continuous_diffusion_analysis(cipher, cipher_input, sbox_precomputations, diff --git a/claasp/cipher_modules/generic_functions_vectorized_bit.py b/claasp/cipher_modules/generic_functions_vectorized_bit.py index 08f3903d..0d0cb69b 100644 --- a/claasp/cipher_modules/generic_functions_vectorized_bit.py +++ b/claasp/cipher_modules/generic_functions_vectorized_bit.py @@ -16,6 +16,7 @@ # along with this program. If not, see . # **************************************************************************** +import inspect import numpy as np @@ -76,11 +77,10 @@ def bit_vector_select_word(input, bits, verbosity=False): print(f'select_word bits : {bits}') print(f'select_word output : {output.transpose()}') print("---") - return output -def bit_vector_SBOX(input, sbox, verbosity=False): +def bit_vector_SBOX(input, sbox, verbosity=False, output_bit_size = None): """ Computes the SBox operation on binary values. @@ -95,6 +95,10 @@ def bit_vector_SBOX(input, sbox, verbosity=False): int_val = np.packbits(tmp, axis=0) int_output = sbox[int_val] output = np.unpackbits(int_output, axis=0) + if output_bit_size is None: + output = output[-input.shape[0]:] + else: + output = output[-output_bit_size:] if verbosity: print("SBox") print("Input : ", input.transpose()) @@ -103,7 +107,7 @@ def bit_vector_SBOX(input, sbox, verbosity=False): print("Output : ", output.transpose()) print("---") - return output[-input.shape[0]:] + return output def bit_vector_XOR(input, number_of_inputs, output_bit_size, verbosity=False): @@ -117,14 +121,22 @@ def bit_vector_XOR(input, number_of_inputs, output_bit_size, verbosity=False): - ``output_bit_size`` -- **integer**; is an integer representing the bit size of the output - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - output = 0 # copy(inputConcatenated[0:output_bit_size]) + output = 0 if number_of_inputs == len(input) and np.all([x.shape[0] == output_bit_size for x in input]): for i in range(number_of_inputs): output = output + input[i] else: - inputConcatenated = bit_vector_CONCAT(input) - for i in range(number_of_inputs): - output += inputConcatenated[i * output_bit_size:(i + 1) * output_bit_size] + assert np.all([x.shape[0] <= output_bit_size for x in input]) + output = np.zeros(shape=(output_bit_size, np.max([input[i].shape[1] for i in range(len(input))])), dtype=np.uint8) + first_bit_index = 0 + for i in range(len(input)): + current_input = input[i] + bit_size = current_input.shape[0] + output[first_bit_index:first_bit_index + bit_size] += current_input + first_bit_index += bit_size + if first_bit_index == output_bit_size: + first_bit_index = 0 + output &= 1 if DEBUG_MODE: @@ -149,6 +161,7 @@ def print_component_info(input, output, component_type): print(output.transpose()) + def bit_vector_CONCAT(input): """ Concatenates binary values @@ -430,7 +443,13 @@ def bit_vector_linear_layer(input, matrix, verbosity=False): - ``matrix`` -- **list**; a list of lists of 0s and 1s. len(matrix) should be equal to input.len - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - output = input.transpose().dot(matrix).transpose() % 2 + m8 = np.uint8(matrix) + # Bit permutation case + if np.sum(m8, axis=0).max() == 1: + permutation_indexes = np.where(m8.T == 1)[1] + output = input[permutation_indexes] + else: + output = input.transpose().dot(m8).transpose() % 2 if verbosity: print("LINEAR LAYER:") print(input) diff --git a/claasp/cipher_modules/generic_functions_vectorized_byte.py b/claasp/cipher_modules/generic_functions_vectorized_byte.py index b42743df..bcad7eab 100644 --- a/claasp/cipher_modules/generic_functions_vectorized_byte.py +++ b/claasp/cipher_modules/generic_functions_vectorized_byte.py @@ -1,30 +1,91 @@ - # **************************************************************************** # Copyright 2023 Technology Innovation Institute -# +# # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program. If not, see . # **************************************************************************** import numpy as np -from copy import copy from functools import reduce -from operator import xor +import math NB = 8 # Number of bits of the representation +def integer_array_to_evaluate_vectorized_input(values, bit_size): + """ + Converts the bit_size integers from the values array to the representation accepted by evaluate_vectorized, a numpy matrix + of unsigned 8-bit integers (one row per byte, one column per value). If needed, the values are padded with zeroes + on the left. If the cipher takes multiple inputs, this function needs to be called once for each. + + INPUT: + - ``values`` -- **list** A list of integers + - ``bit_size`` -- **integer** The bit size of the elements of values. + """ + num_bytes = get_number_of_bytes_needed_for_bit_size(bit_size) + # math.ceil(bit_size / 8) + values_as_np = np.array(values, dtype=object) & (2 ** bit_size - 1) + evaluate_vectorized_input = (np.uint8([(values_as_np >> ((num_bytes - j - 1) * 8)) & 0xff + for j in range(num_bytes)]).reshape((num_bytes, -1))) + return evaluate_vectorized_input + + +def cipher_inputs_to_evaluate_vectorized_inputs(cipher_inputs, cipher_inputs_bit_size): + """ + Converts cipher_inputs from integers to the format expected by evaluate_vectorized. + If cipher_inputs is a list of integers (one per input position), then the function returns a list of numpy matrices + that can be used to evaluate a single set of inputs to the cipher (with a similar api to cipher.evaluate). + If cipher_inputs is a list of lists of integers (one per input position), then the function returns a list of numpy + matrices that can be used to evaluate multiple set of inputs to the cipher. + The produced matrices contain one row per byte, and one column per value. + If needed, the values are padded with zeroes on the left. + + INPUT: + - ``cipher_inputs`` -- **list** A list of lists of integers (one per cipher input position) + - ``cipher_inputs_bit_size`` -- **list** The inputs bit sizes of the cipher. + """ + assert len(cipher_inputs) == len(cipher_inputs_bit_size), "The cipher_input_to_evaluate_vectorized_input expects" \ + "one list of inputs per value in " \ + "cipher_inputs_bit_size " + evaluate_vectorized_inputs = [] + for i, bit_size in enumerate(cipher_inputs_bit_size): + evaluate_vectorized_inputs.append(integer_array_to_evaluate_vectorized_input(cipher_inputs[i], bit_size)) + return evaluate_vectorized_inputs + + +def get_number_of_bytes_needed_for_bit_size(bit_size): + return math.ceil(bit_size / 8) + + +def evaluate_vectorized_outputs_to_integers(evaluate_vectorized_outputs, cipher_output_bit_size): + """ + Converts the outputs of evaluate_vectorized (a list containing a single numpy matrix) to a list of integers + (one per output/row of the matrix) + + INPUT: + - ``evaluate_vectorized_outputs`` -- **list** A list containing one numpy array returned by evaluate_vectorized + - ``cipher_output_bit_size`` -- **integer** The output bit size of the cipher + """ + shifts = np.flip( + np.array([i * 8 for i in range(get_number_of_bytes_needed_for_bit_size(cipher_output_bit_size))], dtype=object)) + int_vals = (np.sum(evaluate_vectorized_outputs[0] << shifts, axis=1) & (2 ** cipher_output_bit_size - 1)).tolist() + if len(int_vals) == 1: + return int_vals[0] + else: + return int_vals + + def byte_vector_print_as_hex_values(name, x): """ Prints a byte vector x as an hex value - used for debugging @@ -61,8 +122,8 @@ def byte_vector_is_consecutive(l): return np.all(l[::-1] == np.arange(l[-1], l[0] + 1).tolist()) -def byte_vector_select_all_words(unformatted_inputs, real_bits, real_inputs, number_of_inputs, words_per_input, - actual_inputs_bits, verbosity=False): +def byte_vector_select_all_words(unformated_inputs, real_bits, real_inputs, number_of_inputs, words_per_input, + actual_inputs_bits): """ Parses the inputs from the cipher into a list of numpy byte arrays, each corresponding to one input to the function. @@ -76,76 +137,95 @@ def byte_vector_select_all_words(unformatted_inputs, real_bits, real_inputs, num - ``number_of_inputs`` -- **integer**; an integer representing the number of inputs expected by the operation - ``words_per_input`` -- **integer**; the number of 8-bit words to be reserved for each of the inputs - ``actual_inputs_bits`` -- **integer**; the bit size of the variables in unformatted_inputs - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - if verbosity: - print("SELECT : ") - print("Input =") - print([x.transpose() for x in unformatted_inputs]) - number_of_columns = [unformatted_inputs[i].shape[1] for i in range(len(unformatted_inputs))] + + number_of_columns = [x.shape[1] for x in unformated_inputs] max_number_of_columns = np.max(number_of_columns) - # Select bits output = [0 for _ in range(number_of_inputs)] for i in range(number_of_inputs): pos = 0 + number_of_output_bits = np.sum([len(x) for x in real_bits[i]]) if len(real_inputs[i]) == 1 and np.all(real_bits[i][0] == list(range(actual_inputs_bits[real_inputs[i][0]]))): - output[i] = unformatted_inputs[real_inputs[i][0]] + output[i] = unformated_inputs[real_inputs[i][0]] + if number_of_output_bits % 8 > 0: + left_byte_mask = 2 ** (number_of_output_bits % 8) - 1 + else: + left_byte_mask = 0xffff + output[i][0, :] &= left_byte_mask else: output[i] = np.zeros(shape=(words_per_input, max_number_of_columns), dtype=np.uint8) - generate_formatted_inputs(actual_inputs_bits, i, output, pos, real_bits, real_inputs, unformatted_inputs, + generate_formatted_inputs(actual_inputs_bits, i, output, pos, real_bits, real_inputs, unformated_inputs, words_per_input) - - if verbosity: - print("realInp :", real_inputs) - print("realBits :", real_bits) - print("ActualInputBits :", actual_inputs_bits) - print("Output =") - print([x.transpose() for x in output]) - print("/SELECT") + #print(f"{output=}") return output +def get_number_of_consecutive_bits(l): + """ + Return the number of consecutive numbers from the start of list l, in decreasing order. + + INPUT: + + - ``l`` -- **list**; a list of bit positions, in reverse order + + EXAMPLES:: + + sage: from claasp.cipher_modules.generic_functions_vectorized_byte import get_number_of_consecutive_bits + sage: L=[4, 3, 5, 7, 2] + sage: get_number_of_consecutive_bits(L) == 2 + True + """ + + number_of_consecutive_bits = 0 + pred = l[0] + for i in range(1, len(l)): + if l[i] == pred - 1: + pred = l[i] + number_of_consecutive_bits += 1 + else: + break + return number_of_consecutive_bits + + def generate_formatted_inputs(actual_inputs_bits, i, output, pos, real_bits, real_inputs, unformatted_inputs, words_per_input): + number_of_output_bits = np.sum([len(x) for x in real_bits[i]]) + if number_of_output_bits % 8 > 0: + left_zero_padding = 8 - (number_of_output_bits % 8) + else: + left_zero_padding = 0 + bits_counter = 0 + binary_output = np.zeros((left_zero_padding + number_of_output_bits, output[i].shape[1]), dtype=np.uint8) + for j in range(len(real_inputs[i])): - val = real_inputs[i][len(real_inputs[i]) - j - 1] - b_list = real_bits[i][len(real_inputs[i]) - j - 1] - b2 = copy(b_list) - b2.reverse() - k = 0 - while k < len(b2): - b = b2[k] - word_pos_in_output = (8 * words_per_input - pos - 1) // 8 - bit_left_shift_in_output = pos % 8 - bits_per_word_in_input = actual_inputs_bits[val] // unformatted_inputs[val].shape[0] - word_pos_in_input = b // bits_per_word_in_input - bit_pos_in_input = 8 - bits_per_word_in_input + (b % bits_per_word_in_input) - - if pos % 8 == 0 and k + 8 <= len(b2) and byte_vector_is_consecutive(b2[k:k + 8]) \ - and b2[k + 7] % 8 == 0 and bits_per_word_in_input == 8: - output[i][word_pos_in_output] = unformatted_inputs[val][word_pos_in_input] - pos = pos + 8 - k = k + 8 - elif pos % 4 == 0 and k + 4 <= len(b2) and byte_vector_is_consecutive(b2[k:k + 4]) \ - and b2[k + 3] % 4 == 0 and bits_per_word_in_input == 4: - if pos % 8 == 0: - output[i][word_pos_in_output] ^= unformatted_inputs[val][word_pos_in_input] - pos = pos + 4 - k = k + 4 - elif pos % 8 == 4: - output[i][word_pos_in_output] ^= unformatted_inputs[val][word_pos_in_input] << 4 - pos = pos + 4 - k = k + 4 - else: - output[i][word_pos_in_output] ^= ((unformatted_inputs[val][word_pos_in_input] >> ( - 8 - 1 - bit_pos_in_input)) & 1) << bit_left_shift_in_output - pos = pos + 1 - k = k + 1 + val = unformatted_inputs[real_inputs[i][- j - 1]] + bits_taken = len(real_bits[i][-j - 1]) + if actual_inputs_bits[real_inputs[i][-j - 1]] % 8 > 0: + offset_for_first_byte = 8 - (actual_inputs_bits[real_inputs[i][-j - 1]] % 8) + else: + offset_for_first_byte = 0 + b_list = np.array(real_bits[i][- j - 1]) + offset_for_first_byte + binary_version = np.unpackbits(val, axis=0) + # print("="*10) + # print(f"{b_list=}") + # print(f"{binary_version=}") + # print(f"{offset_for_first_byte=}") + # print(f"{left_zero_padding=}") + + if j == 0: + last_bit_position = None + else: + last_bit_position = -bits_counter + binary_output[-bits_taken - bits_counter:last_bit_position] = binary_version[b_list, :] + # binary_output[-bits_taken-bits_counter:-bits_counter] = binary_version[b_list, :] + + bits_counter += bits_taken + output[i] = np.packbits(binary_output, axis=0) -def byte_vector_SBOX(val, sbox, verbosity=False): +def byte_vector_SBOX(val, sbox, input_bit_size): """ Computes the result of the SBox operation. @@ -153,42 +233,30 @@ def byte_vector_SBOX(val, sbox, verbosity=False): - ``val`` -- **np.array(dtype = np.uint8)** A numpy matrix with one row per byte and one column per sample. - ``sbox`` -- **np.array(dtype = np.uint8)** An integer numpy array representing the SBox. - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - if verbosity: - print("SBox") - print("Input : ", val[0].transpose()) - print("Output : ", sbox[val[0]].transpose()) - print("---") - - return sbox[val[0]] + if input_bit_size <= 8: + output = np.uint8(sbox)[val[0]] + else: + assert val[0].shape[0] == 2, "The inputs cannot be larger than two bytes each." + input_as_uint16 = (np.uint16(val[0][0, :]) << 8) ^ val[0][1, :] + sub = np.uint16(sbox)[input_as_uint16] + output = np.uint8(np.vstack([sub >> 8, sub & 0xff])) + return output -def byte_vector_XOR(input, verbosity=False): +def byte_vector_XOR(input): """ Computes the result of the XOR operation. INPUT: - ``input`` -- **list**; A list of numpy byte matrices to be XORed, each with one row per byte, and one column per sample. - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - if verbosity: - print("XOR") - print("Input =") - print([x.transpose() for x in input]) - output = reduce(lambda x, y: x ^ y, input) - if verbosity: - print("Output = ") - print(output.transpose()) - print("/XOR") - print("\n") - return output -def byte_vector_AND(input, verbosity=False): +def byte_vector_AND(input): """ Computes the result of the AND operation @@ -197,17 +265,12 @@ def byte_vector_AND(input, verbosity=False): INPUT: - ``input`` -- **list**; A list of numpy byte matrices to be ANDed, each with one row per byte, and one column per sample. - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output. """ output = reduce(lambda x, y: x & y, input) - - if verbosity: - print_component_info(input, output, "AND:") - return output -def byte_vector_OR(input, verbosity=False): +def byte_vector_OR(input): """ Computes the result of the OR operation. @@ -216,17 +279,12 @@ def byte_vector_OR(input, verbosity=False): INPUT: - ``input`` -- **list**; A list of numpy byte matrices to be ORed, each with one row per byte, and one column per sample. - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output. """ output = reduce(lambda x, y: x | y, input) - - if verbosity: - print_component_info(input, output, "OR:") - return output -def byte_vector_NOT(input, verbosity=False): +def byte_vector_NOT(input): """ Computes the result of the NOT operation. @@ -234,17 +292,12 @@ def byte_vector_NOT(input, verbosity=False): - ``input`` -- **list**; A list of one numpy byte matrix to be negated, with one row per byte, and one column per sample - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ output = ~input[0] - - if verbosity: - print_component_info(input, output, "NOT:") - return output -def byte_vector_SHIFT_BY_VARIABLE_AMOUNT(input, input_size, shift_direction, verbosity=False): +def byte_vector_SHIFT_BY_VARIABLE_AMOUNT(input, input_size, shift_direction): """ Computes the bitwise shift by variable amount operation. @@ -254,7 +307,6 @@ def byte_vector_SHIFT_BY_VARIABLE_AMOUNT(input, input_size, shift_direction, ver - ``input_size`` -- **integer**; size in bits of value to be shifted - ``shift_direction`` -- **integer**; the value of the shift, positive for right and negative for left - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ bits = np.uint8(np.log2(input_size)) @@ -268,27 +320,17 @@ def byte_vector_SHIFT_BY_VARIABLE_AMOUNT(input, input_size, shift_direction, ver if len(ind[0]) > 0: output[:, ind[0]] = byte_vector_SHIFT([input0[:, ind[0]]], i * shift_direction) # np.roll(input0[:, ind], i*shift_direction, axis=0) - if verbosity: - print("VARIABLE_SHIFT:") - print("Output with shape ", output.shape) - print(output) - return output -def byte_vector_MODADD(input, verbosity=False): +def byte_vector_MODADD(input): """ Computes the result of the MODADD operation. INPUT: - ``input`` -- **list**; A list of numpy byte matrices to be added, each with one row per byte, and one column per sample. - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - if verbosity: - print("MODADD") - print("Input =") - print(input) for i in range(len(input) - 1): if i == 0: a = input[0].copy() @@ -309,22 +351,16 @@ def byte_vector_MODADD(input, verbosity=False): np.less(m - c[1:], b[1:], out=cbuf) c = reduce(lambda a, b: a + b, [c, b]) b = carry.copy() - if verbosity: - print("Output:") - print(c) - print("/MODADD") - return c -def byte_vector_MODSUB(input, verbosity=False): +def byte_vector_MODSUB(input): """ Computes the result of the MODSUB operation. INPUT: - ``input`` -- **list**; A list of 2 numpy byte matrices to be subtracted, each with one row per byte, and one column per sample. - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ assert len(input) == 2 # Other cases not implemented @@ -337,13 +373,10 @@ def byte_vector_MODSUB(input, verbosity=False): a = byte_vector_MODADD([inputsList[0], inputsList[1]]) output = byte_vector_MODADD([a, one]) - if verbosity: - print_component_info(input, output, "MODSUB:") - return output -def byte_vector_ROTATE(input, rotation_amount, verbosity=False): +def byte_vector_ROTATE(input, rotation_amount, input_bit_size): """ Computes the result of the bitwise ROTATE operation. @@ -353,30 +386,27 @@ def byte_vector_ROTATE(input, rotation_amount, verbosity=False): - ``input_size`` -- **integer**; size in bits of value to be shifted - ``rotation_amount`` -- **integer**; the value of the rotation, positive for right and negative for left - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output - """ - if verbosity: - print("ROTATE, ", rotation_amount) - print("Input = ") - print(input) - rot = rotation_amount - wordRot = int(abs(rot) / NB) - bitRot = int(abs(rot) % NB) - sign = 1 if rot > 0 else -1 - ret = np.roll(input[0], sign * wordRot, axis=0) - if bitRot != 0: - a = ret >> bitRot if sign > 0 else ret << bitRot - b = ret << (8 - bitRot) if sign > 0 else ret >> (8 - bitRot) - ret = a ^ np.roll(b, sign, axis=0) - if verbosity: - print(input[0].transpose()) - print("Output =") - print(ret) - + """ + if input_bit_size % 8 != 0: + bits_to_cut = 8 - (input_bit_size % 8) + bin_input = np.unpackbits(input[0], axis=0) + rotated = np.vstack([np.zeros((bits_to_cut, bin_input.shape[1]), dtype=np.uint8), + np.roll(bin_input[bits_to_cut:, :], rotation_amount, axis=0)]) + ret = np.packbits(rotated, axis=0) + else: + rot = rotation_amount + wordRot = int(abs(rot) / NB) + bitRot = int(abs(rot) % NB) + sign = 1 if rot > 0 else -1 + ret = np.roll(input[0], sign * wordRot, axis=0) + if bitRot != 0: + a = ret >> bitRot if sign > 0 else ret << bitRot + b = ret << (8 - bitRot) if sign > 0 else ret >> (8 - bitRot) + ret = a ^ np.roll(b, sign, axis=0) return ret -def byte_vector_SHIFT(input, shift_amount, verbosity=False): +def byte_vector_SHIFT(input, shift_amount): """ Computes the result of the bitwise SHIFT operation. @@ -386,13 +416,7 @@ def byte_vector_SHIFT(input, shift_amount, verbosity=False): - ``input_size`` -- **integer**; size in bits of value to be shifted - ``shift_smount`` -- **integer**; the value of the shift, positive for right and negative for left - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - if verbosity: - print("SHIFT, ", shift_amount) - print("Input = ") - print(input) - rot = shift_amount wordRot = abs(rot) // NB bitRot = int(abs(rot) % NB) @@ -413,12 +437,6 @@ def byte_vector_SHIFT(input, shift_amount, verbosity=False): ret[-wordRot:] = 0 mask = ((0xff) << bitRot) & 0xff ret[-1 - wordRot] = ret[-1 - wordRot] & mask - - if verbosity: - print("Wordrot:", wordRot, ", bitrot:", bitRot, ", Mask = ", hex(mask)) - print("Output =") - print(ret) - return ret @@ -431,10 +449,21 @@ def byte_vector_linear_layer(input, matrix): - ``input`` -- **np.array(dtype = np.uint8)** A numpy matrix with one row per byte, and one column per sample. - ``matrix`` -- **list**; a list of lists of 0s and 1s """ - return np.packbits(np.dot(np.array([x[0] for x in input], dtype=np.uint8).T, matrix) & 1, axis=1).transpose() + m8 = np.uint8(matrix) + # Bit permutation case + if np.sum(m8, axis=0).max() == 1: + permutation_indexes = np.where(m8.T == 1)[1] + bin_result = np.uint8(input)[permutation_indexes, 0, :] + else: + bin_result = np.dot(m8.T, np.uint8(input)[:, 0, :]) & 1 + if len(input) % 8 != 0: + bin_result = np.vstack([np.zeros((8 - (len(input) % 8), input[0].shape[1]), dtype=np.uint8), bin_result]) + output = np.packbits(bin_result, axis=0) + + return output -def byte_vector_mix_column(input, matrix, mul_table, verbosity=False): +def byte_vector_mix_column(input, matrix, mul_table, word_size): """ Computes the mix_column operation. @@ -443,25 +472,29 @@ def byte_vector_mix_column(input, matrix, mul_table, verbosity=False): - ``input`` -- **np.array(dtype = np.uint8)** A numpy matrix with one row per byte, and one column per sample. - ``matrix`` -- **list**; a list of lists of integers - ``mul_tables`` -- **dictionary**; a dictionary giving the multiplication table by x at key x - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - if verbosity: - print("MIXCOLUMN:") - print(input.transpose()) - output = np.zeros(shape=(len(input), input[0].shape[1]), dtype=np.uint8) + #assert word_size == 4 or word_size == 8, "Vectorized evaluation of mix_columns does not support word sizes other than 8 and 4" + tmp = np.zeros(shape=(len(matrix) * input[0].shape[0], input[0].shape[1]), dtype=np.uint8) + + #tmp = np.zeros(shape=(len(matrix), input[0].shape[1]), dtype=np.uint8) + for i in [*mul_table]: mul_table[i] = np.array(mul_table[i], dtype=np.uint8) for i in range(len(matrix)): for j in range(len(matrix[0])): - output[i] = reduce(lambda x, y: x ^ y, [output[i], mul_table[matrix[i][j]][input[j]]]) - if verbosity: - print(output.transpose()) - print("---") - - return output + tmp[i] = reduce(lambda x, y: x ^ y, [tmp[i], mul_table[matrix[i][j]][input[j]]]) + if word_size >= 8: + return tmp + #else: + return byte_vector_select_all_words(unformated_inputs=[x.reshape(1,-1) for x in tmp], + real_bits = [[list(range(word_size)) for _ in tmp]], + real_inputs = [list(range(len(tmp)))], + number_of_inputs=1, + words_per_input=get_number_of_bytes_needed_for_bit_size(word_size*len(tmp)), + actual_inputs_bits=[word_size for _ in tmp])[0] -def byte_vector_mix_column_poly0(input, matrix, verbosity=False): +def byte_vector_mix_column_poly0(input, matrix, word_size): """ Computes the mix_column operation, special case where poly=0. @@ -469,29 +502,20 @@ def byte_vector_mix_column_poly0(input, matrix, verbosity=False): - ``input`` -- **np.array(dtype = np.uint8)** A numpy matrix with one row per byte, and one column per byte. - ``matrix`` -- **list**; a list of lists of integers - - ``verbosity`` -- **boolean**; (default: `False`); set this flag to True to print the input/output """ - if verbosity: - print("MIXCOLUMN poly 0:") - print(input.transpose()) - output = np.zeros(shape=(len(input) * input[0].shape[0], input[0].shape[1]), dtype=np.uint8) + #tmp = np.zeros(shape=(len(matrix), input[0].shape[1]), dtype=np.uint8) + tmp = np.zeros(shape=(len(matrix) * input[0].shape[0], input[0].shape[1]), dtype=np.uint8) + + #tmp = np.zeros(shape=(len(input) * input[0].shape[0], input[0].shape[1]), dtype=np.uint8) for i in range(len(matrix)): for j in range(len(matrix[0])): - # for k in range(len(matrix)): - # print(output.shape, i, j, input[j].shape, output[i].shape, matrix[i][j]) - # output[i] = output[i]^(matrix[i][j]*input[j]) - output[i * input[0].shape[0]:(i + 1) * input[0].shape[0]] = \ - output[i * input[0].shape[0]:(i + 1) * input[0].shape[0]] ^ matrix[i][j] * input[j] - # reduce(lambda x, y:x^y, [output[i], matrix[i][j]*input[j]]) - if verbosity: - print(output.transpose()) - print("---") - - return output - -def print_component_info(input, output, component_type): - print(component_type) - print("Inputs : ") - print([input[i].transpose() for i in range(len(input))]) - print(" Output:") - print(output.transpose()) + tmp[i * input[0].shape[0]:(i + 1) * input[0].shape[0]] = \ + tmp[i * input[0].shape[0]:(i + 1) * input[0].shape[0]] ^ matrix[i][j] * input[j] + if word_size >=8: + return tmp + return byte_vector_select_all_words(unformated_inputs=[x.reshape(1,-1) for x in tmp], + real_bits = [[list(range(word_size)) for _ in tmp]], + real_inputs = [list(range(len(tmp)))], + number_of_inputs=1, + words_per_input=get_number_of_bytes_needed_for_bit_size(word_size*len(tmp)), + actual_inputs_bits=[word_size for _ in tmp])[0] diff --git a/claasp/cipher_modules/inverse_cipher.py b/claasp/cipher_modules/inverse_cipher.py index dd32cd00..3b1850b0 100644 --- a/claasp/cipher_modules/inverse_cipher.py +++ b/claasp/cipher_modules/inverse_cipher.py @@ -1283,17 +1283,18 @@ def remove_components_from_rounds(cipher, start_round, end_round, keep_key_sched return removed_component_ids, intermediate_outputs -def get_relative_position(target_link, target_bit_positions, descendant): - offset = 0 - if target_link == descendant.id: +def get_relative_position(target_link, target_bit_positions, intermediate_output): + if target_link == intermediate_output.id: return target_bit_positions - for i, link in enumerate(descendant.input_id_links): - child_input_bit_position = descendant.input_bit_positions[i] - if link == target_link: - if set(target_bit_positions) <= set(child_input_bit_position): - return [idx + offset for idx, e in enumerate(child_input_bit_position) if e in target_bit_positions] - offset += len(child_input_bit_position) - return [] + + intermediate_output_position_links = {} + current_bit_position = 0 + for input_id_link, input_bit_positions in zip(intermediate_output.input_id_links, intermediate_output.input_bit_positions): + for i in input_bit_positions: + intermediate_output_position_links[(input_id_link, i)] = current_bit_position + current_bit_position += 1 + + return [intermediate_output_position_links[(target_link, bit)] for bit in target_bit_positions if (target_link, bit) in intermediate_output_position_links] def get_most_recent_intermediate_output(target_link, intermediate_outputs): for index in sorted(intermediate_outputs, reverse=True): @@ -1307,4 +1308,5 @@ def update_input_links_from_rounds(cipher_rounds, removed_components, intermedia if link in removed_components: intermediate_output = get_most_recent_intermediate_output(link, intermediate_outputs) component.input_id_links[i] = f'{intermediate_output.id}' - component.input_bit_positions[i] = get_relative_position(link, component.input_bit_positions[i], intermediate_output) + component.input_bit_positions[i] = get_relative_position(link, component.input_bit_positions[i], + intermediate_output) diff --git a/claasp/cipher_modules/models/algebraic/algebraic_model.py b/claasp/cipher_modules/models/algebraic/algebraic_model.py index a9543d2e..1d4c58bb 100644 --- a/claasp/cipher_modules/models/algebraic/algebraic_model.py +++ b/claasp/cipher_modules/models/algebraic/algebraic_model.py @@ -153,7 +153,7 @@ def polynomial_system(self): sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel sage: toyspn = ToySPN1() sage: AlgebraicModel(toyspn).polynomial_system() - Polynomial Sequence with 80 Polynomials in 48 Variables + Polynomial Sequence with 74 Polynomials in 42 Variables sage: from claasp.ciphers.block_ciphers.fancy_block_cipher import FancyBlockCipher sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel @@ -165,32 +165,55 @@ def polynomial_system(self): sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel sage: speck = SpeckBlockCipher(number_of_rounds=2) sage: AlgebraicModel(speck).polynomial_system() - Polynomial Sequence with 288 Polynomials in 352 Variables + Polynomial Sequence with 192 Polynomials in 256 Variables sage: from claasp.ciphers.block_ciphers.aes_block_cipher import AESBlockCipher sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel sage: aes = AESBlockCipher(word_size=4, state_size=2, number_of_rounds=1) sage: AlgebraicModel(aes).polynomial_system() - Polynomial Sequence with 198 Polynomials in 128 Variables + Polynomial Sequence with 174 Polynomials in 104 Variables sage: from claasp.ciphers.block_ciphers.tea_block_cipher import TeaBlockCipher sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel sage: tea = TeaBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=1) sage: AlgebraicModel(tea).polynomial_system() - Polynomial Sequence with 352 Polynomials in 448 Variables + Polynomial Sequence with 288 Polynomials in 384 Variables + + sage: from claasp.ciphers.permutations.gift_permutation import GiftPermutation + sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel + sage: gift = GiftPermutation(number_of_rounds=1) + sage: AlgebraicModel(gift).polynomial_system() + Polynomial Sequence with 448 Polynomials in 640 Variables + """ polynomials = [] - constant_vars = {} - for r in range(self._cipher.number_of_rounds): - polynomials += self.polynomial_system_at_round(r, True) - constant_vars.update(self._dict_constant_component_polynomials(r)) - if constant_vars is not None: - polynomials = self._remove_constant_polynomials(constant_vars, polynomials) + dict_vars = {} + + for round_number in range(self._cipher.number_of_rounds): + polynomials += self.polynomial_system_at_round(round_number, True) + dict_vars.update(self._dict_const_rot_not_shift_component_polynomials(round_number)) + if round_number == self._cipher.number_of_rounds - 1 and dict_vars: + dict_vars = self._substitute_cipher_output_vars_dict_vars(dict_vars, round_number) + if dict_vars: + polynomials = self._eliminate_const_not_shift_rot_components_polynomials(dict_vars, polynomials) return Sequence(polynomials) - def polynomial_system_at_round(self, r, fun_call_flag=False): + def _substitute_cipher_output_vars_dict_vars(self, dict_vars, round_number): + cipher_dict = {} + cipher_component = self._cipher.get_components_in_round(round_number)[-1] + input_vars, prev_input_vars = self._input_vars_previous_input_vars(cipher_component) + cipher_dict.update({y: x for x, y in zip(input_vars, prev_input_vars)}) + sub_dict_vars = {} + for k, val in dict_vars.items(): + if val not in {0, 1}: + sub_dict_vars[k] = val.subs(cipher_dict) + else: + sub_dict_vars[k] = val + return sub_dict_vars + + def polynomial_system_at_round(self, r, method_call_flag=False): """ Return a polynomial system at round `r`. @@ -226,11 +249,12 @@ def polynomial_system_at_round(self, r, fun_call_flag=False): polynomials = self._apply_connection_variable_mapping(Sequence(polynomials), r) - if fun_call_flag is False: - constant_vars = self._dict_constant_component_polynomials(r) - if constant_vars is not None: - polynomials = self._remove_constant_polynomials(constant_vars, polynomials) - + if method_call_flag is False: + dict_vars = self._dict_const_rot_not_shift_component_polynomials(r) + if r == self._cipher.number_of_rounds - 1 and dict_vars: + dict_vars = self._substitute_cipher_output_vars_dict_vars(dict_vars, r) + if dict_vars: + polynomials = self._eliminate_const_not_shift_rot_components_polynomials(dict_vars, polynomials) return Sequence(polynomials) def _apply_connection_variable_mapping(self, polys, r): @@ -239,7 +263,6 @@ def _apply_connection_variable_mapping(self, polys, r): return polys variable_substitution_dict = {} - for component in self._cipher.get_components_in_round(r): if component.type == "constant": continue @@ -248,7 +271,6 @@ def _apply_connection_variable_mapping(self, polys, r): variable_substitution_dict.update({x: y for x, y in zip(input_vars, prev_input_vars)}) else: variable_substitution_dict.update({y: x for x, y in zip(input_vars, prev_input_vars)}) - polys = polys.subs(variable_substitution_dict) return polys @@ -266,25 +288,45 @@ def _input_vars_previous_input_vars(self, component): prev_input_vars = list(map(self.ring(), prev_input_vars)) return input_vars, prev_input_vars - def _dict_constant_component_polynomials(self, round_number): + def _dict_const_rot_not_shift_component_polynomials(self, round_number): - constant_vars = {} + dict_vars = {} + word_operation = ["ROTATE", "SHIFT", "NOT"] for component in self._cipher.get_components_in_round(round_number): - if component.type == "constant": - output_vars = [component.id + "_" + self.output_postfix + str(i) for i in - range(component.output_bit_size)] - else: - continue - output_vars = list(map(self.ring(), output_vars)) - constant = int(component.description[0], 16) - b = list(map(int, reversed(bin(constant)[2:]))) - b += [0] * (component.output_bit_size - len(b)) - constant_vars.update({x: y for x, y in zip(output_vars, b)}) - return constant_vars - - def _remove_constant_polynomials(self, constant_vars, polys): - - polys = Sequence(polys).subs(constant_vars) + if component.type == "constant" or ( + component.type == "word_operation" and component.description[0] in word_operation): + x = [component.id + "_" + self.output_postfix + str(i) for i in + range(component.output_bit_size)] + + x = list(map(self.ring(), x)) + input_links = component.input_id_links + input_positions = component.input_bit_positions + y = [] + for k in range(len(input_links)): + y += [input_links[k] + "_" + self.output_postfix + str(i) for i in + input_positions[k]] + y = list(map(self.ring(), y)) + noutputs = component.output_bit_size + if component.type == "constant": + constant = int(component.description[0], 16) + b = list(map(int, reversed(bin(constant)[2:]))) + b += [0] * (noutputs - len(b)) + dict_vars.update({x: y for x, y in zip(x, b)}) + else: + if component.description[0] == 'ROTATE': + rotation_const = component.description[1] + dict_vars.update({x[i]: y[(rotation_const + i) % noutputs] for i in range(len(x))}) + elif component.description[0] == 'SHIFT': + shift_constant = component.description[1] % noutputs + dict_vars.update({x[i]: 0 for i in range(shift_constant)}) + dict_vars.update({x[shift_constant:][i]: y[i] for i in range(noutputs - shift_constant)}) + else: + dict_vars.update({x[i]: y[i] + 1 for i in range(len(x))}) + + return dict_vars + + def _eliminate_const_not_shift_rot_components_polynomials(self, dict_vars, polys): + polys = Sequence(polys).subs(dict_vars) polys = [p for p in polys if p != 0] return polys diff --git a/claasp/cipher_modules/models/cp/cp_model.py b/claasp/cipher_modules/models/cp/cp_model.py index 10dfec59..ea73a703 100644 --- a/claasp/cipher_modules/models/cp/cp_model.py +++ b/claasp/cipher_modules/models/cp/cp_model.py @@ -22,12 +22,15 @@ import itertools import subprocess +from copy import deepcopy + 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 +from claasp.cipher_modules.models.cp.solvers import CP_SOLVERS_INTERNAL, CP_SOLVERS_EXTERNAL, MODEL_DEFAULT_PATH, SOLVER_DEFAULT solve_satisfy = 'solve satisfy;' constraint_type_error = 'Constraint type not defined' @@ -294,13 +297,21 @@ def get_command_for_solver_process(self, input_file_path, model_type, solver_nam 'differential_pair_one_solution', 'evaluate_cipher'] write_model_to_file(self._model_constraints, input_file_path) - if model_type in solvers: - command = ['minizinc', f'-p {num_of_processors}', '--solver-statistics', '--time-limit', str(timelimit), - '--solver', solver_name, input_file_path] - else: - command = ['minizinc', f'-p {num_of_processors}', '-a', '--solver-statistics', - '--time-limit', str(timelimit), '--solver', solver_name, input_file_path] - + for i in range(len(CP_SOLVERS_EXTERNAL)): + if solver_name == CP_SOLVERS_EXTERNAL[i]['solver_name']: + command_options = deepcopy(CP_SOLVERS_EXTERNAL[i]) + command_options['keywords']['command']['input_file'].append(input_file_path) + if model_type not in solvers: + command_options['keywords']['command']['options'].insert(0, '-a') + if num_of_processors is not None: + command_options['keywords']['command']['options'].insert(0, f'-p {num_of_processors}') + if timelimit is not None: + command_options['keywords']['command']['options'].append('--time-limit') + command_options['keywords']['command']['options'].append(str(timelimit)) + command = [] + for key in command_options['keywords']['command']['format']: + command.extend(command_options['keywords']['command'][key]) + return command def get_mix_column_all_inputs(self, input_bit_positions_1, input_id_link_1, numb_of_inp_1): @@ -410,7 +421,7 @@ def set_component_solution_value(self, component_solution, truncated, value): else: component_solution['value'] = value - def solve(self, model_type, solver_name=None, num_of_processors=1, timelimit=60000): + def solve(self, model_type, solver_name=SOLVER_DEFAULT, num_of_processors=None, timelimit=None): """ Return the solution of the model. @@ -452,7 +463,7 @@ def solve(self, model_type, solver_name=None, num_of_processors=1, timelimit=600 'total_weight': '5'}] """ cipher_name = self.cipher_id - input_file_path = f'{cipher_name}_Cp_{model_type}_{solver_name}.mzn' + input_file_path = f'{MODEL_DEFAULT_PATH}/{cipher_name}_Cp_{model_type}_{solver_name}.mzn' command = self.get_command_for_solver_process( input_file_path, model_type, solver_name, num_of_processors, timelimit ) @@ -466,6 +477,7 @@ def solve(self, model_type, solver_name=None, num_of_processors=1, timelimit=600 solution = convert_solver_solution_to_dictionary(self._cipher, model_type, solver_name, solve_time, memory, components_values, total_weight) + print(solution) if 'UNSATISFIABLE' in solver_output[0]: solution['status'] = 'UNSATISFIABLE' else: @@ -481,6 +493,24 @@ def solve(self, model_type, solver_name=None, num_of_processors=1, timelimit=600 return solutions[0] else: return solutions + + def solver_names(self, verbose = False): + if not verbose: + print('Internal CP solvers:') + print('solver brand name | solver name') + for i in range(len(CP_SOLVERS_INTERNAL)): + print(f'{CP_SOLVERS_INTERNAL[i]["solver_brand_name"]} | {CP_SOLVERS_INTERNAL[i]["solver_name"]}') + print('\n') + print('External CP solvers:') + print('solver brand name | solver name') + for i in range(len(CP_SOLVERS_EXTERNAL)): + print(f'{CP_SOLVERS_EXTERNAL[i]["solver_brand_name"]} | {CP_SOLVERS_EXTERNAL[i]["solver_name"]}') + else: + print('Internal CP solvers:') + print(CP_SOLVERS_INTERNAL) + print('\n') + print('External CP solvers:') + print(CP_SOLVERS_EXTERNAL) def weight_constraints(self, weight): """ 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 3b78f4ba..a682191f 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 @@ -26,6 +26,7 @@ from claasp.cipher_modules.models.utils import write_model_to_file, convert_solver_solution_to_dictionary from claasp.name_mappings import (CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, MIX_COLUMN, WORD_OPERATION, DETERMINISTIC_TRUNCATED_XOR_DIFFERENTIAL) +from claasp.cipher_modules.models.cp.solvers import MODEL_DEFAULT_PATH, SOLVER_DEFAULT class CpDeterministicTruncatedXorDifferentialModel(CpModel): @@ -246,7 +247,7 @@ def final_impossible_constraints(self, number_of_rounds=None): return cp_constraints def find_all_deterministic_truncated_xor_differential_trail(self, number_of_rounds=None, - fixed_values=[], solver_name='Chuffed'): + fixed_values=[], solver_name=SOLVER_DEFAULT): """ Return the solution representing a differential trail with any weight. @@ -296,7 +297,7 @@ def find_all_deterministic_truncated_xor_differential_trail(self, number_of_roun return self.solve(DETERMINISTIC_TRUNCATED_XOR_DIFFERENTIAL, solver_name) def find_one_deterministic_truncated_xor_differential_trail(self, number_of_rounds=None, - fixed_values=[], solver_name='Chuffed'): + fixed_values=[], solver_name=SOLVER_DEFAULT): """ Return the solution representing a differential trail with any weight. @@ -610,7 +611,7 @@ def _parse_solver_output(self, output_to_parse, model_type): return time, memory, components_values - def solve(self, model_type, solver_name=None, num_of_processors=1, timelimit=60000): + def solve(self, model_type, solver_name=SOLVER_DEFAULT, num_of_processors=None, timelimit=None): """ Return the solution of the model. @@ -654,7 +655,7 @@ def solve(self, model_type, solver_name=None, num_of_processors=1, timelimit=600 """ cipher_name = self.cipher_id - input_file_path = f'{cipher_name}_Cp_{model_type}_{solver_name}.mzn' + input_file_path = f'{MODEL_DEFAULT_PATH}/{cipher_name}_Cp_{model_type}_{solver_name}.mzn' command = self.get_command_for_solver_process( input_file_path, model_type, solver_name, num_of_processors, timelimit ) diff --git a/claasp/cipher_modules/models/cp/cp_models/cp_impossible_xor_differential_model.py b/claasp/cipher_modules/models/cp/cp_models/cp_impossible_xor_differential_model.py index de983663..1296c411 100644 --- a/claasp/cipher_modules/models/cp/cp_models/cp_impossible_xor_differential_model.py +++ b/claasp/cipher_modules/models/cp/cp_models/cp_impossible_xor_differential_model.py @@ -22,6 +22,7 @@ from claasp.name_mappings import (CONSTANT, INTERMEDIATE_OUTPUT, CIPHER_OUTPUT, LINEAR_LAYER, SBOX, MIX_COLUMN, WORD_OPERATION, DETERMINISTIC_TRUNCATED_XOR_DIFFERENTIAL, IMPOSSIBLE_XOR_DIFFERENTIAL) +from claasp.cipher_modules.models.cp.solvers import SOLVER_DEFAULT class CpImpossibleXorDifferentialModel(CpDeterministicTruncatedXorDifferentialModel): @@ -257,7 +258,7 @@ def final_impossible_constraints(self, number_of_rounds, middle_round): return cp_constraints def find_all_impossible_xor_differential_trails(self, number_of_rounds, - fixed_values=[], solver_name=None, middle_round=1): + fixed_values=[], solver_name=SOLVER_DEFAULT, middle_round=1): """ Return the solution representing a differential trail with any weight. @@ -304,7 +305,7 @@ def find_all_impossible_xor_differential_trails(self, number_of_rounds, return self.solve(IMPOSSIBLE_XOR_DIFFERENTIAL, solver_name) def find_one_impossible_xor_differential_trail(self, number_of_rounds=None, - fixed_values=[], solver_name=None, middle_round=1): + fixed_values=[], solver_name=SOLVER_DEFAULT, middle_round=1): """ Return the solution representing a differential trail with any weight. 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 48f8d6d6..1c3494f0 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 @@ -25,6 +25,7 @@ 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) +from claasp.cipher_modules.models.cp.solvers import SOLVER_DEFAULT def and_xor_differential_probability_ddt(numadd): @@ -198,7 +199,7 @@ def final_xor_differential_constraints(self, weight): return cp_constraints - def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='Chuffed'): + def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name=SOLVER_DEFAULT): """ 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. @@ -246,7 +247,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed return solutions def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_weight=64, fixed_values=[], - solver_name='Chuffed'): + solver_name=SOLVER_DEFAULT): """ Return a list of solutions containing all the differential trails. By default, the search is set in the single-key setting. @@ -298,7 +299,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w return solutions - def find_differential_weight(self, fixed_values=[], solver_name='Chuffed'): + def find_differential_weight(self, fixed_values=[], solver_name=SOLVER_DEFAULT): probability = 0 self.build_xor_differential_trail_model(-1, fixed_values) solutions = self.solve(XOR_DIFFERENTIAL, solver_name) @@ -364,7 +365,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name solution['test_name'] = "find_lowest_weight_xor_differential_trail" return solution - def find_one_xor_differential_trail(self, fixed_values=[], solver_name='Chuffed'): + def find_one_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT): """ Return the solution representing a differential trail with any weight. By default, the search is set in the single-key setting. @@ -412,7 +413,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='Chuffed' return solution def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight=-1, fixed_values=[], - solver_name='Chuffed'): + solver_name=SOLVER_DEFAULT): """ 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. diff --git a/claasp/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_fixing_number_of_active_sboxes_model.py b/claasp/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_fixing_number_of_active_sboxes_model.py index 0555ae13..c173ee33 100644 --- a/claasp/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_fixing_number_of_active_sboxes_model.py +++ b/claasp/cipher_modules/models/cp/cp_models/cp_xor_differential_trail_search_fixing_number_of_active_sboxes_model.py @@ -21,6 +21,9 @@ import math import subprocess import time as tm + +from copy import deepcopy + from sage.crypto.sbox import SBox @@ -31,6 +34,7 @@ CpXorDifferentialModel, update_and_or_ddt_valid_probabilities) from claasp.cipher_modules.models.cp.cp_models.cp_xor_differential_number_of_active_sboxes_model import ( CpXorDifferentialNumberOfActiveSboxesModel) +from claasp.cipher_modules.models.cp.solvers import CP_SOLVERS_EXTERNAL, CP_SOLVERS_INTERNAL, MODEL_DEFAULT_PATH, SOLVER_DEFAULT class CpXorDifferentialFixingNumberOfActiveSboxesModel(CpXorDifferentialModel, @@ -73,7 +77,7 @@ def build_xor_differential_trail_second_step_model(self, weight=-1, fixed_variab self._model_constraints.extend(self.final_xor_differential_constraints(weight)) self._model_constraints = self._model_prefix + self._variables_list + self._model_constraints - def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], first_step_solver_name='Chuffed', second_step_solver_name='Chuffed'): + def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], first_step_solver_name=SOLVER_DEFAULT, second_step_solver_name=SOLVER_DEFAULT): """ Return a list of solutions containing all the differential trails having the ``fixed_weight`` weight of correlation. @@ -109,7 +113,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed """ return self.solve_full_two_steps_xor_differential_model('xor_differential_all_solutions', fixed_weight, fixed_values, first_step_solver_name, second_step_solver_name) - def find_lowest_weight_xor_differential_trail(self, fixed_values=[], first_step_solver_name='Chuffed', second_step_solver_name='Chuffed'): + def find_lowest_weight_xor_differential_trail(self, fixed_values=[], first_step_solver_name=SOLVER_DEFAULT, second_step_solver_name=SOLVER_DEFAULT): """ Return the solution representing a differential trail with the lowest weight. @@ -149,7 +153,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], first_step_ """ return self.solve_full_two_steps_xor_differential_model('xor_differential_one_solution', -1, fixed_values, first_step_solver_name, second_step_solver_name) - def find_one_xor_differential_trail(self, fixed_values=[], first_step_solver_name='Chuffed', second_step_solver_name='Chuffed'): + def find_one_xor_differential_trail(self, fixed_values=[], first_step_solver_name=SOLVER_DEFAULT, second_step_solver_name=SOLVER_DEFAULT): """ Return the solution representing a differential trail with any weight. @@ -183,7 +187,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], first_step_solver_nam """ return self.solve_full_two_steps_xor_differential_model('xor_differential_one_solution', 0, fixed_values, first_step_solver_name, second_step_solver_name) - def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight=-1, fixed_values=[], first_step_solver_name='Chuffed', second_step_solver_name='Chuffed'): + def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight=-1, fixed_values=[], first_step_solver_name=SOLVER_DEFAULT, second_step_solver_name=SOLVER_DEFAULT): """ Return the solution representing a differential trail with any weight. @@ -303,7 +307,7 @@ def input_xor_differential_constraints(self): return cp_declarations, cp_constraints def solve_full_two_steps_xor_differential_model(self, model_type='xor_differential_one_solution', weight=-1, fixed_variables=[], - first_step_solver_name=None, second_step_solver_name=None, nmax=2, repetition=1): + first_step_solver_name=SOLVER_DEFAULT, second_step_solver_name=SOLVER_DEFAULT, nmax=2, repetition=1): """ Return the solution of the model for an SPN cipher. @@ -351,10 +355,14 @@ def solve_full_two_steps_xor_differential_model(self, model_type='xor_differenti self.build_xor_differential_trail_second_step_model(weight, fixed_variables) end = tm.time() build_time += end - start - input_file_name = f'{cipher_name}_Cp_xor_differential_{first_step_solver_name}.mzn' - solution_file_name = f'{cipher_name}_table_of_solutions_{first_step_solver_name}.mzn' + input_file_name = f'{MODEL_DEFAULT_PATH}/{cipher_name}_Cp_xor_differential_{first_step_solver_name}.mzn' + solution_file_name = f'{MODEL_DEFAULT_PATH}/{cipher_name}_table_of_solutions_{first_step_solver_name}.mzn' write_model_to_file(self._model_constraints, input_file_name) + for i in range(len(CP_SOLVERS_EXTERNAL)): + if second_step_solver_name == CP_SOLVERS_EXTERNAL[i]['solver_name']: + command_options = deepcopy(CP_SOLVERS_EXTERNAL[i]) + for attempt in range(10000): if weight == -1: start = tm.time() @@ -365,16 +373,30 @@ def solve_full_two_steps_xor_differential_model(self, model_type='xor_differenti 'xor_differential_first_step_find_all_solutions', first_step_solver_name) solve_time += solve_first_step_time self.generate_table_of_solutions(first_step_all_solutions, first_step_solver_name) - command = ['minizinc', '-a', '--solver-statistics', '--solver', - second_step_solver_name, input_file_name, solution_file_name] + + command_options['keywords']['command']['input_file'].append(input_file_name) + command_options['keywords']['command']['output_file'].append(solution_file_name) + command_options['keywords']['command']['options'].insert(0, '-a') + command = [] + for key in command_options['keywords']['command']['format']: + command.extend(command_options['keywords']['command'][key]) elif model_type == 'xor_differential_all_solutions': self.generate_table_of_solutions(first_step_solution, first_step_solver_name) - command = ['minizinc', '-a', '--solver-statistics', '--solver', second_step_solver_name, - input_file_name, solution_file_name] + + command_options['keywords']['command']['input_file'].append(input_file_name) + command_options['keywords']['command']['output_file'].append(solution_file_name) + command_options['keywords']['command']['options'].insert(0, '-a') + command = [] + for key in command_options['keywords']['command']['format']: + command.extend(command_options['keywords']['command'][key]) else: self.generate_table_of_solutions(first_step_solution, first_step_solver_name) - command = ['minizinc', '--solver-statistics', '--solver', second_step_solver_name, - input_file_name, solution_file_name] + + command_options['keywords']['command']['input_file'].append(input_file_name) + command_options['keywords']['command']['output_file'].append(solution_file_name) + command = [] + for key in command_options['keywords']['command']['format']: + command.extend(command_options['keywords']['command'][key]) solver_process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8") if solver_process.returncode < 0: @@ -433,16 +455,25 @@ def solve_model(self, model_type, solver_name=None): """ start = tm.time() cipher_name = self.cipher_id - input_file_name = f'{cipher_name}_Cp_{model_type}_{solver_name}.mzn' + input_file_name = f'{MODEL_DEFAULT_PATH}/{cipher_name}_Cp_{model_type}_{solver_name}.mzn' + for i in range(len(CP_SOLVERS_EXTERNAL)): + if solver_name == CP_SOLVERS_EXTERNAL[i]['solver_name']: + command_options = deepcopy(CP_SOLVERS_EXTERNAL[i]) + command_options['keywords']['command']['input_file'].append(input_file_name) + if model_type == 'xor_differential_first_step_find_all_solutions': write_model_to_file(self._first_step_find_all_solutions, input_file_name) - command = ['minizinc', '-a', '--solver', solver_name, input_file_name] + command_options['keywords']['command']['options'].insert(0, '-a') else: if model_type == 'xor_differential_first_step': write_model_to_file(self._first_step, input_file_name) else: write_model_to_file(self._model_constraints, input_file_name) - command = ['minizinc', '--solver', solver_name, input_file_name] + + command = [] + for key in command_options['keywords']['command']['format']: + command.extend(command_options['keywords']['command'][key]) + command.remove('--solver-statistics') solver_process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8') os.remove(input_file_name) solution = [] @@ -488,6 +519,7 @@ def transform_first_step_model(self, attempt, active_sboxes, weight=-1): sage: first_step_solution, solve_time = cp.solve_model('xor_differential_first_step','Chuffed') sage: cp.transform_first_step_model(0, first_step_solution[0]) """ + print(active_sboxes) self._first_step_find_all_solutions = [] for line in self._first_step: if ': number_of_active_sBoxes;' in line: 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 3b33c7d3..d60bb92a 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 @@ -27,6 +27,7 @@ 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, INPUT_KEY +from claasp.cipher_modules.models.cp.solvers import SOLVER_DEFAULT class CpXorLinearModel(CpModel): @@ -215,7 +216,7 @@ def final_xor_linear_constraints(self, weight): return cp_constraints - def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='Chuffed'): + def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name=SOLVER_DEFAULT): """ 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. @@ -262,7 +263,7 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value return solutions def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight=64, - fixed_values=[], solver_name='Chuffed'): + fixed_values=[], solver_name=SOLVER_DEFAULT): """ 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. @@ -311,7 +312,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight= return solutions - def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='Chuffed'): + def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT): """ Return the solution representing a linear trail with the lowest weight of correlation. By default, the search removes the key schedule, if any. @@ -361,7 +362,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='Chuf return solution - def find_one_xor_linear_trail(self, fixed_values=[], solver_name='Chuffed'): + def find_one_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT): """ Return the solution representing a linear trail with any weight of correlation. By default, the search removes the key schedule, if any. @@ -402,7 +403,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='Chuffed'): return solution - def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight=-1, fixed_values=[], solver_name='Chuffed'): + def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight=-1, fixed_values=[], solver_name=SOLVER_DEFAULT): """ 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. @@ -612,8 +613,9 @@ def update_sbox_lat_valid_probabilities(self, component, valid_probabilities): for i in range(sbox_lat.nrows()): set_of_occurrences = set(sbox_lat.rows()[i]) set_of_occurrences -= {0} - valid_probabilities.update({round(100 * math.log2(2 ** input_size / abs(occurrence))) - for occurrence in set_of_occurrences}) + valid_probabilities.update( + {round(100 * math.log2(abs(pow(2, input_size - 1) / occurence))) for occurence in + set_of_occurrences}) self.sbox_mant.append((description, output_id_link)) def weight_xor_linear_constraints(self, weight): diff --git a/claasp/cipher_modules/models/cp/solvers.py b/claasp/cipher_modules/models/cp/solvers.py new file mode 100644 index 00000000..23270418 --- /dev/null +++ b/claasp/cipher_modules/models/cp/solvers.py @@ -0,0 +1,97 @@ + +# **************************************************************************** +# Copyright 2023 Technology Innovation Institute +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# **************************************************************************** + +import os + +SOLVER_DEFAULT = 'Chuffed' +MODEL_DEFAULT_PATH = os.getcwd() + +CP_SOLVERS_INTERNAL = [] + +CP_SOLVERS_EXTERNAL = [ + { + 'solver_brand_name': 'Chuffed', + 'solver_name': 'Chuffed', # keyword to call the solver + 'keywords': { + 'command': { + 'executable': ['minizinc'], + 'options': ['--solver-statistics'], + 'input_file': [], + 'output_file': [], + 'solver': ['--solver', 'Chuffed'], + 'format': ['executable', 'options', 'solver', 'input_file', 'output_file'], + }, + }, + }, + { + 'solver_brand_name': 'Gecode', + 'solver_name': 'Gecode', # keyword to call the solver + 'keywords': { + 'command': { + 'executable': ['minizinc'], + 'options': ['--solver-statistics'], + 'input_file': [], + 'output_file': [], + 'solver': ['--solver', 'Gecode'], + 'format': ['executable', 'options', 'solver', 'input_file', 'output_file'], + }, + }, + }, + { + 'solver_brand_name': 'OR Tools', + 'solver_name': 'Xor', # keyword to call the solver + 'keywords': { + 'command': { + 'executable': ['minizinc'], + 'options': ['--solver-statistics'], + 'input_file': [], + 'output_file': [], + 'solver': ['--solver', 'Xor'], + 'format': ['executable', 'options', 'solver', 'input_file', 'output_file'], + }, + }, + }, + { + 'solver_brand_name': 'COIN-BC', + 'solver_name': 'COIN-BC', # keyword to call the solver + 'keywords': { + 'command': { + 'executable': ['minizinc'], + 'options': ['--solver-statistics'], + 'input_file': [], + 'output_file': [], + 'solver': ['--solver', 'COIN-BC'], + 'format': ['executable', 'options', 'solver', 'input_file', 'output_file'], + }, + }, + }, + { + 'solver_brand_name': 'Choco', + 'solver_name': 'choco', # keyword to call the solver + 'keywords': { + 'command': { + 'executable': ['minizinc'], + 'options': ['--solver-statistics'], + 'input_file': [], + 'output_file': [], + 'solver': ['--solver', 'choco'], + 'format': ['executable', 'options', 'solver', 'input_file', 'output_file'], + }, + }, + }, +] diff --git a/claasp/cipher_modules/models/milp/milp_model.py b/claasp/cipher_modules/models/milp/milp_model.py index 6b40439e..9b384088 100644 --- a/claasp/cipher_modules/models/milp/milp_model.py +++ b/claasp/cipher_modules/models/milp/milp_model.py @@ -45,6 +45,7 @@ from claasp.cipher_modules.models.milp.solvers import SOLVER_DEFAULT, MODEL_DEFAULT_PATH, MILP_SOLVERS_EXTERNAL, \ MILP_SOLVERS_INTERNAL +from claasp.cipher_modules.models.milp.utils.milp_name_mappings import MILP_DEFAULT_WEIGHT_PRECISION from claasp.cipher_modules.models.milp.utils.utils import _get_data, _parse_external_solver_output, _write_model_to_lp_file from claasp.cipher_modules.models.utils import convert_solver_solution_to_dictionary @@ -205,13 +206,14 @@ def fix_variables_value_constraints(self, fixed_variables=[]): return constraints - def weight_constraints(self, weight): + def weight_constraints(self, weight, weight_precision=MILP_DEFAULT_WEIGHT_PRECISION): """ Return a list of variables and a list of constraints that fix the total weight to a specific value. INPUT: - ``weight`` -- **integer**; the total weight. If negative, no constraints on the weight is added + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. EXAMPLES:: @@ -231,10 +233,10 @@ def weight_constraints(self, weight): constraints = [] if weight >= 0: - constraints.append(p["probability"] == 10 * weight) + constraints.append(p["probability"] == (10 ** weight_precision) * weight) variables = [("p[probability]", p["probability"])] elif weight != -1: - self._model.set_max(p["probability"], - 10 * weight) + self._model.set_max(p["probability"], - (10 ** weight_precision) * weight) variables = [("p[probability]", p["probability"])] return variables, constraints 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 81be9427..1d21f917 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 @@ -18,13 +18,15 @@ import sys import time +import numpy as np from bitstring import BitArray from claasp.cipher_modules.models.milp.solvers import SOLVER_DEFAULT from claasp.cipher_modules.models.milp.milp_model import MilpModel 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, _filter_fixed_variables + MILP_BUILDING_MESSAGE, MILP_XOR_DIFFERENTIAL_OBJECTIVE, MILP_DEFAULT_WEIGHT_PRECISION +from claasp.cipher_modules.models.milp.utils.utils import _string_to_hex, _get_variables_values_as_string, \ + _filter_fixed_variables, _set_weight_precision 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, @@ -35,8 +37,11 @@ class MilpXorDifferentialModel(MilpModel): def __init__(self, cipher, n_window_heuristic=None, verbose=False): super().__init__(cipher, n_window_heuristic, verbose) + self._weight_precision = MILP_DEFAULT_WEIGHT_PRECISION + self._has_non_integer_weight = False - def add_constraints_to_build_in_sage_milp_class(self, weight=-1, fixed_variables=[]): + def add_constraints_to_build_in_sage_milp_class(self, weight=-1, weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, + fixed_variables=[]): """ Take the constraints contained in self._model_constraints and add them to the build-in sage class. @@ -45,6 +50,7 @@ def add_constraints_to_build_in_sage_milp_class(self, weight=-1, fixed_variables - ``model_type`` -- **string**; the model to solve - ``weight`` -- **integer** (default: `-1`); the total weight. If negative, no constraints on the weight is added + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``fixed_variables`` -- **list** (default: `[]`); dictionaries containing the variables to be fixed in standard format @@ -66,6 +72,7 @@ def add_constraints_to_build_in_sage_milp_class(self, weight=-1, fixed_variables 468 """ self._verbose_print(MILP_BUILDING_MESSAGE) + self._weight_precision = weight_precision self.build_xor_differential_trail_model(weight, fixed_variables) mip = self._model p = self._integer_variable @@ -119,11 +126,12 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): self._model_constraints.extend(constraints) if weight != -1: - variables, constraints = self.weight_constraints(weight) + variables, constraints = self.weight_constraints(weight, self._weight_precision) self._variables_list.extend(variables) self._model_constraints.extend(constraints) def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], + weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, 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. @@ -143,6 +151,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed - ``fixed_weight`` -- **integer**; the weight found using :py:meth:`~find_lowest_weight_xor_differential_trail` - ``fixed_values`` -- **list** (default: `[]`); each dictionary contains variables values whose output need to be fixed + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) EXAMPLES:: @@ -174,9 +183,9 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed self._verbose_print(f"Solver used : {solver_name} (Choose Gurobi for Better performance)") mip = self._model mip.set_objective(None) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) number_new_constraints = 0 - variables, constraints = self.weight_constraints(fixed_weight) + _, constraints = self.weight_constraints(fixed_weight, weight_precision) for constraint in constraints: mip.add_constraint(constraint) number_new_constraints += len(constraints) @@ -186,7 +195,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed if fixed_values == []: fixed_values = get_single_key_scenario_format_for_fixed_values(self._cipher) - if self.is_single_key(fixed_values): + if INPUT_KEY in self._cipher.inputs and self.is_single_key(fixed_values): inputs_ids = [i for i in self._cipher.inputs if INPUT_KEY not in i] else: inputs_ids = self._cipher.inputs @@ -314,17 +323,18 @@ def is_single_key(self, fixed_values): """ cipher_inputs = self._cipher.inputs cipher_inputs_bit_size = self._cipher.inputs_bit_size - for fixed_input in fixed_values: + for fixed_input in [value for value in fixed_values if value['component_id'] in cipher_inputs]: input_size = cipher_inputs_bit_size[cipher_inputs.index(fixed_input['component_id'])] if fixed_input['component_id'] == 'key' and fixed_input['constraint_type'] == 'equal' \ - and fixed_input['bit_positions'] == list(range(input_size)) \ + and list(fixed_input['bit_positions']) == list(range(input_size)) \ and all(v == 0 for v in fixed_input['bit_values']): return True return False def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_weight, - fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): + fixed_values=[], weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, + 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. @@ -345,6 +355,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w - ``max_weight`` -- **integer**; the upper bound for the weight. - ``fixed_values`` -- **list** (default: `[]`); each dictionary contains variables values whose output need to be fixed + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) - ``external_solver_name`` -- **string** (default: None); if specified, the library will write the internal Sagemath MILP model as a .lp file and solve it outside of Sagemath, using the external solver. @@ -377,20 +388,21 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w self._verbose_print(f"Solver used : {solver_name} (Choose Gurobi for Better performance)") mip = self._model mip.set_objective(None) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, 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): + if INPUT_KEY in self._cipher.inputs and self.is_single_key(fixed_values): 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): + precision = _set_weight_precision(self, "differential") + for weight in np.arange(min_weight, max_weight + 1, precision): looking_for_other_solutions = 1 - variables, weight_constraints = self.weight_constraints(weight) + _, weight_constraints = self.weight_constraints(weight, weight_precision) for constraint in weight_constraints: mip.add_constraint(constraint) number_new_constraints = len(weight_constraints) @@ -421,8 +433,8 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w return [trail for trail in list_trails if trail['status'] == 'SATISFIABLE'] - def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, - external_solver_name=False): + def find_lowest_weight_xor_differential_trail(self, fixed_values=[], weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, + solver_name=SOLVER_DEFAULT, 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. @@ -435,7 +447,9 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name - ``fixed_values`` -- **list** (default: `[]`); each dictionary contains variables values whose output need to be fixed + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) + - ``external_solver_name`` -- **string** (default: None); if specified, the library will write the internal Sagemath MILP model as a .lp file and solve it outside of Sagemath, using the external solver. EXAMPLES:: @@ -468,7 +482,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name p = self._integer_variable mip.set_objective(p[MILP_XOR_DIFFERENTIAL_OBJECTIVE]) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) end = time.time() building_time = end - start solution = self.solve(MILP_XOR_DIFFERENTIAL, solver_name, external_solver_name) @@ -477,7 +491,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name return solution - def find_one_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): + def find_one_xor_differential_trail(self, fixed_values=[], weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, 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. @@ -486,6 +500,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DE - ``fixed_values`` -- **list** (default: `[]`); dictionaries containing the variables to be fixed in standard format + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `GLPK`); the solver to call - ``external_solver_name`` -- **string** (default: None); if specified, the library will write the internal Sagemath MILP model as a .lp file and solve it outside of Sagemath, using the external solver. @@ -516,7 +531,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DE self._verbose_print(f"Solver used : {solver_name} (Choose Gurobi for Better performance)") mip = self._model mip.set_objective(None) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) end = time.time() building_time = end - start solution = self.solve(MILP_XOR_DIFFERENTIAL, solver_name, external_solver_name) @@ -525,7 +540,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DE return solution - def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], + def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, 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. @@ -536,6 +551,7 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ - ``fixed_weight`` -- **integer**; the weight found using :py:meth:`~find_lowest_weight_xor_differential_trail` - ``fixed_values`` -- **list** (default: `[]`); dictionaries containing the variables to be fixed in standard format + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `GLPK`); the solver to call - ``external_solver_name`` -- **string** (default: None); if specified, the library will write the internal Sagemath MILP model as a .lp file and solve it outside of Sagemath, using the external solver. @@ -570,8 +586,8 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ self._verbose_print(f"Solver used : {solver_name} (Choose Gurobi for Better performance)") mip = self._model mip.set_objective(None) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) - variables, constraints = self.weight_constraints(fixed_weight) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) + _, constraints = self.weight_constraints(fixed_weight, weight_precision) for constraint in constraints: mip.add_constraint(constraint) end = time.time() @@ -620,7 +636,7 @@ def _get_component_values(self, objective_variables, components_variables): def _parse_solver_output(self): mip = self._model objective_variables = mip.get_values(self._integer_variable) - objective_value = objective_variables[MILP_XOR_DIFFERENTIAL_OBJECTIVE] / 10. + objective_value = objective_variables[MILP_XOR_DIFFERENTIAL_OBJECTIVE] / float(10 ** self._weight_precision) components_variables = mip.get_values(self._binary_variable) components_values = self._get_component_values(objective_variables, components_variables) @@ -648,7 +664,10 @@ def _get_final_output(self, component_id, components_variables, probability_vari difference = _string_to_hex(diff_str) weight = 0 if component_id + MILP_PROBABILITY_SUFFIX in probability_variables: - weight = probability_variables[component_id + MILP_PROBABILITY_SUFFIX] / 10. + weight = probability_variables[component_id + MILP_PROBABILITY_SUFFIX] / float(10 ** self._weight_precision) final_output.append(set_component_solution(value=difference, weight=weight)) return final_output + @property + def weight_precision(self): + return self._weight_precision 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 fea4b3a9..717ab946 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 @@ -20,6 +20,7 @@ import os import sys +import numpy as np from bitstring import BitArray from claasp.cipher_modules.models.milp.solvers import SOLVER_DEFAULT @@ -28,9 +29,9 @@ output_dictionary_that_contains_xor_inequalities from claasp.cipher_modules.models.milp.milp_model import MilpModel from claasp.cipher_modules.models.milp.utils.milp_name_mappings import MILP_XOR_LINEAR, MILP_PROBABILITY_SUFFIX, \ - MILP_BUILDING_MESSAGE, MILP_XOR_LINEAR_OBJECTIVE + MILP_BUILDING_MESSAGE, MILP_XOR_LINEAR_OBJECTIVE, MILP_DEFAULT_WEIGHT_PRECISION from claasp.cipher_modules.models.milp.utils.utils import _get_variables_values_as_string, _string_to_hex, \ - _filter_fixed_variables + _filter_fixed_variables, _set_weight_precision from claasp.cipher_modules.models.utils import get_bit_bindings, set_fixed_variables, integer_to_bit_list, \ 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, @@ -41,8 +42,11 @@ class MilpXorLinearModel(MilpModel): def __init__(self, cipher, n_window_heuristic=None, verbose=False): super().__init__(cipher, n_window_heuristic, verbose) self.bit_bindings, self.bit_bindings_for_intermediate_output = get_bit_bindings(cipher, '_'.join) + self._weight_precision = MILP_DEFAULT_WEIGHT_PRECISION + self._has_non_integer_weight = False - def add_constraints_to_build_in_sage_milp_class(self, weight=-1, fixed_variables=[]): + def add_constraints_to_build_in_sage_milp_class(self, weight=-1, weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, + fixed_variables=[]): """ Take the constraints contained in self._model_constraints and add them to the build-in sage class. @@ -51,6 +55,7 @@ def add_constraints_to_build_in_sage_milp_class(self, weight=-1, fixed_variables - ``model_type`` -- **string**; the model to solve - ``weight`` -- **integer** (default: `-1`); the total weight. It is the negative base-2 logarithm of the total correlation of the trail. If negative, no constraints on the weight is added + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``fixed_variables`` -- **list** (default: `[]`); dictionaries containing the variables to be fixed in standard format @@ -72,6 +77,7 @@ def add_constraints_to_build_in_sage_milp_class(self, weight=-1, fixed_variables 1018 """ self._verbose_print(MILP_BUILDING_MESSAGE) + self._weight_precision = weight_precision self.build_xor_linear_trail_model(weight, fixed_variables) mip = self._model p = self._integer_variable @@ -183,7 +189,7 @@ def build_xor_linear_trail_model(self, weight=-1, fixed_variables=[]): self._model_constraints.extend(constraints) if weight != -1: - variables, constraints = self.weight_xor_linear_constraints(weight) + variables, constraints = self.weight_xor_linear_constraints(weight, self._weight_precision) self._variables_list.extend(variables) self._model_constraints.extend(constraints) @@ -252,7 +258,9 @@ 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=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): + def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], + weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, + 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. @@ -272,6 +280,7 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value - ``fixed_weight`` -- **integer**; the weight found using :py:meth:`~find_lowest_weight_xor_linear_trail` - ``fixed_values`` -- **list** (default: `[]`); each dictionary contains variables values whose output need to be fixed + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) EXAMPLES:: @@ -303,8 +312,8 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value mip = self._model mip.set_objective(None) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) - _, constraints = self.weight_xor_linear_constraints(fixed_weight) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) + _, constraints = self.weight_xor_linear_constraints(fixed_weight, weight_precision) for constraint in constraints: mip.add_constraint(constraint) number_new_constraints = len(constraints) @@ -345,6 +354,7 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value return [trail for trail in list_trails if trail['status'] == 'SATISFIABLE'] def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, fixed_values=[], + weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, 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``. @@ -367,7 +377,9 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, - ``max_weight`` -- **integer**; the upper bound for the weight - ``fixed_values`` -- **list** (default: `[]`); each dictionary contains variables values whose output need to be fixed + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) + - ``external_solver_name`` -- **string** (default: None); if specified, the library will write the internal Sagemath MILP model as a .lp file and solve it outside of Sagemath, using the external solver. EXAMPLES:: @@ -397,15 +409,16 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, self._verbose_print(f"Solver used : {solver_name} (Choose Gurobi for Better performance)") mip = self._model mip.set_objective(None) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) end = time.time() building_time = end - start inputs_ids = self._cipher.inputs list_trails = [] - for weight in range(min_weight, max_weight + 1): + precision = _set_weight_precision(self, "linear") + for weight in np.arange(min_weight, max_weight + 1, precision): looking_for_other_solutions = 1 - _, weight_constraints = self.weight_xor_linear_constraints(weight) + _, weight_constraints = self.weight_xor_linear_constraints(weight, weight_precision) for constraint in weight_constraints: mip.add_constraint(constraint) number_new_constraints = len(weight_constraints) @@ -436,7 +449,8 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, return [trail for trail in list_trails if trail['status'] == 'SATISFIABLE'] - def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): + def find_lowest_weight_xor_linear_trail(self, fixed_values=[], weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, + 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. @@ -453,7 +467,9 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVE - ``fixed_values`` -- **list** (default: `[]`); each dictionary contains variables values whose output need to be fixed + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) + - ``external_solver_name`` -- **string** (default: None); if specified, the library will write the internal Sagemath MILP model as a .lp file and solve it outside of Sagemath, using the external solver. EXAMPLES:: @@ -498,7 +514,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVE mip = self._model p = self._integer_variable mip.set_objective(p[MILP_XOR_LINEAR_OBJECTIVE]) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) end = time.time() building_time = end - start solution = self.solve(MILP_XOR_LINEAR, solver_name, external_solver_name) @@ -507,7 +523,8 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=SOLVE return solution - def find_one_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, external_solver_name=None): + def find_one_xor_linear_trail(self, fixed_values=[], weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, + 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. @@ -517,7 +534,9 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, - ``fixed_values`` -- **list** (default: `[]`); dictionaries containing the variables to be fixed in standard format (see ) - - ``solver_name`` -- **string** (default: `GLPK`); the solver to call + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. + - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) + - ``external_solver_name`` -- **string** (default: None); if specified, the library will write the internal Sagemath MILP model as a .lp file and solve it outside of Sagemath, using the external solver. .. SEEALSO:: @@ -545,7 +564,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, self._verbose_print(f"Solver used : {solver_name} (Choose Gurobi for Better performance)") mip = self._model mip.set_objective(None) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) end = time.time() building_time = end - start solution = self.solve(MILP_XOR_LINEAR, solver_name, external_solver_name) @@ -555,6 +574,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name=SOLVER_DEFAULT, return solution def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], + weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, 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. @@ -566,7 +586,9 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values - ``fixed_weight`` -- **integer**; the weight found using :py:meth:`~find_lowest_weight_xor_linear_trail` - ``fixed_values`` -- **list** (default: `[]`); dictionaries containing the variables to be fixed in standard format - - ``solver_name`` -- **string** (default: `GLPK`); the solver to call + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. + - ``solver_name`` -- **string** (default: `GLPK`); the name of the solver (if needed) + - ``external_solver_name`` -- **string** (default: None); if specified, the library will write the internal Sagemath MILP model as a .lp file and solve it outside of Sagemath, using the external solver. .. SEEALSO:: @@ -598,8 +620,8 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values self._verbose_print(f"Solver used : {solver_name} (Choose Gurobi for Better performance)") mip = self._model mip.set_objective(None) - self.add_constraints_to_build_in_sage_milp_class(-1, fixed_values) - _, constraints = self.weight_xor_linear_constraints(fixed_weight) + self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) + _, constraints = self.weight_xor_linear_constraints(fixed_weight, weight_precision) for constraint in constraints: mip.add_constraint(constraint) end = time.time() @@ -722,7 +744,7 @@ def update_xor_linear_constraints_for_more_than_two_bits(self, constraints, inpu constraint += x[output_var] constraints.append(constraint >= 1) - def weight_xor_linear_constraints(self, weight): + def weight_xor_linear_constraints(self, weight, weight_precision): """ Return a list of variables and a list of constraints that fix the total weight to a specific value. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. @@ -731,6 +753,7 @@ def weight_xor_linear_constraints(self, weight): - ``weight`` -- **integer**; the total weight. By default, it is the negative base-2 logarithm of the total correlation of the trail. + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. EXAMPLES:: @@ -745,7 +768,7 @@ def weight_xor_linear_constraints(self, weight): sage: constraints [x_0 == 100] """ - return self.weight_constraints(weight) + return self.weight_constraints(weight, weight_precision) def _get_component_values(self, objective_variables, components_variables): components_values = {} @@ -765,7 +788,7 @@ def _get_component_values(self, objective_variables, components_variables): def _parse_solver_output(self): mip = self._model objective_variables = mip.get_values(self._integer_variable) - objective_value = objective_variables[MILP_XOR_LINEAR_OBJECTIVE] / 10. + objective_value = objective_variables[MILP_XOR_LINEAR_OBJECTIVE] / float(10 ** self._weight_precision) components_variables = mip.get_values(self._binary_variable) components_values = self._get_component_values(objective_variables, components_variables) @@ -795,6 +818,10 @@ def _get_final_output(self, component_id, components_variables, probability_vari mask = _string_to_hex(mask_str) bias = 0 if component_id + MILP_PROBABILITY_SUFFIX in probability_variables: - bias = probability_variables[component_id + MILP_PROBABILITY_SUFFIX] / 10. + bias = probability_variables[component_id + MILP_PROBABILITY_SUFFIX] / float(10 ** self._weight_precision) final_output.append(set_component_solution(mask, bias, sign=1)) return final_output + + @property + def weight_precision(self): + return self._weight_precision \ No newline at end of file diff --git a/claasp/cipher_modules/models/milp/utils/generate_inequalities_for_large_sboxes.py b/claasp/cipher_modules/models/milp/utils/generate_inequalities_for_large_sboxes.py index 3abd8f31..462c1276 100644 --- a/claasp/cipher_modules/models/milp/utils/generate_inequalities_for_large_sboxes.py +++ b/claasp/cipher_modules/models/milp/utils/generate_inequalities_for_large_sboxes.py @@ -37,8 +37,8 @@ def generate_espresso_input(input_size, output_size, value, valid_transformations_matrix): # little_endian - def to_bits(x): - return ZZ(x).digits(base=2, padto=input_size)[::-1] + def to_bits(x, size): + return ZZ(x).digits(base=2, padto=size)[::-1] espresso_input = [f"# there are {input_size + output_size} input variables\n"] espresso_input.append(f".i {input_size + output_size}") @@ -49,7 +49,7 @@ def to_bits(x): n, m = input_size, output_size for i in range(0, 1 << n): for o in range(0, 1 << m): - io = "".join([str(i) for i in to_bits(i) + to_bits(o)]) + io = "".join([str(i) for i in to_bits(i, input_size) + to_bits(o, output_size)]) if i + o > 0 and valid_transformations_matrix[i][o] == value: espresso_input.append(f"{io} 1\n") else: diff --git a/claasp/cipher_modules/models/milp/utils/milp_name_mappings.py b/claasp/cipher_modules/models/milp/utils/milp_name_mappings.py index e650e20e..b175dd44 100644 --- a/claasp/cipher_modules/models/milp/utils/milp_name_mappings.py +++ b/claasp/cipher_modules/models/milp/utils/milp_name_mappings.py @@ -14,4 +14,5 @@ MILP_BUILDING_MESSAGE = "Building model in progress ..." MILP_XOR_DIFFERENTIAL_OBJECTIVE = "probability" MILP_XOR_LINEAR_OBJECTIVE = "probability" -MILP_TRUNCATED_XOR_DIFFERENTIAL_OBJECTIVE = "number_of_unknown_patterns" \ No newline at end of file +MILP_TRUNCATED_XOR_DIFFERENTIAL_OBJECTIVE = "number_of_unknown_patterns" +MILP_DEFAULT_WEIGHT_PRECISION = 2 \ No newline at end of file diff --git a/claasp/cipher_modules/models/milp/utils/utils.py b/claasp/cipher_modules/models/milp/utils/utils.py index c4b0b77c..266633cd 100644 --- a/claasp/cipher_modules/models/milp/utils/utils.py +++ b/claasp/cipher_modules/models/milp/utils/utils.py @@ -20,7 +20,10 @@ from subprocess import run from bitstring import BitArray +from sage.arith.misc import is_power_of_two +from claasp.cipher_modules.models.milp.utils.generate_inequalities_for_large_sboxes import \ + get_dictionary_that_contains_inequalities_for_large_sboxes from claasp.cipher_modules.models.milp.utils.generate_inequalities_for_xor_with_n_input_bits import ( output_dictionary_that_contains_xor_inequalities, update_dictionary_that_contains_xor_inequalities_between_n_input_bits) @@ -31,6 +34,7 @@ MILP_WORDWISE_DETERMINISTIC_TRUNCATED, MILP_BACKWARD_SUFFIX, MILP_TRUNCATED_XOR_DIFFERENTIAL_OBJECTIVE, \ MILP_XOR_DIFFERENTIAL_OBJECTIVE, MILP_BITWISE_IMPOSSIBLE, MILP_WORDWISE_IMPOSSIBLE, MILP_BITWISE_IMPOSSIBLE_AUTO, \ MILP_WORDWISE_IMPOSSIBLE_AUTO +from claasp.name_mappings import SBOX ### -------------------------External solver parsing methods------------------------- ### @@ -97,7 +101,7 @@ def _parse_external_solver_output(model, solver_specs, model_type, solution_file else: components_variables = _get_variables_value(model.binary_variable, read_file) objective_variables = _get_variables_value(model.integer_variable, read_file) - objective_value = objective_variables[MILP_XOR_DIFFERENTIAL_OBJECTIVE] / 10. + objective_value = objective_variables[MILP_XOR_DIFFERENTIAL_OBJECTIVE] / float(10 ** model.weight_precision) components_values = model._get_component_values(objective_variables, components_variables) @@ -714,4 +718,23 @@ def _filter_fixed_variables(fixed_values, fixed_variable, 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 + del fixed_variable["bit_positions"][bit_index] + +def _set_weight_precision(model, analysis_type): + if any(SBOX in item for item in model.non_linear_component_id): + dict_product_of_sum = get_dictionary_that_contains_inequalities_for_large_sboxes(analysis=analysis_type) + for id in model.non_linear_component_id: + sb = tuple(model._cipher.get_component_from_id(id).description) + for proba in dict_product_of_sum[str(sb)].keys(): + if not is_power_of_two(proba): + model._has_non_integer_weight = True + break + else: + continue + break + + if model._has_non_integer_weight: + step = 1 / float(10 ** model.weight_precision) + else: + step = 1 + return step \ No newline at end of file diff --git a/claasp/cipher_modules/models/sat/cms_models/cms_xor_differential_model.py b/claasp/cipher_modules/models/sat/cms_models/cms_xor_differential_model.py index 99248ea8..ae3e0407 100644 --- a/claasp/cipher_modules/models/sat/cms_models/cms_xor_differential_model.py +++ b/claasp/cipher_modules/models/sat/cms_models/cms_xor_differential_model.py @@ -51,8 +51,8 @@ class CmsSatXorDifferentialModel(SatXorDifferentialModel): def __init__(self, cipher, window_size_weight_pr_vars=-1, - counter='sequential', compact=False, window_size_by_round=None): - super().__init__(cipher, window_size_weight_pr_vars, counter, compact, window_size_by_round) + counter='sequential', compact=False): + super().__init__(cipher, window_size_weight_pr_vars, counter, compact) def _add_clauses_to_solver(self, numerical_cnf, solver): """ diff --git a/claasp/cipher_modules/models/sat/sat_model.py b/claasp/cipher_modules/models/sat/sat_model.py index e642ac16..6b3d6770 100644 --- a/claasp/cipher_modules/models/sat/sat_model.py +++ b/claasp/cipher_modules/models/sat/sat_model.py @@ -43,38 +43,11 @@ SAT Solvers ----------- -This module is able to use different SAT solvers. They can be divided in two -categories: external and internal. All over the module, ``solver_name`` -variable can be replaced with a value in the following. - -External SAT solvers need to be installed in the system as they are called -using a subprocess. They and corresponding values for ``solver_name`` variable -are: - - ============================================================== ====================== - SAT solver value - ============================================================== ====================== - `CaDiCal `_ ``'cadical'`` - `CryptoMiniSat `_ ``'cryptominisat'`` - `Glucose `_ ``'glucose'`` - `Glucose-syrup `_ ``'glucose-syrup'`` - `Kissat `_ ``'kissat'`` - `MathSAT `_ ``'mathsat'`` - `Minisat `_ ``'minisat'`` - `Yices-sat `_ ``'yices-sat'`` - ============================================================== ====================== - -Internal SAT solvers should be installed by default. To call them, use the -following values: - - * ``'cryptominisat_sage'`` - * ``'glucose_sage'`` - * ``'glucose-syrup_sage'`` - * ``'LP_sage'`` - * ``'picosat_sage'`` - -For any further information on internal SAT solvers, visit `Abstract SAT solver -`_. +This module is able to use many different SAT solvers. + +For any further information, refer to the file +:py:mod:`claasp.cipher_modules.models.sat.solvers.py` and to the section +:ref:`Available SAT solvers`. **REMARK**: in order to be compliant with the library, the Most Significant Bit (MSB) is indexed by 0. Be careful whenever inspecting the code or, as well, a @@ -89,9 +62,10 @@ from sage.sat.solvers.satsolver import SAT from claasp.editor import remove_permutations, remove_rotations -from claasp.cipher_modules.models.sat.utils import constants, utils +from claasp.cipher_modules.models.sat import solvers +from claasp.cipher_modules.models.sat.utils import utils from claasp.cipher_modules.models.utils import set_component_solution, convert_solver_solution_to_dictionary -from claasp.name_mappings import (SBOX, CIPHER, XOR_LINEAR) +from claasp.name_mappings import SBOX class SatModel: @@ -299,7 +273,9 @@ def _sequential_counter_greater_or_equal(self, weight, dummy_id): self._model_constraints.extend(constraints) def _solve_with_external_sat_solver(self, model_type, solver_name, options, host=None, env_vars_string=""): - if host and (solver_name not in constants.SAT_SOLVERS_DIMACS_COMPLIANT): + solver_specs = [specs for specs in solvers.SAT_SOLVERS_EXTERNAL + if specs['solver_name'] == solver_name.upper()][0] + if host and (not solver_specs['keywords']['is_dimacs_compliant']): raise ValueError('{solver_name} not supported.') # creating the dimacs @@ -309,26 +285,27 @@ def _solve_with_external_sat_solver(self, model_type, solver_name, options, host # running the SAT solver file_id = f'{uuid.uuid4()}' if host is not None: - status, sat_time, sat_memory, values = utils.run_sat_solver(solver_name, options, + status, sat_time, sat_memory, values = utils.run_sat_solver(solver_specs, options, dimacs, host, env_vars_string) else: - if solver_name in constants.SAT_SOLVERS_DIMACS_COMPLIANT: - status, sat_time, sat_memory, values = utils.run_sat_solver(solver_name, options, + if solver_specs['keywords']['is_dimacs_compliant']: + status, sat_time, sat_memory, values = utils.run_sat_solver(solver_specs, options, dimacs) - elif solver_name == 'minisat': + elif solver_specs['solver_name'] == 'MINISAT_EXT': input_file = f'{self.cipher_id}_{file_id}_sat_input.cnf' output_file = f'{self.cipher_id}_{file_id}_sat_output.cnf' - status, sat_time, sat_memory, values = utils.run_minisat(options, dimacs, + status, sat_time, sat_memory, values = utils.run_minisat(solver_specs, options, dimacs, input_file, output_file) - elif solver_name == 'parkissat': + elif solver_specs['solver_name'] == 'PARKISSAT_EXT': input_file = f'{self.cipher_id}_{file_id}_sat_input.cnf' - status, sat_time, sat_memory, values = utils.run_parkissat(options, dimacs, input_file) - elif solver_name == 'yices-sat': + status, sat_time, sat_memory, values = utils.run_parkissat(solver_specs, options, dimacs, input_file) + elif solver_specs['solver_name'] == 'YICES_SAT_EXT': input_file = f'{self.cipher_id}_{file_id}_sat_input.cnf' - status, sat_time, sat_memory, values = utils.run_yices(options, dimacs, input_file) + status, sat_time, sat_memory, values = utils.run_yices(solver_specs, options, dimacs, input_file) # parsing the solution if status == 'SATISFIABLE': + variable2value = self._get_solver_solution_parsed(variable2number, values) component2fields, total_weight = self._parse_solver_output(variable2value) else: @@ -428,7 +405,7 @@ def calculate_component_weight(self, component, out_suffix, output_values_dict): for i in range(component.output_bit_size)]) return weight - def solve(self, model_type, solver_name='cryptominisat', options=None): + def solve(self, model_type, solver_name=solvers.SOLVER_DEFAULT, options=None): """ Return the solution of the model using the ``solver_name`` SAT solver. @@ -462,7 +439,7 @@ def solve(self, model_type, solver_name='cryptominisat', options=None): sage: sat.solve('cipher') # random {'cipher_id': 'tea_p64_k128_o64_r32', 'model_type': 'tea_p64_k128_o64_r32', - 'solver_name': 'cryptominisat', + 'solver_name': 'CRYPTOMINISAT_EXT', ... 'intermediate_output_31_15': {'value': '8ca8d5de0906f08e', 'weight': 0, 'sign': 1}, 'cipher_output_31_16': {'value': '8ca8d5de0906f08e', 'weight': 0, 'sign': 1}}, @@ -471,12 +448,12 @@ def solve(self, model_type, solver_name='cryptominisat', options=None): """ if options is None: options = [] - if solver_name.endswith('_sage'): + if solver_name.endswith('_EXT'): + solution = self._solve_with_external_sat_solver(model_type, solver_name, options) + else: if options: raise ValueError('Options not allowed for SageMath solvers.') - solution = self._solve_with_sage_sat_solver(model_type, solver_name[:-5]) - else: - solution = self._solve_with_external_sat_solver(model_type, solver_name, options) + solution = self._solve_with_sage_sat_solver(model_type, solver_name) return solution diff --git a/claasp/cipher_modules/models/sat/sat_models/sat_bitwise_deterministic_truncated_xor_differential_model.py b/claasp/cipher_modules/models/sat/sat_models/sat_bitwise_deterministic_truncated_xor_differential_model.py index dd8c86b4..38c6a816 100644 --- a/claasp/cipher_modules/models/sat/sat_models/sat_bitwise_deterministic_truncated_xor_differential_model.py +++ b/claasp/cipher_modules/models/sat/sat_models/sat_bitwise_deterministic_truncated_xor_differential_model.py @@ -19,6 +19,7 @@ import time +from claasp.cipher_modules.models.sat import solvers from claasp.cipher_modules.models.sat.sat_model import SatModel from claasp.cipher_modules.models.utils import set_component_solution from claasp.name_mappings import (CIPHER_OUTPUT, CONSTANT, DETERMINISTIC_TRUNCATED_XOR_DIFFERENTIAL, @@ -29,7 +30,8 @@ class SatBitwiseDeterministicTruncatedXorDifferentialModel(SatModel): def __init__(self, cipher, window_size_weight_pr_vars=-1, counter='sequential', compact=False): super().__init__(cipher, window_size_weight_pr_vars, counter, compact) - def build_bitwise_deterministic_truncated_xor_differential_trail_model(self, number_of_unknown_variables=None, fixed_variables=[]): + def build_bitwise_deterministic_truncated_xor_differential_trail_model(self, number_of_unknown_variables=None, + fixed_variables=[]): """ Build the model for the search of deterministic truncated XOR DIFFERENTIAL trails. @@ -144,7 +146,7 @@ def fix_variables_value_constraints(self, fixed_variables=[]): return constraints def find_one_bitwise_deterministic_truncated_xor_differential_trail(self, fixed_values=[], - solver_name='cryptominisat'): + solver_name=solvers.SOLVER_DEFAULT): """ Returns one deterministic truncated XOR differential trail. @@ -196,7 +198,7 @@ def find_one_bitwise_deterministic_truncated_xor_differential_trail(self, fixed_ return solution - def find_lowest_varied_patterns_bitwise_deterministic_truncated_xor_differential_trail(self, fixed_values=[], solver_name='cryptominisat'): + def find_lowest_varied_patterns_bitwise_deterministic_truncated_xor_differential_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a differential trail with the lowest number of unknown variables. diff --git a/claasp/cipher_modules/models/sat/sat_models/sat_cipher_model.py b/claasp/cipher_modules/models/sat/sat_models/sat_cipher_model.py index efacdcd6..2fcc0c21 100644 --- a/claasp/cipher_modules/models/sat/sat_models/sat_cipher_model.py +++ b/claasp/cipher_modules/models/sat/sat_models/sat_cipher_model.py @@ -19,6 +19,7 @@ import time +from claasp.cipher_modules.models.sat import solvers from claasp.cipher_modules.models.sat.sat_model import SatModel from claasp.cipher_modules.models.utils import set_component_solution from claasp.name_mappings import (CIPHER, WORD_OPERATION, CIPHER_OUTPUT, CONSTANT, INTERMEDIATE_OUTPUT, LINEAR_LAYER, @@ -68,7 +69,7 @@ def build_cipher_model(self, fixed_variables=[]): self._model_constraints.extend(constraints) self._variables_list.extend(variables) - def find_missing_bits(self, fixed_values=[], solver_name='cryptominisat'): + def find_missing_bits(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a generic flow of the cipher from plaintext and key to ciphertext. @@ -96,7 +97,7 @@ def find_missing_bits(self, fixed_values=[], solver_name='cryptominisat'): sage: sat.find_missing_bits(fixed_values=[ciphertext]) # random {'cipher_id': 'speck_p32_k64_o32_r22', 'model_type': 'cipher', - 'solver_name': 'cryptominisat', + 'solver_name': 'CRYPTOMINISAT_EXT', ... 'intermediate_output_21_11': {'value': '1411'}, 'cipher_output_21_12': {'value': 'affec7ed'}}, 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 0934784e..393575de 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 @@ -19,6 +19,7 @@ import time from copy import deepcopy +from claasp.cipher_modules.models.sat import solvers 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, get_single_key_scenario_format_for_fixed_values @@ -27,10 +28,12 @@ class SatXorDifferentialModel(SatModel): - def __init__(self, cipher, window_size_weight_pr_vars=-1, counter='sequential', compact=False, - window_size_by_round=None, window_size_by_component_id=None): - self._window_size_by_round = window_size_by_round - self._window_size_by_component_id = window_size_by_component_id + def __init__(self, cipher, window_size_weight_pr_vars=-1, counter='sequential', compact=False): + self._window_size_by_component_id_values = None + self._window_size_by_round_values = None + self._window_size_full_window_vars = None + self._window_size_number_of_full_window = None + self._window_size_full_window_operator = None super().__init__(cipher, window_size_weight_pr_vars, counter, compact) def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): @@ -80,6 +83,26 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): self._variables_list.extend(variables) self._model_constraints.extend(constraints) + if self._window_size_full_window_vars != None: + self._variables_list.extend(self._window_size_full_window_vars) + + if self._window_size_full_window_operator == 'at_least': + greater_or_equal = True + else: + greater_or_equal = False + + if self._window_size_number_of_full_window == 0: + all_ones_dummy_variables, all_ones_constraints = [], [f'-{variable}' for variable in self._window_size_full_window_vars] + else: + all_ones_dummy_variables, all_ones_constraints = self._sequential_counter_algorithm( + self._window_size_full_window_vars, + self._window_size_number_of_full_window, + 'dummy_all_ones_0', + greater_or_equal=greater_or_equal + ) + self._variables_list.extend(all_ones_dummy_variables) + self._model_constraints.extend(all_ones_constraints) + def build_xor_differential_trail_and_checker_model_at_intermediate_output_level( self, weight=-1, fixed_variables=[] ): @@ -114,7 +137,7 @@ def build_xor_differential_trail_and_checker_model_at_intermediate_output_level( self._model_constraints.extend(sat._model_constraints) def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], - solver_name='cryptominisat'): + solver_name=solvers.SOLVER_DEFAULT): """ 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. @@ -123,7 +146,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed - ``fixed_weight`` -- **integer**; the weight to be fixed - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -185,7 +208,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed return solutions_list def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_weight, fixed_values=[], - solver_name='cryptominisat'): + solver_name=solvers.SOLVER_DEFAULT): """ Return a list of solutions. By default, the search is set in the single-key setting. @@ -198,7 +221,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w - ``min_weight`` -- **integer**; the weight from which to start the search - ``max_weight`` -- **integer**; the weight at which the search stops - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -237,12 +260,12 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w solver_name=solver_name) for solution in solutions: - solution['test_name'] = "find_all_xor_differential_trails_with_weight_at_most" + solution['test_name'] = "find_all_xor_differential_trails_with_weight_at_most" solutions_list.extend(solutions) return solutions_list - def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name='cryptominisat'): + def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a trail with the lowest weight. By default, the search is set in the single-key setting. @@ -255,7 +278,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name INPUT: - ``fixed_values`` -- **list** (default: `[]`); can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -310,7 +333,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name return solution - def find_one_xor_differential_trail(self, fixed_values=[], solver_name='cryptominisat'): + def find_one_xor_differential_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a XOR differential trail. By default, the search is set in the single-key setting. @@ -319,7 +342,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='cryptomi INPUT: - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -346,7 +369,9 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='cryptomi ....: constraint_type='not_equal', ....: bit_positions=range(64), ....: bit_values=[0]*64) - sage: sat.find_one_xor_differential_trail(fixed_values=[key]) + sage: result = sat.find_one_xor_differential_trail(fixed_values=[key]) + sage: result['total_weight'] == 9.0 + True """ start_building_time = time.time() self.build_xor_differential_trail_model(fixed_variables=fixed_values) @@ -358,7 +383,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='cryptomi return solution def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], - solver_name='cryptominisat'): + solver_name=solvers.SOLVER_DEFAULT): """ 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. @@ -366,7 +391,7 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ - ``fixed_weight`` -- **integer**; the weight to be fixed - ``fixed_values`` -- **list** (default: `[]`); can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -378,7 +403,8 @@ def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_ 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=3) - sage: sat = SatXorDifferentialModel(speck, window_size_by_round=[0, 0, 0]) + sage: sat = SatXorDifferentialModel(speck) + sage: sat.set_window_size_heuristic_by_round([0, 0, 0]) sage: trail = sat.find_one_xor_differential_trail_with_fixed_weight(3) sage: trail['total_weight'] 3.0 @@ -422,10 +448,36 @@ def _parse_solver_output(self, variable2value): return components_solutions, total_weight + def set_window_size_heuristic_by_round( + self, window_size_by_round_values, number_of_full_windows=None, full_window_operator='at_least' + ): + if not self._cipher.is_arx(): + raise Exception('Cipher is not ARX. Window Size Heuristic is only supported for ARX ciphers.') + self._window_size_by_round_values = window_size_by_round_values + if number_of_full_windows is not None: + self._window_size_full_window_vars = [] + self._window_size_number_of_full_window = number_of_full_windows + self._window_size_full_window_operator = full_window_operator + + def set_window_size_heuristic_by_component_id( + self, window_size_by_component_id_values, number_of_full_windows=None, full_window_operator='at_least' + ): + if not self._cipher.is_arx(): + raise Exception('Cipher is not ARX. Window Size Heuristic is only supported for ARX ciphers.') + self._window_size_by_component_id_values = window_size_by_component_id_values + if number_of_full_windows is not None: + self._window_size_full_window_vars = [] + self._window_size_number_of_full_window = number_of_full_windows + self._window_size_full_window_operator = full_window_operator + + @property + def window_size_number_of_full_window(self): + return self._window_size_number_of_full_window + @property - def window_size_by_round(self): - return self._window_size_by_round + def window_size_by_round_values(self): + return self._window_size_by_round_values @property - def window_size_by_component_id(self): - return self._window_size_by_component_id + def window_size_by_component_id_values(self): + return self._window_size_by_component_id_values 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 db80122d..4bea6e10 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 @@ -19,6 +19,7 @@ import time +from claasp.cipher_modules.models.sat import solvers 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.sat.utils.constants import OUTPUT_BIT_ID_SUFFIX, INPUT_BIT_ID_SUFFIX @@ -114,7 +115,8 @@ def build_xor_linear_trail_model(self, weight=-1, fixed_variables=[]): self._variables_list.extend(variables) self._model_constraints.extend(constraints) - def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='cryptominisat'): + def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], + solver_name=solvers.SOLVER_DEFAULT): """ 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. @@ -124,7 +126,7 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value - ``fixed_weight`` -- **integer**; the weight to be fixed - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -183,7 +185,7 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value return solutions_list def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, fixed_values=[], - solver_name='cryptominisat'): + solver_name=solvers.SOLVER_DEFAULT): """ Return a list of solutions. By default, the search removes the key schedule, if any. @@ -196,7 +198,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, - ``min_weight`` -- **integer**; the weight from which to start the search - ``max_weight`` -- **integer**; the weight at which the search stops - ``fixed_values`` -- **list** (default: `[]`); can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -234,7 +236,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, return solutions_list - def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='cryptominisat'): + def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a XOR LINEAR trail with the lowest possible weight. By default, the search removes the key schedule, if any. @@ -248,7 +250,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='cryp INPUT: - ``fixed_values`` -- **list** (default: `[]`); can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -298,7 +300,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='cryp return solution - def find_one_xor_linear_trail(self, fixed_values=[], solver_name='cryptominisat'): + def find_one_xor_linear_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a XOR linear trail. By default, the search removes the key schedule, if any. @@ -309,7 +311,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='cryptominisat' INPUT: - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: @@ -350,7 +352,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='cryptominisat' return solution def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], - solver_name='cryptominisat'): + solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a XOR linear trail whose weight is ``fixed_weight``. By default, the search removes the key schedule, if any. @@ -360,7 +362,7 @@ def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values - ``fixed_weight`` -- **integer**; the weight to be fixed - ``fixed_values`` -- **list** (default: `[]`); can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `cryptominisat`); the name of the solver + - ``solver_name`` -- **string** (default: `CRYPTOMINISAT_EXT`); the name of the solver .. SEEALSO:: diff --git a/claasp/cipher_modules/models/sat/solvers.py b/claasp/cipher_modules/models/sat/solvers.py new file mode 100644 index 00000000..d2124e14 --- /dev/null +++ b/claasp/cipher_modules/models/sat/solvers.py @@ -0,0 +1,233 @@ +# **************************************************************************** +# Copyright 2023 Technology Innovation Institute +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# **************************************************************************** +"""SAT solvers + +.. _Available SAT solvers: + +Available SAT solvers +--------------------- + +In this file, all the available SAT solvers are listed. They can be divided in +two categories: internal and external. + +Internal SAT solvers should be installed by default and no further action is +needed. For any other information on internal SAT solvers, visit `Abstract SAT +solver `_. + +External SAT solvers need to be installed in the system as long as you want a +bare metal installation since they are called using a subprocess. If you use a +Docker container running the default image for the library no further action is +needed. +""" + + +SOLVER_DEFAULT = "CRYPTOMINISAT_EXT" + + +SAT_SOLVERS_INTERNAL = [ + { + "solver_brand_name": "CryptoMiniSat SAT solver (using Sage backend)", + "solver_name": "cryptominisat", + }, + { + "solver_brand_name": "PicoSAT (using Sage backend)", + "solver_name": "picosat", + }, + { + "solver_brand_name": "Glucose SAT solver (using Sage backend)", + "solver_name": "glucose", + }, + { + "solver_brand_name": "Glucose (Syrup) SAT solver (using Sage backend)", + "solver_name": "glucose-syrup", + }, +] + + +SAT_SOLVERS_EXTERNAL = [ + { + "solver_brand_name": "CaDiCal Simplified Satisfiability Solver", + "solver_name": "CADICAL_EXT", + "keywords": { + "command": { + "executable": "cadical", + "options": [], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "real time", + "memory": "size of process", + "is_dimacs_compliant": True, + "unsat_condition": "s UNSATISFIABLE", + }, + }, + { + "solver_brand_name": "CryptoMiniSat SAT solver", + "solver_name": "CRYPTOMINISAT_EXT", + "keywords": { + "command": { + "executable": "cryptominisat5", + "options": ["--verb=1"], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "c Total time (this thread)", + "memory": "c Max Memory (rss)", + "is_dimacs_compliant": True, + "unsat_condition": "s UNSATISFIABLE", + }, + }, + { + "solver_brand_name": "Glucose SAT solver", + "solver_name": "GLUCOSE_EXT", + "keywords": { + "command": { + "executable": "glucose", + "options": ["-model"], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "CPU time", + "memory": None, + "is_dimacs_compliant": True, + "unsat_condition": "s UNSATISFIABLE", + }, + }, + { + "solver_brand_name": "Glucose (Syrup) SAT solver", + "solver_name": "GLUCOSE_SYRUP_EXT", + "keywords": { + "command": { + "executable": "glucose-syrup", + "options": ["-model"], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "cpu time", + "memory": "Total Memory", + "is_dimacs_compliant": True, + "unsat_condition": "s UNSATISFIABLE", + }, + }, + { + "solver_brand_name": "The Kissat SAT solver", + "solver_name": "KISSAT_EXT", + "keywords": { + "command": { + "executable": "kissat", + "options": [], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "process-time", + "memory": "maximum-resident-set-size", + "is_dimacs_compliant": True, + "unsat_condition": "s UNSATISFIABLE", + }, + }, + { + "solver_brand_name": "ParKissat-RS", + "solver_name": "PARKISSAT_EXT", + "keywords": { + "command": { + "executable": "parkissat", + "options": ["-shr-sleep=500000", "-shr-lit=1500", "-initshuffle"], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": None, + "memory": None, + "is_dimacs_compliant": False, + "unsat_condition": "s UNSATISFIABLE", + }, + }, + { + "solver_brand_name": "MathSAT", + "solver_name": "MATHSAT_EXT", + "keywords": { + "command": { + "executable": "mathsat", + "options": ["-stats", "-model", "-input=dimacs"], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "CPU Time", + "memory": "Memory used", + "is_dimacs_compliant": True, + "unsat_condition": "s UNSATISFIABLE", + }, + }, + { + "solver_brand_name": "MiniSat", + "solver_name": "MINISAT_EXT", + "keywords": { + "command": { + "executable": "minisat", + "options": [], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file", "output_file"], + }, + "time": "CPU time", + "memory": "Memory used", + "is_dimacs_compliant": False, + "unsat_condition": "UNSATISFIABLE", + }, + }, + { + "solver_brand_name": "Yices2", + "solver_name": "YICES_SAT_EXT", + "keywords": { + "command": { + "executable": "yices-sat", + "options": ["--stats", "--model"], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "Search time", + "memory": "Memory used", + "is_dimacs_compliant": False, + "unsat_condition": "unsat", + }, + }, +] diff --git a/claasp/cipher_modules/models/sat/utils/constants.py b/claasp/cipher_modules/models/sat/utils/constants.py index 07270ce3..956ecbde 100644 --- a/claasp/cipher_modules/models/sat/utils/constants.py +++ b/claasp/cipher_modules/models/sat/utils/constants.py @@ -1,52 +1,2 @@ INPUT_BIT_ID_SUFFIX = '_i' OUTPUT_BIT_ID_SUFFIX = '_o' -SAT_SOLVERS_DIMACS_COMPLIANT = ( - 'cadical', 'cryptominisat', 'glucose', 'glucose-syrup', 'kissat', 'mathsat' -) -SAT_SOLVERS = { - 'cadical': { - 'command': ['cadical'], - 'time': 'real time', - 'memory': 'size of process' - }, - 'cryptominisat': { - 'command': ['cryptominisat5', '--verb=1'], - 'time': 'c Total time (this thread)', - 'memory': 'c Max Memory (rss)' - }, - 'glucose': { - 'command': ['glucose', '-model'], - 'time': 'CPU time', - 'memory': None - }, - 'glucose-syrup': { - 'command': ['glucose-syrup', '-model'], - 'time': 'cpu time', - 'memory': 'Total Memory' - }, - 'kissat': { - 'command': ['kissat'], - 'time': 'process-time', - 'memory': 'maximum-resident-set-size' - }, - 'parkissat': { - 'command': ['parkissat', '-shr-sleep=500000', '-shr-lit=1500', '-initshuffle'], - 'time': None, - 'memory': None - }, - 'mathsat': { - 'command': ['mathsat', '-stats', '-model', '-input=dimacs'], - 'time': 'CPU Time', - 'memory': 'Memory used' - }, - 'minisat': { - 'command': ['minisat'], - 'time': 'CPU time', - 'memory': 'Memory used' - }, - 'yices-sat': { - 'command': ['yices-sat', '--stats', '--model'], - 'time': 'Search time', - 'memory': 'Memory used' - } -} diff --git a/claasp/cipher_modules/models/sat/utils/n_window_heuristic_helper.py b/claasp/cipher_modules/models/sat/utils/n_window_heuristic_helper.py index e3ebc66f..514b36b2 100644 --- a/claasp/cipher_modules/models/sat/utils/n_window_heuristic_helper.py +++ b/claasp/cipher_modules/models/sat/utils/n_window_heuristic_helper.py @@ -5511,3 +5511,398 @@ def window_size_0_cnf(x): f'{x1} {x2} -{x0}', f'-{x0} -{x1} -{x2}', ] + +def window_size_with_full_1_window_cnf(a, b, c, aux): + return [ + f'{a[0]} {aux} {b[0]} -{c[0]}', + f'{a[0]} {aux} {c[0]} -{b[0]}', + f'{a[0]} {b[0]} {c[0]} -{aux}', + f'{aux} {b[0]} {c[0]} -{a[0]}', + f'{a[0]} -{aux} -{b[0]} -{c[0]}', + f'{aux} -{a[0]} -{b[0]} -{c[0]}', + f'{b[0]} -{a[0]} -{aux} -{c[0]}', + f'{c[0]} -{a[0]} -{aux} -{b[0]}', + ] + +def window_size_with_full_2_window_cnf(a, b, c, aux): + return [ + f'{a[0]} {b[0]} {c[0]} -{aux}', + f'{a[1]} {b[1]} {c[1]} -{aux}', + f'{a[0]} -{aux} -{b[0]} -{c[0]}', + f'{a[1]} -{aux} -{b[1]} -{c[1]}', + f'{b[0]} -{a[0]} -{aux} -{c[0]}', + f'{b[1]} -{a[1]} -{aux} -{c[1]}', + f'{c[0]} -{a[0]} -{aux} -{b[0]}', + f'{c[1]} -{a[1]} -{aux} -{b[1]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[1]} -{c[0]} -{c[1]}', + f'{a[0]} {a[1]} {aux} {b[0]} {c[1]} -{b[1]} -{c[0]}', + f'{a[0]} {a[1]} {aux} {b[1]} {c[0]} -{b[0]} -{c[1]}', + f'{a[0]} {a[1]} {aux} {c[0]} {c[1]} -{b[0]} -{b[1]}', + f'{a[0]} {aux} {b[0]} {b[1]} {c[1]} -{a[1]} -{c[0]}', + f'{a[0]} {aux} {b[1]} {c[0]} {c[1]} -{a[1]} -{b[0]}', + f'{a[1]} {aux} {b[0]} {b[1]} {c[0]} -{a[0]} -{c[1]}', + f'{a[1]} {aux} {b[0]} {c[0]} {c[1]} -{a[0]} -{b[1]}', + f'{aux} {b[0]} {b[1]} {c[0]} {c[1]} -{a[0]} -{a[1]}', + f'{a[0]} {aux} {b[0]} -{a[1]} -{b[1]} -{c[0]} -{c[1]}', + f'{a[0]} {aux} {c[0]} -{a[1]} -{b[0]} -{b[1]} -{c[1]}', + f'{a[1]} {aux} {b[1]} -{a[0]} -{b[0]} -{c[0]} -{c[1]}', + f'{a[1]} {aux} {c[1]} -{a[0]} -{b[0]} -{b[1]} -{c[0]}', + f'{aux} {b[0]} {c[0]} -{a[0]} -{a[1]} -{b[1]} -{c[1]}', + f'{aux} {b[1]} {c[1]} -{a[0]} -{a[1]} -{b[0]} -{c[0]}', + f'{aux} -{a[0]} -{a[1]} -{b[0]} -{b[1]} -{c[0]} -{c[1]}', + ] +def window_size_with_full_3_window_cnf(a, b, c, aux): + return [ + f'{a[0]} {b[0]} {c[0]} -{aux}', + f'{a[1]} {b[1]} {c[1]} -{aux}', + f'{a[2]} {b[2]} {c[2]} -{aux}', + f'{a[0]} -{aux} -{b[0]} -{c[0]}', + f'{a[1]} -{aux} -{b[1]} -{c[1]}', + f'{a[2]} -{aux} -{b[2]} -{c[2]}', + f'{b[0]} -{a[0]} -{aux} -{c[0]}', + f'{b[1]} -{a[1]} -{aux} -{c[1]}', + f'{b[2]} -{a[2]} -{aux} -{c[2]}', + f'{c[0]} -{a[0]} -{aux} -{b[0]}', + f'{c[1]} -{a[1]} -{aux} -{b[1]}', + f'{c[2]} -{a[2]} -{aux} -{b[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[1]} {c[2]} -{b[2]} -{c[0]} -{c[1]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[2]} {c[1]} -{b[1]} -{c[0]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {c[1]} {c[2]} -{b[1]} -{b[2]} -{c[0]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[1]} {b[2]} {c[0]} -{b[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[1]} {c[0]} {c[2]} -{b[0]} -{b[2]} -{c[1]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[2]} {c[0]} {c[1]} -{b[0]} -{b[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {c[0]} {c[1]} {c[2]} -{b[0]} -{b[1]} -{b[2]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[1]} {b[2]} {c[2]} -{a[2]} -{c[0]} -{c[1]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[2]} {c[1]} {c[2]} -{a[2]} -{b[1]} -{c[0]}', + f'{a[0]} {a[1]} {aux} {b[1]} {b[2]} {c[0]} {c[2]} -{a[2]} -{b[0]} -{c[1]}', + f'{a[0]} {a[1]} {aux} {b[2]} {c[0]} {c[1]} {c[2]} -{a[2]} -{b[0]} -{b[1]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} {c[1]} -{a[1]} -{c[0]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[1]} {c[1]} {c[2]} -{a[1]} -{b[2]} -{c[0]}', + f'{a[0]} {a[2]} {aux} {b[1]} {b[2]} {c[0]} {c[1]} -{a[1]} -{b[0]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {b[1]} {c[0]} {c[1]} {c[2]} -{a[1]} -{b[0]} -{b[2]}', + f'{a[0]} {aux} {b[0]} {b[1]} {b[2]} {c[1]} {c[2]} -{a[1]} -{a[2]} -{c[0]}', + f'{a[0]} {aux} {b[1]} {b[2]} {c[0]} {c[1]} {c[2]} -{a[1]} -{a[2]} -{b[0]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} -{a[0]} -{c[1]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[1]} {c[0]} {c[2]} -{a[0]} -{b[2]} -{c[1]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[2]} {c[0]} {c[1]} -{a[0]} -{b[1]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {b[0]} {c[0]} {c[1]} {c[2]} -{a[0]} -{b[1]} -{b[2]}', + f'{a[1]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[2]} -{a[0]} -{a[2]} -{c[1]}', + f'{a[1]} {aux} {b[0]} {b[2]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[2]} -{b[1]}', + f'{a[2]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[1]} -{a[0]} -{a[1]} -{c[2]}', + f'{a[2]} {aux} {b[0]} {b[1]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{b[2]}', + f'{aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{a[2]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[1]} -{a[2]} -{b[2]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {aux} {b[0]} {c[1]} -{a[2]} -{b[1]} -{b[2]} -{c[0]} -{c[2]}', + f'{a[0]} {a[1]} {aux} {b[1]} {c[0]} -{a[2]} -{b[0]} -{b[2]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {aux} {c[0]} {c[1]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[2]} -{a[1]} -{b[1]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {b[0]} {c[2]} -{a[1]} -{b[1]} -{b[2]} -{c[0]} -{c[1]}', + f'{a[0]} {a[2]} {aux} {b[2]} {c[0]} -{a[1]} -{b[0]} -{b[1]} -{c[1]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {c[0]} {c[2]} -{a[1]} -{b[0]} -{b[1]} -{b[2]} -{c[1]}', + f'{a[0]} {aux} {b[0]} {b[1]} {c[1]} -{a[1]} -{a[2]} -{b[2]} -{c[0]} -{c[2]}', + f'{a[0]} {aux} {b[0]} {b[2]} {c[2]} -{a[1]} -{a[2]} -{b[1]} -{c[0]} -{c[1]}', + f'{a[0]} {aux} {b[1]} {c[0]} {c[1]} -{a[1]} -{a[2]} -{b[0]} -{b[2]} -{c[2]}', + f'{a[0]} {aux} {b[2]} {c[0]} {c[2]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{c[1]}', + f'{a[1]} {a[2]} {aux} {b[1]} {b[2]} -{a[0]} -{b[0]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {b[1]} {c[2]} -{a[0]} -{b[0]} -{b[2]} -{c[0]} -{c[1]}', + f'{a[1]} {a[2]} {aux} {b[2]} {c[1]} -{a[0]} -{b[0]} -{b[1]} -{c[0]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {c[1]} {c[2]} -{a[0]} -{b[0]} -{b[1]} -{b[2]} -{c[0]}', + f'{a[1]} {aux} {b[0]} {b[1]} {c[0]} -{a[0]} -{a[2]} -{b[2]} -{c[1]} -{c[2]}', + f'{a[1]} {aux} {b[0]} {c[0]} {c[1]} -{a[0]} -{a[2]} -{b[1]} -{b[2]} -{c[2]}', + f'{a[1]} {aux} {b[1]} {b[2]} {c[2]} -{a[0]} -{a[2]} -{b[0]} -{c[0]} -{c[1]}', + f'{a[1]} {aux} {b[2]} {c[1]} {c[2]} -{a[0]} -{a[2]} -{b[0]} -{b[1]} -{c[0]}', + f'{a[2]} {aux} {b[0]} {b[2]} {c[0]} -{a[0]} -{a[1]} -{b[1]} -{c[1]} -{c[2]}', + f'{a[2]} {aux} {b[0]} {c[0]} {c[2]} -{a[0]} -{a[1]} -{b[1]} -{b[2]} -{c[1]}', + f'{a[2]} {aux} {b[1]} {b[2]} {c[1]} -{a[0]} -{a[1]} -{b[0]} -{c[0]} -{c[2]}', + f'{a[2]} {aux} {b[1]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{b[0]} -{b[2]} -{c[0]}', + f'{aux} {b[0]} {b[1]} {c[0]} {c[1]} -{a[0]} -{a[1]} -{a[2]} -{b[2]} -{c[2]}', + f'{aux} {b[0]} {b[2]} {c[0]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{b[1]} -{c[1]}', + f'{aux} {b[1]} {b[2]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{c[0]}', + f'{a[0]} {aux} {b[0]} -{a[1]} -{a[2]} -{b[1]} -{b[2]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {aux} {c[0]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{c[1]} -{c[2]}', + f'{a[1]} {aux} {b[1]} -{a[0]} -{a[2]} -{b[0]} -{b[2]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[1]} {aux} {c[1]} -{a[0]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[2]}', + f'{a[2]} {aux} {b[2]} -{a[0]} -{a[1]} -{b[0]} -{b[1]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[2]} {aux} {c[2]} -{a[0]} -{a[1]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[1]}', + f'{aux} {b[0]} {c[0]} -{a[0]} -{a[1]} -{a[2]} -{b[1]} -{b[2]} -{c[1]} -{c[2]}', + f'{aux} {b[1]} {c[1]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[2]} -{c[0]} -{c[2]}', + f'{aux} {b[2]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{c[0]} -{c[1]}', + f'{aux} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[1]} -{c[2]}', + ] + +def window_size_with_full_4_window_cnf(a, b, c, aux): + return [ + f'{a[0]} {b[0]} {c[0]} -{aux}', + f'{a[1]} {b[1]} {c[1]} -{aux}', + f'{a[2]} {b[2]} {c[2]} -{aux}', + f'{a[3]} {b[3]} {c[3]} -{aux}', + f'{a[0]} -{aux} -{b[0]} -{c[0]}', + f'{a[1]} -{aux} -{b[1]} -{c[1]}', + f'{a[2]} -{aux} -{b[2]} -{c[2]}', + f'{a[3]} -{aux} -{b[3]} -{c[3]}', + f'{b[0]} -{a[0]} -{aux} -{c[0]}', + f'{b[1]} -{a[1]} -{aux} -{c[1]}', + f'{b[2]} -{a[2]} -{aux} -{c[2]}', + f'{b[3]} -{a[3]} -{aux} -{c[3]}', + f'{c[0]} -{a[0]} -{aux} -{b[0]}', + f'{c[1]} -{a[1]} -{aux} -{b[1]}', + f'{c[2]} -{a[2]} -{aux} -{b[2]}', + f'{c[3]} -{a[3]} -{aux} -{b[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {c[3]} -{b[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[3]} {c[2]} -{b[2]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {c[2]} {c[3]} -{b[2]} -{b[3]} -{c[0]} -{c[1]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[2]} {b[3]} {c[1]} -{b[1]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[2]} {c[1]} {c[3]} -{b[1]} -{b[3]} -{c[0]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[3]} {c[1]} {c[2]} -{b[1]} -{b[2]} -{c[0]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[0]} {c[1]} {c[2]} {c[3]} -{b[1]} -{b[2]} -{b[3]} -{c[0]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[1]} {b[2]} {b[3]} {c[0]} -{b[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[1]} {b[2]} {c[0]} {c[3]} -{b[0]} -{b[3]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[1]} {b[3]} {c[0]} {c[2]} -{b[0]} -{b[2]} -{c[1]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[1]} {c[0]} {c[2]} {c[3]} -{b[0]} -{b[2]} -{b[3]} -{c[1]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[2]} {b[3]} {c[0]} {c[1]} -{b[0]} -{b[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[2]} {c[0]} {c[1]} {c[3]} -{b[0]} -{b[1]} -{b[3]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {b[3]} {c[0]} {c[1]} {c[2]} -{b[0]} -{b[1]} -{b[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {a[3]} {aux} {c[0]} {c[1]} {c[2]} {c[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[3]} -{a[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[1]} {b[3]} {c[2]} {c[3]} -{a[3]} -{b[2]} -{c[0]} -{c[1]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[2]} {b[3]} {c[1]} {c[3]} -{a[3]} -{b[1]} -{c[0]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[3]} {c[1]} {c[2]} {c[3]} -{a[3]} -{b[1]} -{b[2]} -{c[0]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[1]} {b[2]} {b[3]} {c[0]} {c[3]} -{a[3]} -{b[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[1]} {b[3]} {c[0]} {c[2]} {c[3]} -{a[3]} -{b[0]} -{b[2]} -{c[1]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[2]} {b[3]} {c[0]} {c[1]} {c[3]} -{a[3]} -{b[0]} -{b[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[3]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[3]} -{b[0]} -{b[1]} -{b[2]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[2]} -{a[2]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {c[2]} {c[3]} -{a[2]} -{b[3]} -{c[0]} -{c[1]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[0]} {b[2]} {b[3]} {c[1]} {c[2]} -{a[2]} -{b[1]} -{c[0]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[0]} {b[2]} {c[1]} {c[2]} {c[3]} -{a[2]} -{b[1]} -{b[3]} -{c[0]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[1]} {b[2]} {b[3]} {c[0]} {c[2]} -{a[2]} -{b[0]} -{c[1]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[1]} {b[2]} {c[0]} {c[2]} {c[3]} -{a[2]} -{b[0]} -{b[3]} -{c[1]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[2]} {b[3]} {c[0]} {c[1]} {c[2]} -{a[2]} -{b[0]} -{b[1]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[2]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[2]} -{b[0]} -{b[1]} -{b[3]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[2]} {c[3]} -{a[2]} -{a[3]} -{c[0]} -{c[1]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[2]} {b[3]} {c[1]} {c[2]} {c[3]} -{a[2]} -{a[3]} -{b[1]} -{c[0]}', + f'{a[0]} {a[1]} {aux} {b[1]} {b[2]} {b[3]} {c[0]} {c[2]} {c[3]} -{a[2]} -{a[3]} -{b[0]} -{c[1]}', + f'{a[0]} {a[1]} {aux} {b[2]} {b[3]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[2]} -{a[3]} -{b[0]} -{b[1]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[1]} -{a[1]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {c[1]} {c[3]} -{a[1]} -{b[3]} -{c[0]} -{c[2]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[3]} {c[1]} {c[2]} -{a[1]} -{b[2]} -{c[0]} -{c[3]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {c[1]} {c[2]} {c[3]} -{a[1]} -{b[2]} -{b[3]} -{c[0]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[1]} {b[2]} {b[3]} {c[0]} {c[1]} -{a[1]} -{b[0]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[1]} {b[2]} {c[0]} {c[1]} {c[3]} -{a[1]} -{b[0]} -{b[3]} -{c[2]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[1]} {b[3]} {c[0]} {c[1]} {c[2]} -{a[1]} -{b[0]} -{b[2]} -{c[3]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[1]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[1]} -{b[0]} -{b[2]} -{b[3]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[1]} {c[3]} -{a[1]} -{a[3]} -{c[0]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[1]} {b[3]} {c[1]} {c[2]} {c[3]} -{a[1]} -{a[3]} -{b[2]} -{c[0]}', + f'{a[0]} {a[2]} {aux} {b[1]} {b[2]} {b[3]} {c[0]} {c[1]} {c[3]} -{a[1]} -{a[3]} -{b[0]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {b[1]} {b[3]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[1]} -{a[3]} -{b[0]} -{b[2]}', + f'{a[0]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[1]} {c[2]} -{a[1]} -{a[2]} -{c[0]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {c[1]} {c[2]} {c[3]} -{a[1]} -{a[2]} -{b[3]} -{c[0]}', + f'{a[0]} {a[3]} {aux} {b[1]} {b[2]} {b[3]} {c[0]} {c[1]} {c[2]} -{a[1]} -{a[2]} -{b[0]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {b[1]} {b[2]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[1]} -{a[2]} -{b[0]} -{b[3]}', + f'{a[0]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[1]} {c[2]} {c[3]} -{a[1]} -{a[2]} -{a[3]} -{c[0]}', + f'{a[0]} {aux} {b[1]} {b[2]} {b[3]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[1]} -{a[2]} -{a[3]} -{b[0]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[0]} -{a[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[3]} -{a[0]} -{b[3]} -{c[1]} -{c[2]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[3]} {c[0]} {c[2]} -{a[0]} -{b[2]} -{c[1]} -{c[3]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[1]} {c[0]} {c[2]} {c[3]} -{a[0]} -{b[2]} -{b[3]} -{c[1]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[2]} {b[3]} {c[0]} {c[1]} -{a[0]} -{b[1]} -{c[2]} -{c[3]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[2]} {c[0]} {c[1]} {c[3]} -{a[0]} -{b[1]} -{b[3]} -{c[2]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[0]} {b[3]} {c[0]} {c[1]} {c[2]} -{a[0]} -{b[1]} -{b[2]} -{c[3]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[0]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[0]} -{b[1]} -{b[2]} -{b[3]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[0]} {c[3]} -{a[0]} -{a[3]} -{c[1]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[1]} {b[3]} {c[0]} {c[2]} {c[3]} -{a[0]} -{a[3]} -{b[2]} -{c[1]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[2]} {b[3]} {c[0]} {c[1]} {c[3]} -{a[0]} -{a[3]} -{b[1]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[3]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[3]} -{b[1]} -{b[2]}', + f'{a[1]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[0]} {c[2]} -{a[0]} -{a[2]} -{c[1]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[2]} {c[3]} -{a[0]} -{a[2]} -{b[3]} -{c[1]}', + f'{a[1]} {a[3]} {aux} {b[0]} {b[2]} {b[3]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[2]} -{b[1]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {b[0]} {b[2]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[2]} -{b[1]} -{b[3]}', + f'{a[1]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[0]} {c[2]} {c[3]} -{a[0]} -{a[2]} -{a[3]} -{c[1]}', + f'{a[1]} {aux} {b[0]} {b[2]} {b[3]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[2]} -{a[3]} -{b[1]}', + f'{a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[0]} {c[1]} -{a[0]} -{a[1]} -{c[2]} -{c[3]}', + f'{a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[1]} {c[3]} -{a[0]} -{a[1]} -{b[3]} -{c[2]}', + f'{a[2]} {a[3]} {aux} {b[0]} {b[1]} {b[3]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{b[2]} -{c[3]}', + f'{a[2]} {a[3]} {aux} {b[0]} {b[1]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{b[2]} -{b[3]}', + f'{a[2]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[0]} {c[1]} {c[3]} -{a[0]} -{a[1]} -{a[3]} -{c[2]}', + f'{a[2]} {aux} {b[0]} {b[1]} {b[3]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[3]} -{b[2]}', + f'{a[3]} {aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{c[3]}', + f'{a[3]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{b[3]}', + f'{aux} {b[0]} {b[1]} {b[2]} {b[3]} {c[0]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{a[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} -{a[3]} -{b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[1]} {c[2]} -{a[3]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {b[2]} {c[1]} -{a[3]} -{b[1]} -{b[3]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[0]} {c[1]} {c[2]} -{a[3]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[1]} {b[2]} {c[0]} -{a[3]} -{b[0]} -{b[3]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[1]} {c[0]} {c[2]} -{a[3]} -{b[0]} -{b[2]} -{b[3]} -{c[1]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {b[2]} {c[0]} {c[1]} -{a[3]} -{b[0]} -{b[1]} -{b[3]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[2]} {aux} {c[0]} {c[1]} {c[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[0]} {b[1]} {b[3]} -{a[2]} -{b[2]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[0]} {b[1]} {c[3]} -{a[2]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[0]} {b[3]} {c[1]} -{a[2]} -{b[1]} -{b[2]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[0]} {c[1]} {c[3]} -{a[2]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[2]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[1]} {b[3]} {c[0]} -{a[2]} -{b[0]} -{b[2]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[1]} {c[0]} {c[3]} -{a[2]} -{b[0]} -{b[2]} -{b[3]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {a[3]} {aux} {b[3]} {c[0]} {c[1]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {a[3]} {aux} {c[0]} {c[1]} {c[3]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[2]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[1]} {b[2]} {c[2]} -{a[2]} -{a[3]} -{b[3]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[1]} {b[3]} {c[3]} -{a[2]} -{a[3]} -{b[2]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[2]} {c[1]} {c[2]} -{a[2]} -{a[3]} -{b[1]} -{b[3]} -{c[0]} -{c[3]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[3]} {c[1]} {c[3]} -{a[2]} -{a[3]} -{b[1]} -{b[2]} -{c[0]} -{c[2]}', + f'{a[0]} {a[1]} {aux} {b[1]} {b[2]} {c[0]} {c[2]} -{a[2]} -{a[3]} -{b[0]} -{b[3]} -{c[1]} -{c[3]}', + f'{a[0]} {a[1]} {aux} {b[1]} {b[3]} {c[0]} {c[3]} -{a[2]} -{a[3]} -{b[0]} -{b[2]} -{c[1]} -{c[2]}', + f'{a[0]} {a[1]} {aux} {b[2]} {c[0]} {c[1]} {c[2]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[3]} -{c[3]}', + f'{a[0]} {a[1]} {aux} {b[3]} {c[0]} {c[1]} {c[3]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{c[2]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[0]} {b[2]} {b[3]} -{a[1]} -{b[1]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[0]} {b[2]} {c[3]} -{a[1]} -{b[1]} -{b[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[0]} {b[3]} {c[2]} -{a[1]} -{b[1]} -{b[2]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[0]} {c[2]} {c[3]} -{a[1]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[1]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[2]} {b[3]} {c[0]} -{a[1]} -{b[0]} -{b[1]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[2]} {c[0]} {c[3]} -{a[1]} -{b[0]} -{b[1]} -{b[3]} -{c[1]} -{c[2]}', + f'{a[0]} {a[2]} {a[3]} {aux} {b[3]} {c[0]} {c[2]} -{a[1]} -{b[0]} -{b[1]} -{b[2]} -{c[1]} -{c[3]}', + f'{a[0]} {a[2]} {a[3]} {aux} {c[0]} {c[2]} {c[3]} -{a[1]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[1]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} {c[1]} -{a[1]} -{a[3]} -{b[3]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[1]} {c[1]} {c[2]} -{a[1]} -{a[3]} -{b[2]} -{b[3]} -{c[0]} -{c[3]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[2]} {b[3]} {c[3]} -{a[1]} -{a[3]} -{b[1]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[3]} {c[2]} {c[3]} -{a[1]} -{a[3]} -{b[1]} -{b[2]} -{c[0]} -{c[1]}', + f'{a[0]} {a[2]} {aux} {b[1]} {b[2]} {c[0]} {c[1]} -{a[1]} -{a[3]} -{b[0]} -{b[3]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {aux} {b[1]} {c[0]} {c[1]} {c[2]} -{a[1]} -{a[3]} -{b[0]} -{b[2]} -{b[3]} -{c[3]}', + f'{a[0]} {a[2]} {aux} {b[2]} {b[3]} {c[0]} {c[3]} -{a[1]} -{a[3]} -{b[0]} -{b[1]} -{c[1]} -{c[2]}', + f'{a[0]} {a[2]} {aux} {b[3]} {c[0]} {c[2]} {c[3]} -{a[1]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{c[1]}', + f'{a[0]} {a[3]} {aux} {b[0]} {b[1]} {b[3]} {c[1]} -{a[1]} -{a[2]} -{b[2]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {b[0]} {b[1]} {c[1]} {c[3]} -{a[1]} -{a[2]} -{b[2]} -{b[3]} -{c[0]} -{c[2]}', + f'{a[0]} {a[3]} {aux} {b[0]} {b[2]} {b[3]} {c[2]} -{a[1]} -{a[2]} -{b[1]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {b[0]} {b[2]} {c[2]} {c[3]} -{a[1]} -{a[2]} -{b[1]} -{b[3]} -{c[0]} -{c[1]}', + f'{a[0]} {a[3]} {aux} {b[1]} {b[3]} {c[0]} {c[1]} -{a[1]} -{a[2]} -{b[0]} -{b[2]} -{c[2]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {b[1]} {c[0]} {c[1]} {c[3]} -{a[1]} -{a[2]} -{b[0]} -{b[2]} -{b[3]} -{c[2]}', + f'{a[0]} {a[3]} {aux} {b[2]} {b[3]} {c[0]} {c[2]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{c[1]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {b[2]} {c[0]} {c[2]} {c[3]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{b[3]} -{c[1]}', + f'{a[0]} {aux} {b[0]} {b[1]} {b[2]} {c[1]} {c[2]} -{a[1]} -{a[2]} -{a[3]} -{b[3]} -{c[0]} -{c[3]}', + f'{a[0]} {aux} {b[0]} {b[1]} {b[3]} {c[1]} {c[3]} -{a[1]} -{a[2]} -{a[3]} -{b[2]} -{c[0]} -{c[2]}', + f'{a[0]} {aux} {b[0]} {b[2]} {b[3]} {c[2]} {c[3]} -{a[1]} -{a[2]} -{a[3]} -{b[1]} -{c[0]} -{c[1]}', + f'{a[0]} {aux} {b[1]} {b[2]} {c[0]} {c[1]} {c[2]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[3]} -{c[3]}', + f'{a[0]} {aux} {b[1]} {b[3]} {c[0]} {c[1]} {c[3]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[2]} -{c[2]}', + f'{a[0]} {aux} {b[2]} {b[3]} {c[0]} {c[2]} {c[3]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{c[1]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[1]} {b[2]} {b[3]} -{a[0]} -{b[0]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[1]} {b[2]} {c[3]} -{a[0]} -{b[0]} -{b[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[1]} {b[3]} {c[2]} -{a[0]} -{b[0]} -{b[2]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[1]} {c[2]} {c[3]} -{a[0]} -{b[0]} -{b[2]} -{b[3]} -{c[0]} -{c[1]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[2]} {b[3]} {c[1]} -{a[0]} -{b[0]} -{b[1]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[2]} {c[1]} {c[3]} -{a[0]} -{b[0]} -{b[1]} -{b[3]} -{c[0]} -{c[2]}', + f'{a[1]} {a[2]} {a[3]} {aux} {b[3]} {c[1]} {c[2]} -{a[0]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[3]}', + f'{a[1]} {a[2]} {a[3]} {aux} {c[1]} {c[2]} {c[3]} -{a[0]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[0]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} -{a[0]} -{a[3]} -{b[3]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[1]} {c[0]} {c[2]} -{a[0]} -{a[3]} -{b[2]} -{b[3]} -{c[1]} -{c[3]}', + f'{a[1]} {a[2]} {aux} {b[0]} {b[2]} {c[0]} {c[1]} -{a[0]} -{a[3]} -{b[1]} -{b[3]} -{c[2]} -{c[3]}', + f'{a[1]} {a[2]} {aux} {b[0]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[3]} -{b[1]} -{b[2]} -{b[3]} -{c[3]}', + f'{a[1]} {a[2]} {aux} {b[1]} {b[2]} {b[3]} {c[3]} -{a[0]} -{a[3]} -{b[0]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {b[1]} {b[3]} {c[2]} {c[3]} -{a[0]} -{a[3]} -{b[0]} -{b[2]} -{c[0]} -{c[1]}', + f'{a[1]} {a[2]} {aux} {b[2]} {b[3]} {c[1]} {c[3]} -{a[0]} -{a[3]} -{b[0]} -{b[1]} -{c[0]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {b[3]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{c[0]}', + f'{a[1]} {a[3]} {aux} {b[0]} {b[1]} {b[3]} {c[0]} -{a[0]} -{a[2]} -{b[2]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {b[0]} {b[1]} {c[0]} {c[3]} -{a[0]} -{a[2]} -{b[2]} -{b[3]} -{c[1]} -{c[2]}', + f'{a[1]} {a[3]} {aux} {b[0]} {b[3]} {c[0]} {c[1]} -{a[0]} -{a[2]} -{b[1]} -{b[2]} -{c[2]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {b[0]} {c[0]} {c[1]} {c[3]} -{a[0]} -{a[2]} -{b[1]} -{b[2]} -{b[3]} -{c[2]}', + f'{a[1]} {a[3]} {aux} {b[1]} {b[2]} {b[3]} {c[2]} -{a[0]} -{a[2]} -{b[0]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {b[1]} {b[2]} {c[2]} {c[3]} -{a[0]} -{a[2]} -{b[0]} -{b[3]} -{c[0]} -{c[1]}', + f'{a[1]} {a[3]} {aux} {b[2]} {b[3]} {c[1]} {c[2]} -{a[0]} -{a[2]} -{b[0]} -{b[1]} -{c[0]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {b[2]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[2]} -{b[0]} -{b[1]} -{b[3]} -{c[0]}', + f'{a[1]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[2]} -{a[0]} -{a[2]} -{a[3]} -{b[3]} -{c[1]} -{c[3]}', + f'{a[1]} {aux} {b[0]} {b[1]} {b[3]} {c[0]} {c[3]} -{a[0]} -{a[2]} -{a[3]} -{b[2]} -{c[1]} -{c[2]}', + f'{a[1]} {aux} {b[0]} {b[2]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[2]} -{a[3]} -{b[1]} -{b[3]} -{c[3]}', + f'{a[1]} {aux} {b[0]} {b[3]} {c[0]} {c[1]} {c[3]} -{a[0]} -{a[2]} -{a[3]} -{b[1]} -{b[2]} -{c[2]}', + f'{a[1]} {aux} {b[1]} {b[2]} {b[3]} {c[2]} {c[3]} -{a[0]} -{a[2]} -{a[3]} -{b[0]} -{c[0]} -{c[1]}', + f'{a[1]} {aux} {b[2]} {b[3]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{c[0]}', + f'{a[2]} {a[3]} {aux} {b[0]} {b[2]} {b[3]} {c[0]} -{a[0]} -{a[1]} -{b[1]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[2]} {a[3]} {aux} {b[0]} {b[2]} {c[0]} {c[3]} -{a[0]} -{a[1]} -{b[1]} -{b[3]} -{c[1]} -{c[2]}', + f'{a[2]} {a[3]} {aux} {b[0]} {b[3]} {c[0]} {c[2]} -{a[0]} -{a[1]} -{b[1]} -{b[2]} -{c[1]} -{c[3]}', + f'{a[2]} {a[3]} {aux} {b[0]} {c[0]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{b[1]} -{b[2]} -{b[3]} -{c[1]}', + f'{a[2]} {a[3]} {aux} {b[1]} {b[2]} {b[3]} {c[1]} -{a[0]} -{a[1]} -{b[0]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[2]} {a[3]} {aux} {b[1]} {b[2]} {c[1]} {c[3]} -{a[0]} -{a[1]} -{b[0]} -{b[3]} -{c[0]} -{c[2]}', + f'{a[2]} {a[3]} {aux} {b[1]} {b[3]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{b[0]} -{b[2]} -{c[0]} -{c[3]}', + f'{a[2]} {a[3]} {aux} {b[1]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{b[0]} -{b[2]} -{b[3]} -{c[0]}', + f'{a[2]} {aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[1]} -{a[0]} -{a[1]} -{a[3]} -{b[3]} -{c[2]} -{c[3]}', + f'{a[2]} {aux} {b[0]} {b[1]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{a[3]} -{b[2]} -{b[3]} -{c[3]}', + f'{a[2]} {aux} {b[0]} {b[2]} {b[3]} {c[0]} {c[3]} -{a[0]} -{a[1]} -{a[3]} -{b[1]} -{c[1]} -{c[2]}', + f'{a[2]} {aux} {b[0]} {b[3]} {c[0]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[3]} -{b[1]} -{b[2]} -{c[1]}', + f'{a[2]} {aux} {b[1]} {b[2]} {b[3]} {c[1]} {c[3]} -{a[0]} -{a[1]} -{a[3]} -{b[0]} -{c[0]} -{c[2]}', + f'{a[2]} {aux} {b[1]} {b[3]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[3]} -{b[0]} -{b[2]} -{c[0]}', + f'{a[3]} {aux} {b[0]} {b[1]} {b[3]} {c[0]} {c[1]} -{a[0]} -{a[1]} -{a[2]} -{b[2]} -{c[2]} -{c[3]}', + f'{a[3]} {aux} {b[0]} {b[1]} {c[0]} {c[1]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{b[2]} -{b[3]} -{c[2]}', + f'{a[3]} {aux} {b[0]} {b[2]} {b[3]} {c[0]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{b[1]} -{c[1]} -{c[3]}', + f'{a[3]} {aux} {b[0]} {b[2]} {c[0]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{b[1]} -{b[3]} -{c[1]}', + f'{a[3]} {aux} {b[1]} {b[2]} {b[3]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{c[0]} -{c[3]}', + f'{a[3]} {aux} {b[1]} {b[2]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[3]} -{c[0]}', + f'{aux} {b[0]} {b[1]} {b[2]} {c[0]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[3]} -{c[3]}', + f'{aux} {b[0]} {b[1]} {b[3]} {c[0]} {c[1]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[2]} -{c[2]}', + f'{aux} {b[0]} {b[2]} {b[3]} {c[0]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[1]} -{c[1]}', + f'{aux} {b[1]} {b[2]} {b[3]} {c[1]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{c[0]}', + f'{a[0]} {a[1]} {aux} {b[0]} {b[1]} -{a[2]} -{a[3]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {aux} {b[0]} {c[1]} -{a[2]} -{a[3]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {aux} {b[1]} {c[0]} -{a[2]} -{a[3]} -{b[0]} -{b[2]} -{b[3]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[1]} {aux} {c[0]} {c[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {aux} {b[0]} {b[2]} -{a[1]} -{a[3]} -{b[1]} -{b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {aux} {b[0]} {c[2]} -{a[1]} -{a[3]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[0]} {a[2]} {aux} {b[2]} {c[0]} -{a[1]} -{a[3]} -{b[0]} -{b[1]} -{b[3]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[2]} {aux} {c[0]} {c[2]} -{a[1]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[1]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {b[0]} {b[3]} -{a[1]} -{a[2]} -{b[1]} -{b[2]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {b[0]} {c[3]} -{a[1]} -{a[2]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {a[3]} {aux} {b[3]} {c[0]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {a[3]} {aux} {c[0]} {c[3]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[1]} -{c[2]}', + f'{a[0]} {aux} {b[0]} {b[1]} {c[1]} -{a[1]} -{a[2]} -{a[3]} -{b[2]} -{b[3]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[0]} {aux} {b[0]} {b[2]} {c[2]} -{a[1]} -{a[2]} -{a[3]} -{b[1]} -{b[3]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[0]} {aux} {b[0]} {b[3]} {c[3]} -{a[1]} -{a[2]} -{a[3]} -{b[1]} -{b[2]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[0]} {aux} {b[1]} {c[0]} {c[1]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[2]} -{b[3]} -{c[2]} -{c[3]}', + f'{a[0]} {aux} {b[2]} {c[0]} {c[2]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[3]} -{c[1]} -{c[3]}', + f'{a[0]} {aux} {b[3]} {c[0]} {c[3]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{c[1]} -{c[2]}', + f'{a[1]} {a[2]} {aux} {b[1]} {b[2]} -{a[0]} -{a[3]} -{b[0]} -{b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {a[2]} {aux} {b[1]} {c[2]} -{a[0]} -{a[3]} -{b[0]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[1]} {a[2]} {aux} {b[2]} {c[1]} -{a[0]} -{a[3]} -{b[0]} -{b[1]} -{b[3]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[1]} {a[2]} {aux} {c[1]} {c[2]} -{a[0]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {b[1]} {b[3]} -{a[0]} -{a[2]} -{b[0]} -{b[2]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {b[1]} {c[3]} -{a[0]} -{a[2]} -{b[0]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[1]} {a[3]} {aux} {b[3]} {c[1]} -{a[0]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[1]} {a[3]} {aux} {c[1]} {c[3]} -{a[0]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[2]}', + f'{a[1]} {aux} {b[0]} {b[1]} {c[0]} -{a[0]} -{a[2]} -{a[3]} -{b[2]} -{b[3]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {aux} {b[0]} {c[0]} {c[1]} -{a[0]} -{a[2]} -{a[3]} -{b[1]} -{b[2]} -{b[3]} -{c[2]} -{c[3]}', + f'{a[1]} {aux} {b[1]} {b[2]} {c[2]} -{a[0]} -{a[2]} -{a[3]} -{b[0]} -{b[3]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[1]} {aux} {b[1]} {b[3]} {c[3]} -{a[0]} -{a[2]} -{a[3]} -{b[0]} -{b[2]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[1]} {aux} {b[2]} {c[1]} {c[2]} -{a[0]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[3]} -{c[0]} -{c[3]}', + f'{a[1]} {aux} {b[3]} {c[1]} {c[3]} -{a[0]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[2]}', + f'{a[2]} {a[3]} {aux} {b[2]} {b[3]} -{a[0]} -{a[1]} -{b[0]} -{b[1]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[2]} {a[3]} {aux} {b[2]} {c[3]} -{a[0]} -{a[1]} -{b[0]} -{b[1]} -{b[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[2]} {a[3]} {aux} {b[3]} {c[2]} -{a[0]} -{a[1]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[2]} {a[3]} {aux} {c[2]} {c[3]} -{a[0]} -{a[1]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[1]}', + f'{a[2]} {aux} {b[0]} {b[2]} {c[0]} -{a[0]} -{a[1]} -{a[3]} -{b[1]} -{b[3]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[2]} {aux} {b[0]} {c[0]} {c[2]} -{a[0]} -{a[1]} -{a[3]} -{b[1]} -{b[2]} -{b[3]} -{c[1]} -{c[3]}', + f'{a[2]} {aux} {b[1]} {b[2]} {c[1]} -{a[0]} -{a[1]} -{a[3]} -{b[0]} -{b[3]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[2]} {aux} {b[1]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{a[3]} -{b[0]} -{b[2]} -{b[3]} -{c[0]} -{c[3]}', + f'{a[2]} {aux} {b[2]} {b[3]} {c[3]} -{a[0]} -{a[1]} -{a[3]} -{b[0]} -{b[1]} -{c[0]} -{c[1]} -{c[2]}', + f'{a[2]} {aux} {b[3]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[1]}', + f'{a[3]} {aux} {b[0]} {b[3]} {c[0]} -{a[0]} -{a[1]} -{a[2]} -{b[1]} -{b[2]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[3]} {aux} {b[0]} {c[0]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{b[1]} -{b[2]} -{b[3]} -{c[1]} -{c[2]}', + f'{a[3]} {aux} {b[1]} {b[3]} {c[1]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[2]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[3]} {aux} {b[1]} {c[1]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[2]} -{b[3]} -{c[0]} -{c[2]}', + f'{a[3]} {aux} {b[2]} {b[3]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[3]} {aux} {b[2]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{b[3]} -{c[0]} -{c[1]}', + f'{aux} {b[0]} {b[1]} {c[0]} {c[1]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[2]} -{b[3]} -{c[2]} -{c[3]}', + f'{aux} {b[0]} {b[2]} {c[0]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[1]} -{b[3]} -{c[1]} -{c[3]}', + f'{aux} {b[0]} {b[3]} {c[0]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[1]} -{b[2]} -{c[1]} -{c[2]}', + f'{aux} {b[1]} {b[2]} {c[1]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[3]} -{c[0]} -{c[3]}', + f'{aux} {b[1]} {b[3]} {c[1]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[2]} -{c[0]} -{c[2]}', + f'{aux} {b[2]} {b[3]} {c[2]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{c[0]} -{c[1]}', + f'{a[0]} {aux} {b[0]} -{a[1]} -{a[2]} -{a[3]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[0]} {aux} {c[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {aux} {b[1]} -{a[0]} -{a[2]} -{a[3]} -{b[0]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[1]} {aux} {c[1]} -{a[0]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[2]} -{c[3]}', + f'{a[2]} {aux} {b[2]} -{a[0]} -{a[1]} -{a[3]} -{b[0]} -{b[1]} -{b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[2]} {aux} {c[2]} -{a[0]} -{a[1]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[3]}', + f'{a[3]} {aux} {b[3]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + f'{a[3]} {aux} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[2]}', + f'{aux} {b[0]} {c[0]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[1]} -{b[2]} -{b[3]} -{c[1]} -{c[2]} -{c[3]}', + f'{aux} {b[1]} {c[1]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[2]} -{b[3]} -{c[0]} -{c[2]} -{c[3]}', + f'{aux} {b[2]} {c[2]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[3]} -{c[0]} -{c[1]} -{c[3]}', + f'{aux} {b[3]} {c[3]} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{c[0]} -{c[1]} -{c[2]}', + f'{aux} -{a[0]} -{a[1]} -{a[2]} -{a[3]} -{b[0]} -{b[1]} -{b[2]} -{b[3]} -{c[0]} -{c[1]} -{c[2]} -{c[3]}', + ] diff --git a/claasp/cipher_modules/models/sat/utils/utils.py b/claasp/cipher_modules/models/sat/utils/utils.py index 861df1ca..34d8c518 100644 --- a/claasp/cipher_modules/models/sat/utils/utils.py +++ b/claasp/cipher_modules/models/sat/utils/utils.py @@ -706,10 +706,10 @@ def _get_data(data_keywords, lines): return data -def run_sat_solver(solver_name, options, dimacs_input, host=None, env_vars_string=""): +def run_sat_solver(solver_specs, options, dimacs_input, host=None, env_vars_string=""): """Call the SAT solver specified in `solver_specs`, using input and output pipes.""" - solver_specs = constants.SAT_SOLVERS[solver_name] - command = solver_specs['command'][:] + options + solver_name = solver_specs['solver_name'] + command = [solver_specs['keywords']['command']['executable']] + solver_specs['keywords']['command']['options'] + options if host: command = ['ssh', f'{host}'] + [env_vars_string] + command solver_process = subprocess.run(command, input=dimacs_input, capture_output=True, text=True) @@ -722,7 +722,7 @@ def run_sat_solver(solver_name, options, dimacs_input, host=None, env_vars_strin values.extend(line.split()[1:]) values = values[:-1] if solver_name == 'kissat': - data_keywords = solver_specs['time'] + data_keywords = solver_specs['keywords']['time'] lines = solver_output data_line = [line for line in lines if data_keywords in line][0] seconds_str_index = data_line.find("seconds") - 2 @@ -732,9 +732,9 @@ def run_sat_solver(solver_name, options, dimacs_input, host=None, env_vars_strin seconds_str_index -= 1 time = float(output_str[::-1]) else: - time = _get_data(solver_specs['time'], solver_output) + time = _get_data(solver_specs['keywords']['time'], solver_output) memory = float('inf') - memory_keywords = solver_specs['memory'] + memory_keywords = solver_specs['keywords']['memory'] if memory_keywords: if not (solver_name == 'glucose-syrup' and status != 'SATISFIABLE'): memory = _get_data(memory_keywords, solver_output) @@ -746,18 +746,17 @@ def run_sat_solver(solver_name, options, dimacs_input, host=None, env_vars_strin return status, time, memory, values -def run_minisat(options, dimacs_input, input_file_name, output_file_name): +def run_minisat(solver_specs, options, dimacs_input, input_file_name, output_file_name): """Call the MiniSat solver specified in `solver_specs`, using input and output files.""" with open(input_file_name, 'wt') as input_file: input_file.write(dimacs_input) - solver_specs = constants.SAT_SOLVERS['minisat'] - command = solver_specs['command'][:] + options + command = [solver_specs['keywords']['command']['executable']] + solver_specs['keywords']['command']['options'] + options command.append(input_file_name) command.append(output_file_name) solver_process = subprocess.run(command, capture_output=True, text=True) solver_output = solver_process.stdout.splitlines() - time = _get_data(solver_specs['time'], solver_output) - memory = _get_data(solver_specs['memory'], solver_output) + time = _get_data(solver_specs['keywords']['time'], solver_output) + memory = _get_data(solver_specs['keywords']['memory'], solver_output) status = solver_output[-1] values = [] if status == 'SATISFIABLE': @@ -769,13 +768,12 @@ def run_minisat(options, dimacs_input, input_file_name, output_file_name): return status, time, memory, values -def run_parkissat(options, dimacs_input, input_file_name): +def run_parkissat(solver_specs, options, dimacs_input, input_file_name): """Call the Parkissat solver specified in `solver_specs`, using input and output files.""" with open(input_file_name, 'wt') as input_file: input_file.write(dimacs_input) - solver_specs = constants.SAT_SOLVERS['parkissat'] import time - command = solver_specs['command'][:] + options + command = [solver_specs['keywords']['command']['executable']] + solver_specs['keywords']['command']['options'] + options command.append(input_file_name) start = time.time() solver_process = subprocess.run(command, capture_output=True, text=True) @@ -797,18 +795,17 @@ def run_parkissat(options, dimacs_input, input_file_name): return status, time, memory, values -def run_yices(options, dimacs_input, input_file_name): +def run_yices(solver_specs, options, dimacs_input, input_file_name): """Call the Yices SAT solver specified in `solver_specs`, using input file.""" with open(input_file_name, 'wt') as input_file: input_file.write(dimacs_input) - solver_specs = constants.SAT_SOLVERS['yices-sat'] - command = solver_specs['command'][:] + options + command = [solver_specs['keywords']['command']['executable']] + solver_specs['keywords']['command']['options'] + options command.append(input_file_name) solver_process = subprocess.run(command, capture_output=True, text=True) solver_stats = solver_process.stderr.splitlines() solver_output = solver_process.stdout.splitlines() - time = _get_data(solver_specs['time'], solver_stats) - memory = _get_data(solver_specs['memory'], solver_stats) + time = _get_data(solver_specs['keywords']['time'], solver_stats) + memory = _get_data(solver_specs['keywords']['memory'], solver_stats) status = 'SATISFIABLE' if solver_output[0] == 'sat' else 'UNSATISFIABLE' values = [] if status == 'SATISFIABLE': diff --git a/claasp/cipher_modules/models/smt/smt_model.py b/claasp/cipher_modules/models/smt/smt_model.py index 4c898b63..71989705 100644 --- a/claasp/cipher_modules/models/smt/smt_model.py +++ b/claasp/cipher_modules/models/smt/smt_model.py @@ -31,21 +31,11 @@ class :py:class:`Sat Model `). SMT-LIB is the chosen standard. -An SMT solver is called by a subprocess, therefore note also that you will not -be able to solve the models in the SMT-LIB files until you have installed one -SMT solver at least. In methods, solvers are chosen by ``solver_name`` -variable. Solvers and their corresponding values for ``solver_name`` variable -are: - - =========================================== ================= - SMT solver value - =========================================== ================= - `Z3 `_ ``'z3'`` - `Yices-smt2 `_ ``'yices-smt2'`` - `MathSAT `_ ``'mathsat'`` - =========================================== ================= - -The default choice is z3. +This module is able to use many different SMT solvers. + +For any further information, refer to the file +:py:mod:`claasp.cipher_modules.models.smt.solvers.py` and to the section +:ref:`Available SMT solvers`. """ import math import re @@ -53,6 +43,7 @@ class :py:class:`Sat Model `). SMT-LIB is t from claasp.name_mappings import (SBOX, CIPHER, XOR_LINEAR) from claasp.cipher_modules.models.smt.utils import constants, utils +from claasp.cipher_modules.models.smt import solvers from claasp.cipher_modules.models.utils import convert_solver_solution_to_dictionary, set_component_solution @@ -375,7 +366,7 @@ def update_constraints_for_not_equal_type(self, bit_positions, bit_values, literals.append(f'{component_id}_{position}{out_suffix}') constraints.append(utils.smt_assert(utils.smt_or(literals))) - def solve(self, model_type, solver_name='z3'): + def solve(self, model_type, solver_name=solvers.SOLVER_DEFAULT): """ Return the solution of the model using the ``solver_name`` SMT solver. @@ -386,7 +377,7 @@ def solve(self, model_type, solver_name='z3'): * ``'cipher'`` * ``'xor_differential'`` * ``'xor_linear'`` - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -401,7 +392,7 @@ def solve(self, model_type, solver_name='z3'): sage: smt.solve('xor_differential') # random {'cipher_id': 'speck_p32_k64_o32_r4', 'model_type': 'xor_differential', - 'solver_name': 'z3', + 'solver_name': 'Z3_EXT', 'solving_time_seconds': 0.0, 'memory_megabytes': 0.09, 'components_values': {}, @@ -412,19 +403,21 @@ def _get_data(data_string, lines): data = float(re.findall(r'\d+\.?\d*', data_line)[0]) return data - solver_specs = constants.SMT_SOLVERS[solver_name] - command = solver_specs['command'][:] + solver_specs = [specs for specs in solvers.SMT_SOLVERS_EXTERNAL + if specs['solver_name'] == solver_name.upper()][0] + solver_name = solver_specs['solver_name'] + command = [solver_specs['keywords']['command']['executable']] + solver_specs['keywords']['command']['options'] smt_input = '\n'.join(self._model_constraints) + '\n' solver_process = subprocess.run(command, input=smt_input, capture_output=True, text=True) solver_output = solver_process.stdout.splitlines() - solve_time = _get_data(solver_specs['time'], solver_output) - memory = _get_data(solver_specs['memory'], solver_output) + solve_time = _get_data(solver_specs['keywords']['time'], solver_output) + memory = _get_data(solver_specs['keywords']['memory'], solver_output) if solver_output[0] == 'sat': - if solver_name == 'z3': + if solver_name == 'Z3_EXT': variable2value = z3_parser(solver_output) - elif solver_name == 'yices-smt2': + elif solver_name == 'YICES_EXT': variable2value = yices_parser(solver_output) - elif solver_name == 'mathsat': + elif solver_name == 'MATHSAT_EXT': variable2value = mathsat_parser(solver_output) component2attributes, total_weight = self._parse_solver_output(variable2value) status = 'SATISFIABLE' diff --git a/claasp/cipher_modules/models/smt/smt_models/smt_cipher_model.py b/claasp/cipher_modules/models/smt/smt_models/smt_cipher_model.py index b844d458..9eda9217 100644 --- a/claasp/cipher_modules/models/smt/smt_models/smt_cipher_model.py +++ b/claasp/cipher_modules/models/smt/smt_models/smt_cipher_model.py @@ -19,6 +19,7 @@ import time +from claasp.cipher_modules.models.smt import solvers from claasp.cipher_modules.models.smt.smt_model import SmtModel from claasp.cipher_modules.models.smt.utils import constants from claasp.cipher_modules.models.smt.utils.utils import get_component_hex_value @@ -77,7 +78,7 @@ def build_cipher_model(self, fixed_variables=[]): self._model_constraints = \ constants.MODEL_PREFIX + self._declarations + self._model_constraints + constants.MODEL_SUFFIX - def find_missing_bits(self, fixed_values=[], solver_name='z3'): + def find_missing_bits(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a generic flow of the cipher from plaintext and key to ciphertext. @@ -105,7 +106,7 @@ def find_missing_bits(self, fixed_values=[], solver_name='z3'): sage: smt.find_missing_bits(fixed_values=[ciphertext]) # random {'cipher_id': 'speck_k64_p32_o32_r22', 'model_type': 'speck_k64_p32_o32_r22', - 'solver_name': 'cryptominisat', + 'solver_name': 'Z3_EXT', ... 'intermediate_output_21_11': {'value': '90fe', 'weight': 0}, 'cipher_output_21_12': {'value': 'affec7ed', 'weight': 0}}, 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 7b65f33c..df3b3a22 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 @@ -19,6 +19,7 @@ import time +from claasp.cipher_modules.models.smt import solvers 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, get_single_key_scenario_format_for_fixed_values @@ -87,7 +88,7 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]): self._model_constraints = \ constants.MODEL_PREFIX + self._declarations + self._model_constraints + constants.MODEL_SUFFIX - def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='z3'): + def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ 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. @@ -96,7 +97,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed - ``fixed_weight`` -- **integer**; the weight to be fixed - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` in method - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -158,7 +159,7 @@ def find_all_xor_differential_trails_with_fixed_weight(self, fixed_weight, fixed return solutions_list def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_weight, fixed_values=[], - solver_name='z3'): + solver_name=solvers.SOLVER_DEFAULT): """ Return a list of solutions. By default, the search is set in the single-key setting. @@ -171,7 +172,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w - ``min_weight`` -- **integer**; the weight from which to start the search - ``max_weight`` -- **integer**; the weight at which the search stops - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -214,7 +215,7 @@ def find_all_xor_differential_trails_with_weight_at_most(self, min_weight, max_w return solutions_list - def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name='z3'): + def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a trail with the lowest weight. By default, the search is set in the single-key setting. @@ -227,7 +228,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name INPUT: - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -282,7 +283,7 @@ def find_lowest_weight_xor_differential_trail(self, fixed_values=[], solver_name return solution - def find_one_xor_differential_trail(self, fixed_values=[], solver_name='z3'): + def find_one_xor_differential_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a XOR differential trail. By default, the search is set in the single-key setting. @@ -291,7 +292,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='z3'): INPUT: - ``fixed_values`` -- **list** (default: `[]`); can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -307,7 +308,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='z3'): sage: smt.find_one_xor_differential_trail() # random {'cipher_id': 'speck_p32_k64_o32_r5', 'model_type': 'xor_differential', - 'solver_name': 'z3', + 'solver_name': 'Z3_EXT', 'solving_time_seconds': 0.05, 'memory_megabytes': 19.28, ... @@ -336,7 +337,8 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name='z3'): return solution - def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='z3'): + def find_one_xor_differential_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], + solver_name=solvers.SOLVER_DEFAULT): """ 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. 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 4c023ba5..1bfaf8a2 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 @@ -19,6 +19,7 @@ import time +from claasp.cipher_modules.models.smt import solvers 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.smt.utils.constants import INPUT_BIT_ID_SUFFIX, OUTPUT_BIT_ID_SUFFIX @@ -148,7 +149,8 @@ def cipher_input_xor_linear_variables(self): return cipher_input_bit_ids - def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], solver_name='z3'): + def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_values=[], + solver_name=solvers.SOLVER_DEFAULT): """ 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. @@ -158,7 +160,7 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value - ``fixed_weight`` -- **integer**; the weight to be fixed - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -221,7 +223,8 @@ def find_all_xor_linear_trails_with_fixed_weight(self, fixed_weight, fixed_value return solutions_list - def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, fixed_values=[], solver_name='z3'): + def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, fixed_values=[], + solver_name=solvers.SOLVER_DEFAULT): """ Return a list of solutions. By default, the search removes the key schedule, if any. @@ -234,7 +237,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, - ``min_weight`` -- **integer**; the weight from which to start the search - ``max_weight`` -- **integer**; the weight at which the search stops - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -272,7 +275,7 @@ def find_all_xor_linear_trails_with_weight_at_most(self, min_weight, max_weight, return solutions_list - def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='z3'): + def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a XOR LINEAR trail with the lowest possible weight. By default, the search removes the key schedule, if any. @@ -286,7 +289,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='z3') INPUT: - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -336,7 +339,7 @@ def find_lowest_weight_xor_linear_trail(self, fixed_values=[], solver_name='z3') return solution - def find_one_xor_linear_trail(self, fixed_values=[], solver_name='z3'): + def find_one_xor_linear_trail(self, fixed_values=[], solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a XOR linear trail. By default, the search removes the key schedule, if any. @@ -347,7 +350,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='z3'): INPUT: - ``fixed_values`` -- **list** (default: `[]`); they can be created using ``set_fixed_variables`` method - - ``solver_name`` -- **string** (default: `z3`); the name of the solver + - ``solver_name`` -- **string** (default: `Z3_EXT`); the name of the solver .. SEEALSO:: @@ -362,7 +365,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='z3'): sage: smt.find_one_xor_linear_trail() #random {'cipher_id': 'speck_p32_k64_o32_r4', 'model_type': 'xor_linear', - 'solver_name': 'z3', + 'solver_name': 'Z3_EXT', 'solving_time_seconds': 0.06, 'memory_megabytes': 19.65, ... @@ -387,7 +390,7 @@ def find_one_xor_linear_trail(self, fixed_values=[], solver_name='z3'): return solution def find_one_xor_linear_trail_with_fixed_weight(self, fixed_weight, fixed_values=[], - solver_name='z3'): + solver_name=solvers.SOLVER_DEFAULT): """ Return the solution representing a XOR linear trail whose weight is ``fixed_weight``. By default, the search removes the key schedule, if any. diff --git a/claasp/cipher_modules/models/smt/solvers.py b/claasp/cipher_modules/models/smt/solvers.py new file mode 100644 index 00000000..cf47eaa3 --- /dev/null +++ b/claasp/cipher_modules/models/smt/solvers.py @@ -0,0 +1,94 @@ +# **************************************************************************** +# Copyright 2023 Technology Innovation Institute +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# **************************************************************************** +"""SMT solvers + +.. _Available SMT solvers: + +Available SMT solvers +--------------------- + +In this file, all the available SMT solvers are listed. They are only external. + +External SMT solvers need to be installed in the system as long as you want a +bare metal installation since they are called using a subprocess. If you use a +Docker container running the default image for the library no further action is +needed. +""" + + +SOLVER_DEFAULT = "Z3_EXT" + + +SMT_SOLVERS_INTERNAL = [] + + +SMT_SOLVERS_EXTERNAL = [ + { + "solver_brand_name": "MathSAT 5", + "solver_name": "MATHSAT_EXT", + "keywords": { + "command": { + "executable": "mathsat", + "options": ['-model', '-stats'], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "time-seconds", + "memory": "memory-mb", + "unsat_condition": "unsat", + }, + }, + { + "solver_brand_name": "Yices2", + "solver_name": "YICES_EXT", + "keywords": { + "command": { + "executable": "yices-smt2", + "options": ["--stats"], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "total-run-time", + "memory": "mem-usage", + "unsat_condition": "unsat", + }, + }, + { + "solver_brand_name": "Z3 Theorem Prover", + "solver_name": "Z3_EXT", + "keywords": { + "command": { + "executable": "z3", + "options": ["-st", "-in"], + "input_file": "", + "solve": "", + "output_file": "", + "end": "", + "format": ["executable", "options", "input_file"], + }, + "time": "total-time", + "memory": "memory", + "unsat_condition": "unsat", + }, + }, +] diff --git a/claasp/cipher_modules/models/smt/utils/constants.py b/claasp/cipher_modules/models/smt/utils/constants.py index b065d69e..eaf470a8 100644 --- a/claasp/cipher_modules/models/smt/utils/constants.py +++ b/claasp/cipher_modules/models/smt/utils/constants.py @@ -1,21 +1,4 @@ INPUT_BIT_ID_SUFFIX = '_i' OUTPUT_BIT_ID_SUFFIX = '_o' MODEL_PREFIX = ['(set-option :print-success false)', '(set-logic QF_UF)'] -MODEL_SUFFIX = ['(check-sat)', '(get-model)', '(get-info :all-statistics)', '(exit)'] -SMT_SOLVERS = { - 'mathsat': { - 'command': ['mathsat', '-model', '-stats'], - 'time': 'time-seconds', - 'memory': 'memory-mb' - }, - 'yices-smt2': { - 'command': ['yices-smt2', '--stats'], - 'time': 'total-run-time', - 'memory': 'mem-usage' - }, - 'z3': { - 'command': ['z3', '-st', '-in'], - 'time': 'total-time', - 'memory': 'memory' - } -} +MODEL_SUFFIX = ['(check-sat)', '(get-model)', '(exit)'] diff --git a/claasp/cipher_modules/neural_network_tests.py b/claasp/cipher_modules/neural_network_tests.py index e5793ee4..dbcf0528 100644 --- a/claasp/cipher_modules/neural_network_tests.py +++ b/claasp/cipher_modules/neural_network_tests.py @@ -156,7 +156,7 @@ def _update_blackbox_distinguisher_vectorized_tests_ds(self, base_inputs, base_o base_inputs_np[index] = random_inputs_for_index base_input_index_unpacked = np.unpackbits(base_inputs_np[index].transpose(), axis=1) - cipher_output = evaluator.evaluate_vectorized(self.cipher, base_inputs_np, intermediate_outputs=True) + cipher_output = evaluator.evaluate_vectorized(self.cipher, base_inputs_np, intermediate_output=True) for k in cipher_output: for j in range(len(cipher_output[k])): @@ -325,12 +325,12 @@ 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 int(d).to_bytes(input_lengths[index] // 8, byteorder='big')]) + d_array = np.uint8([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() - - cipher_output = evaluator.evaluate_vectorized(self.cipher, base_inputs_np, intermediate_outputs=True) - other_output = evaluator.evaluate_vectorized(self.cipher, other_inputs_np, intermediate_outputs=True) + print([(x.shape, x.dtype) for x in other_inputs_np]) + cipher_output = evaluator.evaluate_vectorized(self.cipher, base_inputs_np, intermediate_output=True) + other_output = evaluator.evaluate_vectorized(self.cipher, other_inputs_np, intermediate_output=True) for k in cipher_output: for j in range(len(cipher_output[k])): @@ -365,17 +365,17 @@ class RoundNumberTooHigh(Exception): if number_of_rounds < self.cipher.number_of_rounds: C0 = np.unpackbits( - self.cipher.evaluate_vectorized(inputs_0, intermediate_outputs=True)['round_output'][number_of_rounds - 1], + self.cipher.evaluate_vectorized(inputs_0, intermediate_output=True)['round_output'][number_of_rounds - 1], axis=1) C1 = np.unpackbits( - self.cipher.evaluate_vectorized(inputs_1, intermediate_outputs=True)['round_output'][number_of_rounds - 1], + self.cipher.evaluate_vectorized(inputs_1, intermediate_output=True)['round_output'][number_of_rounds - 1], axis=1) elif number_of_rounds == self.cipher.number_of_rounds: C0 = np.unpackbits( - self.cipher.evaluate_vectorized(inputs_0, intermediate_outputs=True)['cipher_output'][0], + self.cipher.evaluate_vectorized(inputs_0, intermediate_output=True)['cipher_output'][0], axis=1) C1 = np.unpackbits( - self.cipher.evaluate_vectorized(inputs_1, intermediate_outputs=True)['cipher_output'][0], + self.cipher.evaluate_vectorized(inputs_1, intermediate_output=True)['cipher_output'][0], axis=1) else: raise RoundNumberTooHigh("The number of rounds required for the differential dataset is larger than the number of rounds of the" @@ -787,7 +787,7 @@ def find_good_input_difference_for_neural_distinguisher(self, difference_positio # Initialisation input_lengths = self.cipher.inputs_bit_size input_tags = self.cipher.inputs - evaluate = lambda x: self.cipher.evaluate_vectorized(x, intermediate_outputs=True) + evaluate = lambda x: self.cipher.evaluate_vectorized(x, intermediate_output=True) threshold = 0.05 # Generation of the baseline ciphertexts inputs0 = [] diff --git a/claasp/cipher_modules/statistical_tests/dataset_generator.py b/claasp/cipher_modules/statistical_tests/dataset_generator.py index ba3bff9b..65c92c7d 100644 --- a/claasp/cipher_modules/statistical_tests/dataset_generator.py +++ b/claasp/cipher_modules/statistical_tests/dataset_generator.py @@ -105,7 +105,7 @@ def generate_avalanche_dataset(self, input_index, number_of_samples, save_file=F inputs.append(np.zeros(shape=(bit_size // 8, number_of_samples), dtype=np.uint8)) # output of cipher - outputs = self.cipher.evaluate_vectorized(inputs, intermediate_outputs=True) + outputs = self.cipher.evaluate_vectorized(inputs, intermediate_output=True) # avalanche output of cipher outputs_avanlanche_list = [ @@ -122,7 +122,7 @@ def generate_avalanche_dataset(self, input_index, number_of_samples, save_file=F for i in range(self.cipher.inputs_bit_size[input_index]): inputs_avalanche = deepcopy(inputs) inputs_avalanche[input_index] = xor(inputs_avalanche[input_index], np.packbits(mask, axis=0)) - outputs_avanlanche = self.cipher.evaluate_vectorized(inputs_avalanche, intermediate_outputs=True) + outputs_avanlanche = self.cipher.evaluate_vectorized(inputs_avalanche, intermediate_output=True) for r in range(self.cipher.number_of_rounds - 1): outputs_avanlanche_list[r][:, i * self.cipher.output_bit_size // 8:(i + 1) * self.cipher.output_bit_size // 8] = \ @@ -220,7 +220,7 @@ def get_cipher_outputs_for_cbc_dataset(self, input_index, number_of_blocks_in_on for j in range(number_of_blocks_in_one_sample): # output of cipher - outputs = self.cipher.evaluate_vectorized(inputs, intermediate_outputs=True) + outputs = self.cipher.evaluate_vectorized(inputs, intermediate_output=True) for round_number in range(self.cipher.number_of_rounds - 1): outputs_list[round_number].append(outputs["round_output"][round_number][round_number]) inputs[input_index][:, round_number] = \ @@ -303,7 +303,7 @@ def get_cipher_outputs_for_correlation_dataset(self, input_index, inputs_fixed, np.random.randint(256, size=(1, bit_size // 8)), dtype=np.uint8) inputs.append(rand_input.transpose()) - outputs = self.cipher.evaluate_vectorized(inputs, intermediate_outputs=True) + outputs = self.cipher.evaluate_vectorized(inputs, intermediate_output=True) for r in range(self.cipher.number_of_rounds - 1): outputs_list[r].append(xor(outputs["round_output"][r], inputs_fixed.transpose())) outputs_list[-1].append(xor(outputs["cipher_output"][0], inputs_fixed.transpose())) @@ -388,7 +388,7 @@ def get_cipher_outputs_for_density_dataset(self, input_index, inputs_density, nu inputs.append(rand_input.transpose()) # output of cipher - outputs = self.cipher.evaluate_vectorized(inputs, intermediate_outputs=True) + outputs = self.cipher.evaluate_vectorized(inputs, intermediate_output=True) for r in range(self.cipher.number_of_rounds - 1): outputs_list[r].append(outputs["round_output"][r]) outputs_list[-1].append(outputs["cipher_output"][0]) @@ -506,7 +506,7 @@ def generate_random_dataset(self, input_index, number_of_samples, inputs.append(rand_input.transpose()) # output of cipher - outputs = self.cipher.evaluate_vectorized(inputs, intermediate_outputs=True) + outputs = self.cipher.evaluate_vectorized(inputs, intermediate_output=True) for round_number in range(self.cipher.number_of_rounds - 1): outputs_list[round_number].append(outputs["round_output"][round_number]) outputs_list[-1].append(outputs["cipher_output"][0]) diff --git a/claasp/ciphers/block_ciphers/aes_block_cipher.py b/claasp/ciphers/block_ciphers/aes_block_cipher.py index 5f48c051..dd47bb48 100644 --- a/claasp/ciphers/block_ciphers/aes_block_cipher.py +++ b/claasp/ciphers/block_ciphers/aes_block_cipher.py @@ -364,4 +364,4 @@ def create_round_output_component(self, add_round_key, number_of_rounds, round_n [[i for i in range(self.CIPHER_BLOCK_SIZE)]], self.CIPHER_BLOCK_SIZE, "round_output") - self.add_round() + self.add_round() \ No newline at end of file diff --git a/claasp/ciphers/block_ciphers/kasumi_block_cipher.py b/claasp/ciphers/block_ciphers/kasumi_block_cipher.py index 8d83e2fb..e02e2fed 100644 --- a/claasp/ciphers/block_ciphers/kasumi_block_cipher.py +++ b/claasp/ciphers/block_ciphers/kasumi_block_cipher.py @@ -70,8 +70,8 @@ ] PARAMETERS_CONFIGURATION_LIST = [{'block_bit_size': 64, 'key_bit_size': 128, 'number_of_rounds': 8}] - - +half_half_word_distribution = [7, 2, 7] +half_word_distribution = half_half_word_distribution + half_half_word_distribution class KasumiBlockCipher(Cipher): """ Return a cipher object of Kasumi Block Cipher. @@ -102,33 +102,123 @@ def __init__(self, block_bit_size=64, key_bit_size=128, number_of_rounds=8): cipher_inputs_bit_size=[key_bit_size, block_bit_size], cipher_output_bit_size=block_bit_size) - p1, p2 = self.round_initialization() + left_half_ids, left_half_positions, right_half_ids, right_half_positions = KasumiBlockCipher.init_halves() + key = [INPUT_KEY], [list(range(self.key_bit_size))] - self.add_round() - key_derived = self.derived_key(key) for round_number in range(self._get_number_of_rounds(number_of_rounds)): - if round_number != 0: - self.add_round() + self.add_round() + if round_number == 0: + key_derived = self.derived_key(key) sub_key = self.round_key(key, key_derived, round_number + 1) if round_number % 2 == 0: - fl = self.fl_function(p1, sub_key) - fo = self.fo_function(fl, sub_key) - self.add_XOR_component([fo.id[0], p2.id[0]], [list(range(2 * self.WORD_SIZE)), - p2.input_bit_positions[0]], 2 * self.WORD_SIZE) - p2 = ComponentState([self.get_current_component_id()], [list(range(2 * self.WORD_SIZE))]) - else: - fo = self.fo_function(p2, sub_key) - fl = self.fl_function(fo, sub_key) - self.add_XOR_component([fl.id[0], p1.id[0]], [list(range(2 * self.WORD_SIZE)), - p1.input_bit_positions[0]], 2 * self.WORD_SIZE) - p1 = ComponentState([self.get_current_component_id()], [list(range(2 * self.WORD_SIZE))]) + right_half_ids, right_half_positions = self._even_round( + left_half_ids, + left_half_positions, + sub_key, + right_half_ids, + right_half_positions + ) - self.add_round_output_component([p1.id[0], p2.id[0]], [list(range(2 * self.WORD_SIZE)), - list(range(2 * self.WORD_SIZE))], - self.block_bit_size) - - self.add_cipher_output_component([p1.id[0], p2.id[0]], [list(range(2*self.WORD_SIZE)), - list(range(2*self.WORD_SIZE))], self.block_bit_size) + else: + left_half_ids, left_half_positions = self._odd_round( + left_half_ids, + left_half_positions, + sub_key, + right_half_ids, + right_half_positions + ) + + self.add_round_output_component( + left_half_ids + right_half_ids, + [list(range(size)) for size in half_word_distribution * 2], + self.block_bit_size + ) + + self.add_cipher_output_component( + left_half_ids + right_half_ids, + [list(range(size)) for size in half_word_distribution * 2], + self.block_bit_size + ) + + @staticmethod + def init_halves(): + left_half_ids = ['plaintext' for _ in range(6)] + left_half_positions = [ + list(range(sum(half_word_distribution[:i]), sum(half_word_distribution[:i + 1]))) for i + in range(len(half_word_distribution)) + ] + right_half_ids = ['plaintext' for _ in range(6)] + offset = 32 + right_half_positions = [ + list(range(sum(half_word_distribution[:i]) + offset, sum(half_word_distribution[:i + 1]) + offset)) + for i in range(len(half_word_distribution)) + ] + return left_half_ids, left_half_positions, right_half_ids, right_half_positions + def _even_round( + self, + left_half_ids, + left_positions, + sub_key, + right_half_ids, + right_positions + ): + temp_positions = [] + for i in range(6): + temp_positions.append(list(range(half_word_distribution[i]))) + + fls = self.fl_function( + left_half_ids, + left_positions, + sub_key + ) + fos = self.fo_function( + fls, + temp_positions, + sub_key + ) + + new_right_half_ids = [] + for i in range(6): + xor = self.add_XOR_component( + [fos[i], right_half_ids[i]], + [list(range(half_word_distribution[i])), right_positions[i]], half_word_distribution[i] + ) + new_right_half_ids.append(xor.id) + + return new_right_half_ids, temp_positions + + def _odd_round( + self, + left_half_ids, + left_positions, + sub_key, + right_half_ids, + right_positions + ): + temp_positions = [] + for i in range(6): + temp_positions.append(list(range(half_word_distribution[i]))) + + fos = self.fo_function( + right_half_ids, + right_positions, sub_key) + + fls = self.fl_function( + fos, + temp_positions, + sub_key + ) + + new_left_half_ids = [] + for i in range(6): + xor = self.add_XOR_component( + [fls[i], left_half_ids[i]], + [list(range(half_word_distribution[i])), left_positions[i]], + half_word_distribution[i] + ) + new_left_half_ids.append(xor.id) + + return new_left_half_ids, temp_positions def _get_number_of_rounds(self, number_of_rounds): if number_of_rounds is not None: @@ -144,120 +234,272 @@ def _get_number_of_rounds(self, number_of_rounds): raise ValueError("No available number of rounds for the given parameters.") return configuration_number_of_rounds - def fi_function(self, p, ki_id, ki_positions): - s9_1 = self.add_SBOX_component([p], [list(range(9))], 9, SBox9).id - cst1 = self.add_constant_component(2, 0b00).id - con1 = self.add_concatenate_component([cst1, p], [list(range(2)), list(range(9, self.WORD_SIZE))], 9).id - xor1 = self.add_XOR_component([s9_1, con1], [list(range(9)), list(range(9))], 9).id + def fi_function1(self, ids, ki_id, ki_positions): + s9_1 = self.add_SBOX_component( + [ids[0], ids[1]], [list(range(7)), list(range(2))], 9, SBox9 + ).id - s7_1 = self.add_SBOX_component([p], [list(range(9, self.WORD_SIZE))], 7, SBox7).id - xor2 = self.add_XOR_component([s7_1, xor1], [list(range(7)), list(range(2, 9))], 7).id - - xor3 = self.add_XOR_component([xor1, ki_id], [list(range(9)), ki_positions[7:16]], 9).id - xor4 = self.add_XOR_component([xor2, ki_id], [list(range(7)), ki_positions[:7]], 7).id - - s9_2 = self.add_SBOX_component([xor3], [list(range(9))], 9, SBox9).id - - con2 = self.add_concatenate_component([cst1, xor4], [list(range(2)), list(range(7))], 9).id - xor5 = self.add_XOR_component([s9_2, con2], [list(range(9)), list(range(9))], 9).id - - s7_2 = self.add_SBOX_component([xor4], [list(range(7))], 7, SBox7).id - xor6 = self.add_XOR_component([s7_2, xor5], [list(range(7)), list(range(2, 9))], 7).id - - self.add_concatenate_component([xor6, xor5], [list(range(7)), list(range(9))], self.WORD_SIZE) - fi = ComponentState([self.get_current_component_id()], [list(range(self.WORD_SIZE))]) - return fi + cst1 = self.add_constant_component(2, 0b00).id - def fo_function(self, p, sub_key): + xor1_1 = self.add_XOR_component( + [s9_1, cst1], [list(range(2)), list(range(2))], 2 + ).id + xor1_2 = self.add_XOR_component( + [s9_1, ids[2]], [list(range(2,9)), list(range(7))], 7 + ).id + + s7_1 = self.add_SBOX_component( + [ids[2]], [list(range(7))], 7, SBox7 + ).id + + xor2 = self.add_XOR_component( + [s7_1, xor1_2], [list(range(7)), list(range(7))], 7 + ).id + + xor3_1 = self.add_XOR_component( + [xor1_1, ki_id], [list(range(2)), ki_positions[7:9]], 2 + ).id + xor3_2 = self.add_XOR_component( + [xor1_2, ki_id], [list(range(7)), ki_positions[9:16]], 7 + ).id + + xor4 = self.add_XOR_component( + [xor2, ki_id], [list(range(7)), ki_positions[:7]], 7 + ).id + + s9_2 = self.add_SBOX_component( + [xor3_1, xor3_2], [list(range(2)), list(range(7))], 9, SBox9 + ).id + + xor5_1 = self.add_XOR_component( + [s9_2, cst1], [list(range(2)), list(range(2))], 2 + ) + xor5_2 = self.add_XOR_component( + [s9_2, xor4], [list(range(2, 9)), list(range(7))], 7 + ) + xor5_2_id = xor5_2.id + + s7_2 = self.add_SBOX_component( + [xor4], [list(range(7))], 7, SBox7 + ).id + xor6 = self.add_XOR_component( + [s7_2, xor5_2_id], [list(range(7)), list(range(7))], 7 + ) + + return [xor6, xor5_1, xor5_2] + + def fo_function(self, ids, positions, sub_key): + start = 32 + xor1s = [] + for i, length in enumerate(half_half_word_distribution): + end = start + length + xor1_temp = self.add_XOR_component( + [ids[i], sub_key], + [positions[i], list(range(start, end))], + length + ) + xor1s.append(xor1_temp.id) + start = end - xor1 = self.add_XOR_component([p.id[0], sub_key], [list(range(self.WORD_SIZE)), - [i + 2 * self.WORD_SIZE for i in range(self.WORD_SIZE)]], - self.WORD_SIZE).id ki_id, ki_positions = extract_inputs([sub_key], [list(range(8 * self.WORD_SIZE))], [i + 5 * self.WORD_SIZE for i in range(self.WORD_SIZE)]) - fi1 = self.fi_function(xor1, ki_id[0], ki_positions[0]) - - xor2 = self.add_XOR_component([fi1.id[0], p.id[0]], [list(range(self.WORD_SIZE)), - [i + self.WORD_SIZE for i in range(self.WORD_SIZE)]], - self.WORD_SIZE).id - xor3 = self.add_XOR_component([p.id[0], sub_key], [[(i + self.WORD_SIZE) for i in range(self.WORD_SIZE)], - [i + 3 * self.WORD_SIZE for i in range(self.WORD_SIZE)]], - self.WORD_SIZE).id - ki2_id, ki2_positions = extract_inputs([sub_key], [list(range(8 * self.WORD_SIZE))], - [i + 6 * self.WORD_SIZE for i in range(self.WORD_SIZE)]) - fi2 = self.fi_function(xor3, ki2_id[0], ki2_positions[0]) - xor4 = self.add_XOR_component([fi2.id[0], xor2], [list(range(self.WORD_SIZE)), list(range(self.WORD_SIZE))], - self.WORD_SIZE).id - - xor5 = self.add_XOR_component([xor2, sub_key], [list(range(self.WORD_SIZE)), - [i + 4 * self.WORD_SIZE for i in range(self.WORD_SIZE)]], - self.WORD_SIZE).id - ki3_id, ki3_positions = extract_inputs([sub_key], [list(range(8 * self.WORD_SIZE))], - [i + 7 * self.WORD_SIZE for i in range(self.WORD_SIZE)]) - fi3 = self.fi_function(xor5, ki3_id[0], ki3_positions[0]) - xor6 = self.add_XOR_component([fi3.id[0], xor4], [list(range(self.WORD_SIZE)), list(range(self.WORD_SIZE))], - self.WORD_SIZE).id - self.add_concatenate_component([xor4, xor6], [list(range(self.WORD_SIZE)), list(range(self.WORD_SIZE))], - 2 * self.WORD_SIZE) - fo = ComponentState([self.get_current_component_id()], [list(range(2 * self.WORD_SIZE))]) - return fo - - def fl_function(self, p, sub_key): - and1 = self.add_AND_component([p.id[0], sub_key], [list(range(self.WORD_SIZE)), list(range(self.WORD_SIZE))], - self.WORD_SIZE).id - rot1 = self.add_rotate_component([and1], [list(range(self.WORD_SIZE))], self.WORD_SIZE, -1).id - xor1 = self.add_XOR_component([rot1, p.id[0]], - [list(range(self.WORD_SIZE)), - [(i + self.WORD_SIZE) for i in range(self.WORD_SIZE)]], - self.WORD_SIZE).id - or1 = self.add_OR_component([xor1, sub_key], - [list(range(self.WORD_SIZE)), [(i + self.WORD_SIZE) for i in range(self.WORD_SIZE)]], - self.WORD_SIZE).id - rot2 = self.add_rotate_component([or1], [list(range(self.WORD_SIZE))], self.WORD_SIZE, -1).id - xor2 = self.add_XOR_component([rot2, p.id[0]], [list(range(self.WORD_SIZE)), list(range(self.WORD_SIZE))], - self.WORD_SIZE).id - - self.add_concatenate_component([xor2, xor1], [list(range(self.WORD_SIZE)), list(range(self.WORD_SIZE))], - 2 * self.WORD_SIZE) - fl = ComponentState([self.get_current_component_id()], [list(range(2 * self.WORD_SIZE))]) - return fl + fis1 = self.fi_function1([xor1s[0], xor1s[1], xor1s[2]], ki_id[0], ki_positions[0]) + + xor2s = [] + for i, length in enumerate(half_half_word_distribution): + xor2_temp = self.add_XOR_component( + [fis1[i].id, ids[i+3]], + [list(range(length)), positions[i+3]], + length + ) + xor2s.append(xor2_temp.id) + + subkey_size = [i + 3 * self.WORD_SIZE for i in range(self.WORD_SIZE)] + + start = 0 + xor3s = [] + for i, length in enumerate(half_half_word_distribution): + end = start + length + xor3_temp = self.add_XOR_component( + [ids[i+3], sub_key], + [positions[i+3], subkey_size[start:end]], + length + ) + xor3s.append(xor3_temp.id) + start = end + + + ki2_id, ki2_positions = extract_inputs( + [sub_key], [list(range(8 * self.WORD_SIZE))], + [i + 6 * self.WORD_SIZE for i in range(self.WORD_SIZE)] + ) + + fis2 = self.fi_function1([xor3s[0], xor3s[1], xor3s[2]], ki2_id[0], ki2_positions[0]) + + xor4s = [] + for i, length in enumerate(half_half_word_distribution): + xor4_temp = self.add_XOR_component( + [fis2[i].id, xor2s[i]], + [list(range(length)), list(range(length))], + length + ) + xor4s.append(xor4_temp.id) + + sub_key_positions = [i + 4 * self.WORD_SIZE for i in range(self.WORD_SIZE)] + + xor5s = [] + start = 0 + for i, length in enumerate(half_half_word_distribution): + end = start + length + xor5_temp = self.add_XOR_component( + [xor2s[i], sub_key], + [list(range(length)), sub_key_positions[start:end]], + length + ) + xor5s.append(xor5_temp.id) + start = end + + ki3_id, ki3_positions = extract_inputs( + [sub_key], [list(range(8 * self.WORD_SIZE))], + [i + 7 * self.WORD_SIZE for i in range(self.WORD_SIZE)] + ) + fis3 = self.fi_function1([xor5s[0], xor5s[1], xor5s[2]], ki3_id[0], ki3_positions[0]) + + xor6s = [] + for i, length in enumerate(half_half_word_distribution): + xor6_temp = self.add_XOR_component( + [fis3[i].id, xor4s[i]], + [list(range(length)), list(range(length))], + length + ) + xor6s.append(xor6_temp.id) + + return xor4s + xor6s + + def fl_function(self, ids, positions, sub_key): + word_size = list(range(self.WORD_SIZE)) + and1s = [] + start = 0 + for i, length in enumerate(half_half_word_distribution): + end = start + length + and1_temp = self.add_AND_component( + [ids[i], sub_key], + [positions[i], word_size[start:end]], + length + ) + and1s.append(and1_temp.id) + start = end + + rot1 = self.add_rotate_component( + [and1s[0], and1s[1], and1s[2]], + [list(range(7)), list(range(2)), list(range(7))], self.WORD_SIZE, -1 + ).id + + rot_size = list(range(self.WORD_SIZE)) + + xor1s = [] + start = 0 + for i, length in enumerate(half_half_word_distribution): + end = start + length + xor1_temp = self.add_XOR_component( + [rot1, ids[i+3]], + [rot_size[start:end], positions[i+3]], + length + ) + xor1s.append(xor1_temp.id) + start = end + + subkey_size = [(i + self.WORD_SIZE) for i in range(self.WORD_SIZE)] + + or1s = [] + start = 0 + for i, length in enumerate(half_half_word_distribution): + end = start + length + or1_temp = self.add_OR_component( + [xor1s[i], sub_key], + [list(range(length)), subkey_size[start:end]], + length + ) + or1s.append(or1_temp.id) + start = end + + + rot2 = self.add_rotate_component(or1s, [list(range(7)), list(range(2)), list(range(7))], + self.WORD_SIZE, -1).id + + rot_size = list(range(self.WORD_SIZE)) + xor2s = [] + start = 0 + for i, length in enumerate(half_half_word_distribution): + end = start + length + xor2_temp = self.add_XOR_component( + [rot2, ids[i]], + [rot_size[start:end], positions[i]], + length + ) + xor2s.append(xor2_temp.id) + start = end + + return xor2s + xor1s def derived_key(self, key): cst = self.add_constant_component(128, 0x123456789ABCDEFFEDCBA9876543210).id - key_der = self.add_XOR_component(key[0] + [cst], - [list(range(self.key_bit_size))] + [list(range(self.key_bit_size))], - self.key_bit_size).id - return key_der + key_der = self.add_XOR_component( + key[0] + [cst], + [list(range(self.key_bit_size))] + [list(range(self.key_bit_size))], + self.key_bit_size + ) + return key_der.id def round_key(self, key, key_der, r): - kl1 = self.add_rotate_component(key[0], [[i + (r - 1) * self.WORD_SIZE for i in range(self.WORD_SIZE)]], - self.WORD_SIZE, -1).id - kl2_id, kl2_positions = extract_inputs([key_der], [list(range(self.key_bit_size))], - [i + ((r + 1) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]) - - ko1 = self.add_rotate_component(key[0], [[i + (r % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]], - self.WORD_SIZE, -5).id - ko2 = self.add_rotate_component(key[0], [[i + ((r + 4) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]], - self.WORD_SIZE, -8).id - ko3 = self.add_rotate_component(key[0], [[i + ((r + 5) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]], - self.WORD_SIZE, -13).id - ki1_id, ki1_positions = extract_inputs([key_der], [list(range(self.key_bit_size))], - [i + ((r + 3) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]) - ki2_id, ki2_positions = extract_inputs([key_der], [list(range(self.key_bit_size))], - [i + ((r + 2) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]) - ki3_id, ki3_positions = extract_inputs([key_der], [list(range(self.key_bit_size))], - [i + ((r + 6) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]) - - sub_key = self.add_round_key_output_component([kl1, kl2_id[0], ko1, ko2, ko3, ki1_id[0], ki2_id[0], ki3_id[0]], - [list(range(self.WORD_SIZE)), kl2_positions[0], - list(range(self.WORD_SIZE)), list(range(self.WORD_SIZE)), - list(range(self.WORD_SIZE)), ki1_positions[0], - ki2_positions[0], ki3_positions[0]], - self.key_bit_size).id - return sub_key - - def round_initialization(self): - p1 = ComponentState([INPUT_PLAINTEXT], [list(range(2 * self.WORD_SIZE))]) - p2 = ComponentState([INPUT_PLAINTEXT], [[(i + 2 * self.WORD_SIZE) for i in range(2 * self.WORD_SIZE)]]) - return p1, p2 + kl1 = self.add_rotate_component( + key[0], [[i + (r - 1) * self.WORD_SIZE for i in range(self.WORD_SIZE)]], + self.WORD_SIZE, -1 + ).id + kl2_id, kl2_positions = extract_inputs( + [key_der], + [list(range(self.key_bit_size))], + [i + ((r + 1) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)] + ) + + ko1 = self.add_rotate_component( + key[0], + [[i + (r % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]], + self.WORD_SIZE, -5 + ).id + ko2 = self.add_rotate_component( + key[0], + [[i + ((r + 4) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]], + self.WORD_SIZE, -8 + ).id + ko3 = self.add_rotate_component( + key[0], + [[i + ((r + 5) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)]], + self.WORD_SIZE, -13 + ).id + ki1_id, ki1_positions = extract_inputs( + [key_der], + [list(range(self.key_bit_size))], + [i + ((r + 3) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)] + ) + ki2_id, ki2_positions = extract_inputs( + [key_der], + [list(range(self.key_bit_size))], + [i + ((r + 2) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)] + ) + ki3_id, ki3_positions = extract_inputs( + [key_der], + [list(range(self.key_bit_size))], + [i + ((r + 6) % 8) * self.WORD_SIZE for i in range(self.WORD_SIZE)] + ) + + sub_key = self.add_round_key_output_component( + [kl1, kl2_id[0], ko1, ko2, ko3, ki1_id[0], ki2_id[0], ki3_id[0]], + [list(range(self.WORD_SIZE)), kl2_positions[0], + list(range(self.WORD_SIZE)), list(range(self.WORD_SIZE)), + list(range(self.WORD_SIZE)), ki1_positions[0], + ki2_positions[0], ki3_positions[0]], + self.key_bit_size + ).id + return sub_key \ No newline at end of file diff --git a/claasp/ciphers/stream_ciphers/bluetooth_stream_cipher_e0.py b/claasp/ciphers/stream_ciphers/bluetooth_stream_cipher_e0.py index a55e0692..a338f1a5 100644 --- a/claasp/ciphers/stream_ciphers/bluetooth_stream_cipher_e0.py +++ b/claasp/ciphers/stream_ciphers/bluetooth_stream_cipher_e0.py @@ -158,7 +158,7 @@ def e0_keystream(self, lfsr_state, fsm_id, fsm_pos, clock_number, ks): ks_id = [lfsr_state, lfsr_state, lfsr_state, lfsr_state, fsm_id[2]] ks_pos = [[1], [32], [57], [96], fsm_pos[2]] z = self.add_XOR_component(ks_id, ks_pos, 1).id - if clock_number is 0: + if clock_number == 0: ks = self.add_round_output_component([z], [list(range(1))], 1).id else: ks = self.add_round_output_component([ks, z], [list(range(clock_number)), [0]], clock_number + 1).id diff --git a/claasp/ciphers/stream_ciphers/trivium_stream_cipher.py b/claasp/ciphers/stream_ciphers/trivium_stream_cipher.py index d1433233..6c858895 100644 --- a/claasp/ciphers/stream_ciphers/trivium_stream_cipher.py +++ b/claasp/ciphers/stream_ciphers/trivium_stream_cipher.py @@ -111,7 +111,7 @@ def trivium_key_stream(self, state, clock_number, key_stream): k_bits_id = [state, state, state, state, state, state] k_bits_pos = [[0], [27], [93], [108], [177], [222]] key_stream_bit = self.add_XOR_component(k_bits_id, k_bits_pos, 1).id - if clock_number is 0: + if clock_number == 0: key_stream = self.add_round_output_component([key_stream_bit], [list(range(1))], 1).id else: key_stream = self.add_round_output_component([key_stream, key_stream_bit], diff --git a/claasp/ciphers/stream_ciphers/zuc_stream_cipher.py b/claasp/ciphers/stream_ciphers/zuc_stream_cipher.py index cc2d57d3..16a7e324 100644 --- a/claasp/ciphers/stream_ciphers/zuc_stream_cipher.py +++ b/claasp/ciphers/stream_ciphers/zuc_stream_cipher.py @@ -229,7 +229,7 @@ def key_stream(self, w, clock_number, key_st): return key_st def lfsr_S_high_16bits(self, S, P): - if len(S) is 3: + if len(S) == 3: s_h_id = S[:2] s_h_ps = [P[0], P[1][:8]] else: @@ -238,7 +238,7 @@ def lfsr_S_high_16bits(self, S, P): return s_h_id, s_h_ps def lfsr_S_low_16bits(self, S, P): - if len(S) is 3: + if len(S) == 3: s_l_id = S[1:3] s_l_ps = [P[1][7:15], P[2]] else: diff --git a/claasp/components/cipher_output_component.py b/claasp/components/cipher_output_component.py index b89d2420..8c1ffc9f 100644 --- a/claasp/components/cipher_output_component.py +++ b/claasp/components/cipher_output_component.py @@ -1,4 +1,3 @@ - # **************************************************************************** # Copyright 2023 Technology Innovation Institute # @@ -230,7 +229,7 @@ def cp_xor_linear_mask_propagation_constraints(self, model=None): def get_bit_based_vectorized_python_code(self, params, convert_output_to_bytes): code = [] cipher_output_params = [f'bit_vector_select_word({self.input_id_links[i]}, {self.input_bit_positions[i]})' - for i in range(len(self.input_id_links))] + for i in range(len(self.input_id_links))] code.append(f' {self.id} = bit_vector_CONCAT([{",".join(cipher_output_params)} ])') code.append(f' if "{self.description[0]}" not in intermediateOutputs.keys():') code.append(f' intermediateOutputs["{self.description[0]}"] = []') @@ -248,7 +247,11 @@ def get_byte_based_vectorized_python_code(self, params): return [f' {self.id} = {params}[0]', f' if "{self.description[0]}" not in intermediateOutputs.keys():', f' intermediateOutputs["{self.description[0]}"] = []', - f' intermediateOutputs["{self.description[0]}"].append({self.id}.transpose())'] + f' if integers_inputs_and_outputs:', +# f' intermediateOutputs["{self.description[0]}"].append(evaluate_vectorized_outputs_to_integers([{self.id}.transpose()], {self.input_bit_size}))', + f' intermediateOutputs["{self.description[0]}"] = evaluate_vectorized_outputs_to_integers([{self.id}.transpose()], {self.input_bit_size})', + f' else:', + f' intermediateOutputs["{self.description[0]}"].append({self.id}.transpose())'] def milp_constraints(self, model): """ diff --git a/claasp/components/constant_component.py b/claasp/components/constant_component.py index 11306a57..b293ba1c 100644 --- a/claasp/components/constant_component.py +++ b/claasp/components/constant_component.py @@ -22,7 +22,7 @@ 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.code_generator import constant_to_bitstring - +from claasp.cipher_modules.generic_functions_vectorized_byte import integer_array_to_evaluate_vectorized_input def constant_to_repr(val, output_size): _val = int(val, 0) @@ -35,6 +35,8 @@ def constant_to_repr(val, output_size): return ret + + class Constant(Component): def __init__(self, current_round_number, current_round_number_of_components, diff --git a/claasp/components/intermediate_output_component.py b/claasp/components/intermediate_output_component.py index 1c8ddda9..6f12a517 100644 --- a/claasp/components/intermediate_output_component.py +++ b/claasp/components/intermediate_output_component.py @@ -118,7 +118,11 @@ def get_byte_based_vectorized_python_code(self, params): return [f' {self.id} = {params}[0]', f' if "{self.description[0]}" not in intermediateOutputs.keys():', f' intermediateOutputs["{self.description[0]}"] = []', - f' intermediateOutputs["{self.description[0]}"].append({self.id}.transpose())'] + f' if integers_inputs_and_outputs:', + #f' intermediateOutputs["{self.description[0]}"].append(evaluate_vectorized_outputs_to_integers([{self.id}.transpose()], {self.input_bit_size}))', + f' intermediateOutputs["{self.description[0]}"] = evaluate_vectorized_outputs_to_integers([{self.id}.transpose()], {self.input_bit_size})', + f' else:', + f' intermediateOutputs["{self.description[0]}"].append({self.id}.transpose())'] def milp_xor_linear_mask_propagation_constraints(self, model): """ diff --git a/claasp/components/mix_column_component.py b/claasp/components/mix_column_component.py index f171f14d..28613fde 100644 --- a/claasp/components/mix_column_component.py +++ b/claasp/components/mix_column_component.py @@ -538,13 +538,14 @@ def get_byte_based_vectorized_python_code(self, params): F2 = FiniteField(2)['x'] _modulus = int_to_poly(polynomial, input_size + 1, F2.gen()) F = FiniteField(pow(2, input_size), name='a', modulus=_modulus) + for row in matrix: for element in row: if element not in mul_tables: mul_tables[element] = [(F.fetch_int(i) * F.fetch_int(element)).integer_representation() for i in range(2 ** input_size)] - return [f' {self.id}=byte_vector_mix_column({params} , {matrix}, {mul_tables})'] - return [f' {self.id}=byte_vector_mix_column_poly0({params} , {matrix})'] + return [f' {self.id}=byte_vector_mix_column({params} , {matrix}, {mul_tables}, {input_size})'] + return [f' {self.id}=byte_vector_mix_column_poly0({params} , {matrix}, {input_size})'] def milp_constraints(self, model): """ diff --git a/claasp/components/modular_component.py b/claasp/components/modular_component.py index ae55aab4..aaf64b06 100644 --- a/claasp/components/modular_component.py +++ b/claasp/components/modular_component.py @@ -31,6 +31,14 @@ def sat_n_window_heuristc_bit_level(window_size, inputs): f'window_size_{window_size}_cnf')(inputs) +def generate_constraints_for_window_size_with_full_windows(first_addend, second_addend, result, aux_var): + import claasp.cipher_modules.models.sat.utils.n_window_heuristic_helper + window_size = len(first_addend) + return getattr( + claasp.cipher_modules.models.sat.utils.n_window_heuristic_helper, + f'window_size_with_full_{window_size}_window_cnf')(first_addend, second_addend, result, aux_var) + + def milp_n_window_heuristic(input_vars, output_vars, component_id, window_size, mip, x): def create_window_size_array(j, input_1_vars, input_2_vars, output_vars): temp_array = [] @@ -379,7 +387,7 @@ def milp_xor_differential_propagation_constraints(self, model): x_15 <= x_48, ... -2 <= -1*x_0 - x_16 - x_17 + x_32 + x_63, - x_64 == 10*x_49 + 10*x_50 + 10*x_51 + 10*x_52 + 10*x_53 + 10*x_54 + 10*x_55 + 10*x_56 + 10*x_57 + 10*x_58 + 10*x_59 + 10*x_60 + 10*x_61 + 10*x_62 + 10*x_63] + x_64 == 100*x_49 + 100*x_50 + 100*x_51 + 100*x_52 + 100*x_53 + 100*x_54 + 100*x_55 + 100*x_56 + 100*x_57 + 100*x_58 + 100*x_59 + 100*x_60 + 100*x_61 + 100*x_62 + 100*x_63] """ x = model.binary_variable p = model.integer_variable @@ -431,7 +439,7 @@ def milp_xor_differential_propagation_constraints(self, model): constraints.append( -x[input_vars[output_bit_size + i]] - x[input_vars[output_bit_size + i - 1]] - x[input_vars[i - 1]] + x[ output_vars[i - 1]] + x[component_id + "_eq_" + str(i)] >= -2) - constraints.append(p[component_id + "_probability"] == 10 * sum( + constraints.append(p[component_id + "_probability"] == (10 ** model.weight_precision) * sum( x[component_id + "_eq_" + str(i)] for i in range(output_bit_size - 1, 0, -1))) # the most significant bit is not taken in consideration if model.n_window_heuristic is not None: @@ -755,7 +763,7 @@ def milp_xor_linear_mask_propagation_constraints(self, model): ... -4 <= x_15 + x_31 + x_47 + x_63 + x_64, x_65 == x_48 + x_49 + x_50 + x_51 + x_52 + x_53 + x_54 + x_55 + x_56 + x_57 + x_58 + x_59 + x_60 + x_61 + x_62 + x_63, - x_66 == 10*x_65] + x_66 == 100*x_65] """ binary_variable = model.binary_variable integer_variable = model.integer_variable @@ -772,7 +780,7 @@ def milp_xor_linear_mask_propagation_constraints(self, model): integer_variable, input_vars, output_vars, 0) - constraints.append(correlation[component_id + "_probability"] == 10 * + constraints.append(correlation[component_id + "_probability"] == (10 ** model.weight_precision) * correlation[component_id + "_modadd_probability" + str(0)]) elif number_of_inputs > 2: @@ -801,7 +809,7 @@ def milp_xor_linear_mask_propagation_constraints(self, model): variables.extend(temp_variables) constraints.extend(temp_constraints) constraints.append(correlation[component_id + "_probability"] == - 10 * sum(correlation[component_id + "_modadd_probability" + str(i)] + (10 ** model.weight_precision) * sum(correlation[component_id + "_modadd_probability" + str(i)] for i in range(number_of_inputs - 1))) result = variables, constraints return result @@ -849,6 +857,22 @@ def extend_constraints_for_window_size( n_window_vars_[3 * j + 1] = input_bit_ids_[output_bit_len_ + i + j] n_window_vars_[3 * j + 2] = output_bit_ids_[i + j] constraints_.extend(sat_n_window_heuristc_bit_level(window_size_, n_window_vars_)) + def extend_constraints_for_window_size_with_full_windows( + model_, output_bit_len_, window_size_, input_bit_ids_, output_bit_ids_, constraints_ + ): + + for i in range(output_bit_len_ - window_size_): + aux_var = f'full_window_track_{self.id}_{i}' + model_._window_size_full_window_vars.append(aux_var) + first_addend = input_bit_ids_[i:i + window_size_] + second_addend = input_bit_ids_[output_bit_len_ + i:output_bit_len_ + i + window_size_] + result = output_bit_ids_[i:i + window_size_] + new_constraints = generate_constraints_for_window_size_with_full_windows( + first_addend, second_addend, result, aux_var + ) + constraints_.extend(new_constraints) + + _, input_bit_ids = self._generate_input_ids() output_bit_len, output_bit_ids = self._generate_output_ids() @@ -880,17 +904,24 @@ def extend_constraints_for_window_size( hw_bit_ids[i: i + (model.window_size_weight_pr_vars + 1)])) component_round_number = model._cipher.get_round_from_component_id(self.id) - if model.window_size_by_round is not None: - window_size = model.window_size_by_round[component_round_number] + if model.window_size_by_round_values is not None: + window_size = model.window_size_by_round_values[component_round_number] extend_constraints_for_window_size(output_bit_len, window_size, input_bit_ids, output_bit_ids, constraints) - if model.window_size_by_component_id is not None: - if self.id not in model.window_size_by_component_id: + if model.window_size_by_component_id_values is not None: + if self.id not in model.window_size_by_component_id_values: raise ValueError(f"component with id {self.id} is not in the list window_size_by_component_id") - window_size = model.window_size_by_component_id[self.id] + window_size = model.window_size_by_component_id_values[self.id] extend_constraints_for_window_size(output_bit_len, window_size, input_bit_ids, output_bit_ids, constraints) - result = output_bit_ids + dummy_bit_ids + hw_bit_ids, constraints - return result + + if model.window_size_number_of_full_window is not None: + extend_constraints_for_window_size_with_full_windows( + model, output_bit_len, window_size, input_bit_ids, output_bit_ids, constraints + ) + + variables = output_bit_ids + dummy_bit_ids + hw_bit_ids + + return variables, constraints def sat_bitwise_deterministic_truncated_xor_differential_constraints(self): """ diff --git a/claasp/components/multi_input_non_linear_logical_operator_component.py b/claasp/components/multi_input_non_linear_logical_operator_component.py index 41edacb1..c6edf30d 100644 --- a/claasp/components/multi_input_non_linear_logical_operator_component.py +++ b/claasp/components/multi_input_non_linear_logical_operator_component.py @@ -258,7 +258,7 @@ def milp_xor_differential_propagation_constraints(self, model): [0 <= -1*x_32 + x_48, 0 <= -1*x_33 + x_49, ... - x_64 == 10*x_48 + 10*x_49 + 10*x_50 + 10*x_51 + 10*x_52 + 10*x_53 + 10*x_54 + 10*x_55 + 10*x_56 + 10*x_57 + 10*x_58 + 10*x_59 + 10*x_60 + 10*x_61 + 10*x_62 + 10*x_63] + x_64 == 100*x_48 + 100*x_49 + 100*x_50 + 100*x_51 + 100*x_52 + 100*x_53 + 100*x_54 + 100*x_55 + 100*x_56 + 100*x_57 + 100*x_58 + 100*x_59 + 100*x_60 + 100*x_61 + 100*x_62 + 100*x_63] """ x = model.binary_variable p = model.integer_variable @@ -277,7 +277,7 @@ def milp_xor_differential_propagation_constraints(self, model): tmp += x[component_id + "_and_" + str(index)] * ineq[self.description[1] + 2] tmp += ineq[0] constraints.append(tmp >= 0) - constraints.append(p[component_id + "_probability"] == 10 * sum(x[component_id + "_and_" + str(i)] + constraints.append(p[component_id + "_probability"] == (10 ** model.weight_precision) * sum(x[component_id + "_and_" + str(i)] for i in range(len(output_vars)))) result = variables, constraints @@ -318,7 +318,7 @@ def milp_xor_linear_mask_propagation_constraints(self, model): ... 0 <= -1*x_15 + x_47, x_48 == x_32 + x_33 + x_34 + x_35 + x_36 + x_37 + x_38 + x_39 + x_40 + x_41 + x_42 + x_43 + x_44 + x_45 + x_46 + x_47, - x_49 == 10*x_48] + x_49 == 100*x_48] """ binary_variable = model.binary_variable integer_variable = model.integer_variable @@ -334,7 +334,7 @@ def milp_xor_linear_mask_propagation_constraints(self, model): if number_of_inputs == 2: variables, constraints = self.milp_twoterms_xor_linear_probability_constraints( binary_variable, integer_variable, input_vars, output_vars, 0) - constraints.append(p[component_id + "_probability"] == 10 * p[component_id + "_and_probability" + str(0)]) + constraints.append(p[component_id + "_probability"] == (10 ** model.weight_precision) * p[component_id + "_and_probability" + str(0)]) elif number_of_inputs > 2: temp_output_vars = [[f"{var}_temp_and_{i}" for var in output_vars] @@ -359,7 +359,7 @@ def milp_xor_linear_mask_propagation_constraints(self, model): variables.extend(temp_variables) constraints.extend(temp_constraints) constraints.append( - p[component_id + "_probability"] == 10 * sum(p[component_id + "_and_probability" + str(i)] + p[component_id + "_probability"] == (10 ** model.weight_precision) * sum(p[component_id + "_and_probability" + str(i)] for i in range(number_of_inputs - 1))) result = variables, constraints diff --git a/claasp/components/or_component.py b/claasp/components/or_component.py index 7a962103..372130df 100644 --- a/claasp/components/or_component.py +++ b/claasp/components/or_component.py @@ -189,7 +189,7 @@ def cp_xor_linear_mask_propagation_constraints(self, model): cp_constraints = [] num_add = self.description[1] input_len = input_size // num_add - cp_declarations.append(f'array[0..{output_size - 1}] of var int: p_{output_id_link};') + cp_declarations.append(f'array[0..{output_size - 1}] of var 0..{100 * output_size}: p_{output_id_link};') cp_declarations.append(f'array[0..{input_size - 1}] of var 0..1:{output_id_link}_i;') cp_declarations.append(f'array[0..{output_size - 1}] of var 0..1:{output_id_link}_o;') model.component_and_probability[output_id_link] = 0 @@ -197,8 +197,8 @@ def cp_xor_linear_mask_propagation_constraints(self, model): for i in range(output_size): new_constraint = f'constraint table(' for j in range(num_add): - new_constraint = new_constraint + f'{output_id_link}_i[{i + input_len * j}]++' - new_constraint = new_constraint + f'{output_id_link}_o[{i}]++p_{output_id_link}[{p_count}],and{num_add}inputs_LAT);' + new_constraint = new_constraint + f'[{output_id_link}_i[{i + input_len * j}]]++' + new_constraint = new_constraint + f'[{output_id_link}_o[{i}]]++[p_{output_id_link}[{p_count}]],and{num_add}inputs_LAT);' cp_constraints.append(new_constraint) p_count = p_count + 1 cp_constraints.append(f'constraint p[{model.c}] = sum(p_{output_id_link});') diff --git a/claasp/components/rotate_component.py b/claasp/components/rotate_component.py index a72131b7..f34d722c 100644 --- a/claasp/components/rotate_component.py +++ b/claasp/components/rotate_component.py @@ -347,7 +347,7 @@ def get_bit_based_vectorized_python_code(self, params, convert_output_to_bytes): return [f' {self.id} = bit_vector_ROTATE([{",".join(params)} ], {self.description[1]})'] def get_byte_based_vectorized_python_code(self, params): - return [f' {self.id} = byte_vector_ROTATE({params}, {self.description[1]})'] + return [f' {self.id} = byte_vector_ROTATE({params}, {self.description[1]}, {self.input_bit_size})'] def get_word_based_c_code(self, verbosity, word_size, wordstring_variables): rotate_code = [] diff --git a/claasp/components/sbox_component.py b/claasp/components/sbox_component.py index 73c648be..54e3d46d 100644 --- a/claasp/components/sbox_component.py +++ b/claasp/components/sbox_component.py @@ -30,6 +30,7 @@ update_dictionary_that_contains_inequalities_for_sboxes_with_undisturbed_bits, \ get_dictionary_that_contains_inequalities_for_sboxes_with_undisturbed_bits, \ delete_dictionary_that_contains_inequalities_for_sboxes_with_undisturbed_bits +from claasp.cipher_modules.models.milp.utils.milp_name_mappings import MILP_DEFAULT_WEIGHT_PRECISION from claasp.cipher_modules.models.milp.utils.utils import espresso_pos_to_constraints from claasp.input import Input from claasp.component import Component, free_input @@ -43,7 +44,6 @@ update_dictionary_that_contains_inequalities_for_small_sboxes, get_dictionary_that_contains_inequalities_for_small_sboxes) -SIZE_SHOULD_BE_EQUAL = 'input_bit_size and output_bit_size should be equal.' def check_table_feasibility(table, table_type, solver): @@ -100,13 +100,49 @@ def cp_update_lat_valid_probabilities(component, valid_probabilities, sbox_mant) for i in range(sbox_lat.nrows()): set_of_occurrences = set(sbox_lat.rows()[i]) set_of_occurrences -= {0} - valid_probabilities.update({round(100 * math.log2(2 ** input_size / abs(occurrence))) - for occurrence in set_of_occurrences}) + valid_probabilities.update({round(100 * math.log2(abs(pow(2, input_size - 1) / occurence))) for occurence in set_of_occurrences}) sbox_mant.append((description, output_id_link)) +def milp_set_constraints_from_dictionnary_for_large_sbox(component_id, input_vars, + output_vars, sbox_input_size, sbox_output_size, x, p, + probability_dictionary, analysis, weight_precision): + constraints = [] + # condition to know if sbox is active or not + constraints.append( + sbox_input_size * x[f"{component_id}_active"] >= sum(x[input_vars[i]] for i in range(sbox_input_size))) + constraints.append( + sbox_input_size * (1 - x[f"{component_id}_active"]) >= + -sum(x[input_vars[i]] for i in range(sbox_input_size)) + 1) + constraints += [x[f"{component_id}_active"] >= x[output_vars[i]] for i in range(sbox_output_size)] + # mip.add_constraint(sum(x[output_vars[i]] for i in range(sbox.input_size())) >= x[id + "_active"]) + + if analysis == "differential": + exponent = sbox_input_size + else: + exponent = sbox_input_size - 1 + + M = (10 ** weight_precision) * sbox_input_size + constraint_choice_proba = 0 + constraint_compute_proba = 0 + for proba in probability_dictionary.keys(): + for ineq in probability_dictionary[proba]: + constraint = milp_large_xor_probability_constraint_for_inequality(M, component_id, ineq, input_vars, + output_vars, proba, sbox_input_size, + sbox_output_size, x) + constraints.append(constraint >= 0) + + constraint_choice_proba += x[f"{component_id}_sboxproba_{proba}"] + constraint_compute_proba += (x[f"{component_id}_sboxproba_{proba}"] * + (10 ** weight_precision) * round(-log(abs(proba) / (2 ** exponent), 2), + weight_precision)) + constraints.append(constraint_choice_proba == x[f"{component_id}_active"]) + constraints.append(p[f"{component_id}_probability"] == constraint_compute_proba) + + return constraints + def milp_large_xor_probability_constraint_for_inequality(M, component_id, ineq, input_vars, - output_vars, proba, sbox_input_size, x): + output_vars, proba, sbox_input_size, sbox_output_size, x): constraint = 0 for i in range(sbox_input_size - 1, -1, -1): char = ineq[i] @@ -114,7 +150,7 @@ def milp_large_xor_probability_constraint_for_inequality(M, component_id, ineq, constraint += 1 - x[input_vars[i]] elif char == "0": constraint += x[input_vars[i]] - for i in range(2 * sbox_input_size - 1, sbox_input_size - 1, -1): + for i in range(sbox_input_size + sbox_output_size - 1, sbox_input_size - 1, -1): char = ineq[i] if char == "1": constraint += 1 - x[output_vars[i % sbox_input_size]] @@ -659,7 +695,6 @@ def cp_xor_linear_mask_propagation_constraints(self, model): cp_constraints.append(new_constraint) model.component_and_probability[output_id_link] = model.c model.c = model.c + 1 - return cp_declarations, cp_constraints def generate_sbox_sign_lat(self): @@ -698,17 +733,17 @@ def get_bit_based_vectorized_python_code(self, params, convert_output_to_bytes): sbox_params = [f'bit_vector_select_word({self.input_id_links[i]}, {self.input_bit_positions[i]})' for i in range(len(self.input_id_links))] return [f' {self.id} = bit_vector_SBOX(bit_vector_CONCAT([{",".join(sbox_params)} ]), ' - f'np.array({self.description}, dtype=np.uint8))'] + f'np.array({self.description}, dtype=np.uint8), output_bit_size = {self.output_bit_size})'] def get_byte_based_vectorized_python_code(self, params): - return [f' {self.id} = byte_vector_SBOX({params}, np.array({self.description}, dtype=np.uint8))'] + return [f' {self.id} = byte_vector_SBOX({params}, {self.description}, {self.input_bit_size})'] def get_word_based_c_code(self, verbosity, word_size, wordstring_variables): # TODO: consider the option for sbox return ['\t//// TODO'] def milp_large_xor_differential_probability_constraints(self, binary_variable, integer_variable, - non_linear_component_id): + non_linear_component_id, weight_precision=MILP_DEFAULT_WEIGHT_PRECISION): """ Return lists of variables and constrains modeling SBOX component, with input bit size less or equal to 6. @@ -722,6 +757,7 @@ def milp_large_xor_differential_probability_constraints(self, binary_variable, i - ``binary_variable`` -- **boolean MIPVariable object** - ``integer_variable`` -- **boolean MIPVariable object** - ``non_linear_component_id`` -- **string** + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. EXAMPLES:: @@ -747,50 +783,25 @@ def milp_large_xor_differential_probability_constraints(self, binary_variable, i 1 - x_0 - x_1 - x_2 - x_3 - x_4 - x_5 - x_6 - x_7 <= 8 - 8*x_16, x_8 <= x_16] """ - if self.output_bit_size != self.input_bit_size: - raise ValueError(SIZE_SHOULD_BE_EQUAL) x = binary_variable p = integer_variable input_vars, output_vars = self._get_input_output_variables() variables = [(f"x[{var}]", x[var]) for var in input_vars + output_vars] - constraints = [] component_id = self.id non_linear_component_id.append(component_id) sbox = SBox(self.description) - sbox_input_size = sbox.input_size() + sbox_input_size, sbox_output_size = sbox.input_size(), sbox.output_size() update_dictionary_that_contains_inequalities_for_large_sboxes(sbox, analysis="differential") dict_product_of_sum = get_dictionary_that_contains_inequalities_for_large_sboxes(analysis="differential") - # condition to know if sbox is active or not - constraints.append( - sbox_input_size * x[f"{component_id}_active"] >= sum(x[input_vars[i]] for i in range(sbox_input_size))) - constraints.append( - sbox_input_size * (1 - x[f"{component_id}_active"]) >= -sum( - x[input_vars[i]] for i in range(sbox_input_size)) + 1) - constraints += [x[f"{component_id}_active"] >= x[output_vars[i]] for i in range(sbox_input_size)] - # mip.add_constraint(sum(x[output_vars[i]] for i in range(sbox.input_size())) >= x[id + "_active"]) - - M = 10 * sbox_input_size - constraint_choice_proba = 0 - constraint_compute_proba = 0 - for proba in dict_product_of_sum[str(sbox)].keys(): - for ineq in dict_product_of_sum[str(sbox)][proba]: - constraint = milp_large_xor_probability_constraint_for_inequality(M, component_id, ineq, - input_vars, output_vars, - proba, sbox_input_size, x) - constraints.append(constraint >= 0) - - constraint_choice_proba += x[f"{component_id}_sboxproba_{proba}"] - constraint_compute_proba += \ - x[f"{component_id}_sboxproba_{proba}"] * 10 * round(-log(proba / 2 ** sbox_input_size, 2), 1) - - constraints.append(constraint_choice_proba == x[f"{component_id}_active"]) - constraints.append(p[f"{component_id}_probability"] == constraint_compute_proba) + constraints = milp_set_constraints_from_dictionnary_for_large_sbox(component_id, input_vars, + output_vars, sbox_input_size, sbox_output_size, x, p, + dict_product_of_sum[str(sbox)], analysis="differential", weight_precision=weight_precision) return variables, constraints - def milp_large_xor_linear_probability_constraints(self, binary_variable, integer_variable, non_linear_component_id): + def milp_large_xor_linear_probability_constraints(self, binary_variable, integer_variable, non_linear_component_id, weight_precision=MILP_DEFAULT_WEIGHT_PRECISION): """ Return lists of variables and constrains modeling SBOX component, with input bit size less or equal to 6. @@ -804,6 +815,7 @@ def milp_large_xor_linear_probability_constraints(self, binary_variable, integer - ``binary_variable`` -- **boolean MIPVariable object** - ``integer_variable`` -- **integer MIPVariable object** - ``non_linear_component_id`` -- **string** + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. EXAMPLES:: @@ -826,52 +838,30 @@ def milp_large_xor_linear_probability_constraints(self, binary_variable, integer 1 - x_0 - x_1 - x_2 - x_3 - x_4 - x_5 - x_6 - x_7 <= 8 - 8*x_16, ... x_17 + x_18 + x_19 + x_20 + x_21 + x_22 + x_23 + x_24 + x_25 + x_26 + x_27 + x_28 + x_29 + x_30 + x_31 + x_32 == x_16, - x_33 == 60*x_17 + 50*x_18 + 44*x_19 + 40*x_20 + 37*x_21 + 34*x_22 + 32*x_23 + 30*x_24 + 30*x_25 + 32*x_26 + 34*x_27 + 37*x_28 + 40*x_29 + 44*x_30 + 50*x_31 + 60*x_32] + x_33 == 600*x_17 + 500*x_18 + 442*x_19 + 400*x_20 + 368*x_21 + 342*x_22 + 319*x_23 + 300*x_24 + 300*x_25 + 319*x_26 + 342*x_27 + 368*x_28 + 400*x_29 + 442*x_30 + 500*x_31 + 600*x_32] """ - if self.output_bit_size != self.input_bit_size: - raise ValueError(SIZE_SHOULD_BE_EQUAL) x = binary_variable p = integer_variable input_vars, output_vars = self._get_independent_input_output_variables() variables = [(f"x[{var}]", x[var]) for var in input_vars + output_vars] - constraints = [] component_id = self.id non_linear_component_id.append(component_id) sbox = SBox(self.description) - sbox_input_size = sbox.input_size() + sbox_input_size, sbox_output_size = sbox.input_size(), sbox.output_size() update_dictionary_that_contains_inequalities_for_large_sboxes(sbox, analysis="linear") dict_product_of_sum = get_dictionary_that_contains_inequalities_for_large_sboxes(analysis="linear") - # condition to know if sbox is active or not - constraints.append( - sbox_input_size * x[f"{component_id}_active"] >= sum(x[input_vars[i]] for i in range(sbox_input_size))) - constraints.append( - sbox_input_size * (1 - x[f"{component_id}_active"]) >= - -sum(x[input_vars[i]] for i in range(sbox_input_size)) + 1) - constraints += [x[f"{component_id}_active"] >= x[output_vars[i]] for i in range(sbox_input_size)] - - M = 10 * sbox_input_size - constraint_choice_proba = 0 - constraint_compute_proba = 0 - for proba in dict_product_of_sum[str(sbox)].keys(): - for ineq in dict_product_of_sum[str(sbox)][proba]: - constraint = milp_large_xor_probability_constraint_for_inequality(M, component_id, ineq, - input_vars, - output_vars, proba, - sbox_input_size, x) - constraints.append(constraint >= 0) - - constraint_choice_proba += x[f"{component_id}_sboxproba_{proba}"] - constraint_compute_proba += (x[f"{component_id}_sboxproba_{proba}"] * - 10 * round(-log(abs(proba) / (2 ** (sbox_input_size - 1)), 2), 1)) - constraints.append(constraint_choice_proba == x[f"{component_id}_active"]) - constraints.append(p[f"{component_id}_probability"] == constraint_compute_proba) + constraints = milp_set_constraints_from_dictionnary_for_large_sbox(component_id, input_vars, + output_vars, sbox_input_size, + sbox_output_size, x, p, + dict_product_of_sum[str(sbox)], + analysis="linear", weight_precision=weight_precision) return variables, constraints def milp_small_xor_differential_probability_constraints(self, binary_variable, integer_variable, - non_linear_component_id): + non_linear_component_id, weight_precision=MILP_DEFAULT_WEIGHT_PRECISION): """ Return a list of variables and a list of constrains modeling a component of type SBOX. @@ -885,6 +875,7 @@ def milp_small_xor_differential_probability_constraints(self, binary_variable, i - ``binary_variable`` -- **boolean MIPVariable object** - ``integer_variable`` -- **integer MIPVariable object** - ``non_linear_component_id`` -- **string** + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. EXAMPLES:: @@ -909,8 +900,6 @@ def milp_small_xor_differential_probability_constraints(self, binary_variable, i x_9 + x_10 == x_8, x_11 == 30*x_9 + 20*x_10] """ - if self.output_bit_size != self.input_bit_size: - raise ValueError(SIZE_SHOULD_BE_EQUAL) x = binary_variable p = integer_variable @@ -922,17 +911,17 @@ def milp_small_xor_differential_probability_constraints(self, binary_variable, i update_dictionary_that_contains_inequalities_for_small_sboxes(sbox, analysis="differential") dictio = get_dictionary_that_contains_inequalities_for_small_sboxes(analysis="differential") dict_inequalities = dictio[f"{sbox}"] - input_size = self.input_bit_size + input_size, output_size = self.input_bit_size, self.output_bit_size # condition to know if sbox is active or not constraints.append(x[f"{self.id}_active"] <= sum(x[input_vars[i]] for i in range(input_size))) for i in range(input_size): constraints.append(x[f"{self.id}_active"] >= x[input_vars[i]]) - for i in range(input_size): + for i in range(output_size): constraints.append(x[f"{self.id}_active"] >= x[output_vars[i]]) # mip.add_constraint(sum(x[output_vars[i]] for i in range(sbox.input_size())) >= x[id + "_active"]) - M = 10 * input_size + M = (10 ** weight_precision) * max(input_size, output_size) dict_constraints = {} for proba in dict_inequalities: dict_constraints[proba] = [] @@ -947,13 +936,14 @@ def milp_small_xor_differential_probability_constraints(self, binary_variable, i constraints.append( sum(x[f"{self.id}_proba_{proba}"] for proba in dict_constraints) == x[f"{self.id}_active"]) - constraints.append(p[f"{self.id}_probability"] == 10 * sum( + constraints.append(p[f"{self.id}_probability"] == (10 ** weight_precision) * sum( x[f"{self.id}_proba_{proba}"] * (-log(proba / 2 ** sbox.input_size(), 2)) for proba in dict_constraints)) return variables, constraints - def milp_small_xor_linear_probability_constraints(self, binary_variable, integer_variable, non_linear_component_id): + def milp_small_xor_linear_probability_constraints(self, binary_variable, integer_variable, non_linear_component_id, + weight_precision=MILP_DEFAULT_WEIGHT_PRECISION): """ Return a list of variables and a list of constrains modeling a component of type Sbox. @@ -968,6 +958,7 @@ def milp_small_xor_linear_probability_constraints(self, binary_variable, integer - ``binary_variable`` -- **MIPVariable object** - ``integer_variable`` -- **MIPVariable object** - ``non_linear_component_id`` -- **list** + - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. EXAMPLES:: @@ -990,10 +981,8 @@ def milp_small_xor_linear_probability_constraints(self, binary_variable, integer x_0 <= x_8, ... x_9 + x_10 + x_11 + x_12 == x_8, - x_13 == 20*x_9 + 10*x_10 + 10*x_11 + 20*x_12] + x_13 == 200*x_9 + 100*x_10 + 100*x_11 + 200*x_12] """ - if self.output_bit_size != self.input_bit_size: - raise ValueError(SIZE_SHOULD_BE_EQUAL) x = binary_variable p = integer_variable @@ -1019,7 +1008,7 @@ def milp_small_xor_linear_probability_constraints(self, binary_variable, integer # Big-M Reformulation method as used in 4.1 of # https://tosc.iacr.org/index.php/ToSC/article/view/805/759 - M = 10 * input_size + M = (10 ** weight_precision) * max(input_size, output_size) dict_constraints = {} for proba in dict_inequalities: dict_constraints[proba] = [] @@ -1036,7 +1025,7 @@ def milp_small_xor_linear_probability_constraints(self, binary_variable, integer sum(x[f"{component_id}_proba_{proba}"] for proba in dict_constraints) == x[f"{component_id}_active"]) # correlation[i,j] = 2p[i,j] - 1, where p[i,j] = LAT[i,j] / 2^n + 1/2 - constraints.append(p[f"{component_id}_probability"] == 10 * sum(x[f"{component_id}_proba_{proba}"] * + constraints.append(p[f"{component_id}_probability"] == (10 ** weight_precision) * sum(x[f"{component_id}_proba_{proba}"] * (log((2 ** (sbox.input_size() - 1)) / abs( proba), 2)) for proba in dict_constraints)) @@ -1076,9 +1065,11 @@ def milp_xor_differential_propagation_constraints(self, model): binary_variable = model.binary_variable integer_variable = model.integer_variable non_linear_component_id = model.non_linear_component_id + weight_precision = model.weight_precision variables, constraints = self.milp_large_xor_differential_probability_constraints(binary_variable, - integer_variable, - non_linear_component_id) + integer_variable, + non_linear_component_id, + weight_precision) return variables, constraints @@ -1111,19 +1102,15 @@ def milp_xor_linear_mask_propagation_constraints(self, model): x_0 <= x_8, ... x_9 + x_10 + x_11 + x_12 == x_8, - x_13 == 20*x_9 + 10*x_10 + 10*x_11 + 20*x_12] + x_13 == 200*x_9 + 100*x_10 + 100*x_11 + 200*x_12] """ binary_variable = model.binary_variable integer_variable = model.integer_variable non_linear_component_id = model.non_linear_component_id - if self.output_bit_size <= 4: - variables, constraints = self.milp_small_xor_linear_probability_constraints(binary_variable, - integer_variable, - non_linear_component_id) - else: - variables, constraints = self.milp_large_xor_linear_probability_constraints(binary_variable, + weight_precision = model.weight_precision + variables, constraints = self.milp_large_xor_linear_probability_constraints(binary_variable, integer_variable, - non_linear_component_id) + non_linear_component_id, weight_precision) return variables, constraints def milp_wordwise_deterministic_truncated_xor_differential_constraints(self, model): diff --git a/configure.sh b/configure.sh index 152f30f7..ad3f5bab 100755 --- a/configure.sh +++ b/configure.sh @@ -2,7 +2,7 @@ apt-get -qq update apt-get install -y python3 -python3 create_bash_script.py $1 +python3 create_bash_script.py chmod +x dependencies_script.sh ./dependencies_script.sh source ~/.bashrc diff --git a/create_bash_script.py b/create_bash_script.py index ad2c72c7..89004614 100644 --- a/create_bash_script.py +++ b/create_bash_script.py @@ -1,7 +1,5 @@ import sys -gurobi_arch = sys.argv[1] or 'linux64' - with open('docker/Dockerfile', 'r') as f: dockerfile_lines = f.readlines() @@ -10,6 +8,10 @@ environment_variables = [] for line in dockerfile_lines: line = line.strip() + + if line.startswith("FROM claasp-base AS claasp-lib"): + break + is_a_comment = line.startswith('#') is_split_command = line.endswith('\\') if not line or is_a_comment: @@ -21,11 +23,7 @@ docker_command += line bash_instruction = '' if docker_command.startswith('RUN'): - command = docker_command.split('RUN')[1].strip() - if 'GUROBI_ARCH' in command: - bash_instruction = command.replace('${GUROBI_ARCH}', gurobi_arch) - else: - bash_instruction = command + bash_instruction = docker_command.split('RUN')[1].strip() elif docker_command.startswith('ENV'): command = docker_command.split('ENV')[1].strip() environment_variable = command.split('=')[0] @@ -34,11 +32,7 @@ bash_instruction = f'export {command}' elif docker_command.startswith('ARG'): command = docker_command.split('ARG')[1].strip() - bash_instruction = 'export ' - if 'GUROBI_ARCH' in command: - bash_instruction += f'GUROBI_ARCH={gurobi_arch}' - else: - bash_instruction += command + bash_instruction = f'export {command}' elif docker_command.startswith('WORKDIR'): directory = docker_command.split('WORKDIR')[1].strip() if directory != '/home/sage/tii-claasp': diff --git a/docker/Dockerfile b/docker/Dockerfile index 0658479a..ef9efc13 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,6 @@ FROM ubuntu:22.04 AS claasp-base ARG DEBIAN_FRONTEND=noninteractive -ARG GUROBI_ARCH=linux64 ARG COPY_CLAASP_LIBRARY=false ARG INSTALL_CLAASP_LIBRARY=false @@ -30,11 +29,11 @@ COPY docker/sitecustomize.py /usr/lib/python3.10/sitecustomize.py WORKDIR /opt -RUN wget https://packages.gurobi.com/10.0/gurobi10.0.0_${GUROBI_ARCH}.tar.gz \ - && tar -xf gurobi10.0.0_${GUROBI_ARCH}.tar.gz \ - && rm gurobi10.0.0_${GUROBI_ARCH}.tar.gz +RUN wget https://packages.gurobi.com/10.0/gurobi10.0.0_linux64.tar.gz \ + && tar -xf gurobi10.0.0_linux64.tar.gz \ + && rm gurobi10.0.0_linux64.tar.gz -ENV GUROBI_HOME="/opt/gurobi1000/${GUROBI_ARCH}" +ENV GUROBI_HOME="/opt/gurobi1000/linux64" ENV PATH="${PATH}:${GUROBI_HOME}/bin" ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${GUROBI_HOME}/lib" @@ -160,9 +159,11 @@ RUN wget https://www.labri.fr/perso/lsimon/downloads/softwares/glucose-syrup-4.1 && tar -xf glucose-syrup-4.1.tgz \ && rm glucose-syrup-4.1.tgz -RUN make glucose-syrup-4.1/simp +RUN cd glucose-syrup-4.1/simp \ + && make -RUN make glucose-syrup-4.1/parallel +RUN cd glucose-syrup-4.1/parallel \ + && make WORKDIR /opt diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index c1787e23..d0e875f4 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -50,9 +50,7 @@ To install the dependencies manually, you can do it through make command or exec root directory of the project. #### Make command -You need to have `make` installed for this execution. -- For m1 macs, run ```make local-installation-m1``` -- For other machines, run ```make local-installation``` +You need to have `make` installed for this execution. Run ```make local-installation``` #### Script execution - For m1 macs, run ```./configure.sh armlinux64``` diff --git a/tests/benchmark/cipher_test.py b/tests/benchmark/cipher_test.py index a5f16870..d9cb8766 100644 --- a/tests/benchmark/cipher_test.py +++ b/tests/benchmark/cipher_test.py @@ -4,9 +4,8 @@ from claasp.ciphers.block_ciphers.aes_block_cipher import AESBlockCipher from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher -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 +from claasp.cipher_modules.generic_functions_vectorized_byte import get_number_of_bytes_needed_for_bit_size speck = SpeckBlockCipher() @@ -29,18 +28,25 @@ def test_evaluate_using_c_with_aes_cipher(benchmark): benchmark(aes.evaluate_using_c, [0x012345, 0x89ABCD], True) -cipher_inputs_parameter_values = [[np.random.randint(256, size=(8, 2), dtype=np.uint8) for _ in range(10)], - [np.random.randint(256, size=(8, 2), dtype=np.uint8) for _ in range(100)], - [np.random.randint(256, size=(8, 2), dtype=np.uint8) for _ in range(10000)], - [np.random.randint(256, size=(8, 2), dtype=np.uint8) for _ in range(1000000)]] +numbers_of_samples = [10**1, 10**2, 10**4, 10**6] +aes_inputs_byte_size = [get_number_of_bytes_needed_for_bit_size(bit_size) for bit_size in aes.inputs_bit_size] +aes_input_parameter_values = [ + [np.random.randint(256, size=(aes_inputs_byte_size[0], nb), dtype=np.uint8), + np.random.randint(256, size=(aes_inputs_byte_size[1], nb), dtype=np.uint8)] + for nb in numbers_of_samples] +speck_inputs_byte_size = [get_number_of_bytes_needed_for_bit_size(bit_size) for bit_size in speck.inputs_bit_size] +speck_input_parameter_values = [ + [np.random.randint(256, size=(speck_inputs_byte_size[0], nb), dtype=np.uint8), + np.random.randint(256, size=(speck_inputs_byte_size[1], nb), dtype=np.uint8)] + for nb in numbers_of_samples] -@pytest.mark.parametrize("cipher_input", cipher_inputs_parameter_values) +@pytest.mark.parametrize("cipher_input", speck_input_parameter_values) def test_evaluate_vectorized_with_speck_cipher(benchmark, cipher_input): benchmark(speck.evaluate_vectorized, cipher_input) -@pytest.mark.parametrize("cipher_input", cipher_inputs_parameter_values) +@pytest.mark.parametrize("cipher_input", aes_input_parameter_values) def test_evaluate_vectorized_with_aes_cipher(benchmark, cipher_input): benchmark(aes.evaluate_vectorized, cipher_input) diff --git a/tests/benchmark/sat_xor_differential_model_test.py b/tests/benchmark/sat_xor_differential_model_test.py index 277f5cf1..ccd504e6 100644 --- a/tests/benchmark/sat_xor_differential_model_test.py +++ b/tests/benchmark/sat_xor_differential_model_test.py @@ -67,7 +67,10 @@ def test_find_lowest_weight_xor_differential_trail_with_speck_cipher(benchmark): def test_find_one_xor_differential_trail_with_fixed_weight(benchmark): window_size_by_round_list = [0 for _ in range(speck.number_of_rounds)] - sat = SatXorDifferentialModel(speck, window_size_by_round=window_size_by_round_list) + sat = SatXorDifferentialModel(speck) + sat.set_window_size_heuristic_by_round( + window_size_by_round_list + ) 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', diff --git a/tests/unit/cipher_modules/code_generator_test.py b/tests/unit/cipher_modules/code_generator_test.py index ddf3f0e3..a9eb17a5 100644 --- a/tests/unit/cipher_modules/code_generator_test.py +++ b/tests/unit/cipher_modules/code_generator_test.py @@ -1,9 +1,9 @@ -from claasp.cipher_modules import code_generator from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - +from claasp.cipher_modules.code_generator import generate_bit_based_vectorized_python_code_string,prepare_input_byte_based_vectorized_python_code_string def test_generate_bit_based_vectorized_python_code_string(): speck = SpeckBlockCipher() - string_python_code = code_generator.generate_bit_based_vectorized_python_code_string(speck) + string_python_code = generate_bit_based_vectorized_python_code_string(speck) assert string_python_code.split("\n")[0] == 'from claasp.cipher_modules.generic_functions_vectorized_bit import *' + diff --git a/tests/unit/cipher_modules/generic_functions_vectorized_byte_test.py b/tests/unit/cipher_modules/generic_functions_vectorized_byte_test.py index 0e90af5d..795bf940 100644 --- a/tests/unit/cipher_modules/generic_functions_vectorized_byte_test.py +++ b/tests/unit/cipher_modules/generic_functions_vectorized_byte_test.py @@ -1,7 +1,267 @@ -from claasp.cipher_modules.generic_functions_vectorized_byte import byte_vector_is_consecutive +from claasp.cipher_modules.generic_functions_vectorized_byte import * +import numpy as np def test_byte_vector_is_consecutive(): L = [3, 2, 1, 0] - assert byte_vector_is_consecutive(L) + + +def test_integer_array_to_evaluate_vectorized_input(): + values = [0, 0xffff, 0xff, 0x01f0] + bit_size = 16 + evaluate_vectorized_input = integer_array_to_evaluate_vectorized_input(values, bit_size) + assert np.all(evaluate_vectorized_input.shape == (2, 4)) + assert np.all(evaluate_vectorized_input[:, 0] == 0) + assert np.all(evaluate_vectorized_input[:, 1] == 255) + assert np.all(evaluate_vectorized_input[:, 2] == (0, 255)) + assert np.all(evaluate_vectorized_input[:, 3] == (1, 240)) + + values = [0, 0x1ffff, 0xff, 0xfffff] + bit_size = 17 + evaluate_vectorized_input = integer_array_to_evaluate_vectorized_input(values, bit_size) + assert np.all(evaluate_vectorized_input.shape == (3, 4)) + assert np.all(evaluate_vectorized_input[:, 0] == 0) + assert np.all(evaluate_vectorized_input[:, 1] == (1, 255, 255)) + assert np.all(evaluate_vectorized_input[:, 2] == (0, 0, 255)) + assert np.all(evaluate_vectorized_input[:, 3] == (1, 255, 255)) + + values = [2 ** 130 - 1, 0] + bit_size = 129 + evaluate_vectorized_input = integer_array_to_evaluate_vectorized_input(values, bit_size) + assert np.all(evaluate_vectorized_input.shape == (17, 2)) + assert np.all(evaluate_vectorized_input[1:, 0] == 255) + assert evaluate_vectorized_input[0, 0] == 1 + assert np.all(evaluate_vectorized_input[:, 1] == 0) + + +def test_cipher_inputs_to_evaluate_vectorized_inputs(): + inputs = [0xff, 0] + cipher_inputs_bit_size = [32, 64] + evaluate_vectorized_inputs = cipher_inputs_to_evaluate_vectorized_inputs(inputs, cipher_inputs_bit_size) + assert np.all(evaluate_vectorized_inputs[0].shape == (4, 1)) + assert np.all(evaluate_vectorized_inputs[1].shape == (8, 1)) + assert np.all(evaluate_vectorized_inputs[0][:, 0] == (0, 0, 0, 255)) + assert np.all(evaluate_vectorized_inputs[1] == 0) + + inputs = [[0xff, 0, 0xcafe], [0, 0, 2 ** 64 - 1]] + cipher_inputs_bit_size = [32, 64] + evaluate_vectorized_inputs = cipher_inputs_to_evaluate_vectorized_inputs(inputs, cipher_inputs_bit_size) + assert np.all(evaluate_vectorized_inputs[0].shape == (4, 3)) + assert np.all(evaluate_vectorized_inputs[1].shape == (8, 3)) + assert np.all(evaluate_vectorized_inputs[0][:, 0] == (0, 0, 0, 255)) + assert np.all(evaluate_vectorized_inputs[0][:, 1] == (0, 0, 0, 0)) + assert np.all(evaluate_vectorized_inputs[0][:, 2] == (0, 0, 0xca, 0xfe)) + assert np.all(evaluate_vectorized_inputs[1][:, :2] == 0) + assert np.all(evaluate_vectorized_inputs[1][:, 2] == 255) + + +def test_get_number_of_bytes_needed_for_bit_size(): + assert get_number_of_bytes_needed_for_bit_size(64) == 8 + assert get_number_of_bytes_needed_for_bit_size(63) == 8 + assert get_number_of_bytes_needed_for_bit_size(65) == 9 + + +def test_evaluate_vectorized_outputs_to_integers(): + bit_size = 256 + values = [0, 2 ** bit_size - 1, 0xff] + evaluate_vectorized_outputs = [integer_array_to_evaluate_vectorized_input(values, bit_size).transpose()] + assert np.all(evaluate_vectorized_outputs_to_integers(evaluate_vectorized_outputs, bit_size) == values) + bit_size = 6 + values = [np.uint8([0xff, 0x3f]).reshape(2, 1)] + assert evaluate_vectorized_outputs_to_integers(values, bit_size) == [0x3f, 0x3f] + values = [np.uint8([0x3f]).reshape(1, 1)] + assert evaluate_vectorized_outputs_to_integers(values, bit_size) == 0x3f + +def test_byte_vector_select_all_words(): + input_bit_size = 64 + num_cols = 2 + # Word operation, easy case + A = np.arange(num_cols * input_bit_size // 8, dtype=np.uint8).reshape(input_bit_size // 8, num_cols) + B = np.arange(num_cols * input_bit_size // 8, 2 * num_cols * input_bit_size // 8, dtype=np.uint8).reshape( + input_bit_size // 8, num_cols) + # Take the first 32 bits of A and last 32 bits of B + unformated_inputs = [A, B] + real_bits = [[list(range(32))], [list(range(32, 64))]] + real_inputs = [[0], [1]] + number_of_inputs = 2 + words_per_input = get_number_of_bytes_needed_for_bit_size(input_bit_size // number_of_inputs) + actual_input_bits = [64, 64] + result = byte_vector_select_all_words(unformated_inputs, real_bits, real_inputs, number_of_inputs, words_per_input, + actual_input_bits) + assert np.all(result[0] == A[:4]) + assert np.all(result[1] == B[4:]) + + # Unexpected leading bits are correctly removed + input_bit_size = 10 + A = np.uint8([0xf1, 0x23]).reshape(2, 1) + unformated_inputs = [A] + real_bits = [[list(range(10))]] + real_inputs = [[0]] + number_of_inputs = 1 + words_per_input = get_number_of_bytes_needed_for_bit_size(input_bit_size) + actual_input_bits = [input_bit_size] + result = byte_vector_select_all_words(unformated_inputs, real_bits, real_inputs, number_of_inputs, words_per_input, + actual_input_bits) + assert np.all(result[0].flatten() == [0x1, 0x23]) + + # Odd case + input_bit_size = 10 + A = np.uint8([0x3, 0x23]).reshape(2, 1) + unformated_inputs = [A] + real_bits = [[list(range(1, 9))]] + real_inputs = [[0]] + number_of_inputs = 1 + words_per_input = get_number_of_bytes_needed_for_bit_size(input_bit_size) + actual_input_bits = [input_bit_size] + result = byte_vector_select_all_words(unformated_inputs, real_bits, real_inputs, number_of_inputs, words_per_input, + actual_input_bits) + assert np.all(result[0].flatten() == [0x91]) + + +def test_byte_vector_linear_layer(): + # Fancy block cipher linear layer + linear_layer = [ + [0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1], + [0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1], + [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1], + [1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0], + [1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0], + [1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0], + [1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1], + [0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0], + [0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0], + [1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1], + [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1], + [0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1], + [0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1], + [0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1], + [0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1], + [1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1] + ] + linear_layer_input = [np.ones((1, 1), dtype=np.uint8) for i in range(24)] + expected_output = [0xef, 0xc4, 0xa3] + result = byte_vector_linear_layer(linear_layer_input, matrix=linear_layer) + assert np.all(result.flatten().tolist() == expected_output) + + +def test_byte_vector_SBOX(): + # 4 bit case + sbox = [12, 5, 6, 11, 9, 0, 10, 13, 3, 14, 15, 8, 4, 7, 1, 2] + values = list(range(16)) + formated_values = integer_array_to_evaluate_vectorized_input(values, 4) + sub = byte_vector_SBOX([formated_values], sbox, input_bit_size=4) + assert np.all(evaluate_vectorized_outputs_to_integers([sub.transpose()], 4) == sbox) + + # 9 bit case + sbox = [ + 167, 239, 161, 379, 391, 334, 9, 338, 38, 226, 48, 358, 452, 385, 90, 397, + 183, 253, 147, 331, 415, 340, 51, 362, 306, 500, 262, 82, 216, 159, 356, 177, + 175, 241, 489, 37, 206, 17, 0, 333, 44, 254, 378, 58, 143, 220, 81, 400, + 95, 3, 315, 245, 54, 235, 218, 405, 472, 264, 172, 494, 371, 290, 399, 76, + 165, 197, 395, 121, 257, 480, 423, 212, 240, 28, 462, 176, 406, 507, 288, 223, + 501, 407, 249, 265, 89, 186, 221, 428, 164, 74, 440, 196, 458, 421, 350, 163, + 232, 158, 134, 354, 13, 250, 491, 142, 191, 69, 193, 425, 152, 227, 366, 135, + 344, 300, 276, 242, 437, 320, 113, 278, 11, 243, 87, 317, 36, 93, 496, 27, + + 487, 446, 482, 41, 68, 156, 457, 131, 326, 403, 339, 20, 39, 115, 442, 124, + 475, 384, 508, 53, 112, 170, 479, 151, 126, 169, 73, 268, 279, 321, 168, 364, + 363, 292, 46, 499, 393, 327, 324, 24, 456, 267, 157, 460, 488, 426, 309, 229, + 439, 506, 208, 271, 349, 401, 434, 236, 16, 209, 359, 52, 56, 120, 199, 277, + 465, 416, 252, 287, 246, 6, 83, 305, 420, 345, 153, 502, 65, 61, 244, 282, + 173, 222, 418, 67, 386, 368, 261, 101, 476, 291, 195, 430, 49, 79, 166, 330, + 280, 383, 373, 128, 382, 408, 155, 495, 367, 388, 274, 107, 459, 417, 62, 454, + 132, 225, 203, 316, 234, 14, 301, 91, 503, 286, 424, 211, 347, 307, 140, 374, + + 35, 103, 125, 427, 19, 214, 453, 146, 498, 314, 444, 230, 256, 329, 198, 285, + 50, 116, 78, 410, 10, 205, 510, 171, 231, 45, 139, 467, 29, 86, 505, 32, + 72, 26, 342, 150, 313, 490, 431, 238, 411, 325, 149, 473, 40, 119, 174, 355, + 185, 233, 389, 71, 448, 273, 372, 55, 110, 178, 322, 12, 469, 392, 369, 190, + 1, 109, 375, 137, 181, 88, 75, 308, 260, 484, 98, 272, 370, 275, 412, 111, + 336, 318, 4, 504, 492, 259, 304, 77, 337, 435, 21, 357, 303, 332, 483, 18, + 47, 85, 25, 497, 474, 289, 100, 269, 296, 478, 270, 106, 31, 104, 433, 84, + 414, 486, 394, 96, 99, 154, 511, 148, 413, 361, 409, 255, 162, 215, 302, 201, + + 266, 351, 343, 144, 441, 365, 108, 298, 251, 34, 182, 509, 138, 210, 335, 133, + 311, 352, 328, 141, 396, 346, 123, 319, 450, 281, 429, 228, 443, 481, 92, 404, + 485, 422, 248, 297, 23, 213, 130, 466, 22, 217, 283, 70, 294, 360, 419, 127, + 312, 377, 7, 468, 194, 2, 117, 295, 463, 258, 224, 447, 247, 187, 80, 398, + 284, 353, 105, 390, 299, 471, 470, 184, 57, 200, 348, 63, 204, 188, 33, 451, + 97, 30, 310, 219, 94, 160, 129, 493, 64, 179, 263, 102, 189, 207, 114, 402, + 438, 477, 387, 122, 192, 42, 381, 5, 145, 118, 180, 449, 293, 323, 136, 380, + 43, 66, 60, 455, 341, 445, 202, 432, 8, 237, 15, 376, 436, 464, 59, 461 + ] + values = list(range(2 ** 9)) + formated_values = integer_array_to_evaluate_vectorized_input(values, 9) + sub = byte_vector_SBOX([formated_values], sbox, input_bit_size=9) + assert np.all(evaluate_vectorized_outputs_to_integers([sub.transpose()], 9) == sbox) + + +def test_byte_vector_XOR(): + input_values = [np.arange(8, dtype = np.uint8).reshape((2,4)), np.arange(240, 248, dtype = np.uint8).reshape((2,4))] + expected_result = input_values[0]^input_values[1] + xor_result = byte_vector_XOR(input_values) + assert np.all(xor_result == expected_result) + +def test_byte_vector_OR(): + input_values = [np.arange(8, dtype = np.uint8).reshape((2,4)), np.arange(240, 248, dtype = np.uint8).reshape((2,4))] + expected_result = input_values[0] | input_values[1] + xor_result = byte_vector_OR(input_values) + assert np.all(xor_result == expected_result) + +def test_byte_vector_AND(): + input_values = [np.arange(8, dtype = np.uint8).reshape((2,4)), np.arange(240, 248, dtype = np.uint8).reshape((2,4))] + expected_result = input_values[0]&input_values[1] + xor_result = byte_vector_AND(input_values) + assert np.all(xor_result == expected_result) + +def test_byte_vector_NOT(): + input_values = [np.arange(8, dtype = np.uint8).reshape((2,4))] + expected_result = input_values[0]^0xff + xor_result = byte_vector_NOT(input_values) + assert np.all(xor_result == expected_result) + +def test_byte_vector_MODADD(): + bits = 48 + A = [0xcafecafecafe] + B = [0xdecadecadeca] + input_values = [integer_array_to_evaluate_vectorized_input(A, bits), integer_array_to_evaluate_vectorized_input(B, bits)] + expected_result = integer_array_to_evaluate_vectorized_input([(A[0]+B[0]) % (2**bits)], bits) + modadd_result = byte_vector_MODADD(input_values) + assert np.all(modadd_result == expected_result) + +def test_byte_vector_MODSUB(): + bits = 48 + A = [0xcafecafecafe] + B = [0xdecadecadeca] + input_values = [integer_array_to_evaluate_vectorized_input(A, bits), integer_array_to_evaluate_vectorized_input(B, bits)] + expected_result = integer_array_to_evaluate_vectorized_input([(A[0]-B[0]) % (2**bits)], bits) + modsub_result = byte_vector_MODSUB(input_values) + + assert np.all(modsub_result == expected_result) + +def test_byte_vector_ROTATE(): + bits = 12 + input_values = integer_array_to_evaluate_vectorized_input([0, 0xfff], bits) + rotate_result = byte_vector_ROTATE([input_values], rotation_amount = -4, input_bit_size=bits) + assert np.all(rotate_result==input_values) + + bits = 12 + input_values = integer_array_to_evaluate_vectorized_input([0, 0xff], bits) + expected_result = integer_array_to_evaluate_vectorized_input([0, 0x1fe], bits) + rotate_result = byte_vector_ROTATE([input_values], rotation_amount = -1, input_bit_size=bits) + + print("In :", evaluate_vectorized_outputs_to_integers([input_values.transpose()], bits)) + print("Exp:", evaluate_vectorized_outputs_to_integers([expected_result.transpose()], bits)) + print("Out:",evaluate_vectorized_outputs_to_integers([rotate_result.transpose()], bits)) + + assert np.all(rotate_result==expected_result) + 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 221761ed..5a08e96c 100644 --- a/tests/unit/cipher_modules/models/sat/sat_model_test.py +++ b/tests/unit/cipher_modules/models/sat/sat_model_test.py @@ -13,16 +13,16 @@ def test_solve(): tea = TeaBlockCipher(number_of_rounds=32) sat = SatCipherModel(tea) sat.build_cipher_model() - solution = sat.solve('cipher', solver_name='cryptominisat') + solution = sat.solve('cipher', solver_name='CRYPTOMINISAT_EXT') assert str(solution['cipher']) == 'tea_p64_k128_o64_r32' - assert solution['solver_name'] == 'cryptominisat' + assert solution['solver_name'] == 'CRYPTOMINISAT_EXT' assert eval('0x' + solution['components_values']['modadd_0_3']['value']) >= 0 assert eval('0x' + solution['components_values']['cipher_output_31_16']['value']) >= 0 # testing with sage solver simon = SimonBlockCipher(number_of_rounds=32) sat = SatCipherModel(simon) sat.build_cipher_model() - solution = sat.solve('cipher', solver_name='cryptominisat_sage') + solution = sat.solve('cipher', solver_name='cryptominisat') 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 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 48808515..f3917c21 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 @@ -16,6 +16,6 @@ def test_find_missing_bits(): 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['solver_name'] == 'CRYPTOMINISAT_EXT' assert missing_bits['components_values'][cipher_output_id] == {'value': '1234abcd'} assert missing_bits['status'] == 'SATISFIABLE' 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 a0b9b763..3198b128 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 @@ -1,4 +1,5 @@ import numpy as np +import pytest from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher from claasp.cipher_modules.models.utils import set_fixed_variables, integer_to_bit_list @@ -6,8 +7,7 @@ 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) + sat = SatXorDifferentialModel(speck_5rounds, window_size_weight_pr_vars=1) assert int(sat.find_all_xor_differential_trails_with_fixed_weight( 9)[0]['total_weight']) == int(9.0) @@ -38,29 +38,73 @@ def test_find_one_xor_differential_trail(): assert str(trail['cipher']) == 'speck_p32_k64_o32_r5' assert trail['model_type'] == 'xor_differential' - assert trail['solver_name'] == 'cryptominisat' + assert trail['solver_name'] == 'CRYPTOMINISAT_EXT' assert trail['status'] == 'SATISFIABLE' - trail = sat.find_one_xor_differential_trail(fixed_values=[plaintext], solver_name="kissat") - assert trail['solver_name'] == 'kissat' + trail = sat.find_one_xor_differential_trail(fixed_values=[plaintext], solver_name="KISSAT_EXT") + assert trail['solver_name'] == 'KISSAT_EXT' assert trail['status'] == 'SATISFIABLE' 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]) + sat = SatXorDifferentialModel(speck) + sat.set_window_size_heuristic_by_round([0, 0, 0]) result = sat.find_one_xor_differential_trail_with_fixed_weight(3) assert int(result['total_weight']) == int(3.0) +def test_find_one_xor_differential_trail_with_fixed_weight_with_at_least_one_full_2_window(): + speck = SpeckBlockCipher(number_of_rounds=9) + sat = SatXorDifferentialModel(speck) + sat.set_window_size_heuristic_by_round( + [2 for i in range(9)], number_of_full_windows=1 + ) + result = sat.find_one_xor_differential_trail_with_fixed_weight(30, solver_name="CADICAL_EXT") + assert int(result['total_weight']) == int(30.0) + +def test_find_one_xor_differential_trail_with_fixed_weight_9_rounds(): + speck = SpeckBlockCipher(number_of_rounds=9) + sat = SatXorDifferentialModel(speck) + + sat.set_window_size_heuristic_by_round( + [2 for i in range(9)] + ) + result = sat.find_one_xor_differential_trail_with_fixed_weight(30, solver_name="CADICAL_EXT") + assert int(result['total_weight']) == int(30.0) + +def test_find_one_xor_differential_trail_with_fixed_weight_with_at_least_one_full_window_parallel(): + speck = SpeckBlockCipher(number_of_rounds=10) + sat = SatXorDifferentialModel(speck) + sat.set_window_size_heuristic_by_round( + [3 for i in range(10)], number_of_full_windows=1 + ) + 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(34, fixed_variables=[plaintext, key]) + result = sat._solve_with_external_sat_solver( + "xor_differential", "PARKISSAT_EXT", ["-c=10"] + ) + assert int(result['total_weight']) == int(34.0) + + def test_find_one_xor_differential_trail_with_fixed_weight_and_window_heuristic_per_component(): speck = SpeckBlockCipher(number_of_rounds=3) filtered_objects = [obj.id for obj in speck.get_all_components() if obj.description[0] == "MODADD"] dict_of_window_heuristic_per_component = {} 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) + sat = SatXorDifferentialModel(speck) + sat.set_window_size_heuristic_by_component_id(dict_of_window_heuristic_per_component) result = sat.find_one_xor_differential_trail_with_fixed_weight(3) assert int(result['total_weight']) == int(3.0) @@ -70,7 +114,7 @@ def test_build_xor_differential_trail_model_fixed_weight_and_parkissat(): speck = SpeckBlockCipher(number_of_rounds=3) sat = SatXorDifferentialModel(speck) sat.build_xor_differential_trail_model(3) - result = sat._solve_with_external_sat_solver("xor_differential", "parkissat", [f'-c={number_of_cores}']) + result = sat._solve_with_external_sat_solver("xor_differential", "PARKISSAT_EXT", [f'-c={number_of_cores}']) assert int(result['total_weight']) == int(3.0) 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 aa589a85..21eeebef 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 @@ -39,7 +39,7 @@ def test_find_one_xor_linear_trail(): assert str(trail['cipher']) == 'speck_p32_k64_o32_r4' assert trail['model_type'] == 'xor_linear' - assert trail['solver_name'] == 'cryptominisat' + assert trail['solver_name'] == 'CRYPTOMINISAT_EXT' assert trail['status'] == 'SATISFIABLE' 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 2514a004..40c4345d 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 @@ -16,6 +16,6 @@ def test_find_missing_bits(): 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['solver_name'] == 'Z3_EXT' assert missing_bits['components_values'][cipher_output_id] == {'value': '1234abcd'} assert missing_bits['status'] == 'SATISFIABLE' 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 311e3c14..29586ebb 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 @@ -21,7 +21,7 @@ def test_find_one_xor_differential_trail(): smt = SmtXorDifferentialModel(speck) solution = smt.find_one_xor_differential_trail() assert str(solution['cipher']) == 'speck_p32_k64_o32_r5' - assert solution['solver_name'] == 'z3' + assert solution['solver_name'] == 'Z3_EXT' assert eval('0x' + solution['components_values']['intermediate_output_0_6']['value']) >= 0 assert solution['components_values']['intermediate_output_0_6']['weight'] == 0 assert eval('0x' + solution['components_values']['cipher_output_4_12']['value']) >= 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 d706f129..88d0ab79 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 @@ -24,7 +24,7 @@ def test_find_one_xor_linear_trail(): smt = SmtXorLinearModel(speck) solution = smt.find_one_xor_linear_trail() assert str(solution['cipher']) == 'speck_p32_k64_o32_r4' - assert solution['solver_name'] == 'z3' + assert solution['solver_name'] == 'Z3_EXT' assert eval('0x' + solution['components_values']['modadd_0_1_i']['value']) >= 0 assert solution['components_values']['modadd_0_1_i']['weight'] == 0 assert solution['components_values']['modadd_0_1_i']['sign'] == 1 diff --git a/tests/unit/cipher_modules/report_test.py b/tests/unit/cipher_modules/report_test.py index 18c1b067..8ca1fc02 100644 --- a/tests/unit/cipher_modules/report_test.py +++ b/tests/unit/cipher_modules/report_test.py @@ -13,6 +13,7 @@ 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 +from claasp.cipher_modules.continuous_diffusion_analysis import ContinuousDiffusionAnalysis def test_save_as_image(): @@ -50,6 +51,14 @@ def test_save_as_image(): report_cca = Report(component_analysis) report_cca.save_as_image() + speck = SpeckBlockCipher(number_of_rounds=2) + cda = ContinuousDiffusionAnalysis(speck) + cda_for_repo = cda.continuous_diffusion_tests() + cda_repo = Report(cda_for_repo) + cda_repo.save_as_image() + + + def test_save_as_latex_table(): simon = SimonBlockCipher(number_of_rounds=2) @@ -126,7 +135,7 @@ def test_save_as_json(): bit_values=[0] * 64) ] trail = sat.find_one_xor_differential_trail_with_fixed_weight(fixed_weight=16, fixed_values=related_key_setting, - solver_name='kissat') + solver_name='KISSAT_EXT') trail_report = Report(trail) trail_report.show() @@ -134,15 +143,6 @@ def test_save_as_json(): 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(neural_network_blackbox_distinguisher_tests_results) - - blackbox_report.save_as_json() - blackbox_report.clean_reports() - def test_show(): speck = SpeckBlockCipher(number_of_rounds=3) @@ -163,7 +163,7 @@ def test_show(): set_fixed_variables(component_id='key', constraint_type='not_equal', bit_positions=list(range(80)), bit_values=[0] * 80)] trail = sat.find_one_xor_differential_trail_with_fixed_weight(fixed_weight=16, fixed_values=related_key_setting, - solver_name='kissat') + solver_name='KISSAT_EXT') trail_report = Report(trail) trail_report.show() diff --git a/tests/unit/cipher_test.py b/tests/unit/cipher_test.py index 5a053ea1..ab471fdb 100644 --- a/tests/unit/cipher_test.py +++ b/tests/unit/cipher_test.py @@ -53,9 +53,9 @@ def test_algebraic_tests(): d = AlgebraicTests(toyspn).algebraic_tests(10) assert d == { 'input_parameters': {'cipher': toyspn, 'timeout_in_seconds': 10, 'test_name': 'algebraic_tests'}, - 'test_results': {'number_of_variables': [30, 48], - 'number_of_equations': [40, 80], - 'number_of_monomials': [60, 108], + 'test_results': {'number_of_variables': [24, 42], + 'number_of_equations': [34, 74], + 'number_of_monomials': [54, 102], 'max_degree_of_equations': [2, 2], 'test_passed': [False, False]}} @@ -64,9 +64,9 @@ def test_algebraic_tests(): assert d == {'input_parameters': {'cipher': speck, 'timeout_in_seconds': 1, 'test_name': 'algebraic_tests'}, - 'test_results': {'number_of_variables': [144], - 'number_of_equations': [96], - 'number_of_monomials': [189], + 'test_results': {'number_of_variables': [112], + 'number_of_equations': [64], + 'number_of_monomials': [157], 'max_degree_of_equations': [2], 'test_passed': [True]}} @@ -75,9 +75,9 @@ def test_algebraic_tests(): compare_result = {'input_parameters': {'cipher': aes, 'timeout_in_seconds': 5, 'test_name': 'algebraic_tests'}, - 'test_results': {'number_of_variables': [128], - 'number_of_equations': [198], - 'number_of_monomials': [296], + 'test_results': {'number_of_variables': [104], + 'number_of_equations': [174], + 'number_of_monomials': [272], 'max_degree_of_equations': [2], 'test_passed': [False]}} @@ -115,6 +115,7 @@ def test_evaluate_vectorized(): X1Lib = int.from_bytes(X[:, 1].tobytes(), byteorder='big') C0Lib = speck.evaluate([X0Lib, K0Lib]) C1Lib = speck.evaluate([X1Lib, K1Lib]) + print(result, C0Lib, C1Lib) assert int.from_bytes(result[-1][0].tobytes(), byteorder='big') == C0Lib assert int.from_bytes(result[-1][1].tobytes(), byteorder='big') == C1Lib @@ -191,19 +192,19 @@ def test_get_round_from_component_id(): fancy = FancyBlockCipher(number_of_rounds=2) assert fancy.get_round_from_component_id('xor_1_14') == 1 - -def test_impossible_differential_search(): - speck6 = SpeckBlockCipher(number_of_rounds=6) - # impossible_differentials = speck6.impossible_differential_search("smt", "yices-smt2") - impossible_differentials = speck6.impossible_differential_search("cp", "chuffed") - - assert ((0x400000, 1) in impossible_differentials) and ((0x400000, 2) in impossible_differentials) and ( - (0x400000, 0x8000) in impossible_differentials) +# +# def test_impossible_differential_search(): +# speck6 = SpeckBlockCipher(number_of_rounds=6) +# # impossible_differentials = speck6.impossible_differential_search("smt", "yices-smt2") +# impossible_differentials = speck6.impossible_differential_search("cp", "Chuffed") +# +# assert ((0x400000, 1) in impossible_differentials) and ((0x400000, 2) in impossible_differentials) and ( +# (0x400000, 0x8000) in impossible_differentials) def test_is_algebraically_secure(): aes = AESBlockCipher(word_size=4, state_size=2, number_of_rounds = 1) - assert aes.is_algebraically_secure(20) is False + assert aes.is_algebraically_secure(200) is False def test_is_andrx(): @@ -234,7 +235,7 @@ def test_is_spn(): def test_polynomial_system(): tea = TeaBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=1) - assert str(tea.polynomial_system()) == 'Polynomial Sequence with 352 Polynomials in 448 Variables' + assert str(tea.polynomial_system()) == 'Polynomial Sequence with 288 Polynomials in 384 Variables' def test_polynomial_system_at_round(): @@ -396,10 +397,9 @@ def test_vector_check(): def test_zero_correlation_linear_search(): speck6 = SpeckBlockCipher(number_of_rounds=6) - zero_correlation_linear_approximations = speck6.zero_correlation_linear_search("smt", "yices-smt2") + zero_correlation_linear_approximations = speck6.zero_correlation_linear_search("smt", "YICES_EXT") assert len(zero_correlation_linear_approximations) > 0 - def test_cipher_inverse(): key = 0xabcdef01abcdef01 plaintext = 0x01234567 diff --git a/tests/unit/ciphers/block_ciphers/aes_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/aes_block_cipher_test.py index 7d3937ba..00c59431 100644 --- a/tests/unit/ciphers/block_ciphers/aes_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/aes_block_cipher_test.py @@ -8,80 +8,104 @@ def test_aes_block_cipher(): assert aes.number_of_rounds == 10 assert aes.id == 'aes_block_cipher_k128_p128_o128_r10' assert aes.component_from(0, 0).id == 'xor_0_0' + key = 0x2b7e151628aed2a6abf7158809cf4f3c + plaintext = 0x6bc1bee22e409f96e93d7e117393172a + ciphertext = 0x3ad77bb40d7a3660a89ecaf32466ef97 + assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext - aes = AESBlockCipher(number_of_rounds=4) - assert aes.number_of_rounds == 4 - assert aes.id == 'aes_block_cipher_k128_p128_o128_r4' - assert aes.component_from(3, 0).id == 'sbox_3_0' - +def test_aes128_block_cipher(): aes = AESBlockCipher() key = 0x2b7e151628aed2a6abf7158809cf4f3c plaintext = 0x6bc1bee22e409f96e93d7e117393172a ciphertext = 0x3ad77bb40d7a3660a89ecaf32466ef97 assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +def test_aes_8_3_block_cipher(): aes = AESBlockCipher(word_size=8, state_size=3) key = 0x2b7e151628aed2a6ab plaintext = 0x6bc1bee22e409f96e9 ciphertext = 0xf8666f8d0ba0dcfced assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +def test_aes_8_2_block_cipher(): aes = AESBlockCipher(word_size=8, state_size=2) key = 0x2b7e1516 plaintext = 0x6bc1bee2 ciphertext = 0xdbbdd038 assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext - aes = AESBlockCipher(word_size=4, state_size=4) - key = 0x2b7e151628aed2a6 - plaintext = 0x6bc1bee22e409f96 - ciphertext = 0x0e51ff61dac37a78 - assert aes.evaluate([key, plaintext]) == ciphertext - - aes = AESBlockCipher(word_size=4, state_size=3) - key = 0b100111100101111110011110010111110000 - plaintext = 0b100111100101111110011110010111110000 - ciphertext = 0x3a54a9d02 - assert aes.evaluate([key, plaintext]) == ciphertext - - aes = AESBlockCipher(word_size=4, state_size=2) - key = 0x2b7e - plaintext = 0x6bc1 - ciphertext = 0xa1fe - assert aes.evaluate([key, plaintext]) == ciphertext +# def test_aes_4_4_block_cipher(): +# aes = AESBlockCipher(word_size=4, state_size=4) +# key = 0x2b7e151628aed2a6 +# plaintext = 0x6bc1bee22e409f96 +# ciphertext = 0x0e51ff61dac37a78 +# assert aes.evaluate([key, plaintext]) == ciphertext +# assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +# +# def test_aes_4_3_block_cipher(): +# aes = AESBlockCipher(word_size=4, state_size=3) +# key = 0b100111100101111110011110010111110000 +# plaintext = 0b100111100101111110011110010111110000 +# ciphertext = 0x3a54a9d02 +# assert aes.evaluate([key, plaintext]) == ciphertext +# assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +# +# def test_aes_4_2_block_cipher(): +# aes = AESBlockCipher(word_size=4, state_size=2) +# key = 0x2b7e +# plaintext = 0x6bc1 +# ciphertext = 0xa1fe +# assert aes.evaluate([key, plaintext]) == ciphertext +# assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +def test_aes_3_4_block_cipher(): aes = AESBlockCipher(word_size=3, state_size=4) key = 0x2b7e151628ae plaintext = 0x6bc1bee22e40 ciphertext = 0x33d9c96fe11c assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +def test_aes_3_3_block_cipher(): aes = AESBlockCipher(word_size=3, state_size=3) key = 0b101101101101101101100011011 plaintext = 0b100001111011110101101100010 ciphertext = 0x0595c25b assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +def test_aes_3_2_block_cipher(): aes = AESBlockCipher(word_size=3, state_size=2) key = 0x2b7 plaintext = 0x6bc ciphertext = 0x2c8 assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +def test_aes_2_4_block_cipher(): aes = AESBlockCipher(word_size=2, state_size=4) key = 0x2b7e1516 plaintext = 0x6bc1bee2 ciphertext = 0x41bed50e assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +def test_aes_2_3_block_cipher(): aes = AESBlockCipher(word_size=2, state_size=3) key = 0b101101101100011011 plaintext = 0b011110101101100010 ciphertext = 0x00de3c assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext +def test_aes_2_2_block_cipher(): aes = AESBlockCipher(word_size=2, state_size=2) key = 0x2b plaintext = 0x6b ciphertext = 0x1f assert aes.evaluate([key, plaintext]) == ciphertext + assert aes.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/bea1_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/bea1_block_cipher_test.py index 96c95b29..6d95e933 100644 --- a/tests/unit/ciphers/block_ciphers/bea1_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/bea1_block_cipher_test.py @@ -12,9 +12,11 @@ def test_bea1_block_cipher(): pt = 0x47a57eff5d6475a68916 ciphertext = 0x439d5298656eccc67dee assert bea.evaluate([key,pt]) == ciphertext + assert bea.evaluate_vectorized([key,pt], evaluate_api=True) == ciphertext bea = BEA1BlockCipher() key = 0xe2f458684631d4b069dd178cf7ace9 pt = 0x4e7a51e6d08c7a3515f0 ciphertext = 0xa36097ea1bdcddf8b06d assert bea.evaluate([key,pt]) == ciphertext + assert bea.evaluate_vectorized([key,pt], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/des_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/des_block_cipher_test.py index b6efef86..fb2ac2f3 100644 --- a/tests/unit/ciphers/block_ciphers/des_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/des_block_cipher_test.py @@ -2,6 +2,7 @@ def test_des_block_cipher(): + import numpy as np des = DESBlockCipher() assert des.type == 'block_cipher' assert des.family_name == 'des_block_cipher' @@ -19,3 +20,4 @@ def test_des_block_cipher(): plaintext = 0x0123456789ABCDEF ciphertext = 0x85E813540F0AB405 assert des.evaluate([key, plaintext]) == ciphertext + assert des.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext \ No newline at end of file diff --git a/tests/unit/ciphers/block_ciphers/des_exact_key_length_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/des_exact_key_length_block_cipher_test.py index 6790470b..65476cb5 100644 --- a/tests/unit/ciphers/block_ciphers/des_exact_key_length_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/des_exact_key_length_block_cipher_test.py @@ -19,3 +19,4 @@ def test_des_exact_key_length_block_cipher(): plaintext = 0x0123456789ABCDEF ciphertext = 0x85E813540F0AB405 assert des_cipher.evaluate([key, plaintext]) == ciphertext + assert des_cipher.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext \ No newline at end of file diff --git a/tests/unit/ciphers/block_ciphers/fancy_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/fancy_block_cipher_test.py index 1d1d36b5..a88ce04d 100644 --- a/tests/unit/ciphers/block_ciphers/fancy_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/fancy_block_cipher_test.py @@ -23,3 +23,4 @@ def test_fancy_block_cipher(): fancy = FancyBlockCipher(number_of_rounds=1) ciphertext = 0xfedcba assert fancy.evaluate([plaintext, key]) == ciphertext + assert fancy.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/hight_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/hight_block_cipher_test.py index bef42ef5..ed7f3f6d 100644 --- a/tests/unit/ciphers/block_ciphers/hight_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/hight_block_cipher_test.py @@ -22,6 +22,7 @@ def test_hight_block_cipher(): plaintext = 0x0011223344556677 key = 0x0 assert hight.evaluate([plaintext, key], verbosity=False) == 0x1055ddee99ba66e0 + assert hight.evaluate_vectorized([plaintext, key], evaluate_api=True) == 0x1055ddee99ba66e0 hight = HightBlockCipher(block_bit_size=64, key_bit_size=128, @@ -29,6 +30,8 @@ def test_hight_block_cipher(): transformations_flag=False) key = 0x000000066770000000a0000000000001 assert hight.evaluate([plaintext, key], verbosity=False) == 0x2b8b6b285d2d0e9c + assert hight.evaluate_vectorized([plaintext, key], evaluate_api=True) == 0x2b8b6b285d2d0e9c hight = HightBlockCipher(block_bit_size=64, key_bit_size=128, number_of_rounds=32) assert hight.evaluate([plaintext, key], verbosity=False) == 0x3b25d694326c4375 + assert hight.evaluate_vectorized([plaintext, key], evaluate_api=True) == 0x3b25d694326c4375 \ No newline at end of file diff --git a/tests/unit/ciphers/block_ciphers/identity_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/identity_block_cipher_test.py index e5b40010..cc678a2c 100644 --- a/tests/unit/ciphers/block_ciphers/identity_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/identity_block_cipher_test.py @@ -20,9 +20,11 @@ def test_identity_block_cipher(): key = 0xffffffff ciphertext = 0x00000000 assert identity.evaluate([plaintext, key]) == ciphertext + assert identity.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext identity = IdentityBlockCipher(block_bit_size=32, key_bit_size=16) plaintext = 0xffffffff key = 0xffff ciphertext = 0xffffffff assert identity.evaluate([plaintext, key]) == ciphertext + assert identity.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/kasumi_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/kasumi_block_cipher_test.py index aabb8985..24e83a7b 100644 --- a/tests/unit/ciphers/block_ciphers/kasumi_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/kasumi_block_cipher_test.py @@ -6,4 +6,5 @@ def test_kasumi_block_cipher_test_vector(): key = 0x9900aabbccddeeff1122334455667788 plaintext = 0xfedcba0987654321 ciphertext = 0x514896226caa4f20 - assert kasumi.evaluate([key, plaintext]) == ciphertext \ No newline at end of file + assert kasumi.evaluate([key, plaintext]) == ciphertext + assert kasumi.evaluate_vectorized([key, plaintext], evaluate_api = True) == ciphertext \ No newline at end of file diff --git a/tests/unit/ciphers/block_ciphers/lblock_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/lblock_block_cipher_test.py index 56ff71f0..16f51cb7 100644 --- a/tests/unit/ciphers/block_ciphers/lblock_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/lblock_block_cipher_test.py @@ -11,8 +11,10 @@ def test_lblock_block_cipher(): key = 0 ciphertext = 0xC218185308E75BCD assert lblock.evaluate([plaintext, key]) == ciphertext + assert lblock.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext plaintext = 0x0123456789abcdef key = 0x0123456789abcdeffedc ciphertext = 0x4B7179D8EBEE0C26 assert lblock.evaluate([plaintext, key]) == ciphertext + assert lblock.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/lea_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/lea_block_cipher_test.py index 9d2edf22..81b2a713 100644 --- a/tests/unit/ciphers/block_ciphers/lea_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/lea_block_cipher_test.py @@ -23,8 +23,10 @@ def test_lea_block_cipher(): plaintext = 0x202122232425262728292a2b2c2d2e2f key = 0x0f1e2d3c4b5a69788796a5b4c3d2e1f0f0e1d2c3b4a59687 assert lea.evaluate([plaintext, key]) == 0x6fb95e325aad1b878cdcf5357674c6f2 + assert lea.evaluate_vectorized([plaintext, key], evaluate_api=True) == 0x6fb95e325aad1b878cdcf5357674c6f2 lea = LeaBlockCipher(block_bit_size=128, key_bit_size=256) plaintext = 0x303132333435363738393a3b3c3d3e3f key = 0x0f1e2d3c4b5a69788796a5b4c3d2e1f0f0e1d2c3b4a5968778695a4b3c2d1e0f assert lea.evaluate([plaintext, key]) == 0xd651aff647b189c13a8900ca27f9e197 + assert lea.evaluate_vectorized([plaintext, key], evaluate_api=True) == 0xd651aff647b189c13a8900ca27f9e197 diff --git a/tests/unit/ciphers/block_ciphers/lowmc_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/lowmc_block_cipher_test.py index 0b0de960..81c4f254 100644 --- a/tests/unit/ciphers/block_ciphers/lowmc_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/lowmc_block_cipher_test.py @@ -20,11 +20,13 @@ def test_lowmc_block_cipher(): plaintext = 0xABFF0000000000000000000000000000 ciphertext = 0x0E30720B9F64D5C2A7771C8C238D8F70 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext key = 0xB5DF537B000000000000000000000000 plaintext = 0xF77DB57B000000000000000000000000 ciphertext = 0x0E5961E9992153B13245AF243DD7DDC0 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext # Vectorsets for Picnic-L3-30 # Very long test @@ -33,12 +35,16 @@ def test_lowmc_block_cipher(): plaintext = 0xABFF00000000000000000000000000000000000000000000 ciphertext = 0xA85B8244344A2E1B10A17BAB043073F6BB649AE6AF659F6F assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + # # Very long test key = 0xB5DF537B0000000000000000000000000000000000000000 plaintext = 0xF77DB57B0000000000000000000000000000000000000000 ciphertext = 0x210BBC4A434B32DB1E85AE7A27FEE9E41582FAC21D035AA1 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + # Vectorsets for Picnic-L5-38 # Very long test @@ -47,12 +53,16 @@ def test_lowmc_block_cipher(): plaintext = 0xABFF000000000000000000000000000000000000000000000000000000000000 ciphertext = 0xB8F20A888A0A9EC4E495F1FB439ABDDE18C1D3D29CF20DF4B10A567AA02C7267 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + # # Very long test key = 0xF77DB57B00000000000000000000000000000000000000000000000000000000 plaintext = 0xB5DF537B00000000000000000000000000000000000000000000000000000000 ciphertext = 0xEEECCE6A584A93306DAEA07519B47AD6402C11DD942AA3166541444977A214C5 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + # Vectorsets for Picnic3-L1-4 # Note that all values need to be truncated to exact block_bit_size value @@ -62,21 +72,29 @@ def test_lowmc_block_cipher(): plaintext = 0xabff000000000000000000000000000000 >> 7 ciphertext = 0x2fd7d5425ee35e667c972f12fb153e9d80 >> 7 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0xab22425149aa612d7fff137220275b1680 >> 7 plaintext = 0x4b992353a60665bf992d035482c1d27900 >> 7 ciphertext = 0x2a4062d835c593ea19f822ad242477d280 >> 7 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0xe73af29cfc7ae53e5220d31e2e5917da80 >> 7 plaintext = 0x304ba7a8de2b5cf887f9a48ab7561bf680 >> 7 ciphertext = 0x5cd2c355328efde9f378c16123d33fb300 >> 7 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0x30f33488532d7eb8a5f8fb4f2e63ba5600 >> 7 plaintext = 0xc26a5df906158dcb6ac7891da9f49f7800 >> 7 ciphertext = 0xb43b65f7c535006cf27e86f551bd01580 >> 7 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + # # Vectorsets for Picnic3-L3-4 lowmc = LowMCBlockCipher(block_bit_size=192, key_bit_size=192, number_of_rounds=4) @@ -84,21 +102,29 @@ def test_lowmc_block_cipher(): plaintext = 0xABFF00000000000000000000000000000000000000000000 ciphertext = 0xf8f7a225de77123129107a20f5543afa7833076653ba2b29 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0x81b85dfe40f612275aa3f9199139ebaae8dff8366f2dd34e plaintext = 0xb865ccf3fcda8ddbed527dc34dd4150d4a482dcbf7e9643c ciphertext = 0x95ef9ed7c37872a7b4602a3fa9c46ebcb84254ed0e44ee9f assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0x2405978fdaad9b6d8dcdd18a0c2c0ec68b69dd0a3754fe38 plaintext = 0x33e8b4552e95ef5279497706bce01ecb4acb860141b7fc43 ciphertext = 0xddaf0f9d9edd572069a8949faea0d1fd2d91ef262b411caf assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0x569d7d822300943d9483477427e88ea227a2e3172c04bcd3 plaintext = 0xaeeb9d5b61a2a56dd598f7da26dfd78cc992e0aea3fc2e39 ciphertext = 0x869870ae6547ad0afef27793170d96bc78e040096944808f assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + # # Vectorsets for Picnic3-L5-4 # # Note that all values need to be truncated to exact block_bit_size value @@ -108,18 +134,25 @@ def test_lowmc_block_cipher(): plaintext = 0xABFF000000000000000000000000000000000000000000000000000000000000 >> 1 ciphertext = 0xD4721D846DD14DBA3A2C41501C02DA282ECAFD72DF77992F3967EFD6E8F3F356 >> 1 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0x7c20be53b6d6008149e19a34b97d9684a0914caf9f7f38b2499811369c3f53da >> 1 plaintext = 0x8863f129c0387ae5a402a49bd64927c4c65964fb8531b0d761b161b4c97b755e >> 1 ciphertext = 0x3b6e4b63cc8b08268b6781d5a629d6e03020c1c048d4684161b90ad73339126 >> 1 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0x6df9e78d0fc1b870dabe520514b959636a42304bf43a2408524506c81ea30b14 >> 1 plaintext = 0x9e5178420520b8cca529595b80c4703b2dcf2a0730643a6f412798605f052b68 >> 1 ciphertext = 0x0f19fcc8bc18869aab8e4fe81e9767d18cfe715081929f92963b4000000626f8 >> 1 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + key = 0xb071c6d4a377e551254c5dc401a3d08acb99609f418a8c2207f5122b5a17fe9a >> 1 plaintext = 0xf7616dc514fd0e1028561d098aafa54c34be728cf24a5024df17b9cc2e33fbfa >> 1 ciphertext = 0x4448c70ac3863021be232c63381687cd5defb50ba28d7b268e19727baebc679a >> 1 assert lowmc.evaluate([plaintext, key]) == ciphertext + assert lowmc.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/midori_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/midori_block_cipher_test.py index b0b20a1d..7536e86c 100644 --- a/tests/unit/ciphers/block_ciphers/midori_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/midori_block_cipher_test.py @@ -22,6 +22,7 @@ def test_midori_block_cipher(): key = 0x687ded3b3c85b3f35b1009863e2a8cbf ciphertext = 0x66bcdc6270d901cd assert midori.evaluate([plaintext, key]) == ciphertext + assert midori.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext assert midori.test_against_reference_code(2) is True midori = MidoriBlockCipher(block_bit_size=128) @@ -30,3 +31,4 @@ def test_midori_block_cipher(): ciphertext = 0x1e0ac4fddff71b4c1801b73ee4afc83d assert midori.evaluate([plaintext, key]) == ciphertext assert midori.test_against_reference_code(2) is True + assert midori.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/present_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/present_block_cipher_test.py index b1421241..c515bf40 100644 --- a/tests/unit/ciphers/block_ciphers/present_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/present_block_cipher_test.py @@ -19,6 +19,8 @@ def test_present_block_cipher(): key = 0x98edeafc899338c45fad ciphertext = 0xa1e546ae14c26565 assert present.evaluate([plaintext, key]) == ciphertext + assert present.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + assert present.test_against_reference_code(2) is True present = PresentBlockCipher(key_bit_size=128) @@ -27,3 +29,4 @@ def test_present_block_cipher(): ciphertext = 0x82f5b82cb02cd1b6 assert present.evaluate([plaintext, key]) == ciphertext assert present.test_against_reference_code(2) is True + assert present.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/qarmav2_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/qarmav2_block_cipher_test.py index 36c2dba6..90b49655 100644 --- a/tests/unit/ciphers/block_ciphers/qarmav2_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/qarmav2_block_cipher_test.py @@ -18,6 +18,7 @@ def test_qarmav2_block_cipher(): tweak = 0x7e5c3a18f6d4b2901eb852fc9630da74 ciphertext = 0x2cc660354929f2ca assert qarmav2.evaluate([key, plaintext, tweak]) == ciphertext + assert qarmav2.evaluate_vectorized([key, plaintext, tweak], evaluate_api = True) == ciphertext qarmav2 = QARMAv2BlockCipher(number_of_rounds = 9) key = 0x0123456789abcdeffedcba9876543210 @@ -25,6 +26,7 @@ def test_qarmav2_block_cipher(): tweak = 0x7e5c3a18f6d4b2901eb852fc9630da74 ciphertext = 0xd459510ab82c66fc assert qarmav2.evaluate([key, plaintext, tweak]) == ciphertext + assert qarmav2.evaluate_vectorized([key, plaintext, tweak], evaluate_api = True) == ciphertext qarmav2 = QARMAv2BlockCipher(number_of_layers = 2, number_of_rounds = 9, key_bit_size = 256, tweak_bit_size = 256) key = 0x00102030405060708090a0b0c0d0e0f00f0e0d0c0b0a09080706050403020100 @@ -32,3 +34,4 @@ def test_qarmav2_block_cipher(): tweak = 0x7e5c3a18f6d4b290e5c3a18f6d4b29071eb852fc630da741b852fc960da741eb ciphertext = 0x361262e2ecf88f03f4ea898d6a4f412f assert qarmav2.evaluate([key, plaintext, tweak]) == ciphertext + assert qarmav2.evaluate_vectorized([key, plaintext, tweak], evaluate_api = True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/qarmav2_with_mixcolumn_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/qarmav2_with_mixcolumn_block_cipher_test.py index f057b69b..e6a679c2 100644 --- a/tests/unit/ciphers/block_ciphers/qarmav2_with_mixcolumn_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/qarmav2_with_mixcolumn_block_cipher_test.py @@ -18,6 +18,7 @@ def test_qarmav2_mixcolumn_block_cipher(): tweak = 0x7e5c3a18f6d4b2901eb852fc9630da74 ciphertext = 0x2cc660354929f2ca assert qarmav2.evaluate([key, plaintext, tweak]) == ciphertext + #assert qarmav2.evaluate_vectorized([key, plaintext, tweak], evaluate_api = True) == ciphertext qarmav2 = QARMAv2MixColumnBlockCipher(number_of_rounds = 9) key = 0x0123456789abcdeffedcba9876543210 @@ -25,6 +26,7 @@ def test_qarmav2_mixcolumn_block_cipher(): tweak = 0x7e5c3a18f6d4b2901eb852fc9630da74 ciphertext = 0xd459510ab82c66fc assert qarmav2.evaluate([key, plaintext, tweak]) == ciphertext + #assert qarmav2.evaluate_vectorized([key, plaintext, tweak], evaluate_api = True) == ciphertext qarmav2 = QARMAv2MixColumnBlockCipher(number_of_layers = 2, number_of_rounds = 9, key_bit_size = 256, tweak_bit_size = 256) key = 0x00102030405060708090a0b0c0d0e0f00f0e0d0c0b0a09080706050403020100 @@ -32,3 +34,4 @@ def test_qarmav2_mixcolumn_block_cipher(): tweak = 0x7e5c3a18f6d4b290e5c3a18f6d4b29071eb852fc630da741b852fc960da741eb ciphertext = 0x361262e2ecf88f03f4ea898d6a4f412f assert qarmav2.evaluate([key, plaintext, tweak]) == ciphertext + #assert qarmav2.evaluate_vectorized([key, plaintext, tweak], evaluate_api = True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/raiden_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/raiden_block_cipher_test.py index 9cc3403a..a1d80d95 100644 --- a/tests/unit/ciphers/block_ciphers/raiden_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/raiden_block_cipher_test.py @@ -19,6 +19,8 @@ def test_raiden_block_cipher(): key = 0x1de1c3c2c65880074c32dce537b22ab3 ciphertext = 0x99bf13c039b49812 assert raiden.evaluate([plaintext, key]) == ciphertext + assert raiden.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext + assert raiden.test_against_reference_code(2) is True raiden = RaidenBlockCipher(32, 64, 32) @@ -27,3 +29,4 @@ def test_raiden_block_cipher(): ciphertext = 0x5a1674df assert raiden.evaluate([plaintext, key]) == ciphertext assert raiden.test_against_reference_code(2) is True + assert raiden.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/rc5_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/rc5_block_cipher_test.py index 330aa341..efc390dc 100644 --- a/tests/unit/ciphers/block_ciphers/rc5_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/rc5_block_cipher_test.py @@ -22,41 +22,48 @@ def test_rc5_block_cipher(): plaintext = 0x0001 ciphertext = 0x212a assert rc5.evaluate([key, plaintext]) == ciphertext + #assert rc5.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext rc5 = RC5BlockCipher(word_size=16, number_of_rounds=16, key_size=64) key = 0x0001020304050607 plaintext = 0x00010203 ciphertext = 0x23a8d72e assert rc5.evaluate([key, plaintext]) == ciphertext + #assert rc5.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext rc5 = RC5BlockCipher(word_size=32, number_of_rounds=20, key_size=128) key = 0x000102030405060708090A0B0C0D0E0F plaintext = 0x0001020304050607 ciphertext = 0x2A0EDC0E9431FF73 assert rc5.evaluate([key, plaintext]) == ciphertext + #assert rc5.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext rc5 = RC5BlockCipher(word_size=64, number_of_rounds=24, key_size=192) key = 0x000102030405060708090A0B0C0D0E0F1011121314151617 plaintext = 0x000102030405060708090A0B0C0D0E0F ciphertext = 0xA46772820EDBCE0235ABEA32AE7178DA assert rc5.evaluate([key, plaintext]) == ciphertext + #assert rc5.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext rc5 = RC5BlockCipher(word_size=128, number_of_rounds=28, key_size=256) key = 0x000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F plaintext = 0x000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F ciphertext = 0xECA5910921A4F4CFDD7AD7AD20A1FCBA068EC7A7CD752D68FE914B7FE180B440 assert rc5.evaluate([key, plaintext]) == ciphertext + #assert rc5.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext rc5 = RC5BlockCipher(word_size=24, number_of_rounds=4, key_size=1) key = 0x0 plaintext = 0x000102030405 ciphertext = 0x89CBDCC9525A assert rc5.evaluate([key, plaintext]) == ciphertext + #assert rc5.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext rc5 = RC5BlockCipher(word_size=80, number_of_rounds=4, key_size=96) key = 0x000102030405060708090A0B plaintext = 0x000102030405060708090A0B0C0D0E0F10111213 ciphertext = 0x9CB59ECBA4EA84568A4278B0E132D5FC9D5819D6 assert rc5.evaluate([key, plaintext]) == ciphertext + #assert rc5.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/scarf_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/scarf_block_cipher_test.py index 35ef4de2..6e0d5f88 100644 --- a/tests/unit/ciphers/block_ciphers/scarf_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/scarf_block_cipher_test.py @@ -14,7 +14,9 @@ def test_scarf_block_cipher(): tweak = 0x71249C3CAAB0 ciphertext = 0xBD assert cipher.evaluate([plaintext, key, tweak]) == ciphertext + assert cipher.evaluate_vectorized([plaintext, key, tweak], evaluate_api = True) == ciphertext plaintext = 0x3FF ciphertext = 0x145 - assert cipher.evaluate([plaintext, key, tweak]) == ciphertext \ No newline at end of file + assert cipher.evaluate([plaintext, key, tweak]) == ciphertext + assert cipher.evaluate_vectorized([plaintext, key, tweak], evaluate_api = True) == ciphertext \ No newline at end of file diff --git a/tests/unit/ciphers/block_ciphers/simon_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/simon_block_cipher_test.py index dc3dc010..e1a70b05 100644 --- a/tests/unit/ciphers/block_ciphers/simon_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/simon_block_cipher_test.py @@ -20,6 +20,8 @@ def test_simon_block_cipher(): ciphertext = 0xc69be9bb assert simon.evaluate([plaintext, key]) == ciphertext assert simon.test_against_reference_code(2) is True + assert simon.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext + simon = SimonBlockCipher(block_bit_size=48, key_bit_size=72) plaintext = 0x6120676e696c @@ -27,6 +29,8 @@ def test_simon_block_cipher(): ciphertext = 0xdae5ac292cac assert simon.evaluate([plaintext, key]) == ciphertext assert simon.test_against_reference_code(2) is True + assert simon.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext + simon = SimonBlockCipher(block_bit_size=48, key_bit_size=96) plaintext = 0x72696320646e @@ -34,6 +38,8 @@ def test_simon_block_cipher(): ciphertext = 0x6e06a5acf156 assert simon.evaluate([plaintext, key]) == ciphertext assert simon.test_against_reference_code(2) is True + assert simon.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext + simon = SimonBlockCipher(block_bit_size=128, key_bit_size=256) plaintext = 0x74206e69206d6f6f6d69732061207369 @@ -41,3 +47,4 @@ def test_simon_block_cipher(): ciphertext = 0x8d2b5579afc8a3a03bf72a87efe7b868 assert simon.evaluate([plaintext, key]) == ciphertext assert simon.test_against_reference_code(2) is True + assert simon.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/skinny_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/skinny_block_cipher_test.py index cf7b17ce..3aff5846 100644 --- a/tests/unit/ciphers/block_ciphers/skinny_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/skinny_block_cipher_test.py @@ -22,8 +22,10 @@ def test_skinny_block_cipher(): key = 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ciphertext = 0x4ced01d20a158953d0968f3a1ce190bc assert skinny.evaluate([plaintext, key]) == ciphertext + assert skinny.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext plaintext = 0xa3994b66ad85a3459f44e92b08f550cb key = 0xdf889548cfc7ea52d296339301797449ab588a34a47f1ab2dfe9c8293fbea9a5ab1afac2611012cd8cef952618c3ebe8 ciphertext = 0xff38d1d24c864c4352a853690fe36e5e assert skinny.evaluate([plaintext, key]) == ciphertext + assert skinny.evaluate_vectorized([plaintext, key], evaluate_api = True) == ciphertext \ No newline at end of file diff --git a/tests/unit/ciphers/block_ciphers/sparx_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/sparx_block_cipher_test.py index e263c084..5e22fca7 100644 --- a/tests/unit/ciphers/block_ciphers/sparx_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/sparx_block_cipher_test.py @@ -20,6 +20,7 @@ def test_sparx_block_cipher(): ciphertext = 0x2bbef15201f55f98 assert sparx.evaluate([plaintext, key]) == ciphertext assert sparx.test_against_reference_code(2) is True + assert sparx.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext sparx = SparxBlockCipher(block_bit_size=128) plaintext = 0x0123456789abcdeffedcba9876543210 @@ -27,6 +28,7 @@ def test_sparx_block_cipher(): ciphertext = 0x1cee75407dbf23d8e0ee1597f42852d8 assert sparx.evaluate([plaintext, key]) == ciphertext assert sparx.test_against_reference_code(2) is True + assert sparx.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext sparx = SparxBlockCipher(block_bit_size=128, key_bit_size=256) plaintext = 0x0123456789abcdeffedcba9876543210 @@ -34,3 +36,4 @@ def test_sparx_block_cipher(): ciphertext = 0x3328e63714c76ce632d15a54e4b0c820 assert sparx.evaluate([plaintext, key]) == ciphertext assert sparx.test_against_reference_code(2) is True + assert sparx.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/speck_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/speck_block_cipher_test.py index d4de01a1..6e567671 100644 --- a/tests/unit/ciphers/block_ciphers/speck_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/speck_block_cipher_test.py @@ -19,9 +19,11 @@ def test_speck_block_cipher(): key = 0x1918111009080100 ciphertext = 0xa86842f2 assert speck.evaluate([plaintext, key]) == ciphertext + assert speck.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext speck = SpeckBlockCipher(block_bit_size=64, key_bit_size=96) plaintext = 0x74614620736e6165 key = 0x131211100b0a090803020100 ciphertext = 0x9f7952ec4175946c assert speck.evaluate([plaintext, key]) == ciphertext + assert speck.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/speedy_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/speedy_block_cipher_test.py index e2e3d9ba..4e5fd1c7 100644 --- a/tests/unit/ciphers/block_ciphers/speedy_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/speedy_block_cipher_test.py @@ -17,6 +17,7 @@ def test_speedy_block_cipher(): key = 0x764c4f6254e1bff208e95862428faed01584f4207a7e8477 ciphertext = 0x01da25a93d1cfc5e4c0b74f677eb746c281a260193b7755a assert speedy.evaluate([plaintext, key]) == ciphertext + assert speedy.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext speedy = SpeedyBlockCipher(number_of_rounds=6) assert speedy.number_of_rounds == 6 @@ -26,6 +27,7 @@ def test_speedy_block_cipher(): key = 0x764c4f6254e1bff208e95862428faed01584f4207a7e8477 ciphertext = 0x88bfd3dc140f38bc53a66687f5307860560ebec41100662d assert speedy.evaluate([plaintext, key]) == ciphertext + assert speedy.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext speedy = SpeedyBlockCipher(number_of_rounds=7) assert speedy.number_of_rounds == 7 @@ -35,3 +37,4 @@ def test_speedy_block_cipher(): key = 0x764c4f6254e1bff208e95862428faed01584f4207a7e8477 ciphertext = 0xed3d0ea11c427bd32570df41c6fd66ebbf4916e760ed0943 assert speedy.evaluate([plaintext, key]) == ciphertext + assert speedy.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/tea_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/tea_block_cipher_test.py index 4d435135..a1867869 100644 --- a/tests/unit/ciphers/block_ciphers/tea_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/tea_block_cipher_test.py @@ -20,6 +20,7 @@ def test_tea_block_cipher(): ciphertext = 0x5e89b6140012c6da assert tea.evaluate([plaintext, key]) == ciphertext assert tea.test_against_reference_code(2) is True + assert tea.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext tea = TeaBlockCipher(32, 64, 32) plaintext = 0xb779ee0a @@ -27,3 +28,4 @@ def test_tea_block_cipher(): ciphertext = 0x25476362 assert tea.evaluate([plaintext, key]) == ciphertext assert tea.test_against_reference_code(2) is True + assert tea.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/threefish_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/threefish_block_cipher_test.py index 7765afe0..5ea80bc0 100644 --- a/tests/unit/ciphers/block_ciphers/threefish_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/threefish_block_cipher_test.py @@ -20,6 +20,7 @@ def test_threefish_block_cipher(): tweak = 0x0 ciphertext = 0x94EEEA8B1F2ADA84ADF103313EAE6670952419A1F4B16D53D83F13E63C9F6B11 assert threefish.evaluate([plaintext, key, tweak]) == ciphertext + assert threefish.evaluate_vectorized([plaintext, key, tweak], evaluate_api = True) == ciphertext plaintext = 0xF8F9FAFBFCFDFEFFF0F1F2F3F4F5F6F7E8E9EAEBECEDEEEFE0E1E2E3E4E5E6E7 key = 0x17161514131211101F1E1D1C1B1A191827262524232221202F2E2D2C2B2A2928 @@ -27,6 +28,7 @@ def test_threefish_block_cipher(): ciphertext = 0xDF8FEA0EFF91D0E0D50AD82EE69281C976F48D58085D869DDF975E95B5567065 assert threefish.evaluate([plaintext, key, tweak]) == ciphertext assert threefish.test_against_reference_code(2) is True + assert threefish.evaluate_vectorized([plaintext, key, tweak], evaluate_api = True) == ciphertext threefish = ThreefishBlockCipher(block_bit_size=512, key_bit_size=512) plaintext = 0x0 @@ -35,6 +37,7 @@ def test_threefish_block_cipher(): ciphertext = int('0xBC2560EFC6BBA2B1E3361F162238EB40FB8631EE0ABBD1757B9479D4C5479ED1CFF0356E58F8C27BB1B7B08430F' '0E7F7E9A380A56139ABF1BE7B6D4AA11EB47E', 16) assert threefish.evaluate([plaintext, key, tweak]) == ciphertext + assert threefish.evaluate_vectorized([plaintext, key, tweak], evaluate_api = True) == ciphertext plaintext = int('0xF8F9FAFBFCFDFEFFF0F1F2F3F4F5F6F7E8E9EAEBECEDEEEFE0E1E2E3E4E5E6E7D8D9DADBDCDDDEDFD0D1D2D3D4D5' 'D6D7C8C9CACBCCCDCECFC0C1C2C3C4C5C6C7', 16) @@ -45,3 +48,4 @@ def test_threefish_block_cipher(): '04478346201A1FEDF11AF3DAF1C5C3D672789', 16) assert threefish.evaluate([plaintext, key, tweak]) == ciphertext assert threefish.test_against_reference_code(2) is True + assert threefish.evaluate_vectorized([plaintext, key, tweak], evaluate_api = True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/twofish_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/twofish_block_cipher_test.py index 1331122a..41fd4c3f 100644 --- a/tests/unit/ciphers/block_ciphers/twofish_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/twofish_block_cipher_test.py @@ -22,15 +22,18 @@ def test_twofish_block_cipher(): plaintext = 0x90AFE91BB288544F2C32DC239B2635E6 ciphertext = 0x6CB4561C40BF0A9705931CB6D408E7FA assert cipher.evaluate([key, plaintext]) == ciphertext + assert cipher.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext two_fish = TwofishBlockCipher(key_length=128, number_of_rounds=16) key = 0x9F589F5CF6122C32B6BFEC2F2AE8C35A plaintext = 0xD491DB16E7B1C39E86CB086B789F5419 ciphertext = 0x019F9809DE1711858FAAC3A3BA20FBC3 assert two_fish.evaluate([key, plaintext]) == ciphertext + assert two_fish.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext two_fish = TwofishBlockCipher(key_length=192, number_of_rounds=16) key = 0x88B2B2706B105E36B446BB6D731A1E88EFA71F788965BD44 plaintext = 0x39DA69D6BA4997D585B6DC073CA341B2 ciphertext = 0x182B02D81497EA45F9DAACDC29193A65 assert two_fish.evaluate([key, plaintext]) == ciphertext + assert two_fish.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/block_ciphers/xtea_block_cipher_test.py b/tests/unit/ciphers/block_ciphers/xtea_block_cipher_test.py index fb0289cc..30aef74a 100644 --- a/tests/unit/ciphers/block_ciphers/xtea_block_cipher_test.py +++ b/tests/unit/ciphers/block_ciphers/xtea_block_cipher_test.py @@ -20,6 +20,7 @@ def test_xtea_block_cipher(): ciphertext = 0x91c0fec24d17fe49 assert xtea.evaluate([plaintext, key]) == ciphertext assert xtea.test_against_reference_code(2) is True + assert xtea.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext xtea = XTeaBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=32) plaintext = 0xb779ee0a @@ -27,3 +28,4 @@ def test_xtea_block_cipher(): ciphertext = 0x5be9022a assert xtea.evaluate([plaintext, key]) == ciphertext assert xtea.test_against_reference_code(2) is True + assert xtea.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/hash_functions/blake2_hash_function_test.py b/tests/unit/ciphers/hash_functions/blake2_hash_function_test.py index 78138d36..54825ea2 100644 --- a/tests/unit/ciphers/hash_functions/blake2_hash_function_test.py +++ b/tests/unit/ciphers/hash_functions/blake2_hash_function_test.py @@ -26,6 +26,7 @@ def test_blake2_hash_function(): '16b8be50ddf8e3e1f4b1722743c0857bf6c7445f1d47ae8e3060320fe0ba4dd22939b7a0', 16) assert blake2.evaluate([plaintext, state]) == output_state assert blake2.test_against_reference_code(2) is True + assert blake2.evaluate_vectorized([plaintext, state], evaluate_api=True) == output_state blake2 = Blake2HashFunction(block_bit_size=512, state_bit_size=512, word_size=32, number_of_rounds=12) plaintext = int('0x2f9a46cd9f2dadf749d0715e6d647ad5227f415a7bf1ca82f1d6ae7799980415b04f36887a6e05ee2e08c71fba4b49' @@ -36,3 +37,4 @@ def test_blake2_hash_function(): 'a9b7c79125928d55d3f8ddbeb1530c05a276', 16) assert blake2.evaluate([plaintext, state]) == output_state assert blake2.test_against_reference_code(2) is True + assert blake2.evaluate_vectorized([plaintext, state], evaluate_api=True) == output_state diff --git a/tests/unit/ciphers/hash_functions/blake_hash_function_test.py b/tests/unit/ciphers/hash_functions/blake_hash_function_test.py index eae18b59..3ad05693 100644 --- a/tests/unit/ciphers/hash_functions/blake_hash_function_test.py +++ b/tests/unit/ciphers/hash_functions/blake_hash_function_test.py @@ -24,6 +24,7 @@ def test_blake_hash_function(): 'ff618d7a1d95f0f298ad48e03e31d69d958c8', 16) assert blake.evaluate([plaintext, state]) == output_state assert blake.test_against_reference_code(2) is True + assert blake.evaluate_vectorized([plaintext, state], evaluate_api=True) == output_state blake = BlakeHashFunction(block_bit_size=1024, state_bit_size=1024, word_size=64) plaintext = int('0x0080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' @@ -37,3 +38,4 @@ def test_blake_hash_function(): 'd2feb3b276d336c6c8bc63d13e99bb3b08feef23aed8a237b480f33c7b6aea4550ab4634', 16) assert blake.evaluate([plaintext, state]) == output_state assert blake.test_against_reference_code(2) is True + assert blake.evaluate_vectorized([plaintext, state], evaluate_api=True) == output_state diff --git a/tests/unit/ciphers/hash_functions/md5_hash_function_test.py b/tests/unit/ciphers/hash_functions/md5_hash_function_test.py index 5bda00ec..a1dda826 100644 --- a/tests/unit/ciphers/hash_functions/md5_hash_function_test.py +++ b/tests/unit/ciphers/hash_functions/md5_hash_function_test.py @@ -19,28 +19,34 @@ def test_md5_hash_function(): '00000000000000000000000000000000f8', 16) ciphertext = 0x3956fba8c05053e5a27040b8ab9a7545 assert md5.evaluate([plaintext]) == ciphertext + assert md5.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x5072616e7a6f206427616371756120666120766f6c746920736768656d62692e800000000000000000000000000000' '0000000000000000000000000000000100', 16) ciphertext = 0x1a062465be03e510e6755e320664156c assert md5.evaluate([plaintext]) == ciphertext + assert md5.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x4368652074656d70692062726576692c207a696f2c207175616e646f20736f6c66656767692e800000000000000000' '0000000000000000000000000000000130', 16) ciphertext = 0xd90762a3fa2e1b39344295f56ce33098 assert md5.evaluate([plaintext]) == ciphertext + assert md5.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x5175616c636865206e6f74697a696120706176657365206d69206661207362616469676c696172652e800000000000' '0000000000000000000000000000000148', 16) ciphertext = 0xc784565cb3c0991ea04e32314599c733 assert md5.evaluate([plaintext]) == ciphertext + assert md5.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x496e207175656c2063616d706f2073692074726f76616e2066756e67686920696e206162626f6e64616e7a612e8000' '0000000000000000000000000000000168', 16) ciphertext = 0x6b0cebf5c4d3e731b56881011179725b assert md5.evaluate([plaintext]) == ciphertext + assert md5.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x5175616c636865207661676f20696f6e65207469706f207a6f6c666f2c2062726f6d6f2c20736f64696f2e80000000' '0000000000000000000000000000000158', 16) ciphertext = 0xa9be46cd1b651b325365939a2a4bc7e2 assert md5.evaluate([plaintext]) == ciphertext + assert md5.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/hash_functions/sha1_hash_function_test.py b/tests/unit/ciphers/hash_functions/sha1_hash_function_test.py index dda7718f..78fb64aa 100644 --- a/tests/unit/ciphers/hash_functions/sha1_hash_function_test.py +++ b/tests/unit/ciphers/hash_functions/sha1_hash_function_test.py @@ -19,28 +19,34 @@ def test_sha1_hash_function(): '0000000000000000000000000000000030', 16) ciphertext = 0x04f0c8e0efe316e609390a3d98e97f5acc53c199 assert sha1.evaluate([plaintext]) == ciphertext + assert sha1.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x68656c6c6f776f726c6480000000000000000000000000000000000000000000000000000000000000000000000000' '0000000000000000000000000000000050', 16) ciphertext = 0x6adfb183a4a2c94a2f92dab5ade762a47889a5a1 assert sha1.evaluate([plaintext]) == ciphertext + assert sha1.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x77657361776176657279626967616e696d616c61747468657a6f6f8000000000000000000000000000000000000000' '00000000000000000000000000000000D8', 16) ciphertext = 0x3a8a662f3e65ef354784dcb6c35f38624596d500 assert sha1.evaluate([plaintext]) == ciphertext + assert sha1.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x546865206170706c65206973206f6e20746865207461626c6580000000000000000000000000000000000000000000' '00000000000000000000000000000000c8', 16) ciphertext = 0x11d6cc738400d6028a783839c2b53d1dc4d7a5bb assert sha1.evaluate([plaintext]) == ciphertext + assert sha1.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x492077616e7420736f6d652070616e63616b6573800000000000000000000000000000000000000000000000000000' '00000000000000000000000000000000a0', 16) ciphertext = 0xa8b6079d4c7beecd288ec792f9adb81ee2287092 assert sha1.evaluate([plaintext]) == ciphertext + assert sha1.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x6C657473686F7065666F72746865626573748000000000000000000000000000000000000000000000000000000000' '0000000000000000000000000000000090', 16) ciphertext = 0x1c5fdb6b3f737e9fd8b2906a1f06d13dc21e794f assert sha1.evaluate([plaintext]) == ciphertext + assert sha1.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/hash_functions/sha2_hash_function_test.py b/tests/unit/ciphers/hash_functions/sha2_hash_function_test.py index 2a581481..c68618a9 100644 --- a/tests/unit/ciphers/hash_functions/sha2_hash_function_test.py +++ b/tests/unit/ciphers/hash_functions/sha2_hash_function_test.py @@ -19,17 +19,20 @@ def test_sha2_hash_function(): '0000000000000000000000000000000030', 16) ciphertext = 0x0d8d2647a12b0d544989a6b03603b8b3c27e2c4e0be08671745366d1a8bc4d95 assert sha2.evaluate([plaintext]) == ciphertext + assert sha2.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext = int('0x68656C6C6F776F726C6480000000000000000000000000000000000000000000000000000000000000000000000000' '0000000000000000000000000000000050', 16) ciphertext = 0x936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af assert sha2.evaluate([plaintext]) == ciphertext + assert sha2.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext sha2 = SHA2HashFunction(output_bit_size=224) plaintext = int('0x686F77617265796F75646F696E67746F64617980000000000000000000000000000000000000000000000000000000' '0000000000000000000000000000000098', 16) ciphertext = 0xc5341a30288d8e3cb4fac54943d13134790010aecd919e6784f3694f assert sha2.evaluate([plaintext]) == ciphertext + assert sha2.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext sha2 = SHA2HashFunction(output_bit_size=512, number_of_rounds=80) plaintext = int('0x7965737465726461796977656E74746F74686562656163686576656E74686F756768696C696B657468656D6F756E74' @@ -38,6 +41,7 @@ def test_sha2_hash_function(): ciphertext = int('0x2e894af7e3825b01e1d254a0ee6b186d2aebd11a6bc9a7446263357ddc1f9fea2194d9c2cdc6c5f554b428d403f30' 'a83df1c029f07c7835db52bc99735517ed1', 16) assert sha2.evaluate([plaintext]) == ciphertext + assert sha2.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext sha2 = SHA2HashFunction(output_bit_size=384, number_of_rounds=80) plaintext = int('0x697472696564736F68617264616E64676F74736F666172627574696E746865656E6469746469646E746576656E6D61' @@ -45,3 +49,4 @@ def test_sha2_hash_function(): '000000000000000000000000000000000000000000000000000000000000000198', 16) ciphertext = 0xba94bfa051856d99251101d5bb718079e163f77f240ff03b5aac0232670589c2279bfb35888ef90970d19bc0c966602a assert sha2.evaluate([plaintext]) == ciphertext + assert sha2.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/hash_functions/whirlpool_hash_function_test.py b/tests/unit/ciphers/hash_functions/whirlpool_hash_function_test.py index c2f5b9bf..2b35bfa0 100644 --- a/tests/unit/ciphers/hash_functions/whirlpool_hash_function_test.py +++ b/tests/unit/ciphers/hash_functions/whirlpool_hash_function_test.py @@ -20,26 +20,32 @@ def test_whirlpool_hash_function(): key = 0x61626380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018 ciphertext = 0x4e2448a4c6f486bb16b6562c73b4020bf3043e3a731bce721ae1b303d97e6d4c7181eebdb6c57e277d0e34957114cbd6c797fc9d95d8b582d225292076d4eef5 assert whirlpool.evaluate([key]) == ciphertext + assert whirlpool.evaluate_vectorized([key], evaluate_api=True) == ciphertext key = 0x61800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008 ciphertext = 0x8aca2602792aec6f11a67206531fb7d7f0dff59413145e6973c45001d0087b42d11bc645413aeff63a42391a39145a591a92200d560195e53b478584fdae231a assert whirlpool.evaluate([key]) == ciphertext + assert whirlpool.evaluate_vectorized([key], evaluate_api=True) == ciphertext key = 0x6d657373616765206469676573748000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070 ciphertext = 0x378c84a4126e2dc6e56dcc7458377aac838d00032230f53ce1f5700c0ffb4d3b8421557659ef55c106b4b52ac5a4aaa692ed920052838f3362e86dbd37a8903e assert whirlpool.evaluate([key]) == ciphertext + assert whirlpool.evaluate_vectorized([key], evaluate_api=True) == ciphertext key = 0x6162636465666768696a6b6c6d6e6f707172737475767778797a80000000000000000000000000000000000000000000000000000000000000000000000000d0 ciphertext = 0xf1d754662636ffe92c82ebb9212a484a8d38631ead4238f5442ee13b8054e41b08bf2a9251c30b6a0b8aae86177ab4a6f68f673e7207865d5d9819a3dba4eb3b assert whirlpool.evaluate([key]) == ciphertext + assert whirlpool.evaluate_vectorized([key], evaluate_api=True) == ciphertext #The following test vector values have been hand made key = 0x68656c6c6f686f77617265796f758000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070 ciphertext = 0x2600a67308114432afa3193d3ae9c4ef0babb2442527dc639d09bea96cae5ece16ffddf15cb81bf2830ecbab906b4518d12c88fbd8a3ff769f61c9ac29350d38 assert whirlpool.evaluate([key]) == ciphertext + assert whirlpool.evaluate_vectorized([key], evaluate_api=True) == ciphertext whirlpool = WhirlpoolHashFunction() key = 0x6162636462636465636465666465666765666768666768696768696a68696a6b8000000000000000000000000000000000000000000000000000000000000000 ciphertext = 0x7738e1b541a036ea458d50f80fa01c447288ce97d1a0dcf01695ffd6e71d092533be309f012a5909729114595f086e760718afe365bc09deb6afa180bcec2a98 - assert whirlpool.evaluate([key]) == ciphertext \ No newline at end of file + assert whirlpool.evaluate([key]) == ciphertext + assert whirlpool.evaluate_vectorized([key], evaluate_api=True) == ciphertext \ No newline at end of file diff --git a/tests/unit/ciphers/permutations/ascon_permutation_test.py b/tests/unit/ciphers/permutations/ascon_permutation_test.py index 2edd0436..55d90f5b 100644 --- a/tests/unit/ciphers/permutations/ascon_permutation_test.py +++ b/tests/unit/ciphers/permutations/ascon_permutation_test.py @@ -31,3 +31,4 @@ def test_ascon_permutation(): plaintext = 0x78ea7ae5cfebb1089b9bfb8513b560f76937f83e03d11a503fe53f36f2c1178c045d648e4def12c9 ciphertext = 0x0e87fa7d4b40022e94f14f2525499af530a1d1621866701c4b419cf3ae4c9962b11ce0a087175b71 assert ascon.evaluate([plaintext]) == ciphertext + assert ascon.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/ascon_sbox_sigma_no_matrix_permutation_test.py b/tests/unit/ciphers/permutations/ascon_sbox_sigma_no_matrix_permutation_test.py index 7ced810c..f04989a8 100644 --- a/tests/unit/ciphers/permutations/ascon_sbox_sigma_no_matrix_permutation_test.py +++ b/tests/unit/ciphers/permutations/ascon_sbox_sigma_no_matrix_permutation_test.py @@ -31,3 +31,4 @@ def test_ascon_sbox_sigma_no_matrix_permutation(): plaintext = 0x78ea7ae5cfebb1089b9bfb8513b560f76937f83e03d11a503fe53f36f2c1178c045d648e4def12c9 ciphertext = 0x0e87fa7d4b40022e94f14f2525499af530a1d1621866701c4b419cf3ae4c9962b11ce0a087175b71 assert ascon.evaluate([plaintext]) == ciphertext + assert ascon.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/ascon_sbox_sigma_permutation_test.py b/tests/unit/ciphers/permutations/ascon_sbox_sigma_permutation_test.py index 3af3c578..1dbdb24d 100644 --- a/tests/unit/ciphers/permutations/ascon_sbox_sigma_permutation_test.py +++ b/tests/unit/ciphers/permutations/ascon_sbox_sigma_permutation_test.py @@ -22,3 +22,4 @@ def test_ascon_sbox_sigma_permutation(): plaintext = 0x78ea7ae5cfebb1089b9bfb8513b560f76937f83e03d11a503fe53f36f2c1178c045d648e4def12c9 ciphertext = 0x0e87fa7d4b40022e94f14f2525499af530a1d1621866701c4b419cf3ae4c9962b11ce0a087175b71 assert ascon.evaluate([plaintext]) == ciphertext + assert ascon.evaluate_vectorized([plaintext], evaluate_api = True) == ciphertext diff --git a/tests/unit/ciphers/permutations/chacha_permutation_test.py b/tests/unit/ciphers/permutations/chacha_permutation_test.py index 73605842..a13a19c0 100644 --- a/tests/unit/ciphers/permutations/chacha_permutation_test.py +++ b/tests/unit/ciphers/permutations/chacha_permutation_test.py @@ -50,3 +50,4 @@ def test_toy_chacha_permutation(): plaintext = int("0x" + "".join(state), 16) output = int('0xe023858e713feb86a730656ac909f76a', 16) assert chacha.evaluate([plaintext], verbosity=False) == output + assert chacha.evaluate_vectorized([plaintext], evaluate_api=True) == output diff --git a/tests/unit/ciphers/permutations/gaston_permutation_test.py b/tests/unit/ciphers/permutations/gaston_permutation_test.py index be8cf79c..c187e93f 100644 --- a/tests/unit/ciphers/permutations/gaston_permutation_test.py +++ b/tests/unit/ciphers/permutations/gaston_permutation_test.py @@ -18,3 +18,4 @@ def test_gaston_permutation(): plaintext = 0xFFFFFFFFFFFFFFFF0123456789ABCDEFFEDCBA9876543210AAAAAAAAAAAAAAAA0101010101010101 ciphertext = 0x3117D51B14937067338F17F773C13F79DFB86E0868D252AB0D461D35EB863DE708BCE3E354C7231A assert gaston.evaluate([plaintext]) == ciphertext + assert gaston.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/gaston_sbox_permutation_test.py b/tests/unit/ciphers/permutations/gaston_sbox_permutation_test.py index 4d9459bd..db11c131 100644 --- a/tests/unit/ciphers/permutations/gaston_sbox_permutation_test.py +++ b/tests/unit/ciphers/permutations/gaston_sbox_permutation_test.py @@ -20,3 +20,4 @@ def test_gaston_sbox_permutation(): plaintext = 0xFFFFFFFFFFFFFFFF0123456789ABCDEFFEDCBA9876543210AAAAAAAAAAAAAAAA0101010101010101 ciphertext = 0x3117D51B14937067338F17F773C13F79DFB86E0868D252AB0D461D35EB863DE708BCE3E354C7231A assert gaston.evaluate([plaintext]) == ciphertext + assert gaston.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/gift_permutation_test.py b/tests/unit/ciphers/permutations/gift_permutation_test.py index 9e59cd76..0603da3a 100644 --- a/tests/unit/ciphers/permutations/gift_permutation_test.py +++ b/tests/unit/ciphers/permutations/gift_permutation_test.py @@ -19,6 +19,7 @@ def test_gift_permutation(): plaintext = 0x000102030405060708090A0B0C0D0E0F ciphertext = 0xA94AF7F9BA181DF9B2B00EB7DBFA93DF assert gift.evaluate([plaintext, key]) == ciphertext + assert gift.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext key1 = 0x000102030405060708090A0B0C0D0E0F plaintext1 = 0x000102030405060708090A0B0C0D0E0F diff --git a/tests/unit/ciphers/permutations/gift_sbox_permutation_test.py b/tests/unit/ciphers/permutations/gift_sbox_permutation_test.py index c1ffe427..76758982 100644 --- a/tests/unit/ciphers/permutations/gift_sbox_permutation_test.py +++ b/tests/unit/ciphers/permutations/gift_sbox_permutation_test.py @@ -19,6 +19,7 @@ def test_gift_sbox_permutation(): plaintext = 0x000102030405060708090A0B0C0D0E0F ciphertext = 0xA94AF7F9BA181DF9B2B00EB7DBFA93DF assert gift.evaluate([plaintext, key]) == ciphertext + assert gift.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext key1 = 0x000102030405060708090A0B0C0D0E0F plaintext1 = 0x000102030405060708090A0B0C0D0E0F diff --git a/tests/unit/ciphers/permutations/gimli_permutation_test.py b/tests/unit/ciphers/permutations/gimli_permutation_test.py index cdee72d2..85200c6e 100644 --- a/tests/unit/ciphers/permutations/gimli_permutation_test.py +++ b/tests/unit/ciphers/permutations/gimli_permutation_test.py @@ -35,3 +35,4 @@ def test_gimli_permutation(): plaintext = 0x1af105601000043540540354354350550000000100000001000000010000000100000001000000010000000100000001 ciphertext = 0x100e4c1d8774953fb2b3d6a5f2e1af9b3f0f3fb5e32cba39245f231bf280918e62126d745cfb6a0221cf7adeb3dee484 assert gimli.evaluate([plaintext]) == ciphertext + assert gimli.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/gimli_sbox_permutation_test.py b/tests/unit/ciphers/permutations/gimli_sbox_permutation_test.py index 0227293b..a21e32cc 100644 --- a/tests/unit/ciphers/permutations/gimli_sbox_permutation_test.py +++ b/tests/unit/ciphers/permutations/gimli_sbox_permutation_test.py @@ -35,3 +35,4 @@ def test_gimli_sbox_permutation(): plaintext = 0x1af105601000043540540354354350550000000100000001000000010000000100000001000000010000000100000001 ciphertext = 0x100e4c1d8774953fb2b3d6a5f2e1af9b3f0f3fb5e32cba39245f231bf280918e62126d745cfb6a0221cf7adeb3dee484 assert gimli.evaluate([plaintext]) == ciphertext + assert gimli.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/grain_core_permutation_test.py b/tests/unit/ciphers/permutations/grain_core_permutation_test.py index 2ee8c45f..9e43e316 100644 --- a/tests/unit/ciphers/permutations/grain_core_permutation_test.py +++ b/tests/unit/ciphers/permutations/grain_core_permutation_test.py @@ -18,4 +18,6 @@ def test_grain_core_permutation(): state = 0xffffffffffffffff state_output = 0xf0f3fa8999f72655ecfb assert grain_core.evaluate([state]) == state_output + assert grain_core.evaluate_vectorized([state], evaluate_api=True) == state_output + assert grain_core.test_against_reference_code(2) is True diff --git a/tests/unit/ciphers/permutations/keccak_invertible_permutation_test.py b/tests/unit/ciphers/permutations/keccak_invertible_permutation_test.py index 834de415..7f5d9972 100644 --- a/tests/unit/ciphers/permutations/keccak_invertible_permutation_test.py +++ b/tests/unit/ciphers/permutations/keccak_invertible_permutation_test.py @@ -47,3 +47,4 @@ def test_keccak_invertible_permutation(): '01000000000000000000000000000010000000000000000000000000000000100000000040000000000000000000000' '0000000004', 16) assert keccak.evaluate([plaintext]) == ciphertext + assert keccak.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/keccak_permutation_test.py b/tests/unit/ciphers/permutations/keccak_permutation_test.py index 1f5f8e2a..64242e21 100644 --- a/tests/unit/ciphers/permutations/keccak_permutation_test.py +++ b/tests/unit/ciphers/permutations/keccak_permutation_test.py @@ -41,3 +41,4 @@ def test_keccak_permutation(): '46611b87c5a554fd00ecb8c3ee88a1ccf32c8940c7922ae3a26141841f924a2c509e416f53526e70465c275f644e97f' '30a13beaf1ff7b5ceca249', 16) assert keccak.evaluate([plaintext]) == ciphertext + assert keccak.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/keccak_sbox_permutation_test.py b/tests/unit/ciphers/permutations/keccak_sbox_permutation_test.py index cc467529..f143ed92 100644 --- a/tests/unit/ciphers/permutations/keccak_sbox_permutation_test.py +++ b/tests/unit/ciphers/permutations/keccak_sbox_permutation_test.py @@ -41,3 +41,4 @@ def test_keccak_sbox_permutation(): '46611b87c5a554fd00ecb8c3ee88a1ccf32c8940c7922ae3a26141841f924a2c509e416f53526e70465c275f644e97f' '30a13beaf1ff7b5ceca249', 16) assert keccak.evaluate([plaintext]) == ciphertext + assert keccak.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/photon_permutation_test.py b/tests/unit/ciphers/permutations/photon_permutation_test.py index 2cb7e38c..d01e15c8 100644 --- a/tests/unit/ciphers/permutations/photon_permutation_test.py +++ b/tests/unit/ciphers/permutations/photon_permutation_test.py @@ -20,6 +20,7 @@ def test_photon_permutation(): plaintext = 0x0000000000000000000000000000000000000000000000000000000000000000 ciphertext = 0x01165907DBDA659C2AF1704BBA93E74BA05C1AB38B8D458260DFF04C062D72E5 assert photon.evaluate([plaintext]) == ciphertext + assert photon.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext plaintext1 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ciphertext1 = 0x429AC9438631CB7F5FDFB81A3F86AD1ED88A9541F2EAEF882959367C8E197294 diff --git a/tests/unit/ciphers/permutations/salsa_permutation_test.py b/tests/unit/ciphers/permutations/salsa_permutation_test.py index 1ecf74dc..ec2814d5 100644 --- a/tests/unit/ciphers/permutations/salsa_permutation_test.py +++ b/tests/unit/ciphers/permutations/salsa_permutation_test.py @@ -33,3 +33,4 @@ def test_salsa_permutation(): output = int('0xccaaf67223d960f79153e63acd9a60d050440492f07cad19ae344aa0df4cfdfcca531c298e7943dbac1680cdd503' 'ca00a74b2ad6bc331c5c1dda24c7ee928277', 16) assert salsa.evaluate([plaintext], verbosity=False) == output + assert salsa.evaluate_vectorized([plaintext], evaluate_api=True) == output diff --git a/tests/unit/ciphers/permutations/sparkle_permutation_test.py b/tests/unit/ciphers/permutations/sparkle_permutation_test.py index 37e0d7a7..2298fed4 100644 --- a/tests/unit/ciphers/permutations/sparkle_permutation_test.py +++ b/tests/unit/ciphers/permutations/sparkle_permutation_test.py @@ -24,3 +24,4 @@ def test_sparkle_permutation(): ciphertext = int('0x00627afd81ed6af7f594e39485b6e59222ba1ed9d8b60cc900ed77965ec691586bf138b79bc1cefcbb71c93113432' '6842374b2f159938253a2349c67f524daf0', 16) assert sparkle.evaluate([plaintext]) == ciphertext + assert sparkle.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/spongent_pi_fsr_permutation_test.py b/tests/unit/ciphers/permutations/spongent_pi_fsr_permutation_test.py index 3856e48a..86863124 100644 --- a/tests/unit/ciphers/permutations/spongent_pi_fsr_permutation_test.py +++ b/tests/unit/ciphers/permutations/spongent_pi_fsr_permutation_test.py @@ -25,3 +25,4 @@ def test_spongent_pi_fsr_permutation(): plaintext = 0x0123456789abcdef0123456789abcdef0123456789ab ciphertext = 0x04adf4b51546dc10694325ff73b1352f141d8023da08 assert spongentpi.evaluate([plaintext]) == ciphertext + #assert spongentpi.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/spongent_pi_permutation_test.py b/tests/unit/ciphers/permutations/spongent_pi_permutation_test.py index d41b123e..a8ba9a0b 100644 --- a/tests/unit/ciphers/permutations/spongent_pi_permutation_test.py +++ b/tests/unit/ciphers/permutations/spongent_pi_permutation_test.py @@ -25,3 +25,4 @@ def test_spongent_pi_permutation(): plaintext = 0x0123456789abcdef0123456789abcdef0123456789ab ciphertext = 0x04adf4b51546dc10694325ff73b1352f141d8023da08 assert spongentpi.evaluate([plaintext]) == ciphertext + assert spongentpi.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/spongent_pi_precomputation_permutation_test.py b/tests/unit/ciphers/permutations/spongent_pi_precomputation_permutation_test.py index 490c05fe..1dbeb37c 100644 --- a/tests/unit/ciphers/permutations/spongent_pi_precomputation_permutation_test.py +++ b/tests/unit/ciphers/permutations/spongent_pi_precomputation_permutation_test.py @@ -25,3 +25,4 @@ def test_spongent_pi_precomputation_permutation(): plaintext = 0x0123456789abcdef0123456789abcdef0123456789ab ciphertext = 0x04adf4b51546dc10694325ff73b1352f141d8023da08 assert spongentpi.evaluate([plaintext]) == ciphertext + assert spongentpi.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/tinyjambu_32bits_word_permutation_test.py b/tests/unit/ciphers/permutations/tinyjambu_32bits_word_permutation_test.py index 5afc5d94..4f617212 100644 --- a/tests/unit/ciphers/permutations/tinyjambu_32bits_word_permutation_test.py +++ b/tests/unit/ciphers/permutations/tinyjambu_32bits_word_permutation_test.py @@ -14,6 +14,7 @@ def test_tinyjambu_32bits_word_permutation(): plaintext = 0x00000000000000000000000000000000 ciphertext = 0xc07a21053c7ca049e687585d161fbad7 assert tinyjambu.evaluate([plaintext, key]) == ciphertext + assert tinyjambu.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext key1 = 0x12345678123456781234567812345678 plaintext1 = 0x00000000000000000000000000000000 diff --git a/tests/unit/ciphers/permutations/tinyjambu_fsr_32bits_word_permutation_test.py b/tests/unit/ciphers/permutations/tinyjambu_fsr_32bits_word_permutation_test.py index 2dedd502..f37833e6 100644 --- a/tests/unit/ciphers/permutations/tinyjambu_fsr_32bits_word_permutation_test.py +++ b/tests/unit/ciphers/permutations/tinyjambu_fsr_32bits_word_permutation_test.py @@ -14,6 +14,7 @@ def test_tinyjambu_fsr_32bits_word_permutation(): plaintext = 0x00000000000000000000000000000000 ciphertext = 0xc07a21053c7ca049e687585d161fbad7 assert tinyjambu.evaluate([plaintext, key]) == ciphertext + #assert tinyjambu.evaluate_vectorized([plaintext, key], evaluate_api=True) == ciphertext key1 = 0x12345678123456781234567812345678 plaintext1 = 0x00000000000000000000000000000000 diff --git a/tests/unit/ciphers/permutations/tinyjambu_permutation_test.py b/tests/unit/ciphers/permutations/tinyjambu_permutation_test.py index 1626eaf6..7223aad4 100644 --- a/tests/unit/ciphers/permutations/tinyjambu_permutation_test.py +++ b/tests/unit/ciphers/permutations/tinyjambu_permutation_test.py @@ -19,6 +19,7 @@ def test_tinyjambu_permutation(): plaintext = 0x00000000000000000000000000000000 ciphertext = 0xc07a21053c7ca049e687585d161fbad7 assert tinyjambu.evaluate([key, plaintext]) == ciphertext + assert tinyjambu.evaluate_vectorized([key, plaintext], evaluate_api=True) == ciphertext key1 = 0x12345678123456781234567812345678 plaintext1 = 0x00000000000000000000000000000000 diff --git a/tests/unit/ciphers/permutations/xoodoo_invertible_permutation_test.py b/tests/unit/ciphers/permutations/xoodoo_invertible_permutation_test.py index 89572344..d303423a 100644 --- a/tests/unit/ciphers/permutations/xoodoo_invertible_permutation_test.py +++ b/tests/unit/ciphers/permutations/xoodoo_invertible_permutation_test.py @@ -23,3 +23,4 @@ def test_xoodoo_invertible_permutation(): plaintext = 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ciphertext = 0x8ad1373a05425c035bfc32401109245109e890a183e9f075929b003c79f22441b0bc1a7e93626968389900d2a8027958 assert xoodoo_invertible_permutation.evaluate([plaintext]) == ciphertext + assert xoodoo_invertible_permutation.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/xoodoo_permutation_test.py b/tests/unit/ciphers/permutations/xoodoo_permutation_test.py index f07da56c..d9646ae3 100644 --- a/tests/unit/ciphers/permutations/xoodoo_permutation_test.py +++ b/tests/unit/ciphers/permutations/xoodoo_permutation_test.py @@ -23,3 +23,4 @@ def test_xoodoo_permutation(): plaintext = 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ciphertext = 0x8ad1373a05425c035bfc32401109245109e890a183e9f075929b003c79f22441b0bc1a7e93626968389900d2a8027958 assert xoodoo_permutation.evaluate([plaintext]) == ciphertext + assert xoodoo_permutation.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/ciphers/permutations/xoodoo_sbox_permutation_test.py b/tests/unit/ciphers/permutations/xoodoo_sbox_permutation_test.py index ef1c246b..103875e9 100644 --- a/tests/unit/ciphers/permutations/xoodoo_sbox_permutation_test.py +++ b/tests/unit/ciphers/permutations/xoodoo_sbox_permutation_test.py @@ -23,3 +23,4 @@ def test_xoodoo_sbox_permutation(): plaintext = 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ciphertext = 0x8ad1373a05425c035bfc32401109245109e890a183e9f075929b003c79f22441b0bc1a7e93626968389900d2a8027958 assert xoodoo_permutation_sbox.evaluate([plaintext]) == ciphertext + assert xoodoo_permutation_sbox.evaluate_vectorized([plaintext], evaluate_api=True) == ciphertext diff --git a/tests/unit/components/multi_input_non_linear_logical_operator_component_test.py b/tests/unit/components/multi_input_non_linear_logical_operator_component_test.py index 39c72738..849644cb 100644 --- a/tests/unit/components/multi_input_non_linear_logical_operator_component_test.py +++ b/tests/unit/components/multi_input_non_linear_logical_operator_component_test.py @@ -1,5 +1,7 @@ from claasp.cipher_modules.models.cp.cp_model import CpModel from claasp.cipher_modules.models.milp.milp_model import MilpModel +from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel +from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel from claasp.ciphers.block_ciphers.fancy_block_cipher import FancyBlockCipher from claasp.ciphers.block_ciphers.simon_block_cipher import SimonBlockCipher @@ -45,7 +47,7 @@ def test_cp_xor_differential_propagation_constraints(): def test_milp_xor_differential_propagation_constraints(): simon = SimonBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpModel(simon) + milp = MilpXorDifferentialModel(simon) milp.init_model_in_sage_milp_class() and_component = simon.get_component_from_id("and_0_4") variables, constraints = and_component.milp_xor_differential_propagation_constraints(milp) @@ -57,14 +59,14 @@ def test_milp_xor_differential_propagation_constraints(): assert str(constraints[0]) == "0 <= -1*x_32 + x_48" assert str(constraints[1]) == "0 <= -1*x_33 + x_49" - assert str(constraints[-1]) == f"x_64 == 10*x_48 + 10*x_49 + 10*x_50 + 10*x_51 + 10*x_52 + 10*x_53 + 10*x_54 + " \ - f"10*x_55 + 10*x_56 + 10*x_57 + 10*x_58 + 10*x_59 + 10*x_60 + 10*x_61 + " \ - f"10*x_62 + 10*x_63" + assert str(constraints[-1]) == f"x_64 == 100*x_48 + 100*x_49 + 100*x_50 + 100*x_51 + 100*x_52 + 100*x_53 + 100*x_54 + " \ + f"100*x_55 + 100*x_56 + 100*x_57 + 100*x_58 + 100*x_59 + 100*x_60 + 100*x_61 + " \ + f"100*x_62 + 100*x_63" def test_milp_xor_linear_mask_propagation_constraints(): simon = SimonBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpModel(simon) + milp = MilpXorLinearModel(simon) milp.init_model_in_sage_milp_class() and_component = simon.get_component_from_id("and_0_4") variables, constraints = and_component.milp_xor_linear_mask_propagation_constraints(milp) @@ -79,7 +81,7 @@ def test_milp_xor_linear_mask_propagation_constraints(): assert str(constraints[-3]) == "0 <= -1*x_15 + x_47" assert str(constraints[-2]) == "x_48 == x_32 + x_33 + x_34 + x_35 + x_36 + x_37 + x_38 + x_39 + x_40 + x_41 + " \ "x_42 + x_43 + x_44 + x_45 + x_46 + x_47" - assert str(constraints[-1]) == "x_49 == 10*x_48" + assert str(constraints[-1]) == "x_49 == 100*x_48" def test_sat_constraints(): diff --git a/tests/unit/components/or_component_test.py b/tests/unit/components/or_component_test.py index 204a3530..8b3d468e 100644 --- a/tests/unit/components/or_component_test.py +++ b/tests/unit/components/or_component_test.py @@ -35,11 +35,11 @@ def test_cp_xor_linear_mask_propagation_constraints(): cp = CpModel(gift) declarations, constraints = or_component.cp_xor_linear_mask_propagation_constraints(cp) - assert declarations == ['array[0..31] of var int: p_or_39_6;', 'array[0..63] of var 0..1:or_39_6_i;', + assert declarations == ['array[0..31] of var 0..3200: p_or_39_6;', 'array[0..63] of var 0..1:or_39_6_i;', 'array[0..31] of var 0..1:or_39_6_o;'] - assert constraints[0] == 'constraint table(or_39_6_i[0]++or_39_6_i[32]++or_39_6_o[0]++p_or_39_6[0],and2inputs_LAT);' - assert constraints[-2] == 'constraint table(or_39_6_i[31]++or_39_6_i[63]++or_39_6_o[31]++p_or_39_6[31],' \ + assert constraints[0] == 'constraint table([or_39_6_i[0]]++[or_39_6_i[32]]++[or_39_6_o[0]]++[p_or_39_6[0]],and2inputs_LAT);' + assert constraints[-2] == 'constraint table([or_39_6_i[31]]++[or_39_6_i[63]]++[or_39_6_o[31]]++[p_or_39_6[31]],' \ 'and2inputs_LAT);' assert constraints[-1] == 'constraint p[0] = sum(p_or_39_6);' diff --git a/tests/unit/components/sbox_component_test.py b/tests/unit/components/sbox_component_test.py index 786fd08e..1e6f0672 100644 --- a/tests/unit/components/sbox_component_test.py +++ b/tests/unit/components/sbox_component_test.py @@ -1,4 +1,6 @@ from claasp.cipher_modules.models.cp.cp_model import CpModel +from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel +from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel from claasp.cipher_modules.models.smt.smt_model import SmtModel from claasp.cipher_modules.models.sat.sat_model import SatModel from claasp.cipher_modules.models.milp.milp_model import MilpModel @@ -165,9 +167,9 @@ def test_milp_large_xor_linear_probability_constraints(): assert str(constraints[1]) == "1 - x_0 - x_1 - x_2 - x_3 - x_4 - x_5 - x_6 - x_7 <= 8 - 8*x_16" assert str(constraints[-2]) == "x_17 + x_18 + x_19 + x_20 + x_21 + x_22 + x_23 + x_24 + x_25 + x_26 + x_27 + " \ "x_28 + x_29 + x_30 + x_31 + x_32 == x_16" - assert str(constraints[-1]) == "x_33 == 60*x_17 + 50*x_18 + 44*x_19 + 40*x_20 + 37*x_21 + 34*x_22 + 32*x_23 + " \ - "30*x_24 + 30*x_25 + 32*x_26 + 34*x_27 + 37*x_28 + 40*x_29 + 44*x_30 + 50*x_31 + " \ - "60*x_32" + assert str(constraints[-1]) == "x_33 == 600*x_17 + 500*x_18 + 442*x_19 + 400*x_20 + 368*x_21 + 342*x_22 + " \ + "319*x_23 + 300*x_24 + 300*x_25 + 319*x_26 + 342*x_27 + 368*x_28 + 400*x_29 + " \ + "442*x_30 + 500*x_31 + 600*x_32" def test_milp_small_xor_differential_probability_constraints(): @@ -187,7 +189,7 @@ def test_milp_small_xor_differential_probability_constraints(): assert str(constraints[0]) == "x_8 <= x_0 + x_1 + x_2 + x_3" assert str(constraints[1]) == X_0_X_8 assert str(constraints[-2]) == "x_9 + x_10 == x_8" - assert str(constraints[-1]) == "x_11 == 30*x_9 + 20*x_10" + assert str(constraints[-1]) == "x_11 == 300*x_9 + 200*x_10" def test_milp_small_xor_linear_probability_constraints(): @@ -207,12 +209,12 @@ def test_milp_small_xor_linear_probability_constraints(): assert str(constraints[0]) == "x_8 <= x_4 + x_5 + x_6 + x_7" assert str(constraints[1]) == X_0_X_8 assert str(constraints[-2]) == "x_9 + x_10 + x_11 + x_12 == x_8" - assert str(constraints[-1]) == "x_13 == 20*x_9 + 10*x_10 + 10*x_11 + 20*x_12" + assert str(constraints[-1]) == "x_13 == 200*x_9 + 100*x_10 + 100*x_11 + 200*x_12" def test_milp_xor_differential_propagation_constraints(): present = PresentBlockCipher(number_of_rounds=6) - milp = MilpModel(present) + milp = MilpXorDifferentialModel(present) milp.init_model_in_sage_milp_class() sbox_component = present.component_from(0, 1) variables, constraints = sbox_component.milp_xor_differential_propagation_constraints(milp) @@ -225,12 +227,12 @@ def test_milp_xor_differential_propagation_constraints(): assert str(constraints[0]) == "x_0 + x_1 + x_2 + x_3 <= 4*x_8" assert str(constraints[1]) == "1 - x_0 - x_1 - x_2 - x_3 <= 4 - 4*x_8" assert str(constraints[-2]) == "x_9 + x_10 == x_8" - assert str(constraints[-1]) == "x_11 == 30*x_9 + 20*x_10" + assert str(constraints[-1]) == "x_11 == 300*x_9 + 200*x_10" def test_milp_xor_linear_mask_propagation_constraints(): present = PresentBlockCipher(number_of_rounds=6) - milp = MilpModel(present) + milp = MilpXorLinearModel(present) milp.init_model_in_sage_milp_class() sbox_component = present.component_from(0, 1) variables, constraints = sbox_component.milp_xor_linear_mask_propagation_constraints(milp) @@ -240,10 +242,10 @@ def test_milp_xor_linear_mask_propagation_constraints(): assert str(variables[-2]) == "('x[sbox_0_1_2_o]', x_6)" assert str(variables[-1]) == "('x[sbox_0_1_3_o]', x_7)" - assert str(constraints[0]) == "x_8 <= x_4 + x_5 + x_6 + x_7" - assert str(constraints[1]) == X_0_X_8 + assert str(constraints[0]) == "x_0 + x_1 + x_2 + x_3 <= 4*x_8" + assert str(constraints[1]) == "1 - x_0 - x_1 - x_2 - x_3 <= 4 - 4*x_8" assert str(constraints[-2]) == "x_9 + x_10 + x_11 + x_12 == x_8" - assert str(constraints[-1]) == "x_13 == 20*x_9 + 10*x_10 + 10*x_11 + 20*x_12" + assert str(constraints[-1]) == "x_13 == 200*x_9 + 100*x_10 + 100*x_11 + 200*x_12" def test_sat_constraints():