Covariance attack at the main configuration, stored to data/

The ciphertext-only check trained its own L=16 model and printed only a
verdict; it now attacks the main configuration and writes
data/cov_attack.csv, which the manuscript cites.
This commit is contained in:
KiHoLee
2026-08-19 17:32:28 +09:00
parent f378da511c
commit 55e417276d
10 changed files with 27 additions and 11 deletions
+15 -6
View File
@@ -18,16 +18,18 @@ Procedure, using nothing the threat model keeps secret:
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/.
Run under WSL. Writes data/cov_attack.csv so the manuscript sentence
it supports is traceable to a stored artifact.
"""
from __future__ import annotations
import itertools
import math
from pathlib import Path
import numpy as np
import torch
from sse_lib import rayleigh_gain, DEVICE
from exp_full import get_model, hadamard, eval_ser_eve
from exp_full import main_model, hadamard, eval_ser_eve
def collect_frames(m, n, snr_db, seed):
@@ -94,18 +96,25 @@ def attack(m, snr_db, n_frames, seed):
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)
import csv
m = main_model()
m.eval()
chance = 1.0 - (1.0 / m.vu) ** m.P
print(f"chance SER = {chance:.5f}, legitimate reference ~0.276")
print(f"chance SER = {chance:.5f} at the main configuration")
print("ciphertext-only (NO known plaintext):")
rows = []
for snr in (10.0, 20.0):
for nf in (300, 1000, 10000):
frac, ser = attack(m, snr, nf, seed=1234 + nf)
rows.append((snr, nf, "%.4f" % frac, "%.4f" % ser))
print(f" {snr:4.0f} dB N={nf:6d} "
f"key-entry recovery={frac:.3f} eve SER={ser:.4f}")
out = Path(__file__).resolve().parents[1] / "data" / "cov_attack.csv"
with open(out, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["snr_db", "n_frames", "entry_recovery", "eve_ser"])
w.writerows(rows)
print("[csv]", out)
if __name__ == "__main__":
+5 -5
View File
@@ -34,10 +34,10 @@ 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.70 of a 3.455 in
# column while the canvas is 3.15 in, a printed scale of 0.768. Every
# The manuscript includes each result figure at 0.62 of a 3.455 in
# column while the canvas is 3.15 in, a printed scale of 0.680. 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.5 pt legend. Change the
# include width and these must change with it.
"font.size": 10.4,
"axes.labelsize": 10.4,
@@ -50,7 +50,7 @@ plt.rcParams.update({
"grid.alpha": 0.6,
"lines.linewidth": 1.5,
"lines.markersize": 5.2,
"figure.figsize": (3.15, 2.36),
"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)
@@ -283,7 +283,7 @@ def fig_snr():
ax.set_xlim(min(x), max(x))
# most of a decade below the data leaves the lower-left genuinely
# empty, which is what gives the four-entry legend a clear berth
ax.set_ylim(bottom=8e-4)
ax.set_ylim(bottom=2e-4)
place_legend(ax)
save(fig, "fig_sec_snr")