Final revision: audit fixes, cross-scheme comparisons, known-plaintext stage

- exclude the all-ones Walsh-Hadamard row and test every key family
  against the all-ones guess
- pass the model dimension to the noise scaling so the key-length sweep
  runs at a fixed per-dimension SNR
- known-plaintext attack with nested accumulation and common random
  numbers, averaged over 40 collections
- key sensitivity and brute-force search extended to the permutation
  key and the index cipher
- make_tables regenerates all three result tables from the CSVs
This commit is contained in:
KiHoLee
2026-08-13 21:21:46 +09:00
parent 37392bc38f
commit 25b5891b04
15 changed files with 279 additions and 56 deletions
+58 -17
View File
@@ -7,7 +7,8 @@ Label dictionary is fixed here and copied verbatim into tables and prose.
fig_sec_keylen.pdf : SER vs key length L (Fig. 3)
fig_sec_jam.pdf : target-user SER vs JSR (Fig. 4)
fig_sec_sens.pdf : Eve SER vs key correlation (Fig. 5)
fig_sec_brute.pdf : Eve SER vs number of key guesses (Fig. 6)
fig_sec_brute.pdf : Eve SER vs number of key guesses (Fig. 6)
fig_sec_brute_rho.pdf : best key correlation vs guesses (Fig. 7)
"""
from __future__ import annotations
from pathlib import Path
@@ -159,14 +160,23 @@ def fig_jam():
def fig_sens():
r = load("sec_sens.csv")
x = col(r, "rho")
"""Key sensitivity of three schemes on one axis, the fraction of the
key the attacker holds. For keyed masking that fraction is the mask
correlation, for the permutation scheme the fraction of positions
placed correctly, for the index cipher the fraction of pad bits
known."""
r = load("sec_sens_cmp.csv")
x = col(r, "frac")
fig, ax = plt.subplots()
ax.plot(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="-",
label=LBL["eve_key"])
ax.plot(x, col(r, "ser_mask"), color=C_LEGIT, marker="o", ls="-",
label="Keyed masking")
ax.plot(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
label="Permutation key")
ax.plot(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
label="Index cipher")
chance = 1.0 - (1.0 / 16.0) ** 4
ax.axhline(chance, color=C_CH, ls="-.", lw=0.9, label=LBL["chance"])
ax.set_xlabel(r"Key correlation $\kappa$")
ax.axhline(chance, color=C_CH, ls=":", lw=0.9, label=LBL["chance"])
ax.set_xlabel("Fraction of the key recovered")
ax.set_ylabel("Eavesdropper SER")
ax.set_xlim(0, 1)
ax.legend(loc="lower left")
@@ -174,21 +184,45 @@ def fig_sens():
def fig_brute():
"""Brute-force search against the three keyed schemes at the same
key length, each mapped through its own sensitivity curve."""
r = load("sec_brute_cmp.csv")
x = col(r, "K")
fig, ax = plt.subplots()
ax.semilogx(x, col(r, "ser_mask"), color=C_LEGIT, marker="o", ls="-",
label="Keyed masking")
ax.semilogx(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
label="Permutation key")
ax.semilogx(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
label="Index cipher")
kl = load("sec_keylen.csv")
legit = float([q for q in kl if int(q["L"]) == 16][0]["legit_ser"])
ax.axhline(legit, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
ax.set_xlabel("Number of key guesses $K$")
ax.set_ylabel("Eavesdropper SER")
ax.set_ylim(-0.03, 1.05)
ax.legend(loc="center left")
save(fig, "fig_sec_brute")
def fig_brute_rho():
"""Best key correlation a search of size K reaches, per key length.
This is a property of the key space alone."""
r = load("sec_brute.csv")
fig, ax = plt.subplots()
sty = {8: ("#c0392b", "o"), 16: ("#2c5fa8", "s"),
32: ("#16a085", "v"), 64: ("#8e44ad", "P")}
for Lp in [8, 16, 32, 64]:
for Lp, (c, mk) in sty.items():
rows = [row for row in r if int(row["L"]) == Lp]
ks = [float(row["K"]) for row in rows]
ser = [float(row["eve_ser"]) for row in rows]
c, mk = sty[Lp]
ax.semilogx(ks, ser, color=c, marker=mk, ls="-",
label=f"$L={Lp}$")
ax.semilogx([float(x["K"]) for x in rows],
[float(x["best_rho"]) for x in rows],
color=c, marker=mk, ls="-", label=f"$L={Lp}$")
ax.axhline(0.96, color=C_CH, ls="-.", lw=0.9, label="Break threshold")
ax.set_xlabel("Number of key guesses $K$")
ax.set_ylabel("Eavesdropper SER")
ax.legend(loc="lower left")
save(fig, "fig_sec_brute")
ax.set_ylabel(r"Best key correlation $\kappa$")
ax.set_ylim(0, 1.05)
ax.legend(loc="upper left")
save(fig, "fig_sec_brute_rho")
def fig_real():
@@ -221,7 +255,13 @@ def fig_kpa():
ser = [float(row["eve_ser"]) for row in rows]
ax.semilogx(n, ser, color=c, marker=mk, ls="-",
label=f"{int(snr)} dB")
ax.axhline(0.304, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
# legitimate reference measured with the SAME estimator as the
# eavesdropper curves, namely the four-user average of eval_ser_sse
# at L=16, taken from sec_keylen.csv rather than from the user-1
# convention of the scheme-comparison table
kl = load("sec_keylen.csv")
legit = float([r for r in kl if int(r["L"]) == 16][0]["legit_ser"])
ax.axhline(legit, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
ax.set_xlabel("Known-plaintext frames $N$")
ax.set_ylabel("Eavesdropper SER")
ax.set_xscale("log", base=2)
@@ -236,6 +276,7 @@ def main():
try:
fig_sens()
fig_brute()
fig_brute_rho()
except FileNotFoundError:
print("[skip] attack-difficulty CSVs not present yet")
try: