From 8f34f55f9c97302a436e1197de0c01757386ebfb Mon Sep 17 00:00:00 2001 From: Sharwan Tiwari Date: Tue, 30 Jan 2024 13:01:33 +0400 Subject: [PATCH 1/7] added fsr component analysis --- .../component_analysis_tests.py | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/claasp/cipher_modules/component_analysis_tests.py b/claasp/cipher_modules/component_analysis_tests.py index b459d135..5a02d9cf 100644 --- a/claasp/cipher_modules/component_analysis_tests.py +++ b/claasp/cipher_modules/component_analysis_tests.py @@ -280,6 +280,7 @@ def branch_number(component, type, format): elif component.type == "mix_column": return min(calculate_weights_for_mix_column(component, format, type)) + def instantiate_matrix_over_correct_field(matrix, polynomial_as_int, word_size, input_bit_size, output_bit_size): """ sage: from claasp.ciphers.block_ciphers.midori_block_cipher import MidoriBlockCipher @@ -319,6 +320,7 @@ def instantiate_matrix_over_correct_field(matrix, polynomial_as_int, word_size, return final_mtr, F + def is_mds(component): """ A matrix is MDS if and only if all the minors (determinants of square submatrices) are non-zero @@ -364,6 +366,7 @@ def is_mds(component): return False return True + def field_element_matrix_to_integer_matrix(matrix): """ Converts a matrix of field elements to the corresponding integer matrix representation @@ -400,6 +403,7 @@ def field_element_matrix_to_integer_matrix(matrix): return Matrix(matrix.nrows(), matrix.ncols(), int_matrix) + def get_inverse_matrix_in_integer_representation(component): """ Returns the inverse matrix in its integer representation @@ -436,6 +440,7 @@ def get_inverse_matrix_in_integer_representation(component): component.input_bit_size, component.output_bit_size) return field_element_matrix_to_integer_matrix(matrix.inverse()) + def has_maximal_branch_number(component): """ INPUT: @@ -479,6 +484,7 @@ def has_maximal_branch_number(component): if component.type == MIX_COLUMN: return branch_number(component, 'linear', 'word') == (output_word_size + 1) + def calculate_weights_for_mix_column(component, format, type): if format == 'word': description = component.description @@ -569,6 +575,8 @@ def select_properties_function(boolean_polynomial_ring, operation): return word_operation_properties(operation, boolean_polynomial_ring) if (component.type == WORD_OPERATION) and (component.description[0] == "MODADD"): return word_operation_properties(operation, boolean_polynomial_ring) + if component.type == 'fsr': + return fsr_properties(operation) if component.type == WORD_OPERATION: print(f"TODO : {component.description[0]}") @@ -632,17 +640,21 @@ def get_all_operations(cipher): collect_component_operations(component, tmp_cipher_operations) for operation in list(tmp_cipher_operations.keys()): - if operation not in [LINEAR_LAYER, MIX_COLUMN]: + if operation not in [LINEAR_LAYER, MIX_COLUMN, 'fsr']: tmp_cipher_operations[operation]["distinguisher"] = \ list(set(tmp_cipher_operations[operation]["distinguisher"])) + if operation == 'fsr': + tmp_list = [] + for item in tmp_cipher_operations[operation]["distinguisher"]: + if item not in tmp_list: + tmp_list.append(item) + tmp_cipher_operations[operation]["distinguisher"] = tmp_list tmp_cipher_operations[operation]["types"] = \ [[] for _ in range(len(tmp_cipher_operations[operation]["distinguisher"]))] collect_components_with_the_same_operation(operation, tmp_cipher_operations) - cipher_operations = {} for operation in list(tmp_cipher_operations.keys()): add_attributes_to_operation(cipher_operations, operation, tmp_cipher_operations) - return cipher_operations @@ -771,6 +783,48 @@ def order_of_linear_component(component): return 0 +def fsr_properties(operation): + component = operation[0] + component_dict = {"type": component.type, "input_bit_size": component.input_bit_size, + "output_bit_size": component.output_bit_size, "description": component.description, + "number_of_occurrences": operation[1], "component_id_list": operation[2]} + + desc = component.description + registers_len = [] + registers_type = [] + registers_feedback_relation_deg = [] + for r in desc[0]: + registers_len.append(r[0]) + d = 0 + for term in r[1]: + if d < len(term): + d = len(term) + registers_feedback_relation_deg.append(d) + if d > 1: + registers_type.append('non-linear') + else: + registers_type.append('linear') + component_dict['number_of_registers'] = len(registers_len) + component_dict['length_of_registers'] = registers_len + component_dict['type_of_registers'] = registers_type + component_dict['degree_of_feedback_relation_of_registers'] = registers_feedback_relation_deg + + R = GF(2)['x'] + lfsrs_primitive = [] + exp = 0 + for index, r in enumerate(desc[0]): + exp = exp + registers_len[index] + if registers_type[index] == 'linear': + f = R(1) + for term in r[1]: + f = f + R.gen() ** (exp - term[0]) + print(f) + lfsrs_primitive.append(f.is_primitive()) + + component_dict['linear_registers_feedback_polynomial_primitive'] = lfsrs_primitive + return component_dict + + def sbox_properties(operation): """ Return a dictionary containing some properties of Sbox component. @@ -1050,4 +1104,4 @@ def remove_components_with_strings_as_values(results_without_xor): str_in_list.append(isinstance(results_without_xor[i]["properties"][result_property]["value"], str)) if True not in str_in_list: results.append(results_without_xor[i]) - return results \ No newline at end of file + return results From 2ec00df4328902fde12dca1b0390b4427228b427 Mon Sep 17 00:00:00 2001 From: Sharwan Tiwari Date: Sun, 4 Feb 2024 17:49:02 +0400 Subject: [PATCH 2/7] added primitive testing for the LFSR feedback polynomial --- .../component_analysis_tests.py | 69 ++++++++++++++----- .../sigma_toy_word_stream_cipher.py | 0 2 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 claasp/ciphers/stream_ciphers/sigma_toy_word_stream_cipher.py diff --git a/claasp/cipher_modules/component_analysis_tests.py b/claasp/cipher_modules/component_analysis_tests.py index 5a02d9cf..84ec4f9a 100644 --- a/claasp/cipher_modules/component_analysis_tests.py +++ b/claasp/cipher_modules/component_analysis_tests.py @@ -785,43 +785,76 @@ def order_of_linear_component(component): def fsr_properties(operation): component = operation[0] + fsr_word_size = component.description[1] component_dict = {"type": component.type, "input_bit_size": component.input_bit_size, - "output_bit_size": component.output_bit_size, "description": component.description, - "number_of_occurrences": operation[1], "component_id_list": operation[2]} + "output_bit_size": component.output_bit_size, "fsr_word_size": fsr_word_size, + "description": component.description, "number_of_occurrences": operation[1], + "component_id_list": operation[2]} desc = component.description registers_len = [] registers_type = [] registers_feedback_relation_deg = [] + lin_flag = False for r in desc[0]: registers_len.append(r[0]) d = 0 - for term in r[1]: - if d < len(term): - d = len(term) + if fsr_word_size == 1: + for term in r[1]: + if d < len(term): # case for binary register + d = len(term) + else: + for term in r[1]: + if d < len(term[1]): # case for non-binary register + d = len(term[1]) registers_feedback_relation_deg.append(d) if d > 1: registers_type.append('non-linear') else: registers_type.append('linear') + lin_flag = True + component_dict['number_of_registers'] = len(registers_len) component_dict['length_of_registers'] = registers_len component_dict['type_of_registers'] = registers_type component_dict['degree_of_feedback_relation_of_registers'] = registers_feedback_relation_deg - R = GF(2)['x'] - lfsrs_primitive = [] - exp = 0 - for index, r in enumerate(desc[0]): - exp = exp + registers_len[index] - if registers_type[index] == 'linear': - f = R(1) - for term in r[1]: - f = f + R.gen() ** (exp - term[0]) - print(f) - lfsrs_primitive.append(f.is_primitive()) - - component_dict['linear_registers_feedback_polynomial_primitive'] = lfsrs_primitive + if lin_flag: + lfsrs_primitive = [] + if fsr_word_size == 1: + exp = 0 + R = GF(2)['x'] + for index, r in enumerate(desc[0]): + exp = exp + registers_len[index] + if registers_type[index] == 'linear': + f = R(1) + for term in r[1]: + f = f + R.gen() ** (exp - term[0]) + print(f) + lfsrs_primitive.append(f.is_primitive()) + del R + else: + exp = 0 + R = GF(2 ** fsr_word_size)['x'] + x = R.gens() + a = R.construction()[1].gen() + for index, r in enumerate(desc[0]): + exp = exp + registers_len[index] + if registers_type[index] == 'linear': + p = R(1) + for term in r[1]: + m = 0 + coef = "{0:b}".format(term[0]) + for i in range(len(coef)): + if coef[i] == '1': m = m + pow(a, len(coef) - 1 - i) + m = m * x[0] ** (exp - term[1][0]) + p += m + print(p) + lfsrs_primitive.append(p.is_primitive()) + breakpoint() + del R + + component_dict['linear_registers_feedback_polynomial_primitive'] = lfsrs_primitive return component_dict diff --git a/claasp/ciphers/stream_ciphers/sigma_toy_word_stream_cipher.py b/claasp/ciphers/stream_ciphers/sigma_toy_word_stream_cipher.py new file mode 100644 index 00000000..e69de29b From de408b43b93bc257d6e8a4e7aa31288e13711873 Mon Sep 17 00:00:00 2001 From: Sharwan Tiwari Date: Tue, 6 Feb 2024 13:37:53 +0400 Subject: [PATCH 3/7] added primitive testing of the connection polynomial of word based lfsr and tests for the fsr component in "component_analysis_tests_test" --- .../component_analysis_tests.py | 135 +++++++++++------- .../sigma_toy_word_stream_cipher.py | 0 .../component_analysis_tests_test.py | 27 +++- 3 files changed, 108 insertions(+), 54 deletions(-) delete mode 100644 claasp/ciphers/stream_ciphers/sigma_toy_word_stream_cipher.py diff --git a/claasp/cipher_modules/component_analysis_tests.py b/claasp/cipher_modules/component_analysis_tests.py index 84ec4f9a..999b6e69 100644 --- a/claasp/cipher_modules/component_analysis_tests.py +++ b/claasp/cipher_modules/component_analysis_tests.py @@ -784,77 +784,106 @@ def order_of_linear_component(component): def fsr_properties(operation): + """ + Return a dictionary containing some properties of fsr component. + + INPUT: + + - ``operation`` -- **list**; a list containing: + + * a component with the operation under study + * number of occurrences of the operation + * list of ids of all the components with the same underlying operation + + EXAMPLES:: + + sage: from claasp.cipher_modules.component_analysis_tests import fsr_properties + sage: from claasp.components.fsr_component import FSR + sage: fsr_component = FSR(0,0, ["input"],[[0,1,2,3]],4,[[[4, [[1,[0]],[3,[1]],[2,[2]]]]],4]) + sage: operation= [fsr_component, 1, ['fsr_0_0']] + sage: dictionary = fsr_properties(operation) + sage: dictionary['fsr_word_size'] == 4 + True + sage: dictionary['lfsr_connection_polynomials'] == ['x^4 + (z4 + 1)*x^3 + z4*x^2 + 1'] + True + + sage: from claasp.ciphers.stream_ciphers.bluetooth_stream_cipher_e0 import BluetoothStreamCipherE0 + sage: from claasp.cipher_modules.component_analysis_tests import component_analysis_tests + sage: e0 = BluetoothStreamCipherE0(keystream_bit_len=2) + sage: dictionary = e0.component_analysis_tests() + sage: assert dictionary[8]["number_of_registers"] == 4 + sage: dictionary[8]["lfsr_connection_polynomials"][0] == 'x^25 + x^20 + x^12 + x^8 + 1' # first lfsr + True + sage: dictionary[8]['lfsr_polynomials_are_primitive'] == [True, True, True, True] + True + + sage: from claasp.ciphers.stream_ciphers.trivium_stream_cipher import TriviumStreamCipher + sage: triv = TriviumStreamCipher(keystream_bit_len=1) + sage: dictionary = triv.component_analysis_tests() + sage: dictionary[0]["type_of_registers"] == ['non-linear', 'non-linear', 'non-linear'] + True + """ component = operation[0] fsr_word_size = component.description[1] - component_dict = {"type": component.type, "input_bit_size": component.input_bit_size, - "output_bit_size": component.output_bit_size, "fsr_word_size": fsr_word_size, - "description": component.description, "number_of_occurrences": operation[1], - "component_id_list": operation[2]} + component_dict = { + "type": component.type, + "input_bit_size": component.input_bit_size, + "output_bit_size": component.output_bit_size, + "fsr_word_size": fsr_word_size, + "description": component.description, + "number_of_occurrences": operation[1], + "component_id_list": operation[2] + } desc = component.description registers_len = [] registers_type = [] registers_feedback_relation_deg = [] + lfsr_connection_polynomials = [] lin_flag = False + for r in desc[0]: registers_len.append(r[0]) - d = 0 - if fsr_word_size == 1: - for term in r[1]: - if d < len(term): # case for binary register - d = len(term) - else: - for term in r[1]: - if d < len(term[1]): # case for non-binary register - d = len(term[1]) + d = max(len(term) if fsr_word_size == 1 else len(term[1]) for term in r[1]) registers_feedback_relation_deg.append(d) - if d > 1: - registers_type.append('non-linear') - else: - registers_type.append('linear') - lin_flag = True + reg_type = 'non-linear' if d > 1 else 'linear' + registers_type.append(reg_type) + lin_flag = lin_flag or (reg_type == 'linear') - component_dict['number_of_registers'] = len(registers_len) - component_dict['length_of_registers'] = registers_len - component_dict['type_of_registers'] = registers_type - component_dict['degree_of_feedback_relation_of_registers'] = registers_feedback_relation_deg + component_dict.update({ + 'number_of_registers': len(registers_len), + 'length_of_registers': registers_len, + 'type_of_registers': registers_type, + 'degree_of_feedback_relation_of_registers': registers_feedback_relation_deg + }) if lin_flag: lfsrs_primitive = [] - if fsr_word_size == 1: - exp = 0 - R = GF(2)['x'] - for index, r in enumerate(desc[0]): - exp = exp + registers_len[index] - if registers_type[index] == 'linear': - f = R(1) - for term in r[1]: - f = f + R.gen() ** (exp - term[0]) - print(f) - lfsrs_primitive.append(f.is_primitive()) - del R - else: - exp = 0 - R = GF(2 ** fsr_word_size)['x'] - x = R.gens() - a = R.construction()[1].gen() - for index, r in enumerate(desc[0]): - exp = exp + registers_len[index] - if registers_type[index] == 'linear': - p = R(1) - for term in r[1]: + exp = 0 + R = GF(2)['x'] if fsr_word_size == 1 else GF(2 ** fsr_word_size)['x'] + x = R.gens() + a = R.construction()[1].gen() + + for index, r in enumerate(desc[0]): + exp = exp + registers_len[index] + if registers_type[index] == 'linear': + p = R(1) + for term in r[1]: + if fsr_word_size == 1: + p = p + x[0] ** (exp - term[0]) + else: # case: word based LFSR m = 0 - coef = "{0:b}".format(term[0]) - for i in range(len(coef)): - if coef[i] == '1': m = m + pow(a, len(coef) - 1 - i) + cf = "{0:b}".format(term[0]) + for i in range(len(cf)): + if cf[i] == '1': m = m + pow(a, len(cf) - 1 - i) m = m * x[0] ** (exp - term[1][0]) p += m - print(p) - lfsrs_primitive.append(p.is_primitive()) - breakpoint() - del R - - component_dict['linear_registers_feedback_polynomial_primitive'] = lfsrs_primitive + lfsr_connection_polynomials.append(str(p)) + lfsrs_primitive.append(p.is_primitive()) + component_dict.update({ + "lfsr_connection_polynomials": lfsr_connection_polynomials, + "lfsr_polynomials_are_primitive": lfsrs_primitive + }) return component_dict diff --git a/claasp/ciphers/stream_ciphers/sigma_toy_word_stream_cipher.py b/claasp/ciphers/stream_ciphers/sigma_toy_word_stream_cipher.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/cipher_modules/component_analysis_tests_test.py b/tests/unit/cipher_modules/component_analysis_tests_test.py index 079102e4..5ca0ee46 100644 --- a/tests/unit/cipher_modules/component_analysis_tests_test.py +++ b/tests/unit/cipher_modules/component_analysis_tests_test.py @@ -1,7 +1,32 @@ from claasp.ciphers.block_ciphers.fancy_block_cipher import FancyBlockCipher from claasp.cipher_modules.component_analysis_tests import generate_boolean_polynomial_ring_from_cipher +from claasp.components.fsr_component import FSR +from claasp.cipher_modules.component_analysis_tests import fsr_properties +from claasp.ciphers.stream_ciphers.bluetooth_stream_cipher_e0 import BluetoothStreamCipherE0 +from claasp.ciphers.stream_ciphers.trivium_stream_cipher import TriviumStreamCipher def test_generate_boolean_polynomial_ring_from_cipher(): fancy = FancyBlockCipher(number_of_rounds=3) - generate_boolean_polynomial_ring_from_cipher(fancy) \ No newline at end of file + generate_boolean_polynomial_ring_from_cipher(fancy) + + +def test_fsr_properties(): + fsr_component = FSR(0, 0, ["input"], [[0, 1, 2, 3]], 4, [[[4, [[1, [0]], [3, [1]], [2, [2]]]]], 4]) + operation = [fsr_component, 1, ['fsr_0_0']] + dictionary = fsr_properties(operation) + assert dictionary['fsr_word_size'] == 4 + assert dictionary['lfsr_connection_polynomials'] == ['x^4 + (z4 + 1)*x^3 + z4*x^2 + 1'] + + e0 = BluetoothStreamCipherE0(keystream_bit_len=2) + dictionary = e0.component_analysis_tests() + assert dictionary[8]["number_of_registers"] == 4 + assert dictionary[8]["lfsr_connection_polynomials"][0] == 'x^25 + x^20 + x^12 + x^8 + 1' + assert dictionary[8]["lfsr_connection_polynomials"][1] == 'x^31 + x^24 + x^16 + x^12 + 1' + assert dictionary[8]["lfsr_connection_polynomials"][2] == 'x^33 + x^28 + x^24 + x^4 + 1' + assert dictionary[8]["lfsr_connection_polynomials"][3] == 'x^39 + x^36 + x^28 + x^4 + 1' + assert dictionary[8]['lfsr_polynomials_are_primitive'] == [True, True, True, True] + + triv = TriviumStreamCipher(keystream_bit_len=1) + dictionary = triv.component_analysis_tests() + assert dictionary[0]["type_of_registers"] == ['non-linear', 'non-linear', 'non-linear'] From 459898a6d98d3e203d849b3d82ce3ba99ab79bd6 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 8 Feb 2024 16:35:16 +0400 Subject: [PATCH 4/7] fix/gohr_resnet input size bug --- claasp/cipher.py | 7 ++++--- claasp/cipher_modules/neural_network_tests.py | 8 +++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/claasp/cipher.py b/claasp/cipher.py index 6f1753e9..ac261989 100644 --- a/claasp/cipher.py +++ b/claasp/cipher.py @@ -1344,7 +1344,7 @@ def data_generator(nr, samples): testing_samples, num_epochs=number_of_epochs) def run_autond_pipeline(self, difference_positions=None, optimizer_samples=10 ** 4, optimizer_generations=50, - training_samples=10 ** 7, testing_samples=10 ** 6, number_of_epochs=40, verbose=False): + training_samples=10 ** 7, testing_samples=10 ** 6, number_of_epochs=40, verbose=False, neural_net = 'dbitnet'): """ Runs the AutoND pipeline ([BGHR2023]): - Find an input difference for the inputs set to True in difference_positions using an optimizer @@ -1365,6 +1365,7 @@ def run_autond_pipeline(self, difference_positions=None, optimizer_samples=10 ** - ``testing_samples`` -- **integer**; (default: `10**6`) number samples used for testing - ``number_of_epochs`` -- **integer**; (default: `40`) number of training epochs - ``verbose`` -- **boolean**; (default: `False`) verbosity of the optimizer + - ``neural_net`` -- **string**; (default: `dbitnet`) the neural network architecture to use; supports 'dbitnet' and 'gohr_resnet' EXAMPLES:: @@ -1395,9 +1396,9 @@ def data_generator(nr, samples): verbose=verbose) input_difference = int_difference_to_input_differences(diff[-1], difference_positions, self.inputs_bit_size) input_size = self.output_bit_size * 2 - neural_network = get_neural_network('dbitnet', input_size = input_size) + neural_network = get_neural_network(neural_net, input_size = input_size) nr = max(1, highest_round-1) - print(f'Training DBitNet on input difference {[hex(x) for x in input_difference]}, from round {nr-1}...') + print(f'Training {neural_net} on input difference {[hex(x) for x in input_difference]} ({self.inputs}), from round {nr}...') return neural_staged_training(self, data_generator, nr, neural_network, training_samples, testing_samples, number_of_epochs) diff --git a/claasp/cipher_modules/neural_network_tests.py b/claasp/cipher_modules/neural_network_tests.py index c3a50abc..69ea3447 100644 --- a/claasp/cipher_modules/neural_network_tests.py +++ b/claasp/cipher_modules/neural_network_tests.py @@ -294,7 +294,7 @@ def get_neural_network(network_name, input_size, word_size = None, depth = 1): if network_name == 'gohr_resnet': if word_size is None or word_size == 0: print("Word size not specified for ", network_name, ", defaulting to ciphertext size...") - word_size = cipher.output_bit_size + word_size = input_size//2 neural_network = make_resnet(word_size = word_size, input_size = input_size, depth = depth) elif network_name == 'dbitnet': neural_network = make_dbitnet(input_size = input_size) @@ -306,14 +306,12 @@ def make_checkpoint(datei): res = ModelCheckpoint(datei, monitor='val_loss', save_best_only=True) return res - def train_neural_distinguisher(cipher, data_generator, starting_round, neural_network, training_samples=10 ** 7, - testing_samples=10 ** 6, num_epochs=1): + testing_samples=10 ** 6, num_epochs=1, batch_size = 5000): acc = 1 - bs = 5000 x, y = data_generator(samples=training_samples, nr=starting_round) x_eval, y_eval = data_generator(samples=testing_samples, nr=starting_round) - h = neural_network.fit(x, y, epochs=int(num_epochs), batch_size=bs, validation_data=(x_eval, y_eval)) + h = neural_network.fit(x, y, epochs=int(num_epochs), batch_size=batch_size, validation_data=(x_eval, y_eval)) acc = np.max(h.history["val_acc"]) print(f'Validation accuracy at {starting_round} rounds :{acc}') return acc From be929eb3246c5a3c1c0d19f10d16c73367391d8f Mon Sep 17 00:00:00 2001 From: David Date: Mon, 12 Feb 2024 19:10:05 +0400 Subject: [PATCH 5/7] . --- claasp/cipher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/claasp/cipher.py b/claasp/cipher.py index ac261989..63c43d56 100644 --- a/claasp/cipher.py +++ b/claasp/cipher.py @@ -1397,7 +1397,8 @@ def data_generator(nr, samples): input_difference = int_difference_to_input_differences(diff[-1], difference_positions, self.inputs_bit_size) input_size = self.output_bit_size * 2 neural_network = get_neural_network(neural_net, input_size = input_size) - nr = max(1, highest_round-1) + #nr = max(1, highest_round-2) + nr = 1 print(f'Training {neural_net} on input difference {[hex(x) for x in input_difference]} ({self.inputs}), from round {nr}...') return neural_staged_training(self, data_generator, nr, neural_network, training_samples, testing_samples, number_of_epochs) From 5cf7a446eed18817c3b157188d945421ffe65e78 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 12 Feb 2024 19:17:02 +0400 Subject: [PATCH 6/7] . --- claasp/cipher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/claasp/cipher.py b/claasp/cipher.py index 63c43d56..ac261989 100644 --- a/claasp/cipher.py +++ b/claasp/cipher.py @@ -1397,8 +1397,7 @@ def data_generator(nr, samples): input_difference = int_difference_to_input_differences(diff[-1], difference_positions, self.inputs_bit_size) input_size = self.output_bit_size * 2 neural_network = get_neural_network(neural_net, input_size = input_size) - #nr = max(1, highest_round-2) - nr = 1 + nr = max(1, highest_round-1) print(f'Training {neural_net} on input difference {[hex(x) for x in input_difference]} ({self.inputs}), from round {nr}...') return neural_staged_training(self, data_generator, nr, neural_network, training_samples, testing_samples, number_of_epochs) From 4457c792da3b5df16c2314919a5326471ce6a332 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 14 Feb 2024 17:32:13 +0400 Subject: [PATCH 7/7] Added distinguisher and history saving --- claasp/cipher.py | 9 +++++---- claasp/cipher_modules/neural_network_tests.py | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/claasp/cipher.py b/claasp/cipher.py index ac261989..0e9d0caa 100644 --- a/claasp/cipher.py +++ b/claasp/cipher.py @@ -1344,7 +1344,7 @@ def data_generator(nr, samples): testing_samples, num_epochs=number_of_epochs) def run_autond_pipeline(self, difference_positions=None, optimizer_samples=10 ** 4, optimizer_generations=50, - training_samples=10 ** 7, testing_samples=10 ** 6, number_of_epochs=40, verbose=False, neural_net = 'dbitnet'): + training_samples=10 ** 7, testing_samples=10 ** 6, number_of_epochs=40, verbose=False, neural_net = 'dbitnet', save_prefix=None): """ Runs the AutoND pipeline ([BGHR2023]): - Find an input difference for the inputs set to True in difference_positions using an optimizer @@ -1397,10 +1397,11 @@ def data_generator(nr, samples): input_difference = int_difference_to_input_differences(diff[-1], difference_positions, self.inputs_bit_size) input_size = self.output_bit_size * 2 neural_network = get_neural_network(neural_net, input_size = input_size) - nr = max(1, highest_round-1) + nr = max(1, highest_round-3) print(f'Training {neural_net} on input difference {[hex(x) for x in input_difference]} ({self.inputs}), from round {nr}...') - return neural_staged_training(self, data_generator, nr, neural_network, training_samples, - testing_samples, number_of_epochs) + return neural_staged_training(self, lambda nr, samples: get_differential_dataset(self, input_difference, number_of_rounds=nr, + samples=samples), nr, neural_network, training_samples, + testing_samples, number_of_epochs, save_prefix) def generate_bit_based_c_code(self, intermediate_output=False, verbosity=False): diff --git a/claasp/cipher_modules/neural_network_tests.py b/claasp/cipher_modules/neural_network_tests.py index 69ea3447..e6294441 100644 --- a/claasp/cipher_modules/neural_network_tests.py +++ b/claasp/cipher_modules/neural_network_tests.py @@ -280,12 +280,12 @@ def get_differential_dataset(cipher, input_differences, number_of_rounds, sample inputs_1.append(inputs_0[-1] ^ integer_to_np(input_differences[i], cipher.inputs_bit_size[i])) inputs_1[-1][:, y == 0] ^= np.frombuffer(urandom(num_rand_samples * cipher.inputs_bit_size[i] // 8), dtype=np.uint8).reshape(-1, num_rand_samples) - C0 = np.unpackbits( cipher.evaluate_vectorized(inputs_0, intermediate_outputs=True)['round_output'][number_of_rounds - 1], axis=1) C1 = np.unpackbits( cipher.evaluate_vectorized(inputs_1, intermediate_outputs=True)['round_output'][number_of_rounds - 1], axis=1) x = np.hstack([C0, C1]) + print("Generating differential dataset for input difference ", input_differences) return x, y @@ -307,18 +307,27 @@ def make_checkpoint(datei): return res def train_neural_distinguisher(cipher, data_generator, starting_round, neural_network, training_samples=10 ** 7, - testing_samples=10 ** 6, num_epochs=1, batch_size = 5000): + testing_samples=10 ** 6, num_epochs=1, batch_size = 5000, save_prefix=None): acc = 1 x, y = data_generator(samples=training_samples, nr=starting_round) x_eval, y_eval = data_generator(samples=testing_samples, nr=starting_round) - h = neural_network.fit(x, y, epochs=int(num_epochs), batch_size=batch_size, validation_data=(x_eval, y_eval)) + print("In train, save prefix is ", save_prefix, flush=True) + if save_prefix is None: + h = neural_network.fit(x, y, epochs=int(num_epochs), batch_size=batch_size, validation_data=(x_eval, y_eval)) + else: + filename = save_prefix + "_" + str(starting_round) + print("In train, filename is ", filename, flush=True) + check = make_checkpoint(filename+'.h5'); + print("In train, check is ", check, flush=True) + h = neural_network.fit(x, y, epochs=int(num_epochs), batch_size=batch_size, validation_data=(x_eval, y_eval), callbacks = [check]) + np.save(filename + '.npy', h.history['val_acc']) acc = np.max(h.history["val_acc"]) print(f'Validation accuracy at {starting_round} rounds :{acc}') return acc def neural_staged_training(cipher, data_generator, starting_round, neural_network=None, training_samples=10 ** 7, - testing_samples=10 ** 6, num_epochs=1): + testing_samples=10 ** 6, num_epochs=1, save_prefix = None): acc = 1 nr = starting_round # threshold at 10 sigma @@ -326,7 +335,7 @@ def neural_staged_training(cipher, data_generator, starting_round, neural_networ accuracies = {} while acc >= threshold and nr < cipher.number_of_rounds: acc = train_neural_distinguisher(cipher, data_generator, nr, neural_network, training_samples, testing_samples, - num_epochs) + num_epochs, save_prefix = save_prefix) accuracies[nr] = acc nr += 1 return accuracies