Skip to content

Commit

Permalink
FEATURE/Add: impossible xor differential models based on infeasibility
Browse files Browse the repository at this point in the history
  • Loading branch information
p-huynh committed Oct 17, 2024
1 parent b18d3bd commit a963062
Show file tree
Hide file tree
Showing 9 changed files with 823 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@

# ****************************************************************************
# Copyright 2023 Technology Innovation Institute
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ****************************************************************************

"""
The search methods presented in this class differ from those of the classes
:py:class:`Milp Bitwise Impossible Xor Differential Model
<cipher_modules.models.milp.milp_models.milp_bitwise_impossible_xor_differential_cipher_model>`
and :py:class:`Milp Wordwise Impossible Xor Differential Model
<cipher_modules.models.milp.milp_models.milp_wordwise_impossible_xor_differential_cipher_model>`.
Indeed, this class implements the framework proposed by `Cui et al.<https://eprint.iacr.org/2016/689.pdf>` which uses
infeasibility of a XOR DIFFERENTIAL model to detect an impossible differential trail.
"""

from itertools import combinations, product

import numpy as np

from claasp.cipher_modules.models.milp import solvers
from claasp.cipher_modules.models.milp.milp_models.milp_xor_differential_model import MilpXorDifferentialModel
from claasp.cipher_modules.models.utils import set_fixed_variables, \
convert_solver_solution_to_dictionary
from claasp.name_mappings import (INPUT_PLAINTEXT, INPUT_KEY, IMPOSSIBLE_XOR_DIFFERENTIAL)


class MilpImpossibleXorDifferentialModel(MilpXorDifferentialModel):
def __init__(self, cipher, counter='sequential', compact=False):
super().__init__(cipher, counter, compact)

def find_one_impossible_xor_differential_trail(self, number_of_active_key_bits=1, number_of_active_pt_bits=1, number_of_active_ct_bits=1, solver_name=solvers.SOLVER_DEFAULT):
"""
Returns one impossible XOR differential trail.
INPUTS:
- ``fixed_values`` -- *list of dict*, the variables to be fixed in
standard format (see :py:meth:`~GenericModel.set_fixed_variables`)
- ``solver_name`` -- *str*, the solver to call
EXAMPLE::
# to retrieve one of the trails of Table 2 from https://eprint.iacr.org/2016/689
sage: from claasp.ciphers.block_ciphers.lblock_block_cipher import LBlockBlockCipher
sage: lblock = LBlockBlockCipher(number_of_rounds=16)
sage: from claasp.cipher_modules.models.milp.milp_models.milp_impossible_xor_differential_model import MilpImpossibleXorDifferentialModel
sage: milp = MilpImpossibleXorDifferentialModel(lblock)
sage: trail = milp.find_one_impossible_xor_differential_trail(1,0,0) # doctest: +SKIP
...
"""

pt_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(INPUT_PLAINTEXT)]
key_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(INPUT_KEY)]
ct_id = [c.id for c in self._cipher.get_all_components() if c.type == 'cipher_output'][0]

key_combinations = combinations(range(key_size), number_of_active_key_bits)
pt_combinations = combinations(range(pt_size), number_of_active_pt_bits)
ct_combinations = combinations(range(self._cipher.output_bit_size), number_of_active_ct_bits)

solving_time = 0

for key_bits, pt_bits, ct_bits in product(key_combinations, pt_combinations, ct_combinations):
key = np.zeros(key_size, dtype=int)
key[list(key_bits)] = 1
key_vars = set_fixed_variables(component_id=INPUT_KEY, constraint_type='equal', bit_positions=range(key_size), bit_values=key)

pt = np.zeros(pt_size, dtype=int)
pt[list(pt_bits)] = 1
pt_vars = set_fixed_variables(component_id=INPUT_PLAINTEXT, constraint_type='equal',
bit_positions=range(pt_size), bit_values=pt)

ct = np.zeros(self._cipher.output_bit_size, dtype=int)
ct[list(ct_bits)] = 1
ct_vars = set_fixed_variables(component_id=ct_id, constraint_type='equal',
bit_positions=range(self._cipher.output_bit_size), bit_values=ct)

trail = self.find_one_xor_differential_trail(fixed_values=[key_vars, pt_vars, ct_vars],
solver_name=solver_name)

solving_time += trail['solving_time_seconds']

if trail['status'] == 'UNSATISFIABLE':
for component in [INPUT_KEY, INPUT_PLAINTEXT, ct_id]:
trail['components_values'][component] = {}
trail['solving_time_seconds'] == solving_time
trail['components_values'][INPUT_KEY]['value'] = np.packbits(key).tobytes().hex()
trail['components_values'][INPUT_PLAINTEXT]['value'] = np.packbits(pt).tobytes().hex()
trail['components_values'][ct_id]['value'] = np.packbits(ct).tobytes().hex()
trail['model_type'] = IMPOSSIBLE_XOR_DIFFERENTIAL
trail['test_name'] = 'find_one_impossible_xor_differential_trail'
trail['status'] = 'SATISFIABLE'
return trail


solution = convert_solver_solution_to_dictionary(self._cipher, IMPOSSIBLE_XOR_DIFFERENTIAL, solver_name, solving_time,
None, [], None)
return solution

def find_all_impossible_xor_differential_trails(self, number_of_active_key_bits=1, number_of_active_pt_bits=1, number_of_active_ct_bits=1, solver_name=solvers.SOLVER_DEFAULT):
"""
Returns all impossible XOR differential trails.
INPUTS:
- ``fixed_values`` -- *list of dict*, the variables to be fixed in
standard format (see :py:meth:`~GenericModel.set_fixed_variables`)
- ``solver_name`` -- *str*, the solver to call
EXAMPLE::
# to retrieve the trails of Table 2 from https://eprint.iacr.org/2016/689
sage: from claasp.ciphers.block_ciphers.lblock_block_cipher import LBlockBlockCipher
sage: lblock = LBlockBlockCipher(number_of_rounds=16)
sage: from claasp.cipher_modules.models.milp.milp_models.milp_impossible_xor_differential_model import MilpImpossibleXorDifferentialModel
sage: milp = MilpImpossibleXorDifferentialModel(lblock)
sage: trails = milp.find_all_impossible_xor_differential_trails(1,0,0) # doctest: +SKIP
...
"""

pt_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(INPUT_PLAINTEXT)]
key_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(INPUT_KEY)]
ct_id = [c.id for c in self._cipher.get_all_components() if c.type == 'cipher_output'][0]

key_combinations = combinations(range(key_size), number_of_active_key_bits)
pt_combinations = combinations(range(pt_size), number_of_active_pt_bits)
ct_combinations = combinations(range(self._cipher.output_bit_size), number_of_active_ct_bits)

solutions_list = []
solving_time = 0

for key_bits, pt_bits, ct_bits in product(key_combinations, pt_combinations, ct_combinations):
key = np.zeros(key_size, dtype=int)
key[list(key_bits)] = 1
key_vars = set_fixed_variables(component_id=INPUT_KEY, constraint_type='equal', bit_positions=range(key_size), bit_values=key)

pt = np.zeros(pt_size, dtype=int)
pt[list(pt_bits)] = 1
pt_vars = set_fixed_variables(component_id=INPUT_PLAINTEXT, constraint_type='equal',
bit_positions=range(pt_size), bit_values=pt)

ct = np.zeros(self._cipher.output_bit_size, dtype=int)
ct[list(ct_bits)] = 1
ct_vars = set_fixed_variables(component_id=ct_id, constraint_type='equal',
bit_positions=range(self._cipher.output_bit_size), bit_values=ct)

trail = self.find_one_xor_differential_trail(fixed_values=[key_vars, pt_vars, ct_vars],
solver_name=solver_name)

solving_time += trail['solving_time_seconds']

if trail['status'] == 'UNSATISFIABLE':
for component in [INPUT_KEY, INPUT_PLAINTEXT, ct_id]:
trail['components_values'][component] = {}
trail['solving_time_seconds'] == solving_time
trail['components_values'][INPUT_KEY]['value'] = np.packbits(key).tobytes().hex()
trail['components_values'][INPUT_PLAINTEXT]['value'] = np.packbits(pt).tobytes().hex()
trail['components_values'][ct_id]['value'] = np.packbits(ct).tobytes().hex()
trail['model_type'] = IMPOSSIBLE_XOR_DIFFERENTIAL
trail['test_name'] = 'find_one_impossible_xor_differential_trail'
trail['status'] = 'SATISFIABLE'
solutions_list.append(trail)

return solutions_list
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@

# ****************************************************************************
# Copyright 2023 Technology Innovation Institute
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ****************************************************************************

