diff --git a/claasp/cipher_modules/models/milp/milp_model.py b/claasp/cipher_modules/models/milp/milp_model.py index cd4f153ab..9083772c4 100644 --- a/claasp/cipher_modules/models/milp/milp_model.py +++ b/claasp/cipher_modules/models/milp/milp_model.py @@ -245,10 +245,12 @@ def fix_variables_value_constraints(self, fixed_variables=[]): a = x[f"{comp_id}_{pos}"] b = x[f"{var_vals[i][0]}_{var_vals[i][1]}"] - constraints.append(lhs <= a + b) - constraints.append(lhs >= a - b) - constraints.append(lhs >= b - a) - constraints.append(lhs + a + b <= 2) + constraints.extend([ + lhs <= a + b, + lhs >= a - b, + lhs >= b - a, + lhs + a + b <= 2, + ]) constraints.append(sum(x[f"{comp_id}{p}_not_equal_{n_trails}"] for p in bit_pos) >= 1) else: @@ -292,17 +294,18 @@ def weight_constraints(self, weight, weight_precision=MILP_DEFAULT_WEIGHT_PRECIS return self.weight_range_constraints(weight, weight, weight_precision) - def weight_range_constraints(self, min_weight, max_weight, weight_precision=MILP_DEFAULT_WEIGHT_PRECISION): + def weight_range_constraints(self, min_weight=None, max_weight=None, weight_precision=MILP_DEFAULT_WEIGHT_PRECISION): """ - Return a list of variables and a list of constraints that bound the total weight to ``[min_weight, max_weight]``. + Return a list of variables and a list of constraints that bound the total weight. - Unlike :py:meth:`weight_constraints`, which pins the weight to a single value, this bounds it to a - range. Setting ``min_weight == max_weight`` is equivalent to fixing the weight. + If both bounds are provided, this bounds the weight to ``[min_weight, max_weight]``. Setting + ``min_weight == max_weight`` is equivalent to fixing the weight. If only one bound is provided, only that + side of the range is constrained. If neither bound is provided, no weight constraint is returned. INPUT: - - ``min_weight`` -- **integer**; lower bound on the total weight - - ``max_weight`` -- **integer**; upper bound on the total weight + - ``min_weight`` -- **integer** or ``None`` (default: ``None``); lower bound on the total weight + - ``max_weight`` -- **integer** or ``None`` (default: ``None``); upper bound on the total weight - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. EXAMPLES:: @@ -319,16 +322,39 @@ def weight_range_constraints(self, min_weight, max_weight, weight_precision=MILP [300 <= x_0, x_0 <= 1000] sage: milp.weight_range_constraints(5, 5)[1] [x_0 == 500] + + sage: from claasp.ciphers.block_ciphers.simon_block_cipher import SimonBlockCipher + sage: from claasp.cipher_modules.models.milp.milp_model import MilpModel + sage: simon = SimonBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: milp = MilpModel(simon) + sage: milp.init_model_in_sage_milp_class() + sage: milp.weight_range_constraints(3, None)[1] + [300 <= x_0] + + sage: from claasp.ciphers.block_ciphers.simon_block_cipher import SimonBlockCipher + sage: from claasp.cipher_modules.models.milp.milp_model import MilpModel + sage: simon = SimonBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: milp = MilpModel(simon) + sage: milp.init_model_in_sage_milp_class() + sage: milp.weight_range_constraints(None, 5)[1] + [x_0 <= 500] """ + if min_weight is None and max_weight is None: + return [], [] + if min_weight is not None and max_weight is not None and min_weight > max_weight: + raise ValueError("lower_bound must be <= upper_bound") + p = self._integer_variable prec = 10**weight_precision variables = [("p[probability]", p["probability"])] if min_weight == max_weight: constraints = [p["probability"] == prec * min_weight] else: - constraints = [p["probability"] <= prec * max_weight] - if min_weight > 0: - constraints.insert(0, p["probability"] >= prec * min_weight) + constraints = [] + if min_weight is not None: + constraints.append(p["probability"] >= prec * min_weight) + if max_weight is not None: + constraints.append(p["probability"] <= prec * max_weight) 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 3341c8635..9cf8b6b94 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 @@ -555,18 +555,27 @@ def find_lowest_weight_xor_differential_trail( def find_one_xor_differential_trail( self, fixed_values=[], + lower_bound=None, + upper_bound=None, 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. + + If bounds are provided, the returned trail has weight in ``[lower_bound, upper_bound]``. Passing only + ``lower_bound`` finds one trail with weight at least that value; passing only ``upper_bound`` finds one trail + with weight at most that value, and passing equal bounds fixes the trail weight. + By default, the search is set in the single-key setting. INPUT: - ``fixed_values`` -- **list** (default: `[]`); dictionaries containing the variables to be fixed in standard format + - ``lower_bound`` -- **integer** (default: `None`); lower bound on the weight of the trail + - ``upper_bound`` -- **integer** (default: `None`); upper bound on the weight of the trail - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `HIGHS_EXT`); 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. @@ -582,72 +591,37 @@ def find_one_xor_differential_trail( 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) - sage: trail = milp.find_one_xor_differential_trail() # random # doctest: +SKIP + sage: trail = milp.find_one_xor_differential_trail() # random - # related-key setting - sage: from claasp.cipher_modules.models.utils import set_fixed_variables - 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) + # single-key setting with upper bound on the weight + sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) sage: milp = MilpXorDifferentialModel(speck) - sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) - sage: trail = milp.find_one_xor_differential_trail(fixed_values=[key]) # random # doctest: +SKIP - """ - start = time.time() - self.init_model_in_sage_milp_class(solver_name) - 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, weight_precision, fixed_values) - end = time.time() - building_time = end - start - solution = self.solve(MILP_XOR_DIFFERENTIAL, solver_name, external_solver_name) - solution["building_time"] = building_time - solution["test_name"] = "find_one_xor_differential_trail" - - return solution - - def find_one_xor_differential_trail_with_weight_at_most( - self, - max_weight, - min_weight=0, - fixed_values=[], - weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, - solver_name=SOLVER_DEFAULT, - external_solver_name=None, - ): - """ - Return one XOR differential trail whose weight lies in ``[min_weight, max_weight]``, in standard format. - By default, the search is set in the single-key setting. - - .. NOTE:: - - Feasibility search: returns any trail with weight in ``[min_weight, max_weight]``, not the trail of - lowest weight. With the default ``min_weight=0`` the returned trail may be trivial. - - INPUT: - - - ``max_weight`` -- **integer**; the upper bound on the weight of the trail - - ``min_weight`` -- **integer** (default: `0`); the lower bound on the weight of the 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: `HIGHS_EXT`); 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. - - .. SEEALSO:: + sage: trail = milp.find_one_xor_differential_trail(upper_bound=5) # random + sage: 0.0 <= trail['total_weight'] <= 5.0 + True - :py:meth:`~cipher_modules.models.utils.set_fixed_variables` + # single-key setting with lower bound on the weight + sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: milp = MilpXorDifferentialModel(speck) + sage: trail = milp.find_one_xor_differential_trail(lower_bound=3) # random + sage: trail['total_weight'] >= 3.0 + True - EXAMPLES:: + # single-key setting with bounded weight + sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: milp = MilpXorDifferentialModel(speck) + sage: trail = milp.find_one_xor_differential_trail(lower_bound=3, upper_bound=5) # random + sage: 3.0 <= trail['total_weight'] <= 5.0 + True + # related-key setting + sage: from claasp.cipher_modules.models.utils import set_fixed_variables 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(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: speck = SpeckBlockCipher(number_of_rounds=5) sage: milp = MilpXorDifferentialModel(speck) - sage: trail = milp.find_one_xor_differential_trail_with_weight_at_most(5) # random - sage: trail['total_weight'] <= 5.0 - True + sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) + sage: trail = milp.find_one_xor_differential_trail(fixed_values=[key]) # random """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -655,73 +629,13 @@ def find_one_xor_differential_trail_with_weight_at_most( mip = self._model mip.set_objective(None) self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) - _, constraints = self.weight_range_constraints(min_weight, max_weight, weight_precision) - for constraint in constraints: + for constraint in self.weight_range_constraints(lower_bound, upper_bound, weight_precision)[1]: mip.add_constraint(constraint) end = time.time() building_time = end - start solution = self.solve(MILP_XOR_DIFFERENTIAL, solver_name, external_solver_name) solution["building_time"] = building_time - solution["test_name"] = "find_one_xor_differential_trail_with_weight_at_most" - - return solution - - 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. - By default, the search is set in the single-key setting. - - INPUT: - - - ``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: `HIGHS_EXT`); 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. - - .. SEEALSO:: - - :py:meth:`~cipher_modules.models.utils.set_fixed_variables` - - EXAMPLES:: - - # single-key setting - 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=3) - sage: milp = MilpXorDifferentialModel(speck) - sage: trail = milp.find_one_xor_differential_trail_with_fixed_weight(3) # random # doctest: +SKIP - sage: trail['total_weight'] # doctest: +SKIP - 3.0 - - # related-key setting - sage: from claasp.cipher_modules.models.utils import set_fixed_variables - 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=3) - sage: milp = MilpXorDifferentialModel(speck) - sage: key = set_fixed_variables('key', 'not_equal', list(range(64)), [0] * 64) - sage: trail = milp.find_one_xor_differential_trail_with_fixed_weight(3, fixed_values=[key]) # random # doctest: +SKIP - sage: trail['total_weight'] # doctest: +SKIP - 3.0 - """ - solution = self.find_one_xor_differential_trail_with_weight_at_most( - fixed_weight, - fixed_weight, - fixed_values=fixed_values, - weight_precision=weight_precision, - solver_name=solver_name, - external_solver_name=external_solver_name, - ) - solution["test_name"] = "find_one_xor_differential_trail_with_fixed_weight" + solution["test_name"] = "find_one_xor_differential_trail" return solution 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 e71ec029d..022da59dd 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 @@ -592,12 +592,19 @@ def find_lowest_weight_xor_linear_trail( def find_one_xor_linear_trail( self, fixed_values=[], + lower_bound=None, + upper_bound=None, 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. + + If bounds are provided, the returned trail has weight in ``[lower_bound, upper_bound]``. Passing only + ``lower_bound`` finds one trail with weight at least that value; passing only ``upper_bound`` finds one trail + with weight at most that value, and passing equal bounds fixes the trail weight. + By default, the search removes the key schedule, if any. By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. @@ -605,6 +612,8 @@ def find_one_xor_linear_trail( - ``fixed_values`` -- **list** (default: `[]`); dictionaries containing the variables to be fixed in standard format (see ) + - ``lower_bound`` -- **integer** (default: `None`); lower bound on the weight of the trail + - ``upper_bound`` -- **integer** (default: `None`); upper bound on the weight of the trail - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - ``solver_name`` -- **string** (default: `HIGHS_EXT`); 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. @@ -621,71 +630,38 @@ def find_one_xor_linear_trail( sage: milp = MilpXorLinearModel(speck) sage: trail = milp.find_one_xor_linear_trail() # random - # including the key schedule in the model sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - sage: milp = MilpXorLinearModel(speck) - sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) - sage: trail = milp.find_one_xor_linear_trail(fixed_values=[key]) # random - """ - start = time.time() - self.init_model_in_sage_milp_class(solver_name) - 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, weight_precision, fixed_values) - end = time.time() - building_time = end - start - solution = self.solve(MILP_XOR_LINEAR, solver_name, external_solver_name) - solution["building_time"] = building_time - solution["test_name"] = "find_lowest_weight_xor_linear_trail" - - return solution - - def find_one_xor_linear_trail_with_weight_at_most( - self, - max_weight, - min_weight=0, - fixed_values=[], - weight_precision=MILP_DEFAULT_WEIGHT_PRECISION, - solver_name=SOLVER_DEFAULT, - external_solver_name=None, - ): - """ - Return one XOR linear trail whose weight lies in ``[min_weight, max_weight]``, in standard format. - By default, the search removes the key schedule, if any, and the weight corresponds to the negative - base-2 logarithm of the correlation of the trail. - - .. NOTE:: - - Feasibility search: returns any trail with weight in ``[min_weight, max_weight]``, not the trail of - lowest weight. With the default ``min_weight=0`` the returned trail may be trivial. - - INPUT: - - - ``max_weight`` -- **integer**; the upper bound on the weight of the trail - - ``min_weight`` -- **integer** (default: `0`); the lower bound on the weight of the 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: `HIGHS_EXT`); 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:: - - :py:meth:`~cipher_modules.models.utils.set_fixed_variables` + sage: trail = milp.find_one_xor_linear_trail(upper_bound=6) + ... + sage: 0.0 <= trail['total_weight'] <= 6.0 + True - EXAMPLES:: + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: trail = milp.find_one_xor_linear_trail(lower_bound=1) + ... + sage: 1.0 <= trail['total_weight'] + True + sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + sage: trail = milp.find_one_xor_linear_trail(lower_bound=1, upper_bound=6) + ... + sage: 1.0 <= trail['total_weight'] <= 6.0 + True + + # including the key schedule in the model sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel + sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher + sage: from claasp.cipher_modules.models.utils import set_fixed_variables sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) sage: milp = MilpXorLinearModel(speck) - sage: trail = milp.find_one_xor_linear_trail_with_weight_at_most(6) # random - sage: trail['total_weight'] <= 6.0 - True + sage: key = set_fixed_variables('key', 'not_equal', list(range(32)), [0] * 32) + sage: trail = milp.find_one_xor_linear_trail(fixed_values=[key]) # random """ start = time.time() self.init_model_in_sage_milp_class(solver_name) @@ -693,73 +669,13 @@ def find_one_xor_linear_trail_with_weight_at_most( mip = self._model mip.set_objective(None) self.add_constraints_to_build_in_sage_milp_class(-1, weight_precision, fixed_values) - _, constraints = self.weight_range_constraints(min_weight, max_weight, weight_precision) - for constraint in constraints: + for constraint in self.weight_range_constraints(lower_bound, upper_bound, weight_precision)[1]: mip.add_constraint(constraint) end = time.time() building_time = end - start solution = self.solve(MILP_XOR_LINEAR, solver_name, external_solver_name) solution["building_time"] = building_time - solution["test_name"] = "find_one_xor_linear_trail_with_weight_at_most" - - 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. - By default, the search removes the key schedule, if any. - By default, the weight corresponds to the negative base-2 logarithm of the correlation of the trail. - - INPUT: - - - ``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 - - ``weight_precision`` -- **integer** (default: `2`); the number of decimals to use when rounding the weight of the trail. - - ``solver_name`` -- **string** (default: `HIGHS_EXT`); 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:: - - :py:meth:`~cipher_modules.models.utils.set_fixed_variables` - - EXAMPLES:: - - sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel - sage: speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - sage: milp = MilpXorLinearModel(speck) - sage: trail = milp.find_one_xor_linear_trail_with_fixed_weight(6) # random - ... - sage: trail['total_weight'] - 6.0 - - sage: from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel - sage: from claasp.ciphers.block_ciphers.speck_block_cipher import SpeckBlockCipher - sage: from claasp.cipher_modules.models.utils import set_fixed_variables - sage: speck = SpeckBlockCipher(block_bit_size=8, key_bit_size=16, number_of_rounds=4) - sage: milp = MilpXorLinearModel(speck) - sage: key = set_fixed_variables('key', 'not_equal', list(range(16)), [0] * 16) - sage: trail = milp.find_one_xor_linear_trail_with_fixed_weight(3, fixed_values=[key]) # random - sage: trail["total_weight"] - 3.0 - """ - solution = self.find_one_xor_linear_trail_with_weight_at_most( - fixed_weight, - fixed_weight, - fixed_values=fixed_values, - weight_precision=weight_precision, - solver_name=solver_name, - external_solver_name=external_solver_name, - ) - solution["test_name"] = "find_one_xor_linear_trail_with_fixed_weight" + solution["test_name"] = "find_one_xor_linear_trail" return solution diff --git a/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_differential_model_test.py b/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_differential_model_test.py index b03905935..9fe35f396 100644 --- a/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_differential_model_test.py +++ b/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_differential_model_test.py @@ -1,3 +1,5 @@ +import pytest + from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel from claasp.cipher_modules.models.utils import integer_to_bit_list, set_fixed_variables from claasp.ciphers.block_ciphers.present_block_cipher import PresentBlockCipher @@ -69,7 +71,7 @@ def test_find_one_xor_differential_trail(): def test_find_one_xor_differential_trail_with_fixed_weight(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) milp = MilpXorDifferentialModel(speck) - trail = milp.find_one_xor_differential_trail_with_fixed_weight(5) + trail = milp.find_one_xor_differential_trail(lower_bound=5, upper_bound=5) assert trail["total_weight"] == 5.0 tea = TeaBlockCipher(block_bit_size=16, key_bit_size=32, number_of_rounds=2) @@ -83,8 +85,8 @@ def test_find_one_xor_differential_trail_with_fixed_weight(): cipher_output = set_fixed_variables( "cipher_output_1_16", "equal", list(range(16)), integer_to_bit_list(0x404A, 16, "big") ) - trail = milp.find_one_xor_differential_trail_with_fixed_weight( - 15, fixed_values=[key, round_0_output, cipher_output] + trail = milp.find_one_xor_differential_trail( + fixed_values=[key, round_0_output, cipher_output], lower_bound=15, upper_bound=15 ) assert trail["total_weight"] == 15.0 # @@ -99,14 +101,29 @@ def test_find_one_xor_differential_trail_with_fixed_weight(): key = set_fixed_variables( component_id=INPUT_KEY, constraint_type="not_equal", bit_positions=range(64), bit_values=(0,) * 64 ) - trail = milp.find_one_xor_differential_trail_with_fixed_weight(5, fixed_values=[key, round_0_output, cipher_output]) + trail = milp.find_one_xor_differential_trail( + fixed_values=[key, round_0_output, cipher_output], lower_bound=5, upper_bound=5 + ) assert trail["total_weight"] == 5.0 def test_find_one_xor_differential_trail_with_weight_at_most(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - trail = MilpXorDifferentialModel(speck).find_one_xor_differential_trail_with_weight_at_most(5) + trail = MilpXorDifferentialModel(speck).find_one_xor_differential_trail(upper_bound=5) assert 0.0 <= trail["total_weight"] <= 5.0 - trail = MilpXorDifferentialModel(speck).find_one_xor_differential_trail_with_weight_at_most(5, 3) + trail = MilpXorDifferentialModel(speck).find_one_xor_differential_trail(lower_bound=3, upper_bound=5) assert 3.0 <= trail["total_weight"] <= 5.0 + + +def test_find_one_xor_differential_trail_with_weight_at_least(): + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + trail = MilpXorDifferentialModel(speck).find_one_xor_differential_trail(lower_bound=3) + assert trail["total_weight"] >= 3.0 + + +def test_find_one_xor_differential_trail_rejects_invalid_bounds(): + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + milp = MilpXorDifferentialModel(speck) + with pytest.raises(ValueError, match="lower_bound must be <= upper_bound"): + milp.find_one_xor_differential_trail(lower_bound=6, upper_bound=5) diff --git a/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_linear_model_test.py b/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_linear_model_test.py index 2885607d7..227b6e20b 100644 --- a/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_linear_model_test.py +++ b/tests/unit/cipher_modules/models/milp/milp_models/milp_xor_linear_model_test.py @@ -1,4 +1,5 @@ import pytest +from sage.numerical.mip import MIPSolverException from claasp.cipher_modules.models.milp.milp_models.milp_xor_linear_model import MilpXorLinearModel from claasp.cipher_modules.models.utils import integer_to_bit_list, set_fixed_variables @@ -101,59 +102,72 @@ def test_find_one_xor_linear_trail(): def test_find_one_xor_linear_trail_with_fixed_weight(): # speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) # milp = MilpXorLinearModel(speck) - # trail = milp.find_one_xor_linear_trail_with_fixed_weight(6) + # trail = milp.find_one_xor_linear_trail(lower_bound=6, upper_bound=6) # assert len(trail) == 9 # assert trail["total_weight"] == 6.0 speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) milp = MilpXorLinearModel(speck) - trail = milp.find_one_xor_linear_trail_with_fixed_weight(1) + trail = milp.find_one_xor_linear_trail(lower_bound=1, upper_bound=1) assert len(trail) == 10 assert trail["total_weight"] == 1.0 # # speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=4) # milp = MilpXorLinearModel(speck) - # trail = milp.find_one_xor_linear_trail_with_fixed_weight(10) + # trail = milp.find_one_xor_linear_trail(lower_bound=10, upper_bound=10) # assert len(trail) == 9 # assert trail["total_weight"] == 10.0 def test_find_one_xor_linear_trail_with_weight_at_most(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - trail = MilpXorLinearModel(speck).find_one_xor_linear_trail_with_weight_at_most(6) + trail = MilpXorLinearModel(speck).find_one_xor_linear_trail(upper_bound=6) assert 0.0 <= trail["total_weight"] <= 6.0 - trail = MilpXorLinearModel(speck).find_one_xor_linear_trail_with_weight_at_most(6, 1) + trail = MilpXorLinearModel(speck).find_one_xor_linear_trail(lower_bound=1, upper_bound=6) assert 1.0 <= trail["total_weight"] <= 6.0 +def test_find_one_xor_linear_trail_with_weight_at_least(): + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + trail = MilpXorLinearModel(speck).find_one_xor_linear_trail(lower_bound=1) + assert trail["total_weight"] >= 1.0 + + +def test_find_one_xor_linear_trail_rejects_invalid_bounds(): + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + milp = MilpXorLinearModel(speck) + with pytest.raises(ValueError, match="lower_bound must be <= upper_bound"): + milp.find_one_xor_linear_trail(lower_bound=7, upper_bound=6) + + def test_find_one_xor_linear_trail_with_fixed_weight_with_external_solver(): speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) milp = MilpXorLinearModel(speck) - trail = milp.find_one_xor_linear_trail_with_fixed_weight(1, external_solver_name="glpk_ext") + trail = milp.find_one_xor_linear_trail(lower_bound=1, upper_bound=1, external_solver_name="glpk_ext") assert len(trail) == 10 assert trail["total_weight"] == 1.0 def test_find_one_xor_linear_trail_with_fixed_weight_with_supported_but_not_installed_external_solver(): - with pytest.raises(Exception) as e_info: - speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck) - trail = milp.find_one_xor_linear_trail_with_fixed_weight(1, external_solver_name="cplex_ext") + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + milp = MilpXorLinearModel(speck) + with pytest.raises(MIPSolverException): + milp.find_one_xor_linear_trail(lower_bound=1, upper_bound=1, external_solver_name="cplex_ext") def test_find_one_xor_linear_trail_with_fixed_weight_with_installed_external_solver_but_missing_license(): - with pytest.raises(Exception) as e_info: - speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck) - trail = milp.find_one_xor_linear_trail_with_fixed_weight(1, external_solver_name="Gurobi_ext") + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + milp = MilpXorLinearModel(speck) + with pytest.raises(MIPSolverException): + milp.find_one_xor_linear_trail(lower_bound=1, upper_bound=1, external_solver_name="Gurobi_ext") def test_find_one_xor_linear_trail_with_fixed_weight_with_unsupported_external_solver(): - with pytest.raises(Exception) as e_info: - speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) - milp = MilpXorLinearModel(speck) - trail = milp.find_one_xor_linear_trail_with_fixed_weight(1, external_solver_name="unsupported_solver") + speck = SpeckBlockCipher(block_bit_size=32, key_bit_size=64, number_of_rounds=2) + milp = MilpXorLinearModel(speck) + with pytest.raises(ValueError): + milp.find_one_xor_linear_trail(lower_bound=1, upper_bound=1, external_solver_name="unsupported_solver") def test_fix_variables_value_xor_linear_constraints():