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:
@@ -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()
|
||||
Reference in New Issue
Block a user