Audit round: fair OMA reference, dense grids, covariance-attack checks
Resource-match the OMA reference in the key-length sweep (oma_ser_keylen), which gives it the L/16 combining gain the longer frame allows. The proposal now passes a resource-matched OMA by 1.27x at L=64 rather than the 4.3x reported against a fixed-d reference. Densify the JSR, sensitivity, and brute-force grids so the curves are smooth, give the index cipher its channel floor instead of error-free reception, and add the permutation-key known-plaintext attack (exp_permkpa) so Fig. 7 carries a conventional linear scheme. Add check_cov_attack.py and check_cov_ceiling.py: a referee raised a ciphertext-only second-order attack; the exact-population test shows the received covariance leaks only a sparse rank-deficient subset of the key Gram and leaves the eavesdropper at the random-guess level. Dump verify_math.csv, move the superseded V=256 pilot CSVs to data/pilot.
This commit is contained in:
@@ -28,8 +28,10 @@ code/
|
|||||||
schemes, key families, scheme comparison, attack difficulty
|
schemes, key families, scheme comparison, attack difficulty
|
||||||
exp_kpa.py stage H: known-plaintext attack on the key
|
exp_kpa.py stage H: known-plaintext attack on the key
|
||||||
exp_refresh.py stage K: the key-refresh layer, invariance group
|
exp_refresh.py stage K: the key-refresh layer, invariance group
|
||||||
|
exp_permkpa.py permutation-key known-plaintext attack (Fig. 7)
|
||||||
|
check_cov_*.py ciphertext-only covariance-attack checks (referee M1)
|
||||||
exp_real_sec.py stage G: real BERT WordPiece token streams
|
exp_real_sec.py stage G: real BERT WordPiece token streams
|
||||||
verify_math.py closed-form checks V1-V5 against Monte Carlo, PASS/FAIL
|
verify_math.py closed-form checks V1-V5, PASS/FAIL and verify_math.csv
|
||||||
replot_security.py every result figure, from data/ to fig/
|
replot_security.py every result figure, from data/ to fig/
|
||||||
make_tables.py LaTeX rows of every result table, from data/
|
make_tables.py LaTeX rows of every result table, from data/
|
||||||
feasibility_security.py early CPU-sized study, kept for the record
|
feasibility_security.py early CPU-sized study, kept for the record
|
||||||
@@ -66,10 +68,10 @@ files.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Fig. 2 SER against SNR | `exp_full.stage_A` | `sec_snr.csv` |
|
| Fig. 2 SER against SNR | `exp_full.stage_A` | `sec_snr.csv` |
|
||||||
| Fig. 3 key length | `exp_full.stage_B` | `sec_keylen.csv` |
|
| Fig. 3 key length | `exp_full.stage_B` | `sec_keylen.csv` |
|
||||||
| Fig. 4 jamming | `exp_full.stage_L` | `sec_jam_cmp.csv`, `sec_jam.csv` |
|
| Fig. 4 jamming (4 schemes) | `exp_full.stage_L` | `sec_jam_cmp.csv`, `sec_jam.csv` |
|
||||||
| Fig. 5 key sensitivity | `exp_full.stage_I` | `sec_sens_cmp.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. 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. 7 known-plaintext attack | `exp_kpa`, `exp_permkpa` | `kpa.csv`, `pkpa.csv` |
|
||||||
| Fig. 8 real token streams | `exp_real_sec` | `real_sec_ter.csv` |
|
| Fig. 8 real token streams | `exp_real_sec` | `real_sec_ter.csv` |
|
||||||
| Scheme comparison table | `exp_full.stage_E` | `sec_compare.csv` |
|
| Scheme comparison table | `exp_full.stage_E` | `sec_compare.csv` |
|
||||||
| Key family table | `exp_full.stage_D` | `sec_maskfam.csv`, `sec_regjam.csv` |
|
| Key family table | `exp_full.stage_D` | `sec_maskfam.csv`, `sec_regjam.csv` |
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
"""Independent check of the ciphertext-only second-order attack (audit M1).
|
||||||
|
|
||||||
|
Claim under test: an eavesdropper who observes only received frames (no
|
||||||
|
known indices) can estimate the key Gram matrix M^T M from the sample
|
||||||
|
covariance, because per period
|
||||||
|
E[y_k y_l] = (1/c^2) * E[h^2] * C_kl * (M^T M)_kl,
|
||||||
|
where C_kl = (1/Vu) sum_i b_{i,k} b_{i,l} is the PUBLIC codebook column
|
||||||
|
correlation and the noise touches only the diagonal.
|
||||||
|
|
||||||
|
Procedure, using nothing the threat model keeps secret:
|
||||||
|
1. collect N received frames y_n = h_n * (1/c) sum_u e_{s_u} ⊙ m_u + noise
|
||||||
|
2. form the per-period sample second moment S_kl = mean_n y_{n,k} y_{n,l}
|
||||||
|
3. divide the off-diagonal by C_kl (public) to get G_hat ≈ M^T M
|
||||||
|
4. set the diagonal of G_hat to U (unit-modulus keys)
|
||||||
|
5. factor G_hat = M_hat^T M_hat (rank U), then for Walsh-Hadamard keys
|
||||||
|
round to ±1 and search the 2^U U! signed permutations, keeping the
|
||||||
|
M_hat that best decodes a handful of the collected frames
|
||||||
|
6. report the recovered-entry fraction and the eavesdropper SER, both
|
||||||
|
WITHOUT ever using a known index
|
||||||
|
|
||||||
|
Run under WSL. Prints a verdict; writes nothing to data/.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
import itertools
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sse_lib import rayleigh_gain, DEVICE
|
||||||
|
from exp_full import get_model, hadamard, eval_ser_eve
|
||||||
|
|
||||||
|
|
||||||
|
def collect_frames(m, n, snr_db, seed):
|
||||||
|
"""Received frames and the true indices (indices kept only for scoring)."""
|
||||||
|
g = torch.Generator().manual_seed(seed)
|
||||||
|
Bn = m.unit_codebook()
|
||||||
|
true_m = m.masks()
|
||||||
|
c = m.c
|
||||||
|
sigma = math.sqrt(1.0 / (m.d * 10.0 ** (snr_db / 10.0)))
|
||||||
|
digits = torch.randint(m.vu, (n, m.users, m.P), generator=g).to(DEVICE)
|
||||||
|
e = Bn[digits] / math.sqrt(m.P) # (n,U,P,L)
|
||||||
|
y = (e * true_m[None, :, None, :]).sum(dim=1) / c # (n,P,L)
|
||||||
|
h = rayleigh_gain((n,), device=DEVICE)
|
||||||
|
y = h[:, None, None] * y + sigma * torch.randn(n, m.P, m.L, device=DEVICE)
|
||||||
|
return y, digits, Bn, true_m
|
||||||
|
|
||||||
|
|
||||||
|
def codebook_corr(Bn):
|
||||||
|
"""Public column correlation C_kl = (1/Vu) sum_i b_ik b_il."""
|
||||||
|
return (Bn.T @ Bn) / Bn.shape[0] # (L,L)
|
||||||
|
|
||||||
|
|
||||||
|
def attack(m, snr_db, n_frames, seed):
|
||||||
|
y, digits, Bn, true_m = collect_frames(m, n_frames, snr_db, seed)
|
||||||
|
U, L = m.users, m.L
|
||||||
|
yf = y.reshape(-1, L) # pool all periods
|
||||||
|
S = (yf.T @ yf) / yf.shape[0] # (L,L) 2nd moment
|
||||||
|
C = codebook_corr(Bn) # public
|
||||||
|
G = torch.zeros(L, L, device=DEVICE)
|
||||||
|
mask = C.abs() > 1e-3
|
||||||
|
G[mask] = S[mask] / C[mask] # ≈ (1/c^2) M^T M
|
||||||
|
scale = float(torch.diagonal(G)[mask.diagonal()].mean()) / U
|
||||||
|
G = G / max(scale, 1e-9) # normalize so diag≈U
|
||||||
|
G.fill_diagonal_(float(U)) # unit-modulus keys
|
||||||
|
# symmetric rank-U factor
|
||||||
|
G = 0.5 * (G + G.T)
|
||||||
|
evals, evecs = torch.linalg.eigh(G)
|
||||||
|
idx = torch.argsort(evals, descending=True)[:U]
|
||||||
|
root = evecs[:, idx] * evals[idx].clamp_min(0).sqrt()
|
||||||
|
Mhat0 = root.T # (U,L), up to U×U orth
|
||||||
|
# for WH keys, snap to ±1 and search signed row permutations
|
||||||
|
cand = torch.sign(Mhat0)
|
||||||
|
cand[cand == 0] = 1.0
|
||||||
|
best = None
|
||||||
|
best_ser = 1.0
|
||||||
|
val = torch.arange(min(2000, n_frames))
|
||||||
|
for perm in itertools.permutations(range(U)):
|
||||||
|
for signs in itertools.product([1.0, -1.0], repeat=U):
|
||||||
|
Mh = (cand[list(perm)] *
|
||||||
|
torch.tensor(signs, device=DEVICE)[:, None])
|
||||||
|
ser = eval_ser_eve(m, Mh.cpu(), [snr_db], frames=20_000,
|
||||||
|
seed=13)[0]
|
||||||
|
if ser < best_ser:
|
||||||
|
best_ser, best = ser, Mh
|
||||||
|
# recovered-entry fraction against the true keys (best sign-aligned)
|
||||||
|
tm = torch.sign(true_m).to(DEVICE)
|
||||||
|
frac = 0.0
|
||||||
|
for perm in itertools.permutations(range(U)):
|
||||||
|
for signs in itertools.product([1.0, -1.0], repeat=U):
|
||||||
|
Mh = (best[list(perm)] *
|
||||||
|
torch.tensor(signs, device=DEVICE)[:, None])
|
||||||
|
frac = max(frac, float((Mh == tm).float().mean()))
|
||||||
|
return frac, best_ser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
U, L = 4, 16
|
||||||
|
K0 = torch.tensor(hadamard(L)[1:U + 1], dtype=torch.float32)
|
||||||
|
m = get_model(iters=4000, freeze_W=K0)
|
||||||
|
m.eval()
|
||||||
|
chance = 1.0 - (1.0 / m.vu) ** m.P
|
||||||
|
print(f"chance SER = {chance:.5f}, legitimate reference ~0.276")
|
||||||
|
print("ciphertext-only (NO known plaintext):")
|
||||||
|
for snr in (10.0, 20.0):
|
||||||
|
for nf in (300, 1000, 10000):
|
||||||
|
frac, ser = attack(m, snr, nf, seed=1234 + nf)
|
||||||
|
print(f" {snr:4.0f} dB N={nf:6d} "
|
||||||
|
f"key-entry recovery={frac:.3f} eve SER={ser:.4f}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
"""Decisive noiseless population test of the covariance attack (audit M1).
|
||||||
|
|
||||||
|
If the second-order statistics leak the key Gram, they leak it best in
|
||||||
|
the noiseless infinite-sample limit. This computes the EXACT per-period
|
||||||
|
second moment E[y_k y_l] over the uniform index distribution with no
|
||||||
|
channel and no noise, then runs the same recovery, and asks whether the
|
||||||
|
keys come out. If they do not come out even here, no finite noisy attack
|
||||||
|
can do better and the leak is not exploitable against this codebook.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
import itertools
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sse_lib import DEVICE
|
||||||
|
from exp_full import get_model, hadamard, eval_ser_eve
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
U, L = 4, 16
|
||||||
|
K0 = torch.tensor(hadamard(L)[1:U + 1], dtype=torch.float32)
|
||||||
|
m = get_model(iters=4000, freeze_W=K0)
|
||||||
|
m.eval()
|
||||||
|
Bn = m.unit_codebook().to(DEVICE) # (Vu,L)
|
||||||
|
true_m = m.masks().to(DEVICE) # (U,L)
|
||||||
|
c = m.c
|
||||||
|
|
||||||
|
# exact population second moment of one period, indices uniform
|
||||||
|
# y_k = (1/c) sum_u e_{s_u,k} m_{u,k}, s_u iid uniform over Vu
|
||||||
|
mu = Bn.mean(0) # codebook column mean
|
||||||
|
R = (Bn.T @ Bn) / Bn.shape[0] # E[e_k e_l], (L,L)
|
||||||
|
G_true = true_m.T @ true_m # (L,L) key Gram, the target
|
||||||
|
# E[y_k y_l] = (1/c^2)[ R_kl (M^TM)_kl + (mu_k mu_l)(rowsum_k rowsum_l
|
||||||
|
# - diag correction) ]; assemble exactly
|
||||||
|
rs = true_m.sum(0) # sum_u m_{u,k}
|
||||||
|
cross = torch.outer(rs, rs) - G_true # sum_{u!=v} m_uk m_vl
|
||||||
|
S = (R * G_true + torch.outer(mu, mu) * cross) / (c * c)
|
||||||
|
|
||||||
|
print(f"codebook column mean |mu|_max = {mu.abs().max():.4f}")
|
||||||
|
offdiag = R - torch.diag(torch.diagonal(R))
|
||||||
|
print(f"codebook R off-diagonal: max|R_kl| = {offdiag.abs().max():.4f}, "
|
||||||
|
f"mean|R_kl| = {offdiag.abs().mean():.4f}")
|
||||||
|
|
||||||
|
# recover G from S using the public R (exactly the attack)
|
||||||
|
C = R
|
||||||
|
keep = C.abs() > 1e-2
|
||||||
|
Ghat = torch.zeros(L, L, device=DEVICE)
|
||||||
|
Ghat[keep] = S[keep] * (c * c) / C[keep]
|
||||||
|
# how well does the off-diagonal of Ghat match the true key Gram?
|
||||||
|
od = ~torch.eye(L, dtype=torch.bool, device=DEVICE)
|
||||||
|
usable = keep & od
|
||||||
|
if usable.any():
|
||||||
|
err = (Ghat[usable] - G_true[usable]).abs().mean()
|
||||||
|
rng = G_true[od].abs().mean()
|
||||||
|
print(f"usable off-diagonal entries: {int(usable.sum())} of {L*(L-1)}")
|
||||||
|
print(f"recovered-Gram error on usable entries: {err:.4f} "
|
||||||
|
f"(true off-diag scale {rng:.4f})")
|
||||||
|
else:
|
||||||
|
print("no usable off-diagonal entries: R is diagonal, zero leak")
|
||||||
|
|
||||||
|
# try to factor and decode from the exact-population Ghat
|
||||||
|
Ghat[~keep] = 0.0
|
||||||
|
Ghat.fill_diagonal_(float(U))
|
||||||
|
Ghat = 0.5 * (Ghat + Ghat.T)
|
||||||
|
ev, evec = torch.linalg.eigh(Ghat)
|
||||||
|
idx = torch.argsort(ev, descending=True)[:U]
|
||||||
|
root = (evec[:, idx] * ev[idx].clamp_min(0).sqrt()).T
|
||||||
|
cand = torch.sign(root)
|
||||||
|
cand[cand == 0] = 1.0
|
||||||
|
best = 1.0
|
||||||
|
for perm in itertools.permutations(range(U)):
|
||||||
|
for sg in itertools.product([1.0, -1.0], repeat=U):
|
||||||
|
Mh = cand[list(perm)] * torch.tensor(sg, device=DEVICE)[:, None]
|
||||||
|
best = min(best, eval_ser_eve(m, Mh.cpu(), [10.0],
|
||||||
|
frames=20_000, seed=13)[0])
|
||||||
|
print(f"best eavesdropper SER from EXACT population covariance: {best:.4f}")
|
||||||
|
print("chance 0.99998, legitimate ~0.276")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+77
-13
@@ -152,7 +152,7 @@ def get_model(P=4, vu=16, d=64, U=4, iters=4000, seed=1, freeze_W=None, tag=""):
|
|||||||
def stage_A():
|
def stage_A():
|
||||||
print("[A] security vs SNR (V=65536) ...")
|
print("[A] security vs SNR (V=65536) ...")
|
||||||
m = get_model(iters=4000)
|
m = get_model(iters=4000)
|
||||||
snr = [0.0, 4.0, 8.0, 12.0, 16.0, 20.0]
|
snr = [float(v) for v in range(0, 21, 2)]
|
||||||
frames = 800_000
|
frames = 800_000
|
||||||
legit = eval_ser_sse(m, snr, frames=frames)
|
legit = eval_ser_sse(m, snr, frames=frames)
|
||||||
ew = eve_wrong_mask(m.users, m.L, seed=20260813).to(DEVICE)
|
ew = eve_wrong_mask(m.users, m.L, seed=20260813).to(DEVICE)
|
||||||
@@ -209,18 +209,46 @@ def get_model_reg(P=4, vu=16, d=64, U=4, iters=4000, seed=1):
|
|||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def oma_ser_keylen(L, snr_db, bits=16, n_grid=200_000):
|
||||||
|
"""Resource-matched OMA reference for the key-length sweep.
|
||||||
|
|
||||||
|
The OMA user owns d/U = L exclusive real dimensions for its 16 index
|
||||||
|
bits at the same per-dimension SNR. For L >= 16 the best use of the
|
||||||
|
allocation is antipodal signaling on 16 dimensions with the frame
|
||||||
|
energy concentrated on them, an energy gain of L/16 per bit. For
|
||||||
|
L < 16 the user must pack 16/L bits per dimension, a 2^(16/L)-ary
|
||||||
|
pulse-amplitude constellation, defined when 16/L is an integer and
|
||||||
|
reported as nan otherwise.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
if L >= bits:
|
||||||
|
return oma_ser([snr_db + 10.0 * math.log10(L / bits)], bits=bits)[0]
|
||||||
|
if bits % L:
|
||||||
|
return float("nan")
|
||||||
|
M = 2 ** (bits // L)
|
||||||
|
x = (np.arange(n_grid) + 0.5) / n_grid
|
||||||
|
h = np.sqrt(-np.log(1.0 - x))
|
||||||
|
g = 10.0 ** (snr_db / 10.0)
|
||||||
|
arg = np.clip(h * math.sqrt(6.0 * g / (M * M - 1.0)), 0, 38)
|
||||||
|
q = (1.0 - 1.0 / M) * np.array([math.erfc(v / math.sqrt(2.0))
|
||||||
|
for v in arg])
|
||||||
|
q = np.clip(q, 0.0, 1.0)
|
||||||
|
return float(np.mean(1.0 - (1.0 - q) ** L))
|
||||||
|
|
||||||
|
|
||||||
def stage_B():
|
def stage_B():
|
||||||
print("[B] key length (dense grid so the curve is smooth) ...")
|
print("[B] key length (dense grid so the curve is smooth) ...")
|
||||||
oma10 = oma_ser([10.0], bits=16)[0]
|
|
||||||
rows = []
|
rows = []
|
||||||
for d in [16, 24, 32, 48, 64, 96, 128, 192, 256]:
|
for d in [16, 24, 32, 40, 48, 56, 64, 80, 96, 128, 192, 256]:
|
||||||
m = get_model(d=d, iters=4000)
|
m = get_model(d=d, iters=4000)
|
||||||
lg = eval_ser_sse(m, [10.0], frames=500_000)[0]
|
lg = eval_ser_sse(m, [10.0], frames=500_000)[0]
|
||||||
ew = eve_wrong_mask(m.users, m.L, seed=20260813).to(DEVICE)
|
ew = eve_wrong_mask(m.users, m.L, seed=20260813).to(DEVICE)
|
||||||
ev = eval_ser_eve(m, ew, [10.0], frames=500_000)[0]
|
ev = eval_ser_eve(m, ew, [10.0], frames=500_000)[0]
|
||||||
xc = mean_abs_xcorr(m.masks().detach())
|
xc = mean_abs_xcorr(m.masks().detach())
|
||||||
rows.append((m.L, d, lg, ev, xc, oma10))
|
oma = oma_ser_keylen(m.L, 10.0)
|
||||||
print(f" L={m.L:4d} legit={lg:.2e} eve={ev:.3f} xcorr={xc:.4f}")
|
rows.append((m.L, d, lg, ev, xc, oma))
|
||||||
|
print(f" L={m.L:4d} legit={lg:.2e} eve={ev:.3f} xcorr={xc:.4f} "
|
||||||
|
f"oma={oma:.4f}")
|
||||||
write_csv(DATA / "sec_keylen.csv",
|
write_csv(DATA / "sec_keylen.csv",
|
||||||
["L", "d", "legit_ser", "eve_ser", "mask_xcorr", "oma"], rows)
|
["L", "d", "legit_ser", "eve_ser", "mask_xcorr", "oma"], rows)
|
||||||
|
|
||||||
@@ -548,8 +576,10 @@ def stage_I():
|
|||||||
between the guessed and the true mask. For the permutation scheme it
|
between the guessed and the true mask. For the permutation scheme it
|
||||||
is the fraction of positions the guessed permutation places
|
is the fraction of positions the guessed permutation places
|
||||||
correctly. For the index cipher it is the fraction of pad bits the
|
correctly. For the index cipher it is the fraction of pad bits the
|
||||||
attacker knows, whose error rate is the closed form
|
attacker knows. Its error rate is the closed form
|
||||||
1 - 2^{-(1-f) log2 V} because the unknown bits are uniform.
|
1 - (1-p_ch) 2^{-(1-f) log2 V}, the probability of decoding the
|
||||||
|
ciphered index over the channel times the probability that the
|
||||||
|
unknown pad bits, which stay uniform, are all guessed right.
|
||||||
"""
|
"""
|
||||||
print("[I] key sensitivity across schemes ...")
|
print("[I] key sensitivity across schemes ...")
|
||||||
m = get_model(iters=4000)
|
m = get_model(iters=4000)
|
||||||
@@ -563,8 +593,12 @@ def stage_I():
|
|||||||
perms = gperm[None].repeat(m.users, 1)
|
perms = gperm[None].repeat(m.users, 1)
|
||||||
# a marker grid comparable to the other result figures, with the
|
# a marker grid comparable to the other result figures, with the
|
||||||
# spacing tightened only where the curves fall
|
# spacing tightened only where the curves fall
|
||||||
fracs = [0.0, 0.2, 0.4, 0.6, 0.75, 0.85, 0.9, 0.94, 0.97, 1.0]
|
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]
|
||||||
bits = math.log2(m.V)
|
bits = math.log2(m.V)
|
||||||
|
# the channel success of a public-mask receiver, which the cipher
|
||||||
|
# cannot exceed even with the full pad
|
||||||
|
lg1 = eval_scheme(m, 10.0, 200_000)
|
||||||
rows = []
|
rows = []
|
||||||
for f in fracs:
|
for f in fracs:
|
||||||
acc_m = []
|
acc_m = []
|
||||||
@@ -585,7 +619,7 @@ def stage_I():
|
|||||||
perms, eve_perms=pperms,
|
perms, eve_perms=pperms,
|
||||||
seed=777 + 13 * t))
|
seed=777 + 13 * t))
|
||||||
ser_perm = sum(acc) / len(acc)
|
ser_perm = sum(acc) / len(acc)
|
||||||
ser_pad = 1.0 - 2.0 ** (-(1.0 - f) * bits)
|
ser_pad = 1.0 - (1.0 - lg1) * 2.0 ** (-(1.0 - f) * bits)
|
||||||
rows.append((f, ser_mask, ser_perm, ser_pad))
|
rows.append((f, ser_mask, ser_perm, ser_pad))
|
||||||
print(f" f={f:.3f} mask={ser_mask:.4f} perm={ser_perm:.4f} "
|
print(f" f={f:.3f} mask={ser_mask:.4f} perm={ser_perm:.4f} "
|
||||||
f"pad={ser_pad:.4f}")
|
f"pad={ser_pad:.4f}")
|
||||||
@@ -602,7 +636,8 @@ def stage_J():
|
|||||||
one that places the most positions correctly, map the resulting
|
one that places the most positions correctly, map the resulting
|
||||||
fraction through the same sensitivity curve.
|
fraction through the same sensitivity curve.
|
||||||
Index cipher: K random pads out of the 2^{log2 V} possible pads, so
|
Index cipher: K random pads out of the 2^{log2 V} possible pads, so
|
||||||
the attacker succeeds with probability K/V on each symbol.
|
the attacker holds the right pad with probability K/V and still has
|
||||||
|
to decode the ciphered index over the channel.
|
||||||
"""
|
"""
|
||||||
print("[J] brute-force search across schemes ...")
|
print("[J] brute-force search across schemes ...")
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -612,7 +647,11 @@ def stage_J():
|
|||||||
perm_arr = np.array([float(r["ser_perm"]) for r in cmp_rows])
|
perm_arr = np.array([float(r["ser_perm"]) for r in cmp_rows])
|
||||||
|
|
||||||
d, L, V = 64, 16, 65536
|
d, L, V = 64, 16, 65536
|
||||||
ks = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000]
|
# channel floor of the cipher receiver, read from the stage-I curve
|
||||||
|
# at a fully known pad so both figures share one source
|
||||||
|
lg1 = 1.0 - (1.0 - float(cmp_rows[-1]["ser_pad"]))
|
||||||
|
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)
|
rng = np.random.default_rng(2026)
|
||||||
trials = 400
|
trials = 400
|
||||||
rows = []
|
rows = []
|
||||||
@@ -629,7 +668,7 @@ def stage_J():
|
|||||||
best_frac[t] = rng.binomial(d, 1.0 / d, size=K).max() / d
|
best_frac[t] = rng.binomial(d, 1.0 / d, size=K).max() / d
|
||||||
ser_mask = float(np.mean(np.interp(best_kappa, f_arr, mask_arr)))
|
ser_mask = float(np.mean(np.interp(best_kappa, f_arr, mask_arr)))
|
||||||
ser_perm = float(np.mean(np.interp(best_frac, f_arr, perm_arr)))
|
ser_perm = float(np.mean(np.interp(best_frac, f_arr, perm_arr)))
|
||||||
ser_pad = 1.0 - min(1.0, K / V)
|
ser_pad = 1.0 - min(1.0, K / V) * (1.0 - lg1)
|
||||||
rows.append((K, ser_mask, ser_perm, ser_pad,
|
rows.append((K, ser_mask, ser_perm, ser_pad,
|
||||||
float(best_kappa.mean()), float(best_frac.mean())))
|
float(best_kappa.mean()), float(best_frac.mean())))
|
||||||
print(f" K={K:8d} mask={ser_mask:.4f} perm={ser_perm:.4f} "
|
print(f" K={K:8d} mask={ser_mask:.4f} perm={ser_perm:.4f} "
|
||||||
@@ -692,7 +731,7 @@ def stage_L():
|
|||||||
d = m.P * m.L
|
d = m.P * m.L
|
||||||
gp = torch.Generator().manual_seed(11)
|
gp = torch.Generator().manual_seed(11)
|
||||||
perms = torch.randperm(d, generator=gp)[None].repeat(m.users, 1)
|
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]
|
jsr = [float(v) for v in range(-10, 21, 2)]
|
||||||
oma = oma_ser_jammed(10.0, jsr, bits=int(math.log2(m.V)), U=m.users)
|
oma = oma_ser_jammed(10.0, jsr, bits=int(math.log2(m.V)), U=m.users)
|
||||||
rows = []
|
rows = []
|
||||||
for i, j in enumerate(jsr):
|
for i, j in enumerate(jsr):
|
||||||
@@ -705,6 +744,31 @@ def stage_L():
|
|||||||
write_csv(DATA / "sec_jam_cmp.csv",
|
write_csv(DATA / "sec_jam_cmp.csv",
|
||||||
["jsr_db", "blind", "matched", "perm_blind", "oma_targeted"],
|
["jsr_db", "blind", "matched", "perm_blind", "oma_targeted"],
|
||||||
rows)
|
rows)
|
||||||
|
stage_L_gap(rows)
|
||||||
|
|
||||||
|
|
||||||
|
def stage_L_gap(rows):
|
||||||
|
"""Store the blind-vs-matched power gap as a raw artifact.
|
||||||
|
|
||||||
|
For every error level both curves reach, the gap is the extra JSR the
|
||||||
|
blind jammer needs to inflict it. Both curves are interpolated on the
|
||||||
|
dense grid, so the quoted range comes from a stored file rather than
|
||||||
|
from a hand interpolation.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
j = np.array([r[0] for r in rows])
|
||||||
|
blind = np.array([r[1] for r in rows])
|
||||||
|
matched = np.array([r[2] for r in rows])
|
||||||
|
lo = max(blind.min(), matched.min())
|
||||||
|
hi = min(blind.max(), matched.max())
|
||||||
|
ser = np.linspace(lo, hi, 200)
|
||||||
|
jb = np.interp(ser, blind, j)
|
||||||
|
jm = np.interp(ser, matched, j)
|
||||||
|
gap = jb - jm
|
||||||
|
write_csv(DATA / "sec_jam_gap.csv", ["ser", "gap_db"],
|
||||||
|
list(zip(ser.tolist(), gap.tolist())))
|
||||||
|
print(f" gap: {gap.min():.2f} to {gap.max():.2f} dB "
|
||||||
|
f"over SER {lo:.3f} to {hi:.3f}")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
"""Known-plaintext attack on the global-permutation key (run under WSL).
|
||||||
|
|
||||||
|
The permutation scheme keeps the masks public and protects the frame
|
||||||
|
with one secret permutation of the d entries shared by all users. Like
|
||||||
|
the keyed masking, the protection is linear, so an attacker that knows
|
||||||
|
the indices a few frames carried can recover the secret. This stage
|
||||||
|
measures how many known frames the recovery needs, mirroring the grid
|
||||||
|
of exp_kpa.py so the two curves share one figure.
|
||||||
|
|
||||||
|
Attack: with N known frames the attacker knows the pre-permutation
|
||||||
|
signal x_n and observes y_n = h_n * perm(x_n) + noise at the collection
|
||||||
|
SNR. The cross-correlation matrix C[i, j] = sum_n y_n[i] x_n[j] peaks at
|
||||||
|
j = perm(i) because h_n > 0, so the permutation is the assignment that
|
||||||
|
maximizes the total correlation, solved by the Hungarian method. The
|
||||||
|
recovered permutation then decodes user 1 at 10 dB, the convention of
|
||||||
|
exp_kpa.py.
|
||||||
|
|
||||||
|
Writes data/pkpa.csv. Fixed seeds: permutation 11 (the stage-I secret),
|
||||||
|
collection 909.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sse_lib import write_csv, set_seed, DATA, DEVICE
|
||||||
|
from exp_full import get_model, eval_scheme_permuted_eve, rayleigh_gain
|
||||||
|
|
||||||
|
try:
|
||||||
|
from scipy.optimize import linear_sum_assignment
|
||||||
|
except ImportError: # greedy fallback
|
||||||
|
def linear_sum_assignment(cost):
|
||||||
|
c = cost.copy()
|
||||||
|
n = c.shape[0]
|
||||||
|
rows = np.empty(n, dtype=int)
|
||||||
|
cols = np.empty(n, dtype=int)
|
||||||
|
for k in range(n):
|
||||||
|
i, j = np.unravel_index(np.argmin(c), c.shape)
|
||||||
|
rows[k], cols[k] = i, j
|
||||||
|
c[i, :] = np.inf
|
||||||
|
c[:, j] = np.inf
|
||||||
|
order = np.argsort(rows)
|
||||||
|
return rows[order], cols[order]
|
||||||
|
|
||||||
|
COLLECT_DB = 20.0
|
||||||
|
DECODE_DB = 10.0
|
||||||
|
TRIALS = 20
|
||||||
|
EVAL_FRAMES = 100_000
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
m = get_model(iters=4000) # training needs grad
|
||||||
|
m.eval()
|
||||||
|
_run(m)
|
||||||
|
|
||||||
|
|
||||||
|
@torch.no_grad()
|
||||||
|
def _run(m):
|
||||||
|
d = m.P * m.L
|
||||||
|
Bn = m.unit_codebook()
|
||||||
|
true_m = m.masks()
|
||||||
|
c = m.c
|
||||||
|
gp = torch.Generator().manual_seed(11)
|
||||||
|
gperm = torch.randperm(d, generator=gp)
|
||||||
|
perms = gperm[None].repeat(m.users, 1)
|
||||||
|
sigma = math.sqrt(1.0 / (d * 10.0 ** (COLLECT_DB / 10.0)))
|
||||||
|
|
||||||
|
print(f"[P] permutation known-plaintext, collect {COLLECT_DB:.0f} dB, "
|
||||||
|
f"decode {DECODE_DB:.0f} dB ...")
|
||||||
|
rows = []
|
||||||
|
for nf in [1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 24, 32, 48, 64]:
|
||||||
|
fr, sr = [], []
|
||||||
|
for t in range(TRIALS):
|
||||||
|
g = torch.Generator().manual_seed(909 + 1000 * t + nf)
|
||||||
|
digits = torch.randint(m.vu, (nf, m.users, m.P), generator=g)
|
||||||
|
e = Bn[digits.to(DEVICE)] / math.sqrt(m.P)
|
||||||
|
x = (e * true_m[None, :, None, :]).sum(dim=1) / c # (nf,P,L)
|
||||||
|
xf = x.reshape(nf, d)
|
||||||
|
h = rayleigh_gain((nf,), device=DEVICE)
|
||||||
|
noise = sigma * torch.randn(nf, d, device=DEVICE)
|
||||||
|
yf = h[:, None] * xf[:, gperm.to(DEVICE)] + noise
|
||||||
|
C = (yf.T @ xf).cpu().numpy() # (d,d)
|
||||||
|
_, est = linear_sum_assignment(-C)
|
||||||
|
est_t = torch.tensor(est, dtype=torch.long)
|
||||||
|
fr.append(float((est_t == gperm).float().mean()))
|
||||||
|
sr.append(eval_scheme_permuted_eve(
|
||||||
|
m, DECODE_DB, EVAL_FRAMES, perms,
|
||||||
|
eve_perms=est_t[None].repeat(m.users, 1),
|
||||||
|
seed=777 + 31 * t))
|
||||||
|
frac = sum(fr) / len(fr)
|
||||||
|
ser = sum(sr) / len(sr)
|
||||||
|
rows.append((nf, frac, ser))
|
||||||
|
print(f" N={nf:3d} frac={frac:.4f} eve={ser:.4f}")
|
||||||
|
write_csv(DATA / "pkpa.csv", ["n_frames", "perm_frac", "eve_ser"], rows)
|
||||||
|
print("[done] pkpa.csv")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -166,7 +166,7 @@ def main():
|
|||||||
print("[Q1] Eve no-mask SER:", [f"{v:.3g}" for v in eve_n])
|
print("[Q1] Eve no-mask SER:", [f"{v:.3g}" for v in eve_n])
|
||||||
print(f"[Q1] chance frame SER = {chance_frame:.4f}")
|
print(f"[Q1] chance frame SER = {chance_frame:.4f}")
|
||||||
|
|
||||||
write_csv(DATA / "feas_q1_eavesdrop.csv",
|
write_csv(DATA / "pilot" / "feas_q1_eavesdrop.csv",
|
||||||
["snr_db", "legit", "eve_wrong", "eve_none", "eve_avg", "chance"],
|
["snr_db", "legit", "eve_wrong", "eve_none", "eve_avg", "chance"],
|
||||||
[(s, legit[i], eve_w[i], eve_n[i], eve_a[i], chance_frame)
|
[(s, legit[i], eve_w[i], eve_n[i], eve_a[i], chance_frame)
|
||||||
for i, s in enumerate(snr_eval)])
|
for i, s in enumerate(snr_eval)])
|
||||||
@@ -187,7 +187,7 @@ def main():
|
|||||||
xc = mean_abs_cross_corr(mdl.masks().detach().cpu())
|
xc = mean_abs_cross_corr(mdl.masks().detach().cpu())
|
||||||
q2_rows.append((mdl.L, d, lg, ev, xc))
|
q2_rows.append((mdl.L, d, lg, ev, xc))
|
||||||
print(f" L={mdl.L:4d} legit={lg:.3g} eve={ev:.3g} |xcorr|={xc:.3f}")
|
print(f" L={mdl.L:4d} legit={lg:.3g} eve={ev:.3g} |xcorr|={xc:.3f}")
|
||||||
write_csv(DATA / "feas_q2_keyentropy.csv",
|
write_csv(DATA / "pilot" / "feas_q2_keyentropy.csv",
|
||||||
["L", "d", "legit_ser", "eve_ser", "mask_xcorr"], q2_rows)
|
["L", "d", "legit_ser", "eve_ser", "mask_xcorr"], q2_rows)
|
||||||
|
|
||||||
# Q3: jamming robustness at SNR=10 dB
|
# Q3: jamming robustness at SNR=10 dB
|
||||||
@@ -197,7 +197,7 @@ def main():
|
|||||||
jam_rd = eval_ser_jam(model, 10.0, jsr, frames=300_000, mode="random")
|
jam_rd = eval_ser_jam(model, 10.0, jsr, frames=300_000, mode="random")
|
||||||
print("[Q3] aligned-jammer SER:", [f"{v:.3g}" for v in jam_al])
|
print("[Q3] aligned-jammer SER:", [f"{v:.3g}" for v in jam_al])
|
||||||
print("[Q3] random-jammer SER:", [f"{v:.3g}" for v in jam_rd])
|
print("[Q3] random-jammer SER:", [f"{v:.3g}" for v in jam_rd])
|
||||||
write_csv(DATA / "feas_q3_jamming.csv",
|
write_csv(DATA / "pilot" / "feas_q3_jamming.csv",
|
||||||
["jsr_db", "ser_aligned", "ser_random"],
|
["jsr_db", "ser_aligned", "ser_random"],
|
||||||
[(j, jam_al[i], jam_rd[i]) for i, j in enumerate(jsr)])
|
[(j, jam_al[i], jam_rd[i]) for i, j in enumerate(jsr)])
|
||||||
|
|
||||||
|
|||||||
+70
-67
@@ -3,20 +3,22 @@ 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).
|
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.
|
Label dictionary is fixed here and copied verbatim into tables and prose.
|
||||||
|
|
||||||
fig_sec_snr.pdf : legitimate and outsider SER vs SNR (Fig. 2)
|
fig_sec_snr.pdf : legitimate and eavesdropper SER vs SNR (Fig. 2)
|
||||||
fig_sec_keylen.pdf : SER vs key length L (Fig. 3)
|
fig_sec_keylen.pdf : SER vs key length L (Fig. 3)
|
||||||
fig_sec_jam.pdf : target-user SER vs JSR, four schemes (Fig. 4)
|
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_sens.pdf : eavesdropper SER vs fraction of key held (Fig. 5)
|
||||||
fig_sec_brute.pdf : outsider SER vs number of key guesses (Fig. 6)
|
fig_sec_brute.pdf : eavesdropper SER vs number of key guesses (Fig. 6)
|
||||||
fig_sec_kpa.pdf : outsider SER vs known-plaintext frames (Fig. 7)
|
fig_sec_kpa.pdf : eavesdropper SER vs known-plaintext frames (Fig. 7)
|
||||||
fig_sec_real.pdf : token error rate on real streams (Fig. 8)
|
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
|
Curves that coincide by construction are drawn deliberately layered, the
|
||||||
the paper.
|
lower one wide and semi-transparent and the upper one narrow with open
|
||||||
|
markers, so every legend entry has a visible curve.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import csv
|
import csv
|
||||||
|
import math
|
||||||
|
|
||||||
import matplotlib
|
import matplotlib
|
||||||
matplotlib.use("Agg")
|
matplotlib.use("Agg")
|
||||||
@@ -32,7 +34,7 @@ plt.rcParams.update({
|
|||||||
"font.serif": ["DejaVu Serif", "Times New Roman"],
|
"font.serif": ["DejaVu Serif", "Times New Roman"],
|
||||||
"font.size": 9,
|
"font.size": 9,
|
||||||
"axes.labelsize": 9,
|
"axes.labelsize": 9,
|
||||||
"legend.fontsize": 6.6,
|
"legend.fontsize": 7.4,
|
||||||
"xtick.labelsize": 8,
|
"xtick.labelsize": 8,
|
||||||
"ytick.labelsize": 8,
|
"ytick.labelsize": 8,
|
||||||
"axes.grid": True,
|
"axes.grid": True,
|
||||||
@@ -57,13 +59,20 @@ C_PUB = "#16a085"
|
|||||||
LBL = {
|
LBL = {
|
||||||
"legit": "Legitimate",
|
"legit": "Legitimate",
|
||||||
"oma": "OMA",
|
"oma": "OMA",
|
||||||
"eve_pub": "Eve, public masks",
|
"eve_pub": "Eavesdropper, public masks",
|
||||||
"eve_key": "Eve, wrong key",
|
"eve_key": "Eavesdropper, wrong key",
|
||||||
"chance": "Random guess",
|
"chance": "Random guess",
|
||||||
"jam_m": "Matched jammer (public masks)",
|
|
||||||
"jam_b": "Blind jammer (proposed)",
|
|
||||||
"nojam": "No jammer",
|
"nojam": "No jammer",
|
||||||
|
"mask": "Keyed masking",
|
||||||
|
"perm": "Permutation key",
|
||||||
|
"pad": "Index cipher",
|
||||||
|
"insider": "Insider",
|
||||||
|
"outsider": "Outsider",
|
||||||
}
|
}
|
||||||
|
# deliberate-layering style for the LOWER of two coinciding curves
|
||||||
|
UNDER = dict(lw=2.6, ms=7, alpha=0.85)
|
||||||
|
# and for the curve riding on top of it
|
||||||
|
OVER = dict(lw=1.2, ms=4.5, mfc="none")
|
||||||
|
|
||||||
|
|
||||||
def load(name):
|
def load(name):
|
||||||
@@ -106,10 +115,11 @@ def fig_snr():
|
|||||||
r = load("sec_snr.csv")
|
r = load("sec_snr.csv")
|
||||||
x = col(r, "snr_db")
|
x = col(r, "snr_db")
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
|
# legitimate and OMA coincide by construction; layered deliberately
|
||||||
ax.semilogy(x, col(r, "legit"), color=C_LEGIT, marker="o", ls="-",
|
ax.semilogy(x, col(r, "legit"), color=C_LEGIT, marker="o", ls="-",
|
||||||
label=LBL["legit"])
|
label=LBL["legit"], **UNDER)
|
||||||
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
|
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
|
||||||
label=LBL["oma"])
|
label=LBL["oma"], **OVER)
|
||||||
ax.semilogy(x, col(r, "eve_public"), color=C_PUB, marker="v",
|
ax.semilogy(x, col(r, "eve_public"), color=C_PUB, marker="v",
|
||||||
ls="none", markersize=5.2, markerfacecolor="none",
|
ls="none", markersize=5.2, markerfacecolor="none",
|
||||||
label=LBL["eve_pub"])
|
label=LBL["eve_pub"])
|
||||||
@@ -125,13 +135,17 @@ def fig_snr():
|
|||||||
|
|
||||||
|
|
||||||
def fig_keylen():
|
def fig_keylen():
|
||||||
|
"""The OMA reference is the resource-matched one of oma_ser_keylen,
|
||||||
|
which is undefined at key lengths where 16/L is not an integer; those
|
||||||
|
rows carry nan and are skipped."""
|
||||||
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"), color=C_LEGIT, marker="o", ls="-",
|
||||||
label=LBL["legit"])
|
label=LBL["legit"])
|
||||||
ax.semilogy(x, col(r, "oma"), color=C_OMA, marker="^", ls=":",
|
op = [(l, v) for l, v in zip(x, col(r, "oma")) if not math.isnan(v)]
|
||||||
label=LBL["oma"])
|
ax.semilogy([p[0] for p in op], [p[1] for p in op], color=C_OMA,
|
||||||
|
marker="^", ls=":", label=LBL["oma"])
|
||||||
ax.semilogy(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="--",
|
ax.semilogy(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="--",
|
||||||
label=LBL["eve_key"])
|
label=LBL["eve_key"])
|
||||||
ax.set_xlabel("Key length $L$")
|
ax.set_xlabel("Key length $L$")
|
||||||
@@ -144,47 +158,47 @@ def fig_keylen():
|
|||||||
def fig_jam():
|
def fig_jam():
|
||||||
"""Target-user SER against JSR for four schemes. A linear axis is
|
"""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
|
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."""
|
would print wide minor tick labels that crowd out the y label. The
|
||||||
|
no-jammer reference is annotated on the line rather than listed in
|
||||||
|
the legend, so the legend never covers it."""
|
||||||
r = load("sec_jam_cmp.csv")
|
r = load("sec_jam_cmp.csv")
|
||||||
x = col(r, "jsr_db")
|
x = col(r, "jsr_db")
|
||||||
|
me = max(1, len(x) // 8)
|
||||||
fig, ax = plt.subplots()
|
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="--",
|
ax.plot(x, col(r, "matched"), color=C_MATCH, marker="P", ls="--",
|
||||||
label="Public masks, matched")
|
markevery=me, label=LBL["mask"] + ", matched")
|
||||||
# the two blind curves agree to 0.0015, so the proposed one is drawn
|
ax.plot(x, col(r, "oma_targeted"), color=C_PUB, marker="^", ls=":",
|
||||||
# first and wide and the permutation key rides on top with open
|
markevery=me, label=LBL["oma"] + ", targeted")
|
||||||
# markers, otherwise one legend entry would have no visible curve
|
# the two blind curves agree to 0.0015; deliberate layering
|
||||||
ax.plot(x, col(r, "blind"), color=C_LEGIT, marker="o", ls="-", lw=2.6,
|
ax.plot(x, col(r, "blind"), color=C_LEGIT, marker="o", ls="-",
|
||||||
ms=7, alpha=0.85, label="Proposed, blind")
|
markevery=me, label=LBL["mask"] + ", blind", **UNDER)
|
||||||
ax.plot(x, col(r, "perm_blind"), color=C_EVE, marker="s", ls="-.",
|
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")
|
markevery=me, label=LBL["perm"] + ", blind", **OVER)
|
||||||
nojam = float(load("sec_jam.csv")[0]["nojam"])
|
nojam = float(load("sec_jam.csv")[0]["nojam"])
|
||||||
ax.axhline(nojam, color=C_OMA, ls=(0, (1, 3)), lw=0.9,
|
ax.axhline(nojam, color=C_OMA, ls=(0, (1, 3)), lw=0.9)
|
||||||
label=LBL["nojam"])
|
ax.text(max(x) - 0.6, nojam + 0.02, LBL["nojam"], ha="right",
|
||||||
|
va="bottom", fontsize=7.4, color="#555555")
|
||||||
ax.set_xlabel("JSR (dB)")
|
ax.set_xlabel("JSR (dB)")
|
||||||
ax.set_ylabel("SER")
|
ax.set_ylabel("SER")
|
||||||
ax.set_xlim(min(x), max(x))
|
ax.set_xlim(min(x), max(x))
|
||||||
ax.set_ylim(0.2, 1.02)
|
ax.set_ylim(0.2, 1.02)
|
||||||
ax.legend(loc="lower right")
|
ax.legend(loc="center right", bbox_to_anchor=(0.985, 0.47))
|
||||||
save(fig, "fig_sec_jam")
|
save(fig, "fig_sec_jam")
|
||||||
|
|
||||||
|
|
||||||
def fig_sens():
|
def fig_sens():
|
||||||
"""Key sensitivity of three schemes on one axis, the fraction of the
|
"""Key sensitivity of three schemes on one axis, the fraction of the
|
||||||
key the attacker holds. For keyed masking that fraction is the mask
|
key the attacker holds. All three ride the random-guess level over
|
||||||
correlation, for the permutation scheme the fraction of positions
|
most of the range, so the flat region is deliberately layered."""
|
||||||
placed correctly, for the index cipher the fraction of pad bits
|
|
||||||
known."""
|
|
||||||
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"), color=C_LEGIT, marker="o", ls="-",
|
||||||
label="Keyed masking")
|
label=LBL["mask"], **UNDER)
|
||||||
ax.plot(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
|
ax.plot(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
|
||||||
label="Permutation key")
|
label=LBL["perm"], **OVER)
|
||||||
ax.plot(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
|
ax.plot(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
|
||||||
label="Index cipher")
|
lw=1.2, ms=4.5, mfc="none", label=LBL["pad"])
|
||||||
chance = 1.0 - (1.0 / 16.0) ** 4
|
chance = 1.0 - (1.0 / 16.0) ** 4
|
||||||
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"])
|
||||||
ax.set_xlabel("Fraction of the key recovered")
|
ax.set_xlabel("Fraction of the key recovered")
|
||||||
@@ -200,54 +214,35 @@ 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_mask"), color=C_LEGIT, marker="o", ls="-",
|
|
||||||
label="Keyed masking")
|
|
||||||
ax.semilogx(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
|
ax.semilogx(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
|
||||||
label="Permutation key")
|
label=LBL["perm"], **UNDER)
|
||||||
ax.semilogx(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
|
ax.semilogx(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
|
||||||
label="Index cipher")
|
label=LBL["pad"], **OVER)
|
||||||
|
ax.semilogx(x, col(r, "ser_mask"), color=C_LEGIT, marker="o", ls="-",
|
||||||
|
label=LBL["mask"])
|
||||||
kl = load("sec_keylen.csv")
|
kl = load("sec_keylen.csv")
|
||||||
legit = float([q for q in kl if int(q["L"]) == 16][0]["legit_ser"])
|
legit = float([q for q in kl if int(q["L"]) == 16][0]["legit_ser"])
|
||||||
ax.axhline(legit, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
|
ax.axhline(legit, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
|
||||||
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.03, 1.05)
|
ax.set_ylim(0.2, 1.05)
|
||||||
ax.legend(loc="center left")
|
ax.legend(loc="lower left")
|
||||||
save(fig, "fig_sec_brute")
|
save(fig, "fig_sec_brute")
|
||||||
|
|
||||||
|
|
||||||
def fig_brute_rho():
|
|
||||||
"""Best key correlation a search of size K reaches, per key length.
|
|
||||||
This is a property of the key space alone."""
|
|
||||||
r = load("sec_brute.csv")
|
|
||||||
fig, ax = plt.subplots()
|
|
||||||
sty = {8: ("#c0392b", "o"), 16: ("#2c5fa8", "s"),
|
|
||||||
32: ("#16a085", "v"), 64: ("#8e44ad", "P")}
|
|
||||||
for Lp, (c, mk) in sty.items():
|
|
||||||
rows = [row for row in r if int(row["L"]) == Lp]
|
|
||||||
ax.semilogx([float(x["K"]) for x in rows],
|
|
||||||
[float(x["best_rho"]) for x in rows],
|
|
||||||
color=c, marker=mk, ls="-", label=f"$L={Lp}$")
|
|
||||||
ax.axhline(0.96, color=C_CH, ls="-.", lw=0.9, label="Break threshold")
|
|
||||||
ax.set_xlabel("Number of key guesses $K$")
|
|
||||||
ax.set_ylabel(r"Best key correlation $\kappa$")
|
|
||||||
ax.set_ylim(0, 1.05)
|
|
||||||
ax.legend(loc="upper left")
|
|
||||||
save(fig, "fig_sec_brute_rho")
|
|
||||||
|
|
||||||
|
|
||||||
def fig_real():
|
def fig_real():
|
||||||
r = load("real_sec_ter.csv")
|
r = load("real_sec_ter.csv")
|
||||||
x = col(r, "snr_db")
|
x = col(r, "snr_db")
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
|
# legitimate/OMA and insider/outsider coincide pairwise; layered
|
||||||
ax.semilogy(x, col(r, "ter_legit"), color=C_LEGIT, marker="o", ls="-",
|
ax.semilogy(x, col(r, "ter_legit"), color=C_LEGIT, marker="o", ls="-",
|
||||||
label=LBL["legit"])
|
label=LBL["legit"], **UNDER)
|
||||||
ax.semilogy(x, col(r, "ter_oma"), color=C_OMA, marker="^", ls=":",
|
ax.semilogy(x, col(r, "ter_oma"), color=C_OMA, marker="^", ls=":",
|
||||||
label=LBL["oma"])
|
label=LBL["oma"], **OVER)
|
||||||
ax.semilogy(x, col(r, "ter_insider"), color=C_PUB, marker="v", ls="-.",
|
ax.semilogy(x, col(r, "ter_insider"), color=C_PUB, marker="v", ls="-.",
|
||||||
label="Insider")
|
lw=2.6, alpha=0.85, ms=7, label=LBL["insider"])
|
||||||
ax.semilogy(x, col(r, "ter_eve"), color=C_EVE, marker="s", ls="--",
|
ax.semilogy(x, col(r, "ter_eve"), color=C_EVE, marker="s", ls="--",
|
||||||
label=LBL["eve_key"])
|
label=LBL["outsider"], **OVER)
|
||||||
ax.set_xlabel("SNR (dB)")
|
ax.set_xlabel("SNR (dB)")
|
||||||
ax.set_ylabel("TER")
|
ax.set_ylabel("TER")
|
||||||
ax.set_xlim(min(x), max(x))
|
ax.set_xlim(min(x), max(x))
|
||||||
@@ -256,6 +251,9 @@ def fig_real():
|
|||||||
|
|
||||||
|
|
||||||
def fig_kpa():
|
def fig_kpa():
|
||||||
|
"""Known-plaintext recovery of the keyed masks at three collection
|
||||||
|
SNRs, with the permutation key under the same attack as the linear
|
||||||
|
comparison scheme."""
|
||||||
r = load("kpa.csv")
|
r = load("kpa.csv")
|
||||||
fig, ax = plt.subplots()
|
fig, ax = plt.subplots()
|
||||||
sty = {0.0: ("#c0392b", "o"), 10.0: ("#2c5fa8", "s"),
|
sty = {0.0: ("#c0392b", "o"), 10.0: ("#2c5fa8", "s"),
|
||||||
@@ -265,7 +263,13 @@ def fig_kpa():
|
|||||||
n = [float(row["n_frames"]) for row in rows]
|
n = [float(row["n_frames"]) for row in rows]
|
||||||
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="-",
|
||||||
label=f"{int(snr)} dB")
|
label=LBL["mask"] + f", {int(snr)} dB")
|
||||||
|
try:
|
||||||
|
p = load("pkpa.csv")
|
||||||
|
ax.semilogx(col(p, "n_frames"), col(p, "eve_ser"), color=C_MATCH,
|
||||||
|
marker="P", ls="--", label=LBL["perm"] + ", 20 dB")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("[skip] pkpa.csv not present yet")
|
||||||
# legitimate reference measured with the SAME estimator as the
|
# legitimate reference measured with the SAME estimator as the
|
||||||
# eavesdropper curves, namely the four-user average of eval_ser_sse
|
# eavesdropper curves, namely the four-user average of eval_ser_sse
|
||||||
# at L=16, taken from sec_keylen.csv rather than from the user-1
|
# at L=16, taken from sec_keylen.csv rather than from the user-1
|
||||||
@@ -287,7 +291,6 @@ def main():
|
|||||||
try:
|
try:
|
||||||
fig_sens()
|
fig_sens()
|
||||||
fig_brute()
|
fig_brute()
|
||||||
fig_brute_rho()
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("[skip] attack-difficulty CSVs not present yet")
|
print("[skip] attack-difficulty CSVs not present yet")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -33,9 +33,13 @@ def masks(U, d, rng):
|
|||||||
return M / np.linalg.norm(M, axis=1, keepdims=True) * np.sqrt(d)
|
return M / np.linalg.norm(M, axis=1, keepdims=True) * np.sqrt(d)
|
||||||
|
|
||||||
|
|
||||||
|
ROWS = [] # (tag, claim, emp, err, tol, verdict)
|
||||||
|
|
||||||
|
|
||||||
def report(tag, claim, emp, tol, extra=""):
|
def report(tag, claim, emp, tol, extra=""):
|
||||||
err = abs(claim - emp)
|
err = abs(claim - emp)
|
||||||
ok = err <= tol
|
ok = err <= tol
|
||||||
|
ROWS.append((tag, claim, emp, err, tol, "PASS" if ok else "FAIL"))
|
||||||
print(f"[{'PASS' if ok else 'FAIL'}] {tag}: claim={claim:.5g} "
|
print(f"[{'PASS' if ok else 'FAIL'}] {tag}: claim={claim:.5g} "
|
||||||
f"emp={emp:.5g} |err|={err:.2g} tol={tol:g} {extra}")
|
f"emp={emp:.5g} |err|={err:.2g} tol={tol:g} {extra}")
|
||||||
return ok
|
return ok
|
||||||
@@ -195,6 +199,16 @@ def main():
|
|||||||
}
|
}
|
||||||
print("\nsummary:", {k: ("PASS" if v else "FAIL") for k, v in results.items()})
|
print("\nsummary:", {k: ("PASS" if v else "FAIL") for k, v in results.items()})
|
||||||
print("ALL PASS" if all(results.values()) else "SOME FAILED")
|
print("ALL PASS" if all(results.values()) else "SOME FAILED")
|
||||||
|
# stored artifact so every quoted verification number has a raw file
|
||||||
|
import csv as _csv
|
||||||
|
from pathlib import Path as _Path
|
||||||
|
data = _Path(__file__).resolve().parents[1] / "data"
|
||||||
|
with open(data / "verify_math.csv", "w", newline="") as f:
|
||||||
|
w = _csv.writer(f)
|
||||||
|
w.writerow(["check", "claim", "empirical", "abs_err", "tol",
|
||||||
|
"verdict"])
|
||||||
|
w.writerows(ROWS)
|
||||||
|
print("[csv]", data / "verify_math.csv")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Superseded CPU-sized pilot run (V=256) from feasibility_security.py.
|
||||||
|
Kept for the record; every quoted number in the paper comes from the
|
||||||
|
full-scale CSVs one level up or from verify_math.csv.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
n_frames,perm_frac,eve_ser
|
||||||
|
1,0.20234375,0.998977
|
||||||
|
2,0.7140625,0.7107255
|
||||||
|
3,0.91484375,0.4172725
|
||||||
|
4,0.94921875,0.324415
|
||||||
|
5,0.9515625,0.340107
|
||||||
|
6,0.94765625,0.3034795
|
||||||
|
8,0.95625,0.303056
|
||||||
|
10,0.95546875,0.3027505
|
||||||
|
12,0.94921875,0.3028
|
||||||
|
16,0.9546875,0.3036285
|
||||||
|
24,0.94921875,0.303151
|
||||||
|
32,0.95234375,0.302631
|
||||||
|
48,0.95,0.3030415
|
||||||
|
64,0.9515625,0.3025125
|
||||||
|
+14
-7
@@ -1,8 +1,15 @@
|
|||||||
K,ser_mask,ser_perm,ser_pad,best_kappa,best_frac
|
K,ser_mask,ser_perm,ser_pad,best_kappa,best_frac
|
||||||
1,0.9993678262,0.9999544349,0.9999847412,0.1909643153,0.0160546875
|
1,0.9993527855,0.9999542171,0.9999893771,0.1909643153,0.0160546875
|
||||||
10,0.9948030015,0.9999095052,0.9998474121,0.4626973216,0.041015625
|
3,0.9977393447,0.9999284847,0.9999681312,0.3326517476,0.0281640625
|
||||||
100,0.9825717055,0.9998648567,0.9984741211,0.6342127242,0.0658203125
|
10,0.9952796283,0.9998968587,0.9998937706,0.450762326,0.043046875
|
||||||
1000,0.9568330187,0.9998315989,0.9847412109,0.7432459045,0.084296875
|
30,0.9895360243,0.9998741146,0.9996813118,0.5560672497,0.05375
|
||||||
10000,0.909109588,0.9997988333,0.8474121094,0.8165387856,0.1025
|
100,0.9834025595,0.9998495442,0.998937706,0.6290900875,0.0653125
|
||||||
100000,0.8435049061,0.9997690911,0,0.8680494354,0.1190234375
|
300,0.9728417994,0.9998307015,0.996813118,0.688703621,0.0741796875
|
||||||
1000000,0.7503523409,0.9997409661,0,0.905556646,0.1346484375
|
1000,0.9568452325,0.9998081233,0.9893770599,0.7427389508,0.0848046875
|
||||||
|
3000,0.9366731394,0.9997877864,0.9681311798,0.7822538913,0.094375
|
||||||
|
10000,0.9075372546,0.9997705208,0.8937705994,0.8169040678,0.1025
|
||||||
|
30000,0.8818766363,0.9997525081,0.6813117981,0.8434367197,0.1109765625
|
||||||
|
65536,0.8592861449,0.9997413851,0.303815,0.8592030095,0.1162109375
|
||||||
|
100000,0.8443657504,0.9997355745,0.303815,0.8678447033,0.1189453125
|
||||||
|
300000,0.802269081,0.9997181429,0.303815,0.8874619916,0.1271484375
|
||||||
|
1000000,0.7622180175,0.9997021224,0.303815,0.9034448904,0.1346875
|
||||||
|
|||||||
|
+15
-6
@@ -1,8 +1,17 @@
|
|||||||
jsr_db,blind,matched,perm_blind,oma_targeted
|
jsr_db,blind,matched,perm_blind,oma_targeted
|
||||||
-10,0.4691233333,0.7203966667,0.4694833333,0.6401244609
|
-10,0.4691233333,0.7203966667,0.4694833333,0.6401244609
|
||||||
-5,0.6347966667,0.87413,0.6333466667,0.8146859285
|
-8,0.52954,0.7906333333,0.5275833333,0.7152454705
|
||||||
0,0.80602,0.95331,0.8066566667,0.9237966241
|
-6,0.5965433333,0.8503566667,0.59758,0.7840240868
|
||||||
5,0.9194566667,0.98428,0.9189733333,0.9727474174
|
-4,0.6703433333,0.8956333333,0.6699733333,0.8424432091
|
||||||
10,0.9703833333,0.99481,0.97034,0.9908905586
|
-2,0.7420833333,0.9283666667,0.7431533333,0.8888838836
|
||||||
15,0.9903833333,0.9983633333,0.9900733333,0.9970319887
|
0,0.8070433333,0.9532066667,0.8059933333,0.9237966241
|
||||||
20,0.9967733333,0.9995166667,0.9968766667,0.9990359654
|
2,0.8593066667,0.96942,0.85862,0.9488822017
|
||||||
|
4,0.9014266667,0.9805533333,0.9025733333,0.9662811712
|
||||||
|
6,0.9327633333,0.9872533333,0.9318466667,0.9780307416
|
||||||
|
8,0.9549433333,0.9917766667,0.9550866667,0.9858109157
|
||||||
|
10,0.9699833333,0.99508,0.9707366667,0.9908905586
|
||||||
|
12,0.98118,0.9966966667,0.9804366667,0.9941743502
|
||||||
|
14,0.9872866667,0.9979833333,0.9879066667,0.9962827887
|
||||||
|
16,0.9922966667,0.99871,0.9921133333,0.9976304229
|
||||||
|
18,0.9948766667,0.9992,0.99484,0.9984893291
|
||||||
|
20,0.99685,0.9994833333,0.9969366667,0.9990359654
|
||||||
|
|||||||
|
@@ -0,0 +1,201 @@
|
|||||||
|
ser,gap_db
|
||||||
|
0.7203966667,7.395409349
|
||||||
|
0.7217858794,7.394580398
|
||||||
|
0.7231750921,7.393751447
|
||||||
|
0.7245643049,7.392922496
|
||||||
|
0.7259535176,7.392093545
|
||||||
|
0.7273427303,7.391264594
|
||||||
|
0.728731943,7.390435644
|
||||||
|
0.7301211558,7.389606693
|
||||||
|
0.7315103685,7.388777742
|
||||||
|
0.7328995812,7.387948791
|
||||||
|
0.734288794,7.38711984
|
||||||
|
0.7356780067,7.386290889
|
||||||
|
0.7370672194,7.385461939
|
||||||
|
0.7384564322,7.384632988
|
||||||
|
0.7398456449,7.383804037
|
||||||
|
0.7412348576,7.382975086
|
||||||
|
0.7426240704,7.383719533
|
||||||
|
0.7440132831,7.386932812
|
||||||
|
0.7454024958,7.390146092
|
||||||
|
0.7467917085,7.393359371
|
||||||
|
0.7481809213,7.39657265
|
||||||
|
0.749570134,7.39978593
|
||||||
|
0.7509593467,7.402999209
|
||||||
|
0.7523485595,7.406212489
|
||||||
|
0.7537377722,7.409425768
|
||||||
|
0.7551269849,7.412639048
|
||||||
|
0.7565161977,7.415852327
|
||||||
|
0.7579054104,7.419065607
|
||||||
|
0.7592946231,7.422278886
|
||||||
|
0.7606838358,7.425492166
|
||||||
|
0.7620730486,7.428705445
|
||||||
|
0.7634622613,7.431918725
|
||||||
|
0.764851474,7.435132004
|
||||||
|
0.7662406868,7.438345284
|
||||||
|
0.7676298995,7.441558563
|
||||||
|
0.7690191122,7.444771843
|
||||||
|
0.770408325,7.447985122
|
||||||
|
0.7717975377,7.451198402
|
||||||
|
0.7731867504,7.454411681
|
||||||
|
0.7745759631,7.457624961
|
||||||
|
0.7759651759,7.46083824
|
||||||
|
0.7773543886,7.46405152
|
||||||
|
0.7787436013,7.467264799
|
||||||
|
0.7801328141,7.470478079
|
||||||
|
0.7815220268,7.473691358
|
||||||
|
0.7829112395,7.476904638
|
||||||
|
0.7843004523,7.480117917
|
||||||
|
0.785689665,7.483331197
|
||||||
|
0.7870788777,7.486544476
|
||||||
|
0.7884680905,7.489757756
|
||||||
|
0.7898573032,7.492971035
|
||||||
|
0.7912465159,7.493110679
|
||||||
|
0.7926357286,7.4893604
|
||||||
|
0.7940249414,7.485610121
|
||||||
|
0.7954141541,7.481859841
|
||||||
|
0.7968033668,7.478109562
|
||||||
|
0.7981925796,7.474359282
|
||||||
|
0.7995817923,7.470609003
|
||||||
|
0.800971005,7.466858724
|
||||||
|
0.8023602178,7.463108444
|
||||||
|
0.8037494305,7.459358165
|
||||||
|
0.8051386432,7.455607885
|
||||||
|
0.8065278559,7.451857606
|
||||||
|
0.8079170687,7.454642491
|
||||||
|
0.8093062814,7.461282924
|
||||||
|
0.8106954941,7.467923358
|
||||||
|
0.8120847069,7.474563791
|
||||||
|
0.8134739196,7.481204225
|
||||||
|
0.8148631323,7.487844658
|
||||||
|
0.8162523451,7.494485092
|
||||||
|
0.8176415578,7.501125525
|
||||||
|
0.8190307705,7.507765959
|
||||||
|
0.8204199832,7.514406392
|
||||||
|
0.821809196,7.521046826
|
||||||
|
0.8231984087,7.527687259
|
||||||
|
0.8245876214,7.534327693
|
||||||
|
0.8259768342,7.540968126
|
||||||
|
0.8273660469,7.54760856
|
||||||
|
0.8287552596,7.554248993
|
||||||
|
0.8301444724,7.560889427
|
||||||
|
0.8315336851,7.567529861
|
||||||
|
0.8329228978,7.574170294
|
||||||
|
0.8343121106,7.580810728
|
||||||
|
0.8357013233,7.587451161
|
||||||
|
0.837090536,7.594091595
|
||||||
|
0.8384797487,7.600732028
|
||||||
|
0.8398689615,7.607372462
|
||||||
|
0.8412581742,7.614012895
|
||||||
|
0.8426473869,7.620653329
|
||||||
|
0.8440365997,7.627293762
|
||||||
|
0.8454258124,7.633934196
|
||||||
|
0.8468150251,7.640574629
|
||||||
|
0.8482042379,7.647215063
|
||||||
|
0.8495934506,7.653855496
|
||||||
|
0.8509826633,7.653807084
|
||||||
|
0.852371876,7.645603621
|
||||||
|
0.8537610888,7.637400158
|
||||||
|
0.8551503015,7.629196695
|
||||||
|
0.8565395142,7.620993232
|
||||||
|
0.857928727,7.612789769
|
||||||
|
0.8593179397,7.604690194
|
||||||
|
0.8607071524,7.609289208
|
||||||
|
0.8620963652,7.613888221
|
||||||
|
0.8634855779,7.618487234
|
||||||
|
0.8648747906,7.623086248
|
||||||
|
0.8662640034,7.627685261
|
||||||
|
0.8676532161,7.632284274
|
||||||
|
0.8690424288,7.636883288
|
||||||
|
0.8704316415,7.641482301
|
||||||
|
0.8718208543,7.646081314
|
||||||
|
0.873210067,7.650680328
|
||||||
|
0.8745992797,7.655279341
|
||||||
|
0.8759884925,7.659878354
|
||||||
|
0.8773777052,7.664477367
|
||||||
|
0.8787669179,7.669076381
|
||||||
|
0.8801561307,7.673675394
|
||||||
|
0.8815453434,7.678274407
|
||||||
|
0.8829345561,7.682873421
|
||||||
|
0.8843237688,7.687472434
|
||||||
|
0.8857129816,7.692071447
|
||||||
|
0.8871021943,7.696670461
|
||||||
|
0.888491407,7.701269474
|
||||||
|
0.8898806198,7.705868487
|
||||||
|
0.8912698325,7.7104675
|
||||||
|
0.8926590452,7.715066514
|
||||||
|
0.894048258,7.719665527
|
||||||
|
0.8954374707,7.72426454
|
||||||
|
0.8968266834,7.708663797
|
||||||
|
0.8982158961,7.689747699
|
||||||
|
0.8996051089,7.670831601
|
||||||
|
0.9009943216,7.651915503
|
||||||
|
0.9023835343,7.648634256
|
||||||
|
0.9037727471,7.652417362
|
||||||
|
0.9051619598,7.656200468
|
||||||
|
0.9065511725,7.659983574
|
||||||
|
0.9079403853,7.66376668
|
||||||
|
0.909329598,7.667549785
|
||||||
|
0.9107188107,7.671332891
|
||||||
|
0.9121080235,7.675115997
|
||||||
|
0.9134972362,7.678899103
|
||||||
|
0.9148864489,7.682682209
|
||||||
|
0.9162756616,7.686465314
|
||||||
|
0.9176648744,7.69024842
|
||||||
|
0.9190540871,7.694031526
|
||||||
|
0.9204432998,7.697814632
|
||||||
|
0.9218325126,7.701597738
|
||||||
|
0.9232217253,7.705380843
|
||||||
|
0.924610938,7.709163949
|
||||||
|
0.9260001508,7.712947055
|
||||||
|
0.9273893635,7.716730161
|
||||||
|
0.9287785762,7.712515836
|
||||||
|
0.9301677889,7.68932668
|
||||||
|
0.9315570017,7.666137524
|
||||||
|
0.9329462144,7.647766978
|
||||||
|
0.9343354271,7.661181255
|
||||||
|
0.9357246399,7.674595531
|
||||||
|
0.9371138526,7.688009808
|
||||||
|
0.9385030653,7.701424084
|
||||||
|
0.9398922781,7.714838361
|
||||||
|
0.9412814908,7.728252637
|
||||||
|
0.9426707035,7.741666914
|
||||||
|
0.9440599162,7.75508119
|
||||||
|
0.945449129,7.768495467
|
||||||
|
0.9468383417,7.781909743
|
||||||
|
0.9482275544,7.79532402
|
||||||
|
0.9496167672,7.808738296
|
||||||
|
0.9510059799,7.822152572
|
||||||
|
0.9523951926,7.835566849
|
||||||
|
0.9537844054,7.82423082
|
||||||
|
0.9551736181,7.787989163
|
||||||
|
0.9565628308,7.801358196
|
||||||
|
0.9579520436,7.814727229
|
||||||
|
0.9593412563,7.828096263
|
||||||
|
0.960730469,7.841465296
|
||||||
|
0.9621196817,7.85483433
|
||||||
|
0.9635088945,7.868203363
|
||||||
|
0.9648981072,7.881572397
|
||||||
|
0.9662873199,7.89494143
|
||||||
|
0.9676765327,7.908310464
|
||||||
|
0.9690657454,7.921679497
|
||||||
|
0.9704549581,7.898323164
|
||||||
|
0.9718441709,7.896911546
|
||||||
|
0.9732333836,7.895499928
|
||||||
|
0.9746225963,7.894088311
|
||||||
|
0.976011809,7.892676693
|
||||||
|
0.9774010218,7.891265075
|
||||||
|
0.9787902345,7.889853457
|
||||||
|
0.9801794472,7.888441839
|
||||||
|
0.98156866,7.824207805
|
||||||
|
0.9829578727,7.864499773
|
||||||
|
0.9843470854,7.904791741
|
||||||
|
0.9857362982,7.945083709
|
||||||
|
0.9871255109,7.985375677
|
||||||
|
0.9885147236,7.932516304
|
||||||
|
0.9899039363,7.872849328
|
||||||
|
0.9912931491,7.813182351
|
||||||
|
0.9926823618,7.750636227
|
||||||
|
0.9940715745,7.986447804
|
||||||
|
0.9954607873,8.12093709
|
||||||
|
0.99685,7.761658031
|
||||||
|
+11
-8
@@ -1,10 +1,13 @@
|
|||||||
L,d,legit_ser,eve_ser,mask_xcorr,oma
|
L,d,legit_ser,eve_ser,mask_xcorr,oma
|
||||||
4,16,0.9997925,0.999963,0.01188752614,0.2747696909
|
4,16,0.9997925,0.999963,0.01188752614,0.961963405
|
||||||
6,24,0.9921175,0.9996935,0.09415384382,0.2747696909
|
6,24,0.9921175,0.9996935,0.09415384382,nan
|
||||||
8,32,0.9297855,0.999972,0.007307400461,0.2747696909
|
8,32,0.9297855,0.999972,0.007307400461,0.4769767714
|
||||||
12,48,0.416604,0.999781,0.005153660662,0.2747696909
|
10,40,0.6965535,0.9999585,0.006223429926,nan
|
||||||
|
12,48,0.416604,0.999781,0.005153660662,nan
|
||||||
|
14,56,0.3323575,0.999695,0.005685989745,nan
|
||||||
16,64,0.2762895,0.9999285,0.007116591092,0.2747696909
|
16,64,0.2762895,0.9999285,0.007116591092,0.2747696909
|
||||||
24,96,0.1829615,0.999975,0.002973971656,0.2747696909
|
20,80,0.2076175,0.9996245,0.003162040841,0.2289444229
|
||||||
32,128,0.131901,0.9998895,0.005575809628,0.2747696909
|
24,96,0.1829615,0.999975,0.002973971656,0.1961714033
|
||||||
48,192,0.090206,0.999845,0.005743456539,0.2747696909
|
32,128,0.131901,0.9998895,0.005575809628,0.1524639978
|
||||||
64,256,0.0635265,0.9997915,0.006678360514,0.2747696909
|
48,192,0.090206,0.999845,0.005743456539,0.1054308944
|
||||||
|
64,256,0.0635265,0.9997915,0.006678360514,0.08056383667
|
||||||
|
|||||||
|
+13
-10
@@ -1,11 +1,14 @@
|
|||||||
frac,ser_mask,ser_perm,ser_pad
|
frac,ser_mask,ser_perm,ser_pad
|
||||||
0,0.99998375,0.9999833333,0.9999847412
|
0,0.9999779167,0.9999883333,0.9999893771
|
||||||
0.2,0.9997954167,0.9996233333,0.999859778
|
0.2,0.9997833333,0.9995633333,0.9999023796
|
||||||
0.4,0.9985329167,0.9949383333,0.9987114181
|
0.4,0.9984945833,0.9950233333,0.9991029086
|
||||||
0.6,0.99153625,0.9540933333,0.9881584643
|
0.6,0.9915358333,0.9543866667,0.9917561005
|
||||||
0.75,0.9644704167,0.880335,0.9375
|
0.75,0.9643629167,0.8802716667,0.9564884375
|
||||||
0.85,0.88742875,0.7279433333,0.8105354292
|
0.85,0.8872583333,0.727755,0.8680976078
|
||||||
0.9,0.7780379167,0.5716733333,0.6701230223
|
0.9,0.7787070833,0.5709983333,0.7703445963
|
||||||
0.94,0.6256225,0.4316466667,0.4859430867
|
0.92,0.7202866667,0.502115,0.7133141438
|
||||||
0.97,0.43810125,0.3708666667,0.283022376
|
0.94,0.6145370833,0.4848966667,0.6421212878
|
||||||
1,0.2756983333,0.3021566667,0
|
0.955,0.52254125,0.4416266667,0.5773478672
|
||||||
|
0.97,0.4333570833,0.3520316667,0.5008509328
|
||||||
|
0.985,0.3505958333,0.30335,0.4105086147
|
||||||
|
1,0.2758220833,0.303165,0.303815
|
||||||
|
|||||||
|
+11
-6
@@ -1,7 +1,12 @@
|
|||||||
snr_db,legit,eve_wrong,eve_none,eve_public,oma,chance
|
snr_db,legit,eve_wrong,eve_none,eve_public,oma,chance
|
||||||
0,0.8944596875,0.9999515625,0.999990625,0.8943865625,0.8933480658,0.9999847412
|
0,0.8944596875,0.9999621875,0.999988125,0.8945528125,0.8933480658,0.9999847412
|
||||||
4,0.66980875,0.9999575,0.99998625,0.670624375,0.6686275787,0.9999847412
|
2,0.79877875,0.9999559375,0.999986875,0.798940625,0.7973276257,0.9999847412
|
||||||
8,0.39054875,0.9999425,0.9999865625,0.3902384375,0.3892153151,0.9999847412
|
4,0.6697890625,0.9999446875,0.99998625,0.6702265625,0.6686275787,0.9999847412
|
||||||
12,0.1880821875,0.9999284375,0.9999890625,0.1877565625,0.1870712987,0.9999847412
|
6,0.5269903125,0.999944375,0.99999125,0.5272609375,0.525415822,0.9999847412
|
||||||
16,0.0811665625,0.9999203125,0.9999875,0.081488125,0.08092517452,0.9999847412
|
8,0.39087,0.9999415625,0.999988125,0.39062875,0.3892153151,0.9999847412
|
||||||
20,0.033519375,0.999921875,0.999988125,0.0334946875,0.03334949917,0.9999847412
|
10,0.2760796875,0.9999296875,0.999986875,0.2753090625,0.2747696909,0.9999847412
|
||||||
|
12,0.1876809375,0.9999203125,0.9999884375,0.1878209375,0.1870712987,0.9999847412
|
||||||
|
14,0.1250471875,0.999920625,0.9999896875,0.12470875,0.1241256148,0.9999847412
|
||||||
|
16,0.0812634375,0.9999153125,0.999985625,0.08121,0.08092517452,0.9999847412
|
||||||
|
18,0.05243375,0.9999090625,0.9999865625,0.0526228125,0.05214810026,0.9999847412
|
||||||
|
20,0.0335228125,0.9999196875,0.999988125,0.033636875,0.03334949917,0.9999847412
|
||||||
|
|||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
check,claim,empirical,abs_err,tol,verdict
|
||||||
|
V1 legit self-alignment,1.0,0.9994724071424732,0.000527592857526793,0.02,PASS
|
||||||
|
V2a eve mean advantage,0.0,0.0006469982936097643,0.0006469982936097643,0.003,PASS
|
||||||
|
V2b eve SER @ 0dB,0.99609375,0.995,0.0010937500000000044,0.015,PASS
|
||||||
|
V2b eve SER @ 10dB,0.99609375,0.9888333333333333,0.007260416666666658,0.015,PASS
|
||||||
|
V2b eve SER @ 20dB,0.99609375,0.9881666666666666,0.007927083333333362,0.015,PASS
|
||||||
|
V2b eve SER @ 80dB,0.99609375,0.9896666666666667,0.0064270833333333055,0.015,PASS
|
||||||
|
V3b random mask E|corr|,0.09973557010035818,0.10187042771408686,0.002134857613728683,0.009973557010035819,PASS
|
||||||
|
V4a blind jammer projection mean,0.0,0.00026396107284673695,0.00026396107284673695,0.003,PASS
|
||||||
|
V4b blind jammer projection variance,0.01558576233568703,0.015476787953278994,0.00010897438240803532,0.0007792881167843516,PASS
|
||||||
|
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.
Reference in New Issue
Block a user