Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
192
doc/pages/experiment.md
Normal file
192
doc/pages/experiment.md
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
# Logging, statistics, and experiments {#experiment_costs}
|
||||
|
||||
Paper and production runs need three things the bare key API does not:
|
||||
**leveled run logs**, **granular cost statistics**, and **replayable coins**.
|
||||
All three live in the same measurement stack —
|
||||
[`dpf/log.hpp`](@ref dpf/log.hpp), [`dpf/run_log.hpp`](@ref dpf/run_log.hpp),
|
||||
[`dpf::experiment`](@ref dpf/experiment.hpp), and the
|
||||
[`prg::count`](@ref dpf/prg_count.hpp) / [`thread_work`](@ref dpf/thread_work.hpp)
|
||||
counters.
|
||||
|
||||
Uninstrumented code stays quiet and keeps reading `/dev/urandom`. Logging and
|
||||
experiments are opt-in. `run_measured`, the battery, and `party_node` call
|
||||
[`app::start_logging`](@ref dpf/run_log.hpp) and install an experiment covering
|
||||
the thread that constructs it. `run_parties`, `measure_plan`, and the battery
|
||||
give party `i` its own stream, `ex.derive_party(i)`, whose master is SHA-256 of
|
||||
the experiment's master and `i`, and they use it for every trial, warmup
|
||||
included. A kernel handed to a compute pool draws from its party's stream.
|
||||
Replaying the master replays every party, as long as each party makes the same
|
||||
draws in the same order. The party masters are noted as `p0/master`,
|
||||
`p1/master`, and so on.
|
||||
|
||||
## Run log {#run_log}
|
||||
|
||||
[`dpf/log.hpp`](@ref dpf/log.hpp) writes one `key=value` line per event to
|
||||
stderr, syslog, or a file. Nothing is written until `log::configure` runs
|
||||
(normally through `app::start_logging`), so library code can log freely and a
|
||||
test that never configures the log stays quiet. Every line starts with
|
||||
`ts`, `lvl`, `inv` (invocation id), `pid`, `tid`, then `role=<party>` when the
|
||||
thread has one, then `ev=<event>` and the event's fields. Seed bytes go through
|
||||
`record::seed` as hex, as a SHA-256 fingerprint, or not at all.
|
||||
|
||||
[`app::start_logging`](@ref dpf/run_log.hpp) turns it on and writes the
|
||||
provenance banner first, at `info`:
|
||||
|
||||
| Event | Contents |
|
||||
| --- | --- |
|
||||
| `ev=start` | program, cwd, UTC/local start, invocation id shared with `runs.csv` |
|
||||
| `ev=build` | git rev (`LIBDPF_GIT_REV`), compiler, `NDEBUG`, ISA, sanitizers |
|
||||
| `ev=host` | hostname, kernel, CPU model, affinity, governor, turbo, memory, load; `ev=if` per interface |
|
||||
| `ev=argv` | shell-quoted command line |
|
||||
| `ev=env` | `DPF_*` / `LIBDPF_*` and perf-relevant vars (`LD_PRELOAD`, …) |
|
||||
| `ev=config` | every `run_config` setting |
|
||||
|
||||
After that come seeds with their source, listeners and links (endpoints, peer
|
||||
authentication, encryption, applied socket options, kernel RTT), each party's
|
||||
plan and costs, trial statistics, and failures. `ev=end` at exit gives elapsed
|
||||
time. The settings are `run_config` keys:
|
||||
|
||||
```
|
||||
DPF_LOG_LEVEL=debug DPF_LOG=file:/tmp/run.log,stderr DPF_LOG_SEEDS=hash
|
||||
--log_level=debug --log=syslog --log_seeds=off
|
||||
```
|
||||
|
||||
Levels are `silent`, `error`, `warning`, `info` (the default), `debug`, and
|
||||
`trace`. With `log_seeds=full` the log holds every master, so anyone who has
|
||||
the file can regenerate that party's randomness. `hash` prints a SHA-256
|
||||
fingerprint instead.
|
||||
|
||||
## Replayable seeds
|
||||
|
||||
```cpp
|
||||
dpf::experiment ex("keyword_pir"); // fresh 32-byte master
|
||||
auto master = ex.seed(); // record for the paper
|
||||
auto x = dpf::uniform_sample<std::uint64_t>();
|
||||
|
||||
auto again = dpf::experiment::replay("keyword_pir", master);
|
||||
assert(dpf::uniform_sample<std::uint64_t>() == x);
|
||||
```
|
||||
|
||||
While the experiment is installed, every `uniform_fill` / `uniform_sample`
|
||||
draw on that thread (DPF roots, Beaver default sampler, pads, Shamir, IKNP,
|
||||
field rejection) comes from AES-CTR of the master. The master itself is always
|
||||
drawn from OS entropy, even when nested under another experiment.
|
||||
|
||||
Constructors that already own a seed note it automatically:
|
||||
|
||||
| Type | Seed name |
|
||||
| --- | --- |
|
||||
| (master) | `master` |
|
||||
| `beavers::oracle` | `beavers::oracle` (+ `lane_table`) |
|
||||
| `buffered_prg` | `buffered_prg` |
|
||||
| `prg_pad_rng` | `prg_pad_rng` |
|
||||
| `pseudorandom_root_sampler` | `pseudorandom_root_sampler` |
|
||||
|
||||
Call `ex.note_seed("label", value)` for anything else. Replay is
|
||||
**order-sensitive**: the same party must make the same `uniform_sample` calls.
|
||||
Indexed `beavers::oracle(seed)` stays the seekable Beaver path; its seed still
|
||||
appears in the report when constructed under the experiment.
|
||||
|
||||
## Measuring a compose plan {#statistics}
|
||||
|
||||
```cpp
|
||||
auto plan = dpf::protocol::fss_point_plan(0);
|
||||
auto ex = dpf::app::measure_plan("fss_point", plan);
|
||||
ex.write_csv("/tmp/run1");
|
||||
```
|
||||
|
||||
Or from an application sketch:
|
||||
|
||||
```cpp
|
||||
// Prints rounds, live bytes, wall/CPU, PRG evals, random bytes, seed hex.
|
||||
// Set DPF_EXPERIMENT_DIR=/tmp/run1 to also emit CSVs.
|
||||
dpf::app::run_measured("keyword_pir",
|
||||
dpf::protocol::keyword_pir_compose_plan(0, depth), 2);
|
||||
```
|
||||
|
||||
What is recorded:
|
||||
|
||||
| Metric | Source |
|
||||
| --- | --- |
|
||||
| Interactive rounds / DAG depth | `plan::exchange_waves()` / `plan::waves()` |
|
||||
| Critical path | back-walk of max-`wave_of` inputs |
|
||||
| Schedule bytes per edge | `slot_bytes` + `wave_channel` |
|
||||
| Bytes out/in per round | the round's send and receive slots, on its own channel |
|
||||
| Wall / CPU | `steady_clock` / party 0's thread CPU plus its pool kernels' CPU |
|
||||
| Symmetric-key blocks | `prg::count(purpose, primitive)` per thread |
|
||||
| Random bytes | `uniform_fill` TLS counter |
|
||||
| Seeds | master + every `note_experiment_seed` |
|
||||
|
||||
Symmetric-key blocks are counted by purpose and primitive. The purposes are
|
||||
`expand` (tree expansion, leaf conversion, label and column PRGs), `hash`
|
||||
(IKNP row hashes, garbled-gate hashes), and `harness` (the experiment's own
|
||||
seed stream). The primitives are AES-128, AES-256, ChaCha, and LowMC.
|
||||
`prg_evals` is `expand` plus `hash` over every primitive. Harness blocks are
|
||||
never part of it. The counters are per thread; a kernel a party hands to a
|
||||
compute pool (`compute_threads > 0`) is charged to that party when it returns
|
||||
([`dpf/thread_work.hpp`](@ref dpf/thread_work.hpp)).
|
||||
|
||||
## CSV layout
|
||||
|
||||
`write_csv(dir)` appends. Every row ends with the id of the process that
|
||||
wrote it (`invocation`), so rows from different runs into one directory stay
|
||||
apart. A file whose header differs from this layout is renamed to
|
||||
`<name>.before-<UTC>.csv` before new rows go in.
|
||||
|
||||
- `summary.csv` — one row per run, including `master_seed` hex. `wall_ns`,
|
||||
`cpu_ns`, and the counters come from the last (instrumented) trial;
|
||||
`median_ns` and `slowest_median_ns` are medians over the timed trials for
|
||||
party 0 and for the slowest party.
|
||||
- `rounds.csv` — per-round deltas and edge name
|
||||
- `edges.csv` — protocol totals per peer / rss_next / dealer
|
||||
- `seeds.csv` — every noted seed as hex
|
||||
- `critical_path.csv` — node id, wave, opcode, effect
|
||||
- `config.csv` — every `run_config` setting
|
||||
- `trials.csv` — every party's wall time for each timed trial
|
||||
- `wire.csv` — party 0's link counters, headers included
|
||||
- `sym.csv` — symmetric-key blocks by purpose and primitive
|
||||
- `runs.csv` — the invocation id that ties rows to the run log, and whether
|
||||
the master was fresh, provided, or derived
|
||||
|
||||
Before any party starts its clock, all parties wait at a start gate until
|
||||
every one has finished setup.
|
||||
|
||||
## The battery
|
||||
|
||||
The harness is `party_bench` and `profile_party`. Both spawn `p0`, `p1`, and
|
||||
`p2` and dial [`net::trio`](@ref dpf/net/trio.hpp), the library's localhost
|
||||
mesh (`p0-p1`, `p0-p2`, `p1-p2`). A flow's bytes and rounds are the frames on
|
||||
that mesh. The repeat barrier is not included.
|
||||
|
||||
```
|
||||
party_bench --tag bench --repeat 3 --warmup 1
|
||||
party_bench --case arith_mul_p11 --repeat 5
|
||||
profile_party --suite gadget --repeat 3 --warmup 1
|
||||
```
|
||||
|
||||
`--tag bench` is the default list. The gadget rows are tagged `bench` as well,
|
||||
so they sit on that list. `profile_party --suite gadget` runs only those rows.
|
||||
`profile_party --suite all` appends them after the core and extreme suites.
|
||||
|
||||
A secret index stays a DPF. The gadget rows time what you do after a key, or
|
||||
on shares the parties already hold:
|
||||
|
||||
- `arith_proj_*`, `arith_mul_*`, `arith_thresh_*`, `arith_chain_mul4` —
|
||||
Ball–Malkin–Rosulek word garbling. Party 0 sends the evaluator view on the
|
||||
p0–p1 link. Party 1 evaluates that view and opens.
|
||||
- `yao_if_*`, `yao_onehot_*` — stacked and one-hot garbling on the garbler.
|
||||
The active branch's tables and the evaluator's labels go across the p0–p1
|
||||
channel, and party 1 evaluates those bytes. Base OT stays on the `iknp` tag.
|
||||
- `flute_d*` — FLUTE. Party 2 deals the mask shares. Parties 0 and 1 exchange
|
||||
the online bits and open.
|
||||
- `shuffle_n*` — three hidden-shuffle passes. Each pass's array is sent on
|
||||
the ring and consumed as the next party's inbound. Party 0 opens the sum.
|
||||
|
||||
Sizes are the modulus, the branch width, the table width, and the column
|
||||
length. The manual pages are [a small word](@ref arith_garble_word),
|
||||
[a public table](@ref flute_lut), [a secret branch](@ref yao_stack), and
|
||||
[an array of shares](@ref share_shuffle).
|
||||
|
||||
See also [Network, parties, and MPC](@ref network_and_mpc),
|
||||
[Protocol composition](@ref protocol_compose), and the
|
||||
[application mockups](@ref applications).
|
||||
Loading…
Add table
Add a link
Reference in a new issue