HAL  v4.5.0-124-g47ab54673
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
sbox_database.cpp
Go to the documentation of this file.
2 
4 #include "rapidjson/document.h"
5 #include "rapidjson/filereadstream.h"
6 #include "rapidjson/stringbuffer.h"
7 #include "rapidjson/writer.h"
8 
9 #include <cmath>
10 #include <fstream>
11 #include <iostream>
12 #include <vector>
13 
14 // debug options to enforce using default implementation
15 // #undef __AVX2__
16 // #undef __ARM_NEON
17 
18 #ifdef __AVX2__
19 #include <immintrin.h>
20 
21 using smallset_t = __m256i;
22 #elif defined(__ARM_NEON)
23 #include <arm_neon.h>
24 
25 using smallset_t = uint64x2x2_t;
26 #else
27 const uint64_t _ONE_ = 1;
28 
30 {
31 public:
32  smallset_t(int preset = 0);
33 
34  void set(u8 bit);
35  bool is_set(u8 bit) const;
36 
37  void dump() const;
38 
39  smallset_t operator|(const smallset_t& other) const;
40  smallset_t operator&(const smallset_t& other) const;
41  smallset_t operator^(const smallset_t& other) const;
42  smallset_t shuffle(u8 shift) const;
43 
44  void to_array(u64* arr, bool swap = false) const;
45 
46  u8 least_bit() const;
47  static int least_bit(u64 dw);
48 
49  int size() const;
50  static int size(u64 dw, int level);
51 
52  bool empty() const;
53 
54 private:
55  u64 dw64[4];
56 };
57 
59 {
60  memset(dw64, 0, sizeof(dw64));
61  switch (preset)
62  {
63  case 8:
64  dw64[0] = 0xFF;
65  break;
66  case 16:
67  dw64[0] = 0xFFFF;
68  break;
69  case 32:
70  dw64[0] = 0xFFFFFFFF;
71  break;
72  case 64:
73  memset(dw64, 0xFF, sizeof(u64));
74  break;
75  case 128:
76  memset(dw64, 0xFF, 2 * sizeof(u64));
77  break;
78  case 256:
79  memset(dw64, 0xFF, sizeof(dw64));
80  break;
81  }
82 }
83 
84 bool smallset_t::empty() const
85 {
86  for (int i = 0; i < 4; i++)
87  {
88  if (dw64[i])
89  {
90  return false;
91  }
92  }
93  return true;
94 }
95 
97 {
98  for (int i = 0; i < 4; i++)
99  {
100  if (dw64[i])
101  {
102  return i * 64 + least_bit(dw64[i]);
103  }
104  }
105  std::cerr << "Called smallset_t::least_bit() on empty set\n" << std::endl;
106  return 0;
107 }
108 
110 {
111  smallset_t retval(*this);
112  if (shift & 0x80)
113  {
114  smallset_t temp = retval;
115  retval.dw64[0] = temp.dw64[2];
116  retval.dw64[1] = temp.dw64[3];
117  retval.dw64[2] = temp.dw64[0];
118  retval.dw64[3] = temp.dw64[1];
119  }
120  if (shift & 0x40)
121  {
122  smallset_t temp = retval;
123  retval.dw64[0] = temp.dw64[1];
124  retval.dw64[1] = temp.dw64[0];
125  retval.dw64[2] = temp.dw64[3];
126  retval.dw64[3] = temp.dw64[2];
127  }
128  if (shift & 0x20)
129  {
130  for (int i = 0; i < 4; i++)
131  {
132  retval.dw64[i] = ((retval.dw64[i] & 0xFFFFFFFF00000000ULL) >> 32) | ((retval.dw64[i] & 0x00000000FFFFFFFFULL) << 32);
133  }
134  }
135  if (shift & 0x10)
136  {
137  for (int i = 0; i < 4; i++)
138  {
139  retval.dw64[i] = ((retval.dw64[i] & 0xFFFF0000FFFF0000ULL) >> 16) | ((retval.dw64[i] & 0x0000FFFF0000FFFFULL) << 16);
140  }
141  }
142  if (shift & 0x08)
143  {
144  for (int i = 0; i < 4; i++)
145  {
146  retval.dw64[i] = ((retval.dw64[i] & 0xFF00FF00FF00FF00ULL) >> 8) | ((retval.dw64[i] & 0x00FF00FF00FF00FFULL) << 8);
147  }
148  }
149  if (shift & 0x04)
150  {
151  for (int i = 0; i < 4; i++)
152  {
153  retval.dw64[i] = ((retval.dw64[i] & 0xF0F0F0F0F0F0F0F0ULL) >> 4) | ((retval.dw64[i] & 0x0F0F0F0F0F0F0F0FULL) << 4);
154  }
155  }
156  if (shift & 0x02)
157  {
158  for (int i = 0; i < 4; i++)
159  {
160  retval.dw64[i] = ((retval.dw64[i] & 0xCCCCCCCCCCCCCCCCULL) >> 2) | ((retval.dw64[i] & 0x3333333333333333ULL) << 2);
161  }
162  }
163  if (shift & 0x01)
164  {
165  for (int i = 0; i < 4; i++)
166  {
167  retval.dw64[i] = ((retval.dw64[i] & 0xAAAAAAAAAAAAAAAAULL) >> 1) | ((retval.dw64[i] & 0x5555555555555555ULL) << 1);
168  }
169  }
170  return retval;
171 }
172 
173 void smallset_t::to_array(u64* arr, bool swap) const
174 {
175  for (int i = 0; i < 4; i++)
176  {
177  arr[i] = dw64[swap ? 3 - i : i];
178  }
179 }
180 
182 {
183  dw64[bit / 64] |= (_ONE_ << (bit % 64));
184 }
185 
186 bool smallset_t::is_set(u8 bit) const
187 {
188  return (dw64[bit / 64] & (_ONE_ << (bit % 64))) != 0;
189 }
190 
191 int smallset_t::size() const
192 {
193  int retval = 0;
194  for (int i = 0; i < 4; i++)
195  retval += size(dw64[i], 0);
196  return retval;
197 }
198 
199 int smallset_t::size(u64 dw, int level)
200 {
201  static const u64 segmask[] = {0xFFFFFFFF, 0xFFFF, 0xFF, 0xF};
202  static const int segshft[] = {32, 16, 8, 4};
203  static const int szlookup[16] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
204 
205  if (level >= 4)
206  return szlookup[dw & 0xF];
207 
208  int retval = 0;
209  retval += size(dw & segmask[level], level + 1);
210  dw >>= segshft[level];
211  retval += size(dw & segmask[level], level + 1);
212 
213  return retval;
214 }
215 
217 {
218  static const u64 segmask[] = {0xFFFFFFFF, 0xFFFF, 0xFF, 0xF};
219  static const int lblookup[16] = {-61, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0};
220 
221  int retval = 0;
222  int segval = 32;
223 
224  for (int iseg = 0; iseg < 4; iseg++)
225  {
226  if (!(dw & segmask[iseg]))
227  {
228  retval += segval;
229  dw >>= segval;
230  }
231  segval /= 2;
232  }
233 
234  return retval + lblookup[dw & 0xF];
235 }
236 
237 void smallset_t::dump() const
238 {
239  for (int i = 3; i >= 0; i--)
240  printf("%016lx", dw64[i]);
241  printf("\n");
242 
243  for (unsigned int i = 0; i < 256; i++)
244  {
245  if (dw64[i / 64] & (_ONE_ << (i % 64)))
246  printf("%8d\n", i);
247  }
248 }
249 
251 {
252  smallset_t retval = other;
253  for (int i = 0; i < 4; i++)
254  retval.dw64[i] |= dw64[i];
255  return retval;
256 }
257 
259 {
260  smallset_t retval = other;
261  for (int i = 0; i < 4; i++)
262  retval.dw64[i] &= dw64[i];
263  return retval;
264 }
265 
267 {
268  smallset_t retval = other;
269  for (int i = 0; i < 4; i++)
270  retval.dw64[i] ^= dw64[i];
271  return retval;
272 }
273 
274 #endif
275 
276 namespace hal
277 {
278  namespace hawkeye
279  {
280  namespace
281  {
296  constexpr u64 LINEAR_REPRESENTATIVE_BUDGET = 250000;
297 
298  std::vector<u8> compute_linear_representative_bounded(const std::vector<u8>& sbox, u64& budget);
299  } // namespace
300 
301  SBoxDatabase::SBoxDatabase(const std::map<std::string, std::vector<u8>>& sboxes)
302  {
303  add(sboxes).is_ok();
304  }
305 
306  Result<SBoxDatabase> SBoxDatabase::from_file(const std::filesystem::path& file_path)
307  {
308  auto db = SBoxDatabase();
309  if (const auto res = db.load(file_path); res.is_ok())
310  {
311  return OK(db);
312  }
313  else
314  {
315  return ERR(res.get_error());
316  }
317  }
318 
319  Result<std::monostate> SBoxDatabase::add(const std::string& name, const std::vector<u8>& sbox)
320 
321  {
322  u32 bit_size = std::log2(sbox.size());
323 
324  if (bit_size > 8)
325  {
326  return ERR("S-box '" + name + "' has bit-size greater 8, but only S-boxes of up to 8 bits are supported");
327  }
328 
329  for (size_t alpha = 0; alpha < sbox.size(); alpha++)
330  {
331  std::vector<u8> sbox_alpha;
332  for (u32 i = 0; i < sbox.size(); i++)
333  {
334  sbox_alpha.push_back(sbox.at(i) ^ alpha);
335  }
336  u64 budget = LINEAR_REPRESENTATIVE_BUDGET;
337  auto lin_rep = compute_linear_representative_bounded(sbox_alpha, budget);
338  if (budget == 0)
339  {
340  // storing a representative from a truncated search would silently break every lookup against this
341  // entry, and a real S-box is never degenerate enough to exhaust the search in the first place
342  return ERR("cannot add S-box '" + name + "' to the database: the canonical form search was abandoned, the S-box is too close to linear");
343  }
344  m_data[bit_size][lin_rep].push_back(std::make_pair(name, alpha));
345  }
346  return OK({});
347  }
348 
349  Result<std::monostate> SBoxDatabase::add(const std::map<std::string, std::vector<u8>>& sboxes)
350  {
351  for (const auto& [name, sbox] : sboxes)
352  {
353  if (const auto res = add(name, sbox); res.is_error())
354  {
355  return ERR(res.get_error());
356  }
357  }
358  return OK({});
359  }
360 
361  Result<std::monostate> SBoxDatabase::load(const std::filesystem::path& file_path, bool overwrite)
362  {
363  FILE* fp = fopen(file_path.string().c_str(), "r");
364  if (fp == NULL)
365  {
366  return ERR("could not parse S-box database file '" + file_path.string() + "' : unable to open file");
367  }
368 
369  char buffer[65536];
370  rapidjson::FileReadStream is(fp, buffer, sizeof(buffer));
371  rapidjson::Document document;
372  document.ParseStream<0, rapidjson::UTF8<>, rapidjson::FileReadStream>(is);
373  fclose(fp);
374 
375  if (document.HasParseError())
376  {
377  return ERR("could not parse S-box database file '" + file_path.string() + "': failed parsing JSON format");
378  }
379 
380  if (overwrite)
381  {
382  m_data.clear();
383  }
384 
385  for (auto size_it = document.MemberBegin(); size_it != document.MemberEnd(); ++size_it)
386  {
387  u32 bit_size = std::stoul(std::string(size_it->name.GetString()));
388  const rapidjson::Value& cipher_val = size_it->value;
389 
390  for (auto cipher_it = cipher_val.MemberBegin(); cipher_it != cipher_val.MemberEnd(); ++cipher_it)
391  {
392  std::string cipher_name = cipher_it->name.GetString();
393  const rapidjson::Value& const_val = cipher_it->value;
394 
395  for (auto const_it = const_val.MemberBegin(); const_it != const_val.MemberEnd(); ++const_it)
396  {
397  u8 const_alpha = (u8)std::stoul(std::string(const_it->name.GetString()));
398  const rapidjson::Value& lin_rep_val = const_it->value;
399 
400  std::vector<u8> lin_rep;
401  for (u32 i = 0; i < lin_rep_val.Size(); i++)
402  {
403  lin_rep.push_back((u8)(lin_rep_val[i].GetUint()));
404  }
405 
406  m_data[bit_size][lin_rep].push_back(std::make_pair(cipher_name, const_alpha));
407  }
408  }
409  }
410 
411  return OK({});
412  }
413 
414  Result<std::monostate> SBoxDatabase::store(const std::filesystem::path& file_path) const
415  {
416  FILE* fp = fopen(file_path.string().c_str(), "w");
417  if (fp == NULL)
418  {
419  return ERR("could not write S-box database file '" + file_path.string() + "' : unable to open file");
420  }
421 
422  rapidjson::Document document;
423  document.SetObject();
424 
425  rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
426 
427  for (const auto& [bit_size, lin_rep_map] : m_data)
428  {
429  std::map<std::string, std::map<u8, std::vector<u8>>> pretty_data;
430  for (const auto& [lin_rep, cipher_vec] : lin_rep_map)
431  {
432  for (const auto& [name, alpha] : cipher_vec)
433  {
434  pretty_data[name][alpha] = lin_rep;
435  }
436  }
437 
438  rapidjson::Value cipher_json(rapidjson::kObjectType);
439  for (const auto& [cipher_name, lin_rep_map] : pretty_data)
440  {
441  rapidjson::Value alpha_json(rapidjson::kObjectType);
442  for (const auto& [const_alph, lin_rep] : lin_rep_map)
443  {
444  rapidjson::Value lin_rep_json(rapidjson::kArrayType);
445  for (const auto val : lin_rep)
446  {
447  lin_rep_json.PushBack(val, allocator);
448  }
449  alpha_json.AddMember(rapidjson::Value(std::to_string(const_alph).c_str(), allocator).Move(), lin_rep_json, allocator);
450  }
451  cipher_json.AddMember(rapidjson::Value(cipher_name.c_str(), allocator).Move(), alpha_json, allocator);
452  }
453  document.AddMember(rapidjson::Value(std::to_string(bit_size).c_str(), allocator).Move(), cipher_json, allocator);
454  }
455 
456  rapidjson::StringBuffer buffer;
457  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
458 
459  document.Accept(writer);
460 
461  std::ofstream file(file_path);
462  if (!file.is_open())
463  {
464  return ERR("could not store the S-box database: failed to open file '" + file_path.string() + "'");
465  }
466  file << buffer.GetString();
467  file.close();
468 
469  return OK({});
470  }
471 
472  Result<std::string> SBoxDatabase::lookup(const std::vector<u8>& sbox) const
473  {
474  u32 bit_size = std::log2(sbox.size());
475 
476  if (bit_size > 8)
477  {
478  return ERR("S-box has bit-size greater 8, but only S-boxes of up to 8 bits are supported");
479  }
480 
481  const auto size_it = m_data.find(bit_size);
482  if (size_it == m_data.end())
483  {
484  return ERR("no S-box of matching bit-size of " + std::to_string(bit_size) + " bits contained in database");
485  }
486 
487  // beta has to count beyond the largest table index, so it must be wider than a table entry: a u8 stays
488  // below a size of 256 forever, which made this loop endless for every 8-bit S-box not in the database
489  for (u32 beta = 0; beta < sbox.size(); beta++)
490  {
491  std::vector<u8> sbox_beta;
492  for (u32 i = 0; i < sbox.size(); i++)
493  {
494  sbox_beta.push_back(sbox.at(i) ^ beta);
495  }
496 
497  u64 budget = LINEAR_REPRESENTATIVE_BUDGET;
498  auto lin_rep = compute_linear_representative_bounded(sbox_beta, budget);
499  if (budget == 0)
500  {
501  // XORing a constant onto the outputs does not change how close to linear the table is, so if the
502  // search degenerates for one beta it degenerates for all of them, and no real S-box ever does
503  log_info("hawkeye", "giving up the S-box lookup, as the table is too close to linear to be a real S-box.");
504  break;
505  }
506 
507  const auto& matching_size_data = std::get<1>(*size_it);
508  const auto rep_it = matching_size_data.find(lin_rep);
509  if (rep_it != matching_size_data.end())
510  {
511  return OK(rep_it->second.front().first);
512  }
513  }
514 
515  return ERR("no match found within database");
516  }
517 
518  void SBoxDatabase::print() const
519  {
520  for (const auto& [bit_size, lin_rep_map] : m_data)
521  {
522  std::cout << std::endl;
523  std::cout << "### WIDTH: " << bit_size << std::endl;
524  std::cout << "#######################" << std::endl;
525 
526  std::map<std::string, std::map<u8, std::vector<u8>>> pretty_data;
527  for (const auto& [lin_rep, cipher_vec] : lin_rep_map)
528  {
529  for (const auto& [name, alpha] : cipher_vec)
530  {
531  pretty_data[name][alpha] = lin_rep;
532  }
533  }
534 
535  for (const auto& [cipher_name, lin_rep_map] : pretty_data)
536  {
537  std::cout << "* " << cipher_name << std::endl;
538 
539  for (const auto& [const_alph, lin_rep] : lin_rep_map)
540  {
541  std::cout << " - " << (u32)const_alph << ": [" << (u32)(lin_rep.at(0));
542  for (u32 i = 1; i < lin_rep.size(); i++)
543  {
544  std::cout << ", " << (u32)(lin_rep.at(i));
545  }
546  std::cout << "]" << std::endl;
547  }
548  }
549  }
550 
551  std::cout << std::endl;
552  }
553 
554  namespace
555  {
556  void smallset_print(const std::string& name, const smallset_t& a)
557  {
558  u64 elements[4];
559 #ifdef __AVX2__
560 
561  elements[0] = _mm256_extract_epi64(a, 3);
562  elements[1] = _mm256_extract_epi64(a, 2);
563  elements[2] = _mm256_extract_epi64(a, 1);
564  elements[3] = _mm256_extract_epi64(a, 0);
565 #elif defined(__ARM_NEON)
566  elements[0] = a.val[1][1];
567  elements[1] = a.val[1][0];
568  elements[2] = a.val[0][1];
569  elements[3] = a.val[0][0];
570 #else
571  a.to_array(elements, true);
572 #endif
573  std::cout << name << ": 0b";
574  for (u32 i = 0; i < 4; i++)
575  {
576  for (int j = 63; j >= 0; j--)
577  {
578  u32 bit = (elements[i] >> j) & 1;
579  std::cout << bit;
580  }
581  std::cout << " ";
582  }
583  std::cout << std::endl;
584  }
585 
586  u8 smallset_least_element(const smallset_t& a)
587  {
588  u64 chunks[4];
589 #ifdef __AVX2__
590  chunks[0] = _mm256_extract_epi64(a, 0);
591  chunks[1] = _mm256_extract_epi64(a, 1);
592  chunks[2] = _mm256_extract_epi64(a, 2);
593  chunks[3] = _mm256_extract_epi64(a, 3);
594 #elif defined(__ARM_NEON)
595  chunks[0] = a.val[0][0];
596  chunks[1] = a.val[0][1];
597  chunks[2] = a.val[1][0];
598  chunks[3] = a.val[1][1];
599 #else
600  return a.least_bit();
601 #endif
602  for (u32 i = 0; i < 4; i++)
603  {
604  u64 current_chunk = chunks[i];
605  if (current_chunk != 0)
606  {
607  u8 idx = __builtin_ctzll(current_chunk) + i * 64;
608  return idx;
609  }
610  }
611 
612  // set is empty -- caller's fault
613  std::cout << "CALLED LEAST ELEMENT ON EMPTY SET!" << std::endl;
614  return 0;
615  }
616 
617  inline smallset_t smallset_intersect(const smallset_t& a, const smallset_t& b)
618  {
619 #ifdef __AVX2__
620  return _mm256_and_si256(a, b);
621 #elif defined(__ARM_NEON)
622  return {vandq_u64(a.val[0], b.val[0]), vandq_u64(a.val[1], b.val[1])};
623 #else
624  return (a & b);
625 #endif
626  }
627 
628  inline smallset_t smallset_union(const smallset_t& a, const smallset_t& b)
629  {
630 #ifdef __AVX2__
631  return _mm256_or_si256(a, b);
632 #elif defined(__ARM_NEON)
633  return {vorrq_u64(a.val[0], b.val[0]), vorrq_u64(a.val[1], b.val[1])};
634 #else
635  return (a | b);
636 #endif
637  }
638 
639  inline u16 smallset_size(const smallset_t& a)
640  {
641  u16 count = 0;
642 #ifdef __AVX2__
643  u64 chunk = _mm256_extract_epi64(a, 0);
644  count += __builtin_popcountll(chunk);
645  chunk = _mm256_extract_epi64(a, 1);
646  count += __builtin_popcountll(chunk);
647  chunk = _mm256_extract_epi64(a, 2);
648  count += __builtin_popcountll(chunk);
649  chunk = _mm256_extract_epi64(a, 3);
650  count += __builtin_popcountll(chunk);
651 #elif defined(__ARM_NEON)
652  count += __builtin_popcountll(a.val[0][0]);
653  count += __builtin_popcountll(a.val[0][1]);
654  count += __builtin_popcountll(a.val[1][0]);
655  count += __builtin_popcountll(a.val[1][1]);
656 #else
657  return a.size();
658 #endif
659  return count;
660  }
661 
662  inline bool smallset_is_empty(const smallset_t& a)
663  {
664 #ifdef __AVX2__
665  return _mm256_testz_si256(a, a);
666 #elif defined(__ARM_NEON)
667  auto tmp = vandq_u64(vceqzq_u64(a.val[0]), vceqzq_u64(a.val[1]));
668  return (tmp[0] & tmp[1]) & 1;
669 #else
670  return a.empty();
671 #endif
672  }
673 
674  smallset_t smallset_add_element(const smallset_t& a, const u8 elm)
675  {
676  // compute union of a and {elm}
677  u32 index = elm / 64;
678 #ifdef __AVX2__
679  u64 mask[4] = {0};
680  mask[index] = (u64)1 << (elm % 64);
681  __m256i _mask = _mm256_set_epi64x(mask[3], mask[2], mask[1], mask[0]);
682  return _mm256_or_si256(a, _mask);
683 #elif defined(__ARM_NEON)
684  u64 mask[2] = {0};
685  mask[index & 1] = (u64)1 << (elm % 64);
686  auto _mask = vld1q_u64(mask);
687  if (index < 2)
688  {
689  return {vorrq_u64(a.val[0], _mask), a.val[1]};
690  }
691  else
692  {
693  return {a.val[0], vorrq_u64(a.val[1], _mask)};
694  }
695 #else
696  smallset_t retval(a);
697  retval.set(elm);
698  return retval;
699 #endif
700  }
701 
702  smallset_t smallset_shift(const smallset_t& b, const u8 shift)
703  {
704 #if !defined(__AVX2__) && !defined(__ARM_NEON)
705  return b.shuffle(shift);
706 #endif
707 
708  auto a = b;
709  // compute a \oplus shift
710  if ((shift >> 7) & 0x1)
711  {
712 #ifdef __AVX2__
713  a = _mm256_permute2x128_si256(a, a, 1);
714 #elif defined(__ARM_NEON)
715  a.val[0] = b.val[1];
716  a.val[1] = b.val[0];
717 #endif
718  }
719  if ((shift >> 6) & 0x1)
720  {
721 #ifdef __AVX2__
722  a = _mm256_permute4x64_epi64(a, _MM_SHUFFLE(2, 3, 0, 1));
723 #elif defined(__ARM_NEON)
724  a.val[0] = vextq_u64(a.val[0], a.val[0], 1);
725  a.val[1] = vextq_u64(a.val[1], a.val[1], 1);
726 #endif
727  }
728  if ((shift >> 5) & 0x1)
729  {
730 #ifdef __AVX2__
731  a = _mm256_shuffle_epi32(a, _MM_SHUFFLE(2, 3, 0, 1));
732 #elif defined(__ARM_NEON)
733  a.val[0] = (uint64x2_t) vrev64q_u32((uint32x4_t) a.val[0]);
734  a.val[1] = (uint64x2_t) vrev64q_u32((uint32x4_t) a.val[1]);
735 
736 #endif
737  }
738  if ((shift >> 4) & 0x1)
739  {
740 #ifdef __AVX2__
741  a = _mm256_shufflelo_epi16(a, _MM_SHUFFLE(2, 3, 0, 1));
742  a = _mm256_shufflehi_epi16(a, _MM_SHUFFLE(2, 3, 0, 1));
743 #elif defined(__ARM_NEON)
744  a.val[0] = (uint64x2_t) vrev64q_u16((uint16x8_t) a.val[0]);
745  a.val[0] = (uint64x2_t) vrev64q_u32((uint32x4_t) a.val[0]);
746  a.val[1] = (uint64x2_t) vrev64q_u16((uint16x8_t) a.val[1]);
747  a.val[1] = (uint64x2_t) vrev64q_u32((uint32x4_t) a.val[1]);
748 #endif
749  }
750  if ((shift >> 3) & 0x1)
751  {
752 #ifdef __AVX2__
753  const __m256i mask = _mm256_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
754  a = _mm256_shuffle_epi8(a, mask);
755 #elif defined(__ARM_NEON)
756  a.val[0] = (uint64x2_t) vrev64q_u8 ((uint8x16_t) a.val[0]);
757  a.val[0] = (uint64x2_t) vrev64q_u16((uint16x8_t) a.val[0]);
758  a.val[1] = (uint64x2_t) vrev64q_u8 ((uint8x16_t) a.val[1]);
759  a.val[1] = (uint64x2_t) vrev64q_u16((uint16x8_t) a.val[1]);
760 #endif
761  }
762  if ((shift >> 2) & 0x1)
763  {
764 #ifdef __AVX2__
765  const __m256i mask_high = _mm256_set1_epi8((char)0xF0);
766  const __m256i mask_low = _mm256_set1_epi8(0x0F);
767  const __m256i high = _mm256_and_si256(a, mask_high);
768  const __m256i low = _mm256_and_si256(a, mask_low);
769  a = _mm256_or_si256(_mm256_srli_epi16(high, 4), _mm256_slli_epi16(low, 4));
770 #elif defined(__ARM_NEON)
771  const auto mask_high = vdupq_n_u64(0xF0F0F0F0F0F0F0F0);
772  const auto mask_low = vdupq_n_u64(0x0F0F0F0F0F0F0F0F);
773 
774  for (u32 i = 0; i < 2; i++)
775  {
776  const auto high = vandq_u64(a.val[i], mask_high);
777  const auto low = vandq_u64(a.val[i], mask_low);
778 
779  a.val[i] = vorrq_u64(vshrq_n_u64(high, 4), vshlq_n_u64(low, 4));
780  }
781 #endif
782  }
783  if ((shift >> 1) & 0x1)
784  {
785 #ifdef __AVX2__
786  const __m256i mask_high = _mm256_set1_epi8((char)0xCC);
787  const __m256i mask_low = _mm256_set1_epi8(0x33);
788  const __m256i high = _mm256_and_si256(a, mask_high);
789  const __m256i low = _mm256_and_si256(a, mask_low);
790  a = _mm256_or_si256(_mm256_srli_epi16(high, 2), _mm256_slli_epi16(low, 2));
791 #elif defined(__ARM_NEON)
792  const auto mask_high = vdupq_n_u64(0xCCCCCCCCCCCCCCCC);
793  const auto mask_low = vdupq_n_u64(0x3333333333333333);
794 
795  for (u32 i = 0; i < 2; i++)
796  {
797  const auto high = vandq_u64(a.val[i], mask_high);
798  const auto low = vandq_u64(a.val[i], mask_low);
799 
800  a.val[i] = vorrq_u64(vshrq_n_u64(high, 2), vshlq_n_u64(low, 2));
801  }
802 #endif
803  }
804  if (shift & 0x1)
805  {
806 #ifdef __AVX2__
807  const __m256i mask_high = _mm256_set1_epi8((char)0xAA);
808  const __m256i mask_low = _mm256_set1_epi8(0x55);
809  const __m256i high = _mm256_and_si256(a, mask_high);
810  const __m256i low = _mm256_and_si256(a, mask_low);
811  a = _mm256_or_si256(_mm256_srli_epi16(high, 1), _mm256_slli_epi16(low, 1));
812 #elif defined(__ARM_NEON)
813  const auto mask_high = vdupq_n_u64(0xAAAAAAAAAAAAAAAA);
814  const auto mask_low = vdupq_n_u64(0x5555555555555555);
815 
816  for (u32 i = 0; i < 2; i++)
817  {
818  const auto high = vandq_u64(a.val[i], mask_high);
819  const auto low = vandq_u64(a.val[i], mask_low);
820 
821  a.val[i] = vorrq_u64(vshrq_n_u64(high, 1), vshlq_n_u64(low, 1));
822  }
823 #endif
824  }
825  return a;
826  }
827 
828  smallset_t smallset_shift_union(const smallset_t& a, const u8 shift)
829  {
830  smallset_t b = smallset_shift(a, shift);
831  return smallset_union(a, b);
832  }
833 
834  std::vector<u8> smallset_get_elements(const smallset_t& a)
835  {
836  std::vector<u8> e;
837  u64 chunks[4];
838 #ifdef __AVX2__
839  chunks[0] = _mm256_extract_epi64(a, 0);
840  chunks[1] = _mm256_extract_epi64(a, 1);
841  chunks[2] = _mm256_extract_epi64(a, 2);
842  chunks[3] = _mm256_extract_epi64(a, 3);
843 #elif defined(__ARM_NEON)
844  chunks[0] = a.val[0][0];
845  chunks[1] = a.val[0][1];
846  chunks[2] = a.val[1][0];
847  chunks[3] = a.val[1][1];
848 #else
849  a.to_array(chunks);
850 #endif
851  for (u32 i = 0; i < 4; i++)
852  {
853  u64 current_chunk = chunks[i];
854  while (current_chunk != 0)
855  {
856  u8 idx = __builtin_ctzll(current_chunk) + i * 64;
857  e.push_back(idx);
858  current_chunk &= (current_chunk - 1);
859  }
860  }
861  return e;
862  }
863 
864  inline smallset_t smallset_init_empty()
865  {
866 #ifdef __AVX2__
867  return _mm256_setzero_si256();
868 #elif defined(__ARM_NEON)
869  return {vdupq_n_u64(0), vdupq_n_u64(0)};
870 #else
871  return smallset_t();
872 #endif
873  }
874 
875  inline smallset_t smallset_init_full(const u32 len)
876  {
877 #if !defined(__AVX2__) && !defined(__ARM_NEON)
878  return smallset_t(len);
879 #endif
880  // N must be in {256, 128, 64, 32, 16, 8}
881  if (len == 256)
882  {
883 #ifdef __AVX2__
884  return _mm256_set_epi64x(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF);
885 #elif defined(__ARM_NEON)
886  return {vdupq_n_u64(0xFFFFFFFFFFFFFFFF), vdupq_n_u64(0xFFFFFFFFFFFFFFFF)};
887 #endif
888  }
889  else if (len == 128)
890  {
891 #ifdef __AVX2__
892  return _mm256_set_epi64x(0, 0, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF);
893 #elif defined(__ARM_NEON)
894  return {vdupq_n_u64(0xFFFFFFFFFFFFFFFF), vdupq_n_u64(0)};
895 #endif
896  }
897  else if (len == 64)
898  {
899 #ifdef __AVX2__
900  return _mm256_set_epi64x(0, 0, 0, 0xFFFFFFFFFFFFFFFF);
901 #elif defined(__ARM_NEON)
902  auto tmp = vdupq_n_u64(0);
903  return {vsetq_lane_u64(0xFFFFFFFFFFFFFFFF, tmp, 0), vdupq_n_u64(0)};
904 #endif
905  }
906  else if (len == 32)
907  {
908 #ifdef __AVX2__
909  return _mm256_set_epi64x(0, 0, 0, 0xFFFFFFFF);
910 #elif defined(__ARM_NEON)
911  auto tmp = vdupq_n_u64(0);
912  return {(uint64x2_t)vsetq_lane_u32(0xFFFFFFFF, (uint32x4_t)tmp, 0), vdupq_n_u64(0)};
913 #endif
914  }
915  else if (len == 16)
916  {
917 #ifdef __AVX2__
918  return _mm256_set_epi64x(0, 0, 0, 0xFFFF);
919 #elif defined(__ARM_NEON)
920  auto tmp = vdupq_n_u64(0);
921  return {(uint64x2_t)vsetq_lane_u16(0xFFFF, (uint16x8_t)tmp, 0), vdupq_n_u64(0)};
922 #endif
923  }
924  else if (len == 8)
925  {
926 #ifdef __AVX2__
927  return _mm256_set_epi64x(0, 0, 0, 0xFF);
928 #elif defined(__ARM_NEON)
929  auto tmp = vdupq_n_u64(0);
930  return {(uint64x2_t)vsetq_lane_u8(0xFF, (uint8x16_t)tmp, 0), vdupq_n_u64(0)};
931 #endif
932  }
933  else
934  {
935 #ifdef __AVX2__
936  return _mm256_set_epi64x(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF);
937 #elif defined(__ARM_NEON)
938  return {vdupq_n_u64(0xFFFFFFFFFFFFFFFF), vdupq_n_u64(0xFFFFFFFFFFFFFFFF)};
939 #endif
940  }
941  }
942 
943  inline smallset_t smallset_invert(const smallset_t& a, const u32 len)
944  {
945  const smallset_t b = smallset_init_full(len);
946 #ifdef __AVX2__
947  return _mm256_xor_si256(a, b);
948 #elif defined(__ARM_NEON)
949  return {veorq_u64(a.val[0], b.val[0]), veorq_u64(a.val[1], b.val[1])};
950 #else
951  return (a ^ b);
952 #endif
953  }
954 
955  inline smallset_t smallset_setminus(const smallset_t& a, const smallset_t& b, const u32 len)
956  {
957  const smallset_t b_not = smallset_invert(b, len);
958  return smallset_intersect(a, b_not);
959  }
960 
961  bool smallset_elm_is_in_set(const u8 e, const smallset_t& a)
962  {
963 #if !defined(__AVX2__) && !defined(__ARM_NEON)
964  return a.is_set(e);
965 #else
966  smallset_t b = smallset_init_empty();
967  b = smallset_add_element(b, e);
968  b = smallset_intersect(a, b);
969  return !smallset_is_empty(b);
970 #endif
971  }
972  // END OF SMALL SET //
973 
974  // state of the linear_representative algorithm
975  typedef struct
976  {
977  std::vector<u8> A;
978  std::vector<u8> B;
979  std::vector<u8> R_S;
988  } state_t;
989 
990  // lexicographically compare R_S and R_S_best
991  bool is_greater(const std::vector<u8>& R_S, const std::vector<u8>& R_S_best, const u32 len)
992  {
993  if ((R_S_best[0] == 0) && (R_S_best[1] == 0))
994  return false;
995 
996  for (u32 x = 0; x < len; x++)
997  {
998  // special case: R_S[x] not defined (=> 0) and R_S_best[x] = 0
999  // works out with this
1000  if (R_S[x] > R_S_best[x])
1001  return true;
1002  if (R_S[x] < R_S_best[x])
1003  return false;
1004  }
1005  // can happen if there are self equivalences (?)
1006  return false;
1007  }
1008 
1009  bool update_linear(std::vector<u8>& A, u8 new_x, const u32 len)
1010  {
1011  u8 new_y = A[new_x];
1012  for (u32 i = 1; i < len; i++)
1013  {
1014  u8 e = A[i];
1015  if (e == 0)
1016  continue;
1017  else if (A[new_x ^ i] == 0)
1018  A[new_x ^ i] = e ^ new_y;
1019  else if (A[new_x ^ i] != (e ^ new_y))
1020  {
1021  return false;
1022  }
1023  }
1024  return true;
1025  }
1026 
1027  bool subroutine(const std::vector<u8>& S, const std::vector<u8>& S_inv, const state_t& state, std::vector<u8>& R_S_best, const u32 len, u64& budget)
1028  {
1029  // The search backtracks over guesses of a linear map, which finishes quickly for anything that looks
1030  // like a real S-box but degenerates on tables that are close to linear, as those have too many linear
1031  // self-equivalences to enumerate. Such a table cannot be a real S-box, so give up on it instead:
1032  // R_S_best then holds the best representative found so far, which still belongs to the equivalence
1033  // class of S, so a truncated search can only miss a match in the database, never invent one.
1034  if (budget == 0)
1035  {
1036  return false;
1037  }
1038  budget--;
1039 
1040  std::vector<u8> A(state.A);
1041  std::vector<u8> B(state.B);
1042  std::vector<u8> R_S(state.R_S);
1043 
1044  smallset_t D_A = (state.D_A);
1045  smallset_t D_B = (state.D_B);
1046  smallset_t C_A = (state.C_A);
1047  smallset_t C_B = (state.C_B);
1048  smallset_t N_A = (state.N_A);
1049  smallset_t N_B = (state.N_B);
1050  smallset_t U_A = (state.U_A);
1051  smallset_t U_B = (state.U_B);
1052 
1053  while (!smallset_is_empty(N_A))
1054  {
1055  u8 x = smallset_least_element(N_A);
1056  u8 y = smallset_least_element(U_B);
1057 
1058  B[y] = S[A[x]];
1059  if (!update_linear(B, y, len))
1060  return false;
1061  smallset_t D_B_new = smallset_shift(D_B, y);
1062  D_B = smallset_union(D_B, D_B_new);
1063  U_B = smallset_setminus(U_B, D_B_new, len);
1064 
1065  smallset_t SoA_N_A = smallset_init_empty();
1066  for (u8 x : smallset_get_elements(N_A))
1067  {
1068  SoA_N_A = smallset_add_element(SoA_N_A, S[A[x]]);
1069  }
1070  smallset_t B_D_B_new = smallset_init_empty();
1071  for (u8 d : smallset_get_elements(D_B_new))
1072  {
1073  B_D_B_new = smallset_add_element(B_D_B_new, B[d]);
1074  if (smallset_elm_is_in_set(B[d], SoA_N_A))
1075  {
1076  C_B = smallset_add_element(C_B, d);
1077  }
1078  else
1079  {
1080  N_B = smallset_add_element(N_B, d);
1081  }
1082  }
1083  smallset_t C_A_new = smallset_init_empty();
1084  for (u8 x : smallset_get_elements(N_A))
1085  {
1086  if (smallset_elm_is_in_set(S[A[x]], B_D_B_new))
1087  {
1088  C_A_new = smallset_add_element(C_A_new, x);
1089  }
1090  }
1091  C_A = smallset_union(C_A, C_A_new);
1092  N_A = smallset_setminus(N_A, C_A_new, len);
1093  for (u8 x : smallset_get_elements(C_A_new))
1094  {
1095  u8 y = 0;
1096  for (u32 i = 0; i < len; i++)
1097  {
1098  if (B[i] == S[A[x]])
1099  {
1100  y = i;
1101  break;
1102  }
1103  }
1104  R_S[x] = y;
1105  }
1106  if (is_greater(R_S, R_S_best, len))
1107  {
1108  return false;
1109  }
1110 
1111  while (smallset_is_empty(N_A) && !smallset_is_empty(N_B))
1112  {
1113  u8 x = smallset_least_element(U_A);
1114  u8 y = smallset_least_element(N_B);
1115  A[x] = S_inv[B[y]];
1116  if (!update_linear(A, x, len))
1117  {
1118  return false;
1119  }
1120  smallset_t D_A_new = smallset_shift(D_A, x);
1121  D_A = smallset_union(D_A, D_A_new);
1122  U_A = smallset_setminus(U_A, D_A_new, len);
1123  smallset_t SinvoB_N_B = smallset_init_empty();
1124  for (u8 y : smallset_get_elements(N_B))
1125  {
1126  SinvoB_N_B = smallset_add_element(SinvoB_N_B, S_inv[B[y]]);
1127  }
1128  smallset_t A_D_A_new = smallset_init_empty();
1129  for (u8 d : smallset_get_elements(D_A_new))
1130  {
1131  A_D_A_new = smallset_add_element(A_D_A_new, A[d]);
1132  if (smallset_elm_is_in_set(A[d], SinvoB_N_B))
1133  {
1134  C_A = smallset_add_element(C_A, d);
1135  }
1136  else
1137  {
1138  N_A = smallset_add_element(N_A, d);
1139  }
1140  }
1141  smallset_t C_B_new = smallset_init_empty();
1142  for (u8 y : smallset_get_elements(N_B))
1143  {
1144  if (smallset_elm_is_in_set(S_inv[B[y]], A_D_A_new))
1145  {
1146  C_B_new = smallset_add_element(C_B_new, y);
1147  }
1148  }
1149  C_B = smallset_union(C_B, C_B_new);
1150  N_B = smallset_setminus(N_B, C_B_new, len);
1151  for (u8 y : smallset_get_elements(C_B_new))
1152  {
1153  u8 x = 0;
1154  for (u32 i = 0; i < len; i++)
1155  {
1156  if (A[i] == S_inv[B[y]])
1157  {
1158  x = i;
1159  break;
1160  }
1161  }
1162  R_S[x] = y;
1163  }
1164  if (is_greater(R_S, R_S_best, len))
1165  {
1166  return false;
1167  }
1168  }
1169  }
1170  if (smallset_is_empty(U_A) && smallset_is_empty(U_B))
1171  {
1172  for (u32 i = 0; i < len; i++)
1173  {
1174  // new best
1175  R_S_best[i] = R_S[i];
1176  }
1177  return true;
1178  }
1179  else
1180  {
1181  u8 x = smallset_least_element(U_A);
1182  smallset_t D_A_new = smallset_shift(D_A, x);
1183  U_A = smallset_setminus(U_A, D_A_new, len);
1184  D_A = smallset_union(D_A, D_A_new);
1185  N_A = smallset_union(N_A, D_A_new);
1186  bool flag = false;
1187  smallset_t Y = smallset_init_full(len);
1188  smallset_t A_set = smallset_init_empty();
1189  for (u32 i = 0; i < len; i++)
1190  {
1191  A_set = smallset_add_element(A_set, A[i]);
1192  }
1193  Y = smallset_setminus(Y, A_set, len);
1194  for (u8 y : smallset_get_elements(Y))
1195  {
1196  std::vector<u8> A_next_guess(len);
1197 
1198  for (u32 i = 0; i < len; i++)
1199  {
1200  A_next_guess[i] = A[i];
1201  }
1202  A_next_guess[x] = y;
1203  if (!update_linear(A_next_guess, x, len))
1204  continue;
1205  state_t state_next;
1206  state_next.A = A_next_guess;
1207  state_next.B = B;
1208  state_next.R_S = R_S;
1209  state_next.D_A = D_A;
1210  state_next.D_B = D_B;
1211  state_next.C_A = C_A;
1212  state_next.C_B = C_B;
1213  state_next.N_A = N_A;
1214  state_next.N_B = N_B;
1215  state_next.U_A = U_A;
1216  state_next.U_B = U_B;
1217 
1218  if (subroutine(S, S_inv, state_next, R_S_best, len, budget))
1219  {
1220  flag = true;
1221  }
1222  }
1223 
1224  return flag;
1225  }
1226  }
1227  std::vector<u8> compute_linear_representative_bounded(const std::vector<u8>& sbox, u64& budget)
1228  {
1229  u32 len = sbox.size();
1230 
1231  // variable for current best candidate
1232  std::vector<u8> R_S_best(len, 0);
1233 
1234  // invert sbox
1235  std::vector<u8> S_inv(len, 0);
1236  for (u32 x = 0; x < len; x++)
1237  {
1238  u8 y = sbox[x];
1239  S_inv[y] = x;
1240  }
1241 
1242  // init the state of the algorithm
1243  state_t state;
1244  state.A = std::vector<u8>(len, 0);
1245  state.B = std::vector<u8>(len, 0);
1246  state.R_S = std::vector<u8>(len, 0);
1247 
1248  state.D_A = smallset_add_element(smallset_init_empty(), 0);
1249  state.D_B = smallset_add_element(smallset_init_empty(), 0);
1250 
1251  state.C_A = smallset_init_empty();
1252  state.C_B = smallset_init_empty();
1253 
1254  state.N_A = smallset_add_element(smallset_init_empty(), 0);
1255  state.N_B = smallset_add_element(smallset_init_empty(), 0);
1256 
1257  state.U_A = smallset_setminus(smallset_init_full(len), state.D_A, len);
1258  state.U_B = smallset_setminus(smallset_init_full(len), state.D_A, len);
1259 
1260  // init in special case S[0] == 0
1261  if (sbox[0] == 0)
1262  {
1263  state.C_A = smallset_add_element(smallset_init_empty(), 0);
1264  state.C_B = smallset_add_element(smallset_init_empty(), 0);
1265 
1266  state.N_A = smallset_init_empty();
1267  state.N_B = smallset_init_empty();
1268  }
1269 
1270  // compute linear representative recursively
1271  subroutine(sbox, S_inv, state, R_S_best, len, budget);
1272 
1273  return R_S_best;
1274  }
1275  } // namespace
1276 
1277  std::vector<u8> SBoxDatabase::compute_linear_representative(const std::vector<u8>& sbox)
1278  {
1279  u64 budget = LINEAR_REPRESENTATIVE_BUDGET;
1280  auto res = compute_linear_representative_bounded(sbox, budget);
1281  if (budget == 0)
1282  {
1283  log_info("hawkeye", "gave up computing the canonical form of a table of {} entries, as it is too close to linear to be a real S-box.", sbox.size());
1284  }
1285  return res;
1286  }
1287  } // namespace hawkeye
1288 } // namespace hal
Result< std::monostate > add(const std::string &name, const std::vector< u8 > &sbox)
Add an S-box to the database.
SBoxDatabase()=default
Construct an empty S-box database.
Result< std::string > lookup(const std::vector< u8 > &sbox) const
Attempt to look up an S-box in the database.
Result< std::monostate > load(const std::filesystem::path &file_path, bool overwrite=false)
Load S-boxes from a file and add them to the existing database.
static Result< SBoxDatabase > from_file(const std::filesystem::path &file_path)
Construct an S-box database from file.
void print() const
Print the database.
Result< std::monostate > store(const std::filesystem::path &file_path) const
Store the S-box database to a database file.
static std::vector< u8 > compute_linear_representative(const std::vector< u8 > &sbox)
Compute the linear representative of the given S-box.
smallset_t operator^(const smallset_t &other) const
smallset_t(int preset=0)
void set(u8 bit)
void to_array(u64 *arr, bool swap=false) const
void dump() const
bool is_set(u8 bit) const
bool empty() const
smallset_t operator|(const smallset_t &other) const
smallset_t shuffle(u8 shift) const
smallset_t operator&(const smallset_t &other) const
int size() const
u8 least_bit() const
uint64_t u64
Definition: defines.h:42
uint16_t u16
Definition: defines.h:40
uint32_t u32
Definition: defines.h:41
uint8_t u8
Definition: defines.h:39
#define log_info(channel,...)
Definition: log.h:70
#define ERR(message)
Definition: result.h:60
#define OK(...)
Definition: result.h:56
Definition: defines.h:45
std::string name
std::vector< u8 > R_S
smallset_t U_B
smallset_t U_A
std::vector< u8 > B
smallset_t N_A
smallset_t C_A
std::vector< u8 > A
smallset_t D_A
smallset_t C_B
smallset_t D_B
const uint64_t _ONE_
smallset_t N_B
This file contains the S-box database class that holds and manages known cryptographic S-boxes up to ...