diff --git a/code/exp_infotheory.py b/code/exp_infotheory.py new file mode 100644 index 0000000..0261532 --- /dev/null +++ b/code/exp_infotheory.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +"""Information-theoretic security metrics for the main configuration. + +The evaluation so far reported only the eavesdropper SER. This stage adds +the quantities a physical-layer-security reader expects, all computed +from the SAME Monte Carlo the SER curves use, so no new modelling +assumption enters. + +Every metric is derived from the empirical joint law of the transmitted +digit and the DECISION each receiver makes. That decision is a +deterministic function of the received frame, so the data-processing +inequality makes each leakage number a LOWER bound on the true +I(s_u; y_E): what the modelled correlation eavesdropper actually +extracts. Reported per frame, an index carries P digits, so the frame +quantities are P times the per-digit ones under the independent-digit +source the evaluation uses. + + I(s;s_hat) mutual information between the digit and the decision + H(s|s_hat) equivocation, and its ratio to log2(V) + TV distinguishing advantage, the average total variation + between the decision law given a digit and its marginal + R_s secrecy rate, the legitimate information rate minus the + eavesdropper one, per frame + +Run under WSL. Writes data/infotheory.csv. +""" +from __future__ import annotations + +import csv +import math +import sys +from pathlib import Path + +import torch + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import sse_lib as L +from sse_lib import DATA, DEVICE, rayleigh_gain, snr_to_sigma2 +from exp_full import main_model, eve_wrong_mask +from exp_refresh import kdf_invariant, install + +FRAMES = 400_000 +CHUNK = 40_000 +SNRS = [0.0, 5.0, 10.0, 15.0, 20.0] + + +@torch.no_grad() +def confusion_refreshed(m, snr_db, sub_key, base_keys, base_book, + blocks=64, frames=FRAMES, seed=4242): + """The same joint counts when the key is redrawn from the invariance + group every block, against an eavesdropper holding one fixed + substitute. Each block contributes frames/blocks frames.""" + torch.manual_seed(seed + int(10 * snr_db)) + C = torch.zeros(m.vu, m.vu, dtype=torch.float64, device=DEVICE) + per = max(CHUNK // 4, frames // blocks) + for b in range(blocks): + sg, cp, up = kdf_invariant(5150, b, m.users, m.L) + install(m, sg * base_keys[up], base_book, colperm=cp) + C += confusion(m, snr_db, sub_key=sub_key, frames=per, + seed=seed + 97 * b) + install(m, base_keys, base_book) + return C + + +@torch.no_grad() +def confusion(m, snr_db, sub_key=None, frames=FRAMES, seed=777): + """Empirical joint counts of (transmitted digit, decided digit) for + user 0, pooled over the P periods. sub_key None means the legitimate + receiver; otherwise the eavesdropper substitutes that key.""" + torch.manual_seed(seed + int(10 * snr_db)) + C = torch.zeros(m.vu, m.vu, dtype=torch.float64, device=DEVICE) + keys = m.masks() if sub_key is None else sub_key.to(DEVICE) + done = 0 + while done < frames: + n = min(CHUNK, frames - done) + dig = torch.randint(m.vu, (n, m.users, m.P), device=DEVICE) + Bn = m.unit_codebook() + e = Bn[dig] / math.sqrt(m.P) + y = (e * m.masks()[None, :, None, :]).sum(dim=1) / m.c + h = rayleigh_gain((n, 1), device=DEVICE) + sig = snr_to_sigma2(torch.full((n,), snr_db), m.d).to(DEVICE).sqrt() + rx = h[:, :, None, None] * y[:, None] \ + + sig[:, None, None, None] * torch.randn(n, 1, m.P, m.L, + device=DEVICE) + r = rx / h[:, :, None, None].clamp_min(1e-6) + cand = Bn[None, :, :] * keys[:1, None, :] + dec = torch.einsum("nupl,uvl->nupv", r, cand).argmax(-1)[:, 0] + idx = dig[:, 0].reshape(-1) * m.vu + dec.reshape(-1) + C += torch.bincount(idx, minlength=m.vu * m.vu).reshape( + m.vu, m.vu).to(torch.float64) + done += n + return C + + +def metrics(C, P, V): + """Mutual information, equivocation and distinguishing advantage from + a joint count matrix, all in bits.""" + J = C / C.sum() + px, py = J.sum(1), J.sum(0) + nz = J > 0 + mi = float((J[nz] * (J[nz] / (px[:, None] * py[None, :])[nz]).log2()).sum()) + hx = float(-(px[px > 0] * px[px > 0].log2()).sum()) + equiv = hx - mi # H(digit | decision) + # distinguishing advantage: E_s || p(dec|s) - p(dec) ||_TV + cond = J / px[:, None].clamp_min(1e-300) + tv = float((px * 0.5 * (cond - py[None, :]).abs().sum(1)).sum()) + return {"mi_digit": mi, "equiv_digit": equiv, + "mi_frame": P * mi, "equiv_frame": P * equiv, + "equiv_ratio": P * equiv / math.log2(V), "tv": tv} + + +def main(): + m = main_model() + m.eval() + ew = eve_wrong_mask(m.users, m.L, seed=20260813) + base_keys = m.W.detach().clone().cpu() + base_book = m.B.detach().clone().cpu() + rows = [] + for snr in SNRS: + lg = metrics(confusion(m, snr), m.P, m.V) + ev = metrics(confusion(m, snr, sub_key=ew), m.P, m.V) + rf = metrics(confusion_refreshed(m, snr, ew, base_keys, base_book), + m.P, m.V) + rs = max(0.0, lg["mi_frame"] - ev["mi_frame"]) + rs_r = max(0.0, lg["mi_frame"] - rf["mi_frame"]) + rows.append((snr, + "%.4f" % lg["mi_frame"], "%.6f" % ev["mi_frame"], + "%.6f" % ev["equiv_ratio"], "%.6f" % ev["tv"], + "%.4f" % rs, + "%.6f" % rf["mi_frame"], "%.6f" % rf["equiv_ratio"], + "%.6f" % rf["tv"], "%.4f" % rs_r)) + print("%5.1f dB legit %6.3f | fixed key: MI %.3f TV %.3f Rs %6.3f " + "| refreshed: MI %.4f TV %.4f Rs %6.3f" + % (snr, lg["mi_frame"], ev["mi_frame"], ev["tv"], rs, + rf["mi_frame"], rf["tv"], rs_r), flush=True) + out = DATA / "infotheory.csv" + with open(out, "w", newline="") as f: + w = csv.writer(f) + w.writerow(["snr_db", "mi_legit_bits", + "mi_eve_fixed_bits", "equiv_ratio_fixed", "tv_fixed", + "secrecy_rate_fixed_bits", + "mi_eve_refresh_bits", "equiv_ratio_refresh", + "tv_refresh", "secrecy_rate_refresh_bits"]) + w.writerows(rows) + print("[csv]", out) + + +if __name__ == "__main__": + main() diff --git a/code/exp_semantic.py b/code/exp_semantic.py new file mode 100644 index 0000000..32e816a --- /dev/null +++ b/code/exp_semantic.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +"""Semantic leakage: does a wrong index still carry the meaning? + +The symbol error rate counts any wrong index as a total failure, which +is the right accounting for a bit pipe and the wrong one for a semantic +pipe: a token decoded as a near synonym has leaked the meaning even +though the index is wrong. This stage measures what the SER cannot see, +on two semantic scales. + + codeword cosine cos(e_shat, e_s) between the embedding a receiver + reconstructs and the transmitted one, uniform indices + BERT cosine cos of the BERT input embeddings of the decoded and + the transmitted token, on the AG News stream, which + is semantic similarity in the space the vocabulary + was built for + +Each is reported for the legitimate receiver, the outsider and the +insider, against the chance level of two independently drawn tokens. +A scheme leaks semantically if the adversary's similarity sits above +that chance level. + +Run under WSL. Writes data/semantic.csv. +""" +from __future__ import annotations + +import csv +import math +import sys +from pathlib import Path + +import torch +import torch.nn.functional as F + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import sse_lib as L +from sse_lib import DATA, DEVICE, rayleigh_gain, snr_to_sigma2 +from exp_full import main_model, eve_wrong_mask + +FRAMES = 200_000 +CHUNK = 20_000 +SNRS = [0.0, 10.0, 20.0] +REAL_SNRS = [0.0, 10.0, 20.0, 28.0] + + +@torch.no_grad() +def decide(m, dig, snr_db, keys, gen=None): + """Decisions of a receiver holding `keys`, for the frames carrying + `dig`. Returns the decided digits of user 0.""" + n = dig.shape[0] + Bn = m.unit_codebook() + e = Bn[dig] / math.sqrt(m.P) + y = (e * m.masks()[None, :, None, :]).sum(dim=1) / m.c + h = rayleigh_gain((n, 1), device=DEVICE) + sig = snr_to_sigma2(torch.full((n,), snr_db), m.d).to(DEVICE).sqrt() + rx = h[:, :, None, None] * y[:, None] \ + + sig[:, None, None, None] * torch.randn(n, 1, m.P, m.L, + device=DEVICE) + r = rx / h[:, :, None, None].clamp_min(1e-6) + cand = Bn[None, :, :] * keys[:1, None, :] + return torch.einsum("nupl,uvl->nupv", r, cand).argmax(-1)[:, 0] + + +def frame_embedding(m, digits): + """The d-dimensional embedding an index maps to, digits (N,P).""" + Bn = m.unit_codebook() + return (Bn[digits] / math.sqrt(m.P)).reshape(digits.shape[0], -1) + + +@torch.no_grad() +def codeword_cosine(m, snr_db, keys, seed): + """Mean cosine between the reconstructed and the true embedding.""" + torch.manual_seed(seed + int(10 * snr_db)) + tot, done = 0.0, 0 + while done < FRAMES: + n = min(CHUNK, FRAMES - done) + dig = torch.randint(m.vu, (n, m.users, m.P), device=DEVICE) + dec = decide(m, dig, snr_db, keys) + c = F.cosine_similarity(frame_embedding(m, dec), + frame_embedding(m, dig[:, 0]), dim=1) + tot += float(c.sum()) + done += n + return tot / done + + +@torch.no_grad() +def codeword_chance(m, seed=99): + """Cosine between two independently drawn indices.""" + torch.manual_seed(seed) + a = torch.randint(m.vu, (FRAMES, m.P), device=DEVICE) + b = torch.randint(m.vu, (FRAMES, m.P), device=DEVICE) + return float(F.cosine_similarity(frame_embedding(m, a), + frame_embedding(m, b), dim=1).mean()) + + +def load_bert_embeddings(): + """BERT input embedding matrix, the space AG News tokens live in.""" + from transformers import AutoModel + mdl = AutoModel.from_pretrained("bert-base-uncased") + return mdl.get_input_embeddings().weight.detach().to(DEVICE) + + +@torch.no_grad() +def real_semantic(m, emb, ids_all, snr_db, keys, seed): + """Mean BERT cosine between the decoded and the transmitted token of + user 0. ids_all is (N,U): every user carries its OWN stream, so an + insider decoding user 0 gains nothing from its own traffic.""" + torch.manual_seed(seed + int(10 * snr_db)) + n = ids_all.shape[0] + dig = torch.stack([(ids_all // (m.vu ** p)) % m.vu + for p in range(m.P)], -1).to(DEVICE) # (N,U,P) + tot, done = 0.0, 0 + while done < n: + k = min(CHUNK, n - done) + dec = decide(m, dig[done:done + k], snr_db, keys) + rec = sum(dec[:, p] * (m.vu ** p) for p in range(m.P)) + true = ids_all[done:done + k, 0].to(DEVICE) + rec = rec.clamp(max=emb.shape[0] - 1) + tot += float(F.cosine_similarity(emb[rec], emb[true], dim=1).sum()) + done += k + return tot / n + + +def main(): + m = main_model() + m.eval() + ew = eve_wrong_mask(m.users, m.L, seed=20260813) + insider = m.masks()[1:2].detach() # user 2 attacking user 1 + rows = [] + + chance = codeword_chance(m) + print("codeword chance cosine %.4f" % chance, flush=True) + for snr in SNRS: + lg = codeword_cosine(m, snr, m.masks(), 5150) + ev = codeword_cosine(m, snr, ew.to(DEVICE), 5151) + ins = codeword_cosine(m, snr, insider, 5152) + rows.append(("codeword", snr, "%.4f" % lg, "%.4f" % ev, + "%.4f" % ins, "%.4f" % chance)) + print("codeword %4.0f dB legit %.4f outsider %.4f insider %.4f" + % (snr, lg, ev, ins), flush=True) + + # real token streams in the BERT embedding space + try: + from exp_real_sec import load_streams + streams, _bounds, _vocab = load_streams() + nmin = min(len(x) for x in streams) + ids = torch.stack([torch.as_tensor(x[:nmin], dtype=torch.long) + for x in streams], dim=1)[:100_000] # (N,U) + emb = load_bert_embeddings() + rnd = torch.randint(0, emb.shape[0], (ids.shape[0],)) + ch = float(F.cosine_similarity(emb[ids[:, 0].to(DEVICE)], + emb[rnd.to(DEVICE)], dim=1).mean()) + print("BERT chance cosine %.4f" % ch, flush=True) + for snr in REAL_SNRS: + lg = real_semantic(m, emb, ids, snr, m.masks(), 5160) + ev = real_semantic(m, emb, ids, snr, ew.to(DEVICE), 5161) + ins = real_semantic(m, emb, ids, snr, insider, 5162) + rows.append(("bert", snr, "%.4f" % lg, "%.4f" % ev, + "%.4f" % ins, "%.4f" % ch)) + print("bert %4.0f dB legit %.4f outsider %.4f insider %.4f" + % (snr, lg, ev, ins), flush=True) + except Exception as exc: # keep the codeword rows + print("[skip] real-token semantic stage: %s: %s" + % (type(exc).__name__, exc), flush=True) + + out = DATA / "semantic.csv" + with open(out, "w", newline="") as f: + w = csv.writer(f) + w.writerow(["space", "snr_db", "legit", "outsider", "insider", + "chance"]) + w.writerows(rows) + print("[csv]", out) + + +if __name__ == "__main__": + main() diff --git a/code/exp_users_csi.py b/code/exp_users_csi.py new file mode 100644 index 0000000..4d4a4dc --- /dev/null +++ b/code/exp_users_csi.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +"""Two robustness sweeps the evaluation was missing. + +Users. Every other stage fixes U=4. The structured family admits U up to +L-1, and as U approaches L the frame fills with cross-user patterns, so +this sweep asks what the load costs the legitimate users and whether the +confidentiality survives it. + +Channel estimation. Every other stage equalizes with the exact gain. +Here the receiver divides by an estimate h+e with e zero mean and +variance sigma_e^2 relative to the gain, so the residual phase and +amplitude error enters the correlation the same way a key mismatch +would, and the question is how much of the legitimate margin it costs. + +Run under WSL. Writes data/users.csv and data/csi.csv. +""" +from __future__ import annotations + +import csv +import math +import sys +from pathlib import Path + +import torch + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import sse_lib as L +from sse_lib import DATA, DEVICE, rayleigh_gain, snr_to_sigma2, eval_ser_sse +from exp_full import (main_model, base_keys, get_model, eve_wrong_mask, + eval_ser_eve, mean_abs_xcorr, MAIN_D) + +SNR = 10.0 +FRAMES = 400_000 +CHUNK = 40_000 +USERS = [2, 4, 8, 16, 32, 48] +CSI = [0.0, 1e-3, 1e-2, 3e-2, 1e-1] +PHASE = [0.0, 0.02, 0.05, 0.10, 0.20] # residual phase error, radians rms + + +@torch.no_grad() +def ser_with_csi_error(m, snr_db, nmse, frames=FRAMES, seed=606): + """Legitimate SER when the receiver equalizes with a noisy estimate.""" + torch.manual_seed(seed + int(1e4 * nmse)) + wrong = tot = 0 + while tot < frames: + n = min(CHUNK, frames - tot) + dig = torch.randint(m.vu, (n, m.users, m.P), device=DEVICE) + Bn = m.unit_codebook() + e = Bn[dig] / math.sqrt(m.P) + y = (e * m.masks()[None, :, None, :]).sum(dim=1) / m.c + h = rayleigh_gain((n, m.users), device=DEVICE) + sig = snr_to_sigma2(torch.full((n,), snr_db), m.d).to(DEVICE).sqrt() + rx = h[:, :, None, None] * y[:, None] \ + + sig[:, None, None, None] * torch.randn(n, m.users, m.P, m.L, + device=DEVICE) + # estimate with a zero-mean error of the stated relative variance + hhat = h + math.sqrt(nmse) * h.abs() * torch.randn_like(h) + r = rx / hhat[:, :, None, None].clamp_min(1e-6) + cand = Bn[None, :, :] * m.masks()[:, None, :] + dec = torch.einsum("nupl,uvl->nupv", r, cand).argmax(-1) + wrong += int((dec != dig).any(dim=-1).sum()) + tot += n * m.users + return wrong / tot + + +@torch.no_grad() +def ser_with_phase_error(m, snr_db, rms, frames=FRAMES, seed=707): + """Legitimate SER under a residual phase error. Entries 2n-1 and 2n + are the I and Q of one complex channel use, so an uncompensated + phase rotates that pair. Unlike an amplitude error, this is not a + common scale and the argmax is not invariant to it.""" + torch.manual_seed(seed + int(1e3 * rms)) + wrong = tot = 0 + half = m.L // 2 + while tot < frames: + n = min(CHUNK, frames - tot) + dig = torch.randint(m.vu, (n, m.users, m.P), device=DEVICE) + Bn = m.unit_codebook() + e = Bn[dig] / math.sqrt(m.P) + y = (e * m.masks()[None, :, None, :]).sum(dim=1) / m.c + h = rayleigh_gain((n, m.users), device=DEVICE) + sig = snr_to_sigma2(torch.full((n,), snr_db), m.d).to(DEVICE).sqrt() + noise = torch.randn(n, m.users, m.P, m.L, device=DEVICE) + rx = h[:, :, None, None] * y[:, None] + sig[:, None, None, None] * noise + r = rx / h[:, :, None, None].clamp_min(1e-6) + if rms > 0: # rotate each I/Q pair + th = rms * torch.randn(n, m.users, 1, half, device=DEVICE) + v = r.reshape(n, m.users, m.P, half, 2) + i, q = v[..., 0], v[..., 1] + c_, s_ = th.cos(), th.sin() # broadcast over periods + r = torch.stack([i * c_ - q * s_, i * s_ + q * c_], + dim=-1).reshape(n, m.users, m.P, m.L) + cand = Bn[None, :, :] * m.masks()[:, None, :] + dec = torch.einsum("nupl,uvl->nupv", r, cand).argmax(-1) + wrong += int((dec != dig).any(dim=-1).sum()) + tot += n * m.users + return wrong / tot + + +def main(): + # --- users ------------------------------------------------------- + rows = [] + print("user load at %g dB, L=%d" % (SNR, MAIN_D // 4), flush=True) + for U in USERS: + Lp = MAIN_D // 4 + if U > Lp - 1: + print(" U=%d exceeds L-1, skipped" % U, flush=True) + continue + m = get_model(d=MAIN_D, U=U, iters=4000, freeze_W=base_keys(U, Lp)) + m.eval() + lg = eval_ser_sse(m, [SNR], frames=FRAMES)[0] + ew = eve_wrong_mask(U, Lp, seed=20260813) + ev = eval_ser_eve(m, ew, [SNR], frames=FRAMES)[0] + xc = mean_abs_xcorr(m.masks().detach()) + rows.append((U, "%.6f" % lg, "%.6f" % ev, "%.6f" % xc)) + print(" U=%2d legit %.4f eve %.5f xcorr %.2e" + % (U, lg, ev, xc), flush=True) + with open(DATA / "users.csv", "w", newline="") as f: + w = csv.writer(f) + w.writerow(["users", "legit_ser", "eve_ser", "mask_xcorr"]) + w.writerows(rows) + print("[csv]", DATA / "users.csv", flush=True) + + # --- channel estimation error ------------------------------------ + m = main_model() + m.eval() + rows = [] + print("channel estimation error at %g dB" % SNR, flush=True) + for nmse in CSI: + s = ser_with_csi_error(m, SNR, nmse) + rows.append(("%g" % nmse, "%.6f" % s)) + print(" nmse %-6g legit %.4f" % (nmse, s), flush=True) + print("residual phase error at %g dB" % SNR, flush=True) + prows = [] + for rms in PHASE: + s_ = ser_with_phase_error(m, SNR, rms) + prows.append(("%g" % rms, "%.6f" % s_)) + print(" phase rms %-5g legit %.4f" % (rms, s_), flush=True) + with open(DATA / "csi.csv", "w", newline="") as f: + w = csv.writer(f) + w.writerow(["impairment", "level", "legit_ser"]) + w.writerows([("amplitude_nmse",) + r for r in rows] + + [("phase_rms_rad",) + r for r in prows]) + print("[csv]", DATA / "csi.csv") + + +if __name__ == "__main__": + main() diff --git a/code/replot_security.py b/code/replot_security.py index 76410f6..b955e57 100644 --- a/code/replot_security.py +++ b/code/replot_security.py @@ -34,16 +34,16 @@ FIG.mkdir(exist_ok=True) plt.rcParams.update({ "font.family": "serif", "font.serif": ["DejaVu Serif", "Times New Roman"], - # The manuscript includes each result figure at 0.85 of a 3.455 in - # column while the canvas is 3.15 in, a printed scale of 0.933. Every + # The manuscript includes each result figure at 0.70 of a 3.455 in + # column while the canvas is 3.15 in, a printed scale of 0.768. Every # size below is therefore pre-divided by that scale so the PRINTED - # sizes are 9 pt labels, 8 pt ticks and a 6.6 pt legend. Change the + # sizes are 8 pt labels, 7 pt ticks and a 5.8 pt legend. Change the # include width and these must change with it. - "font.size": 9.7, - "axes.labelsize": 9.7, - "legend.fontsize": 7.1, - "xtick.labelsize": 8.6, - "ytick.labelsize": 8.6, + "font.size": 10.4, + "axes.labelsize": 10.4, + "legend.fontsize": 7.6, + "xtick.labelsize": 9.2, + "ytick.labelsize": 9.2, "axes.grid": True, "grid.linestyle": "--", "grid.linewidth": 0.4, @@ -53,7 +53,7 @@ plt.rcParams.update({ "figure.figsize": (3.15, 2.25), # shorter canvas: same printed width and font size, less page height "pdf.fonttype": 42, }) -AXES_RECT = dict(left=0.205, right=0.970, top=0.955, bottom=0.215) +AXES_RECT = dict(left=0.215, right=0.970, top=0.955, bottom=0.225) C_LEGIT = "#c0392b" C_EVE = "#2c5fa8" @@ -204,7 +204,7 @@ def main_legit(snr_db="10"): def place_legend(ax, cands=("lower left", "upper left", "center left", "center right", "lower center", "upper right", "upper center", "center", "lower right"), - sizes=(7.1, 6.8, 6.6), ncol=1): + sizes=(7.6, 7.2, 6.8, 6.4, 6.0), ncol=1): """Choose the location and font size whose box the fewest curve points fall inside, scored on rendered geometry rather than guessed from the data. The size sweep is what makes a long label set placeable: a diff --git a/data/csi.csv b/data/csi.csv new file mode 100644 index 0000000..8eb442b --- /dev/null +++ b/data/csi.csv @@ -0,0 +1,11 @@ +impairment,level,legit_ser +amplitude_nmse,0,0.053300 +amplitude_nmse,0.001,0.052325 +amplitude_nmse,0.01,0.053213 +amplitude_nmse,0.03,0.052790 +amplitude_nmse,0.1,0.052969 +phase_rms_rad,0,0.053000 +phase_rms_rad,0.02,0.052800 +phase_rms_rad,0.05,0.052500 +phase_rms_rad,0.1,0.053800 +phase_rms_rad,0.2,0.056600 diff --git a/data/infotheory.csv b/data/infotheory.csv new file mode 100644 index 0000000..165e725 --- /dev/null +++ b/data/infotheory.csv @@ -0,0 +1,6 @@ +snr_db,mi_legit_bits,mi_eve_fixed_bits,equiv_ratio_fixed,tv_fixed,secrecy_rate_fixed_bits,mi_eve_refresh_bits,equiv_ratio_refresh,tv_refresh,secrecy_rate_refresh_bits +0.0,9.9561,0.653799,0.959136,0.186184,9.3023,0.022776,0.998576,0.036130,9.9334 +5.0,13.3042,1.036897,0.935192,0.234795,12.2673,0.039079,0.997557,0.047628,13.2651 +10.0,14.9300,1.344006,0.915999,0.266709,13.5860,0.055213,0.996548,0.056616,14.8748 +15.0,15.6143,1.531415,0.904285,0.284074,14.0829,0.066127,0.995866,0.061766,15.5482 +20.0,15.8604,1.631648,0.898021,0.292605,14.2287,0.070091,0.995618,0.063096,15.7903 diff --git a/data/semantic.csv b/data/semantic.csv new file mode 100644 index 0000000..084d549 --- /dev/null +++ b/data/semantic.csv @@ -0,0 +1,8 @@ +space,snr_db,legit,outsider,insider,chance +codeword,0.0,0.7866,-0.0164,0.0008,-0.0000 +codeword,10.0,0.9730,-0.0238,0.0000,-0.0000 +codeword,20.0,0.9972,-0.0262,-0.0001,-0.0000 +bert,0.0,0.7051,0.2576,0.2482,0.2579 +bert,10.0,0.9619,0.2498,0.2451,0.2579 +bert,20.0,0.9962,0.2471,0.2447,0.2579 +bert,28.0,0.9991,0.2466,0.2447,0.2579 diff --git a/data/users.csv b/data/users.csv new file mode 100644 index 0000000..24bd2e5 --- /dev/null +++ b/data/users.csv @@ -0,0 +1,7 @@ +users,legit_ser,eve_ser,mask_xcorr +2,0.026755,0.999999,0.000000 +4,0.053062,0.999707,0.000000 +8,0.106523,0.999414,0.000000 +16,0.256325,0.999948,0.000000 +32,0.946055,0.999983,0.000000 +48,0.997737,0.999983,0.000000 diff --git a/fig/fig_sec_brute.pdf b/fig/fig_sec_brute.pdf index cc9785d..764ef86 100644 Binary files a/fig/fig_sec_brute.pdf and b/fig/fig_sec_brute.pdf differ diff --git a/fig/fig_sec_jam.pdf b/fig/fig_sec_jam.pdf index 4a64983..1eab075 100644 Binary files a/fig/fig_sec_jam.pdf and b/fig/fig_sec_jam.pdf differ diff --git a/fig/fig_sec_keylen.pdf b/fig/fig_sec_keylen.pdf index e2c9a06..b7941c4 100644 Binary files a/fig/fig_sec_keylen.pdf and b/fig/fig_sec_keylen.pdf differ diff --git a/fig/fig_sec_kpa.pdf b/fig/fig_sec_kpa.pdf index da0520b..1e2552f 100644 Binary files a/fig/fig_sec_kpa.pdf and b/fig/fig_sec_kpa.pdf differ diff --git a/fig/fig_sec_real.pdf b/fig/fig_sec_real.pdf index 2070a6e..e0a0628 100644 Binary files a/fig/fig_sec_real.pdf and b/fig/fig_sec_real.pdf differ diff --git a/fig/fig_sec_sens.pdf b/fig/fig_sec_sens.pdf index 6a2613d..72b7823 100644 Binary files a/fig/fig_sec_sens.pdf and b/fig/fig_sec_sens.pdf differ diff --git a/fig/fig_sec_snr.pdf b/fig/fig_sec_snr.pdf index 05bf6c0..a2401ae 100644 Binary files a/fig/fig_sec_snr.pdf and b/fig/fig_sec_snr.pdf differ