Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/cipher partial inversion #222

Closed
wants to merge 9 commits into from
23 changes: 13 additions & 10 deletions claasp/cipher_modules/algebraic_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]}}

Expand All @@ -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]}}

Expand All @@ -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))
Expand Down
24 changes: 13 additions & 11 deletions claasp/cipher_modules/inverse_cipher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
112 changes: 77 additions & 35 deletions claasp/cipher_modules/models/algebraic/algebraic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`.

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

Expand Down
47 changes: 38 additions & 9 deletions claasp/cipher_modules/models/cp/cp_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -481,6 +492,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):
"""
Expand Down
Loading
Loading