Final revision: audit fixes, cross-scheme comparisons, known-plaintext stage

- exclude the all-ones Walsh-Hadamard row and test every key family
  against the all-ones guess
- pass the model dimension to the noise scaling so the key-length sweep
  runs at a fixed per-dimension SNR
- known-plaintext attack with nested accumulation and common random
  numbers, averaged over 40 collections
- key sensitivity and brute-force search extended to the permutation
  key and the index cipher
- make_tables regenerates all three result tables from the CSVs
This commit is contained in:
KiHoLee
2026-08-13 21:21:46 +09:00
parent 37392bc38f
commit 25b5891b04
15 changed files with 279 additions and 56 deletions
+150 -11
View File
@@ -249,33 +249,42 @@ def stage_D():
fams = {}
# random fixed masks
set_seed(7); fams["random"] = random_mask(U, Lp)
# Walsh-Hadamard rows (orthogonal)
Hd = torch.tensor(hadamard(Lp)[:U], dtype=torch.float32) # ||row||=sqrt(Lp)
# Walsh-Hadamard rows (orthogonal). Row 0 of the Sylvester
# construction is the all-ones vector, which any adversary can write
# down, so it is excluded and the users take rows 1 to U.
Hd = torch.tensor(hadamard(Lp)[1:U + 1], dtype=torch.float32)
fams["hadamard"] = Hd
ones = torch.ones(U, Lp) # the cheapest possible guess
rows = []
for name, W in fams.items():
m = get_model(P=P, vu=vu, d=d, U=U, iters=4000, freeze_W=W)
lg = eval_ser_sse(m, [10.0], frames=500_000)[0]
ew = eve_wrong_mask(U, Lp, seed=20260813).to(DEVICE)
ev = eval_ser_eve(m, ew, [10.0], frames=500_000)[0]
ev1 = eval_ser_eve(m, ones, [10.0], frames=500_000)[0]
xc = mean_abs_xcorr(m.masks().detach())
rows.append((name, lg, ev, xc))
print(f" {name:9s} legit={lg:.2e} eve={ev:.3f} xcorr={xc:.4f}")
rows.append((name, lg, ev, ev1, xc))
print(f" {name:9s} legit={lg:.2e} eve={ev:.3f} ones={ev1:.3f} "
f"xcorr={xc:.4f}")
# learned masks (plain cross entropy)
m = get_model(P=P, vu=vu, d=d, U=U, iters=4000)
lg = eval_ser_sse(m, [10.0], frames=500_000)[0]
ew = eve_wrong_mask(U, Lp, seed=20260813).to(DEVICE)
ev = eval_ser_eve(m, ew, [10.0], frames=500_000)[0]
ev1 = eval_ser_eve(m, ones, [10.0], frames=500_000)[0]
xc = mean_abs_xcorr(m.masks().detach())
rows.append(("learned", lg, ev, xc))
print(f" {'learned':9s} legit={lg:.2e} eve={ev:.3f} xcorr={xc:.4f}")
rows.append(("learned", lg, ev, ev1, xc))
print(f" {'learned':9s} legit={lg:.2e} eve={ev:.3f} ones={ev1:.3f} "
f"xcorr={xc:.4f}")
# regularized key learning (orthogonality + constant modulus)
mr = get_model_reg(P=P, vu=vu, d=d, U=U, iters=4000)
lgr = eval_ser_sse(mr, [10.0], frames=500_000)[0]
evr = eval_ser_eve(mr, ew, [10.0], frames=500_000)[0]
evr1 = eval_ser_eve(mr, ones, [10.0], frames=500_000)[0]
xcr = mean_abs_xcorr(mr.masks().detach())
rows.append(("learned_reg", lgr, evr, xcr))
print(f" {'learn_reg':9s} legit={lgr:.2e} eve={evr:.3f} xcorr={xcr:.4f}")
rows.append(("learned_reg", lgr, evr, evr1, xcr))
print(f" {'learn_reg':9s} legit={lgr:.2e} eve={evr:.3f} ones={evr1:.3f} "
f"xcorr={xcr:.4f}")
# jamming robustness of plain vs regularized keys (blind jammer)
jsr = [-10.0, -5.0, 0.0, 5.0, 10.0, 15.0, 20.0]
jb_plain = eval_ser_jam(m, 10.0, jsr, frames=300_000, mode="blind")
@@ -284,7 +293,8 @@ def stage_D():
["jsr_db", "plain", "regularized"],
[(j, jb_plain[i], jb_reg[i]) for i, j in enumerate(jsr)])
write_csv(DATA / "sec_maskfam.csv",
["family", "legit_ser", "eve_ser", "mask_xcorr"], rows)
["family", "legit_ser", "eve_ser", "eve_ones_ser",
"mask_xcorr"], rows)
@torch.no_grad()
@@ -407,9 +417,11 @@ def stage_E():
@torch.no_grad()
def eval_scheme_permuted_eve(model: SSE, snr_db, frames, perms,
chunk=100_000, seed=777):
chunk=100_000, seed=777, eve_perms=None):
"""Eve for S3: sees the per-user permuted tx, holds the PUBLIC masks
but not the permutation, decodes user 0 raw."""
but not the permutation, decodes user 0 raw. When eve_perms is given,
Eve first undoes the permutation she believes was used, which models
an attacker holding a partially recovered permutation key."""
model.eval().to(DEVICE)
Bn = model.unit_codebook()
true_m = model.masks()
@@ -431,6 +443,9 @@ def eval_scheme_permuted_eve(model: SSE, snr_db, frames, perms,
y_rx = h[:, None, None] * y + sigma * torch.randn(
n, model.P, model.L, device=DEVICE)
r = y_rx / h[:, None, None].clamp_min(1e-6)
if eve_perms is not None:
inv = torch.argsort(eve_perms[0]).to(r.device)
r = r.reshape(n, d)[:, inv].reshape(n, model.P, model.L)
cand = Bn * true_m[0][None, :]
scores = torch.einsum("npl,vl->npv", r, cand)
wrong = (scores.argmax(-1) != digits[:, 0]).any(dim=1)
@@ -508,6 +523,128 @@ def stage_F():
["L", "K", "best_rho", "eve_ser"], rows)
def partial_perm(true_perm: torch.Tensor, frac: float, gen: torch.Generator):
"""A permutation that agrees with true_perm on a fraction frac of the
positions and is scrambled on the rest, which is what an attacker
holding part of a permutation key would have."""
d = true_perm.numel()
k = int(round(frac * d))
idx = torch.randperm(d, generator=gen)
keep, rest = idx[:k], idx[k:]
out = true_perm.clone()
if rest.numel() > 1:
out[rest] = true_perm[rest][torch.randperm(rest.numel(), generator=gen)]
return out
TRIALS_PERM = 60
def stage_I():
"""Key sensitivity of three schemes on one axis.
The axis is the fraction of the key the attacker has recovered. For
the proposed scheme that fraction is the normalized correlation
between the guessed and the true mask. For the permutation scheme it
is the fraction of positions the guessed permutation places
correctly. For the index cipher it is the fraction of pad bits the
attacker knows, whose error rate is the closed form
1 - 2^{-(1-f) log2 V} because the unknown bits are uniform.
"""
print("[I] key sensitivity across schemes ...")
m = get_model(iters=4000)
F = 600_000 # more frames per point for a smooth curve
TRIALS_MASK = 12 # independent substitute keys per point
d = m.P * m.L
true_m = m.masks().detach().cpu()
gen = torch.Generator().manual_seed(31)
gp = torch.Generator().manual_seed(11)
gperm = torch.randperm(d, generator=gp)
perms = gperm[None].repeat(m.users, 1)
# a marker grid comparable to the other result figures, with the
# spacing tightened only where the curves fall
fracs = [0.0, 0.2, 0.4, 0.6, 0.75, 0.85, 0.9, 0.94, 0.97, 1.0]
bits = math.log2(m.V)
rows = []
for f in fracs:
acc_m = []
for t in range(TRIALS_MASK):
mt = correlated_masks(true_m, f, gen)
acc_m.append(eval_ser_eve(m, mt, [10.0],
frames=F // TRIALS_MASK,
seed=777 + 17 * t)[0])
ser_mask = sum(acc_m) / len(acc_m)
# a partial permutation is combinatorially lumpy, so the point
# is averaged over independent draws of which positions the
# attacker holds
acc = []
for t in range(TRIALS_PERM):
pp = partial_perm(gperm, f, gen)
pperms = pp[None].repeat(m.users, 1)
acc.append(eval_scheme_permuted_eve(m, 10.0, F // TRIALS_PERM,
perms, eve_perms=pperms,
seed=777 + 13 * t))
ser_perm = sum(acc) / len(acc)
ser_pad = 1.0 - 2.0 ** (-(1.0 - f) * bits)
rows.append((f, ser_mask, ser_perm, ser_pad))
print(f" f={f:.3f} mask={ser_mask:.4f} perm={ser_perm:.4f} "
f"pad={ser_pad:.4f}")
write_csv(DATA / "sec_sens_cmp.csv",
["frac", "ser_mask", "ser_perm", "ser_pad"], rows)
def stage_J():
"""Brute-force search against three schemes at the same key length.
Keyed masking: K random unit keys, keep the best correlation, map it
through the measured sensitivity curve of stage I.
Permutation key: K random permutations of the d positions, keep the
one that places the most positions correctly, map the resulting
fraction through the same sensitivity curve.
Index cipher: K random pads out of the 2^{log2 V} possible pads, so
the attacker succeeds with probability K/V on each symbol.
"""
print("[J] brute-force search across schemes ...")
import numpy as np
cmp_rows = list(csv_rows(DATA / "sec_sens_cmp.csv"))
f_arr = np.array([float(r["frac"]) for r in cmp_rows])
mask_arr = np.array([float(r["ser_mask"]) for r in cmp_rows])
perm_arr = np.array([float(r["ser_perm"]) for r in cmp_rows])
d, L, V = 64, 16, 65536
ks = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000]
rng = np.random.default_rng(2026)
trials = 400
rows = []
for K in ks:
# keyed masking: best |first coordinate| of K random unit vectors
best_kappa = np.empty(trials)
best_frac = np.empty(trials)
for t in range(trials):
g = rng.standard_normal((K, L))
g /= np.linalg.norm(g, axis=1, keepdims=True)
best_kappa[t] = np.abs(g[:, 0]).max()
# permutation: fraction of fixed points, Binomial(d, 1/d) per
# draw, so the best of K draws is the max of K such counts
best_frac[t] = rng.binomial(d, 1.0 / d, size=K).max() / d
ser_mask = float(np.mean(np.interp(best_kappa, f_arr, mask_arr)))
ser_perm = float(np.mean(np.interp(best_frac, f_arr, perm_arr)))
ser_pad = 1.0 - min(1.0, K / V)
rows.append((K, ser_mask, ser_perm, ser_pad,
float(best_kappa.mean()), float(best_frac.mean())))
print(f" K={K:8d} mask={ser_mask:.4f} perm={ser_perm:.4f} "
f"pad={ser_pad:.4f}")
write_csv(DATA / "sec_brute_cmp.csv",
["K", "ser_mask", "ser_perm", "ser_pad",
"best_kappa", "best_frac"], rows)
def csv_rows(path):
import csv as _csv
with open(path) as f:
yield from _csv.DictReader(f)
def main():
print(f"device={DEVICE}")
stage_A()
@@ -516,6 +653,8 @@ def main():
stage_D()
stage_E()
stage_F()
stage_I()
stage_J()
print("[done] full-scale security CSVs in", DATA)
+40 -16
View File
@@ -1,9 +1,11 @@
"""Generate the LaTeX rows of the two result tables from the CSVs, so
"""Generate the LaTeX rows of the three result tables from the CSVs, so
that every table in the paper is reproducible from data/ (TIFS mandate).
Prints the tabular body; paste into main.tex without edits.
Prints the tabular bodies; paste into main.tex without edits.
"""
from __future__ import annotations
import csv
import json
import math
from pathlib import Path
DATA = Path(__file__).resolve().parents[1] / "data"
@@ -17,39 +19,61 @@ NAME = {
"random": "Random",
"hadamard": "Walsh-Hadamard",
"learned": "Learned",
"learned_reg": r"Regularized~\eqref{eq:regloss}",
}
RECEIVER = {
"legit": "Legitimate", "oma": "OMA",
"insider": "Insider", "eve": "Outsider eavesdropper",
}
def f3(x: str) -> str:
"""Three decimals, or an em-dash for a value that does not apply."""
try:
return f"{float(x):.3f}"
except ValueError:
v = float(x)
except (TypeError, ValueError):
return "--"
return "--" if math.isnan(v) else f"{v:.3f}"
def cell(x: str, bold: bool) -> str:
s = f3(x)
if s == "--":
return "--"
return rf"$\mathbf{{{s}}}$" if bold else f"${s}$"
def compare_table():
print("% Table: scheme comparison (from sec_compare.csv)")
rows = list(csv.DictReader(open(DATA / "sec_compare.csv")))
order = ["public_mask", "perm_key", "index_cipher", "oma_plain", "proposed"]
rows = sorted(rows, key=lambda r: order.index(r["scheme"]))
rows.sort(key=lambda r: order.index(r["scheme"]))
for r in rows:
cells = [f3(r["legit_ser"]), f3(r["eve_out"]), f3(r["eve_in"]),
f3(r["jam0_ser"])]
if r["scheme"] == "proposed":
cells = [rf"$\mathbf{{{c}}}$" for c in cells]
else:
cells = [f"${c}$" if c != "--" else "--" for c in cells]
b = r["scheme"] == "proposed"
cells = [cell(r[k], b) for k in
("legit_ser", "eve_out", "eve_in", "jam0_ser")]
print(f"{NAME[r['scheme']]} & " + " & ".join(cells) + r" \\")
def maskfam_table():
print("% Table: key families (from sec_maskfam.csv)")
for r in csv.DictReader(open(DATA / "sec_maskfam.csv")):
print(f"{NAME[r['family']]} & ${f3(r['legit_ser'])}$ & "
f"${f3(r['eve_ser'])}$ & ${f3(r['mask_xcorr'])}$" + r" \\")
cells = [cell(r[k], False) for k in
("legit_ser", "eve_ser", "eve_ones_ser", "mask_xcorr")]
print(f"{NAME[r['family']]} & " + " & ".join(cells) + r" \\")
def real_table():
print("% Table: headline recovery (from real_sec_stats.json)")
st = json.loads((DATA / "real_sec_stats.json").read_text())
rec = st["recovery"]
snrs = sorted(rec, key=float)
for key in ("legit", "oma", "insider", "eve"):
cells = " & ".join(f"${rec[s][key]:.3f}$" for s in snrs)
print(f"{RECEIVER[key]} & {cells}" + r" \\")
if __name__ == "__main__":
compare_table()
print()
maskfam_table()
compare_table(); print()
maskfam_table(); print()
real_table()
+58 -17
View File
@@ -7,7 +7,8 @@ Label dictionary is fixed here and copied verbatim into tables and prose.
fig_sec_keylen.pdf : SER vs key length L (Fig. 3)
fig_sec_jam.pdf : target-user SER vs JSR (Fig. 4)
fig_sec_sens.pdf : Eve SER vs key correlation (Fig. 5)
fig_sec_brute.pdf : Eve SER vs number of key guesses (Fig. 6)
fig_sec_brute.pdf : Eve SER vs number of key guesses (Fig. 6)
fig_sec_brute_rho.pdf : best key correlation vs guesses (Fig. 7)
"""
from __future__ import annotations
from pathlib import Path
@@ -159,14 +160,23 @@ def fig_jam():
def fig_sens():
r = load("sec_sens.csv")
x = col(r, "rho")
"""Key sensitivity of three schemes on one axis, the fraction of the
key the attacker holds. For keyed masking that fraction is the mask
correlation, for the permutation scheme the fraction of positions
placed correctly, for the index cipher the fraction of pad bits
known."""
r = load("sec_sens_cmp.csv")
x = col(r, "frac")
fig, ax = plt.subplots()
ax.plot(x, col(r, "eve_ser"), color=C_EVE, marker="s", ls="-",
label=LBL["eve_key"])
ax.plot(x, col(r, "ser_mask"), color=C_LEGIT, marker="o", ls="-",
label="Keyed masking")
ax.plot(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
label="Permutation key")
ax.plot(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
label="Index cipher")
chance = 1.0 - (1.0 / 16.0) ** 4
ax.axhline(chance, color=C_CH, ls="-.", lw=0.9, label=LBL["chance"])
ax.set_xlabel(r"Key correlation $\kappa$")
ax.axhline(chance, color=C_CH, ls=":", lw=0.9, label=LBL["chance"])
ax.set_xlabel("Fraction of the key recovered")
ax.set_ylabel("Eavesdropper SER")
ax.set_xlim(0, 1)
ax.legend(loc="lower left")
@@ -174,21 +184,45 @@ def fig_sens():
def fig_brute():
"""Brute-force search against the three keyed schemes at the same
key length, each mapped through its own sensitivity curve."""
r = load("sec_brute_cmp.csv")
x = col(r, "K")
fig, ax = plt.subplots()
ax.semilogx(x, col(r, "ser_mask"), color=C_LEGIT, marker="o", ls="-",
label="Keyed masking")
ax.semilogx(x, col(r, "ser_perm"), color=C_EVE, marker="s", ls="--",
label="Permutation key")
ax.semilogx(x, col(r, "ser_pad"), color=C_PUB, marker="v", ls="-.",
label="Index cipher")
kl = load("sec_keylen.csv")
legit = float([q for q in kl if int(q["L"]) == 16][0]["legit_ser"])
ax.axhline(legit, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
ax.set_xlabel("Number of key guesses $K$")
ax.set_ylabel("Eavesdropper SER")
ax.set_ylim(-0.03, 1.05)
ax.legend(loc="center left")
save(fig, "fig_sec_brute")
def fig_brute_rho():
"""Best key correlation a search of size K reaches, per key length.
This is a property of the key space alone."""
r = load("sec_brute.csv")
fig, ax = plt.subplots()
sty = {8: ("#c0392b", "o"), 16: ("#2c5fa8", "s"),
32: ("#16a085", "v"), 64: ("#8e44ad", "P")}
for Lp in [8, 16, 32, 64]:
for Lp, (c, mk) in sty.items():
rows = [row for row in r if int(row["L"]) == Lp]
ks = [float(row["K"]) for row in rows]
ser = [float(row["eve_ser"]) for row in rows]
c, mk = sty[Lp]
ax.semilogx(ks, ser, color=c, marker=mk, ls="-",
label=f"$L={Lp}$")
ax.semilogx([float(x["K"]) for x in rows],
[float(x["best_rho"]) for x in rows],
color=c, marker=mk, ls="-", label=f"$L={Lp}$")
ax.axhline(0.96, color=C_CH, ls="-.", lw=0.9, label="Break threshold")
ax.set_xlabel("Number of key guesses $K$")
ax.set_ylabel("Eavesdropper SER")
ax.legend(loc="lower left")
save(fig, "fig_sec_brute")
ax.set_ylabel(r"Best key correlation $\kappa$")
ax.set_ylim(0, 1.05)
ax.legend(loc="upper left")
save(fig, "fig_sec_brute_rho")
def fig_real():
@@ -221,7 +255,13 @@ def fig_kpa():
ser = [float(row["eve_ser"]) for row in rows]
ax.semilogx(n, ser, color=c, marker=mk, ls="-",
label=f"{int(snr)} dB")
ax.axhline(0.304, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
# legitimate reference measured with the SAME estimator as the
# eavesdropper curves, namely the four-user average of eval_ser_sse
# at L=16, taken from sec_keylen.csv rather than from the user-1
# convention of the scheme-comparison table
kl = load("sec_keylen.csv")
legit = float([r for r in kl if int(r["L"]) == 16][0]["legit_ser"])
ax.axhline(legit, color=C_OMA, ls=":", lw=0.9, label=LBL["legit"])
ax.set_xlabel("Known-plaintext frames $N$")
ax.set_ylabel("Eavesdropper SER")
ax.set_xscale("log", base=2)
@@ -236,6 +276,7 @@ def main():
try:
fig_sens()
fig_brute()
fig_brute_rho()
except FileNotFoundError:
print("[skip] attack-difficulty CSVs not present yet")
try:
+8
View File
@@ -0,0 +1,8 @@
K,ser_mask,ser_perm,ser_pad,best_kappa,best_frac
1,0.9993678262,0.9999544349,0.9999847412,0.1909643153,0.0160546875
10,0.9948030015,0.9999095052,0.9998474121,0.4626973216,0.041015625
100,0.9825717055,0.9998648567,0.9984741211,0.6342127242,0.0658203125
1000,0.9568330187,0.9998315989,0.9847412109,0.7432459045,0.084296875
10000,0.909109588,0.9997988333,0.8474121094,0.8165387856,0.1025
100000,0.8435049061,0.9997690911,0,0.8680494354,0.1190234375
1000000,0.7503523409,0.9997409661,0,0.905556646,0.1346484375
1 K ser_mask ser_perm ser_pad best_kappa best_frac
2 1 0.9993678262 0.9999544349 0.9999847412 0.1909643153 0.0160546875
3 10 0.9948030015 0.9999095052 0.9998474121 0.4626973216 0.041015625
4 100 0.9825717055 0.9998648567 0.9984741211 0.6342127242 0.0658203125
5 1000 0.9568330187 0.9998315989 0.9847412109 0.7432459045 0.084296875
6 10000 0.909109588 0.9997988333 0.8474121094 0.8165387856 0.1025
7 100000 0.8435049061 0.9997690911 0 0.8680494354 0.1190234375
8 1000000 0.7503523409 0.9997409661 0 0.905556646 0.1346484375
+5 -5
View File
@@ -1,5 +1,5 @@
family,legit_ser,eve_ser,mask_xcorr
random,0.64962,0.998488,0.2709003091
hadamard,0.2568,0.9995605,0
learned,0.2762895,0.9999285,0.007116591092
learned_reg,0.315952,0.9999555,0.01121100038
family,legit_ser,eve_ser,eve_ones_ser,mask_xcorr
random,0.64962,0.998488,0.999988,0.2709003091
hadamard,0.257299,0.9999905,0.9999755,0
learned,0.2762895,0.9999285,0.99999,0.007116591092
learned_reg,0.315952,0.9999555,0.9999175,0.01121100038
1 family legit_ser eve_ser eve_ones_ser mask_xcorr
2 random 0.64962 0.998488 0.999988 0.2709003091
3 hadamard 0.2568 0.257299 0.9995605 0.9999905 0.9999755 0
4 learned 0.2762895 0.9999285 0.99999 0.007116591092
5 learned_reg 0.315952 0.9999555 0.9999175 0.01121100038
+7 -7
View File
@@ -1,8 +1,8 @@
jsr_db,plain,regularized
-10,0.46879,0.4691633333
-5,0.6335366667,0.6307333333
0,0.8056466667,0.8018166667
5,0.9187366667,0.91602
10,0.9708533333,0.9693866667
15,0.9902133333,0.9896433333
20,0.9967333333,0.9965933333
-10,0.46703,0.4719866667
-5,0.6348,0.6301533333
0,0.8073133333,0.80122
5,0.9191366667,0.91577
10,0.9707266667,0.96911
15,0.9901433333,0.99006
20,0.9968333333,0.9964333333
1 jsr_db plain regularized
2 -10 0.46879 0.46703 0.4691633333 0.4719866667
3 -5 0.6335366667 0.6348 0.6307333333 0.6301533333
4 0 0.8056466667 0.8073133333 0.8018166667 0.80122
5 5 0.9187366667 0.9191366667 0.91602 0.91577
6 10 0.9708533333 0.9707266667 0.9693866667 0.96911
7 15 0.9902133333 0.9901433333 0.9896433333 0.99006
8 20 0.9967333333 0.9968333333 0.9965933333 0.9964333333
+11
View File
@@ -0,0 +1,11 @@
frac,ser_mask,ser_perm,ser_pad
0,0.99998375,0.9999833333,0.9999847412
0.2,0.9997954167,0.9996233333,0.999859778
0.4,0.9985329167,0.9949383333,0.9987114181
0.6,0.99153625,0.9540933333,0.9881584643
0.75,0.9644704167,0.880335,0.9375
0.85,0.88742875,0.7279433333,0.8105354292
0.9,0.7780379167,0.5716733333,0.6701230223
0.94,0.6256225,0.4316466667,0.4859430867
0.97,0.43810125,0.3708666667,0.283022376
1,0.2756983333,0.3021566667,0
1 frac ser_mask ser_perm ser_pad
2 0 0.99998375 0.9999833333 0.9999847412
3 0.2 0.9997954167 0.9996233333 0.999859778
4 0.4 0.9985329167 0.9949383333 0.9987114181
5 0.6 0.99153625 0.9540933333 0.9881584643
6 0.75 0.9644704167 0.880335 0.9375
7 0.85 0.88742875 0.7279433333 0.8105354292
8 0.9 0.7780379167 0.5716733333 0.6701230223
9 0.94 0.6256225 0.4316466667 0.4859430867
10 0.97 0.43810125 0.3708666667 0.283022376
11 1 0.2756983333 0.3021566667 0
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.
Binary file not shown.