"""
The search methods presented in this class differ from those of the classes
:py:class:`Cms Bitwise Impossible Xor Differential Model
<cipher_modules.models.sat.sat_models.cms_bitwise_impossible_xor_differential_cipher_model>`
and :py:class:`Cms Wordwise Impossible Xor Differential Model
<cipher_modules.models.sat.sat_models.cms_wordwise_impossible_xor_differential_cipher_model>`.
Indeed, this class implements the framework proposed by `Cui et al.<https://eprint.iacr.org/2016/689.pdf>` which uses
infeasibility of a XOR DIFFERENTIAL model to detect an impossible differential trail.
"""

from itertools import combinations, product

import numpy as np

from claasp.cipher_modules.models.sat import solvers
from claasp.cipher_modules.models.sat.cms_models.cms_xor_differential_model import CmsSatXorDifferentialModel
from claasp.cipher_modules.models.utils import set_fixed_variables, \
convert_solver_solution_to_dictionary
from claasp.name_mappings import (INPUT_PLAINTEXT, INPUT_KEY, IMPOSSIBLE_XOR_DIFFERENTIAL)


class CmsSatImpossibleXorDifferentialModel(CmsSatXorDifferentialModel):
def __init__(self, cipher, counter='sequential', compact=False):
super().__init__(cipher, counter, compact)

def find_one_impossible_xor_differential_trail(self, number_of_active_key_bits=1, number_of_active_pt_bits=1, number_of_active_ct_bits=1, solver_name=solvers.SOLVER_DEFAULT):
"""
Returns one impossible XOR differential trail.
INPUTS:
- ``fixed_values`` -- *list of dict*, the variables to be fixed in
standard format (see :py:meth:`~GenericModel.set_fixed_variables`)
- ``solver_name`` -- *str*, the solver to call
EXAMPLE::
# to retrieve one of the trails of Table 2 from https://eprint.iacr.org/2016/689
sage: from claasp.ciphers.block_ciphers.lblock_block_cipher import LBlockBlockCipher
sage: lblock = LBlockBlockCipher(number_of_rounds=16)
sage: from claasp.cipher_modules.models.sat.cms_models.cms_impossible_xor_differential_model import CmsSatImpossibleXorDifferentialModel
sage: cms = CmsSatImpossibleXorDifferentialModel(lblock)
sage: trail = cms.find_one_impossible_xor_differential_trail(1,0,0) # doctest: +SKIP
...
"""

pt_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(INPUT_PLAINTEXT)]
key_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(INPUT_KEY)]
ct_id = [c.id for c in self._cipher.get_all_components() if c.type == 'cipher_output'][0]

key_combinations = combinations(range(key_size), number_of_active_key_bits)
pt_combinations = combinations(range(pt_size), number_of_active_pt_bits)
ct_combinations = combinations(range(self._cipher.output_bit_size), number_of_active_ct_bits)

solving_time = 0

for key_bits, pt_bits, ct_bits in product(key_combinations, pt_combinations, ct_combinations):
key = np.zeros(key_size, dtype=int)
key[list(key_bits)] = 1
key_vars = set_fixed_variables(component_id=INPUT_KEY, constraint_type='equal', bit_positions=range(key_size), bit_values=key)

pt = np.zeros(pt_size, dtype=int)
pt[list(pt_bits)] = 1
pt_vars = set_fixed_variables(component_id=INPUT_PLAINTEXT, constraint_type='equal',
bit_positions=range(pt_size), bit_values=pt)

ct = np.zeros(self._cipher.output_bit_size, dtype=int)
ct[list(ct_bits)] = 1
ct_vars = set_fixed_variables(component_id=ct_id, constraint_type='equal',
bit_positions=range(self._cipher.output_bit_size), bit_values=ct)

trail = self.find_one_xor_differential_trail(fixed_values=[key_vars, pt_vars, ct_vars],
solver_name=solver_name)

solving_time += trail['solving_time_seconds']

if trail['status'] == 'UNSATISFIABLE':
for component in [INPUT_KEY, INPUT_PLAINTEXT, ct_id]:
trail['components_values'][component] = {}
trail['solving_time_seconds'] == solving_time
trail['components_values'][INPUT_KEY]['value'] = np.packbits(key).tobytes().hex()
trail['components_values'][INPUT_PLAINTEXT]['value'] = np.packbits(pt).tobytes().hex()
trail['components_values'][ct_id]['value'] = np.packbits(ct).tobytes().hex()
trail['model_type'] = IMPOSSIBLE_XOR_DIFFERENTIAL
trail['test_name'] = 'find_one_impossible_xor_differential_trail'
trail['status'] = 'SATISFIABLE'
return trail


