libdpf/doc/pages/arith.md
Ryan Henry 0d22946a0e 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>
2026-09-28 05:59:19 -06:00

151 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Arithmetic share runtime {#arith_runtime}
\htmlonly
<div class="eli5"><b>ELI5.</b> Beside FSS keys you get ordinary secret shares: add them locally, multiply with Beaver or RSS, flip between bits and numbers with edaBits, and truncate fixed-point products. The composer schedules those opens next to FSS walks.</div>
\endhtmlonly
Semi-honest 2PC and honest-majority 3PC. Openings may carry Shark/SPDZ IT-MACs
from the beaver session. A leaf that must enter a boolean netlist uses
[b2y / a2y](@ref yao_leaf) and comes back with `y2b` / `y2a`. There is no
Yao domain on the composer.
## Domains
| Domain | Meaning |
| --- | --- |
| `a` / `b` / `fss` | Existing additive, subtractive, FSS leaf |
| `rss` / `y` | Existing replicated / product factor |
| `bin` | (2,2) XOR bit shares (packed) |
| `bin_rss` | (2,3) replicated bits |
`composer::as` never casts to or from `bin` / `bin_rss`. Use `bin_a2b` /
`bin_b2a` / `bin_inject` (see [compose.hpp](@ref dpf/compose.hpp) opcodes
400–405).
## Building blocks
| Header | Role |
| --- | --- |
| [rss_seed.hpp](@ref dpf/rss_seed.hpp) | Pairwise PRG seeds, zero-sharing, RSS mul local |
| [ot_pack.hpp](@ref dpf/ot_pack.hpp) | Consumable B2A / bit pads or dealer dabits |
| [edabit.hpp](@ref dpf/edabit.hpp) | daBits and edaBits; A2B |
| [bit_inject.hpp](@ref dpf/bit_inject.hpp) | Bit × arithmetic (2PC / RSS) |
| [trunc.hpp](@ref dpf/trunc.hpp) | Probabilistic and exact truncate; mul_trunc |
| [share_cmp.hpp](@ref dpf/share_cmp.hpp) | Share–share compare, ReLU, max, range, div |
| [share_vec.hpp](@ref dpf/share_vec.hpp) | Lane-block share vectors |
| [fixed_share.hpp](@ref dpf/fixed_share.hpp) | `fixed<Int,Frac>` |
| [share_expr.hpp](@ref dpf/share_expr.hpp) | Imperative recorder |
| [gilboa.hpp](@ref dpf/gilboa.hpp) | One-off product / tape fill |
| [matmul.hpp](@ref dpf/matmul.hpp) | Matrix triples and matmul |
| [shuffle.hpp](@ref dpf/shuffle.hpp) | Hidden reorder of an RSS column. A secret index stays a DPF |
| [arith_garble.hpp](@ref dpf/arith_garble.hpp) | Free add and a unary projection, after a word has left the key |
| [flute.hpp](@ref dpf/flute.hpp) | Public table on short masked bits. A DPF point stays a key |
| [cost_pass.hpp](@ref dpf/cost_pass.hpp) | DCF vs edaBit vs A2B strategy |
| [yao.hpp](@ref dpf/yao.hpp) | Half-gates netlist on a leaf's XOR bits. Party 0 garbles |
| [yao_share.hpp](@ref dpf/yao_share.hpp) | Leaf share to those bits and back (`b2y`, `a2y`, `fss2y`, `rss2y`) |
| [yao_aes.hpp](@ref dpf/yao_aes.hpp) | The PRG's zero-key MMO block, and AES-128 under a shared key |
## When to use which comparison
- **One input public or keyed:** DCF / `geneval_*` / interval keys.
- **Both inputs arithmetic shares:** `share_cmp` (mask, open, MSB / DCF at the public difference).
- **The leaf must enter a deep bit circuit:** [Yao](@ref yao_leaf). Not a comparison, a mux, or a public-offset LUT.
## A small word after the key {#arith_garble_word}
Comparisons, intervals, and public-offset polynomials stay on the key.
[Grotto](@ref jet_and_ring) covers a public offset. This section is the
word you already hold as shares, when the next step is addition, a public
scale, or a unary map, and you do not want an opening between the gates.
[arith_garble.hpp](@ref dpf/arith_garble.hpp) is that circuit
([Ball, Malkin, and Rosulek, CCS 2016](@ref bib_garble_gadgets)). Addition
is free. Scaling by a public constant coprime to the modulus is free. A
unary map of a mod-`m` wire sends `m − 1` ciphertexts. A threshold of `b`
bits that already live in `Z_{b+1}` is one such map, so the row count is
`b`. A product in a small prime field is the discrete-log reduction: project
to the exponent, add, project back, and drop the zero cases.
The beaver [session](@ref beaver_triples) is the other tool. It opens one
masked wire and multiplies interactively. It does not garble these gadgets.
```cpp
dpf::arith_garble::circuit c;
auto x = c.input(7);
auto y = c.input(7);
c.out(c.mul(c.add(x, y), x));
auto opened = dpf::arith_garble::eval_pair(c, in);
```
`opened.mask` is the garbler's share and `opened.color` is the evaluator's.
`open_shares` subtracts them mod the output modulus.
## A public table on short shares {#flute_lut}
A secret point in a public table is a DPF dotted with that table. FLUTE is
the case where the index is already a handful of masked bits sitting in an
ABY2.0 or three-party XOR sharing, and the result has to stay in that
sharing.
[flute.hpp](@ref dpf/flute.hpp) follows Brüggemann, Hundt, Schneider, Suresh,
and Yalame, [IEEE S&P 2023](@ref bib_flute). The table is an inner product of
those bits. The online exchange is two bits per output bit for two parties,
and three bits per output bit for `eval_trio`, independent of how wide the
index is. The index is at most 8 bits. Bit 0 of the index is the least
significant bit of the row.
```cpp
auto pair = dpf::flute::eval_pair(delta, n_out, columns, bits);
auto trio = dpf::flute::eval_trio(delta, n_out, columns, bits);
```
`columns[w * 2^δ + row]` is output bit `w` on that row. `opened` is the
clear bit. `masked` XOR the party's mask shares is the same bit.
## An array of shares, not a secret index {#share_shuffle}
A secret index is a DPF. One read of a shared memory is a unit key, a public
rotate, and a dot product, as in [3-party Duoram](@ref app_duoram). The
shuffle is the other job: the parties already hold a share of every row, and
the next step opens values. The opened order must not be the stored order.
`shuffle_party` derives one permutation from `k01` and applies it to every
component. A caller who passes the whole seed bundle can recompute that
order. Use it when the order is allowed to be known.
`shuffle_hidden_pass` is the order no single party should learn. Three
passes use the pairwise seeds `k01`, `k12`, and `k20`, leaving out the party
who does not hold that seed (party 2, then 0, then 1). Each party is given
`rss::party_seeds` only. On a pass, the two parties who share the seed
permute a two-party split of the column. The left-out party sends nothing
and receives one fresh component. Both messages go to the sender's RSS
neighbor. `permute` means `out[i] = in[pi[i]]`. Replaying the clear column
is `π01`, then `π12`, then `π20`.
```cpp
auto opened = dpf::shuffle::shuffle_hidden_triple(column, bundle, /*index=*/0);
```
`composer::shuffle_hidden(n, value_bytes)` records those three exchanges as
`shuffle_send`. `aux` on each node is the left-out party.
This is for a column you already share: a Duoram memory before a bulk open,
a histogram, a PSI payload. It does not replace a key. One hidden cell is
still a DPF. A comparison or a sort by a secret key is a DCF or
`share_cmp`. A gather to data-dependent indices is a DPF per access. The
seed permutation is chosen before the data.
## Truncation
- `trunc_prob` — local right shift, error in `{0,1}`, no round.
- `trunc_exact` — edaBit / carry correction.
- `mul_trunc` — product then shift (fixed-point multiply).
## Networking
`schedule_round` carries `phase::{setup,online}`, `round_dir::{duplex,send_next,recv_prev}`,
and `receive_rule::ring_next`. `drive_options` adds `pipeline_credit`, `cleartext`,
and `check_open`. Pairwise seeds replace `dealer_zero` via `rss_zero_mask`.
**Go deeper:** [Beaver](@ref beaver_triples), [compose](@ref protocol_compose),
[Grotto carry](@ref jet_and_ring).