libdpf/party/flows_bulk.cpp
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

465 lines
15 KiB
C++

/// @file party/flows_bulk.cpp
/// @brief Parametric beaver / DCF / Horner flows for coverage and benchmarks.
#include "cases.hpp"
#include "flow_util.hpp"
#include "key_io.hpp"
#include "registry.hpp"
#include <cstdint>
#include <string>
#include <vector>
#include "simde/simde/x86/avx2.h"
#include "dpf/beaver.hpp"
#include "dpf/dcf.hpp"
#include "dpf/dpf_key.hpp"
#include "dpf/eval_point.hpp"
#include "dpf/eval_unified.hpp"
#include "dpf/incremental.hpp"
#include "dpf/prg_aes.hpp"
#include <tuple>
namespace dpf
{
namespace party
{
namespace bulk
{
using util::Counter;
using util::evaluate_online;
using util::install_and_bind_u64;
using util::open_additive;
using util::require;
using util::role;
using util::share_bits;
using util::trio;
using util::u64;
// ---- parametric beaver product ----
template <u64 X, u64 Y>
int beaver_product_xy(role self, trio & net)
{
beavers::session<u64> s;
auto x = s.input();
auto y = s.input();
auto z = s(x * y);
if (self == role::p2)
{
Counter rng;
s.sample(rng);
net::deal_session(net, s);
return 0;
}
install_and_bind_u64(s, net, self, {x, y}, {X, Y});
evaluate_online(s, net, self);
auto o = open_additive(net, self, s.value_party(z));
if (self == role::p0)
require(o == X * Y, "product_xy");
return 0;
}
// ---- parametric dot ----
template <std::size_t N>
int beaver_dot_n(role self, trio & net)
{
beavers::session<u64> s;
std::vector<beavers::session<u64>::wire> xs, ys;
std::vector<u64> secrets;
u64 expect = 0;
for (std::size_t i = 0; i < N; ++i)
{
xs.push_back(s.input());
ys.push_back(s.input());
u64 a = static_cast<u64>(i + 1);
u64 b = static_cast<u64>(N - i);
secrets.push_back(a);
expect += a * b;
}
for (std::size_t i = 0; i < N; ++i)
secrets.push_back(static_cast<u64>(N - i));
auto z = s.dot(xs, ys);
if (self == role::p2)
{
Counter rng;
s.sample(rng);
require(s.round_of(z) == 1, "dot round");
net::deal_session(net, s);
return 0;
}
std::vector<beavers::session<u64>::wire> wires = xs;
wires.insert(wires.end(), ys.begin(), ys.end());
install_and_bind_u64(s, net, self, wires, secrets);
evaluate_online(s, net, self);
auto o = open_additive(net, self, s.value_party(z));
if (self == role::p0)
require(o == expect, "dot_n");
return 0;
}
// ---- parametric scale ----
template <std::size_t N>
int beaver_scale_n(role self, trio & net)
{
beavers::session<u64> s;
auto sc = s.input();
std::vector<beavers::session<u64>::wire> lanes;
std::vector<u64> secrets{3u};
for (std::size_t i = 0; i < N; ++i)
{
lanes.push_back(s.input());
secrets.push_back(static_cast<u64>(i + 1));
}
auto out = s.scale(sc, lanes);
if (self == role::p2)
{
Counter rng;
s.sample(rng);
net::deal_session(net, s);
return 0;
}
std::vector<beavers::session<u64>::wire> wires{sc};
wires.insert(wires.end(), lanes.begin(), lanes.end());
install_and_bind_u64(s, net, self, wires, secrets);
evaluate_online(s, net, self);
std::vector<u64> mine;
mine.reserve(N);
for (std::size_t i = 0; i < N; ++i)
mine.push_back(s.value_party(out[i]));
auto opened = open_additive(net, self, mine);
if (self == role::p0)
{
for (std::size_t i = 0; i < N; ++i)
require(opened[i] == 3u * static_cast<u64>(i + 1), "scale_n");
}
return 0;
}
// ---- parametric stream ----
template <std::size_t N>
int beaver_stream_n(role self, trio & net)
{
using block = prg::aes128::block_type;
block seed = simde_mm_set_epi64x(0x9, static_cast<long long>(N));
if (self == role::p2)
{
beavers::oracle<u64> src(seed, 8);
std::vector<beavers::beaver2<u64>> bulk(N);
beavers::fill_beaver2(src, 0, bulk.data(), N);
require(bulk[0].ab.open() == bulk[0].a.open() * bulk[0].b.open(), "s0");
require(bulk[N - 1].ab.open() == bulk[N - 1].a.open() * bulk[N - 1].b.open(),
"sN");
std::vector<u64> p0, p1;
p0.reserve(N * 4);
p1.reserve(N * 4);
for (std::size_t i = 0; i < N; ++i)
{
p0.insert(p0.end(),
{bulk[i].a.p0, bulk[i].b.p0, bulk[i].ab.p0, bulk[i].out.p0});
p1.insert(p1.end(),
{bulk[i].a.p1, bulk[i].b.p1, bulk[i].ab.p1, bulk[i].out.p1});
}
net.to(role::p0).send_vec(p0);
net.to(role::p1).send_vec(p1);
return 0;
}
auto shares = net.to(role::p2).recv_vec<u64>();
require(shares.size() == N * 4, "stream size");
// Online open copies 0 and N/2.
auto open_copy = [&](std::size_t i, u64 sx, u64 sy) {
u64 a = shares[i * 4 + 0];
u64 b = shares[i * 4 + 1];
u64 ab = shares[i * 4 + 2];
auto [x0, x1] = util::split_u64(sx, static_cast<unsigned>(i * 2 + 1));
auto [y0, y1] = util::split_u64(sy, static_cast<unsigned>(i * 2 + 2));
u64 xs = self == role::p0 ? x0 : x1;
u64 ys = self == role::p0 ? y0 : y1;
role peer = self == role::p0 ? role::p1 : role::p0;
u64 dx = (xs + a) + net.exchange_with(peer, xs + a);
u64 dy = (ys + b) + net.exchange_with(peer, ys + b);
u64 z = ab;
z -= dx * b;
z -= dy * a;
if (self == role::p0)
z += dx * dy;
auto open = open_additive(net, self, z);
if (self == role::p0)
require(open == sx * sy, "stream online");
};
open_copy(0, 6u, 7u);
open_copy(N / 2, 3u, 11u);
return 0;
}
// ---- parametric public horner (one round) ----
template <std::size_t Degree>
int beaver_horner_d(role self, trio & net)
{
beavers::session<u64> s;
auto x = s.input();
beavers::session<u64>::wire z;
if constexpr (Degree == 1)
z = s.horner(x, {1ull, 2ull});
else if constexpr (Degree == 2)
z = s.horner(x, {1ull, 2ull, 3ull});
else if constexpr (Degree == 3)
z = s.horner(x, {1ull, 2ull, 3ull, 4ull});
else if constexpr (Degree == 4)
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull});
else if constexpr (Degree == 5)
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull, 6ull});
else if constexpr (Degree == 6)
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull});
else if constexpr (Degree == 7)
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull, 8ull});
else
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull, 8ull, 9ull});
const u64 xv = 5u;
u64 expect = 0;
u64 pow = 1;
for (std::size_t i = 0; i <= Degree; ++i)
{
expect += static_cast<u64>(i + 1) * pow;
pow *= xv;
}
if (self == role::p2)
{
require(s.round_of(z) == 1, "horner one round");
Counter rng;
s.sample(rng);
net::deal_session(net, s);
return 0;
}
install_and_bind_u64(s, net, self, {x}, {xv});
evaluate_online(s, net, self);
auto o = open_additive(net, self, s.value_party(z));
if (self == role::p0)
require(o == expect, "horner_d");
return 0;
}
// ---- sign * horner one round ----
int beaver_sign_horner(role self, trio & net)
{
beavers::session<u64> s;
auto sgn = s.input();
auto x = s.input();
auto z = s.horner(sgn, x, {u64{4}, u64{5}, u64{6}}); // sgn*(4+5x+6x^2)
if (self == role::p2)
{
require(s.round_of(z) == 1, "sign horner round");
Counter rng;
s.sample(rng);
net::deal_session(net, s);
return 0;
}
install_and_bind_u64(s, net, self, {sgn, x}, {2u, 3u});
evaluate_online(s, net, self);
auto o = open_additive(net, self, s.value_party(z));
if (self == role::p0)
require(o == 2u * (4u + 5u * 3u + 6u * 9u), "sign_horner");
return 0;
}
// ---- DCF kinds ----
template <int Kind> // 0=lt 1=leq 2=gt 3=geq
int dcf_kind_point(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x40;
const Input x0 = 0x15;
const Input x1 = static_cast<Input>(alpha ^ x0);
const std::uint64_t beta = 9;
auto check = [&](const auto & key) {
auto below = eval_point(cmp, key, static_cast<Input>(alpha - 1));
auto on = eval_point(cmp, key, alpha);
auto above = eval_point(cmp, key, static_cast<Input>(alpha + 1));
std::vector<u64> mine{
share_bits(below) & key.cmp().mask,
share_bits(on) & key.cmp().mask,
share_bits(above) & key.cmp().mask};
auto opened = open_additive(net, self, mine);
const auto ob = opened[0] & key.cmp().mask;
const auto oo = opened[1] & key.cmp().mask;
const auto oa = opened[2] & key.cmp().mask;
if (self == role::p0)
{
if constexpr (Kind == 0)
require(ob == beta && oo == 0 && oa == 0, "lt");
else if constexpr (Kind == 1)
require(ob == beta && oo == beta && oa == 0, "leq");
else if constexpr (Kind == 2)
require(ob == 0 && oo == 0 && oa == beta, "gt");
else
require(ob == 0 && oo == beta && oa == beta, "geq");
}
};
if constexpr (Kind == 0)
{
require_opened(dist_with_cmp_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, lt(beta), check, check),
self, encoded_xor_point(x0, x1));
}
else if constexpr (Kind == 1)
{
require_opened(dist_with_cmp_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, leq(beta), check, check),
self, encoded_xor_point(x0, x1));
}
else if constexpr (Kind == 2)
{
require_opened(dist_with_cmp_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, gt(beta), check, check),
self, encoded_xor_point(x0, x1));
}
else
{
require_opened(dist_with_cmp_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, geq(beta), check, check),
self, encoded_xor_point(x0, x1));
}
return 0;
}
// ---- classic point eval bench ----
template <std::uint8_t Alpha, std::uint64_t Beta>
int dpf_point_ab(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha{Alpha};
const Input x0 = static_cast<Input>(alpha ^ Input{0x5a});
const Input x1 = static_cast<Input>(alpha ^ x0);
auto on = [&](const auto & key) {
auto y = *eval_point(key, alpha);
auto open = util::open_subtractive(net, self, share_bits(y));
if (self == role::p0)
require(open == Beta, "point");
auto yz = *eval_point(key, static_cast<Input>(Alpha ^ 1u));
auto oz = util::open_subtractive(net, self, share_bits(yz));
if (self == role::p0)
require(oz == 0u, "point off");
};
require_tree_prefix(
dist_with_point_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, std::uint64_t{Beta}, on, on),
self, verifiable_tree_prefix<prg::aes128, prg::aes128, std::uint64_t>(
x0, x1));
return 0;
}
// ---- multivariate poly one-round ----
int beaver_poly_cubic_cluster(role self, trio & net)
{
beavers::session<u64> s;
auto x = s.input();
auto y = s.input();
auto z = s.input();
auto out = s(x * y * z + x * y + z);
if (self == role::p2)
{
// Degree-3 monomials need two interactive rounds (see beaver_test xyz).
require(s.round_of(out) == 2, "cluster round");
Counter rng;
s.sample(rng);
net::deal_session(net, s);
return 0;
}
install_and_bind_u64(s, net, self, {x, y, z}, {2u, 3u, 5u});
evaluate_online(s, net, self);
auto o = open_additive(net, self, s.value_party(out));
if (self == role::p0)
require(o == 2u * 3u * 5u + 2u * 3u + 5u, "cluster");
return 0;
}
int beaver_like_terms(role self, trio & net)
{
beavers::session<u64> s;
auto x = s.input();
auto y = s.input();
auto z = s(3 * x * y + 2 * x * y);
if (self == role::p2)
{
Counter rng;
s.sample(rng);
net::deal_session(net, s);
return 0;
}
install_and_bind_u64(s, net, self, {x, y}, {4u, 5u});
evaluate_online(s, net, self);
auto o = open_additive(net, self, s.value_party(z));
if (self == role::p0)
require(o == 5u * 4u * 5u, "like");
return 0;
}
#define REG(name, tags, fn, benchable) \
register_flow(flow{#name, tags, fn, benchable})
} // namespace bulk
void register_bulk_flows()
{
using namespace bulk;
REG(beaver_product_3_5, "beaver product bench", (beaver_product_xy<3, 5>), true);
REG(beaver_product_11_13, "beaver product bench", (beaver_product_xy<11, 13>), true);
REG(beaver_product_100_200, "beaver product bench", (beaver_product_xy<100, 200>), true);
REG(beaver_product_max8, "beaver product bench",
(beaver_product_xy<0xffffffffffffffffull, 2>), true);
REG(beaver_dot_n1, "beaver dot bench", beaver_dot_n<1>, true);
REG(beaver_dot_n2, "beaver dot bench", beaver_dot_n<2>, true);
REG(beaver_dot_n4, "beaver dot bench", beaver_dot_n<4>, true);
REG(beaver_dot_n8, "beaver dot bench", beaver_dot_n<8>, true);
REG(beaver_dot_n16, "beaver dot bench", beaver_dot_n<16>, true);
REG(beaver_dot_n32, "beaver dot bench", beaver_dot_n<32>, true);
REG(beaver_scale_n1, "beaver scale bench", beaver_scale_n<1>, true);
REG(beaver_scale_n4, "beaver scale bench", beaver_scale_n<4>, true);
REG(beaver_scale_n16, "beaver scale bench", beaver_scale_n<16>, true);
REG(beaver_scale_n64, "beaver scale bench", beaver_scale_n<64>, true);
REG(beaver_stream_n8, "beaver stream bench", beaver_stream_n<8>, true);
REG(beaver_stream_n32, "beaver stream bench", beaver_stream_n<32>, true);
REG(beaver_stream_n128, "beaver stream bench", beaver_stream_n<128>, true);
REG(beaver_stream_n512, "beaver stream bench", beaver_stream_n<512>, true);
REG(beaver_stream_n2048, "beaver stream bench", beaver_stream_n<2048>, true);
REG(beaver_horner_d1, "beaver horner grotto bench", beaver_horner_d<1>, true);
REG(beaver_horner_d2, "beaver horner grotto bench", beaver_horner_d<2>, true);
REG(beaver_horner_d3, "beaver horner grotto bench", beaver_horner_d<3>, true);
REG(beaver_horner_d4, "beaver horner grotto bench", beaver_horner_d<4>, true);
REG(beaver_horner_d5, "beaver horner grotto bench", beaver_horner_d<5>, true);
REG(beaver_horner_d6, "beaver horner grotto bench", beaver_horner_d<6>, true);
REG(beaver_horner_d7, "beaver horner grotto bench", beaver_horner_d<7>, true);
REG(beaver_horner_d8, "beaver horner grotto bench", beaver_horner_d<8>, true);
REG(beaver_sign_horner, "beaver horner grotto bench", beaver_sign_horner, true);
REG(beaver_poly_cubic_cluster, "beaver poly bench", beaver_poly_cubic_cluster, true);
REG(beaver_like_terms, "beaver poly bench", beaver_like_terms, true);
REG(dcf_lt, "dcf grotto bench", dcf_kind_point<0>, true);
REG(dcf_leq, "dcf grotto bench", dcf_kind_point<1>, true);
REG(dcf_gt, "dcf grotto bench", dcf_kind_point<2>, true);
REG(dcf_geq, "dcf grotto bench", dcf_kind_point<3>, true);
REG(dpf_point_2a_7, "dpf geneval bench", (dpf_point_ab<0x2a, 7>), true);
REG(dpf_point_00_1, "dpf geneval bench", (dpf_point_ab<0x00, 1>), true);
REG(dpf_point_ff_99, "dpf geneval bench", (dpf_point_ab<0xff, 99>), true);
REG(dpf_point_80_42, "dpf geneval bench", (dpf_point_ab<0x80, 42>), true);
}
#undef REG
} // namespace party
} // namespace dpf