Skip to content

Fix missing flip-flop edges in DANA and S-box location and identification in HAWKEYE - #682

Open
julianspeith wants to merge 4 commits into
masterfrom
fix/dana-cache-hawkeye-sboxes
Open

julianspeith wants to merge 4 commits into
masterfrom
fix/dana-cache-hawkeye-sboxes

Conversation

@julianspeith

Copy link
Copy Markdown
Contributor

This PR fixes one bug in DANA and three problems in HAWKEYE. I found them on two synthesized AES-128 netlists (Synopsys DC, LSI10k, about 850 flip-flops each). On those netlists, DANA lost flip-flop edges, and HAWKEYE identified only 6 of the 16 S-boxes on one netlist and reported 64 bogus 2-bit S-boxes on the other.

Changes

DANA: missing flip-flop edges (8bd08f4)

identify_successors_predecessors shares its per-net caches across the traversals of all target gates, but a traversal never pushes a net it has already visited. With reconvergent fanout, the nets on the second path to such a net therefore never received its targets. Their cache entries stayed incomplete, and every later traversal that hit them lost those edges. On the two netlists, 202 of 20,016 and 178 of 19,982 edges were missing. In the steered analysis this split a 128-bit register into 120 + 8 bits.

The fix hands the cache entry of an already visited net to the nets of the current path, unless that net lies on the path itself (a combinational loop, still being explored). The predecessor cache of known net groups gets the same fix. A Python re-implementation of the old routine reproduces DANA's old graph edge for edge. After the fix, the graph equals the one from NetlistTraversalDecorator::get_next_sequential_gates_map on both netlists.

HAWKEYE: identification with surplus outputs and for the inversion class (ec8f9f8)

  • lookup_sbox reduced surplus outputs by a linear elimination that reused pivot outputs for later rows, so it got the rank wrong (too high in about a fifth of random tests). It also counted a complemented copy of an output as independent. identify_sbox holds key inputs at 0, which turns a key addition (XOR/XNOR) into exactly such a copy. The elimination is now a proper echelon-form elimination with the constant function in the basis first, and the selected outputs are kept unchanged (the database does not care about output constants). On the affected netlist, all 16 S-boxes are identified instead of 6.
  • SBoxDatabase::lookup gave up as soon as the canonical form search ran out of budget, assuming this happens for all output constants alike. It does not: a table that maps 0 to 0 takes a search of its own, which runs out of budget for the inversion class, i.e., for every AES-equivalent table with T(0) = 0. lookup now skips that one constant and goes on with the others. add leaves that entry out instead of failing, so SBoxDatabase().add("AES", ...) works again.

HAWKEYE: minimum S-box size when splitting a component (e258778)

locate_sboxes accepted every split of a component into equal groups of more than one flip-flop, while whole components need at least 3. When synthesis starts the S-boxes with 2-to-4 predecoders on pairs of input bits, the round function first splits into groups of 2. On one netlist this produced 64 bogus 2-bit S-boxes (384 of 816 located ones). Both cases now use the same minimum, MIN_SBOX_BITS = 3.

HAWKEYE: differential spectrum prefilter in SBoxDatabase::lookup (bbad8ce)

The canonical form search runs once per output constant and takes about 20 s for an 8-bit table that matches no entry. With the corrected output reduction, such tables reach the lookup more often, e.g., two 4-bit S-boxes side by side that HAWKEYE locates as one 8-bit S-box in LED. This made the ASIC LED benchmark go from 2 s to 53 s. Affine equivalence and XORing constants only permute the entries of the difference distribution table, so lookup now first compares the spectrum of the table with those of the database entries and gives up right away if none matches. Rejecting a non-matching 8-bit table now takes 0.03 s.

Testing

Regression against the 65 benchmarks of the HAWKEYE artifact (Crypto'24), running master and this branch side by side:

  • plugins/hawkeye/scripts/characterize.py gives identical output for all 65 benchmarks: same candidates, round functions, and S-boxes, gate by gate.
  • A port of the artifact's run_isolated_benchmarks.py and run_soc_benchmarks.py to the CipherCandidate API (not part of this PR) gives the same 130 summary lines (candidates, true/false positives, identified S-box) on both. identify_sboxes identifies more S-boxes per candidate: LED-64 on FPGA 16 instead of 8 (both methods), CRAFT on ASIC with method 2 14 instead of 10. No benchmark gets worse. The whole run takes 982 s, against 988 s on master.
  • S-box database: AES XORed with any of the 256 output constants is found (before, 0x63 failed). Random linear transforms of AES are found, also with T(0) = 0 (before, 0 of 10). Every representative stored in the shipped database is found again.

Not part of this PR

  • netlist_preprocessing.simplify_lut_inits throws bad_variant_access on the FPGA noise benchmarks cpu_edge and cpu_ibex_risc-v. This happens on master as well; for the regression, I skipped the failing step on both sides.
  • Compared with the reference output in the artifact (HAL 4.4.1), master deviates on 29 ASIC lines. All 29 disappear with the lsi_10k.hgl from before 3145cb7, so they come from the corrected pin types, not from HAWKEYE. The only other deviation (cpu_ibex_risc-v on FPGA, 774 instead of 775 flip-flops) is probably due to the skipped preprocessing step.

🤖 Generated with Claude Code

julianspeith and others added 4 commits November 11, 2026 14:02
identify_successors_predecessors shares its per-net caches across the
traversals of all target gates, but a traversal does not push a net it
has visited before. With reconvergent fanout, the nets on the second
path to such a net never got its targets, so their cache entries stayed
incomplete, and every later traversal that hit them lost those edges.
On two synthesized AES netlists (about 850 flip-flops, 20k edges), 178
and 202 edges were missing, which split a 128-bit register into 120 + 8
bits in the steered analysis.

Hand the cache entry of a visited net to the nets of the current path,
unless it lies on the path itself (a combinational loop, still being
explored). The same applies to the predecessor cache of known net
groups. The flip-flop graph now matches the one of
NetlistTraversalDecorator::get_next_sequential_gates_map on both
netlists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ersion class

lookup_sbox reduced surplus outputs by a linear elimination that reused
pivot outputs for later rows, so it miscounted the rank (too high in
about a fifth of random tests). It also treated a complemented copy of
an output as independent, although identify_sbox holds key inputs at 0,
which turns a key addition (XOR/XNOR) into exactly such a copy. Use a
proper echelon-form elimination with the constant function in the basis
first, and keep the outputs as they are (the database does not care
about output constants). On a synthesized AES netlist, this identifies
all 16 S-boxes instead of 6.

SBoxDatabase::lookup gave up as soon as the canonical form search ran out
of budget, assuming that this happens for every output constant alike.
It does not: a table that maps 0 to 0 takes a search of its own, which
exhausts the budget for the inversion class, i.e., for every
AES-equivalent table with T(0) = 0. Skip that constant and go on with
the others in lookup, and leave the entry out in add instead of failing,
so that SBoxDatabase().add("AES", ...) works again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
locate_sboxes accepted every split of a component into equal groups of
more than one flip-flop, while whole components need at least 3. When
synthesis starts the S-boxes with 2-to-4 predecoders on pairs of input
bits, the round function splits into groups of 2 first, which produced
64 bogus 2-bit S-boxes (384 of 816 located ones) on an AES netlist. Use
one minimum for both cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rm search

SBoxDatabase::lookup computes a canonical form for every output constant
beta, which takes about 20 seconds for an 8-bit table that matches no
entry. With the corrected output reduction, such tables now reach the
lookup more often, e.g., two 4-bit S-boxes side by side that HAWKEYE
locates as one 8-bit S-box in LED, which made the ASIC LED benchmark go
from 2 to 53 seconds.

Affine equivalence and XORing constants only permute the entries of the
difference distribution table, so compare the spectrum of the table with
those of the entries of the database first and give up right away if
none matches. Rejecting a non-matching 8-bit table now takes 0.03
seconds; every representative stored in the shipped database is still
found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant