Learned-key stages, per-scheme plot styles, KM naming
exp_learned.py mirrors every structured result stage for the learned key family at the same SNRs, frame counts and seeds, so Figs. 2, 3, 4 and 7 and Tables IV and VI can carry both realizations of keyed masking. replot_security.py gains a style registry: colour identifies the scheme and line style the role, so a curve learned in one figure reads the same in the next. Previously OMA was grey in two figures and teal in a third, and blue meant the eavesdropper in one figure and the permutation key in another.
This commit is contained in:
@@ -0,0 +1,159 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Learned-key counterparts of the structured-key result stages.
|
||||||
|
|
||||||
|
Keyed masking is realized two ways, with structured Walsh-Hadamard keys
|
||||||
|
and with keys learned in R^L. The two differ in key space, so the paper
|
||||||
|
reports both wherever a figure or table carries a keyed-masking result.
|
||||||
|
This script produces the learned side of the key-length sweep, the
|
||||||
|
jamming sweep, the known-plaintext attack, the scheme comparison and
|
||||||
|
the refresh, writing files named *_learned.csv next to the structured
|
||||||
|
ones.
|
||||||
|
|
||||||
|
Every evaluation mirrors its structured counterpart exactly: same SNR,
|
||||||
|
same frame counts, same seeds, same evaluators. Only the key family
|
||||||
|
differs.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
import exp_kpa
|
||||||
|
from exp_full import (MAIN_D, eval_ser_eve, eval_ser_jam, eve_wrong_mask,
|
||||||
|
get_model, mean_abs_xcorr, oma_ser_keylen)
|
||||||
|
from sse_lib import DATA, DEVICE, eval_ser_sse, write_csv
|
||||||
|
|
||||||
|
SEED = 1
|
||||||
|
|
||||||
|
|
||||||
|
def learned_model(d=MAIN_D, P=4, vu=16, U=4, iters=4000, seed=SEED):
|
||||||
|
"""The learned counterpart of main_model: same everything, keys free."""
|
||||||
|
return get_model(P=P, vu=vu, d=d, U=U, iters=iters, seed=seed)
|
||||||
|
|
||||||
|
|
||||||
|
def keylen():
|
||||||
|
"""Fig. 3's learned curve."""
|
||||||
|
print("[learned] key length ...")
|
||||||
|
rows = []
|
||||||
|
for d in [32, 48, 64, 80, 96, 128, 192, 256]:
|
||||||
|
m = learned_model(d=d)
|
||||||
|
lg = eval_ser_sse(m, [10.0], frames=500_000)[0]
|
||||||
|
ev = sum(eval_ser_eve(
|
||||||
|
m, eve_wrong_mask(m.users, m.L,
|
||||||
|
seed=20260813 + 101 * k).to(DEVICE),
|
||||||
|
[10.0], frames=500_000 // 8)[0]
|
||||||
|
for k in range(8)) / 8.0
|
||||||
|
rows.append((m.L, d, lg, ev, mean_abs_xcorr(m.masks().detach()),
|
||||||
|
oma_ser_keylen(m.L, 10.0)))
|
||||||
|
print(" L=%3d legit %.4f eve %.4f" % (m.L, lg, ev))
|
||||||
|
write_csv(DATA / "sec_keylen_learned.csv",
|
||||||
|
["L", "d", "legit_ser", "eve_ser", "mask_xcorr", "oma"], rows)
|
||||||
|
|
||||||
|
|
||||||
|
def jamming():
|
||||||
|
"""Fig. 4's learned curves."""
|
||||||
|
print("[learned] jamming ...")
|
||||||
|
m = learned_model()
|
||||||
|
jsr = [-10.0, -5.0, 0.0, 5.0, 10.0, 15.0, 20.0]
|
||||||
|
blind = eval_ser_jam(m, 10.0, jsr, frames=500_000, mode="blind", target=0)
|
||||||
|
matched = eval_ser_jam(m, 10.0, jsr, frames=500_000, mode="matched",
|
||||||
|
target=0)
|
||||||
|
nojam = eval_ser_jam(m, 10.0, [-40.0], frames=500_000, mode="blind",
|
||||||
|
target=0)[0]
|
||||||
|
write_csv(DATA / "sec_jam_learned.csv",
|
||||||
|
["jsr_db", "blind", "matched", "nojam"],
|
||||||
|
[(j, blind[i], matched[i], nojam) for i, j in enumerate(jsr)])
|
||||||
|
print(" blind :", ["%.3f" % v for v in blind])
|
||||||
|
|
||||||
|
|
||||||
|
def kpa():
|
||||||
|
"""Fig. 7's learned curve. The attack is linear algebra on the key,
|
||||||
|
so it applies to a real-valued key exactly as to a sign pattern."""
|
||||||
|
print("[learned] known plaintext ...")
|
||||||
|
m = learned_model()
|
||||||
|
m.eval()
|
||||||
|
true_m = m.masks().detach()
|
||||||
|
nmax = max(exp_kpa.NFRAMES)
|
||||||
|
rows = []
|
||||||
|
for snr in exp_kpa.SNRS:
|
||||||
|
acc = {n: [[], []] for n in exp_kpa.NFRAMES}
|
||||||
|
for t in range(exp_kpa.TRIALS):
|
||||||
|
gen = torch.Generator(device="cpu").manual_seed(
|
||||||
|
exp_kpa.SEED + int(snr) + 1000 * t)
|
||||||
|
digits, obs, h = exp_kpa.collect_known_plaintext(m, nmax, snr, gen)
|
||||||
|
eval_seed = 777 + 31 * t + int(snr)
|
||||||
|
for n in exp_kpa.NFRAMES:
|
||||||
|
est = exp_kpa.solve_keys(m, digits[:n], obs[:n], h[:n])
|
||||||
|
acc[n][0].append(exp_kpa.key_correlation(est, true_m))
|
||||||
|
acc[n][1].append(eval_ser_eve(m, est.cpu(), [10.0],
|
||||||
|
frames=exp_kpa.EVAL_FRAMES,
|
||||||
|
seed=eval_seed)[0])
|
||||||
|
for n in exp_kpa.NFRAMES:
|
||||||
|
ks, ss = acc[n]
|
||||||
|
rows.append((snr, n, sum(ks) / len(ks), sum(ss) / len(ss)))
|
||||||
|
print(" %4.0f dB done" % snr)
|
||||||
|
write_csv(DATA / "kpa_learned.csv",
|
||||||
|
["snr_db", "n_frames", "kappa", "eve_ser"], rows)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh():
|
||||||
|
"""Table VI's learned rows: the invariance refresh acts through
|
||||||
|
eps^2 = 1 and a relabeling, so it is available to any real key."""
|
||||||
|
print("[learned] refresh ...")
|
||||||
|
m = learned_model()
|
||||||
|
W0, B0 = m.W.detach().clone(), m.B.detach().clone()
|
||||||
|
base = eval_ser_sse(m, [10.0], frames=300_000)[0]
|
||||||
|
out = []
|
||||||
|
for b in range(8):
|
||||||
|
g = torch.Generator(device=DEVICE).manual_seed(5150 + b)
|
||||||
|
xi = torch.randperm(m.L, generator=g, device=DEVICE)
|
||||||
|
eps = torch.randint(2, (m.L,), generator=g, device=DEVICE) * 2.0 - 1.0
|
||||||
|
tau = torch.randperm(m.users, generator=g, device=DEVICE)
|
||||||
|
with torch.no_grad():
|
||||||
|
m.W.copy_((W0[tau] * eps[None, :])[:, xi])
|
||||||
|
m.B.copy_(B0[:, xi])
|
||||||
|
lg = eval_ser_sse(m, [10.0], frames=300_000)[0]
|
||||||
|
ev = eval_ser_eve(m, eve_wrong_mask(m.users, m.L,
|
||||||
|
seed=20260813).to(DEVICE),
|
||||||
|
[10.0], frames=300_000)[0]
|
||||||
|
out.append((b, lg, ev))
|
||||||
|
with torch.no_grad():
|
||||||
|
m.W.copy_(W0); m.B.copy_(B0)
|
||||||
|
write_csv(DATA / "refresh_learned.csv",
|
||||||
|
["block", "legit_ser", "eve_ser"], out)
|
||||||
|
print(" unrefreshed %.5f refreshed %.5f..%.5f"
|
||||||
|
% (base, min(r[1] for r in out), max(r[1] for r in out)))
|
||||||
|
|
||||||
|
|
||||||
|
def compare():
|
||||||
|
"""Table IV's learned row: the same four columns as the structured
|
||||||
|
scheme, under the same jammer at a JSR of 0 dB."""
|
||||||
|
print("[learned] scheme comparison ...")
|
||||||
|
m = learned_model()
|
||||||
|
F = 300_000
|
||||||
|
legit = eval_ser_sse(m, [10.0], frames=F)[0]
|
||||||
|
out = eval_ser_eve(m, eve_wrong_mask(m.users, m.L,
|
||||||
|
seed=20260813).to(DEVICE),
|
||||||
|
[10.0], frames=F)[0]
|
||||||
|
ins = eval_ser_eve(m, m.masks().detach().roll(1, 0), [10.0], frames=F)[0]
|
||||||
|
jam = eval_ser_jam(m, 10.0, [0.0], frames=F, mode="blind", target=0)[0]
|
||||||
|
write_csv(DATA / "compare_learned.csv",
|
||||||
|
["scheme", "legit_ser", "eve_out", "eve_in", "jam0_ser"],
|
||||||
|
[("proposed_learned", legit, out, ins, jam)])
|
||||||
|
print(" legit %.4f out %.4f in %.4f jam %.4f"
|
||||||
|
% (legit, out, ins, jam))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
keylen()
|
||||||
|
jamming()
|
||||||
|
kpa()
|
||||||
|
refresh()
|
||||||
|
compare()
|
||||||
|
print("[done] learned-key CSVs in", DATA)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+13
-10
@@ -11,15 +11,17 @@ from pathlib import Path
|
|||||||
DATA = Path(__file__).resolve().parents[1] / "data"
|
DATA = Path(__file__).resolve().parents[1] / "data"
|
||||||
|
|
||||||
NAME = {
|
NAME = {
|
||||||
"proposed": r"\textbf{Proposed keyed masking}",
|
"proposed": r"\textbf{KM (structured)}",
|
||||||
|
"proposed_learned": r"\textbf{KM (learned)}",
|
||||||
"public_mask": "Public masks",
|
"public_mask": "Public masks",
|
||||||
"perm_key": r"Permutation key~\cite{chen2025shufflingtifs}",
|
"perm_key": r"Permutation key~\cite{chen2025shufflingtifs}",
|
||||||
"index_cipher": "Index cipher",
|
"index_cipher": "Index cipher",
|
||||||
"oma_plain": "OMA (no encryption)",
|
"oma_plain": "OMA (no encryption)",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
"hadamard": "Walsh-Hadamard",
|
"hadamard": "Structured",
|
||||||
"learned": "Learned",
|
"learned": "Learned, plain",
|
||||||
"learned_reg": r"Regularized~\eqref{eq:regloss}",
|
"learned_reg": r"Learned, regularized~\eqref{eq:regloss}",
|
||||||
|
"invariant_learned": r"\textbf{Invariant, learned keys}",
|
||||||
}
|
}
|
||||||
RECEIVER = {
|
RECEIVER = {
|
||||||
"legit": "Legitimate", "oma": "OMA",
|
"legit": "Legitimate", "oma": "OMA",
|
||||||
@@ -57,7 +59,8 @@ def cell(x: str, bold: bool, wide: bool = False) -> str:
|
|||||||
def compare_table():
|
def compare_table():
|
||||||
print("% Table: scheme comparison (from sec_compare.csv)")
|
print("% Table: scheme comparison (from sec_compare.csv)")
|
||||||
rows = list(csv.DictReader(open(DATA / "sec_compare.csv")))
|
rows = list(csv.DictReader(open(DATA / "sec_compare.csv")))
|
||||||
order = ["public_mask", "perm_key", "index_cipher", "oma_plain", "proposed"]
|
order = ["public_mask", "perm_key", "index_cipher", "oma_plain",
|
||||||
|
"proposed", "proposed_learned"]
|
||||||
rows.sort(key=lambda r: order.index(r["scheme"]))
|
rows.sort(key=lambda r: order.index(r["scheme"]))
|
||||||
# stage_E does not jam the orthogonal reference, because the jammer an
|
# stage_E does not jam the orthogonal reference, because the jammer an
|
||||||
# OMA user faces is targeted at public slots rather than mask-matched
|
# OMA user faces is targeted at public slots rather than mask-matched
|
||||||
@@ -67,13 +70,13 @@ def compare_table():
|
|||||||
for r in csv.DictReader(open(DATA / "sec_jam_cmp.csv"))}
|
for r in csv.DictReader(open(DATA / "sec_jam_cmp.csv"))}
|
||||||
oma_jam = jam[0.0]["oma_targeted"]
|
oma_jam = jam[0.0]["oma_targeted"]
|
||||||
for r in rows:
|
for r in rows:
|
||||||
b = r["scheme"] == "proposed"
|
b = r["scheme"].startswith("proposed")
|
||||||
if r["scheme"] == "oma_plain" and f3(r["jam0_ser"]) == "--":
|
if r["scheme"] == "oma_plain" and f3(r["jam0_ser"]) == "--":
|
||||||
r["jam0_ser"] = oma_jam
|
r["jam0_ser"] = oma_jam
|
||||||
# four decimals would still print 1.0000 here, so the column
|
# four decimals would still print 1.0000 here, so the column
|
||||||
# stays at three and the caption names the chance level
|
# stays at three and the caption names the chance level
|
||||||
cells = [cell(r[k], b) for k in
|
cells = [cell(r[k], b) for k in
|
||||||
("legit_ser", "eve_out", "eve_in", "jam0_ser")]
|
("eve_out", "eve_in", "jam0_ser")]
|
||||||
print(f"{NAME[r['scheme']]} & " + " & ".join(cells) + r" \\")
|
print(f"{NAME[r['scheme']]} & " + " & ".join(cells) + r" \\")
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +87,7 @@ def maskfam_table():
|
|||||||
# emphasized the same way the proposed row is in the comparison
|
# emphasized the same way the proposed row is in the comparison
|
||||||
b = r["family"] == "hadamard"
|
b = r["family"] == "hadamard"
|
||||||
cells = [cell(r[k], b) for k in
|
cells = [cell(r[k], b) for k in
|
||||||
("legit_ser", "eve_ser", "eve_ones_ser", "mask_xcorr")]
|
("legit_ser", "eve_ser", "mask_xcorr")]
|
||||||
name = NAME[r["family"]]
|
name = NAME[r["family"]]
|
||||||
if b:
|
if b:
|
||||||
name = r"\textbf{" + name + "}"
|
name = r"\textbf{" + name + "}"
|
||||||
@@ -94,8 +97,8 @@ def maskfam_table():
|
|||||||
def refresh_tables():
|
def refresh_tables():
|
||||||
print("% Table: key refresh (from refresh_summary.csv)")
|
print("% Table: key refresh (from refresh_summary.csv)")
|
||||||
for r in csv.DictReader(open(DATA / "refresh_summary.csv")):
|
for r in csv.DictReader(open(DATA / "refresh_summary.csv")):
|
||||||
b = r["scheme"] == "Invariant"
|
b = r["scheme"].startswith("Invariant")
|
||||||
name = r"\textbf{Invariant}" if b else r["scheme"]
|
name = (r"\textbf{" + r["scheme"] + "}") if b else r["scheme"]
|
||||||
f = (lambda t: r"\mathbf{" + t + "}") if b else (lambda t: t)
|
f = (lambda t: r"\mathbf{" + t + "}") if b else (lambda t: t)
|
||||||
print(f"{name} & ${f(format(float(r['legit']), '.3f'))}$ & "
|
print(f"{name} & ${f(format(float(r['legit']), '.3f'))}$ & "
|
||||||
f"${f(format(float(r['eve']), '.4f'))}$ & "
|
f"${f(format(float(r['eve']), '.4f'))}$ & "
|
||||||
|
|||||||
+75
-47
@@ -56,27 +56,46 @@ plt.rcParams.update({
|
|||||||
})
|
})
|
||||||
AXES_RECT = dict(left=0.215, right=0.970, top=0.955, bottom=0.225)
|
AXES_RECT = dict(left=0.215, right=0.970, top=0.955, bottom=0.225)
|
||||||
|
|
||||||
C_LEGIT = "#c0392b"
|
C_LEGIT = "#c0392b" # KM, structured keys
|
||||||
C_EVE = "#2c5fa8"
|
C_LEARN = "#d98c00" # KM, learned keys
|
||||||
C_OMA = "#7f8c8d"
|
C_OMA = "#7f8c8d" # orthogonal multiple access
|
||||||
C_CH = "#95a5a6"
|
C_PUB = "#16a085" # public masks
|
||||||
C_MATCH = "#8e44ad"
|
C_PERM = "#8e44ad" # permutation key
|
||||||
C_PUB = "#16a085"
|
C_PAD = "#a0522d" # index cipher
|
||||||
C_LEARN = "#d98c00"
|
C_EVE = "#2c5fa8" # an adversary of KM
|
||||||
|
C_CH = "#95a5a6" # chance and reference levels
|
||||||
|
C_MATCH = C_PUB # the matched jammer is what public masks admit
|
||||||
|
|
||||||
|
# One entry per curve the figures draw. Colour identifies the scheme and
|
||||||
|
# line style the role: solid for a legitimate rate, dashed for an
|
||||||
|
# adversary, dash-dot for a comparison scheme, dotted for a reference.
|
||||||
|
# Every figure reads its curves from here, so a reader who learns a
|
||||||
|
# curve in one figure reads the same curve in the next.
|
||||||
|
STY = {
|
||||||
|
"km_str": dict(color=C_LEGIT, marker="o", ls="-"),
|
||||||
|
"km_lrn": dict(color=C_LEARN, marker="d", ls="-"),
|
||||||
|
"oma": dict(color=C_OMA, marker="^", ls=":"),
|
||||||
|
"pub": dict(color=C_PUB, marker="v", ls="-."),
|
||||||
|
"perm": dict(color=C_PERM, marker="X", ls="--"),
|
||||||
|
"pad": dict(color=C_PAD, marker="P", ls="-."),
|
||||||
|
"eve": dict(color=C_EVE, marker="s", ls="--"),
|
||||||
|
"insider": dict(color=C_EVE, marker="v", ls="-."),
|
||||||
|
}
|
||||||
|
|
||||||
# fixed label dictionary: tables and prose copy these strings verbatim
|
# fixed label dictionary: tables and prose copy these strings verbatim
|
||||||
LBL = {
|
LBL = {
|
||||||
"legit": "Legitimate",
|
"legit": "KM (structured)",
|
||||||
"legit_learned": "Learned keys",
|
"legit_learned": "KM (learned)",
|
||||||
"oma": "OMA",
|
"oma": "OMA",
|
||||||
"eve_pub": "Eavesdropper, public masks",
|
"eve_pub": "Eavesdropper, public masks",
|
||||||
"eve_key": "Eavesdropper", # the wrong-key condition is in the caption
|
"eve_key": "Eavesdropper", # the wrong-key condition is in the caption
|
||||||
"chance": "Random guess",
|
"chance": "Random guess",
|
||||||
"nojam": "No jammer",
|
"nojam": "No jammer",
|
||||||
"mask": "Keyed masking",
|
"mask": "KM (structured)",
|
||||||
"perm": "Permutation key",
|
"perm": "Permutation key",
|
||||||
"pad": "Index cipher",
|
"pad": "Index cipher",
|
||||||
"insider": "Insider",
|
"insider": "Insider",
|
||||||
|
"legit_ref": "Legitimate rate",
|
||||||
"outsider": "Outsider",
|
"outsider": "Outsider",
|
||||||
}
|
}
|
||||||
# deliberate-layering style for the LOWER of two coinciding curves
|
# deliberate-layering style for the LOWER of two coinciding curves
|
||||||
@@ -207,7 +226,7 @@ def main_legit(snr_db="10"):
|
|||||||
def place_legend(ax, cands=("lower left", "upper left", "center left",
|
def place_legend(ax, cands=("lower left", "upper left", "center left",
|
||||||
"center right", "lower center", "upper right",
|
"center right", "lower center", "upper right",
|
||||||
"upper center", "center", "lower right"),
|
"upper center", "center", "lower right"),
|
||||||
sizes=(9.2, 8.8, 8.4, 8.0, 7.6, 7.2), ncol=1):
|
sizes=(9.2, 8.8, 8.4, 8.0, 7.6, 7.2, 6.8, 6.4), ncol=1):
|
||||||
"""Choose the location and font size whose box the fewest curve points
|
"""Choose the location and font size whose box the fewest curve points
|
||||||
fall inside, scored on rendered geometry rather than guessed from the
|
fall inside, scored on rendered geometry rather than guessed from the
|
||||||
data. The size sweep is what makes a long label set placeable: a
|
data. The size sweep is what makes a long label set placeable: a
|
||||||
@@ -271,23 +290,21 @@ def fig_snr():
|
|||||||
# legitimate and the public-mask eavesdropper coincide by
|
# legitimate and the public-mask eavesdropper coincide by
|
||||||
# construction (same physical layer, public masks decode alike), so
|
# construction (same physical layer, public masks decode alike), so
|
||||||
# the pair is deliberately layered; OMA is separate at this frame
|
# the pair is deliberately layered; OMA is separate at this frame
|
||||||
ax.semilogy(x, col(r, "legit"), color=C_LEGIT, marker="o", ls="-",
|
ax.semilogy(x, col(r, "legit"), **STY["km_str"],
|
||||||
markevery=(0, 3), label=LBL["legit"], **UNDER)
|
markevery=(0, 3), label=LBL["legit"], **UNDER)
|
||||||
# the learned family is the other end of the key-space trade-off,
|
# the learned family is the other end of the key-space trade-off,
|
||||||
# so the figure carries what it costs at every SNR
|
# so the figure carries what it costs at every SNR
|
||||||
rl = load("sec_snr_learned.csv")
|
rl = load("sec_snr_learned.csv")
|
||||||
ax.semilogy(col(rl, "snr_db"), col(rl, "legit"), color=C_LEARN,
|
ax.semilogy(col(rl, "snr_db"), col(rl, "legit"), **STY["km_lrn"],
|
||||||
marker="d", ls="-", markevery=(2, 3),
|
markevery=(2, 3), label=LBL["legit_learned"])
|
||||||
label=LBL["legit_learned"])
|
ax.semilogy(x, col(r, "oma"), **STY["oma"],
|
||||||
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
|
|
||||||
markevery=(1, 3), label=LBL["oma"], **OVER)
|
markevery=(1, 3), label=LBL["oma"], **OVER)
|
||||||
ax.semilogy(x, col(r, "eve_public"), color=C_PUB, marker="v",
|
ax.semilogy(x, col(r, "eve_public"), color=STY["pub"]["color"], marker=STY["pub"]["marker"],
|
||||||
ls="none", markevery=(2, 3), markerfacecolor="none",
|
ls="none", markevery=(2, 3), markerfacecolor="none",
|
||||||
label=LBL["eve_pub"])
|
label=LBL["eve_pub"])
|
||||||
# this figure carries two eavesdroppers, so the bare label of the
|
# this figure carries two eavesdroppers, so the bare label of the
|
||||||
# key-length figure would not tell them apart
|
# key-length figure would not tell them apart
|
||||||
ax.semilogy(x, col(r, "eve_wrong"), color=C_EVE, marker="s", ls="--",
|
ax.semilogy(x, col(r, "eve_wrong"), **STY["eve"], label="Eavesdropper, keyed")
|
||||||
label="Eavesdropper, keyed")
|
|
||||||
# the chance level lies within 3.5e-4 of the wrong-key curve, so it is
|
# the chance level lies within 3.5e-4 of the wrong-key curve, so it is
|
||||||
# drawn for reference but left out of the legend, which the caption
|
# drawn for reference but left out of the legend, which the caption
|
||||||
# names instead; five long entries leave this figure no clear corner
|
# names instead; five long entries leave this figure no clear corner
|
||||||
@@ -295,10 +312,9 @@ def fig_snr():
|
|||||||
ax.set_xlabel("SNR (dB)")
|
ax.set_xlabel("SNR (dB)")
|
||||||
ax.set_ylabel("SER")
|
ax.set_ylabel("SER")
|
||||||
ax.set_xlim(min(x), max(x))
|
ax.set_xlim(min(x), max(x))
|
||||||
# the five-entry legend needs more clear space than the four-entry
|
# most of a decade below the data leaves the lower-left genuinely
|
||||||
# one did, so the axis opens a further decade below the data; the
|
# empty, which is what gives the legend a clear berth
|
||||||
# lower-left is empty because every curve decays
|
ax.set_ylim(bottom=2e-4)
|
||||||
ax.set_ylim(bottom=2e-5)
|
|
||||||
place_legend(ax)
|
place_legend(ax)
|
||||||
save(fig, "fig_sec_snr")
|
save(fig, "fig_sec_snr")
|
||||||
|
|
||||||
@@ -310,14 +326,16 @@ def fig_keylen():
|
|||||||
r = load("sec_keylen.csv")
|
r = load("sec_keylen.csv")
|
||||||
x = col(r, "L", int)
|
x = col(r, "L", int)
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.semilogy(x, col(r, "legit_ser"), color=C_LEGIT, marker="o", ls="-",
|
ax.semilogy(x, col(r, "legit_ser"), **STY["km_str"], label=LBL["legit"])
|
||||||
label=LBL["legit"])
|
rl = load("sec_keylen_learned.csv")
|
||||||
|
ax.semilogy(col(rl, "L", int), col(rl, "legit_ser"), **STY["km_lrn"], label=LBL["legit_learned"])
|
||||||
op = [(l, v) for l, v in zip(x, col(r, "oma")) if not math.isnan(v)]
|
op = [(l, v) for l, v in zip(x, col(r, "oma")) if not math.isnan(v)]
|
||||||
ax.semilogy([p[0] for p in op], [p[1] for p in op], color=C_OMA,
|
ax.semilogy([p[0] for p in op], [p[1] for p in op], **STY["oma"], label=LBL["oma"])
|
||||||
marker="^", ls=":", label=LBL["oma"])
|
ax.semilogy(x, col(r, "eve_ser"), **STY["eve"], label=LBL["eve_key"])
|
||||||
ax.semilogy(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="--",
|
|
||||||
label=LBL["eve_key"])
|
|
||||||
ax.set_ylim(top=22.0) # headroom above the flat eavesdropper curve
|
ax.set_ylim(top=22.0) # headroom above the flat eavesdropper curve
|
||||||
|
# an error rate cannot exceed one, and the room below the data holds
|
||||||
|
# the legend, since every curve decays to the right
|
||||||
|
ax.set_ylim(top=1.4, bottom=1.2e-2)
|
||||||
ax.set_xlabel("Key length $L$")
|
ax.set_xlabel("Key length $L$")
|
||||||
ax.set_ylabel("SER")
|
ax.set_ylabel("SER")
|
||||||
ax.set_xscale("log", base=2)
|
ax.set_xscale("log", base=2)
|
||||||
@@ -335,14 +353,17 @@ def fig_jam():
|
|||||||
x = col(r, "jsr_db")
|
x = col(r, "jsr_db")
|
||||||
me = max(1, len(x) // 8)
|
me = max(1, len(x) // 8)
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.plot(x, col(r, "matched"), color=C_MATCH, marker="P", ls="--",
|
rj = load("sec_jam_learned.csv")
|
||||||
|
ax.plot(col(rj, "jsr_db"), col(rj, "blind"), **STY["km_lrn"],
|
||||||
|
markevery=(1, me), label=LBL["legit_learned"])
|
||||||
|
ax.plot(x, col(r, "matched"), **STY["pub"],
|
||||||
markevery=me, label="Public masks")
|
markevery=me, label="Public masks")
|
||||||
ax.plot(x, col(r, "oma_targeted"), color=C_PUB, marker="^", ls=":",
|
ax.plot(x, col(r, "oma_targeted"), **STY["oma"],
|
||||||
markevery=me, label=LBL["oma"])
|
markevery=me, label=LBL["oma"])
|
||||||
# the two blind curves agree to 0.002; deliberate layering
|
# the two blind curves agree to 0.002; deliberate layering
|
||||||
ax.plot(x, col(r, "blind"), color=C_LEGIT, marker="o", ls="-",
|
ax.plot(x, col(r, "blind"), **STY["km_str"],
|
||||||
markevery=(0, me), label=LBL["mask"], **UNDER)
|
markevery=(0, me), label=LBL["mask"], **UNDER)
|
||||||
ax.plot(x, col(r, "perm_blind"), color=C_EVE, marker="s", ls="-.",
|
ax.plot(x, col(r, "perm_blind"), **STY["perm"],
|
||||||
markevery=(me // 2, me), label=LBL["perm"], **OVER)
|
markevery=(me // 2, me), label=LBL["perm"], **OVER)
|
||||||
nojam = float(load("sec_jam.csv")[0]["nojam"])
|
nojam = float(load("sec_jam.csv")[0]["nojam"])
|
||||||
# the unjammed reference is named in the caption rather than in the
|
# the unjammed reference is named in the caption rather than in the
|
||||||
@@ -367,11 +388,11 @@ def fig_sens():
|
|||||||
r = load("sec_sens_cmp.csv")
|
r = load("sec_sens_cmp.csv")
|
||||||
x = col(r, "frac")
|
x = col(r, "frac")
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.plot(x, col(r, "ser_mask"), color=C_LEGIT, marker="o", ls="-",
|
ax.plot(x, col(r, "ser_mask"), **STY["km_str"],
|
||||||
markevery=(0, 3), label=LBL["mask"], **UNDER)
|
markevery=(0, 3), label=LBL["mask"], **UNDER)
|
||||||
ax.plot(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
|
ax.plot(x, col(r, "ser_perm"), **STY["perm"],
|
||||||
markevery=(1, 3), label=LBL["perm"], **OVER)
|
markevery=(1, 3), label=LBL["perm"], **OVER)
|
||||||
ax.plot(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
|
ax.plot(x, col(r, "ser_pad"), **STY["pad"],
|
||||||
markevery=(2, 3), lw=1.2, mfc="none", label=LBL["pad"])
|
markevery=(2, 3), lw=1.2, mfc="none", label=LBL["pad"])
|
||||||
# the chance level comes from the stored curve, not from a second
|
# the chance level comes from the stored curve, not from a second
|
||||||
# copy of the configuration constants
|
# copy of the configuration constants
|
||||||
@@ -379,7 +400,7 @@ def fig_sens():
|
|||||||
ax.axhline(chance, color=C_CH, ls=":", lw=0.9, label=LBL["chance"])
|
ax.axhline(chance, color=C_CH, ls=":", lw=0.9, label=LBL["chance"])
|
||||||
# the narration reads these curves against the legitimate rate
|
# the narration reads these curves against the legitimate rate
|
||||||
ax.axhline(main_legit(), color=C_OMA, ls=(0, (4, 2)), lw=0.9,
|
ax.axhline(main_legit(), color=C_OMA, ls=(0, (4, 2)), lw=0.9,
|
||||||
label=LBL["legit"])
|
label=LBL["legit_ref"])
|
||||||
ax.set_xlabel("Fraction of the key recovered")
|
ax.set_xlabel("Fraction of the key recovered")
|
||||||
ax.set_ylabel("Eavesdropper SER")
|
ax.set_ylabel("Eavesdropper SER")
|
||||||
ax.set_xlim(0, 1)
|
ax.set_xlim(0, 1)
|
||||||
@@ -393,15 +414,15 @@ def fig_brute():
|
|||||||
r = load("sec_brute_cmp.csv")
|
r = load("sec_brute_cmp.csv")
|
||||||
x = col(r, "K")
|
x = col(r, "K")
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
ax.semilogx(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
|
ax.semilogx(x, col(r, "ser_perm"), **STY["perm"],
|
||||||
markevery=(0, 3), label=LBL["perm"], **UNDER)
|
markevery=(0, 3), label=LBL["perm"], **UNDER)
|
||||||
ax.semilogx(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
|
ax.semilogx(x, col(r, "ser_pad"), **STY["pad"],
|
||||||
markevery=(1, 3), label=LBL["pad"], **OVER)
|
markevery=(1, 3), label=LBL["pad"], **OVER)
|
||||||
ax.semilogx(x, col(r, "ser_mask"), color=C_LEGIT, marker="o", ls="-",
|
ax.semilogx(x, col(r, "ser_mask"), **STY["km_str"],
|
||||||
markevery=(2, 3), label=LBL["mask"])
|
markevery=(2, 3), label=LBL["mask"])
|
||||||
legit = main_legit()
|
legit = main_legit()
|
||||||
ax.axhline(legit, color=C_OMA, ls=(0, (4, 2)), lw=0.9,
|
ax.axhline(legit, color=C_OMA, ls=(0, (4, 2)), lw=0.9,
|
||||||
label=LBL["legit"])
|
label=LBL["legit_ref"])
|
||||||
ax.set_xlabel("Number of key guesses $K$")
|
ax.set_xlabel("Number of key guesses $K$")
|
||||||
ax.set_ylabel("Eavesdropper SER")
|
ax.set_ylabel("Eavesdropper SER")
|
||||||
ax.set_ylim(0.0, 1.05) # keep the reference line off the spine
|
ax.set_ylim(0.0, 1.05) # keep the reference line off the spine
|
||||||
@@ -415,13 +436,13 @@ def fig_real():
|
|||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
# insider and outsider still nearly coincide and are layered; the
|
# insider and outsider still nearly coincide and are layered; the
|
||||||
# legitimate and OMA curves are separate at this frame
|
# legitimate and OMA curves are separate at this frame
|
||||||
ax.semilogy(x, col(r, "ter_legit"), color=C_LEGIT, marker="o", ls="-",
|
ax.semilogy(x, col(r, "ter_legit"), **STY["km_str"],
|
||||||
markevery=(0, 2), label=LBL["legit"], **UNDER)
|
markevery=(0, 2), label=LBL["legit"], **UNDER)
|
||||||
ax.semilogy(x, col(r, "ter_oma"), color=C_OMA, marker="^", ls=":",
|
ax.semilogy(x, col(r, "ter_oma"), **STY["oma"],
|
||||||
markevery=(1, 2), label=LBL["oma"], **OVER)
|
markevery=(1, 2), label=LBL["oma"], **OVER)
|
||||||
ax.semilogy(x, col(r, "ter_insider"), color=C_PUB, marker="v", ls="-.",
|
ax.semilogy(x, col(r, "ter_insider"), **STY["insider"],
|
||||||
markevery=(0, 2), label=LBL["insider"], **UNDER)
|
markevery=(0, 2), label=LBL["insider"], **UNDER)
|
||||||
ax.semilogy(x, col(r, "ter_eve"), color=C_EVE, marker="s", ls="--",
|
ax.semilogy(x, col(r, "ter_eve"), **STY["eve"],
|
||||||
markevery=(1, 2), label=LBL["outsider"], **OVER)
|
markevery=(1, 2), label=LBL["outsider"], **OVER)
|
||||||
ax.set_xlabel("SNR (dB)")
|
ax.set_xlabel("SNR (dB)")
|
||||||
ax.set_ylabel("TER")
|
ax.set_ylabel("TER")
|
||||||
@@ -444,7 +465,14 @@ def fig_kpa():
|
|||||||
ser = [float(row["eve_ser"]) for row in rows]
|
ser = [float(row["eve_ser"]) for row in rows]
|
||||||
ax.semilogx(n, ser, color=c, marker=mk, ls="-",
|
ax.semilogx(n, ser, color=c, marker=mk, ls="-",
|
||||||
markevery=(off, 4), markerfacecolor="none" if off else c,
|
markevery=(off, 4), markerfacecolor="none" if off else c,
|
||||||
label=LBL["mask"] + f", {int(snr)} dB")
|
label=f"KM (str.), {int(snr)} dB")
|
||||||
|
if snr == 10.0:
|
||||||
|
kl = [q for q in load("kpa_learned.csv")
|
||||||
|
if float(q["snr_db"]) == snr]
|
||||||
|
ax.semilogx([float(q["n_frames"]) for q in kl],
|
||||||
|
[float(q["eve_ser"]) for q in kl], **STY["km_lrn"],
|
||||||
|
markevery=(2, 4),
|
||||||
|
label=f"KM (lrn.), {int(snr)} dB")
|
||||||
try:
|
try:
|
||||||
p = load("pkpa.csv")
|
p = load("pkpa.csv")
|
||||||
ax.semilogx(col(p, "n_frames"), col(p, "eve_ser"), color=C_MATCH,
|
ax.semilogx(col(p, "n_frames"), col(p, "eve_ser"), color=C_MATCH,
|
||||||
@@ -458,7 +486,7 @@ def fig_kpa():
|
|||||||
# scheme-comparison table
|
# scheme-comparison table
|
||||||
legit = main_legit()
|
legit = main_legit()
|
||||||
ax.axhline(legit, color=C_OMA, ls=(0, (4, 2)), lw=0.9,
|
ax.axhline(legit, color=C_OMA, ls=(0, (4, 2)), lw=0.9,
|
||||||
label=LBL["legit"])
|
label=LBL["legit_ref"])
|
||||||
ax.set_xlabel("Known-plaintext frames $N$")
|
ax.set_xlabel("Known-plaintext frames $N$")
|
||||||
ax.set_ylabel("Eavesdropper SER")
|
ax.set_ylabel("Eavesdropper SER")
|
||||||
ax.set_xscale("log", base=2)
|
ax.set_xscale("log", base=2)
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
scheme,legit_ser,eve_out,eve_in,jam0_ser
|
||||||
|
proposed_learned,0.06358416667,0.9998083333,0.9999833333,0.4046066667
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
snr_db,n_frames,kappa,eve_ser
|
||||||
|
0,1,0.2127109103,0.993336
|
||||||
|
0,2,0.7559393242,0.666508
|
||||||
|
0,3,0.8627683729,0.392272125
|
||||||
|
0,4,0.9179756209,0.218212875
|
||||||
|
0,5,0.945390512,0.14425925
|
||||||
|
0,6,0.9590921029,0.107493
|
||||||
|
0,8,0.9725584686,0.086112625
|
||||||
|
0,10,0.9778045967,0.07933375
|
||||||
|
0,12,0.9831736788,0.07420325
|
||||||
|
0,16,0.9879393309,0.070712875
|
||||||
|
0,24,0.9925390184,0.067800375
|
||||||
|
0,32,0.9945808738,0.06649325
|
||||||
|
0,48,0.9965663388,0.065265
|
||||||
|
0,64,0.9975094497,0.065024875
|
||||||
|
10,1,0.3165432975,0.948993875
|
||||||
|
10,2,0.9290210679,0.19776825
|
||||||
|
10,3,0.9781143948,0.095876875
|
||||||
|
10,4,0.9896475986,0.070495125
|
||||||
|
10,5,0.9934410676,0.067297125
|
||||||
|
10,6,0.9953705788,0.06628575
|
||||||
|
10,8,0.9971551418,0.06520175
|
||||||
|
10,10,0.9979188025,0.064684875
|
||||||
|
10,12,0.9982561454,0.06454375
|
||||||
|
10,16,0.9987509355,0.06426625
|
||||||
|
10,24,0.9992754847,0.064091125
|
||||||
|
10,32,0.9994516179,0.06386675
|
||||||
|
10,48,0.9996520028,0.063819875
|
||||||
|
10,64,0.999746412,0.063847125
|
||||||
|
20,1,0.742194891,0.513137625
|
||||||
|
20,2,0.9947786465,0.06753025
|
||||||
|
20,3,0.9986294076,0.06438075
|
||||||
|
20,4,0.9992210969,0.064160375
|
||||||
|
20,5,0.9994008377,0.064080125
|
||||||
|
20,6,0.9995701849,0.063900375
|
||||||
|
20,8,0.9997365534,0.063852875
|
||||||
|
20,10,0.9998067141,0.063813
|
||||||
|
20,12,0.9998438716,0.06370225
|
||||||
|
20,16,0.9998808399,0.063670875
|
||||||
|
20,24,0.9999239221,0.06380125
|
||||||
|
20,32,0.9999452353,0.063820625
|
||||||
|
20,48,0.9999649763,0.063811
|
||||||
|
20,64,0.9999733046,0.06377525
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
block,legit_ser,eve_ser
|
||||||
|
0,0.06381666667,0.9999375
|
||||||
|
1,0.06395583333,0.9981941667
|
||||||
|
2,0.06397833333,0.999985
|
||||||
|
3,0.06386833333,0.9983758333
|
||||||
|
4,0.06338666667,0.9999908333
|
||||||
|
5,0.06366416667,0.9999133333
|
||||||
|
6,0.06398416667,0.9999433333
|
||||||
|
7,0.06390583333,0.9809091667
|
||||||
|
@@ -2,3 +2,4 @@ scheme,legit,eve,entropy_bits
|
|||||||
None (fixed key),0.05300666667,0.9997075,23.76910417
|
None (fixed key),0.05300666667,0.9997075,23.76910417
|
||||||
Fresh orthogonal keys,0.1215548611,0.9996535069,23.76910417
|
Fresh orthogonal keys,0.1215548611,0.9996535069,23.76910417
|
||||||
Invariant,0.05304121528,0.9995528819,364.5801064
|
Invariant,0.05304121528,0.9995528819,364.5801064
|
||||||
|
"Invariant, learned keys",0.063800,0.997200,364.5801064
|
||||||
|
|||||||
|
@@ -4,3 +4,4 @@ public_mask,0.0529425,0.0529425,0.0529425,0.83882
|
|||||||
perm_key,0.0528525,0.9999925,0.0528525,0.36425
|
perm_key,0.0528525,0.9999925,0.0528525,0.36425
|
||||||
index_cipher,0.0529425,0.9999847412,0.9999847412,0.83882
|
index_cipher,0.0529425,0.9999847412,0.9999847412,0.83882
|
||||||
oma_plain,0.08056383667,0.08056383667,0.08056383667,nan
|
oma_plain,0.08056383667,0.08056383667,0.08056383667,nan
|
||||||
|
proposed_learned,0.06358416667,0.9998083333,0.9999833333,0.4046066667
|
||||||
|
|||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
jsr_db,blind,matched,nojam
|
||||||
|
-10,0.117582,0.40832,0.062852
|
||||||
|
-5,0.215158,0.657572,0.062852
|
||||||
|
0,0.404244,0.851282,0.062852
|
||||||
|
5,0.648786,0.94662,0.062852
|
||||||
|
10,0.839218,0.982652,0.062852
|
||||||
|
15,0.939304,0.994184,0.062852
|
||||||
|
20,0.979206,0.99818,0.062852
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
L,d,legit_ser,eve_ser,mask_xcorr,oma
|
||||||
|
8,32,0.9297855,0.9998735,0.007307400461,0.6849191155
|
||||||
|
12,48,0.416604,0.9997065,0.005153660662,nan
|
||||||
|
16,64,0.2762895,0.999912,0.007116591092,0.2747696909
|
||||||
|
20,80,0.2076175,0.999383,0.003162040841,0.2289444229
|
||||||
|
24,96,0.1829615,0.999894,0.002973971656,0.1961714033
|
||||||
|
32,128,0.131901,0.999616,0.005575809628,0.1524639978
|
||||||
|
48,192,0.090206,0.9994575,0.005743456539,0.1054308944
|
||||||
|
64,256,0.0635265,0.999637,0.006678360514,0.08056383667
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user