solution = convert_solver_solution_to_dictionary(self._cipher, IMPOSSIBLE_XOR_DIFFERENTIAL, solver_name, solving_time,
None, [], None)
return solution

def find_all_impossible_xor_differential_trails(self, number_of_active_key_bits=1, number_of_active_pt_bits=1, number_of_active_ct_bits=1, solver_name=solvers.SOLVER_DEFAULT):
"""
Returns all impossible XOR differential trails.
INPUTS:
- ``fixed_values`` -- *list of dict*, the variables to be fixed in
standard format (see :py:meth:`~GenericModel.set_fixed_variables`)
- ``solver_name`` -- *str*, the solver to call
EXAMPLE::
# to retrieve the trails of Table 2 from https://eprint.iacr.org/2016/689
sage: from claasp.ciphers.block_ciphers.lblock_block_cipher import LBlockBlockCipher
sage: lblock = LBlockBlockCipher(number_of_rounds=16)
sage: from claasp.cipher_modules.models.sat.cms_models.cms_impossible_xor_differential_model import CmsSatImpossibleXorDifferentialModel
sage: cms = CmsSatImpossibleXorDifferentialModel(lblock)
sage: trails = cms.find_all_impossible_xor_differential_trails(1,0,0) # doctest: +SKIP
...
"""

pt_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(INPUT_PLAINTEXT)]
key_size = self._cipher.inputs_bit_size[self._cipher.inputs.index(INPUT_KEY)]
ct_id = [c.id for c in self._cipher.get_all_components() if c.type == 'cipher_output'][0]

key_combinations = combinations(range(key_size), number_of_active_key_bits)
pt_combinations = combinations(range(pt_size), number_of_active_pt_bits)
ct_combinations = combinations(range(self._cipher.output_bit_size), number_of_active_ct_bits)

solutions_list = []
solving_time = 0

for key_bits, pt_bits, ct_bits in product(key_combinations, pt_combinations, ct_combinations):
key = np.zeros(key_size, dtype=int)
key[list(key_bits)] = 1
key_vars = set_fixed_variables(component_id=INPUT_KEY, constraint_type='equal', bit_positions=range(key_size), bit_values=key)

pt = np.zeros(pt_size, dtype=int)
pt[list(pt_bits)] = 1
pt_vars = set_fixed_variables(component_id=INPUT_PLAINTEXT, constraint_type='equal',
bit_positions=range(pt_size), bit_values=pt)

ct = np.zeros(self._cipher.output_bit_size, dtype=int)
ct[list(ct_bits)] = 1
ct_vars = set_fixed_variables(component_id=ct_id, constraint_type='equal',
bit_positions=range(self._cipher.output_bit_size), bit_values=ct)

trail = self.find_one_xor_differential_trail(fixed_values=[key_vars, pt_vars, ct_vars],
solver_name=solver_name)

solving_time += trail['solving_time_seconds']

if trail['status'] == 'UNSATISFIABLE':
for component in [INPUT_KEY, INPUT_PLAINTEXT, ct_id]:
trail['components_values'][component] = {}
trail['solving_time_seconds'] == solving_time
trail['components_values'][INPUT_KEY]['value'] = np.packbits(key).tobytes().hex()
trail['components_values'][INPUT_PLAINTEXT]['value'] = np.packbits(pt).tobytes().hex()
trail['components_values'][ct_id]['value'] = np.packbits(ct).tobytes().hex()
trail['model_type'] = IMPOSSIBLE_XOR_DIFFERENTIAL
trail['test_name'] = 'find_one_impossible_xor_differential_trail'
trail['status'] = 'SATISFIABLE'
solutions_list.append(trail)

return solutions_list
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ def build_xor_differential_trail_model(self, weight=-1, fixed_variables=[]):
operation = component.description[0]
if component.type not in component_types or (
WORD_OPERATION == component.type and operation not in operation_types):
variables, constraints = component.cms_xor_differential_propagation_constraints(self)
else:
print(f'{component.id} not yet implemented')
else:
variables, constraints = component.cms_xor_differential_propagation_constraints(self)

self._variables_list.extend(variables)
self._model_constraints.extend(constraints)
Expand Down
Loading

0 comments on commit a963062

Please sign in to comment.