Key-space attacks against both key families, and the learned SNR sweep

check_family_enum.py now runs both attacks against both families. The
outsider ranks the L-1 Walsh rows; the insider, holding m_v, ranks the
L-1 products m_v .* m_r, which works because Walsh rows are closed
under the elementwise product and the per-block sign cancels in
m_u .* m_v. Both need a list to rank, and only the structured family
supplies one: the structured family falls at 0.905 from one frame at
10 dB and 0.990 from four, the refresh takes the outsider to 0.000 and
leaves the insider at 0.980, and the learned family gives 0.000
throughout.

exp_full.stage_N sweeps the learned family over the same SNR grid at
the same frame count as stage_A, so Fig. 2 can carry both families and
a reader can see what the key space costs at every SNR rather than at
one point.

check_consistency.py gains four assertions for the key-space
measurements and two for the learned sweep, 82 in all.

README: the assertion count was two rounds stale, and the figure map
omitted family_enum, cov_attack and maskdegen, whose CSVs back quoted
manuscript numbers.
This commit is contained in:
KiHoLee
2026-08-28 19:30:58 +09:00
parent 17d23fa76a
commit 3a9a5eebf4
16 changed files with 291 additions and 96 deletions
+12 -3
View File
@@ -62,10 +62,12 @@ C_OMA = "#7f8c8d"
C_CH = "#95a5a6"
C_MATCH = "#8e44ad"
C_PUB = "#16a085"
C_LEARN = "#d98c00"
# fixed label dictionary: tables and prose copy these strings verbatim
LBL = {
"legit": "Legitimate",
"legit_learned": "Learned keys",
"oma": "OMA",
"eve_pub": "Eavesdropper, public masks",
"eve_key": "Eavesdropper", # the wrong-key condition is in the caption
@@ -271,6 +273,12 @@ def fig_snr():
# the pair is deliberately layered; OMA is separate at this frame
ax.semilogy(x, col(r, "legit"), color=C_LEGIT, marker="o", ls="-",
markevery=(0, 3), label=LBL["legit"], **UNDER)
# the learned family is the other end of the key-space trade-off,
# so the figure carries what it costs at every SNR
rl = load("sec_snr_learned.csv")
ax.semilogy(col(rl, "snr_db"), col(rl, "legit"), color=C_LEARN,
marker="d", ls="-", markevery=(2, 3),
label=LBL["legit_learned"])
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
markevery=(1, 3), label=LBL["oma"], **OVER)
ax.semilogy(x, col(r, "eve_public"), color=C_PUB, marker="v",
@@ -287,9 +295,10 @@ def fig_snr():
ax.set_xlabel("SNR (dB)")
ax.set_ylabel("SER")
ax.set_xlim(min(x), max(x))
# most of a decade below the data leaves the lower-left genuinely
# empty, which is what gives the four-entry legend a clear berth
ax.set_ylim(bottom=2e-4)
# the five-entry legend needs more clear space than the four-entry
# one did, so the axis opens a further decade below the data; the
# lower-left is empty because every curve decays
ax.set_ylim(bottom=2e-5)
place_legend(ax)
save(fig, "fig_sec_snr")