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")
+7
View File
@@ -0,0 +1,7 @@
snr_db,n_frames,entry_recovery,eve_ser
10.0,300,0.9023,0.2583
10.0,1000,0.8047,0.6036
10.0,10000,0.8477,0.4613
20.0,300,0.7852,0.6494
20.0,1000,0.7734,0.7093
20.0,10000,0.8164,0.4676
1 snr_db n_frames entry_recovery eve_ser
2 10.0 300 0.9023 0.2583
3 10.0 1000 0.8047 0.6036
4 10.0 10000 0.8477 0.4613
5 20.0 300 0.7852 0.6494
6 20.0 1000 0.7734 0.7093
7 20.0 10000 0.8164 0.4676
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.