modularot 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modularot/__init__.py +49 -0
- modularot/cli.py +381 -0
- modularot/custom_kems/__init__.py +0 -0
- modularot/custom_kems/ggh_fo/__init__.py +0 -0
- modularot/custom_kems/ggh_fo/ggh_fo.py +280 -0
- modularot/custom_kems/ntru_fo/__init__.py +0 -0
- modularot/custom_kems/ntru_fo/ntru_fo.py +347 -0
- modularot/kem_adapter/__init__.py +0 -0
- modularot/kem_adapter/adapter.py +58 -0
- modularot/oblivious_sampling.py +207 -0
- modularot/ot_protocol.py +332 -0
- modularot-0.1.0.dist-info/METADATA +63 -0
- modularot-0.1.0.dist-info/RECORD +17 -0
- modularot-0.1.0.dist-info/WHEEL +5 -0
- modularot-0.1.0.dist-info/entry_points.txt +2 -0
- modularot-0.1.0.dist-info/licenses/LICENSE +21 -0
- modularot-0.1.0.dist-info/top_level.txt +1 -0
modularot/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
modularot -- modular KEM-based 1-out-of-n Oblivious Transfer (semi-honest).
|
|
3
|
+
|
|
4
|
+
The protocol is KEM-agnostic; the only KEM-specific operation (sampling an
|
|
5
|
+
oblivious public key) is injected as a callable. See `ot_protocol` for the
|
|
6
|
+
construction and `oblivious_sampling` for the samplers.
|
|
7
|
+
|
|
8
|
+
Quick start (any liboqs lattice KEM)::
|
|
9
|
+
|
|
10
|
+
from modularot import run_ot, oqs_kem_factory, resolve_oqs
|
|
11
|
+
|
|
12
|
+
result = run_ot(
|
|
13
|
+
make_kem=oqs_kem_factory("ML-KEM-768"),
|
|
14
|
+
sampler=resolve_oqs("ML-KEM-768"),
|
|
15
|
+
label="ML-KEM-768",
|
|
16
|
+
choice=1, # branch the receiver wants
|
|
17
|
+
n=4, # 1-out-of-4
|
|
18
|
+
)
|
|
19
|
+
assert result["ok"] and result["secure"]
|
|
20
|
+
|
|
21
|
+
`OTSender`, `OTReceiver` and `KEMAdapter` are exported for custom KEMs; see
|
|
22
|
+
`modularot.custom_kems` (NTRU_FO, GGH_FO) for examples.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from .ot_protocol import (
|
|
26
|
+
OTReceiver,
|
|
27
|
+
OTSender,
|
|
28
|
+
run_ot,
|
|
29
|
+
receiver_cannot_read_other,
|
|
30
|
+
)
|
|
31
|
+
from .ot_protocol import _oqs_kem_factory as oqs_kem_factory
|
|
32
|
+
from .kem_adapter.adapter import KEM, KEMAdapter, KEMInfo
|
|
33
|
+
from .oblivious_sampling import resolve_oqs, is_supported_oqs
|
|
34
|
+
|
|
35
|
+
__version__ = "0.1.0"
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"OTReceiver",
|
|
39
|
+
"OTSender",
|
|
40
|
+
"run_ot",
|
|
41
|
+
"receiver_cannot_read_other",
|
|
42
|
+
"oqs_kem_factory",
|
|
43
|
+
"KEM",
|
|
44
|
+
"KEMAdapter",
|
|
45
|
+
"KEMInfo",
|
|
46
|
+
"resolve_oqs",
|
|
47
|
+
"is_supported_oqs",
|
|
48
|
+
"__version__",
|
|
49
|
+
]
|
modularot/cli.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for modularot: the modular KEM-based 1-out-of-n OT.
|
|
3
|
+
|
|
4
|
+
Subcommands:
|
|
5
|
+
run -- run one 1-out-of-n OT transfer (KEM, n, choice, messages).
|
|
6
|
+
bench -- per KEM and per n: request/transfer/finalize timing, bandwidth
|
|
7
|
+
and KeyGen-vs-oblivious-sample cost. Console tables + CSV.
|
|
8
|
+
list-kems -- list the names usable with `run --kem` / `bench --kems`.
|
|
9
|
+
|
|
10
|
+
Usage (`modularot` entry point or `python -m modularot.cli`):
|
|
11
|
+
modularot run # ML-KEM-768, 1-out-of-2, placeholder messages
|
|
12
|
+
modularot run --kem ML-KEM-768 --n 4 --choice 2
|
|
13
|
+
modularot run --n 3 --message "a" --message "b" --message "c" --choice 1
|
|
14
|
+
modularot run --kem GGH_FO --ggh-dim 200 --quiet --output secret.bin
|
|
15
|
+
modularot list-kems
|
|
16
|
+
modularot bench # default KEM subset + NTRU_FO + GGH_FO
|
|
17
|
+
modularot bench --all # every supported liboqs mechanism, + custom
|
|
18
|
+
modularot bench --kems ML-KEM-768 sntrup761 --no-custom
|
|
19
|
+
modularot bench --ns 2 4 8 16 32 --reps 5 --csv out.csv
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import csv
|
|
24
|
+
import statistics
|
|
25
|
+
import sys
|
|
26
|
+
import time
|
|
27
|
+
|
|
28
|
+
import oqs
|
|
29
|
+
|
|
30
|
+
from .ot_protocol import run_ot, _oqs_kem_factory, _fmt_ns
|
|
31
|
+
from .oblivious_sampling import resolve_oqs, is_supported_oqs, sample_oblivious_ntru_fo
|
|
32
|
+
from .kem_adapter.adapter import KEMAdapter
|
|
33
|
+
|
|
34
|
+
# One per (family, security level): short run covering every encoding family.
|
|
35
|
+
DEFAULT_KEMS = [
|
|
36
|
+
"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024", # structured, q=3329
|
|
37
|
+
"FrodoKEM-640-AES", "FrodoKEM-976-AES", "FrodoKEM-1344-AES", # unstructured LWE
|
|
38
|
+
"NTRU-HPS-2048-509", "NTRU-HRSS-701", # NTRU, bit-packed
|
|
39
|
+
"sntrup761", # NTRU Prime, mixed-radix
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
DEFAULT_NS = [2, 4, 8, 16, 32]
|
|
43
|
+
|
|
44
|
+
CUSTOM_KEM_NAMES = {"NTRU_FO", "GGH_FO"}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _ntrufo_entry(N: int, q: int = 8192, p: int = 3):
|
|
48
|
+
"""(label, kem_factory, sampler) for NTRU_FO; each factory call builds an
|
|
49
|
+
independent instance."""
|
|
50
|
+
from .custom_kems.ntru_fo.ntru_fo import NTRU_FO
|
|
51
|
+
|
|
52
|
+
def factory():
|
|
53
|
+
impl = NTRU_FO(N=N, p=p, q=q)
|
|
54
|
+
return KEMAdapter(gen=impl.generate_keys, encap=impl.encapsulate,
|
|
55
|
+
decap=impl.decapsulate)
|
|
56
|
+
|
|
57
|
+
return (f"NTRU_FO-{N}", factory, lambda pk: sample_oblivious_ntru_fo(pk, q=q))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _gghfo_entry(dimension: int):
|
|
61
|
+
"""(label, kem_factory, sampler) for GGH-HNF FO (GGH_private=True). Needs
|
|
62
|
+
`pip install modularot[custom-kems]`; dimension=200 is teaching-scale.
|
|
63
|
+
Classic GGH has no sound oblivious sampler."""
|
|
64
|
+
from .custom_kems.ggh_fo import ggh_fo
|
|
65
|
+
from .oblivious_sampling import sample_oblivious_ggh_hnf
|
|
66
|
+
|
|
67
|
+
def factory():
|
|
68
|
+
return KEMAdapter(
|
|
69
|
+
gen=lambda: ggh_fo.gen(dimension=dimension),
|
|
70
|
+
encap=ggh_fo.encaps,
|
|
71
|
+
decap=ggh_fo.decaps,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return (f"GGH_FO-{dimension}", factory, sample_oblivious_ggh_hnf)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _build_entry(kem: str, ntrufo_n: int, ggh_dim: int):
|
|
78
|
+
"""(label, kem_factory, sampler) for one KEM name -- custom or liboqs."""
|
|
79
|
+
if kem == "NTRU_FO":
|
|
80
|
+
try:
|
|
81
|
+
return _ntrufo_entry(ntrufo_n)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise SystemExit(f"cannot build NTRU_FO: {e}")
|
|
84
|
+
if kem == "GGH_FO":
|
|
85
|
+
try:
|
|
86
|
+
return _gghfo_entry(ggh_dim)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
raise SystemExit(
|
|
89
|
+
f"cannot build GGH_FO (needs the GGH_crypto package): {e}")
|
|
90
|
+
|
|
91
|
+
enabled = set(oqs.get_enabled_kem_mechanisms())
|
|
92
|
+
if kem not in enabled:
|
|
93
|
+
raise SystemExit(f"KEM '{kem}' not enabled in this liboqs build "
|
|
94
|
+
f"(see `modularot list-kems`)")
|
|
95
|
+
if not is_supported_oqs(kem):
|
|
96
|
+
raise SystemExit(f"KEM '{kem}' has no oblivious sampler, refused "
|
|
97
|
+
f"rather than weaken security")
|
|
98
|
+
return (kem, _oqs_kem_factory(kem), resolve_oqs(kem))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _median(xs):
|
|
102
|
+
return int(statistics.median(xs))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def warmup(kem_factory) -> None:
|
|
106
|
+
"""Absorb lazy-init overhead outside the timed region."""
|
|
107
|
+
kem = kem_factory()
|
|
108
|
+
pk, sk = kem.Gen()
|
|
109
|
+
ss, ct = kem.encaps(pk)
|
|
110
|
+
kem.decaps(sk, ct)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def bench_sampler_vs_keygen(kem_factory, sampler, reps: int) -> dict:
|
|
114
|
+
"""Median cost of one real KeyGen vs one oblivious sample."""
|
|
115
|
+
keygen_ns, sample_ns = [], []
|
|
116
|
+
for _ in range(reps):
|
|
117
|
+
kem = kem_factory()
|
|
118
|
+
t0 = time.perf_counter_ns()
|
|
119
|
+
pk, _sk = kem.Gen()
|
|
120
|
+
keygen_ns.append(time.perf_counter_ns() - t0)
|
|
121
|
+
|
|
122
|
+
t0 = time.perf_counter_ns()
|
|
123
|
+
sampler(pk)
|
|
124
|
+
sample_ns.append(time.perf_counter_ns() - t0)
|
|
125
|
+
return {"keygen": _median(keygen_ns), "sample": _median(sample_ns)}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def bench_cell(label: str, kem_factory, sampler, n: int, reps: int) -> dict:
|
|
129
|
+
"""Median timings over `reps` runs of a full 1-out-of-n OT session."""
|
|
130
|
+
runs = []
|
|
131
|
+
for r in range(reps):
|
|
132
|
+
# security check on the first rep only
|
|
133
|
+
runs.append(run_ot(kem_factory, sampler, label,
|
|
134
|
+
choice=0, n=n, check_security=(r == 0),
|
|
135
|
+
verbose=False))
|
|
136
|
+
first = runs[0]
|
|
137
|
+
return {
|
|
138
|
+
"KEM": label, "n": n,
|
|
139
|
+
"request": _median([r["request"] for r in runs]),
|
|
140
|
+
"transfer": _median([r["transfer"] for r in runs]),
|
|
141
|
+
"finalize": _median([r["finalize"] for r in runs]),
|
|
142
|
+
"TOTAL": _median([r["TOTAL"] for r in runs]),
|
|
143
|
+
"pk_bw": first["pk_bw"], "ct_bw": first["ct_bw"],
|
|
144
|
+
"ok": all(r["ok"] for r in runs), "secure": first["secure"],
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ── `run`: use the library from the command line ────────────────────────────
|
|
149
|
+
|
|
150
|
+
def cmd_run(args) -> None:
|
|
151
|
+
label, factory, sampler = _build_entry(args.kem, args.ntrufo_n, args.ggh_dim)
|
|
152
|
+
|
|
153
|
+
n = args.n
|
|
154
|
+
if not 0 <= args.choice < n:
|
|
155
|
+
raise SystemExit(f"--choice must be in [0, {n - 1}]")
|
|
156
|
+
|
|
157
|
+
if args.message and args.message_file:
|
|
158
|
+
raise SystemExit("use either --message or --message-file, not both")
|
|
159
|
+
if args.message:
|
|
160
|
+
if len(args.message) != n:
|
|
161
|
+
raise SystemExit(
|
|
162
|
+
f"need exactly {n} --message values (one per branch, in "
|
|
163
|
+
f"order), got {len(args.message)}")
|
|
164
|
+
messages = [m.encode() for m in args.message]
|
|
165
|
+
elif args.message_file:
|
|
166
|
+
if len(args.message_file) != n:
|
|
167
|
+
raise SystemExit(
|
|
168
|
+
f"need exactly {n} --message-file values (one per branch, "
|
|
169
|
+
f"in order), got {len(args.message_file)}")
|
|
170
|
+
messages = [open(p, "rb").read() for p in args.message_file]
|
|
171
|
+
else:
|
|
172
|
+
messages = None # run_ot falls back to placeholder messages
|
|
173
|
+
|
|
174
|
+
result = run_ot(factory, sampler, label, choice=args.choice, n=n,
|
|
175
|
+
messages=messages, check_security=not args.no_security_check,
|
|
176
|
+
verbose=not args.quiet)
|
|
177
|
+
|
|
178
|
+
if args.quiet:
|
|
179
|
+
sys.stdout.buffer.write(result["message"] or b"")
|
|
180
|
+
|
|
181
|
+
if args.output:
|
|
182
|
+
with open(args.output, "wb") as f:
|
|
183
|
+
f.write(result["message"] or b"")
|
|
184
|
+
if not args.quiet:
|
|
185
|
+
print(f"Decrypted message written to {args.output}")
|
|
186
|
+
|
|
187
|
+
if not result["ok"]:
|
|
188
|
+
raise SystemExit(1)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ── `list-kems` ───────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
def cmd_list_kems(args) -> None:
|
|
194
|
+
enabled = sorted(a for a in oqs.get_enabled_kem_mechanisms() if is_supported_oqs(a))
|
|
195
|
+
print(f"liboqs KEMs with an oblivious sampler ({len(enabled)}, usable with --kem/--kems):")
|
|
196
|
+
for k in enabled:
|
|
197
|
+
print(f" {k}")
|
|
198
|
+
print("\nCustom KEMs (need `pip install modularot[custom-kems]`):")
|
|
199
|
+
print(" NTRU_FO ring degree via --ntrufo-n (default 701)")
|
|
200
|
+
print(" GGH_FO dimension via --ggh-dim (default 200)")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ── `bench`: scalability sweep ───────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
def cmd_bench(args) -> None:
|
|
206
|
+
enabled = set(oqs.get_enabled_kem_mechanisms())
|
|
207
|
+
if args.all:
|
|
208
|
+
kems = sorted(a for a in enabled if is_supported_oqs(a))
|
|
209
|
+
else:
|
|
210
|
+
kems = args.kems or DEFAULT_KEMS
|
|
211
|
+
missing = [a for a in kems if a not in enabled]
|
|
212
|
+
unsupported = [a for a in kems if a in enabled and not is_supported_oqs(a)]
|
|
213
|
+
if missing:
|
|
214
|
+
print(f"skipping (not enabled in this liboqs build): {', '.join(missing)}")
|
|
215
|
+
if unsupported:
|
|
216
|
+
print(f"skipping (no oblivious sampler): {', '.join(unsupported)}")
|
|
217
|
+
kems = [a for a in kems if a in enabled and is_supported_oqs(a)]
|
|
218
|
+
|
|
219
|
+
# (label, kem_factory, sampler) — oqs mechanisms plus the custom KEMs
|
|
220
|
+
entries = [(alg, _oqs_kem_factory(alg), resolve_oqs(alg)) for alg in kems]
|
|
221
|
+
|
|
222
|
+
if not args.no_custom:
|
|
223
|
+
try:
|
|
224
|
+
entries.append(_ntrufo_entry(args.ntrufo_n))
|
|
225
|
+
except Exception as e:
|
|
226
|
+
print(f"skipping NTRU_FO: {e}")
|
|
227
|
+
try:
|
|
228
|
+
entries.append(_gghfo_entry(args.ggh_dim))
|
|
229
|
+
except Exception as e:
|
|
230
|
+
print(f"skipping GGH_FO (needs the GGH_crypto package): {e}")
|
|
231
|
+
|
|
232
|
+
if not entries:
|
|
233
|
+
raise SystemExit("no benchmarkable KEMs selected")
|
|
234
|
+
|
|
235
|
+
print(f"KEMs: {', '.join(label for label, _, _ in entries)}")
|
|
236
|
+
print(f"n: {args.ns} reps/cell: {args.reps}\n")
|
|
237
|
+
|
|
238
|
+
# ── table 1: real KeyGen vs oblivious sample ──────────────────────────────
|
|
239
|
+
print("═" * 78)
|
|
240
|
+
print("KeyGen vs oblivious sample (median, one call)")
|
|
241
|
+
print("═" * 78)
|
|
242
|
+
print(f"{'KEM':<22} {'KeyGen':>14} {'ObliviousSample':>16} {'ratio':>9}")
|
|
243
|
+
print("─" * 78)
|
|
244
|
+
ratios = {}
|
|
245
|
+
for label, factory, sampler in entries:
|
|
246
|
+
warmup(factory)
|
|
247
|
+
r = bench_sampler_vs_keygen(factory, sampler, args.reps)
|
|
248
|
+
ratio = r["keygen"] / max(r["sample"], 1)
|
|
249
|
+
ratios[label] = r
|
|
250
|
+
print(f"{label:<22} {_fmt_ns(r['keygen']):>14} {_fmt_ns(r['sample']):>16} "
|
|
251
|
+
f"{ratio:>8.1f}x")
|
|
252
|
+
print("─" * 78)
|
|
253
|
+
|
|
254
|
+
# ── table 2: full-session scaling over n ─────────────────────────────────
|
|
255
|
+
rows = []
|
|
256
|
+
for label, factory, sampler in entries:
|
|
257
|
+
print(f"\n{'═' * 98}")
|
|
258
|
+
print(f"{label} — 1-out-of-n scaling (medians of {args.reps} runs)")
|
|
259
|
+
print(f"{'═' * 98}")
|
|
260
|
+
print(f"{'n':>4} {'Request':>12} {'Transfer':>12} {'Finalize':>12} "
|
|
261
|
+
f"{'TOTAL':>12} {'BW R->S':>12} {'BW S->R':>12} OK SEC")
|
|
262
|
+
print("─" * 98)
|
|
263
|
+
for n in args.ns:
|
|
264
|
+
cell = bench_cell(label, factory, sampler, n, args.reps)
|
|
265
|
+
rows.append(cell)
|
|
266
|
+
print(f"{n:>4} {_fmt_ns(cell['request']):>12} {_fmt_ns(cell['transfer']):>12} "
|
|
267
|
+
f"{_fmt_ns(cell['finalize']):>12} {_fmt_ns(cell['TOTAL']):>12} "
|
|
268
|
+
f"{cell['pk_bw']:>10} B {cell['ct_bw']:>10} B "
|
|
269
|
+
f"{'✓' if cell['ok'] else '✗'} {'✓' if cell['secure'] else '✗'}")
|
|
270
|
+
print("─" * 98)
|
|
271
|
+
|
|
272
|
+
# ── CSV for plots ─────────────────────────────────────────────────────────
|
|
273
|
+
# units in the header, fixed-precision milliseconds
|
|
274
|
+
fields = ["kem", "n",
|
|
275
|
+
"request_ms", "transfer_ms", "finalize_ms", "total_ms",
|
|
276
|
+
"pk_bytes", "ct_bytes", "total_bytes",
|
|
277
|
+
"keygen_ms", "obliv_sample_ms", "keygen_over_sample",
|
|
278
|
+
"ok", "secure"]
|
|
279
|
+
|
|
280
|
+
def _ms(ns: int) -> float:
|
|
281
|
+
return round(ns / 1e6, 4)
|
|
282
|
+
|
|
283
|
+
with open(args.csv, "w", newline="") as f:
|
|
284
|
+
w = csv.DictWriter(f, fieldnames=fields)
|
|
285
|
+
w.writeheader()
|
|
286
|
+
for row in rows:
|
|
287
|
+
r = ratios[row["KEM"]]
|
|
288
|
+
w.writerow({
|
|
289
|
+
"kem": row["KEM"], "n": row["n"],
|
|
290
|
+
"request_ms": _ms(row["request"]),
|
|
291
|
+
"transfer_ms": _ms(row["transfer"]),
|
|
292
|
+
"finalize_ms": _ms(row["finalize"]),
|
|
293
|
+
"total_ms": _ms(row["TOTAL"]),
|
|
294
|
+
"pk_bytes": row["pk_bw"],
|
|
295
|
+
"ct_bytes": row["ct_bw"],
|
|
296
|
+
"total_bytes": row["pk_bw"] + row["ct_bw"],
|
|
297
|
+
"keygen_ms": _ms(r["keygen"]),
|
|
298
|
+
"obliv_sample_ms": _ms(r["sample"]),
|
|
299
|
+
"keygen_over_sample": round(r["keygen"] / max(r["sample"], 1), 2),
|
|
300
|
+
"ok": row["ok"], "secure": row["secure"],
|
|
301
|
+
})
|
|
302
|
+
print(f"\nCSV written to {args.csv} ({len(rows)} rows)")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# ── argument parsing / dispatch ──────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
def _add_run_args(p: argparse.ArgumentParser) -> None:
|
|
308
|
+
p.add_argument("--kem", default="ML-KEM-768",
|
|
309
|
+
help="KEM mechanism name, liboqs or custom (NTRU_FO, "
|
|
310
|
+
"GGH_FO); see `modularot list-kems` (default: ML-KEM-768)")
|
|
311
|
+
p.add_argument("--n", type=int, default=2,
|
|
312
|
+
help="number of branches, 1-out-of-n (default: 2)")
|
|
313
|
+
p.add_argument("--choice", type=int, default=0,
|
|
314
|
+
help="branch the receiver wants to learn (default: 0)")
|
|
315
|
+
p.add_argument("--message", action="append", metavar="MSG",
|
|
316
|
+
help="sender's message for one branch (utf-8 text); pass "
|
|
317
|
+
"once per branch, in branch order (default: "
|
|
318
|
+
"placeholder demo messages)")
|
|
319
|
+
p.add_argument("--message-file", action="append", metavar="PATH",
|
|
320
|
+
help="like --message but read raw bytes from a file; "
|
|
321
|
+
"pass once per branch, in branch order")
|
|
322
|
+
p.add_argument("--output", metavar="PATH",
|
|
323
|
+
help="write the receiver's decrypted message to this "
|
|
324
|
+
"file (default: printed to stdout)")
|
|
325
|
+
p.add_argument("--ggh-dim", type=int, metavar="DIM", default=200,
|
|
326
|
+
help="dimension for --kem GGH_FO (default: 200)")
|
|
327
|
+
p.add_argument("--ntrufo-n", type=int, metavar="N", default=701,
|
|
328
|
+
help="ring degree for --kem NTRU_FO (default: 701)")
|
|
329
|
+
p.add_argument("--no-security-check", action="store_true",
|
|
330
|
+
help="skip the adversarial curious-receiver check")
|
|
331
|
+
p.add_argument("--quiet", action="store_true",
|
|
332
|
+
help="print nothing but the raw decrypted message bytes")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _add_bench_args(p: argparse.ArgumentParser) -> None:
|
|
336
|
+
p.add_argument("--kems", nargs="+", default=None,
|
|
337
|
+
help="mechanisms to benchmark (default: representative subset)")
|
|
338
|
+
p.add_argument("--all", action="store_true",
|
|
339
|
+
help="benchmark every supported enabled mechanism")
|
|
340
|
+
p.add_argument("--ns", nargs="+", type=int, default=DEFAULT_NS,
|
|
341
|
+
help=f"branch counts (default: {DEFAULT_NS})")
|
|
342
|
+
p.add_argument("--reps", type=int, default=5,
|
|
343
|
+
help="repetitions per cell, median reported (default: 5)")
|
|
344
|
+
p.add_argument("--no-custom", action="store_true",
|
|
345
|
+
help="skip the custom NTRU_FO and GGH_FO KEMs (on by default)")
|
|
346
|
+
p.add_argument("--ggh-dim", type=int, metavar="DIM", default=200,
|
|
347
|
+
help="dimension for the custom GGH_FO KEM (default: 200; "
|
|
348
|
+
"teaching-scale, needs the GGH_crypto package)")
|
|
349
|
+
p.add_argument("--ntrufo-n", type=int, metavar="N", default=701,
|
|
350
|
+
help="ring degree for the custom NTRU_FO KEM (default: 701)")
|
|
351
|
+
p.add_argument("--csv", default="ot_scaling.csv",
|
|
352
|
+
help="CSV output path (default: ot_scaling.csv)")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
356
|
+
ap = argparse.ArgumentParser(
|
|
357
|
+
prog="modularot",
|
|
358
|
+
description="Modular KEM-based 1-out-of-n Oblivious Transfer (semi-honest)")
|
|
359
|
+
sub = ap.add_subparsers(dest="command", required=True)
|
|
360
|
+
|
|
361
|
+
rp = sub.add_parser("run", help="run one 1-out-of-n OT transfer")
|
|
362
|
+
_add_run_args(rp)
|
|
363
|
+
rp.set_defaults(func=cmd_run)
|
|
364
|
+
|
|
365
|
+
bp = sub.add_parser("bench", help="scaling benchmark across KEMs and branch counts")
|
|
366
|
+
_add_bench_args(bp)
|
|
367
|
+
bp.set_defaults(func=cmd_bench)
|
|
368
|
+
|
|
369
|
+
lp = sub.add_parser("list-kems", help="list KEM names usable with run/bench")
|
|
370
|
+
lp.set_defaults(func=cmd_list_kems)
|
|
371
|
+
|
|
372
|
+
return ap
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def main() -> None:
|
|
376
|
+
args = build_parser().parse_args()
|
|
377
|
+
args.func(args)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if __name__ == "__main__":
|
|
381
|
+
main()
|
|
File without changes
|
|
File without changes
|