Skip to content

Commit

Permalink
Merge branch 'develop' into feat/add-cp-solvers-dictionary
Browse files Browse the repository at this point in the history
  • Loading branch information
SimoPez committed Apr 16, 2024
2 parents 037aa74 + e8bdc21 commit 9da9b8c
Show file tree
Hide file tree
Showing 27 changed files with 308 additions and 146 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/run-benchmark-tests.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Run benchmark tests
name: run-benchmark-tests
on:
pull_request_target:
pull_request:
types: [ opened, synchronize, reopened, edited ]
branches:
- main
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/run-pytest-and-sonarcloud-scan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
- '**'
pull_request_target:
pull_request:
types: [opened, synchronize, reopened, edited]
branches:
- develop
Expand Down Expand Up @@ -47,11 +47,10 @@ jobs:
path: /home/runner/_work/claasp/coverage.xml

run-code-coverage:
if: ${{ !github.event.repository.fork }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
persist-credentials: false
fetch-depth: 0
Expand All @@ -73,4 +72,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
needs: run-pytest
needs: run-pytest
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.3.0
v2.4.0
10 changes: 6 additions & 4 deletions claasp/cipher_modules/algebraic_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,23 @@ class AlgebraicTests:

def __init__(self, cipher):
self._cipher = cipher
self._algebraic_model = AlgebraicModel(cipher)

def algebraic_tests(self, timeout_in_seconds=60):
from sage.structure.sequence import Sequence
nvars_up_to_round = []

npolynomials_up_to_round = []
nmonomials_up_to_round = []
max_deg_of_equations_up_to_round = []
tests_up_to_round = []

F = []

algebraic_model = AlgebraicModel(self._cipher)
constant_vars = {}
for round_number in range(self._cipher.number_of_rounds):
F += algebraic_model.polynomial_system_at_round(round_number)
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)
Fseq = Sequence(F)
nvars_up_to_round.append(Fseq.nvariables())
npolynomials_up_to_round.append(len(Fseq))
Expand Down
46 changes: 39 additions & 7 deletions claasp/cipher_modules/models/algebraic/algebraic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,34 +159,38 @@ def polynomial_system(self):
sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel
sage: fancy = FancyBlockCipher(number_of_rounds=1)
sage: AlgebraicModel(fancy).polynomial_system()
Polynomial Sequence with 252 Polynomials in 168 Variables
Polynomial Sequence with 228 Polynomials in 144 Variables
sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher
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 304 Polynomials in 368 Variables
Polynomial Sequence with 288 Polynomials in 352 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 206 Polynomials in 136 Variables
Polynomial Sequence with 198 Polynomials in 128 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 368 Polynomials in 464 Variables
Polynomial Sequence with 352 Polynomials in 448 Variables
"""
polynomials = []
constant_vars = {}
for r in range(self._cipher.number_of_rounds):
polynomials += self.polynomial_system_at_round(r)
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)

return Sequence(polynomials)

def polynomial_system_at_round(self, r):
def polynomial_system_at_round(self, r, fun_call_flag=False):
"""
Return a polynomial system at round `r`.
Expand All @@ -200,7 +204,7 @@ def polynomial_system_at_round(self, r):
sage: from claasp.cipher_modules.models.algebraic.algebraic_model import AlgebraicModel
sage: fancy = FancyBlockCipher(number_of_rounds=1)
sage: AlgebraicModel(fancy).polynomial_system_at_round(0)
Polynomial Sequence with 252 Polynomials in 168 Variables
Polynomial Sequence with 228 Polynomials in 144 Variables
"""
if not 0 <= r < self._cipher.number_of_rounds:
raise ValueError(f"r must be in the range 0 <= r < {self._cipher.number_of_rounds}")
Expand All @@ -221,6 +225,12 @@ def polynomial_system_at_round(self, r):
raise ValueError(f"polynomial generation of {operation} operation is not supported at present")

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)

return Sequence(polynomials)

def _apply_connection_variable_mapping(self, polys, r):
Expand Down Expand Up @@ -256,6 +266,28 @@ 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):

constant_vars = {}
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)
polys = [p for p in polys if p != 0]
return polys

def ring(self):
"""
Return the polynomial ring for the system of equations.
Expand Down
72 changes: 50 additions & 22 deletions claasp/cipher_modules/models/milp/milp_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@

from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException

