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():