HAL  v4.5.0-124-g47ab54673
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
python_bindings.cpp
Go to the documentation of this file.
2 
4 
9 #include "pybind11/pybind11.h"
10 #include "pybind11/stl.h"
11 
12 namespace py = pybind11;
13 
14 namespace hal
15 {
16 
17  // the name in PYBIND11_MODULE/PYBIND11_PLUGIN *MUST* match the filename of the output library (without extension),
18  // otherwise you will get "ImportError: dynamic module does not define module export function" when importing the module
19 
20 #ifdef PYBIND11_MODULE
21  PYBIND11_MODULE(hawkeye, m)
22  {
23  m.doc() = "Automated tool to locate arbitrary symmetric cryptographic implementations in gate-level netlists.";
24 #else
25  PYBIND11_PLUGIN(hawkeye)
26  {
27  py::module m("hawkeye", "Automated tool to locate arbitrary symmetric cryptographic implementations in gate-level netlists.");
28 #endif // ifdef PYBIND11_MODULE
29 
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.)");
32 
33  py_hawkeye_plugin.def_property_readonly("name", &HawkeyePlugin::get_name, R"(
34  The name of the plugin.
35 
36  :type: str
37  )");
38 
39  py_hawkeye_plugin.def("get_name", &HawkeyePlugin::get_name, R"(
40  Get the name of the plugin.
41 
42  :returns: The name of the plugin.
43  :rtype: str
44  )");
45 
46  py_hawkeye_plugin.def_property_readonly("version", &HawkeyePlugin::get_version, R"(
47  The version of the plugin.
48 
49  :type: str
50  )");
51 
52  py_hawkeye_plugin.def("get_version", &HawkeyePlugin::get_version, R"(
53  Get the version of the plugin.
54 
55  :returns: The version of the plugin.
56  :rtype: str
57  )");
58 
59  py_hawkeye_plugin.def_property_readonly("description", &HawkeyePlugin::get_description, R"(
60  The description of the plugin.
61 
62  :type: str
63  )");
64 
65  py_hawkeye_plugin.def("get_description", &HawkeyePlugin::get_description, R"(
66  Get the description of the plugin.
67 
68  :returns: The description of the plugin.
69  :rtype: str
70  )");
71 
72  py_hawkeye_plugin.def_property_readonly("dependencies", &HawkeyePlugin::get_dependencies, R"(
73  A set of plugin names that this plugin depends on.
74 
75  :type: set[str]
76  )");
77 
78  py_hawkeye_plugin.def("get_dependencies", &HawkeyePlugin::get_dependencies, R"(
79  Get a set of plugin names that this plugin depends on.
80 
81  :returns: A set of plugin names that this plugin depends on.
82  :rtype: set[str]
83  )");
84 
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.
87  )");
88 
89  py_hawkeye_sbox_database.def(py::init<>(), R"(
90  Construct an empty S-box database.
91  )");
92 
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.
95 
96  :param dict[str,list[int]] sboxes: A dict from S-box name to the respective S-box.
97  )");
98 
99  py_hawkeye_sbox_database.def_static(
100  "from_file",
101  [](const std::filesystem::path& file_path) -> std::optional<hawkeye::SBoxDatabase> {
102  auto res = hawkeye::SBoxDatabase::from_file(file_path);
103  if (res.is_ok())
104  {
105  return res.get();
106  }
107  else
108  {
109  log_error("python_context", "{}", res.get_error().get());
110  return std::nullopt;
111  }
112  },
113  py::arg("file_path"),
114  R"(
115  Construct an S-box database from file.
116 
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
120  )");
121 
122  py_hawkeye_sbox_database.def_static("compute_linear_representative", &hawkeye::SBoxDatabase::compute_linear_representative, py::arg("sbox"), R"(
123  Compute the linear representative of the given S-box.
124 
125  :param list[int] sbox: The S-box.
126  :returns: The linear representative.
127  :rtype: list[int]
128  )");
129 
130  py_hawkeye_sbox_database.def(
131  "add",
132  [](hawkeye::SBoxDatabase& self, const std::string& name, const std::vector<u8>& sbox) -> bool {
133  auto res = self.add(name, sbox);
134  if (res.is_ok())
135  {
136  return true;
137  }
138  else
139  {
140  log_error("python_context", "{}", res.get_error().get());
141  return false;
142  }
143  },
144  py::arg("name"),
145  py::arg("sbox"),
146  R"(
147  Add an S-box to the database.
148 
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.
152  :rtype: bool
153  )");
154 
155  py_hawkeye_sbox_database.def(
156  "add",
157  [](hawkeye::SBoxDatabase& self, const std::map<std::string, std::vector<u8>>& sboxes) -> bool {
158  auto res = self.add(sboxes);
159  if (res.is_ok())
160  {
161  return true;
162  }
163  else
164  {
165  log_error("python_context", "{}", res.get_error().get());
166  return false;
167  }
168  },
169  py::arg("sboxes"),
170  R"(
171  Add multiple S-boxes to the database.
172 
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.
175  :rtype: bool
176  )");
177 
178  py_hawkeye_sbox_database.def(
179  "load",
180  [](hawkeye::SBoxDatabase& self, const std::filesystem::path& file_path, bool overwrite = false) -> bool {
181  auto res = self.load(file_path, overwrite);
182  if (res.is_ok())
183  {
184  return true;
185  }
186  else
187  {
188  log_error("python_context", "{}", res.get_error().get());
189  return false;
190  }
191  },
192  py::arg("file_path"),
193  py::arg("overwrite") = false,
194  R"(
195  Load S-boxes from a file and add them to the existing database.
196 
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.
200  :rtype: bool
201  )");
202 
203  py_hawkeye_sbox_database.def(
204  "store",
205  [](const hawkeye::SBoxDatabase& self, const std::filesystem::path& file_path) -> bool {
206  auto res = self.store(file_path);
207  if (res.is_ok())
208  {
209  return true;
210  }
211  else
212  {
213  log_error("python_context", "{}", res.get_error().get());
214  return false;
215  }
216  },
217  py::arg("file_path"),
218  R"(
219  Store the S-box database to a database file.
220 
221  :param pathlib.Path file_path: The path to where to store the S-box database file.
222  :returns: ``True`` on success, ``False`` otherwise.
223  :rtype: bool
224  )");
225 
226  py_hawkeye_sbox_database.def(
227  "lookup",
228  [](const hawkeye::SBoxDatabase& self, const std::vector<u8>& sbox) -> std::optional<std::string> {
229  auto res = self.lookup(sbox);
230  if (res.is_ok())
231  {
232  return res.get();
233  }
234  else
235  {
236  log_error("python_context", "{}", res.get_error().get());
237  return std::nullopt;
238  }
239  },
240  py::arg("sbox"),
241  R"(
242  Attempt to look up an S-box in the database.
243 
244  :param list[int] sbox: The S-box to look for.
245  :returns: The S-box name on success, ``None`` otherwise.
246  :rtype: str or None
247  )");
248 
249  py_hawkeye_sbox_database.def("print", &hawkeye::SBoxDatabase::print, R"(
250  Print the database.
251  )");
252 
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``.)");
255 
256  py_hawkeye_detection_configuration.def(py::init<>(), R"(
257  Constructs a default DetectionConfiguration.
258  )");
259 
260  py::enum_<hawkeye::DetectionConfiguration::Control> py_hawkeye_detection_configuration_control(
261  py_hawkeye_detection_configuration,
262  "Control",
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.)");
264 
265  py_hawkeye_detection_configuration_control
266  .value("CHECK_FF",
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.)")
269  .value("CHECK_TYPE",
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.)")
272  .value(
273  "CHECK_PINS",
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.)")
276  .value(
277  "CHECK_NETS",
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.)")
280  .export_values();
281 
282  py_hawkeye_detection_configuration.def_readwrite("control", &hawkeye::DetectionConfiguration::control, R"(
283  Checks to be performed on flip-flop control inputs during candidate search.
284 
285  :type: hawkeye.DetectionConfiguration.Control
286  )");
287 
288  py::enum_<hawkeye::DetectionConfiguration::Components> py_hawkeye_detection_configuration_components(py_hawkeye_detection_configuration,
289  "Components",
290  R"(
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.
292  )");
293 
294  py_hawkeye_detection_configuration_components
295  .value("NONE", hawkeye::DetectionConfiguration::Components::NONE, R"(Do not use SCC detection and instead resort to the simple neighborhood discovery algorithm.)")
296  .value("CHECK_SCC", hawkeye::DetectionConfiguration::Components::CHECK_SCC, R"(Use SCC detection within the currently explored neighborhood of a start flip-flop.)")
297  .export_values();
298 
299  py_hawkeye_detection_configuration.def_readwrite("components", &hawkeye::DetectionConfiguration::components, R"(
300  Determines whether to use SCC detection as part of neighborhood discovery.
301 
302  :type: hawkeye.DetectionConfiguration.Components
303  )");
304 
305  py_hawkeye_detection_configuration.def_readwrite("equivalent_types", &hawkeye::DetectionConfiguration::equivalent_types, R"(
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.
307 
308  :type: list[list[str]]
309  )");
310 
311  py_hawkeye_detection_configuration.def_readwrite("timeout", &hawkeye::DetectionConfiguration::timeout, R"(
312  Neighborhood discovery iteration timeout.
313 
314  :type: int
315  )");
316 
317  py_hawkeye_detection_configuration.def_readwrite("min_register_size", &hawkeye::DetectionConfiguration::min_register_size, R"(
318  Minimum number of flip-flops of a register for a candidate to be created from it.
319 
320  :type: int
321  )");
322 
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``.)");
324 
325  py_hawkeye_sbox_status.value("unidentified", hawkeye::SBoxStatus::unidentified, R"(Identification ran but the S-box is not contained in the database.)")
326  .value("identified", hawkeye::SBoxStatus::identified, R"(Identification ran and matched, see ``SBox.identified_as``.)")
327  .value("superseded", hawkeye::SBoxStatus::superseded, R"(Identification did not run, another variant of the same S-box was identified before.)")
328  .export_values();
329 
330  py::class_<hawkeye::SBox> py_hawkeye_sbox(m, "SBox", R"(
331  An S-box located within the round function of a ``CipherCandidate``.
332 
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.
334 
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``.
336  )");
337 
338  py_hawkeye_sbox.def_readonly("component", &hawkeye::SBox::component, borrowed(), R"(
339  The gates of the connected component that the S-box was located in, including its input flip-flops.
340 
341  :type: list[hal_py.Gate]
342  )");
343 
344  py_hawkeye_sbox.def_readonly("input_gates", &hawkeye::SBox::input_gates, borrowed(), R"(
345  The input flip-flops of the S-box, ordered by gate ID.
346 
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.
348 
349  :type: list[hal_py.Gate]
350  )");
351 
352  py_hawkeye_sbox.def_readonly("output_gates", &hawkeye::SBox::output_gates, borrowed(), R"(
353  The output gates of the S-box, ordered by gate ID. Usually combinational gates feeding the linear layer.
354 
355  :type: list[hal_py.Gate]
356  )");
357 
358  py_hawkeye_sbox.def_readonly("identified_as", &hawkeye::SBox::identified_as, R"(
359  The name of the S-box in the database it was identified as, empty unless ``status`` is ``identified``.
360 
361  :type: str
362  )");
363 
364  py_hawkeye_sbox.def_readonly("status", &hawkeye::SBox::status, R"(
365  The outcome of trying to identify the S-box, ``unidentified`` until ``identify_sboxes`` ran.
366 
367  :type: hawkeye.SBoxStatus
368  )");
369 
370  py_hawkeye_sbox.def("get_combinational_gates", &hawkeye::SBox::get_combinational_gates, borrowed(), R"(
371  Get the combinational gates computing the outputs of the S-box from its input flip-flops.
372 
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.
374 
375  :returns: The combinational gates of the S-box, ordered by gate ID.
376  :rtype: list[hal_py.Gate]
377  )");
378 
379  py::class_<hawkeye::CipherCandidate> py_hawkeye_cipher_candidate(m, "CipherCandidate", R"(
380  A candidate for a symmetric cryptographic implementation within a netlist.
381 
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.
383 
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.
385  )");
386 
387  py_hawkeye_cipher_candidate.def(py::init<>(), R"(Default constructor for ``CipherCandidate``.)");
388 
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.
391 
392  :param set[hal_py.Gate] round_reg: The state register of the candidate.
393  )");
394 
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.
397 
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.
400  )");
401 
402  py_hawkeye_cipher_candidate.def_static(
403  "detect",
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>> {
406  auto res = hawkeye::CipherCandidate::detect(nl, configs, min_state_size, start_ffs);
407  if (res.is_ok())
408  {
409  return std::move(res.get());
410  }
411  else
412  {
413  log_error("python_context", "cannot detect crypto candidates:\n{}", res.get_error().get());
414  return std::nullopt;
415  }
416  },
417  py::arg("nl"),
418  py::arg("configs"),
419  py::arg("min_state_size") = 40,
420  py::arg("start_ffs") = std::vector<Gate*>(),
421  R"(
422  Attempt to locate candidates for symmetric cryptographic SPN, Feistel, and ARX implementations within a gate-level netlist.
423 
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.
428 
429  The returned candidates only know their state register, call ``build_round_function`` on a candidate to analyze it further.
430 
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
437  )");
438 
439  py_hawkeye_cipher_candidate.def(
440  "build_round_function",
441  [](hawkeye::CipherCandidate& self) -> bool {
442  auto res = self.build_round_function();
443  if (res.is_ok())
444  {
445  return true;
446  }
447  log_error("python_context", "cannot build the round function of the candidate:\n{}", res.get_error().get());
448  return false;
449  },
450  R"(
451  Determine the round function of the candidate, i.e., the combinational logic computing the next state.
452 
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``.
455 
456  Recomputes the round function on every call, which only makes a difference if the netlist changed in the meantime.
457 
458  :returns: ``True`` on success, ``False`` otherwise.
459  :rtype: bool
460  )");
461 
462  py_hawkeye_cipher_candidate.def(
463  "locate_sboxes",
464  [](hawkeye::CipherCandidate& self) -> std::optional<std::vector<hawkeye::SBox*>> {
465  auto res = self.locate_sboxes();
466  if (res.is_ok())
467  {
468  return res.get();
469  }
470  log_error("python_context", "cannot locate S-boxes:\n{}", res.get_error().get());
471  return std::nullopt;
472  },
473  borrowed(),
474  R"(
475  Try to locate S-boxes within the round function of the candidate.
476 
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.
480 
481  Returns the S-boxes located by an earlier call unchanged instead of locating them again. Call ``clear_sboxes`` to locate them anew.
482 
483  :returns: The S-boxes of the candidate on success, ``None`` otherwise.
484  :rtype: list[hawkeye.SBox] or None
485  )");
486 
487  py_hawkeye_cipher_candidate.def("clear_sboxes", &hawkeye::CipherCandidate::clear_sboxes, R"(
488  Discard the S-boxes located so far.
489 
490  Invalidates all S-boxes previously returned by ``locate_sboxes`` and ``get_sboxes``.
491  )");
492 
493  py_hawkeye_cipher_candidate.def(
494  "identify_sboxes",
495  [](hawkeye::CipherCandidate& self, const hawkeye::SBoxDatabase& db) -> std::optional<u32> {
496  auto res = self.identify_sboxes(db);
497  if (res.is_ok())
498  {
499  return res.get();
500  }
501  log_error("python_context", "cannot identify the S-boxes of the candidate:\n{}", res.get_error().get());
502  return std::nullopt;
503  },
504  py::arg("db"),
505  R"(
506  Try to identify all S-boxes of the candidate by matching them against a database of known S-boxes.
507 
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.
509 
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``.
512 
513  :param hawkeye.SBoxDatabase db: The database of known S-boxes.
514  :returns: The number of identified S-boxes on success, ``None`` otherwise.
515  :rtype: int or None
516  )");
517 
518  py_hawkeye_cipher_candidate.def(
519  "identify_sbox",
520  [](const hawkeye::CipherCandidate& self, const hawkeye::SBox* sbox, const hawkeye::SBoxDatabase& db) -> std::optional<std::string> {
521  auto res = self.identify_sbox(sbox, db);
522  if (res.is_ok())
523  {
524  return res.get();
525  }
526  log_error("python_context", "cannot identify S-box:\n{}", res.get_error().get());
527  return std::nullopt;
528  },
529  py::arg("sbox"),
530  py::arg("db"),
531  R"(
532  Try to identify a single S-box of this candidate by matching it against a database of known S-boxes under affine equivalence.
533 
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``.
535 
536  Does not annotate the S-box, use ``identify_sboxes`` for that.
537 
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.
539 
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.
543  :rtype: str or None
544  )");
545 
546  py_hawkeye_cipher_candidate.def(
547  "create_modules",
548  [](hawkeye::CipherCandidate& self) -> Module* {
549  auto res = self.create_modules();
550  if (res.is_ok())
551  {
552  return res.get();
553  }
554  log_error("python_context", "cannot create the modules of the candidate:\n{}", res.get_error().get());
555  return nullptr;
556  },
557  borrowed(), R"(
558  Write the candidate back into the netlist as a module hierarchy.
559 
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.
562 
563  :returns: The module holding the candidate on success, ``None`` otherwise.
564  :rtype: hal_py.Module or None
565  )");
566 
567  py_hawkeye_cipher_candidate.def("get_netlist", &hawkeye::CipherCandidate::get_netlist, py::return_value_policy::reference, R"(
568  Get the netlist that the candidate belongs to.
569 
570  :returns: The netlist of the candidate.
571  :rtype: hal_py.Netlist
572  )");
573 
574  py_hawkeye_cipher_candidate.def("get_size", &hawkeye::CipherCandidate::get_size, R"(
575  Get the size of the candidate, i.e., the width of its state register.
576 
577  :returns: The size of the candidate.
578  :rtype: int
579  )");
580 
581  py_hawkeye_cipher_candidate.def("is_round_based", &hawkeye::CipherCandidate::is_round_based, R"(
582  Check whether the candidate is round-based, i.e., whether its input and output register are the same.
583 
584  :returns: ``True`` if the candidate is round-based, ``False`` if it is pipelined.
585  :rtype: bool
586  )");
587 
588  py_hawkeye_cipher_candidate.def("has_round_function", &hawkeye::CipherCandidate::has_round_function, R"(
589  Check whether the round function of the candidate has been computed, see ``build_round_function``.
590 
591  :returns: ``True`` if the round function has been computed, ``False`` otherwise.
592  :rtype: bool
593  )");
594 
595  py_hawkeye_cipher_candidate.def("get_input_reg", &hawkeye::CipherCandidate::get_input_reg, borrowed(), R"(
596  Get the input register of the candidate, ordered by gate ID.
597 
598  :returns: The input register of the candidate.
599  :rtype: list[hal_py.Gate]
600  )");
601 
602  py_hawkeye_cipher_candidate.def("get_output_reg", &hawkeye::CipherCandidate::get_output_reg, borrowed(), R"(
603  Get the output register of the candidate, ordered by gate ID. Equal to the input register for a round-based candidate.
604 
605  :returns: The output register of the candidate.
606  :rtype: list[hal_py.Gate]
607  )");
608 
609  py_hawkeye_cipher_candidate.def("get_round_logic", &hawkeye::CipherCandidate::get_round_logic, borrowed(), R"(
610  Get the combinational logic computing the next state, ordered by gate ID.
611 
612  :returns: The round function of the candidate, empty if it has not been computed yet.
613  :rtype: list[hal_py.Gate]
614  )");
615 
616  py_hawkeye_cipher_candidate.def("get_gates", &hawkeye::CipherCandidate::get_gates, borrowed(), R"(
617  Get all gates of the candidate, i.e., its registers together with its round function, ordered by gate ID.
618 
619  :returns: The gates of the candidate.
620  :rtype: list[hal_py.Gate]
621  )");
622 
623  py_hawkeye_cipher_candidate.def("get_sboxes", &hawkeye::CipherCandidate::get_sboxes, borrowed(), R"(
624  Get the S-boxes located within the round function of the candidate.
625 
626  :returns: The S-boxes of the candidate, empty if they have not been located yet.
627  :rtype: list[hawkeye.SBox]
628  )");
629 
630  py_hawkeye_cipher_candidate.def(
631  "get_graph",
633  // The graph is a type of the graph_algorithm plugin, and pybind11 can only hand a type to Python once
634  // the module defining it has been imported. Import it here, where the type is about to be handed over,
635  // rather than at module initialization: importing a sibling extension module while this one is still
636  // initializing changes the order in which the two libraries are torn down, which aborts the interpreter
637  // at exit on some platforms. The plugin links against graph_algorithm, so this only fails if this
638  // module is imported outside of the hal_plugins package, in which case get_graph is the only thing that
639  // stops working.
640  py::module_::import("hal_plugins.graph_algorithm");
641  return self.get_graph();
642  },
643  borrowed(),
644  R"(
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.
646 
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
649  )");
650 
651  py_hawkeye_cipher_candidate.def("get_state_inputs", &hawkeye::CipherCandidate::get_state_inputs, borrowed(), R"(
652  Get the state inputs of the round function.
653 
654  :returns: The state inputs of the candidate.
655  :rtype: set[hal_py.Net]
656  )");
657 
658  py_hawkeye_cipher_candidate.def("get_control_inputs", &hawkeye::CipherCandidate::get_control_inputs, borrowed(), R"(
659  Get the control inputs of the round function.
660 
661  :returns: The control inputs of the candidate.
662  :rtype: set[hal_py.Net]
663  )");
664 
665  py_hawkeye_cipher_candidate.def("get_other_inputs", &hawkeye::CipherCandidate::get_other_inputs, borrowed(), R"(
666  Get the remaining inputs of the round function.
667 
668  :returns: The other inputs of the candidate.
669  :rtype: set[hal_py.Net]
670  )");
671 
672  py_hawkeye_cipher_candidate.def("get_state_outputs", &hawkeye::CipherCandidate::get_state_outputs, borrowed(), R"(
673  Get the state outputs of the round function.
674 
675  :returns: The state outputs of the candidate.
676  :rtype: set[hal_py.Net]
677  )");
678 
679  py_hawkeye_cipher_candidate.def("get_input_ffs_of_gate", &hawkeye::CipherCandidate::get_input_ffs_of_gate, borrowed(), R"(
680  Get a dict from each gate of the round function to the input flip-flops it depends on.
681 
682  :returns: A dict from gates to sets of input flip-flops.
683  :rtype: dict[hal_py.Gate,set[hal_py.Gate]]
684  )");
685 
686  py_hawkeye_cipher_candidate.def("get_longest_distance_to_gate", &hawkeye::CipherCandidate::get_longest_distance_to_gate, R"(
687  Get a dict from a distance to all gates reachable within at most that distance from any input flip-flop.
688 
689  :returns: A dict from longest distance to a set of gates.
690  :rtype: dict[int,set[hal_py.Gate]]
691  )");
692 
693  m.def(
694  "identify_sbox",
695  [](const std::vector<BooleanFunction>& output_functions, const hawkeye::SBoxDatabase& db) -> std::optional<std::string> {
696  auto res = hawkeye::CipherCandidate::identify_sbox(output_functions, db);
697  if (res.is_ok())
698  {
699  return res.get();
700  }
701  log_error("python_context", "cannot identify S-box:\n{}", res.get_error().get());
702  return std::nullopt;
703  },
704  py::arg("output_functions"),
705  py::arg("db"),
706  R"(
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.
708 
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.
711 
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.
713 
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.
717  :rtype: str or None
718  )");
719 
720 #ifndef PYBIND11_MODULE
721  return m.ptr();
722 #endif // PYBIND11_MODULE
723  }
724 } // namespace hal
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.
Definition: netlist_graph.h:60
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.
Definition: sbox_database.h:50
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.
uint32_t u32
Definition: defines.h:41
#define log_error(channel,...)
Definition: log.h:78
const Module * module(const Gate *g, const NodeBoxes &boxes)
Definition: defines.h:45
PYBIND11_PLUGIN(hal_py)
std::string name
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.