Ciphertext-only family enumeration, and checks that reproduce off a GPU

check_family_enum.py measures the attack the manuscript now states in
Section III-A: the winning correlation is an index-free verifier, so
ranking the 63 non-constant Walsh rows by mean winning correlation
recovers the user set from one frame in 0.905 of 200 trials at 10 dB
and from four frames in 0.990, using nothing outside the stated threat
model. Under the invariance refresh it recovers it in none, because the
entry permutation relabels the codebook the adversary must align
against.

V8 and V9 read the trained codebook through main_model(), which
retrains on every call, and a codebook trained on CUDA is not the one
trained on CPU. The shipped verify_math.csv therefore read PASS here
and FAIL for anyone running this package without a GPU. model_main.pt
is 7 KB and fixes the codebook, which is what both checks are about;
delete it to retrain. V1-V11 now pass on both.

New checks: V10, the format-matched OMA reference Section VI-B quotes,
and V11, the closed-form against Monte Carlo comparison the manuscript
claimed and never stored. V3a's bias-linearity result was computed and
printed but never written to the CSV, so the one linearity claim the
paper quotes was the one this package could not show.

check_consistency.py gains 21 assertions, covering five data files that
no assertion read (users, csi, semantic, cov_attack, sec_jam) and the
trend claims it structurally could not see, since it compared values
and not shapes.

README: the figure map named stages that do not write the artifacts
they list, so following it did not reproduce Figs. 4 and 6; the
reproduction block was five scripts short; and the refresh numbers were
from a superseded run (nearly three, 15.0 to 64.8 bits) against the
manuscript's 2.3 and 23.8 to 364.6.
This commit is contained in:
KiHoLee
2026-08-28 17:40:28 +09:00
parent 8dd70a1776
commit 17d23fa76a
9 changed files with 328 additions and 10 deletions
+11 -5
View File
@@ -31,7 +31,7 @@ code/
exp_permkpa.py permutation-key known-plaintext attack (Fig. 7) exp_permkpa.py permutation-key known-plaintext attack (Fig. 7)
check_cov_*.py ciphertext-only covariance-attack checks (referee M1) check_cov_*.py ciphertext-only covariance-attack checks (referee M1)
exp_real_sec.py stage G: real BERT WordPiece token streams exp_real_sec.py stage G: real BERT WordPiece token streams
verify_math.py closed-form checks V1-V5, PASS/FAIL and verify_math.csv verify_math.py closed-form checks V1-V11, PASS/FAIL and verify_math.csv
replot_security.py every result figure, from data/ to fig/ replot_security.py every result figure, from data/ to fig/
make_tables.py LaTeX rows of every result table, from data/ make_tables.py LaTeX rows of every result table, from data/
feasibility_security.py early CPU-sized study, kept for the record feasibility_security.py early CPU-sized study, kept for the record
@@ -53,6 +53,12 @@ python exp_full.py # stages A-F and L
python exp_kpa.py # known-plaintext attack python exp_kpa.py # known-plaintext attack
python exp_refresh.py # the key-refresh layer python exp_refresh.py # the key-refresh layer
python exp_real_sec.py # real token streams python exp_real_sec.py # real token streams
python exp_permkpa.py # permutation-key known plaintext
python exp_infotheory.py # mutual information and equivocation
python exp_semantic.py # semantic-similarity leakage
python exp_users_csi.py # load and channel-estimate sweeps
python check_cov_attack.py # ciphertext-only covariance attack
python check_family_enum.py # ciphertext-only enumeration of the key family
python replot_security.py # all figures from the CSVs python replot_security.py # all figures from the CSVs
python make_tables.py # LaTeX rows of the result tables python make_tables.py # LaTeX rows of the result tables
``` ```
@@ -77,9 +83,9 @@ Logarithms in an entropy or an information rate are base two.
|---|---|---| |---|---|---|
| Fig. 2 SER against SNR | `exp_full.stage_A` | `sec_snr.csv` | | Fig. 2 SER against SNR | `exp_full.stage_A` | `sec_snr.csv` |
| Fig. 3 key length | `exp_full.stage_B` | `sec_keylen.csv` | | Fig. 3 key length | `exp_full.stage_B` | `sec_keylen.csv` |
| Fig. 4 jamming (4 schemes) | `exp_full.stage_L` | `sec_jam_cmp.csv`, `sec_jam.csv` | | Fig. 4 jamming (4 schemes) | `exp_full.stage_C`, `stage_L` | `sec_jam_cmp.csv`, `sec_jam.csv` |
| Fig. 5 key sensitivity | `exp_full.stage_I` | `sec_sens_cmp.csv` | | Fig. 5 key sensitivity | `exp_full.stage_I` | `sec_sens_cmp.csv` |
| Fig. 6 brute-force search | `exp_full.stage_J` | `sec_brute_cmp.csv`, `sec_brute.csv` | | Fig. 6 brute-force search | `exp_full.stage_I`, `stage_F`, `stage_J` | `sec_brute_cmp.csv`, `sec_brute.csv` |
| Fig. 7 known-plaintext attack | `exp_kpa`, `exp_permkpa` | `kpa.csv`, `pkpa.csv` | | Fig. 7 known-plaintext attack | `exp_kpa`, `exp_permkpa` | `kpa.csv`, `pkpa.csv` |
| Fig. 8 real token streams | `exp_real_sec` | `real_sec_ter.csv` | | Fig. 8 real token streams | `exp_real_sec` | `real_sec_ter.csv` |
| Scheme comparison table | `exp_full.stage_E` | `sec_compare.csv` | | Scheme comparison table | `exp_full.stage_E` | `sec_compare.csv` |
@@ -103,8 +109,8 @@ measures. The key must therefore be refreshed per coherence block from a shared
seed. `exp_refresh.py` implements that layer and shows why it has to seed. `exp_refresh.py` implements that layer and shows why it has to
draw from the transformations that leave the decision statistic draw from the transformations that leave the decision statistic
invariant: a refresh that installs fresh orthogonal keys instead costs invariant: a refresh that installs fresh orthogonal keys instead costs
the legitimate users a factor of nearly three, while the invariant the legitimate users a factor of 2.3, while the invariant
refresh costs nothing and raises the per-block key from 15.0 to 64.8 refresh costs nothing and raises the per-block key from 23.8 to 364.6
bits. bits.
## License ## License
+86
View File
@@ -268,6 +268,92 @@ chk("secrecy rate 14.87 of 14.93",
"%s of %s" % (it["secrecy_rate_refresh_bits"], it["mi_legit_bits"])) "%s of %s" % (it["secrecy_rate_refresh_bits"], it["mi_legit_bits"]))
_fe = {(float(r["snr_db"]), int(r["n_frames"]), r["keying"]): float(r["recovery"])
for r in rows("family_enum.csv")}
chk("family enumeration recovers the user set at 10 dB",
abs(_fe[(10.0, 1, "fixed")] - 0.905) < 5e-3
and abs(_fe[(10.0, 4, "fixed")] - 0.990) < 5e-3,
"N=1 %.3f, N=4 %.3f" % (_fe[(10.0, 1, "fixed")],
_fe[(10.0, 4, "fixed")]))
chk("the refresh defeats the family enumeration",
_fe[(10.0, 2, "refreshed")] == 0.0,
"%.3f over 200 blocks" % _fe[(10.0, 2, "refreshed")])
# --- trends, which the value assertions above cannot see ---------------
_snr = rows("sec_snr.csv")
_lg = [float(r["legit"]) for r in _snr]
chk("legitimate SER falls monotonically with SNR",
all(a > b for a, b in zip(_lg, _lg[1:])), "%d points" % len(_lg))
chk("legitimate below the binary OMA reference at every SNR",
all(float(r["legit"]) < float(r["oma"]) for r in _snr),
"min margin %.3f" % min(1 - float(r["legit"]) / float(r["oma"])
for r in _snr))
_kl = rows("sec_keylen.csv")
chk("legitimate SER falls monotonically with key length",
all(float(a["legit_ser"]) > float(b["legit_ser"]) for a, b in zip(_kl, _kl[1:])),
"%d lengths" % len(_kl))
chk("legitimate surpasses the reference from L=16 onward",
all(float(r["legit_ser"]) < float(r["oma"]) for r in _kl
if r["oma"] != "nan" and int(float(r["L"])) >= 16),
"checked L>=16")
_ter = rows("real_sec_ter.csv")
chk("legitimate TER below OMA over the whole range",
all(float(r["ter_legit"]) < float(r["ter_oma"]) for r in _ter),
"%d points" % len(_ter))
chk("outsider TER stays above 0.9991",
min(float(r["ter_eve"]) for r in _ter) > 0.9991,
"min %.6f" % min(float(r["ter_eve"]) for r in _ter))
# --- files no assertion read ------------------------------------------
_us = rows("users.csv")
chk("keys stay exactly orthogonal at every load",
all(float(r["mask_xcorr"]) == 0.0 for r in _us),
"U up to %s" % _us[-1]["users"])
chk("eavesdropper never leaves chance across the load sweep",
all(float(r["eve_ser"]) > 0.999 for r in _us),
"min %.6f" % min(float(r["eve_ser"]) for r in _us))
_u = {r["users"]: r for r in _us}
chk("load endpoints 0.027 and 0.946",
abs(float(_u["2"]["legit_ser"]) - 0.027) < 5e-4
and abs(float(_u["32"]["legit_ser"]) - 0.946) < 5e-4,
"%.4f, %.4f" % (float(_u["2"]["legit_ser"]),
float(_u["32"]["legit_ser"])))
chk("the OMA crossing lies between U=16 and U=32",
float(_u["16"]["legit_ser"]) < float(_u["16"]["oma"])
and float(_u["32"]["legit_ser"]) > float(_u["32"]["oma"]),
"16: %.3f<%.3f, 32: %.3f>%.3f"
% (float(_u["16"]["legit_ser"]), float(_u["16"]["oma"]),
float(_u["32"]["legit_ser"]), float(_u["32"]["oma"])))
_csi = rows("csi.csv")
chk("phase residual moves the rate to 0.057 at 0.2 rad",
any(abs(float(r["legit_ser"]) - 0.057) < 1e-3 for r in _csi),
"%d rows" % len(_csi))
_sem = rows("semantic.csv")
chk("legitimate similarity at least 0.96 in both spaces",
all(float(r["legit"]) >= 0.96 for r in _sem if r["snr_db"] == "10.0"),
"%d rows" % len(_sem))
_cov = rows("cov_attack.csv")
chk("covariance attack reaches 0.26 at 300 same-key frames",
any(r["n_frames"] == "300" and abs(float(r["eve_ser"]) - 0.26) < 0.01
for r in _cov),
"%d rows" % len(_cov))
chk("unjammed reference is 0.053",
abs(col("sec_jam.csv", "nojam")[0] - 0.053) < 0.05, "sec_jam.csv read")
# --- the closed-form checks the manuscript quotes ----------------------
_vm = {r["check"]: r for r in rows("verify_math.csv")}
for _k, _c in [("V8 cross-period remainder", 0.0005),
("V9 score-variance ratio", 0.05),
("V10 format-matched OMA at 10 dB", 0.001),
("V3a bias slope in kappa", 0.03)]:
chk("stored check %s passes" % _k.split()[0],
_k in _vm and _vm[_k]["verdict"] == "PASS"
and float(_vm[_k]["abs_err"]) <= _c,
_vm[_k]["empirical"] if _k in _vm else "row missing")
chk("format-matched OMA quoted as 0.055",
"$0.055$ at $10$~dB against the proposed" in tex, "Section VI-B",
needs_tex=True)
# --- tables against their generator ----------------------------------- # --- tables against their generator -----------------------------------
# Every printed table cell must be the one make_tables.py derives from # Every printed table cell must be the one make_tables.py derives from
# data/, so a rerun that moves a number cannot leave the manuscript behind. # data/, so a rerun that moves a number cannot leave the manuscript behind.
+113
View File
@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
"""Ciphertext-only enumeration of the structured key family.
Section III-A states that the winning correlation is itself an
index-free verifier: with the right key the winning score is of order
1/c, with a wrong key of order 1/sqrt(L). That makes the finite
structured family exhaustible by an adversary that never sees a
transmitted index, which is why the refresh of Section V-C is required
rather than optional. This script is the measurement behind that
claim.
The attack. The threat model grants the adversary the public codebook,
the key family and its distribution, the channel model and the
normalizer, and it uses exactly those. For each of the L-1 non-constant
Walsh-Hadamard rows the adversary de-masks the received frame with that
row and records the mean winning per-digit correlation over N frames,
then keeps the U highest-scoring rows. It reads only the size of the
peak, never which candidate won, so no transmitted index is touched.
It also runs the same attack against a refreshed key. The per-block
sign draw and entry permutation relabel the codebook the adversary
would have to align against, and the attack fails there.
Writes data/family_enum.csv.
"""
from __future__ import annotations
import math
from pathlib import Path
import torch
from exp_full import base_keys, main_model
from sse_lib import DEVICE, rayleigh_gain, snr_to_sigma2, write_csv
DATA = Path(__file__).resolve().parents[1] / "data"
TRIALS = 200
SEED = 8131
@torch.no_grad()
def _observe(m, keys, snr_db, n, g):
"""n superposed frames under the given key set, seen by Eve.
Eve has her own flat-fading gain and knows it, which is the
strongest reading of the threat model.
"""
Bn = m.unit_codebook()
idx = torch.randint(m.vu, (n, m.users, m.P), generator=g, device=DEVICE)
e = Bn[idx] / math.sqrt(m.P) # (n,U,P,L)
y = (e * keys[None, :, None, :]).sum(dim=1) / m.c # (n,P,L)
h = rayleigh_gain((n, 1, 1), device=DEVICE)
sig = float(snr_to_sigma2(torch.tensor(snr_db), m.d).sqrt())
rx = h * y + sig * torch.randn(n, m.P, m.L, generator=g, device=DEVICE)
return rx / h
@torch.no_grad()
def _peak_scores(m, r, cand, Bn):
"""Mean winning per-digit correlation for every candidate row."""
out = torch.empty(cand.shape[0])
for k in range(cand.shape[0]):
z = torch.einsum("npl,vl->npv", r * cand[k][None, None, :], Bn)
out[k] = z.max(dim=2).values.mean()
return out
def run():
torch.manual_seed(SEED)
m = main_model() # trains, so not under no_grad
_attack(m)
@torch.no_grad()
def _attack(m):
keys = m.masks() # (U,L) the true rows
cand = base_keys(m.L - 1, m.L).to(DEVICE) # every non-constant row
Bn = m.unit_codebook() / math.sqrt(m.P)
rows = []
for snr in (0.0, 10.0, 20.0):
for n in (1, 2, 4):
hit = 0
for t in range(TRIALS):
g = torch.Generator(device=DEVICE).manual_seed(
SEED + 1000 * int(snr) + 10 * n + t)
r = _observe(m, keys, snr, n, g)
top = _peak_scores(m, r, cand, Bn).topk(m.users).indices
hit += int(set(int(i) for i in top) == set(range(m.users)))
rows.append((snr, n, "fixed", hit / TRIALS))
print(" %4.0f dB N=%d fixed recovery %.3f"
% (snr, n, hit / TRIALS))
hit = 0
for t in range(TRIALS):
g = torch.Generator(device=DEVICE).manual_seed(SEED + 77 + t)
perm = torch.randperm(m.L, generator=g, device=DEVICE)
sign = torch.randint(2, (m.L,), generator=g,
device=DEVICE) * 2.0 - 1.0
rk = (keys * sign[None, :])[:, perm]
r = _observe(m, rk, 10.0, 2, g)
top = _peak_scores(m, r, cand, Bn).topk(m.users).indices
hit += int(set(int(i) for i in top) == set(range(m.users)))
rows.append((10.0, 2, "refreshed", hit / TRIALS))
print(" 10 dB N=2 refreshed recovery %.3f" % (hit / TRIALS))
write_csv(DATA / "family_enum.csv",
["snr_db", "n_frames", "keying", "recovery"], rows)
print("[csv]", DATA / "family_enum.csv")
if __name__ == "__main__":
run()
+1 -1
View File
@@ -13,7 +13,7 @@ DATA = Path(__file__).resolve().parents[1] / "data"
NAME = { NAME = {
"proposed": r"\textbf{Proposed keyed masking}", "proposed": r"\textbf{Proposed keyed masking}",
"public_mask": "Public masks", "public_mask": "Public masks",
"perm_key": r"Permutation key~\cite{chen2023shuffling}", "perm_key": r"Permutation key~\cite{chen2025shufflingtifs}",
"index_cipher": "Index cipher", "index_cipher": "Index cipher",
"oma_plain": "OMA (no encryption)", "oma_plain": "OMA (no encryption)",
"random": "Random", "random": "Random",
+38
View File
@@ -309,6 +309,44 @@ def oma_ser(snr_db_list, bits: int = 16, n_grid: int = 200_000):
return out return out
def oma_ser_orth(snr_db_list, P: int = 4, vu: int = 16, L: int = 64,
n_h: int = 20_000, n_z: int = 2001):
"""Format-matched OMA reference.
The binary reference of oma_ser_keylen spends 16 of its L exclusive
dimensions on antipodal bits, a one-bit-per-dimension format inside
a log2(V)/L = 0.25 bit-per-dimension budget. The better uncoded use
of the same allocation is the format the proposed scheme itself
uses: P orthogonal decisions among vu candidates, each over L/P
exclusive dimensions, which needs exactly vu = L/P of them and so
fits the allocation with nothing to spare.
Energy accounting matches oma_ser, where one unit of energy on a
dimension gives 2Es/N0 = snr, so an L-dimension user spending its L
units on P symbols puts L/P units in each. Given the fading gain h
the correct matched-filter output is N(h sqrt(Es), N0/2) against
vu-1 outputs N(0, N0/2), so a digit is right with probability
E_z[Phi(z + h sqrt((L/P) snr))^(vu-1)] and the index is right when
all P digits are.
"""
from scipy.special import log_ndtr
x = (np.arange(n_h) + 0.5) / n_h
h = np.sqrt(-np.log(1.0 - x)) # h^2 ~ Exp(1)
z = np.linspace(-8.0, 8.0, n_z)
phi = np.exp(-0.5 * z * z) / math.sqrt(2.0 * math.pi)
out = []
for s in snr_db_list:
a = h * math.sqrt((L / P) * 10.0 ** (s / 10.0))
pc = np.empty_like(a)
for i in range(0, a.size, 2048): # bound the working set
blk = a[i:i + 2048][:, None]
pc[i:i + 2048] = np.trapezoid(
phi * np.exp((vu - 1) * log_ndtr(z[None, :] + blk)),
z, axis=1)
out.append(float(np.mean(1.0 - pc ** P)))
return out
@torch.no_grad() @torch.no_grad()
def oma_ser_mc(snr_db_list, bits: int = 16, frames: int = 2_000_000, def oma_ser_mc(snr_db_list, bits: int = 16, frames: int = 2_000_000,
chunk: int = 200_000, seed: int = 777): chunk: int = 200_000, seed: int = 777):
+65 -4
View File
@@ -18,9 +18,32 @@ Run on CPU (NumPy); no training involved, pure algebra checks.
from __future__ import annotations from __future__ import annotations
import numpy as np import numpy as np
from pathlib import Path
RNG = np.random.default_rng(2026) RNG = np.random.default_rng(2026)
D, U, V = 64, 4, 256 D, U, V = 64, 4, 256
CKPT = Path(__file__).resolve().parent.parent / "data" / "model_main.pt"
def cached_main_model():
"""The trained main-configuration model, from a checkpoint.
V8 and V9 read the trained codebook. Retraining it reproduces only
on the device that trained it, so a CPU run of the released package
disagreed with the shipped numbers. The checkpoint fixes the
codebook, which is what both checks are about; delete it to retrain.
"""
import torch
from exp_full import main_model
m = main_model()
if CKPT.exists():
m.load_state_dict(torch.load(CKPT, map_location="cpu"))
else:
torch.save({k: v.cpu() for k, v in m.state_dict().items()}, CKPT)
return m
def unit_codebook(V, d, rng): def unit_codebook(V, d, rng):
E = rng.standard_normal((V, d)) E = rng.standard_normal((V, d))
@@ -121,6 +144,9 @@ def v3_leakage_vs_correlation():
lin_ok = all(abs(b - rho * b1) <= 3e-2 for rho, b in slopes) lin_ok = all(abs(b - rho * b1) <= 3e-2 for rho, b in slopes)
print(f"[{'PASS' if lin_ok else 'FAIL'}] V3a bias linear in rho: " print(f"[{'PASS' if lin_ok else 'FAIL'}] V3a bias linear in rho: "
+ ", ".join(f"rho={r:.2f}->{b:.3f}" for r, b in slopes)) + ", ".join(f"rho={r:.2f}->{b:.3f}" for r, b in slopes))
ROWS.append(("V3a bias slope in kappa", "1.0", "%.4f" % b1,
"%.4f" % abs(1.0 - b1), "0.03",
"PASS" if lin_ok else "FAIL"))
# (b) random independent mask correlation: E|corr| = sqrt(2/(pi d)) # (b) random independent mask correlation: E|corr| = sqrt(2/(pi d))
# (the folded-normal mean of a N(0, 1/d) variable) # (the folded-normal mean of a N(0, 1/d) variable)
corrs = [] corrs = []
@@ -263,8 +289,7 @@ def v8_cross_period_terms():
the codebook, which is the claim the proof rests on.""" the codebook, which is the claim the proof rests on."""
import math import math
import torch import torch
from exp_full import main_model m = cached_main_model()
m = main_model()
Bn = m.unit_codebook().detach().cpu() Bn = m.unit_codebook().detach().cpu()
pat = m.masks().detach().cpu()[0] pat = m.masks().detach().cpu()[0]
L, P, d = m.L, m.P, m.d L, P, d = m.L, m.P, m.d
@@ -294,8 +319,7 @@ def v9_score_variance_ratio():
of sum_j e_j^4 / sum_j e_j^2 e'_j^2 over ordered codeword pairs of of sum_j e_j^4 / sum_j e_j^2 e'_j^2 over ordered codeword pairs of
the trained unit codebook, quoted as 2.8 in the manuscript.""" the trained unit codebook, quoted as 2.8 in the manuscript."""
import torch import torch
from exp_full import main_model m = cached_main_model()
m = main_model()
B = m.unit_codebook().detach().cpu().double() B = m.unit_codebook().detach().cpu().double()
B = B / B.norm(dim=1, keepdim=True) B = B / B.norm(dim=1, keepdim=True)
n = B.shape[0] n = B.shape[0]
@@ -313,6 +337,41 @@ def v9_score_variance_ratio():
return ok return ok
def v10_format_matched_oma():
"""The format-matched OMA reference of Section VI-B.
The binary reference spends 16 of its 64 exclusive dimensions on
antipodal bits. The same allocation spent the way the proposed
scheme spends it, P=4 sixteen-ary orthogonal decisions over 16
dimensions each, is the comparison a reviewer will ask for."""
from sse_lib import oma_ser_orth
from exp_full import oma_ser_keylen
val = oma_ser_orth([10.0])[0]
binary = oma_ser_keylen(64, 10.0)
ok = abs(val - 0.055) < 0.001
print("V10 format-matched OMA at 10 dB: %.5f (binary %.5f)"
% (val, binary))
ROWS.append(("V10 format-matched OMA at 10 dB", "0.055", "%.5f" % val,
"%.5f" % abs(val - 0.055), "0.001", "PASS" if ok else "FAIL"))
return ok
def v11_oma_closed_form_vs_mc():
"""The manuscript says the OMA closed form agrees with Monte Carlo
to within one percent. That check had no stored artifact."""
from sse_lib import oma_ser, oma_ser_mc
cf = oma_ser([16.0])[0]
mc = oma_ser_mc([16.0], frames=2_000_000)[0]
rel = abs(cf - mc) / mc
ok = rel < 0.01
print("V11 OMA closed form %.6f vs Monte Carlo %.6f (%.2f%%)"
% (cf, mc, 100 * rel))
ROWS.append(("V11 OMA closed form vs Monte Carlo", "%.6f" % mc,
"%.6f" % cf, "%.4f" % rel, "0.01", "PASS" if ok else "FAIL"))
return ok
def main(): def main():
print(f"config d={D} U={U} V={V}\n") print(f"config d={D} U={U} V={V}\n")
results = { results = {
@@ -325,6 +384,8 @@ def main():
"V7": v7_symbolic_identities(), "V7": v7_symbolic_identities(),
"V8": v8_cross_period_terms(), "V8": v8_cross_period_terms(),
"V9": v9_score_variance_ratio(), "V9": v9_score_variance_ratio(),
"V10": v10_format_matched_oma(),
"V11": v11_oma_closed_form_vs_mc(),
} }
print("\nsummary:", {k: ("PASS" if v else "FAIL") for k, v in results.items()}) print("\nsummary:", {k: ("PASS" if v else "FAIL") for k, v in results.items()})
print("ALL PASS" if all(results.values()) else "SOME FAILED") print("ALL PASS" if all(results.values()) else "SOME FAILED")
+11
View File
@@ -0,0 +1,11 @@
snr_db,n_frames,keying,recovery
0,1,fixed,0.425
0,2,fixed,0.46
0,4,fixed,0.645
10,1,fixed,0.905
10,2,fixed,0.95
10,4,fixed,0.99
20,1,fixed,0.985
20,2,fixed,1
20,4,fixed,1
10,2,refreshed,0
1 snr_db n_frames keying recovery
2 0 1 fixed 0.425
3 0 2 fixed 0.46
4 0 4 fixed 0.645
5 10 1 fixed 0.905
6 10 2 fixed 0.95
7 10 4 fixed 0.99
8 20 1 fixed 0.985
9 20 2 fixed 1
10 20 4 fixed 1
11 10 2 refreshed 0
Binary file not shown.
+3
View File
@@ -5,6 +5,7 @@ V2b eve SER @ 0dB,0.99609375,0.995,0.0010937500000000044,0.015,PASS
V2b eve SER @ 10dB,0.99609375,0.9888333333333333,0.007260416666666658,0.015,PASS V2b eve SER @ 10dB,0.99609375,0.9888333333333333,0.007260416666666658,0.015,PASS
V2b eve SER @ 20dB,0.99609375,0.9881666666666666,0.007927083333333362,0.015,PASS V2b eve SER @ 20dB,0.99609375,0.9881666666666666,0.007927083333333362,0.015,PASS
V2b eve SER @ 80dB,0.99609375,0.9896666666666667,0.0064270833333333055,0.015,PASS V2b eve SER @ 80dB,0.99609375,0.9896666666666667,0.0064270833333333055,0.015,PASS
V3a bias slope in kappa,1.0,0.9932,0.0068,0.03,PASS
V3b random mask E|corr|,0.09973557010035818,0.10187042771408686,0.002134857613728683,0.002992067103010745,PASS V3b random mask E|corr|,0.09973557010035818,0.10187042771408686,0.002134857613728683,0.002992067103010745,PASS
V4a blind jammer projection mean,0.0,0.00026396107284673695,0.00026396107284673695,0.003,PASS V4a blind jammer projection mean,0.0,0.00026396107284673695,0.00026396107284673695,0.003,PASS
V4b blind jammer projection variance,0.01558576233568703,0.015476787953278994,0.00010897438240803532,0.0001558576233568703,PASS V4b blind jammer projection variance,0.01558576233568703,0.015476787953278994,0.00010897438240803532,0.0001558576233568703,PASS
@@ -13,3 +14,5 @@ V6 coded-OMA outage @ 10 dB,0.0406,0.040575,0,0,REFERENCE
V7 symbolic identities,exact,exact,0,0,PASS V7 symbolic identities,exact,exact,0,0,PASS
V8 cross-period remainder,0.0,0.000337,0.000337,0.0005,PASS V8 cross-period remainder,0.0,0.000337,0.000337,0.0005,PASS
V9 score-variance ratio,2.8,2.8252,0.0252,0.05,PASS V9 score-variance ratio,2.8,2.8252,0.0252,0.05,PASS
V10 format-matched OMA at 10 dB,0.055,0.05520,0.00020,0.001,PASS
V11 OMA closed form vs Monte Carlo,0.081245,0.080925,0.0039,0.01,PASS
1 check claim empirical abs_err tol verdict
5 V2b eve SER @ 10dB 0.99609375 0.9888333333333333 0.007260416666666658 0.015 PASS
6 V2b eve SER @ 20dB 0.99609375 0.9881666666666666 0.007927083333333362 0.015 PASS
7 V2b eve SER @ 80dB 0.99609375 0.9896666666666667 0.0064270833333333055 0.015 PASS
8 V3a bias slope in kappa 1.0 0.9932 0.0068 0.03 PASS
9 V3b random mask E|corr| 0.09973557010035818 0.10187042771408686 0.002134857613728683 0.002992067103010745 PASS
10 V4a blind jammer projection mean 0.0 0.00026396107284673695 0.00026396107284673695 0.003 PASS
11 V4b blind jammer projection variance 0.01558576233568703 0.015476787953278994 0.00010897438240803532 0.0001558576233568703 PASS
14 V7 symbolic identities exact exact 0 0 PASS
15 V8 cross-period remainder 0.0 0.000337 0.000337 0.0005 PASS
16 V9 score-variance ratio 2.8 2.8252 0.0252 0.05 PASS
17 V10 format-matched OMA at 10 dB 0.055 0.05520 0.00020 0.001 PASS
18 V11 OMA closed form vs Monte Carlo 0.081245 0.080925 0.0039 0.01 PASS