Learned-key sensitivity, brute-force and real-token stages; legend order

exp_learned.py completes the learned side of the result stages, so every
figure can carry both realizations of keyed masking. real() holds the
structured artifacts aside and restores them, since exp_real_sec writes
fixed file names.

replot_security.py ranks legend handles from one declared order at all
three ax.legend call sites, so entries no longer follow plot-call order
and drift between figures.
This commit is contained in:
KiHoLee
2026-08-28 20:31:50 +09:00
parent c00e8ab666
commit 669737e83d
16 changed files with 210 additions and 26 deletions
+78
View File
@@ -157,3 +157,81 @@ def main():
if __name__ == "__main__":
main()
def sens():
"""Fig. 5's learned curve: eavesdropper SER against the fraction of
the key the attacker holds. correlated_masks builds a substitute at
a prescribed correlation to any real key, so the sweep applies to a
learned key exactly as to a sign pattern."""
from exp_full import correlated_masks
print("[learned] key sensitivity ...")
m = learned_model()
F, TR = 600_000, 12
true_m = m.masks().detach().cpu()
gen = torch.Generator().manual_seed(31)
fracs = [0.0, 0.2, 0.4, 0.6, 0.75, 0.85, 0.9, 0.92, 0.94, 0.955,
0.97, 0.985, 1.0]
rows = []
for f in fracs:
acc = [eval_ser_eve(m, correlated_masks(true_m, f, gen), [10.0],
frames=F // TR, seed=777 + 17 * t)[0]
for t in range(TR)]
rows.append((f, sum(acc) / len(acc)))
write_csv(DATA / "sec_sens_learned.csv", ["frac", "ser_mask"], rows)
print(" f=0 %.4f f=1 %.4f" % (rows[0][1], rows[-1][1]))
def brute():
"""Fig. 6's learned curve. The best-of-K correlation is a property of
the key space, which both realizations share at the same L, so only
the sensitivity mapping differs and it is re-read from the learned
sweep."""
import csv as _csv
import numpy as np
print("[learned] brute-force search ...")
with open(DATA / "sec_sens_learned.csv") as f:
cmp_rows = list(_csv.DictReader(f))
f_arr = np.array([float(r["frac"]) for r in cmp_rows])
mask_arr = np.array([float(r["ser_mask"]) for r in cmp_rows])
L = MAIN_D // 4
ks = [1, 3, 10, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 65_536,
100_000, 300_000, 1_000_000]
rng = np.random.default_rng(2026)
rows = []
for K in ks:
best = np.sqrt(rng.beta(0.5, (L - 1) / 2.0, size=(400, K)).max(1))
rows.append((K, float(np.mean(np.interp(best, f_arr, mask_arr)))))
write_csv(DATA / "sec_brute_learned.csv", ["K", "ser_mask"], rows)
print(" K=1e6 %.4f" % rows[-1][1])
def real():
"""Fig. 8's learned curves.
exp_real_sec writes fixed file names, so the structured artifacts are
held aside, the run is repeated with the learned model, its output is
copied to *_learned names, and the originals are put back. A failure
anywhere restores them.
"""
import shutil
import exp_real_sec as R
print("[learned] real token streams ...")
names = ("real_sec_ter.csv", "real_sec_stats.json")
saved = {n: (DATA / n).read_bytes() for n in names
if (DATA / n).exists()}
orig = R.main_model
try:
R.main_model = lambda **kw: learned_model(
d=kw.get("d", MAIN_D), P=kw.get("P", 4),
vu=kw.get("vu", 16), U=kw.get("U", 4))
R.main()
for n in names:
if (DATA / n).exists():
shutil.copyfile(DATA / n,
DATA / n.replace(".", "_learned.", 1))
finally:
R.main_model = orig
for n, blob in saved.items():
(DATA / n).write_bytes(blob)
print(" learned artifacts written, structured ones restored")