from claasp.cipher_modules.models.milp.utils.config import SOLVER_DEFAULT, get_external_milp_solver_configuration
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.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

Expand Down Expand Up @@ -256,24 +257,30 @@ def init_model_in_sage_milp_class(self, solver_name=SOLVER_DEFAULT):
sage: milp._model
Mixed Integer Program (no objective, 0 variables, 0 constraints)
"""
if solver_name.upper().endswith("_EXT"):
solver_name = SOLVER_DEFAULT
self._model = MixedIntegerLinearProgram(maximization=False, solver=solver_name)
self._binary_variable = self._model.new_variable(binary=True)
self._integer_variable = self._model.new_variable(integer=True)
self._non_linear_component_id = []

def _solve_with_external_solver(self, model_type, model_path, solver_name=SOLVER_DEFAULT):

solvers_configuration = get_external_milp_solver_configuration(f'{model_path[:-3]}.sol')
if solver_name not in solvers_configuration:
raise ValueError(f"Invalid solver name: {solver_name}."
f"Currently supported solvers are 'Gurobi', 'cplex', 'scip' and 'glpk'.")

solver_specs = solvers_configuration[solver_name]
command = solver_specs['command']
options = solver_specs['options']
tracemalloc.start()
solver_specs = [specs for specs in MILP_SOLVERS_EXTERNAL if specs["solver_name"] == solver_name.upper()][0]
solution_file_path = f'{MODEL_DEFAULT_PATH}/{model_path[:-3]}.sol'

command += model_path + options
command = ""
for key in solver_specs['keywords']['command']['format']:
parameter = solver_specs['keywords']['command'][key]
if key == "input_file":
parameter += " " + model_path
elif key == "output_file":
parameter = parameter + solution_file_path if parameter.endswith('=') else parameter + " " + solution_file_path
elif key == "options":
parameter = " ".join(parameter)
command += " " + parameter
tracemalloc.start()
solver_process = subprocess.run(command, capture_output=True, shell=True, text=True)
milp_memory = tracemalloc.get_traced_memory()[1] / 10 ** 6
tracemalloc.stop()
Expand All @@ -282,29 +289,29 @@ def _solve_with_external_solver(self, model_type, model_path, solver_name=SOLVER
raise MIPSolverException("Make sure that the solver is correctly installed.")

if 'memory' in solver_specs:
milp_memory = _get_data(solver_specs['memory'], str(solver_process))
milp_memory = _get_data(solver_specs['keywords']['memory'], str(solver_process))

return _parse_external_solver_output(self, solvers_configuration, model_type, solver_name, solver_process) + (milp_memory,)
return _parse_external_solver_output(self, solver_specs, model_type, solution_file_path, solver_process.stdout) + (milp_memory,)

def _solve_with_internal_solver(self):

mip = self._model

status = 'UNSATISFIABLE'
self._verbose_print("Solving model in progress ...")
time_start = time.time()
tracemalloc.start()
try:
mip.solve()
status = 'SATISFIABLE'

except MIPSolverException as milp_exception:
print(milp_exception)
finally:
milp_memory = tracemalloc.get_traced_memory()[1] / 10 ** 6
tracemalloc.stop()
time_end = time.time()
milp_time = time_end - time_start
self._verbose_print(f"Time for solving the model = {milp_time}")
status = 'SATISFIABLE'

except MIPSolverException as milp_exception:
status = 'UNSATISFIABLE'
print(milp_exception)

return status, milp_time, milp_memory

Expand All @@ -329,23 +336,44 @@ def solve(self, model_type, solver_name=SOLVER_DEFAULT, external_solver_name=Non
...
sage: solution = milp.solve("xor_differential") # random
"""
if external_solver_name:
solver_name_in_solution = external_solver_name
if external_solver_name or (solver_name.upper().endswith("_EXT")):
solver_choice = external_solver_name or solver_name
if solver_choice.upper() not in [specs["solver_name"] for specs in MILP_SOLVERS_EXTERNAL]:
raise ValueError(f"Invalid solver name: {solver_choice}.\n"
f"Please select a solver in the following list: {[specs['solver_name'] for specs in MILP_SOLVERS_EXTERNAL]}.")

solver_name_in_solution = solver_choice
model_path = _write_model_to_lp_file(self, model_type)
solution_file_path, status, objective_value, components_values, milp_time, milp_memory = self._solve_with_external_solver(
model_type, model_path, external_solver_name)
model_type, model_path, solver_choice)
os.remove(model_path)
os.remove(f"{solution_file_path}")
else:
objective_value = None
components_values = None
solver_name_in_solution = solver_name
status, milp_time, milp_memory = self._solve_with_internal_solver()
objective_value, components_values = self._parse_solver_output()
if status == 'SATISFIABLE':
objective_value, components_values = self._parse_solver_output()

solution = convert_solver_solution_to_dictionary(self._cipher, model_type, solver_name_in_solution, milp_time,
milp_memory, components_values, objective_value)
solution['status'] = status
return solution

def solver_names(self, verbose=False):
solver_names = []

keys = ['solver_brand_name', 'solver_name']
for solver in MILP_SOLVERS_INTERNAL:
solver_names.append({key: solver[key] for key in keys})
if verbose:
keys = ['solver_brand_name', 'solver_name', 'keywords']

for solver in MILP_SOLVERS_EXTERNAL:
solver_names.append({key: solver[key] for key in keys})
return solver_names

@property
def binary_variable(self):
return self._binary_variable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# ****************************************************************************

import time
from claasp.cipher_modules.models.milp.utils.config import SOLVER_DEFAULT
from claasp.cipher_modules.models.milp.solvers import SOLVER_DEFAULT
from claasp.cipher_modules.models.milp.utils.milp_name_mappings import MILP_BITWISE_DETERMINISTIC_TRUNCATED, \
MILP_BACKWARD_SUFFIX, MILP_BUILDING_MESSAGE, MILP_TRUNCATED_XOR_DIFFERENTIAL_OBJECTIVE
from claasp.cipher_modules.models.milp.utils.milp_truncated_utils import \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import time

from claasp.cipher_modules.inverse_cipher import get_key_schedule_component_ids
from claasp.cipher_modules.models.milp.utils.config import SOLVER_DEFAULT
from claasp.cipher_modules.models.milp.solvers import SOLVER_DEFAULT
from claasp.cipher_modules.models.milp.milp_models.milp_bitwise_deterministic_truncated_xor_differential_model import \
MilpBitwiseDeterministicTruncatedXorDifferentialModel
from claasp.cipher_modules.models.milp.utils.milp_name_mappings import MILP_BITWISE_IMPOSSIBLE, \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# ****************************************************************************

import time
from claasp.cipher_modules.models.milp.utils.config import SOLVER_DEFAULT
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_WORDWISE_DETERMINISTIC_TRUNCATED, \
MILP_BUILDING_MESSAGE, MILP_TRUNCATED_XOR_DIFFERENTIAL_OBJECTIVE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import time

from claasp.cipher_modules.inverse_cipher import get_key_schedule_component_ids
from claasp.cipher_modules.models.milp.utils.config import SOLVER_DEFAULT
from claasp.cipher_modules.models.milp.solvers import SOLVER_DEFAULT
from claasp.cipher_modules.models.milp.milp_models.milp_wordwise_deterministic_truncated_xor_differential_model import MilpWordwiseDeterministicTruncatedXorDifferentialModel
from claasp.cipher_modules.models.milp.utils.milp_name_mappings import MILP_WORDWISE_IMPOSSIBLE_AUTO, \
MILP_WORDWISE_IMPOSSIBLE, MILP_BACKWARD_SUFFIX, MILP_BUILDING_MESSAGE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from bitstring import BitArray

from claasp.cipher_modules.models.milp.utils.config import SOLVER_DEFAULT
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
Expand Down Expand Up @@ -496,7 +496,7 @@ def find_one_xor_differential_trail(self, fixed_values=[], solver_name=SOLVER_DE
EXAMPLES::
# single-key setting
from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher
sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher
sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel
sage: speck = SpeckBlockCipher(number_of_rounds=5)
sage: milp = MilpXorDifferentialModel(speck)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from bitstring import BitArray

from claasp.cipher_modules.models.milp.utils.config import SOLVER_DEFAULT
from claasp.cipher_modules.models.milp.solvers import SOLVER_DEFAULT
from claasp.cipher_modules.models.milp.utils.generate_inequalities_for_xor_with_n_input_bits import \
update_dictionary_that_contains_xor_inequalities_between_n_input_bits, \
output_dictionary_that_contains_xor_inequalities
Expand Down
Loading

0 comments on commit 9da9b8c

Please sign in to comment.