libdpf/doc/pages/yao.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

6.2 KiB
Raw Blame History

A boolean function of a leaf

\htmlonly

ELI5. 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.
\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.

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

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

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

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).