Publish a scannable manual and a bibliography that links each paper back to the pages that use it.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
cf8054a0b3
commit
695f8e84f7
45 changed files with 4848 additions and 338 deletions
564
doc/pages/applications.md
Normal file
564
doc/pages/applications.md
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
# Application mockups {#applications}
|
||||
|
||||
These programs are the DPF step of a larger protocol.
|
||||
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
|
||||
|
||||
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
|
||||
`uint8→uint64`, two-leaf multileaf, wildcard assign, 16-bit `eval_until`,
|
||||
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 |
|
||||
| [SUBLEQ](@ref app_subleq) | Instruction fetch under a secret PC |
|
||||
| [BitMore](@ref app_bitmore) | `2^L` servers, one hot slot |
|
||||
| [Keyword PIR](@ref app_keyword) | Cuckoo buckets over keywords |
|
||||
| [Prio](@ref app_prio) | Heavy-hitter prefix walk |
|
||||
| [I-DPF max / k-th](@ref app_idpf_agg) | `eval_until` order statistics |
|
||||
| [LLAMA](@ref app_llama) | Lookup and truncation |
|
||||
| [Pika](@ref app_pika) | Comparison tree |
|
||||
| [Express](@ref app_express) | Authenticated memory |
|
||||
| [PRAC](@ref app_prac) | Range and prefix counts |
|
||||
| [Splinter](@ref app_splinter) | Function secret-sharing queries |
|
||||
| [Mastic](@ref app_mastic) | Aggregation |
|
||||
| [Waldo](@ref app_waldo) | Private search |
|
||||
| [Sabre](@ref app_sabre) | Robust aggregation |
|
||||
| [(2,3) ledger](@ref app_ledger23) | Three-party update |
|
||||
| [PSI](@ref app_psi) | Private set intersection |
|
||||
| [Range count](@ref app_range_count) | Interval payload |
|
||||
| [Floram](@ref app_floram) | ORAM read |
|
||||
| [Three-server PIR](@ref app_pir3) | Information-theoretic DPF |
|
||||
| [What the walk folds in](@ref application_gaps) | Library surface those programs used to do by hand |
|
||||
|
||||
|
||||
## 3-party Duoram {#app_duoram}
|
||||
|
||||
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.
|
||||
|
||||
The program uses one dealer unit key for the read and one payload key
|
||||
for the update.
|
||||
`eval_full_inner_product(dpf::paired, key, memory, dpf::rotate{s})` dots
|
||||
the unit key with the memory read at `(i + s) mod n`, so the caller keeps
|
||||
one unrotated vector.
|
||||
The update expands straight into the subtractive memory shares with
|
||||
`eval_full_add_into(mem, key, dpf::rotate{s})` — no separate expansion,
|
||||
shift, and add.
|
||||
A 1-bit leaf at the same index lifts to `+1` or `-1`; its sign share is
|
||||
recorded at keygen with `dpf::unit_sign`.
|
||||
A word payload of `1` opens to `+1`.
|
||||
|
||||
This is a `(2,2)` key.
|
||||
The auxiliary party holds some of those keys.
|
||||
[dpf::make_dpf3](@ref dpf/dpf3.hpp) shares one point Shamir-style among
|
||||
three evaluators.
|
||||
|
||||
\include{cpp} applications/duoram3.cpp
|
||||
|
||||
## MPC SUBLEQ {#app_subleq}
|
||||
|
||||
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
|
||||
|
||||
D[B] -= D[A]
|
||||
pc = (D[B] ≤ 0) ? C : pc + 3
|
||||
|
||||
The DPF work is prepaid. The dealer ships wildcard unit keys
|
||||
`[[* | 1]]` for the addresses that will be read (and for writing `B`).
|
||||
Each party expands those keys over the full address domain with
|
||||
`defer_eval_full` **before** the addresses are known — that is all of
|
||||
the PRG. Online, the parties open each address into `offset_x`, and
|
||||
`.get()` on the deferred view rotates the prepaid unit vector. A read
|
||||
is the Du-Atallah / local dot of that view with the memory shares. The
|
||||
write reuses the same `e_B` view: add `(-D[A]) · e_B` into the memory
|
||||
shares (a Beaver multiplication in the protocol; the listing opens the
|
||||
scale). The branch is a path evaluation only —
|
||||
`make_dpf(0, dpf::leq(1))` evaluated at `x = D'[B]` — never a full-domain
|
||||
expand of the word domain. The full protocol can instead assign a
|
||||
wildcard comparison key to `x` and evaluate at public `0` with `geq`
|
||||
(same bit).
|
||||
|
||||
Instruction fetch is the same prepaid unit dotted against three sliding
|
||||
windows of `D` (the three addresses of the instruction). The listing
|
||||
starts after `(A, B, C)` are already shares.
|
||||
|
||||
An equivalent prepaid form plants the unit at a random `r` and opens
|
||||
`addr - r`, then folds the shift with `dpf::rotate{s}` on
|
||||
`eval_full_inner_product` / `eval_full_add_into` (the Duoram / Pika
|
||||
surface). Same online AES; only the blinding convention differs.
|
||||
|
||||
Still by hand: Du-Atallah blinds, the ABY2 mux of `C` against `pc+3`,
|
||||
and out-of-bounds prefix-parity checks from the thesis.
|
||||
|
||||
\include{cpp} applications/subleq.cpp
|
||||
|
||||
## BitMore, `2^L` servers {#app_bitmore}
|
||||
|
||||
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`.
|
||||
`dpf::pack_bit_columns(keys...)` runs the full-domain bit walk once per
|
||||
key and writes lane `e` = key `e` into one integer per row, so the
|
||||
server reads `symbol[row]` instead of unpacking one `int` per bit.
|
||||
Off the secret row every server computes the same digit.
|
||||
On the secret row the digits are `symbol(0) XOR j`, a permutation of
|
||||
`0 .. ell-1`.
|
||||
When the server count `ℓ` is not a power of two,
|
||||
`dpf::mod_bit_columns<ℓ>(keys...)` is that same integer modulo `ℓ`.
|
||||
The first key is still the low bit. The information-theoretic
|
||||
virtual-bucket response starts from those digits.
|
||||
|
||||
`L = 1` is the two-server member of the same family.
|
||||
Each party XORs the records its bit selects.
|
||||
The two answers XOR to the record.
|
||||
|
||||
\include{cpp} applications/bitmore.cpp
|
||||
|
||||
## Keyword PIR {#app_keyword}
|
||||
|
||||
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.
|
||||
Each server walks the dictionary with `eval_sequence` and XORs the
|
||||
record where its bit share is set.
|
||||
Off the keyword the two bits match, so the record cancels.
|
||||
A keyword absent from the dictionary opens to 0.
|
||||
`eval_sequence` takes the dictionary in nondecreasing order.
|
||||
|
||||
A batch of keywords that should come back as separate records is one
|
||||
key per keyword.
|
||||
[dpf::make_multipoint](@ref dpf/multipoint.hpp) adds several secret
|
||||
points into one output. That is a histogram of hits, or a payload the
|
||||
client chose.
|
||||
That packing is de Castro–Polychroniadou (EUROCRYPT 2022): `t` points
|
||||
into `m ≈ O(t)` cuckoo buckets, each an ordinary GGM key.
|
||||
S&P 2025 (Boyle, Gilboa, Hamilis, Ishai, Tu) shrinks the dealer message
|
||||
with a silent OLE / PCG that expands a short seed into many correlated
|
||||
bucket keys.
|
||||
That is a different primitive (the same family as silent VOLE), not a
|
||||
new `multipoint_params` field, and this library does not ship a PCG
|
||||
stack.
|
||||
|
||||
\include{cpp} applications/keyword_pir.cpp
|
||||
|
||||
## Prio and the heavy-hitter prefix walk {#app_prio}
|
||||
|
||||
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.
|
||||
Each client keys a point at its bin with payload `1` in
|
||||
[dpf::field64](@ref dpf/field64.hpp), the same prime as libprio `Field64`.
|
||||
Each server folds a full-domain expansion into its running histogram
|
||||
share with `eval_full_add_into(hist, key)`.
|
||||
The opened bin is the count.
|
||||
Prio's validity proof for a general encoding is a SNIP.
|
||||
This program is the DPF encoding only.
|
||||
|
||||
The prefix walk is Poplar (Boneh, Boyle, Corrigan-Gibbs, Gilboa, and
|
||||
Ishai, [ePrint 2021/017](@ref bib_poplar)).
|
||||
[dpf::idpf](@ref dpf/placement.hpp) plants a `1` on prefix lengths 1, 2,
|
||||
and 3, counting from the high bit.
|
||||
Two clients hold `0xA0` and `0xB0`.
|
||||
They share the length-3 prefix `101` and split at the next bit.
|
||||
The servers evaluate `out<i, length>` at one representative of each node
|
||||
and add the opened values.
|
||||
|
||||
\include{cpp} applications/prio.cpp
|
||||
|
||||
## I-DPF max and k-th {#app_idpf_agg}
|
||||
|
||||
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
|
||||
inputs were summed.
|
||||
Each secret `uint16_t` is one [dpf::idpf](@ref dpf/placement.hpp) with a
|
||||
unit payload on every prefix length.
|
||||
Servers resume only the live prefixes with
|
||||
[dpf::eval_until](@ref dpf/eval_until.hpp).
|
||||
At each depth they open the two children: max keeps the nonempty
|
||||
`1`-child; the k-th largest takes the `1`-child when its count is at
|
||||
least `k`.
|
||||
`idpf_agg_max` / `idpf_agg_kth` in [dpf/idpf_agg.hpp](@ref dpf/idpf_agg.hpp)
|
||||
share that walk with the gtest.
|
||||
|
||||
\include{cpp} applications/idpf_agg.cpp
|
||||
|
||||
## LLAMA {#app_llama}
|
||||
|
||||
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`.
|
||||
The sign test is a comparison: `make_dpf(knot + r, dpf::gt(1))`,
|
||||
evaluated at `x_hat`.
|
||||
A degree-0 spline is one [dpf::ic](@ref dpf/interval.hpp) key per piece.
|
||||
The piece value is the payload.
|
||||
The same comparison on `int8_t` follows numeric order: `100 > -3` opens
|
||||
to 1.
|
||||
|
||||
\include{cpp} applications/llama.cpp
|
||||
|
||||
## Pika {#app_pika}
|
||||
|
||||
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
|
||||
`x = r - a`.
|
||||
The inner product of the DPF with the table read at `(i - x) mod n` is
|
||||
the table entry at `a`; `dpf::rotate{s}` folds that offset into the walk.
|
||||
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`.
|
||||
|
||||
\include{cpp} applications/pika.cpp
|
||||
|
||||
## Express {#app_express}
|
||||
|
||||
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.
|
||||
The client sends one key each.
|
||||
Each server adds its expansion into its share.
|
||||
The owner opens one address.
|
||||
An [extractable](@ref dpf/verifiable.hpp) sketch accepts the honest
|
||||
write and rejects a second hot mailbox.
|
||||
|
||||
The message is [dpf::fp61](@ref dpf/fp61.hpp) because that is the sketch
|
||||
field.
|
||||
Each server folds the expansion into its mailbox shares and the one-hot
|
||||
audit in one walk: `eval_full_add_into(box, key, dpf::sketch(s, r))`.
|
||||
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.
|
||||
|
||||
\include{cpp} applications/express.cpp
|
||||
|
||||
## PRAC {#app_prac}
|
||||
|
||||
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.
|
||||
|
||||
Binary search on a sorted array reads `lg n` locations.
|
||||
The accessible set at each depth is a public stride, and the index in
|
||||
the next stride is the previous index with one comparison bit appended.
|
||||
One [idpf](@ref dpf/placement.hpp) supplies every stride's unit vector.
|
||||
The program's array is `[1, 3, 5, 7, 9, 11, 13, 15]` and the needle is
|
||||
`10`. The public midpoint is index 3.
|
||||
`eval_prefix_inner_product(out<i, length>, key, stride)` walks to the
|
||||
prefix depth once and dots the `2^length` prefix shares with the stride,
|
||||
replacing one `eval_point` per stride slot. The prefix of length 1
|
||||
selects `11` from the stride `{1, 5}`. The prefix of length 2 selects
|
||||
`9` from `{0, 2, 4, 6}`. The bits `101` are the lower bound, index 5.
|
||||
|
||||
Heapify reads a parent and its two children, three strides at one
|
||||
index, with one unit key.
|
||||
The update at that index is a [dpf::vec](@ref dpf/vec.hpp) of three
|
||||
lanes.
|
||||
An incremental wide key is `idpf` of those vectors. That call already
|
||||
evaluates.
|
||||
|
||||
The dealer in this program knows the path and keys it up front.
|
||||
The protocol appends each comparison bit after the key exists.
|
||||
|
||||
\include{cpp} applications/prac.cpp
|
||||
|
||||
## Splinter {#app_splinter}
|
||||
|
||||
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.
|
||||
Each server dots the selector with a public column pre-aggregated by
|
||||
attribute, so `eval_full_inner_product(dpf::paired, key, group_sum)` is
|
||||
`SELECT SUM(value) WHERE attribute = secret`, and the all-ones column is
|
||||
`COUNT(*)`.
|
||||
Neither server learns the queried key.
|
||||
|
||||
Still by hand: `MAX` / `TOP-k` and multi-predicate conjunctions are not
|
||||
one selector DPF; Splinter composes several FSS instances for those.
|
||||
|
||||
\include{cpp} applications/splinter.cpp
|
||||
|
||||
## Mastic {#app_mastic}
|
||||
|
||||
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
|
||||
prefix length is its weight, not `1`.
|
||||
`eval_prefixes(out<i, length>, key)` returns the prefix shares; the
|
||||
servers sum them across clients and threshold to keep the heavy prefix.
|
||||
Two clients on `0xA0` and `0xB0` with weights 5 and 3 make prefix `101`
|
||||
heavy with total weight 8.
|
||||
`verify_idpf_path<Depth>` is the one-time VIDPF path check
|
||||
([path_sketch.hpp](@ref dpf/path_sketch.hpp)).
|
||||
|
||||
\include{cpp} applications/mastic.cpp
|
||||
|
||||
## Waldo {#app_waldo}
|
||||
|
||||
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
|
||||
servers' value shares with `eval_full_add_into`, never an update.
|
||||
A range/threshold aggregate uses the comparison channel:
|
||||
`eval_full_inner_product(dpf::cmp, key, magnitude)` dots the
|
||||
per-timestamp `gt` shares with a public magnitude column over the whole
|
||||
domain, so the SUM over timestamps past a *secret* threshold reveals
|
||||
neither the threshold nor the matches. The two halves open with
|
||||
`reconstruct_cmp_halves`.
|
||||
|
||||
Still by hand: Waldo's aggregation trees over several predicates, and
|
||||
range endpoints that are themselves secret-shared, compose more than one
|
||||
comparison key.
|
||||
|
||||
\include{cpp} applications/waldo.cpp
|
||||
|
||||
## Sabre {#app_sabre}
|
||||
|
||||
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`).
|
||||
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.
|
||||
|
||||
Still by hand: Sabre's blame / accountability phase that identifies a
|
||||
cheating client is protocol logic above the DPF proof.
|
||||
|
||||
\include{cpp} applications/sabre.cpp
|
||||
|
||||
## A (2,3) ledger {#app_ledger23}
|
||||
|
||||
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
|
||||
`verify_dpf3` over the three `prove_dpf3` tokens rejects an append that
|
||||
is not a single well-formed point before it is applied.
|
||||
Each server folds the point into its slot shares with
|
||||
`eval_full_add_into(shares, key)` (the (2,3) full-domain overload), and
|
||||
any two servers `shamir3::reconstruct` a balance.
|
||||
Two credits to slot 5 (100 then 7) open to 107.
|
||||
|
||||
Still by hand: the transaction / consensus layer around the append
|
||||
(ordering, replay protection) is protocol logic above the DPF step.
|
||||
|
||||
\include{cpp} applications/ledger23.cpp
|
||||
|
||||
## Private set intersection {#app_psi}
|
||||
|
||||
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.
|
||||
Each receiver element is a unit DPF.
|
||||
`eval_full_inner_product(dpf::paired, key, table)` is that server's
|
||||
share of `PRF(y)`.
|
||||
The sender publishes `PRF(x)` for each element of their own set.
|
||||
`y` is in the intersection when the opened tag appears in that image.
|
||||
|
||||
Still by hand: cuckoo hashing in the PSI application layer, and the GGM
|
||||
puncture that keeps a large domain at `O(n)` communication instead of a
|
||||
table. The multipoint cuckoo VDMPF stays the GGM packing above; S&P 2025
|
||||
DMPF seed packing needs a PCG this library does not provide.
|
||||
|
||||
\include{cpp} applications/psi.cpp
|
||||
|
||||
## Range count {#app_range_count}
|
||||
|
||||
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
|
||||
value, so the two endpoints differ by 1 exactly when
|
||||
`lo <= v < hi`.
|
||||
The count is the sum of those bits over the values.
|
||||
A neighbor just below `lo`, and the open end `hi`, contribute 0.
|
||||
|
||||
Still by hand: a secret interval over a public histogram is two
|
||||
comparison inner products (the Waldo aggregate, subtracted).
|
||||
This program is the other direction, secret values and a public range.
|
||||
|
||||
\include{cpp} applications/range_count.cpp
|
||||
|
||||
## Floram {#app_floram}
|
||||
|
||||
Doerner and shelat ([CCS 2017](@ref bib_ds)) read and write an array at a secret
|
||||
address.
|
||||
Both parties see the memory.
|
||||
The address is XOR-shared, so the key is
|
||||
[dpf::make_dpf_doerner_shelat](@ref dpf/doerner_shelat.hpp) rather than a
|
||||
dealer key.
|
||||
The read dots a unit payload with the array.
|
||||
The write adds a payload key from the same address shares into
|
||||
subtractive copies of the memory, with `eval_full_add_into`.
|
||||
|
||||
Still by hand: Floram's stash, position map, and refresh. This program
|
||||
is the FSS access.
|
||||
|
||||
\include{cpp} applications/floram.cpp
|
||||
|
||||
## Three-server PIR {#app_pir3}
|
||||
|
||||
The database is public and replicated on three servers.
|
||||
|
||||
The computational path is one (2,3) point key from
|
||||
[dpf::make_dpf3](@ref dpf/dpf3.hpp) ([ePrint 2024/1658](@ref bib_dpf3)).
|
||||
Each server dots its share with the database:
|
||||
`eval_full_inner_product(key, database)`.
|
||||
Any two of those dots `shamir3::reconstruct` to the record.
|
||||
|
||||
\include{cpp} applications/pir3.cpp
|
||||
|
||||
The information-theoretic path is
|
||||
[dpf::make_it_dpf3](@ref dpf/it_dpf3.hpp) ([ePrint 2023/028](@ref bib_itdpf)).
|
||||
Each server holds an additive share of the characteristic vector; the
|
||||
three dots sum to the record. Distinct from `make_dpf3`.
|
||||
|
||||
\include{cpp} applications/it_pir3.cpp
|
||||
|
||||
## What the walk now folds in {#application_gaps}
|
||||
|
||||
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).
|
||||
|
||||
## Shift, then add {#gap_shift}
|
||||
|
||||
Duoram and Pika rotate a vector by an opened offset, then dot.
|
||||
`dpf::rotate{s}` on `eval_full_inner_product` reads the weight at
|
||||
`(i + s) mod 2^n` inside the walk, so the caller keeps one unrotated
|
||||
vector. `dpf::cyclic_shift(buf, s)` rotates a materialized share buffer.
|
||||
Duoram's update, Prio's histogram, and Express's mailbox add the
|
||||
expansion into a buffer the caller already holds with
|
||||
`eval_full_add_into(buf, key)` (and a `dpf::rotate` or `dpf::sketch`
|
||||
overload). Express's audit folds in the same pass.
|
||||
|
||||
Still by hand: none for the deferred leaf.
|
||||
`dpf::leaf_later` expands without the leaf correction word and fills a
|
||||
parallel control-bit buffer. After `cyclic_shift_pair` (or
|
||||
`eval_full_add_into(..., leaf_later, rotate{s})`),
|
||||
`apply_leaf_correction(buf, control, F)` does `buf[i] += F * control[i]`.
|
||||
[Doerner–Shelat](@ref dpf/doerner_shelat.hpp) already builds a key from
|
||||
index shares the parties hold.
|
||||
|
||||
## The bit leaf's sign {#gap_sign}
|
||||
|
||||
Duoram's flag vector and Pika's early-stop leaf are 1-bit DPFs that lift
|
||||
to a ring unit of `+1` or `-1`. `dpf::make_dpf(alpha, dpf::bit::one,
|
||||
dpf::unit_sign{w0, w1})` records that sign at keygen — `w0 - w1` is the
|
||||
`±1` unit — from the final control bit `Gen` sees and one key hides.
|
||||
Absent the tag, keygen is unchanged.
|
||||
|
||||
## Answers that are XORs or bit columns {#gap_xor}
|
||||
|
||||
BitMore stacks `L` full-domain bit vectors and reads them as `L`-bit
|
||||
digits. `dpf::pack_bit_columns(keys...)` runs the bit walk once per key
|
||||
and writes one integer per row (lane `e` is key `e`).
|
||||
`dpf::mod_bit_columns<ℓ>(keys...)` reduces that integer modulo a server
|
||||
count `ℓ` from 2 through 32768, which is the digit when `ℓ` is not a
|
||||
power of two. The virtual-bucket response that consumes the digits stays
|
||||
outside the DPF layer.
|
||||
|
||||
Keyword PIR XORs dictionary records selected by a bit share with
|
||||
`eval_sequence_xor(key, begin, end, records)`, which folds that loop into
|
||||
the sequence walk.
|
||||
|
||||
## Prefixes {#gap_prefix}
|
||||
|
||||
Poplar and PRAC score every node at one depth.
|
||||
`eval_prefixes(out<i, length>, key)` returns the `2^length` prefix
|
||||
shares in one walk, and
|
||||
`eval_prefix_inner_product(out<i, length>, key, values)` dots them with
|
||||
a public vector, each replacing one `eval_point` per node.
|
||||
|
||||
`verify_idpf_path<Depth>` / `sketch_path_level` /
|
||||
`sketch_path_parent` ([path_sketch.hpp](@ref dpf/path_sketch.hpp))
|
||||
check that an incremental key is a single path: each depth is
|
||||
weight-1 (or one nonzero weight), and each parent equals the sum of its
|
||||
children. Leaf one-hot remains
|
||||
[sketch_fold](@ref dpf/verifiable.hpp).
|
||||
|
||||
## A growing index {#gap_stride}
|
||||
|
||||
PRAC's binary search key grows: the next comparison bit is appended to
|
||||
the index. `dpf::extend` / `dpf::add_output` realize
|
||||
[F_Grow](@ref ideal_functionalities): a dealer (or joint) view turns an
|
||||
existing `dpf_key` pair into a richer one. Specs are the same objects
|
||||
`make_dpf` accepts. Memoizer overloads skip the O(d) rewalk when both
|
||||
path memoizers are already filled through the frontier.
|
||||
`extend_ds` / `add_output_ds` realize [F_GrowDS](@ref ideal_functionalities):
|
||||
one Doerner–Shelat correction-word round on extend, or leaf-open only on
|
||||
`add_output`, from warm frontiers.
|
||||
|
||||
**Cost (dealer F_Grow).** O(d) rewalk of the spine, or O(1) seed reads
|
||||
with warm memoizers; one `make_cw` / `advance` on extend; one exterior
|
||||
leaf plant per new packing group. Zero rounds and zero bytes on the wire.
|
||||
|
||||
**Cost (F_GrowDS).** One interactive level on extend (blinds, CW share,
|
||||
advice, AND — same shape as one `point_party` level) plus leaf pads when
|
||||
planting. `local_cw_protocol` opens in-process (no wire). `add_output_ds`
|
||||
has no interior CW round.
|
||||
|
||||
A wide leaf is a `vec`.
|
||||
`idpf` of several `vec` payloads is the incremental wide key heapify
|
||||
uses for every level at once. That call already evaluates.
|
||||
|
||||
## Gates LLAMA still has to assemble {#gap_llama}
|
||||
|
||||
One comparison and one public interval are already gates, and an
|
||||
`int8_t` comparison follows numeric order.
|
||||
A spline with several pieces is several `dpf::ic` keys in the program.
|
||||
One key whose payload is the coefficient vector of the selected piece,
|
||||
with Horner on the shares and the public `x_hat`, is the Boyle gate
|
||||
LLAMA cites.
|
||||
`dpf::gt(hi, lo)` with both payloads a [dpf::vec](@ref dpf/vec.hpp)
|
||||
carries the vector. That is a wide comparison leaf.
|
||||
`dpf::gt(hi)` alone uses a zero of the same type as `hi`, so a
|
||||
`dpf::vec` false payload is the zero vector.
|
||||
Signed extension and truncate-reduce are
|
||||
`grotto::sign_extend` / `grotto::truncate_reduce` (wrappers over
|
||||
`make_carry_keys` + `eval_carry_extend` / `eval_carry_in`). Grotto's
|
||||
piecewise Horner evaluates a public polynomial from a point key. It is a
|
||||
different construction.
|
||||
|
||||
## Sketches on the group the protocol writes {#gap_sketch}
|
||||
|
||||
Express audits the same key that writes the mailbox.
|
||||
`dpf::blob<N>` is the XOR leaf for a row of `N` bytes. A caller fold
|
||||
`eval_full_add_into(buf, key, fold)` invokes `fold(index, share)` once
|
||||
per written output in the same walk; `dpf::sketch` remains the `fp61`
|
||||
weight-1 fold. Pika's malicious check is the same shape over `Z/2^k`,
|
||||
using their bilinear Schwartz–Zippel lemma.
|
||||
|
||||
`eval_full` on an extractable `fp61` key now matches `eval_point` on
|
||||
every lane, including both lanes of the packed leaf that holds the
|
||||
programmed point. Express folds its expansion and audit in one
|
||||
`eval_full_add_into(box, key, dpf::sketch(...))` walk.
|
||||
`extractable_full_test` pins the share-for-share match.
|
||||
|
||||
## Punctured PRF {#gap_pprf}
|
||||
|
||||
KKRT's PSI is cuckoo hashing plus a set check in the application. The
|
||||
library object is `dpf::pprf_master` / `dpf::puncture` / `dpf::pprf_eval`
|
||||
on the AES PRG, including a 128-bit domain. The programmed value at the
|
||||
punctured point is stored beside the puncture when the master holder
|
||||
computes it. The same walk over a set `H` returns a `dpf::pprf_copath`:
|
||||
every node whose parent lies on a path to `H` and which itself does not,
|
||||
with shared prefixes stored once. Empty `H` publishes the root; a
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue