9 #include "pybind11/pybind11.h"
10 #include "pybind11/stl.h"
20 #ifdef PYBIND11_MODULE
21 PYBIND11_MODULE(hawkeye, m)
23 m.doc() =
"Automated tool to locate arbitrary symmetric cryptographic implementations in gate-level netlists.";
27 py::module m(
"hawkeye",
"Automated tool to locate arbitrary symmetric cryptographic implementations in gate-level netlists.");
30 py::class_<HawkeyePlugin, RawPtrWrapper<HawkeyePlugin>,
BasePluginInterface> py_hawkeye_plugin(
31 m,
"HawkeyePlugin", R
"(This class provides an interface to integrate the HAWKEYE tool as a plugin within the HAL framework.)");
34 The name of the plugin.
40 Get the name of the plugin.
42 :returns: The name of the plugin.
47 The version of the plugin.
53 Get the version of the plugin.
55 :returns: The version of the plugin.
60 The description of the plugin.
66 Get the description of the plugin.
68 :returns: The description of the plugin.
73 A set of plugin names that this plugin depends on.
79 Get a set of plugin names that this plugin depends on.
81 :returns: A set of plugin names that this plugin depends on.
85 py::class_<hawkeye::SBoxDatabase> py_hawkeye_sbox_database(m, "SBoxDatabase", R
"(
86 This class holds and manages known S-boxes and allows to perform efficient S-box lookups in the database.
89 py_hawkeye_sbox_database.def(py::init<>(), R"(
90 Construct an empty S-box database.
93 py_hawkeye_sbox_database.def(py::init<const std::map<std::string, std::vector<u8>>&>(), py::arg(
"sboxes"), R
"(
94 Construct an S-box database from the given S-boxes.
96 :param dict[str,list[int]] sboxes: A dict from S-box name to the respective S-box.
99 py_hawkeye_sbox_database.def_static(
101 [](
const std::filesystem::path& file_path) -> std::optional<hawkeye::SBoxDatabase> {
109 log_error(
"python_context",
"{}", res.get_error().get());
113 py::arg(
"file_path"),
115 Construct an S-box database from file.
117 :param pathlib.Path file_path: The path from which to load the S-box database file.
118 :returns: The S-box database on success, ``None`` otherwise.
119 :rtype: hawkeye.SBoxDatabase or None
123 Compute the linear representative of the given S-box.
125 :param list[int] sbox: The S-box.
126 :returns: The linear representative.
130 py_hawkeye_sbox_database.def(
133 auto res =
self.add(
name, sbox);
140 log_error(
"python_context",
"{}", res.get_error().get());
147 Add an S-box to the database.
149 :param str name: The name of the S-box.
150 :patam list[int] sbox: The S-box.
151 :returns: ``True`` on success, ``False`` otherwise.
155 py_hawkeye_sbox_database.def(
158 auto res =
self.add(sboxes);
165 log_error(
"python_context",
"{}", res.get_error().get());
171 Add multiple S-boxes to the database.
173 :param dict[str,list[int]] sboxes: A dict from S-box name to the respective S-box.
174 :returns: ``True`` on success, ``False`` otherwise.
178 py_hawkeye_sbox_database.def(
180 [](
hawkeye::SBoxDatabase&
self,
const std::filesystem::path& file_path,
bool overwrite =
false) ->
bool {
181 auto res =
self.load(file_path, overwrite);
188 log_error(
"python_context",
"{}", res.get_error().get());
192 py::arg(
"file_path"),
193 py::arg(
"overwrite") =
false,
195 Load S-boxes from a file and add them to the existing database.
197 :param pathlib.Path file_path: The path from which to load the S-box database file.
198 :param bool overwrite: Set ``True`` to overwrite existing database, ``False`` otherwise. Defaults to ``False``.
199 :returns: ``True`` on success, ``False`` otherwise.
203 py_hawkeye_sbox_database.def(
206 auto res =
self.store(file_path);
213 log_error(
"python_context",
"{}", res.get_error().get());
217 py::arg(
"file_path"),
219 Store the S-box database to a database file.
221 :param pathlib.Path file_path: The path to where to store the S-box database file.
222 :returns: ``True`` on success, ``False`` otherwise.
226 py_hawkeye_sbox_database.def(
229 auto res =
self.lookup(sbox);
236 log_error(
"python_context",
"{}", res.get_error().get());
242 Attempt to look up an S-box in the database.
244 :param list[int] sbox: The S-box to look for.
245 :returns: The S-box name on success, ``None`` otherwise.
253 py::class_<hawkeye::DetectionConfiguration> py_hawkeye_detection_configuration(
254 m, "DetectionConfiguration", R
"(This class holds important parameters that configure the candidate search of HAWKEYE, see ``CipherCandidate.detect``.)");
256 py_hawkeye_detection_configuration.def(py::init<>(), R"(
257 Constructs a default DetectionConfiguration.
260 py::enum_<hawkeye::DetectionConfiguration::Control> py_hawkeye_detection_configuration_control(
261 py_hawkeye_detection_configuration,
263 R
"(This enum specifies the checks that are to be performed on the flip-flops of the netlist to determine whether there should be an edge between two flip-flops or not.)");
265 py_hawkeye_detection_configuration_control
268 R
"(If two flip-flops ``ff1`` and ``ff2`` are connected through combinational logic, an edge is added such that ``(ff1,ff2)`` is part of the graph.)")
271 R
"(If two flip-flops ``ff1`` and ``ff2`` are connected through combinational logic and are of the same gate type, an edge is added such that ``(ff1,ff2)`` is part of the graph.)")
275 R
"(If two flip-flops ``ff1`` and ``ff2`` are connected through combinational logic and are controlled through the same input pins, an edge is added such that ``(ff1,ff2)`` is part of the graph.)")
279 R
"(If two flip-flops ``ff1`` and ``ff2`` are connected through combinational logic and are controlled through the same input nets, an edge is added such that ``(ff1,ff2)`` is part of the graph.)")
283 Checks to be performed on flip-flop control inputs during candidate search.
285 :type: hawkeye.DetectionConfiguration.Control
288 py::enum_<hawkeye::DetectionConfiguration::Components> py_hawkeye_detection_configuration_components(py_hawkeye_detection_configuration,
291 This enum specifies whether SCC detection should be used to refine the results of neighborhood discovery. If SCC detection is used, the exploration only stops if the size of the largest discovered SCC saturates. Specifically, it does no longer require the size of the entire neighborhood to saturate.
294 py_hawkeye_detection_configuration_components
300 Determines whether to use SCC detection as part of neighborhood discovery.
302 :type: hawkeye.DetectionConfiguration.Components
306 A list of a list of gate types that are treated as identical types by the candidate search, i.e., when checking equality of the types of two gates that are different but declared equivalent, ``True`` is returned.
308 :type: list[list[str]]
312 Neighborhood discovery iteration timeout.
318 Minimum number of flip-flops of a register for a candidate to be created from it.
323 py::enum_<hawkeye::SBoxStatus> py_hawkeye_sbox_status(m, "SBoxStatus", R
"(The outcome of trying to identify an S-box, see ``CipherCandidate.identify_sboxes``.)");
330 py::class_<hawkeye::SBox> py_hawkeye_sbox(m, "SBox", R
"(
331 An S-box located within the round function of a ``CipherCandidate``.
333 Owned by the candidate it was located in, which also owns every gate it refers to, so an S-box is only valid for as long as its candidate is.
335 The exact size and shape of an S-box is not known in advance, so the search deliberately produces more S-boxes than the round function actually contains, among them smaller ones nested inside larger ones. Identification resolves that, see ``SBoxStatus``.
339 The gates of the connected component that the S-box was located in, including its input flip-flops.
341 :type: list[hal_py.Gate]
345 The input flip-flops of the S-box, ordered by gate ID.
347 These are the flip-flops of the state register that the S-box reads, and hence the only link between the identified S-box and the state bits it operates on. They are **not** ordered by S-box input bit: the database matches under affine equivalence, which absorbs any permutation of the input and output bits, so no bit correspondence is established during identification.
349 :type: list[hal_py.Gate]
353 The output gates of the S-box, ordered by gate ID. Usually combinational gates feeding the linear layer.
355 :type: list[hal_py.Gate]
359 The name of the S-box in the database it was identified as, empty unless ``status`` is ``identified``.
365 The outcome of trying to identify the S-box, ``unidentified`` until ``identify_sboxes`` ran.
367 :type: hawkeye.SBoxStatus
371 Get the combinational gates computing the outputs of the S-box from its input flip-flops.
373 Walks back from the output gates within the component and stops at the flip-flops, so the result is the logic of this S-box alone rather than that of the whole component, which several S-boxes may share.
375 :returns: The combinational gates of the S-box, ordered by gate ID.
376 :rtype: list[hal_py.Gate]
379 py::class_<hawkeye::CipherCandidate> py_hawkeye_cipher_candidate(m, "CipherCandidate", R
"(
380 A candidate for a symmetric cryptographic implementation within a netlist.
382 A candidate is discovered by ``detect`` in stages and is filled in as the analysis proceeds: detection only establishes the state register, ``build_round_function`` adds the combinational logic computing the next state, and ``locate_sboxes`` adds the S-boxes within that logic. Use ``has_round_function`` and ``get_sboxes`` to find out how far a candidate has been analyzed.
384 All gates and nets of a candidate belong to the netlist it was detected in, so they can be inspected and grouped into modules directly.
387 py_hawkeye_cipher_candidate.def(py::init<>(), R"(Default constructor for ``CipherCandidate``.)");
389 py_hawkeye_cipher_candidate.def(py::init<const std::set<Gate*>&>(), py::arg(
"round_reg"), R
"(
390 Construct a round-based candidate, i.e., one whose input and output register are the same.
392 :param set[hal_py.Gate] round_reg: The state register of the candidate.
395 py_hawkeye_cipher_candidate.def(py::init<const std::set<Gate*>&,
const std::set<Gate*>&>(), py::arg(
"in_reg"), py::arg(
"out_reg"), R
"(
396 Construct a candidate from an input and an output register. The candidate is round-based if both registers are equal.
398 :param set[hal_py.Gate] in_reg: The input register of the candidate.
399 :param set[hal_py.Gate] out_reg: The output register of the candidate.
402 py_hawkeye_cipher_candidate.def_static(
404 [](
Netlist* nl,
const std::vector<hawkeye::DetectionConfiguration>& configs,
u32 min_state_size = 40,
const std::vector<Gate*>& start_ffs = {})
405 -> std::optional<std::vector<hawkeye::CipherCandidate>> {
409 return std::move(res.get());
413 log_error(
"python_context",
"cannot detect crypto candidates:\n{}", res.get_error().get());
419 py::arg(
"min_state_size") = 40,
420 py::arg(
"start_ffs") = std::vector<Gate*>(),
422 Attempt to locate candidates for symmetric cryptographic SPN, Feistel, and ARX implementations within a gate-level netlist.
424 Operates on an abstraction of the netlist that holds only the flip-flops as vertices and their connections through combinational logic as edges.
425 Computes the k-neighborhood of every flip-flop for ``k = 1, ..., config.timeout`` and stops once the size of the neighborhood saturates, at which point a candidate is created if the neighborhood is larger than ``config.min_register_size``.
426 Depending on the ``config``, further criteria narrow down the search, see ``DetectionConfiguration.Control`` and ``DetectionConfiguration.Components``.
427 The candidates found are then reduced by discarding those smaller than ``min_state_size`` as well as those that fully contain a smaller candidate.
429 The returned candidates only know their state register, call ``build_round_function`` on a candidate to analyze it further.
431 :param hal_py.Netlist nl: The netlist to operate on.
432 :param list[hawkeye.DetectionConfiguration] configs: The configurations of the detection approaches to be executed one after another on each start flip-flop.
433 :param int min_state_size: The minimum size of a candidate to be considered a cryptographic state register. Defaults to ``40``.
434 :param list[hal_py.Gate] start_ffs: The flip-flops to analyze. Defaults to an empty list, i.e., all flip-flops of the netlist are analyzed.
435 :returns: A list of candidates on success, ``None`` otherwise.
436 :rtype: list[hawkeye.CipherCandidate] or None
439 py_hawkeye_cipher_candidate.def(
440 "build_round_function",
442 auto res =
self.build_round_function();
447 log_error(
"python_context",
"cannot build the round function of the candidate:\n{}", res.get_error().get());
451 Determine the round function of the candidate, i.e., the combinational logic computing the next state.
453 Determines the state logic between the input and the output register together with the state, control, and other inputs of the candidate, and builds the graph that ``locate_sboxes`` operates on.
454 Discards any S-boxes located so far, as they are derived from the round function, which invalidates all S-boxes previously returned by ``locate_sboxes`` and ``get_sboxes``.
456 Recomputes the round function on every call, which only makes a difference if the netlist changed in the meantime.
458 :returns: ``True`` on success, ``False`` otherwise.
462 py_hawkeye_cipher_candidate.def(
465 auto res =
self.locate_sboxes();
470 log_error(
"python_context",
"cannot locate S-boxes:\n{}", res.get_error().get());
475 Try to locate S-boxes within the round function of the candidate.
477 Computes an initial set of connected components within the round function.
478 If these components are reasonably small and their input and output sizes match, they are turned into S-boxes right away.
479 Otherwise, iteratively considers more combinational gates starting from the components' input gates and searches for sub-components.
481 Returns the S-boxes located by an earlier call unchanged instead of locating them again. Call ``clear_sboxes`` to locate them anew.
483 :returns: The S-boxes of the candidate on success, ``None`` otherwise.
484 :rtype: list[hawkeye.SBox] or None
488 Discard the S-boxes located so far.
490 Invalidates all S-boxes previously returned by ``locate_sboxes`` and ``get_sboxes``.
493 py_hawkeye_cipher_candidate.def(
496 auto res =
self.identify_sboxes(db);
501 log_error(
"python_context",
"cannot identify the S-boxes of the candidate:\n{}", res.get_error().get());
506 Try to identify all S-boxes of the candidate by matching them against a database of known S-boxes.
508 Annotates every S-box with the outcome, see ``SBox.status`` and ``SBox.identified_as``. An S-box that is not contained in the database is not an error.
510 Since the exact outputs of an S-box are not known in advance, ``locate_sboxes`` produces many variants of the same S-box that differ only in which of the surplus gates are taken as its outputs but all read the same input flip-flops.
511 Variants are therefore identified as a group, and the group is left as soon as one of them matches, marking the remaining ones ``superseded``.
513 :param hawkeye.SBoxDatabase db: The database of known S-boxes.
514 :returns: The number of identified S-boxes on success, ``None`` otherwise.
518 py_hawkeye_cipher_candidate.def(
521 auto res =
self.identify_sbox(sbox, db);
526 log_error(
"python_context",
"cannot identify S-box:\n{}", res.get_error().get());
532 Try to identify a single S-box of this candidate by matching it against a database of known S-boxes under affine equivalence.
534 Tries every assignment of the control inputs that the S-box reads, as the round function computes the S-box for one of them and something else for the others, and the right one is not known in advance. The remaining inputs are held at ``0``.
536 Does not annotate the S-box, use ``identify_sboxes`` for that.
538 Note that an S-box which simply does not match anything in the database is not an error: in that case an empty string is returned. ``None`` is only returned if the S-box could not be analyzed at all.
540 :param hawkeye.SBox sbox: The S-box to identify. Must be one of the S-boxes of this candidate.
541 :param hawkeye.SBoxDatabase db: The database of known S-boxes.
542 :returns: The name of the matching S-box, or an empty string if no S-box of the database matched. ``None`` on error.
546 py_hawkeye_cipher_candidate.def(
549 auto res =
self.create_modules();
554 log_error(
"python_context",
"cannot create the modules of the candidate:\n{}", res.get_error().get());
558 Write the candidate back into the netlist as a module hierarchy.
560 Creates one module holding the entire candidate, a submodule holding its state register, and one submodule per identified S-box holding its combinational gates.
561 S-boxes that were not identified are skipped, as are identified S-boxes that overlap an S-box module already created, since a gate belongs to exactly one module. Every skipped S-box is reported to the log.
563 :returns: The module holding the candidate on success, ``None`` otherwise.
564 :rtype: hal_py.Module or None
568 Get the netlist that the candidate belongs to.
570 :returns: The netlist of the candidate.
571 :rtype: hal_py.Netlist
575 Get the size of the candidate, i.e., the width of its state register.
577 :returns: The size of the candidate.
582 Check whether the candidate is round-based, i.e., whether its input and output register are the same.
584 :returns: ``True`` if the candidate is round-based, ``False`` if it is pipelined.
589 Check whether the round function of the candidate has been computed, see ``build_round_function``.
591 :returns: ``True`` if the round function has been computed, ``False`` otherwise.
596 Get the input register of the candidate, ordered by gate ID.
598 :returns: The input register of the candidate.
599 :rtype: list[hal_py.Gate]
603 Get the output register of the candidate, ordered by gate ID. Equal to the input register for a round-based candidate.
605 :returns: The output register of the candidate.
606 :rtype: list[hal_py.Gate]
610 Get the combinational logic computing the next state, ordered by gate ID.
612 :returns: The round function of the candidate, empty if it has not been computed yet.
613 :rtype: list[hal_py.Gate]
617 Get all gates of the candidate, i.e., its registers together with its round function, ordered by gate ID.
619 :returns: The gates of the candidate.
620 :rtype: list[hal_py.Gate]
624 Get the S-boxes located within the round function of the candidate.
626 :returns: The S-boxes of the candidate, empty if they have not been located yet.
627 :rtype: list[hawkeye.SBox]
630 py_hawkeye_cipher_candidate.def(
640 py::module_::import(
"hal_plugins.graph_algorithm");
641 return self.get_graph();
645 Get the graph of the round function, in which the gates of the state register are represented by a primary and a shadow vertex so that the feedback of a round-based candidate does not close a cycle.
647 :returns: The graph of the round function, ``None`` if the round function has not been computed yet.
648 :rtype: graph_algorithm.NetlistGraph or None
652 Get the state inputs of the round function.
654 :returns: The state inputs of the candidate.
655 :rtype: set[hal_py.Net]
659 Get the control inputs of the round function.
661 :returns: The control inputs of the candidate.
662 :rtype: set[hal_py.Net]
666 Get the remaining inputs of the round function.
668 :returns: The other inputs of the candidate.
669 :rtype: set[hal_py.Net]
673 Get the state outputs of the round function.
675 :returns: The state outputs of the candidate.
676 :rtype: set[hal_py.Net]
680 Get a dict from each gate of the round function to the input flip-flops it depends on.
682 :returns: A dict from gates to sets of input flip-flops.
683 :rtype: dict[hal_py.Gate,set[hal_py.Gate]]
687 Get a dict from a distance to all gates reachable within at most that distance from any input flip-flop.
689 :returns: A dict from longest distance to a set of gates.
690 :rtype: dict[int,set[hal_py.Gate]]
695 [](
const std::vector<BooleanFunction>& output_functions,
const hawkeye::SBoxDatabase& db) -> std::optional<std::string> {
701 log_error(
"python_context",
"cannot identify S-box:\n{}", res.get_error().get());
704 py::arg(
"output_functions"),
707 Try to identify an S-box given as one Boolean function per output bit by matching it against a database of known S-boxes under affine equivalence.
709 Evaluates the Boolean functions into a truth table and looks that up in the database, so it works on functions that do not come from a netlist at all.
710 Every variable occurring in them is taken as an input bit of the S-box, so substitute anything that is not one beforehand.
712 Note that an S-box which simply does not match anything in the database is not an error: in that case an empty string is returned. ``None`` is only returned if the S-box could not be analyzed at all.
714 :param list[hal_py.BooleanFunction] output_functions: The Boolean functions of the S-box, one per output bit. Their order does not matter, as affine equivalence absorbs a permutation of the output bits.
715 :param hawkeye.SBoxDatabase db: The database of known S-boxes.
716 :returns: The name of the matching S-box, or an empty string if no S-box of the database matched. ``None`` on error.
720 #ifndef PYBIND11_MODULE
This file contains the struct for configuring HAWKEYE's candidate search, see CipherCandidate::detect...
This file contains the class that holds all information on a candidate for a symmetric cryptographic ...
std::string get_name() const override
Get the name of the plugin.
std::set< std::string > get_dependencies() const override
Get the plugin dependencies.
std::string get_description() const override
Get a short description of the plugin.
std::string get_version() const override
Get the version of the plugin.
A directed graph corresponding to a netlist.
A candidate for a symmetric cryptographic implementation within a netlist.
const std::map< u32, std::set< Gate * > > & get_longest_distance_to_gate() const
Get a map from a distance to all gates reachable within at most that distance from any input flip-flo...
std::vector< Gate * > get_gates() const
Get all gates of the candidate, i.e., its registers together with its round function,...
const std::set< Net * > & get_control_inputs() const
Get the control inputs of the round function.
std::vector< SBox * > get_sboxes() const
Get the S-boxes located within the round function of the candidate.
void clear_sboxes()
Discard the S-boxes located so far.
const std::set< Net * > & get_state_outputs() const
Get the state outputs of the round function.
const std::vector< Gate * > & get_input_reg() const
Get the input register of the candidate, ordered by gate ID.
const std::vector< Gate * > & get_round_logic() const
Get the combinational logic computing the next state, ordered by gate ID.
const std::set< Net * > & get_state_inputs() const
Get the state inputs of the round function.
Netlist * get_netlist() const
Get the netlist that the candidate belongs to.
const std::set< Net * > & get_other_inputs() const
Get the remaining inputs of the round function.
bool is_round_based() const
Check whether the candidate is round-based, i.e., whether its input and output register are the same.
static Result< std::vector< CipherCandidate > > detect(Netlist *nl, const std::vector< DetectionConfiguration > &configs, u32 min_state_size=40, const std::vector< Gate * > &start_ffs={})
Attempt to locate candidates for symmetric cryptographic SPN, Feistel, and ARX implementations within...
Result< std::string > identify_sbox(const SBox *sbox, const SBoxDatabase &db) const
Try to identify a single S-box of this candidate by matching it against a database of known S-boxes u...
u32 get_size() const
Get the size of the candidate, i.e., the width of its state register.
const std::vector< Gate * > & get_output_reg() const
Get the output register of the candidate, ordered by gate ID. Equal to the input register for a round...
bool has_round_function() const
Check whether the round function of the candidate has been computed, see build_round_function.
const std::map< Gate *, std::set< Gate * > > & get_input_ffs_of_gate() const
Get a map from each gate of the round function to the input flip-flops it depends on.
Database of known S-boxes.
static Result< SBoxDatabase > from_file(const std::filesystem::path &file_path)
Construct an S-box database from file.
void print() const
Print the database.
static std::vector< u8 > compute_linear_representative(const std::vector< u8 > &sbox)
Compute the linear representative of the given S-box.
#define log_error(channel,...)
const Module * module(const Gate *g, const NodeBoxes &boxes)
This file contains the class that holds a netlist graph.
This file contains all functions related to the HAL plugin API.
This file contains the S-box database class that holds and manages known cryptographic S-boxes up to ...
u32 min_register_size
Minimum number of flip-flops of a register for a candidate to be created from it.
enum hal::hawkeye::DetectionConfiguration::Components components
@ CHECK_NETS
If two flip-flops ff1 and ff2 are connected through combinational logic and are controlled through th...
@ CHECK_TYPE
If two flip-flops ff1 and ff2 are connected through combinational logic and are of the same gate type...
@ CHECK_FF
If two flip-flops ff1 and ff2 are connected through combinational logic, an edge is added such that (...
@ CHECK_PINS
If two flip-flops ff1 and ff2 are connected through combinational logic and are controlled through th...
std::vector< std::vector< std::string > > equivalent_types
A vector of a vector of gate types that are treated as identical types by the candidate search,...
enum hal::hawkeye::DetectionConfiguration::Control control
@ CHECK_SCC
Use SCC detection within the currently explored neighborhood of a start flip-flop.
@ NONE
Do not use SCC detection and instead resort to the simple neighborhood discovery algorithm.
u32 timeout
Neighborhood discovery iteration timeout.
An S-box located within the round function of a CipherCandidate.
std::string identified_as
The name of the S-box in the database it was identified as, empty unless status is identified.
std::vector< Gate * > output_gates
The output gates of the S-box, ordered by gate ID. Usually combinational gates feeding the linear lay...
std::vector< Gate * > component
The gates of the connected component that the S-box was located in, including its input flip-flops.
std::vector< Gate * > input_gates
The input flip-flops of the S-box, ordered by gate ID.
SBoxStatus status
The outcome of trying to identify the S-box, unidentified until identify_sboxes ran.
std::vector< Gate * > get_combinational_gates() const
Get the combinational gates computing the outputs of the S-box from its input flip-flops.