Reproducibility package for the TIFS submission: transmit and receive core, security stages (eavesdropper, jamming, key families, attack difficulty, known-plaintext), real BERT token streams, closed-form verification, and the scripts that regenerate every figure and table from the released CSVs.
254 lines
8.2 KiB
Python
254 lines
8.2 KiB
Python
"""Canonical replot script for paper 11: regenerates every result figure
|
|
from ../data/*.csv and writes paper-ready PDFs to ../fig/. No experiment
|
|
is rerun. All result plots share one canvas and axes rectangle (8:6 box).
|
|
Label dictionary is fixed here and copied verbatim into tables and prose.
|
|
|
|
fig_sec_snr.pdf : legitimate vs eavesdropper SER vs SNR (Fig. 2)
|
|
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)
|
|
"""
|
|
from __future__ import annotations
|
|
from pathlib import Path
|
|
import csv
|
|
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DATA = ROOT / "data"
|
|
FIG = ROOT / "fig"
|
|
FIG.mkdir(exist_ok=True)
|
|
|
|
plt.rcParams.update({
|
|
"font.family": "serif",
|
|
"font.serif": ["DejaVu Serif", "Times New Roman"],
|
|
"font.size": 9,
|
|
"axes.labelsize": 9,
|
|
"legend.fontsize": 6.6,
|
|
"xtick.labelsize": 8,
|
|
"ytick.labelsize": 8,
|
|
"axes.grid": True,
|
|
"grid.linestyle": "--",
|
|
"grid.linewidth": 0.4,
|
|
"grid.alpha": 0.6,
|
|
"lines.linewidth": 1.3,
|
|
"lines.markersize": 3.4,
|
|
"figure.figsize": (3.15, 2.36),
|
|
"pdf.fonttype": 42,
|
|
})
|
|
AXES_RECT = dict(left=0.185, right=0.965, top=0.955, bottom=0.195)
|
|
|
|
C_LEGIT = "#c0392b"
|
|
C_EVE = "#2c5fa8"
|
|
C_OMA = "#7f8c8d"
|
|
C_CH = "#95a5a6"
|
|
C_MATCH = "#8e44ad"
|
|
C_PUB = "#16a085"
|
|
|
|
# fixed label dictionary: tables and prose copy these strings verbatim
|
|
LBL = {
|
|
"legit": "Legitimate",
|
|
"oma": "OMA",
|
|
"eve_pub": "Eve, public masks",
|
|
"eve_key": "Eve, wrong key",
|
|
"chance": "Random guess",
|
|
"jam_m": "Matched jammer (public masks)",
|
|
"jam_b": "Blind jammer (proposed)",
|
|
"nojam": "No jammer",
|
|
}
|
|
|
|
|
|
def load(name):
|
|
with open(DATA / name) as f:
|
|
return list(csv.DictReader(f))
|
|
|
|
|
|
def col(rows, k, f=float):
|
|
return [f(r[k]) for r in rows]
|
|
|
|
|
|
def save(fig, name):
|
|
"""Write the figure and assert that no axis label is clipped.
|
|
|
|
A long y label, or wide minor tick labels such as 6x10^-1 on a log
|
|
axis that spans less than a decade, silently pushes the label off
|
|
the canvas under the fixed axes rectangle. Reading the plotting code
|
|
cannot reveal this, so the check is made on the rendered geometry.
|
|
"""
|
|
fig.subplots_adjust(**AXES_RECT)
|
|
fig.canvas.draw()
|
|
fbox = fig.get_window_extent()
|
|
for ax in fig.axes:
|
|
for lbl in (ax.yaxis.label, ax.xaxis.label):
|
|
if not lbl.get_text():
|
|
continue
|
|
b = lbl.get_window_extent()
|
|
if (b.x0 < fbox.x0 or b.y0 < fbox.y0
|
|
or b.x1 > fbox.x1 or b.y1 > fbox.y1):
|
|
raise RuntimeError(
|
|
f"{name}: axis label '{lbl.get_text()}' is clipped "
|
|
f"(label {b} outside figure {fbox}); shorten the "
|
|
f"label or widen the margin")
|
|
fig.savefig(FIG / f"{name}.pdf")
|
|
plt.close(fig)
|
|
print("[OK]", name)
|
|
|
|
|
|
def fig_snr():
|
|
r = load("sec_snr.csv")
|
|
x = col(r, "snr_db")
|
|
fig, ax = plt.subplots()
|
|
ax.semilogy(x, col(r, "legit"), color=C_LEGIT, marker="o", ls="-",
|
|
label=LBL["legit"])
|
|
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
|
|
label=LBL["oma"])
|
|
ax.semilogy(x, col(r, "eve_public"), color=C_PUB, marker="v",
|
|
ls="none", markersize=5.2, markerfacecolor="none",
|
|
label=LBL["eve_pub"])
|
|
ax.semilogy(x, col(r, "eve_wrong"), color=C_EVE, marker="s", ls="--",
|
|
label=LBL["eve_key"])
|
|
ax.plot(x, col(r, "chance"), color=C_CH, ls="-.", lw=0.9,
|
|
label=LBL["chance"])
|
|
ax.set_xlabel("SNR (dB)")
|
|
ax.set_ylabel("SER")
|
|
ax.set_xlim(min(x), max(x))
|
|
ax.legend(loc="lower left")
|
|
save(fig, "fig_sec_snr")
|
|
|
|
|
|
def fig_keylen():
|
|
r = load("sec_keylen.csv")
|
|
x = col(r, "L", int)
|
|
fig, ax = plt.subplots()
|
|
ax.semilogy(x, col(r, "legit_ser"), color=C_LEGIT, marker="o", ls="-",
|
|
label=LBL["legit"])
|
|
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
|
|
label=LBL["oma"])
|
|
ax.semilogy(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="--",
|
|
label=LBL["eve_key"])
|
|
ax.set_xlabel("Key length $L$")
|
|
ax.set_ylabel("SER")
|
|
ax.set_xscale("log", base=2)
|
|
ax.legend(loc="center right", bbox_to_anchor=(0.98, 0.72))
|
|
save(fig, "fig_sec_keylen")
|
|
|
|
|
|
def fig_jam():
|
|
# the target-user SER spans 0.3 to 1.0, less than one decade, so a
|
|
# linear axis is used: a log axis here produces wide minor tick
|
|
# labels (6x10^-1) that crowd out the y label under the fixed
|
|
# axes rectangle
|
|
r = load("sec_jam.csv")
|
|
x = col(r, "jsr_db")
|
|
fig, ax = plt.subplots()
|
|
ax.plot(x, col(r, "matched"), color=C_MATCH, marker="P", ls="--",
|
|
label=LBL["jam_m"])
|
|
ax.plot(x, col(r, "blind"), color=C_LEGIT, marker="o", ls="-",
|
|
label=LBL["jam_b"])
|
|
nojam = col(r, "nojam")[0]
|
|
ax.axhline(nojam, color=C_OMA, ls=":", lw=0.9, label=LBL["nojam"])
|
|
ax.set_xlabel("JSR (dB)")
|
|
ax.set_ylabel("SER")
|
|
ax.set_xlim(min(x), max(x))
|
|
ax.set_ylim(0.2, 1.02)
|
|
ax.legend(loc="lower right")
|
|
save(fig, "fig_sec_jam")
|
|
|
|
|
|
def fig_sens():
|
|
r = load("sec_sens.csv")
|
|
x = col(r, "rho")
|
|
fig, ax = plt.subplots()
|
|
ax.plot(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="-",
|
|
label=LBL["eve_key"])
|
|
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.set_ylabel("Eavesdropper SER")
|
|
ax.set_xlim(0, 1)
|
|
ax.legend(loc="lower left")
|
|
save(fig, "fig_sec_sens")
|
|
|
|
|
|
def fig_brute():
|
|
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]:
|
|
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.set_xlabel("Number of key guesses $K$")
|
|
ax.set_ylabel("Eavesdropper SER")
|
|
ax.legend(loc="lower left")
|
|
save(fig, "fig_sec_brute")
|
|
|
|
|
|
def fig_real():
|
|
r = load("real_sec_ter.csv")
|
|
x = col(r, "snr_db")
|
|
fig, ax = plt.subplots()
|
|
ax.semilogy(x, col(r, "ter_legit"), color=C_LEGIT, marker="o", ls="-",
|
|
label=LBL["legit"])
|
|
ax.semilogy(x, col(r, "ter_oma"), color=C_OMA, marker="^", ls=":",
|
|
label=LBL["oma"])
|
|
ax.semilogy(x, col(r, "ter_insider"), color=C_PUB, marker="v", ls="-.",
|
|
label="Insider")
|
|
ax.semilogy(x, col(r, "ter_eve"), color=C_EVE, marker="s", ls="--",
|
|
label=LBL["eve_key"])
|
|
ax.set_xlabel("SNR (dB)")
|
|
ax.set_ylabel("TER")
|
|
ax.set_xlim(min(x), max(x))
|
|
ax.legend(loc="lower left")
|
|
save(fig, "fig_sec_real")
|
|
|
|
|
|
def fig_kpa():
|
|
r = load("kpa.csv")
|
|
fig, ax = plt.subplots()
|
|
sty = {0.0: ("#c0392b", "o"), 10.0: ("#2c5fa8", "s"),
|
|
20.0: ("#16a085", "v")}
|
|
for snr, (c, mk) in sty.items():
|
|
rows = [row for row in r if float(row["snr_db"]) == snr]
|
|
n = [float(row["n_frames"]) for row in rows]
|
|
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"])
|
|
ax.set_xlabel("Known-plaintext frames $N$")
|
|
ax.set_ylabel("Eavesdropper SER")
|
|
ax.set_xscale("log", base=2)
|
|
ax.legend(loc="upper right")
|
|
save(fig, "fig_sec_kpa")
|
|
|
|
|
|
def main():
|
|
fig_snr()
|
|
fig_keylen()
|
|
fig_jam()
|
|
try:
|
|
fig_sens()
|
|
fig_brute()
|
|
except FileNotFoundError:
|
|
print("[skip] attack-difficulty CSVs not present yet")
|
|
try:
|
|
fig_real()
|
|
except FileNotFoundError:
|
|
print("[skip] real-token CSV not present yet")
|
|
try:
|
|
fig_kpa()
|
|
except FileNotFoundError:
|
|
print("[skip] known-plaintext CSV not present yet")
|
|
print("[done] figures in", FIG)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|