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 6. report the recovered-entry fraction and the eavesdropper SER, both
WITHOUT ever using a known index 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 from __future__ import annotations
import itertools import itertools
import math import math
from pathlib import Path
import numpy as np import numpy as np
import torch import torch
from sse_lib import rayleigh_gain, DEVICE 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): def collect_frames(m, n, snr_db, seed):
@@ -94,18 +96,25 @@ def attack(m, snr_db, n_frames, seed):
def main(): def main():
U, L = 4, 16 import csv
K0 = torch.tensor(hadamard(L)[1:U + 1], dtype=torch.float32) m = main_model()
m = get_model(iters=4000, freeze_W=K0)
m.eval() m.eval()
chance = 1.0 - (1.0 / m.vu) ** m.P 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):") print("ciphertext-only (NO known plaintext):")
rows = []
for snr in (10.0, 20.0): for snr in (10.0, 20.0):
for nf in (300, 1000, 10000): for nf in (300, 1000, 10000):
frac, ser = attack(m, snr, nf, seed=1234 + nf) 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} " print(f" {snr:4.0f} dB N={nf:6d} "
f"key-entry recovery={frac:.3f} eve SER={ser:.4f}") 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__": if __name__ == "__main__":
+5 -5
View File
@@ -34,10 +34,10 @@ FIG.mkdir(exist_ok=True)
plt.rcParams.update({ plt.rcParams.update({
"font.family": "serif", "font.family": "serif",
"font.serif": ["DejaVu Serif", "Times New Roman"], "font.serif": ["DejaVu Serif", "Times New Roman"],
# The manuscript includes each result figure at 0.70 of a 3.455 in # 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.768. Every # 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 # 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. # include width and these must change with it.
"font.size": 10.4, "font.size": 10.4,
"axes.labelsize": 10.4, "axes.labelsize": 10.4,
@@ -50,7 +50,7 @@ plt.rcParams.update({
"grid.alpha": 0.6, "grid.alpha": 0.6,
"lines.linewidth": 1.5, "lines.linewidth": 1.5,
"lines.markersize": 5.2, "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, "pdf.fonttype": 42,
}) })
AXES_RECT = dict(left=0.205, right=0.970, top=0.955, bottom=0.215) 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)) ax.set_xlim(min(x), max(x))
# most of a decade below the data leaves the lower-left genuinely # most of a decade below the data leaves the lower-left genuinely
# empty, which is what gives the four-entry legend a clear berth # 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) place_legend(ax)
save(fig, "fig_sec_snr") 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.