Skip to content

Commit

Permalink
Merge pull request #233 from Crypto-TII/fix/fix_vectorized_evaluation…
Browse files Browse the repository at this point in the history
…_bug_for_des

Fix/fix vectorized evaluation bug for des
  • Loading branch information
peacker authored May 20, 2024
2 parents 9534624 + 5c8e0d5 commit f5d8f55
Show file tree
Hide file tree
Showing 78 changed files with 845 additions and 327 deletions.
17 changes: 6 additions & 11 deletions claasp/cipher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -1710,10 +1712,3 @@ def update_input_id_links_from_component_id(self, component_id, new_input_id_lin
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)

def all_sboxes_are_standard(self):
for comp in self.get_all_components():
if 'sbox' in comp.id:
if (comp.input_bit_size != comp.output_bit_size) or (comp.input_bit_size % 2 != 0) or (
comp.output_bit_size % 2 != 0):
return False
return True
4 changes: 2 additions & 2 deletions claasp/cipher_modules/avalanche_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand All @@ -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] = \
Expand Down
97 changes: 74 additions & 23 deletions claasp/cipher_modules/code_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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)

Expand All @@ -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)]
Expand Down Expand Up @@ -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':
Expand All @@ -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()
Expand Down
30 changes: 11 additions & 19 deletions claasp/cipher_modules/evaluator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# ****************************************************************************
# Copyright 2023 Technology Innovation Institute
#
Expand All @@ -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__)

Expand Down Expand Up @@ -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) or not cipher.all_sboxes_are_standard():
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,
Expand Down
27 changes: 21 additions & 6 deletions claasp/cipher_modules/generic_functions_vectorized_bit.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ****************************************************************************

import inspect

import numpy as np

Expand Down Expand Up @@ -76,7 +77,6 @@ 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


Expand Down Expand Up @@ -121,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:
Expand All @@ -153,6 +161,7 @@ def print_component_info(input, output, component_type):
print(output.transpose())



def bit_vector_CONCAT(input):
"""
Concatenates binary values
Expand Down Expand Up @@ -434,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)
Expand Down
Loading

0 comments on commit f5d8f55

Please sign in to comment.