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>
This commit is contained in:
Ryan Henry 2026-09-28 05:59:19 -06:00
parent 695f8e84f7
commit 0d22946a0e
1835 changed files with 170291 additions and 2849 deletions

503
party/dist_dpf3.hpp Normal file
View file

@ -0,0 +1,503 @@
/// @file party/dist_dpf3.hpp
/// @brief Networked (2,3) Shamir DPF keygen (three key holders).
/// @details A pure two-party variant is not meaningful: after keygen each of
/// three parties must hold a Shamir share of the evaluation. Use
/// `dist_with_*_iknp` in `iknp_deal.hpp` for two-party VDPF keygen
/// without a pad dealer.
/// @brief Distributed dual-spine Doerner–Shelat keygen for (2,3) point keys.
/// @details Two `deal_point` / `point_party` runs (spines A and B) with Fig-3
/// `τ` payloads. p0 samples `τ` and `π`; overlapping halves are shipped
/// so p0 holds party-1, p2 holds party-2, p1 holds party-3.
/// Produced keys are always verifiable. Default spines keep `α` as XOR
/// shares (F_DPF3DS): `π` is peeled from the leaf seed of the shared
/// path, not from an opened point. p0 sends p1 only its `τ` halves.
/// Pass `RevealPoint=true` only when the caller wants the tree prefix
/// (and packed lane on updatable keys). Pass `dpf::updatable` for
/// beaver leaves and a later networked Fig-10 update via
/// `dist_update_payload`.
#ifndef LIBDPF_PARTY_DIST_DPF3_HPP__
#define LIBDPF_PARTY_DIST_DPF3_HPP__
#include <cstring>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include "hedley/hedley.h"
#include "dist_ds.hpp"
#include "key_io.hpp"
#include "dpf/dpf3.hpp"
#include "dpf/dpf3_ds.hpp"
#include "dpf/fp61.hpp"
#include "dpf/shamir3.hpp"
#include "dpf/wildcard.hpp"
namespace dpf
{
namespace party
{
/// @brief How trio roles map onto Shamir party indices `{1,2,3}`.
/// @details **dealer**: p0→1, p1→2, p2→3 (trusted keygen on p2, then ship).
/// **dist**: p0→1, p2→2, p1→3 (`dist_with_dpf3_key` assembly).
enum class dpf3_role_map : unsigned
{
dealer = 0,
dist = 1,
};
/// @brief Shamir party index for a trio role under `map`.
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr int dpf3_party_of(role r, dpf3_role_map map) noexcept
{
if (map == dpf3_role_map::dist)
{
if (r == role::p0)
return 1;
if (r == role::p2)
return 2;
return 3; // p1
}
// dealer
if (r == role::p0)
return 1;
if (r == role::p1)
return 2;
return 3; // p2
}
/// @brief Public VDPF+ offsets for one (2,3) keygen.
struct dpf3_pi_msg
{
shamir3::xor61 pi_a{};
shamir3::xor61 pi_b{};
};
/// @brief Fig-10 patch broadcast (leaf patches so p2 need not learn `α`).
template <typename Leaf>
struct dpf3_fig10_msg
{
Leaf patch_a{};
Leaf patch_b{};
shamir3::xor61 pi_a{};
shamir3::xor61 pi_b{};
};
/// @brief p1's τ halves for spines A and B (party-3 strings). Does not reveal β.
struct dpf3_tau_share
{
shamir3::xor61 t1{};
shamir3::xor61 t3{};
};
namespace detail_dist_dpf3
{
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename OutputT>
using spine_out0 = decltype(dist::point_party<InteriorPRG, ExteriorPRG, InputT,
OutputT, role::p0>(std::declval<trio &>(), std::declval<InputT>(),
std::declval<OutputT>()));
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename OutputT>
using spine_key0 = spine_out0<InteriorPRG, ExteriorPRG, InputT, OutputT>;
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename OutputT>
using spine_out1 = decltype(dist::point_party<InteriorPRG, ExteriorPRG, InputT,
OutputT, role::p1>(std::declval<trio &>(), std::declval<InputT>(),
std::declval<OutputT>()));
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename OutputT>
using spine_key1 = spine_out1<InteriorPRG, ExteriorPRG, InputT, OutputT>;
template <typename Key, typename Share>
void assign_wildcard_over_link(Key & key, Share my_share, trio & net, role self)
{
const role peer = self == role::p0 ? role::p1 : role::p0;
auto & wrap = std::get<0>(key.leaf_nodes);
auto blinded = wrap.compute_and_get_blinded_output_share(my_share);
auto peer_blinded = net.exchange_with(peer, blinded);
auto leaf = wrap.compute_and_get_leaf_share(peer_blinded);
auto peer_leaf = net.exchange_with(peer, leaf);
wrap.reconstruct_correction_word(peer_leaf);
}
/// @brief Assign XOR payload from each party's τ half (payload never opened).
template <typename Key>
void assign_spine_tau_halves(trio & net, role self, Key & key,
shamir3::xor61 my_half)
{
assign_wildcard_over_link(key, my_half, net, self);
}
template <typename Key, typename Leaf>
void apply_fig10_leaves(Key & key, const dpf3_fig10_msg<Leaf> & msg)
{
detail::dpf3_impl::apply_leaf_patch(key.a.dpf_key, msg.patch_a);
detail::dpf3_impl::apply_leaf_patch(key.b.dpf_key, msg.patch_b);
}
template <typename Key, typename Leaf>
void apply_fig10(Key & key, const dpf3_fig10_msg<Leaf> & msg)
{
apply_fig10_leaves(key, msg);
key.a.offset = msg.pi_a;
key.b.offset = msg.pi_b;
}
template <typename Key, typename Node>
HEDLEY_WARN_UNUSED_RESULT
shamir3::xor61 peel_from_seed(const Key & key, const Node & seed)
{
const auto y = key.template traverse_exterior<0>(seed);
using Y = std::decay_t<decltype(y)>;
if constexpr (is_secret_share_v<Y>)
return shamir3::xor61{y.raw()};
else if constexpr (std::is_integral_v<Y>
|| std::is_convertible_v<Y, std::uint64_t>)
return shamir3::xor61{static_cast<std::uint64_t>(y)};
else
{
std::uint64_t w = 0;
static_assert(sizeof(Y) >= sizeof(w),
"peel_from_seed: leaf narrower than xor61");
std::memcpy(&w, &y, sizeof(w));
return shamir3::xor61{w};
}
}
template <typename InputT>
struct dpf3_opened
{
InputT opened_prefix{};
/// Meaningful when the spine payload is a packed wildcard.
unsigned opened_lane = 0;
bool lane_opened = false;
};
template <typename T, typename = void>
struct has_nested_key : std::false_type {};
template <typename T>
struct has_nested_key<T, std::void_t<decltype(std::declval<T &>().key)>>
: std::true_type {};
template <typename T>
inline constexpr bool has_nested_key_v = has_nested_key<T>::value;
template <typename T, typename = void>
struct has_opened_prefix : std::false_type {};
template <typename T>
struct has_opened_prefix<T,
std::void_t<decltype(std::declval<const T &>().opened_prefix)>>
: std::true_type {};
template <typename T>
inline constexpr bool has_opened_prefix_v = has_opened_prefix<T>::value;
template <typename T, typename = void>
struct has_opened_lane : std::false_type {};
template <typename T>
struct has_opened_lane<T,
std::void_t<decltype(std::declval<const T &>().opened_lane)>>
: std::true_type {};
template <typename T>
inline constexpr bool has_opened_lane_v = has_opened_lane<T>::value;
template <typename T, typename = void>
struct has_member_dpf_key : std::false_type {};
template <typename T>
struct has_member_dpf_key<T, std::void_t<decltype(std::declval<T &>().dpf_key)>>
: std::true_type {};
template <typename Held>
auto spine_key_of(Held && held)
{
using H = std::decay_t<Held>;
if constexpr (has_member_dpf_key<H>::value)
return std::move(held.dpf_key);
else if constexpr (has_nested_key_v<H>)
return std::move(held.key);
else
return std::move(held);
}
template <typename Held, typename InputT>
void note_opened(dpf3_opened<InputT> & learned, const Held & held)
{
if constexpr (has_opened_prefix_v<std::decay_t<Held>>)
{
learned.opened_prefix = held.opened_prefix;
if constexpr (has_opened_lane_v<std::decay_t<Held>>)
{
learned.opened_lane = held.opened_lane;
learned.lane_opened = true;
}
}
}
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename OutputT, bool Updatable, bool RevealPoint, typename Fn1,
typename Fn2, typename Fn3>
std::optional<dpf3_opened<InputT>> dist_with_dpf3_key_impl(trio & net, role self,
InputT x0, InputT x1,
const fp61 beta, Fn1 && on1, Fn2 && on2, Fn3 && on3)
{
using X = shamir3::xor61;
using tau_quad = detail::dpf3_impl::tau_quad;
using Key0 = spine_key0<InteriorPRG, ExteriorPRG, InputT, OutputT>;
using Key1 = spine_key1<InteriorPRG, ExteriorPRG, InputT, OutputT>;
using node = typename dpf::tree_traits<InteriorPRG>::node;
constexpr bool V = true; // dist point_party always opens correction seeds
if (self == role::p2)
{
dist::deal_point<InteriorPRG, InputT, OutputT, !RevealPoint>(net);
dist::deal_point<InteriorPRG, InputT, OutputT, !RevealPoint>(net);
const auto pi = net.recv_from<dpf3_pi_msg>(role::p0, net::msg::delta);
auto key_b0 = recv_key<Key0>(net, role::p0);
auto key_a1 = recv_key<Key1>(net, role::p1);
detail::dpf3_impl::vdpf_plus_key<Key1> plus_a1{std::move(key_a1),
pi.pi_a};
detail::dpf3_impl::vdpf_plus_key<Key0> plus_b0{std::move(key_b0),
pi.pi_b};
dpf3_key<2, decltype(plus_a1), decltype(plus_b0)> k2{
std::move(plus_a1), std::move(plus_b0), V, false, Updatable};
std::forward<Fn2>(on2)(std::move(k2));
return std::nullopt;
}
// p0 samples τ from a Shamir split of β; p1 receives only (t1, t3).
tau_quad t{};
X my_a{};
X my_b{};
if (self == role::p0)
{
t = detail::dpf3_impl::sample_taus(beta);
my_a = t.t0;
my_b = t.t2;
net.send_to(role::p1, net::msg::delta, dpf3_tau_share{t.t1, t.t3});
}
else
{
const auto sh = net.recv_from<dpf3_tau_share>(role::p0, net::msg::delta);
my_a = sh.t1;
my_b = sh.t3;
t.t1 = sh.t1;
t.t3 = sh.t3;
}
dpf3_opened<InputT> learned{};
node seed_a{};
node seed_b{};
// Non-updatable and updatable both plant beaver leaves; τ halves are
// assigned without opening the spine payload. The outer Updatable flag
// only gates Fig-10.
// Spines still use per-message `exchange_with`. Wiring RoundSink here
// needs a round budget that covers wild+hash leaf mux; undersizing hangs
// the peer on `msg::round_batch`.
OutputT wild{};
if (self == role::p0)
{
auto out_a0 = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
OutputT, role::p0, false, RevealPoint>(net, x0, wild, false,
&seed_a);
note_opened(learned, out_a0);
auto key_a0 = spine_key_of(std::move(out_a0));
auto out_b0 = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
OutputT, role::p0, false, RevealPoint>(net, x0, wild, false,
&seed_b);
auto key_b0 = spine_key_of(std::move(out_b0));
assign_spine_tau_halves(net, self, key_a0, my_a);
assign_spine_tau_halves(net, self, key_b0, my_b);
dpf3_pi_msg pi{};
pi.pi_a = t.t0 + peel_from_seed(key_a0, seed_a);
pi.pi_b = t.t2 + peel_from_seed(key_b0, seed_b);
net.send_to(role::p1, net::msg::delta, pi);
net.send_to(role::p2, net::msg::delta, pi);
send_key(net, role::p2, key_b0);
detail::dpf3_impl::vdpf_plus_key<Key0> plus_a0{std::move(key_a0),
pi.pi_a};
detail::dpf3_impl::vdpf_plus_key<Key0> plus_b0{std::move(key_b0),
pi.pi_b};
dpf3_key<1, decltype(plus_a0), decltype(plus_b0)> k1{
std::move(plus_a0), std::move(plus_b0), V, false, Updatable};
std::forward<Fn1>(on1)(std::move(k1));
if constexpr (RevealPoint)
return learned;
return std::nullopt;
}
auto out_a1 = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
OutputT, role::p1, false, RevealPoint>(net, x1, wild, false,
&seed_a);
note_opened(learned, out_a1);
auto key_a1 = spine_key_of(std::move(out_a1));
auto out_b1 = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
OutputT, role::p1, false, RevealPoint>(net, x1, wild, false,
&seed_b);
auto key_b1 = spine_key_of(std::move(out_b1));
assign_spine_tau_halves(net, self, key_a1, my_a);
assign_spine_tau_halves(net, self, key_b1, my_b);
const auto pi = net.recv_from<dpf3_pi_msg>(role::p0, net::msg::delta);
send_key(net, role::p2, key_a1);
detail::dpf3_impl::vdpf_plus_key<Key1> plus_a1{std::move(key_a1),
pi.pi_a};
detail::dpf3_impl::vdpf_plus_key<Key1> plus_b1{std::move(key_b1),
pi.pi_b};
dpf3_key<3, decltype(plus_a1), decltype(plus_b1)> k3{
std::move(plus_a1), std::move(plus_b1), V, false, Updatable};
std::forward<Fn3>(on3)(std::move(k3));
if constexpr (RevealPoint)
return learned;
return std::nullopt;
}
} // namespace detail_dist_dpf3
/// @brief Distributed dual-spine (2,3) keygen over the trio.
/// @details Role map after assembly: **p0 → party 1**, **p2 → party 2**,
/// **p1 → party 3**. Keys are always verifiable. Each computing
/// party's view of `α` is only its XOR share; p1's view of `β` is
/// only its τ halves (not a clear payload). Default return is empty.
/// @tparam RevealPoint when true, p0/p1 also reconstruct the tree prefix
/// @return Opened prefix when `RevealPoint`, else empty. Empty on p2.
/// @throws std::runtime_error if a frame is truncated or tagged wrong
/// \complexity Two `point_party` spines, so the local work is two O(n) walks, plus O(1) τ arithmetic.
/// \rounds The rounds of two `point_party` calls, then one τ-share send (p0 to p1), one `dpf3_pi_msg` to p1 and p2, and one key send to p2. p2 runs two `deal_point` calls first. Counted in `dist_with_dpf3_key_impl`. Fig-10 update is `dist_update_payload`, not this function.
/// \communication Two dealer tapes (see `deal_point` / `point_party`), one `dpf3_tau_share`, one `dpf3_pi_msg` (two `xor61` values), and one key blob (`send_key`, `sizeof` of the spine key).
/// \preprocessing p2's `deal_point` pads for both spines. p0 samples the four τ strings locally from a Shamir split of β.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
bool RevealPoint = false,
typename InputT,
typename Fn1,
typename Fn2,
typename Fn3>
[[nodiscard]] std::optional<InputT> dist_with_dpf3_key(trio & net, role self,
InputT x0, InputT x1, const fp61 beta, Fn1 && on1, Fn2 && on2, Fn3 && on3)
{
auto opened = detail_dist_dpf3::dist_with_dpf3_key_impl<InteriorPRG,
ExteriorPRG, InputT, dpf::wildcard_value<shamir3::xor61>, false,
RevealPoint>(net, self, x0, x1, beta, std::forward<Fn1>(on1),
std::forward<Fn2>(on2), std::forward<Fn3>(on3));
if (!opened)
return std::nullopt;
return opened->opened_prefix;
}
/// @brief What an updatable (2,3) keygen reconstructs when `RevealPoint`.
template <typename InputT>
struct dpf3_updatable_opened
{
InputT opened_prefix{};
unsigned opened_lane = 0;
};
/// @brief Distributed dual-spine (2,3) keygen with beaver leaves (Fig-10-ready).
/// @tparam RevealPoint when true, return prefix and packed lane on p0/p1
/// @return Opened values when `RevealPoint`, else empty. Empty on p2.
/// \complexity Two `point_party` spines, so the local work is two O(n) walks, plus O(1) τ arithmetic.
/// \rounds The rounds of two `point_party` calls, then one τ-share send (p0 to p1), one `dpf3_pi_msg` to p1 and p2, and one key send to p2. p2 runs two `deal_point` calls first. Counted in `dist_with_dpf3_key_impl`. Fig-10 update is `dist_update_payload`, not this function.
/// \communication Two dealer tapes (see `deal_point` / `point_party`), one `dpf3_tau_share`, one `dpf3_pi_msg` (two `xor61` values), and one key blob (`send_key`, `sizeof` of the spine key).
/// \preprocessing p2's `deal_point` pads for both spines. p0 samples the four τ strings locally from a Shamir split of β.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
bool RevealPoint = false,
typename InputT,
typename Fn1,
typename Fn2,
typename Fn3>
[[nodiscard]] std::optional<dpf3_updatable_opened<InputT>> dist_with_dpf3_key(
trio & net, role self, InputT x0, InputT x1, const fp61 beta, updatable,
Fn1 && on1, Fn2 && on2, Fn3 && on3)
{
auto opened = detail_dist_dpf3::dist_with_dpf3_key_impl<InteriorPRG,
ExteriorPRG, InputT, dpf::wildcard_value<shamir3::xor61>, true,
RevealPoint>(net, self, x0, x1, beta, std::forward<Fn1>(on1),
std::forward<Fn2>(on2), std::forward<Fn3>(on3));
if (!opened)
return std::nullopt;
return dpf3_updatable_opened<InputT>{opened->opened_prefix,
opened->opened_lane};
}
/// @brief Networked Fig-10 payload update for keys from `dist_with_dpf3_key`.
/// @details p0 and p1 (who are given `α`) exchange peels, p0 samples fresh `τ`
/// and broadcasts leaf patches + new public `π`. p2 applies patches
/// without learning `α`. Requires `key.updatable`.
/// @throws std::invalid_argument if the key is not updatable
template <typename Key, typename InputT>
void dist_update_payload(trio & net, role self, Key & key, InputT alpha,
fp61 beta_new)
{
static_assert(Key::is_dpf3, "dist_update_payload: dpf3 key");
if (!key.updatable)
throw std::invalid_argument(
"dist_update_payload: key was not generated with dpf::updatable");
using X = shamir3::xor61;
using InnerA = typename Key::plus_a_type::inner_type;
using Leaf = decltype(detail::dpf3_impl::make_leaf_patch<InnerA>(alpha,
X{}));
using Msg = dpf3_fig10_msg<Leaf>;
struct peel_pair
{
X peel_a{};
X peel_b{};
};
Msg msg{};
if (self == role::p0 || self == role::p1)
{
peel_pair mine{detail::dpf3_impl::peel(key.a.dpf_key, alpha),
detail::dpf3_impl::peel(key.b.dpf_key, alpha)};
const role peer = self == role::p0 ? role::p1 : role::p0;
const peel_pair theirs = net.exchange_with(peer, mine);
if (self == role::p0)
{
// p0 = party 1 (A0,B0); p1 = party 3 (A1,B1).
// Leaf CW patches only move the half whose path control bit is set;
// compute π from a post-patch peel (same as dealer refresh_offset).
detail::dpf3_impl::tau_quad told{};
told.t0 = mine.peel_a + key.a.offset;
told.t1 = theirs.peel_a + key.a.offset;
told.t2 = mine.peel_b + key.b.offset;
told.t3 = theirs.peel_b + key.b.offset;
const auto tnew = detail::dpf3_impl::sample_taus(beta_new);
const X dA = (tnew.t0 + tnew.t1) + (told.t0 + told.t1);
const X dB = (tnew.t2 + tnew.t3) + (told.t2 + told.t3);
msg.patch_a =
detail::dpf3_impl::make_leaf_patch<InnerA>(alpha, dA);
msg.patch_b =
detail::dpf3_impl::make_leaf_patch<InnerA>(alpha, dB);
detail_dist_dpf3::apply_fig10_leaves(key, msg);
msg.pi_a = tnew.t0 + detail::dpf3_impl::peel(key.a.dpf_key, alpha);
msg.pi_b = tnew.t2 + detail::dpf3_impl::peel(key.b.dpf_key, alpha);
key.a.offset = msg.pi_a;
key.b.offset = msg.pi_b;
net.send_to(role::p1, net::msg::delta, msg);
net.send_to(role::p2, net::msg::delta, msg);
return;
}
msg = net.recv_from<Msg>(role::p0, net::msg::delta);
detail_dist_dpf3::apply_fig10(key, msg);
return;
}
msg = net.recv_from<Msg>(role::p0, net::msg::delta);
detail_dist_dpf3::apply_fig10(key, msg);
}
} // namespace party
} // namespace dpf
#endif // LIBDPF_PARTY_DIST_DPF3_HPP__