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:
Ryan Henry 2026-09-28 05:59:19 -06:00
parent 695f8e84f7
commit 0d22946a0e
1835 changed files with 170291 additions and 2849 deletions

View file

@ -5,9 +5,18 @@ Each one is a single process.
A dealer stands in where the paper generates keys from shares.
They compile from the repository root:
c++ -std=c++17 -march=native -I include -I thirdparty examples/applications/duoram3.cpp
c++ -std=c++17 -march=native -pthread -I include -I thirdparty examples/applications/duoram3.cpp
The online compose sketch at the end of each file uses
[`dpf::app::run_measured`](@ref dpf/app_flow.hpp): it prints rounds, live
bytes, wall/CPU, PRG evals, random bytes, and the master seed hex. Set
`DPF_EXPERIMENT_DIR=/tmp/run` to also write the CSV tables described in
[Experiments](@ref experiment_costs).
The same compile line with the other filenames builds the rest. Timing for
the party protocols, including word garbling, stacked branches, FLUTE, and
the hidden column reorder, is `party_bench` / `profile_party` (see
[The battery](@ref experiment_costs)).
The same line with the other filenames builds the rest.
Optional Python bindings configure with `-DLIBDPF_PYTHON=ON` in the test
build directory, then `make pydpf` (and `make pydpf_pytest`).
`pydpf` exposes point / interval / full / sequence / recipe eval on
@ -16,7 +25,6 @@ and `it_dpf3`. Not in this module yet: geneval, Doerner–Shelat, VDPF
`prove`/`sketch`, DCF, or grotto.
What those programs had to do by hand is [the library surface underneath](@ref application_gaps).
| Sketch | What the DPF step is |
| --- | --- |
| [3-party Duoram](@ref app_duoram) | Unit key, rotate, inner product |
@ -38,17 +46,25 @@ What those programs had to do by hand is [the library surface underneath](@ref a
| [Range count](@ref app_range_count) | Interval payload |
| [Floram](@ref app_floram) | ORAM read |
| [Three-server PIR](@ref app_pir3) | Information-theoretic DPF |
| [Protocol composition](@ref protocol_compose) | Fused walks, early-stop, RSS refresh, ABY scale |
| PIRsona fetch | BitMore star upload+answer (`pirsona_fetch.cpp`) |
| Hushmap ADD | Dealer tape + two opens (`hushmap_add.cpp`) |
| [What the walk folds in](@ref application_gaps) | Library surface those programs used to do by hand |
## 3-party Duoram {#app_duoram}
\htmlonly
<div class="eli5"><b>ELI5.</b> Preprocessing plants a unit DPF at a random index r. Online the parties open i* − r, rotate the expanded unit vector by that public shift, and dot with the memory. The update rotates a payload vector the same way and adds it in.</div>
\endhtmlonly
Vadapalli, Henry, and Goldberg ([USENIX Security 2023](@ref bib_duoram)) keep a memory in
shares and read or add at a secret index.
Preprocessing builds unit DPFs at a random index `r`.
Online, the parties open `i* - r` and cyclic-shift the expanded vector.
The read is the dot product of that vector with the memory.
The update adds a payload vector, shifted the same way.
Opening the whole memory, rather than one index, is a hidden shuffle of
that column ([an array of shares](@ref share_shuffle)), not another DPF.
The program uses one dealer unit key for the read and one payload key
for the update.
@ -71,6 +87,10 @@ three evaluators.
## MPC SUBLEQ {#app_subleq}
\htmlonly
<div class="eli5"><b>ELI5.</b> The address is not known when the keys are built, so the unit vectors are expanded early into a deferred buffer. Online, opening the address rotates that buffer. The read is a dot with memory; the write adds the scaled unit vector back.</div>
\endhtmlonly
Jiang and Henry ([MSc thesis, University of Calgary](@ref bib_subleq)) emulate the
subtract-and-branch-if-less-than-or-equal-to-zero (SUBLEQ) OISC for
private function evaluation. One instruction is
@ -109,6 +129,10 @@ and out-of-bounds prefix-parity checks from the thesis.
## BitMore, `2^L` servers {#app_bitmore}
\htmlonly
<div class="eli5"><b>ELI5.</b> The label is L bits. Each bit is its own 1-bit DPF, expanded over the whole domain. Stacking the L bit-vectors and reading a column produces the 2^L-server answer for that label.</div>
\endhtmlonly
Hafiz and Henry ([PoPETs 2019](@ref bib_bitmore), §5.2) query `ell = 2^L` servers with `L`
independent 1-bit DPFs, all at the same row.
Server `j` receives key number `j_e` from DPF `e`.
@ -131,6 +155,10 @@ The two answers XOR to the record.
## Keyword PIR {#app_keyword}
\htmlonly
<div class="eli5"><b>ELI5.</b> The keyword is hashed into cuckoo buckets. Each bucket is a point key, and the record is the inner product of the probed buckets with the dictionary. The S&amp;P 2025 seed-packing of those buckets is not what this program does.</div>
\endhtmlonly
Gilboa and Ishai ([EUROCRYPT 2014](@ref bib_dpf2014)) retrieve one record by a keyword.
[dpf::keyword](@ref dpf/keyword.hpp) is the domain, so the DPF point is
the keyword itself.
@ -158,6 +186,10 @@ stack.
## Prio and the heavy-hitter prefix walk {#app_prio}
\htmlonly
<div class="eli5"><b>ELI5.</b> A one-hot vote is a unit DPF in the Prio field. Each server adds the expanded vector into a running histogram. The heavy-hitter pass is the same key read at successive prefixes: the servers add the opened prefix shares and keep the heavy nodes.</div>
\endhtmlonly
Corrigan-Gibbs and Boneh ([NSDI 2017](@ref bib_prio)) aggregate client encodings.
A frequency count is a one-hot vector.
A unit DPF is that vector, compressed.
@ -182,6 +214,10 @@ and add the opened values.
## I-DPF max and k-th {#app_idpf_agg}
\htmlonly
<div class="eli5"><b>ELI5.</b> Each secret integer is one incremental DPF with a unit payload on every prefix. At each bit the servers open the two children. Max keeps the child that holds mass. The k-th keeps the 1-child when its count covers k, and otherwise descends the 0-child with k reduced.</div>
\endhtmlonly
Cheng, Mitrokotsa, Zhang, and Hartmann ([ePrint 2024/1190](@ref bib_idpfagg)) aggregate
secret values with an incremental DPF.
Communication tracks the bit length of the domain, not how many secret
@ -200,6 +236,10 @@ share that walk with the gtest.
## LLAMA {#app_llama}
\htmlonly
<div class="eli5"><b>ELI5.</b> The comparison is the gate. eval_point on a gt or lt key opens to the payload when the public query is on the true side of the secret, including across the sign bit, because signed inputs flip the high bit before the walk.</div>
\endhtmlonly
Gupta, Kumaraswamy, Chandran, and Gupta ([ePrint 2022/793](@ref bib_llama)) evaluate a
nonlinear gate from a dealer key and one opened masked input
`x_hat = x + r`.
@ -214,6 +254,10 @@ to 1.
## Pika {#app_pika}
\htmlonly
<div class="eli5"><b>ELI5.</b> The dealer keys a unit DPF at a fresh r and the parties open x = r − a. Rotating the public table by x and dotting with the DPF reads the entry at a. The sign of an early-stop bit leaf is recorded at keygen, so the evaluators never open r.</div>
\endhtmlonly
Wagh ([PoPETs 2022](@ref bib_pika), Fig. 1) looks up `Func(a)` in a table of a bounded
domain.
The dealer keys a unit DPF at a fresh index `r` and the parties open
@ -224,11 +268,17 @@ A word payload of `1` opens to `+1`.
The paper's early-stop bit leaf opens to `+1` or `-1`; the dealer records
that sign at keygen with `dpf::unit_sign` (the final control bit `Gen`
sees), so the evaluators never open `r`.
A networked early-stop *walk* (BGI Remark 3.4) drops ν CW rounds with
`fss_point_early_stop` on a [composer](@ref protocol_compose).
\include{cpp} applications/pika.cpp
## Express {#app_express}
\htmlonly
<div class="eli5"><b>ELI5.</b> A mailbox write is a full-domain add of one DPF into the shared array. Every box is touched by the expand; only the programmed box survives when the shares are opened.</div>
\endhtmlonly
Eskandarian, Corrigan-Gibbs, Zaharia, and Boneh ([USENIX Security 2021](@ref bib_express),
§3.1) write one mailbox.
Two servers hold subtractive shares of the mailboxes.
@ -246,10 +296,19 @@ The extractable full-domain leaf now matches point evaluation on every
lane, so this one call replaces the earlier `eval_point`-per-address
loop and the separate `sketch_fold` pass.
On a networked walk the audit share rides in the last correction-word
flush — `fss_point_fused` / `level_walk_fused` on a
[composer](@ref protocol_compose) — so the sketch does not add a round.
The same shape is Sabre's proof token.
\include{cpp} applications/express.cpp
## PRAC {#app_prac}
\htmlonly
<div class="eli5"><b>ELI5.</b> Binary search needs a unit vector on a stride that grows by one bit per comparison. One incremental key holds all of those prefixes, and a prefix inner product dots a stride without a separate point key per slot. A heap update is a three-lane vector at the parent and its two children.</div>
\endhtmlonly
Sasy, Vadapalli, and Goldberg ([ePrint 2023/1897](@ref bib_prac)) run dynamic data
structures on a 3-party Duoram.
The new DPF shapes are an incremental key and a wide leaf.
@ -280,6 +339,10 @@ The protocol appends each comparison bit after the key exists.
## Splinter {#app_splinter}
\htmlonly
<div class="eli5"><b>ELI5.</b> The secret WHERE value is a unit DPF. The server dots it with a column that was already summed by attribute, which is the grouped SUM, and with an all-ones column, which is the COUNT. The queried attribute is not revealed.</div>
\endhtmlonly
Wang, Yun, Goldwasser, Vaikuntanathan, and Zaharia ([NSDI 2017](@ref bib_splinter)) answer
private queries on public data with two-server FSS.
The client's private `WHERE` value is a unit DPF at that attribute.
@ -296,6 +359,10 @@ one selector DPF; Splinter composes several FSS instances for those.
## Mastic {#app_mastic}
\htmlonly
<div class="eli5"><b>ELI5.</b> This is the Poplar prefix walk with a weight instead of 1 on every prefix. Servers sum the prefix shares across clients and drop prefixes under the threshold. A path sketch can check that each client programmed a single path.</div>
\endhtmlonly
Mastic (private weighted heavy-hitters and attribute-based metrics) is
Poplar's prefix walk with a weight payload.
Each client keys an [idpf](@ref dpf/placement.hpp) whose β on every
@ -311,6 +378,10 @@ heavy with total weight 8.
## Waldo {#app_waldo}
\htmlonly
<div class="eli5"><b>ELI5.</b> Each append is a fresh unit DPF added into the value shares; old events are not rewritten. A threshold query is a comparison inner product of those shares with a public magnitude column, so the sum past a secret threshold does not reveal the threshold or the matches.</div>
\endhtmlonly
Dauterman, Rathee, Popa, and Stoica ([S&P 2022](@ref bib_waldo)) build a private time-series
database from FSS.
The store is append-only: each event is a fresh unit DPF folded into the
@ -330,6 +401,10 @@ comparison key.
## Sabre {#app_sabre}
\htmlonly
<div class="eli5"><b>ELI5.</b> The write is the same full-domain add as Express. The audit folds a constant-size proof token along that key. verify accepts one honest point and rejects a key that was hot in more than one place.</div>
\endhtmlonly
Vadapalli, Storrier, and Henry ([S&P 2022](@ref bib_sabre)) send anonymous messages with a
fast audit.
The write is Express's full-domain add (`eval_full_add_into`).
@ -337,6 +412,8 @@ The audit is a *verifiable* DPF proof rather than Express's `fp61`
sketch: `prove_full(key, dpf::prove(π))` folds a constant-size token per
party, and `dpf::verify(π0, π1)` accepts an honest single-point write and
rejects the mismatched fold a multi-point key produces.
Networked audits pack the proof share into the last CW with
`fss_point_fused` ([protocol composition](@ref protocol_compose)).
Still by hand: Sabre's blame / accountability phase that identifies a
cheating client is protocol logic above the DPF proof.
@ -345,6 +422,10 @@ cheating client is protocol logic above the DPF proof.
## A (2,3) ledger {#app_ledger23}
\htmlonly
<div class="eli5"><b>ELI5.</b> An append is one verifiable three-party point at the slot. The three proof tokens are checked before the point is added into the slot shares. Any two servers reconstruct a balance.</div>
\endhtmlonly
A replicated ledger held as (2-of-3) shares by three servers, on this
group's `dpf3` VDPF+ construction.
Each append is one `make_dpf3(slot, amount, dpf::verifiable{})`; a
@ -362,6 +443,10 @@ Still by hand: the transaction / consensus layer around the append
## Private set intersection {#app_psi}
\htmlonly
<div class="eli5"><b>ELI5.</b> Each element on one side is a unit DPF. Dotting it with the other side's table is the membership test. The cuckoo layout and the OPRF that would sit around that test are not in the DPF call.</div>
\endhtmlonly
Kolesnikov, Kumaresan, Rosulek, and Trieu ([CCS 2016](@ref bib_kkrt)) test membership
with an oblivious PRF.
On this domain the PRF is a table both servers hold.
@ -380,6 +465,10 @@ DMPF seed packing needs a PCG this library does not provide.
## Range count {#app_range_count}
\htmlonly
<div class="eli5"><b>ELI5.</b> A value falls in [lo, hi) when the greater-than bit at hi and the greater-than bit at lo differ. Each secret value is one comparison key. The count is the sum of those opened bits.</div>
\endhtmlonly
Each secret value is one comparison.
The interval `[lo, hi)` is public.
`eval_point(dpf::cmp, key, q)` opens to 1 when `q` is strictly above the
@ -396,6 +485,10 @@ This program is the other direction, secret values and a public range.
## Floram {#app_floram}
\htmlonly
<div class="eli5"><b>ELI5.</b> The address is shared, not known to a dealer. The Doerner–Shelat opening builds the unit key level by level, and the read or write is the inner product of that key with the array.</div>
\endhtmlonly
Doerner and shelat ([CCS 2017](@ref bib_ds)) read and write an array at a secret
address.
Both parties see the memory.
@ -413,6 +506,10 @@ is the FSS access.
## Three-server PIR {#app_pir3}
\htmlonly
<div class="eli5"><b>ELI5.</b> All three servers hold the database. A Shamir DPF3 key makes each server return an inner product; any two of those field elements open the record. The information-theoretic key instead adds all three inner products.</div>
\endhtmlonly
The database is public and replicated on three servers.
The computational path is one (2,3) point key from
@ -432,8 +529,21 @@ three dots sum to the record. Distinct from `make_dpf3`.
## What the walk now folds in {#application_gaps}
\htmlonly
<div class="eli5"><b>ELI5.</b> Rotate-then-dot, the sign of a bit leaf, bit columns, prefix dots, and path sketches used to be loops around eval. They are parameters of one walk now.</div>
\endhtmlonly
The calls the eight programs used to build by hand are now the library
surface. See [dpf/eval_walk.hpp](@ref dpf/eval_walk.hpp).
Multi-protocol *schedules* (FSS + ABY + RSS on one RoundSink) are
[protocol composition](@ref protocol_compose). Each listing under
`examples/applications/` records that paper's online flow on a
`composer` and calls `dpf::app::run`, which drives both parties on an
in-process sink and prints `name rounds= bytes=`. That line is the
experiment: compare it with the round and bandwidth column of the
paper. PIR listings are a client and two or three servers (one upload
round, one answer round). The servers do not open shares with each
other.
## Shift, then add {#gap_shift}
@ -562,3 +672,7 @@ full-domain `H` publishes nothing. The walk is O(|H| · n) and never
materializes the domain. An audit opening of a replica-seed pool is that
copath with `program_hidden = false`, so the live seeds stay out. The
one-point layout stays for PSI; `{α}` with programming agrees with it.
\htmlonly
<div class="tldr"><b>TL;DR.</b> Each program is only the DPF step, in one process, with a dealer standing in for shared keygen. Memory reads are a unit vector, a public shift or a prefix, and a dot. PIR and grouped sums are inner products. Heavy hitters are prefix walks. Mailbox writes and the ledger are full-domain adds, plus a proof when the write must be a single point.</div>
\endhtmlonly