Four-scheme jamming comparison and a fully generated refresh table

Give the orthogonal-access jammer its own Rayleigh channel in
oma_ser_jammed, matching the convention every simulated scheme already
used. Without it the closed-form curve faced a jammer at full power in
every frame while the Monte Carlo curves faced a fading one, which
inverted the ordering of the comparison.

Measure the outsider error rate for the fixed-key and naive-refresh
cases as well, and emit the two refresh tables from make_tables.py, so
no cell of the paper is hand-typed.
This commit is contained in:
KiHoLee
2026-08-13 22:22:18 +09:00
parent 057c555374
commit c31e6a3fe0
17 changed files with 192 additions and 61 deletions
+12 -9
View File
@@ -24,14 +24,14 @@ hold the key, at no extra bandwidth, power, or rate.
```
code/
sse_lib.py transmit and receive core, channel, training, OMA reference
exp_full.py stages A-F: SNR sweep, key length, jamming, key families,
scheme comparison, attack difficulty
exp_full.py stages A-F and L: SNR sweep, key length, jamming across
schemes, key families, scheme comparison, attack difficulty
exp_kpa.py stage H: known-plaintext attack on the key
exp_refresh.py stage K: the key-refresh layer, invariance group
exp_real_sec.py stage G: real BERT WordPiece token streams
verify_math.py closed-form checks V1-V5 against Monte Carlo, PASS/FAIL
replot_security.py every result figure, from data/ to fig/
make_tables.py LaTeX rows of the two result tables, from data/
make_tables.py LaTeX rows of every result table, from data/
feasibility_security.py early CPU-sized study, kept for the record
data/ CSV results, one file per stage
fig/ figure PDFs, regenerated by replot_security.py
@@ -47,15 +47,18 @@ libraries.
```bash
python verify_math.py # closed-form verification, prints PASS/FAIL
python exp_full.py # stages A-F
python exp_full.py # stages A-F and L
python exp_kpa.py # known-plaintext attack
python exp_refresh.py # the key-refresh layer
python exp_real_sec.py # real token streams
python replot_security.py # all figures from the CSVs
python make_tables.py # LaTeX rows of the result tables
```
Seeds are fixed: training 1, evaluation 777, attacker key guess
20260813. Re-running reproduces the released CSV files.
20260813, key recovery 4242, brute-force search 31, cross-scheme
comparison 11, key refresh 5150. Re-running reproduces the released CSV
files.
## Figure and table map
@@ -63,15 +66,15 @@ Seeds are fixed: training 1, evaluation 777, attacker key guess
|---|---|---|
| Fig. 2 SER against SNR | `exp_full.stage_A` | `sec_snr.csv` |
| Fig. 3 key length | `exp_full.stage_B` | `sec_keylen.csv` |
| Fig. 4 jamming | `exp_full.stage_C` | `sec_jam.csv` |
| Fig. 5 key sensitivity | `exp_full.stage_F` | `sec_sens.csv` |
| Fig. 6 brute-force search | `exp_full.stage_F` | `sec_brute.csv` |
| Fig. 4 jamming | `exp_full.stage_L` | `sec_jam_cmp.csv`, `sec_jam.csv` |
| Fig. 5 key sensitivity | `exp_full.stage_I` | `sec_sens_cmp.csv` |
| Fig. 6 brute-force search | `exp_full.stage_J` | `sec_brute_cmp.csv`, `sec_brute.csv` |
| Fig. 7 known-plaintext attack | `exp_kpa` | `kpa.csv` |
| Fig. 8 real token streams | `exp_real_sec` | `real_sec_ter.csv` |
| Scheme comparison table | `exp_full.stage_E` | `sec_compare.csv` |
| Key family table | `exp_full.stage_D` | `sec_maskfam.csv`, `sec_regjam.csv` |
| Headline recovery table | `exp_real_sec` | `real_sec_stats.json` |
| Key refresh tables | `exp_refresh` | `refresh.csv`, `refresh_kpa.csv` |
| Key refresh tables | `exp_refresh` | `refresh_summary.csv`, `refresh_kpa.csv` |
## Security scope
+63
View File
@@ -645,6 +645,68 @@ def csv_rows(path):
yield from _csv.DictReader(f)
def oma_ser_jammed(snr_db, jsr_db_list, bits=16, U=4, n_grid=4096):
"""OMA under a jammer that concentrates on the victim's slots.
An OMA user occupies d/U exclusive real dimensions that are public,
so a jammer needs no key to put all of its power there. With unit
energy per real dimension and a total jammer energy of rho times the
frame energy, concentrating on d/U of the d dimensions gives a
per-dimension jammer variance of U*rho.
The jammer reaches the victim through its own Rayleigh channel, the
same convention eval_scheme uses for every simulated scheme, so the
victim sees an effective noise variance of 1/snr + U*rho*hJ**2 with
E[hJ**2]=1. Averaging over the independent signal and jammer gains
uses a product of exponential quantile grids.
"""
q = (torch.arange(n_grid, dtype=torch.float64) + 0.5) / n_grid
h2 = -torch.log1p(-q) # |h|^2 ~ Exp(1)
hj2 = h2.clone() # |hJ|^2 ~ Exp(1), independent
h = h2.sqrt()[:, None] # (n,1) signal amplitude
snr = 10.0 ** (snr_db / 10.0)
out = []
for jsr_db in jsr_db_list:
rho = 10.0 ** (jsr_db / 10.0)
var = (1.0 / snr + U * rho * hj2)[None, :] # (1,n)
arg = (h / var.sqrt()).clamp(0, 38)
pe = 0.5 * torch.erfc(arg / math.sqrt(2.0)) # per-bit error
out.append(float((1.0 - (1.0 - pe) ** bits).mean()))
return out
def stage_L():
"""Jamming comparison across schemes at 10 dB.
proposed blind : the strongest jammer the proposed scheme admits
while the key stays secret
public matched : the jammer a public-mask scheme always faces
permutation blind: the shuffling-style scheme, whose secret
permutation also denies the jammer a target
OMA targeted : the jammer an orthogonal scheme faces, since its
slot assignment is public and needs no key
"""
print("[L] jamming across schemes ...")
m = get_model(iters=4000)
F = 300_000
d = m.P * m.L
gp = torch.Generator().manual_seed(11)
perms = torch.randperm(d, generator=gp)[None].repeat(m.users, 1)
jsr = [-10.0, -5.0, 0.0, 5.0, 10.0, 15.0, 20.0]
oma = oma_ser_jammed(10.0, jsr, bits=int(math.log2(m.V)), U=m.users)
rows = []
for i, j in enumerate(jsr):
blind = eval_scheme(m, 10.0, F, jam_w="blind", jsr_db=j)
matched = eval_scheme(m, 10.0, F, jam_w="matched", jsr_db=j)
perm = eval_scheme(m, 10.0, F, perms=perms, jam_w="blind", jsr_db=j)
rows.append((j, blind, matched, perm, oma[i]))
print(f" JSR={j:6.1f} blind={blind:.4f} matched={matched:.4f} "
f"perm={perm:.4f} oma={oma[i]:.4f}")
write_csv(DATA / "sec_jam_cmp.csv",
["jsr_db", "blind", "matched", "perm_blind", "oma_targeted"],
rows)
def main():
print(f"device={DEVICE}")
stage_A()
@@ -655,6 +717,7 @@ def main():
stage_F()
stage_I()
stage_J()
stage_L()
print("[done] full-scale security CSVs in", DATA)
+23 -4
View File
@@ -109,6 +109,11 @@ def main():
B0 = m.B.detach().clone().cpu()
ew = eve_wrong_mask(U, Lp, seed=20260813)
# the no-refresh reference: the trained keys, held for every block
install(m, K0, B0)
lg_fixed = eval_ser_sse(m, [10.0], frames=FRAMES)[0]
ev_fixed = eval_ser_eve(m, ew, [10.0], frames=FRAMES)[0]
rows = []
for t in range(BLOCKS):
signs, colperm, userperm = kdf_invariant(SEED, t, U, Lp)
@@ -117,18 +122,32 @@ def main():
ev = eval_ser_eve(m, ew, [10.0], frames=FRAMES)[0]
install(m, kdf_naive(SEED, t, U, Lp), B0)
lg_naive = eval_ser_sse(m, [10.0], frames=FRAMES)[0]
rows.append((t, lg, lg_naive, ev))
ev_naive = eval_ser_eve(m, ew, [10.0], frames=FRAMES)[0]
rows.append((t, lg, lg_naive, ev, ev_naive))
if t < 3 or t == BLOCKS - 1:
print(f" block {t:3d} invariant={lg:.4f} naive={lg_naive:.4f} "
f"eve={ev:.4f}")
write_csv(DATA / "refresh.csv",
["block", "legit_invariant", "legit_naive", "eve_ser"], rows)
["block", "legit_invariant", "legit_naive", "eve_invariant",
"eve_naive"], rows)
inv = [r[1] for r in rows]; nai = [r[2] for r in rows]
ev = [r[3] for r in rows]
ev = [r[3] for r in rows]; evn = [r[4] for r in rows]
print(f" invariant refresh: mean={np.mean(inv):.4f} "
f"min={min(inv):.4f} max={max(inv):.4f}")
print(f" naive refresh : mean={np.mean(nai):.4f}")
print(f" eavesdropper : mean={np.mean(ev):.5f}")
print(f" eavesdropper : mean={np.mean(ev):.5f} "
f"min={min(ev):.5f} max={max(ev):.5f}")
# the three rows of the refresh table, so no cell is hand-typed. Both
# fixed and naive draw U of the L-1 non-constant Hadamard rows.
fam = math.lgamma(Lp) / math.log(2.0) - math.lgamma(Lp - U) / math.log(2.0)
write_csv(DATA / "refresh_summary.csv",
["scheme", "legit", "eve", "entropy_bits"],
[("None (fixed key)", lg_fixed, ev_fixed, fam),
("Fresh orthogonal keys", float(np.mean(nai)),
float(np.mean(evn)), fam),
("Invariant", float(np.mean(inv)), float(np.mean(ev)),
entropy_bits(U, Lp))])
print("[K] known plaintext across a refresh ...")
kpa_rows = []
+25 -2
View File
@@ -23,7 +23,7 @@ NAME = {
}
RECEIVER = {
"legit": "Legitimate", "oma": "OMA",
"insider": "Insider", "eve": "Outsider eavesdropper",
"insider": "Insider", "eve": "Outsider",
}
@@ -73,7 +73,30 @@ def real_table():
print(f"{RECEIVER[key]} & {cells}" + r" \\")
def refresh_tables():
print("% Table: key refresh (from refresh_summary.csv)")
for r in csv.DictReader(open(DATA / "refresh_summary.csv")):
b = r["scheme"] == "Invariant"
name = r"\textbf{Invariant}" if b else r["scheme"]
f = (lambda t: r"\mathbf{" + t + "}") if b else (lambda t: t)
print(f"{name} & ${f(format(float(r['legit']), '.3f'))}$ & "
f"${f(format(float(r['eve']), '.4f'))}$ & "
f"${f(format(float(r['entropy_bits']), '.1f'))}$~bits" + r" \\")
print()
print("% Table: known plaintext across a refresh (from refresh_kpa.csv)")
rows = {r["n_frames"]: r for r in csv.DictReader(open(DATA / "refresh_kpa.csv"))}
keep = ["2", "8", "64"]
print("Frames used by the attacker & "
+ " & ".join(f"${k}$" for k in keep) + r" \\")
for lbl, key in (("Same block", "ser_same_block"),
("Next block", "ser_next_block")):
print(f"{lbl} & "
+ " & ".join(f"${float(rows[k][key]):.3f}$" for k in keep)
+ r" \\")
if __name__ == "__main__":
compare_table(); print()
maskfam_table(); print()
real_table()
real_table(); print()
refresh_tables()
+26 -15
View File
@@ -3,12 +3,16 @@ 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_snr.pdf : legitimate and outsider 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)
fig_sec_brute_rho.pdf : best key correlation vs guesses (Fig. 7)
fig_sec_jam.pdf : target-user SER vs JSR, four schemes (Fig. 4)
fig_sec_sens.pdf : outsider SER vs fraction of key held (Fig. 5)
fig_sec_brute.pdf : outsider SER vs number of key guesses (Fig. 6)
fig_sec_kpa.pdf : outsider SER vs known-plaintext frames (Fig. 7)
fig_sec_real.pdf : token error rate on real streams (Fig. 8)
fig_sec_brute_rho.pdf is also emitted as a diagnostic and is not used in
the paper.
"""
from __future__ import annotations
from pathlib import Path
@@ -138,19 +142,26 @@ def fig_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")
"""Target-user SER against JSR for four schemes. A linear axis is
used because the range spans less than one decade, where a log axis
would print wide minor tick labels that crowd out the y label."""
r = load("sec_jam_cmp.csv")
x = col(r, "jsr_db")
fig, ax = plt.subplots()
ax.plot(x, col(r, "oma_targeted"), color=C_PUB, marker="^", ls=":",
label="OMA, targeted")
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"])
label="Public masks, matched")
# the two blind curves agree to 0.0015, so the proposed one is drawn
# first and wide and the permutation key rides on top with open
# markers, otherwise one legend entry would have no visible curve
ax.plot(x, col(r, "blind"), color=C_LEGIT, marker="o", ls="-", lw=2.6,
ms=7, alpha=0.85, label="Proposed, blind")
ax.plot(x, col(r, "perm_blind"), color=C_EVE, marker="s", ls="-.",
lw=1.2, ms=4.5, mfc="none", label="Permutation key, blind")
nojam = float(load("sec_jam.csv")[0]["nojam"])
ax.axhline(nojam, color=C_OMA, ls=(0, (1, 3)), lw=0.9,
label=LBL["nojam"])
ax.set_xlabel("JSR (dB)")
ax.set_ylabel("SER")
ax.set_xlim(min(x), max(x))
+25 -25
View File
@@ -1,25 +1,25 @@
block,legit_invariant,legit_naive,eve_ser
0,0.2575,0.982775,0.9998575
1,0.256835,0.7427116667,0.9998133333
2,0.2566516667,0.8483041667,0.9999883333
3,0.2572983333,0.778605,0.9996666667
4,0.2569683333,0.6199566667,0.9979908333
5,0.2571308333,0.6580941667,0.9997825
6,0.2572883333,0.7788558333,0.9997916667
7,0.2577975,0.6935075,0.9999575
8,0.2573458333,0.8400325,0.99839
9,0.25766,0.6547933333,0.9999866667
10,0.2565575,0.4783583333,0.9995791667
11,0.2578183333,0.65481,0.99993
12,0.2573583333,0.8477883333,0.9997283333
13,0.2571408333,0.6548475,0.9999741667
14,0.2571491667,0.6579591667,0.9999975
15,0.2574591667,0.4778091667,0.9994458333
16,0.2568716667,0.76187,0.9999083333
17,0.2572141667,0.8045925,0.9994866667
18,0.2573291667,0.8065691667,0.9997483333
19,0.2571316667,0.6892025,0.9992841667
20,0.2576716667,0.5278108333,0.9980375
21,0.25724,0.742345,0.9999641667
22,0.2570333333,0.6545633333,0.9997633333
23,0.25745,0.68938,0.99989
block,legit_invariant,legit_naive,eve_invariant,eve_naive
0,0.25722,0.9825916667,0.9998433333,0.999975
1,0.257695,0.7424366667,0.9997983333,0.998495
2,0.2575741667,0.8482983333,0.9999891667,0.9999966667
3,0.2574108333,0.7789558333,0.9997116667,0.9998625
4,0.2578316667,0.619975,0.9978516667,0.9999391667
5,0.2575941667,0.6579858333,0.99976,0.9997716667
6,0.2568508333,0.7788175,0.9997783333,0.9999341667
7,0.2574466667,0.6938383333,0.9999633333,0.9989225
8,0.2568958333,0.8399391667,0.9983208333,0.99991
9,0.2572158333,0.6549775,0.9999891667,0.9999816667
10,0.2575733333,0.4789683333,0.9995841667,0.9995091667
11,0.2569025,0.6551183333,0.999925,0.9999741667
12,0.2582966667,0.8479616667,0.9997208333,0.9999233333
13,0.2579675,0.6551841667,0.9999758333,0.9996183333
14,0.2572158333,0.6582558333,1,0.999915
15,0.2578983333,0.4791275,0.9994441667,0.9999233333
16,0.2570508333,0.7614066667,0.9998991667,0.9993675
17,0.2576233333,0.8045108333,0.9994666667,0.9999475
18,0.2571108333,0.8073525,0.9997158333,0.999985
19,0.257955,0.6890983333,0.9992433333,0.9994441667
20,0.25777,0.5282241667,0.9981,0.9992808333
21,0.2568141667,0.7426725,0.9999566667,0.999965
22,0.2578066667,0.6547841667,0.9997633333,0.9990433333
23,0.2575241667,0.68849,0.9998791667,0.99982
1 block legit_invariant legit_naive eve_ser eve_invariant eve_naive
2 0 0.2575 0.25722 0.982775 0.9825916667 0.9998575 0.9998433333 0.999975
3 1 0.256835 0.257695 0.7427116667 0.7424366667 0.9998133333 0.9997983333 0.998495
4 2 0.2566516667 0.2575741667 0.8483041667 0.8482983333 0.9999883333 0.9999891667 0.9999966667
5 3 0.2572983333 0.2574108333 0.778605 0.7789558333 0.9996666667 0.9997116667 0.9998625
6 4 0.2569683333 0.2578316667 0.6199566667 0.619975 0.9979908333 0.9978516667 0.9999391667
7 5 0.2571308333 0.2575941667 0.6580941667 0.6579858333 0.9997825 0.99976 0.9997716667
8 6 0.2572883333 0.2568508333 0.7788558333 0.7788175 0.9997916667 0.9997783333 0.9999341667
9 7 0.2577975 0.2574466667 0.6935075 0.6938383333 0.9999575 0.9999633333 0.9989225
10 8 0.2573458333 0.2568958333 0.8400325 0.8399391667 0.99839 0.9983208333 0.99991
11 9 0.25766 0.2572158333 0.6547933333 0.6549775 0.9999866667 0.9999891667 0.9999816667
12 10 0.2565575 0.2575733333 0.4783583333 0.4789683333 0.9995791667 0.9995841667 0.9995091667
13 11 0.2578183333 0.2569025 0.65481 0.6551183333 0.99993 0.999925 0.9999741667
14 12 0.2573583333 0.2582966667 0.8477883333 0.8479616667 0.9997283333 0.9997208333 0.9999233333
15 13 0.2571408333 0.2579675 0.6548475 0.6551841667 0.9999741667 0.9999758333 0.9996183333
16 14 0.2571491667 0.2572158333 0.6579591667 0.6582558333 0.9999975 1 0.999915
17 15 0.2574591667 0.2578983333 0.4778091667 0.4791275 0.9994458333 0.9994441667 0.9999233333
18 16 0.2568716667 0.2570508333 0.76187 0.7614066667 0.9999083333 0.9998991667 0.9993675
19 17 0.2572141667 0.2576233333 0.8045925 0.8045108333 0.9994866667 0.9994666667 0.9999475
20 18 0.2573291667 0.2571108333 0.8065691667 0.8073525 0.9997483333 0.9997158333 0.999985
21 19 0.2571316667 0.257955 0.6892025 0.6890983333 0.9992841667 0.9992433333 0.9994441667
22 20 0.2576716667 0.25777 0.5278108333 0.5282241667 0.9980375 0.9981 0.9992808333
23 21 0.25724 0.2568141667 0.742345 0.7426725 0.9999641667 0.9999566667 0.999965
24 22 0.2570333333 0.2578066667 0.6545633333 0.6547841667 0.9997633333 0.9997633333 0.9990433333
25 23 0.25745 0.2575241667 0.68938 0.68849 0.99989 0.9998791667 0.99982
+6 -6
View File
@@ -1,7 +1,7 @@
n_frames,ser_same_block,ser_next_block
2,0.2777190625,0.998930625
4,0.2615640625,0.998644375
8,0.2594196875,0.998631875
16,0.2582471875,0.99867
32,0.2579625,0.99871875
64,0.257455,0.9987190625
2,0.27116875,0.9988428125
4,0.260861875,0.998726875
8,0.2588515625,0.99869375
16,0.2586403125,0.998735625
32,0.2571871875,0.9986965625
64,0.25772125,0.99869375
1 n_frames ser_same_block ser_next_block
2 2 0.2777190625 0.27116875 0.998930625 0.9988428125
3 4 0.2615640625 0.260861875 0.998644375 0.998726875
4 8 0.2594196875 0.2588515625 0.998631875 0.99869375
5 16 0.2582471875 0.2586403125 0.99867 0.998735625
6 32 0.2579625 0.2571871875 0.99871875 0.9986965625
7 64 0.257455 0.25772125 0.9987190625 0.99869375
+4
View File
@@ -0,0 +1,4 @@
scheme,legit,eve,entropy_bits
None (fixed key),0.2573025,0.9999908333,14.99964774
Fresh orthogonal keys,0.7103737847,0.9996877083,14.99964774
Invariant,0.2574685069,0.99957,64.83510297
1 scheme legit eve entropy_bits
2 None (fixed key) 0.2573025 0.9999908333 14.99964774
3 Fresh orthogonal keys 0.7103737847 0.9996877083 14.99964774
4 Invariant 0.2574685069 0.99957 64.83510297
+8
View File
@@ -0,0 +1,8 @@
jsr_db,blind,matched,perm_blind,oma_targeted
-10,0.4691233333,0.7203966667,0.4694833333,0.6401244609
-5,0.6347966667,0.87413,0.6333466667,0.8146859285
0,0.80602,0.95331,0.8066566667,0.9237966241
5,0.9194566667,0.98428,0.9189733333,0.9727474174
10,0.9703833333,0.99481,0.97034,0.9908905586
15,0.9903833333,0.9983633333,0.9900733333,0.9970319887
20,0.9967733333,0.9995166667,0.9968766667,0.9990359654
1 jsr_db blind matched perm_blind oma_targeted
2 -10 0.4691233333 0.7203966667 0.4694833333 0.6401244609
3 -5 0.6347966667 0.87413 0.6333466667 0.8146859285
4 0 0.80602 0.95331 0.8066566667 0.9237966241
5 5 0.9194566667 0.98428 0.9189733333 0.9727474174
6 10 0.9703833333 0.99481 0.97034 0.9908905586
7 15 0.9903833333 0.9983633333 0.9900733333 0.9970319887
8 20 0.9967733333 0.9995166667 0.9968766667 0.9990359654
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.
Binary file not shown.