Structured key family as the main configuration
Unconstrained key training converged to disjoint sparse supports: 99 percent of each users key energy sat on three or four of the sixteen entries, with pairwise disjoint supports and one numerically dead codebook column. That is an orthogonal slot allocation, so the superposition collapsed into OMA and the key space was far smaller than the dense direction the brute-force study assumes. The main configuration is now the structured Walsh-Hadamard family, which is dense, exactly orthogonal, unit modulus, and already the best family in the key-family table. base_keys generalizes to any key length by truncating the next power-of-two Sylvester order, and the key-length sweep keeps only lengths where the truncated rows stay exactly orthogonal, verified numerically. Also fixes the M-PAM energy normalization in oma_ser_keylen, which used sqrt(6g/(M^2-1)) where unit average symbol energy gives A^2=3/(M^2-1); the closed form was 3 dB optimistic and now reproduces a direct Monte Carlo to 1e-5. Results move accordingly: the proposal now stays below OMA at every SNR and reaches 1.52x at key length 64, while the jamming margin falls to 5.5-6.3 dB and the brute-force curve to 0.59 at a million guesses.
This commit is contained in:
+73
-26
@@ -1,14 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Final consistency check: every headline number vs its raw CSV."""
|
||||
"""Final consistency check: every headline number vs its raw CSV.
|
||||
|
||||
A quoted value that goes stale during a revision is the failure mode this
|
||||
guards against, so each assertion recomputes from data/ rather than from
|
||||
another quoted value. The manuscript-side assertions are skipped when
|
||||
main.tex is absent, which is the case in the reproducibility package.
|
||||
"""
|
||||
import csv
|
||||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
base = Path(__file__).resolve().parents[1]
|
||||
# The manuscript is not part of the reproducibility package, so the
|
||||
# tex-side assertions are skipped when it is absent and the data-side
|
||||
# assertions still run.
|
||||
_tex_path = base / "main.tex"
|
||||
HAVE_TEX = _tex_path.exists()
|
||||
tex = _tex_path.read_text(encoding="utf-8") if HAVE_TEX else ""
|
||||
@@ -38,56 +41,100 @@ def chk(label, cond, detail, needs_tex=False):
|
||||
|
||||
print("headline numbers vs raw data")
|
||||
|
||||
# 1.27x key-length ratio
|
||||
# --- Fig. 2: the proposal is below OMA -------------------------------
|
||||
sn = rows("sec_snr.csv")
|
||||
lg = [float(x["legit"]) for x in sn]
|
||||
om = [float(x["oma"]) for x in sn]
|
||||
rel = [(a - b) / b * 100 for a, b in zip(lg, om)]
|
||||
chk("legit below OMA at every SNR", max(rel) < 0, "max relative %+.2f%%" % max(rel))
|
||||
chk("gain 1.3 to 7.9 percent",
|
||||
round(-max(rel), 1) == 1.3 and round(-min(rel), 1) == 7.9,
|
||||
"%.2f to %.2f percent" % (-max(rel), -min(rel)))
|
||||
chk("1.3 and 7.9 in tex", "$1.3$ to\n$7.9$~percent" in tex or "$1.3$ to $7.9$~percent" in tex,
|
||||
"searched tex", needs_tex=True)
|
||||
ew = [float(x["eve_wrong"]) for x in sn]
|
||||
ch = float(sn[0]["chance"])
|
||||
chk("outsider at chance to 2e-5", max(abs(x - ch) for x in ew) < 2e-5,
|
||||
"max deviation %.2e" % max(abs(x - ch) for x in ew))
|
||||
|
||||
# --- Fig. 3: key-length ratio ----------------------------------------
|
||||
k = rows("sec_keylen.csv")
|
||||
r64 = [x for x in k if int(x["L"]) == 64][0]
|
||||
ratio = float(r64["oma"]) / float(r64["legit_ser"])
|
||||
chk("key-length ratio 1.27", round(ratio, 2) == 1.27, "%.4f" % ratio)
|
||||
chk("1.27 in tex", tex.count("1.27") >= 2, "%d occurrences" % tex.count("1.27"), needs_tex=True)
|
||||
chk("key-length ratio 1.52", round(ratio, 2) == 1.52, "%.4f" % ratio)
|
||||
chk("1.52 in tex", tex.count("1.52") >= 2, "%d occurrences" % tex.count("1.52"),
|
||||
needs_tex=True)
|
||||
chk("keys exactly orthogonal in the sweep",
|
||||
max(float(x["mask_xcorr"]) for x in k) < 1e-6,
|
||||
"max xcorr %.2e" % max(float(x["mask_xcorr"]) for x in k))
|
||||
|
||||
# blind-jammer gap
|
||||
# --- Fig. 4: jamming --------------------------------------------------
|
||||
g = col("sec_jam_gap.csv", "gap_db")
|
||||
chk("gap 7.4-8.1 dB", round(min(g), 1) == 7.4 and round(max(g), 1) == 8.1,
|
||||
chk("gap 5.5-6.3 dB", round(min(g), 1) == 5.5 and round(max(g), 1) == 6.3,
|
||||
"%.3f to %.3f" % (min(g), max(g)))
|
||||
chk("no stale 8.5 dB", "8.5$~dB" not in tex, "searched tex", needs_tex=True)
|
||||
lin = (10 ** (min(g) / 10), 10 ** (max(g) / 10))
|
||||
chk("about six times power", lin[0] < 6.5 and lin[1] > 5.5,
|
||||
chk("about four times power", lin[0] < 4.5 and lin[1] > 3.4,
|
||||
"%.2f to %.2f" % lin)
|
||||
|
||||
# blind vs permutation
|
||||
j = rows("sec_jam_cmp.csv")
|
||||
dmax = max(abs(float(r["blind"]) - float(r["perm_blind"])) for r in j)
|
||||
chk("within 0.002", dmax <= 0.002, "%.5f" % dmax)
|
||||
chk("no stale 0.0015 in jamming", "$0.0015$ of the proposed" not in tex, "ok", needs_tex=True)
|
||||
chk("no stale 8.1 dB", "$8.1$~dB" not in tex, "searched tex", needs_tex=True)
|
||||
|
||||
# brute force
|
||||
# --- Fig. 6: brute force ---------------------------------------------
|
||||
b = rows("sec_brute_cmp.csv")
|
||||
sm = float(b[-1]["ser_mask"])
|
||||
chk("brute 0.76 both places", tex.count("$0.76$") >= 2, "%.4f measured" % sm, needs_tex=True)
|
||||
chk("no stale 0.75 in summary",
|
||||
"$0.75$ after $10^{6}$" not in tex, "summary row", needs_tex=True)
|
||||
chk("brute 0.59 at 1e6", round(sm, 2) == 0.59, "%.4f" % sm)
|
||||
chk("0.59 in tex", "$0.59$" in tex, "searched tex", needs_tex=True)
|
||||
pad0 = next((x["K"] for x in b if float(x["ser_pad"]) < 0.27), None)
|
||||
chk("index cipher collapses at 65536", pad0 == "65536", str(pad0))
|
||||
|
||||
# refresh
|
||||
rs = {r["scheme"]: r for r in rows("refresh_summary.csv")}
|
||||
# --- Fig. 7: known plaintext -----------------------------------------
|
||||
kp = rows("kpa.csv")
|
||||
legit = float([x for x in k if int(x["L"]) == 16][0]["legit_ser"])
|
||||
thr = legit * 1.02
|
||||
first20 = next((x["n_frames"] for x in kp
|
||||
if int(x["snr_db"]) == 20 and float(x["eve_ser"]) <= thr), None)
|
||||
first10 = next((x["n_frames"] for x in kp
|
||||
if int(x["snr_db"]) == 10 and float(x["eve_ser"]) <= thr), None)
|
||||
chk("KPA five frames at 20 dB", first20 == "5", "first N = %s" % first20)
|
||||
chk("KPA twenty-four frames at 10 dB", first10 == "24", "first N = %s" % first10)
|
||||
pk = rows("pkpa.csv")
|
||||
p6 = float([x for x in pk if x["n_frames"] == "6"][0]["eve_ser"])
|
||||
chk("perm KPA at N=6 near its own 0.258", abs(p6 - 0.258) < 0.005, "%.4f" % p6)
|
||||
|
||||
# --- refresh ----------------------------------------------------------
|
||||
rs = {x["scheme"]: x for x in rows("refresh_summary.csv")}
|
||||
chk("refresh 64.8 bits",
|
||||
round(float(rs["Invariant"]["entropy_bits"]), 1) == 64.8,
|
||||
"%.3f" % float(rs["Invariant"]["entropy_bits"]))
|
||||
chk("fixed key 15.0 bits",
|
||||
round(float(rs["None (fixed key)"]["entropy_bits"]), 1) == 15.0,
|
||||
"%.4f" % float(rs["None (fixed key)"]["entropy_bits"]))
|
||||
chk("invariant refresh free",
|
||||
abs(float(rs["Invariant"]["legit"]) - float(rs["None (fixed key)"]["legit"]))
|
||||
< 0.001, "%.4f vs %.4f" % (float(rs["Invariant"]["legit"]),
|
||||
float(rs["None (fixed key)"]["legit"])))
|
||||
|
||||
# permutation KPA
|
||||
pk = rows("pkpa.csv")
|
||||
p6 = float([r for r in pk if r["n_frames"] == "6"][0]["eve_ser"])
|
||||
chk("perm KPA at N=6 near 0.303", abs(p6 - 0.303) < 0.005, "%.4f" % p6)
|
||||
# --- real tokens ------------------------------------------------------
|
||||
import json
|
||||
st = json.loads((base / "data" / "real_sec_stats.json").read_text())
|
||||
rec = st["recovery"]["28"]
|
||||
chk("headline recovery 78 vs 76 percent",
|
||||
round(rec["legit"] * 100) == 78 and round(rec["oma"] * 100) == 76,
|
||||
"%.1f vs %.1f" % (rec["legit"] * 100, rec["oma"] * 100))
|
||||
chk("legit leads OMA at every point",
|
||||
all(st["recovery"][s]["legit"] > st["recovery"][s]["oma"]
|
||||
for s in st["recovery"]),
|
||||
"checked %d points" % len(st["recovery"]))
|
||||
|
||||
# abstract
|
||||
# --- abstract ---------------------------------------------------------
|
||||
a = (tex.split(r"\begin{abstract}")[1].split(r"\end{abstract}")[0].strip()
|
||||
if HAVE_TEX else "")
|
||||
w = len(re.split(r"\s+", a)) if a else 0
|
||||
chk("abstract <= 250 words", w <= 250, "%d words" % w, needs_tex=True)
|
||||
chk("abstract has no abbreviations",
|
||||
not re.findall(r"\b[A-Z]{2,}\b", a), str(re.findall(r"\b[A-Z]{2,}\b", a)), needs_tex=True)
|
||||
not re.findall(r"\b[A-Z]{2,}\b", a), str(re.findall(r"\b[A-Z]{2,}\b", a)),
|
||||
needs_tex=True)
|
||||
|
||||
print()
|
||||
print("ALL CONSISTENT" if ok else "INCONSISTENCIES FOUND")
|
||||
|
||||
Reference in New Issue
Block a user