Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions claasp/cipher_modules/models/milp/milp_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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::
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -582,146 +591,51 @@ 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)
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)
_, 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

Expand Down
Loading
Loading