125 lines
6.2 KiB
Markdown
125 lines
6.2 KiB
Markdown
|
|
# A boolean function of a leaf {#yao_leaf}
|
|||
|
|
|
|||
|
|
\htmlonly
|
|||
|
|
<div class="eli5"><b>ELI5.</b> The key still answers the point, the comparison, and the interval. When the leaf you already hold has to go through a bit circuit the key does not contain, split that leaf into XOR bits, garble the circuit, and share the result back in the leaf's own type.</div>
|
|||
|
|
\endhtmlonly
|
|||
|
|
|
|||
|
|
Party 0 garbles. Party 1 evaluates. Free-XOR, half-gates
|
|||
|
|
([ePrint 2014/756](@ref bib_halfgates)). One table row is 32 bytes.
|
|||
|
|
Outputs are XOR shares of the output bits: the garbler's share is the
|
|||
|
|
permute bit, the evaluator's share is the color of the label it holds.
|
|||
|
|
|
|||
|
|
The circuit this library is built around is the zero-key AES-MMO block
|
|||
|
|
`prg::aes128::eval` already uses on every tree expand. A seed or a leaf
|
|||
|
|
can enter that block without being opened. The same netlist is a hand-built
|
|||
|
|
straight line of XOR, AND, XNOR, and NOT. Inputs are declared first.
|
|||
|
|
|
|||
|
|
| Leaf you hold | Into the netlist | Back out |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| Point leaf (subtractive) | `b2y` | `y2b` |
|
|||
|
|
| Comparison leaf (additive) | `a2y` | `y2a` |
|
|||
|
|
| `fss_share` | `fss2y` | `y2fss` |
|
|||
|
|
| Replicated, parties 0 and 1 garble | `rss2y` | `y2rss` |
|
|||
|
|
|
|||
|
|
Bits are least-significant first, one byte each, 0 or 1. `width` 0 means
|
|||
|
|
the whole ring on the way in, and the vector length on the way out.
|
|||
|
|
Both parties' shares are arguments. The edaBit open of `x - r` and the
|
|||
|
|
daBit open of `b ⊕ r` are resolved inside the call, the same way
|
|||
|
|
`edabit::a2b_gmw_pair` does. Those masked values are uniform. The integer
|
|||
|
|
stays shared.
|
|||
|
|
|
|||
|
|
`rss2y` does not wake party 2. Party 0 already holds `x0` and `x1`. Party 1
|
|||
|
|
already holds `x2`. `r0.next` must equal `r1.own`. Top-level `dpf::rss2y`
|
|||
|
|
and `dpf::y2rss` are the local (3,3) casts and are different functions.
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
auto [k0, k1] = dpf::make_dpf(std::uint8_t{42}, std::uint32_t{0x6b});
|
|||
|
|
auto s0 = *dpf::eval_point(k0, std::uint8_t{42});
|
|||
|
|
auto s1 = *dpf::eval_point(k1, std::uint8_t{42});
|
|||
|
|
auto [y0, y1] = dpf::yao::b2y(s0, s1, 8);
|
|||
|
|
|
|||
|
|
dpf::yao::netlist n;
|
|||
|
|
dpf::yao::bit in[8];
|
|||
|
|
for (int i = 0; i < 8; ++i)
|
|||
|
|
in[i] = n.shared_in();
|
|||
|
|
n.out(n.and_(in[0], in[1]));
|
|||
|
|
|
|||
|
|
dpf::yao::session garbler;
|
|||
|
|
auto out0 = garbler.eval(0, n, y0.data(), link); // party 1 passes y1
|
|||
|
|
auto [z0, z1] = dpf::yao::y2b<std::uint32_t>(out0, out1, 1);
|
|||
|
|
// reconstruct(z0, z1) == (0x6b & 1) & ((0x6b >> 1) & 1)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`session` keeps the IKNP base OT. The first `eval` that needs a choice
|
|||
|
|
label runs Chou–Orlandi. Later evals on that session only extend. Do not
|
|||
|
|
interleave `iknp::sample` on the same channel. Party 0 is the garbler for
|
|||
|
|
the life of the session. Tables are one-time. The model is semi-honest.
|
|||
|
|
|
|||
|
|
## What stays a key {#yao_not}
|
|||
|
|
|
|||
|
|
A public query against a secret point is a comparison key. An interval is
|
|||
|
|
an interval key. A polynomial in a public offset is Grotto, one comparison
|
|||
|
|
and a local dot. A product of two leaf shares is a Beaver triple. A word
|
|||
|
|
mux is one bit×ring inject. `cost_pass` still chooses among a DCF mask, an
|
|||
|
|
edaBit MSB, and a full adder for those.
|
|||
|
|
|
|||
|
|
Garble when the AND depth is the cost and the circuit is this shape: the
|
|||
|
|
MMO block (5120 ANDs, one message, about 160 KiB of tables), the AES-128
|
|||
|
|
block under a shared key (6400 ANDs, key schedule included), or a netlist
|
|||
|
|
you built because the leaf bits are the input. The Boyar–Peralta S-box
|
|||
|
|
inside those blocks is 32 ANDs. `correction_level` is that hash as one garble: eight MMO blocks, lanes
|
|||
|
|
`0..3` on each seed, 40960 ANDs, XOR shares of the four-block digest.
|
|||
|
|
The level and the prefix share are packed into the inputs
|
|||
|
|
(`pack_correction_level`); the netlist itself does not change per level.
|
|||
|
|
`party/oblivious_hash.hpp` still walks the same S-box as GMW AND layers,
|
|||
|
|
80 opens per level.
|
|||
|
|
|
|||
|
|
## A secret branch {#yao_stack}
|
|||
|
|
|
|||
|
|
The netlist above is a straight line. A secret if/else, or a secret choice
|
|||
|
|
among a few blocks, still belongs on that leaf circuit: the bits came from
|
|||
|
|
`b2y` / `a2y` / `rss2y`, and the answer goes back with `y2b` / `y2a` /
|
|||
|
|
`y2rss`. It is not a reason to open the leaf.
|
|||
|
|
|
|||
|
|
`yao::eval_if` stacks the two branches
|
|||
|
|
([Heath and Kolesnikov, CRYPTO 2020](@ref bib_stacked)). Each branch is
|
|||
|
|
garbled from the hash of the control label for that semantic bit. The
|
|||
|
|
generator XORs the materials. The evaluator rebuilds the inactive branch
|
|||
|
|
from a seed under the control label and XORs it out. Transmitted AND rows
|
|||
|
|
follow the heavier branch, plus four translation rows per output bit so the
|
|||
|
|
garbler's share does not depend on the branch.
|
|||
|
|
|
|||
|
|
`yao::eval_one_hot` is the same stack for `k` netlists, `k` from 2 to 8
|
|||
|
|
([Heath and Kolesnikov, CCS 2021](@ref bib_onehot)). The index is
|
|||
|
|
`index_p0 XOR index_p1`. The demux row for the selector color carries the
|
|||
|
|
inactive seeds.
|
|||
|
|
|
|||
|
|
Both parties pass the same netlists. Branch inputs use the same layout as
|
|||
|
|
`eval_pair`: a shared entry is that party's XOR share, `priv0` is read on
|
|||
|
|
party 0, `priv1` on party 1. The reconstructed bit is `share0[i] XOR share1[i]`.
|
|||
|
|
`stack_blocks` is the stacked material. `naive_blocks` is the sum of the
|
|||
|
|
branches. A comparison or a public-offset polynomial still stays on the key.
|
|||
|
|
|
|||
|
|
## Calls {#yao_calls}
|
|||
|
|
|
|||
|
|
| Call | What it does |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `yao::netlist` | XOR, AND, XNOR, NOT, XOR with a public bit. `n_and()` is the row count |
|
|||
|
|
| `yao::eval_plain` | The same wires in the clear |
|
|||
|
|
| `yao::eval_local` / `eval_pair` | Garble and evaluate in one process |
|
|||
|
|
| `yao::session::eval` | Garble on the peer channel. Party 0 sends the tables |
|
|||
|
|
| `yao::a2y` `b2y` `fss2y` `rss2y` | Ring share to LSB-first XOR bits |
|
|||
|
|
| `yao::y2a` `y2b` `y2fss` `y2rss` | Those bits back to a ring share |
|
|||
|
|
| `yao::aes_mmo` | One zero-key MMO block on a session. `pos` is public |
|
|||
|
|
| `yao::correction_level` | Eight of those blocks: the correction-seed hash for one tree level |
|
|||
|
|
| `yao::aes128` | AES-128 of a shared block under a shared key |
|
|||
|
|
| `yao::eval_if` | Stacked if/else. Rows follow the heavier branch ([CRYPTO 2020](@ref bib_stacked)) |
|
|||
|
|
| `yao::eval_one_hot` | One stack over `k` branches ([CCS 2021](@ref bib_onehot)) |
|
|||
|
|
|
|||
|
|
**Go deeper:** [a secret branch](@ref yao_stack), [yao.hpp](@ref dpf/yao.hpp),
|
|||
|
|
[yao_stack.hpp](@ref dpf/yao_stack.hpp), [yao_share.hpp](@ref dpf/yao_share.hpp),
|
|||
|
|
[yao_aes.hpp](@ref dpf/yao_aes.hpp), [F_Yao](@ref yao.hpp),
|
|||
|
|
[F_YaoShare](@ref yao_share.hpp), [half-gates](@ref bib_halfgates),
|
|||
|
|
[edaBits](@ref bib_edabits). The walk that still uses GMW for the hash is
|
|||
|
|
[the dealer-free tour](@ref tour_ds).
|