/// @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 #include #include #include #include #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 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 using spine_out0 = decltype(dist::point_party(std::declval(), std::declval(), std::declval())); template using spine_key0 = spine_out0; template using spine_out1 = decltype(dist::point_party(std::declval(), std::declval(), std::declval())); template using spine_key1 = spine_out1; template 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 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 void apply_fig10_leaves(Key & key, const dpf3_fig10_msg & 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 void apply_fig10(Key & key, const dpf3_fig10_msg & msg) { apply_fig10_leaves(key, msg); key.a.offset = msg.pi_a; key.b.offset = msg.pi_b; } template 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; if constexpr (is_secret_share_v) return shamir3::xor61{y.raw()}; else if constexpr (std::is_integral_v || std::is_convertible_v) return shamir3::xor61{static_cast(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 struct dpf3_opened { InputT opened_prefix{}; /// Meaningful when the spine payload is a packed wildcard. unsigned opened_lane = 0; bool lane_opened = false; }; template struct has_nested_key : std::false_type {}; template struct has_nested_key().key)>> : std::true_type {}; template inline constexpr bool has_nested_key_v = has_nested_key::value; template struct has_opened_prefix : std::false_type {}; template struct has_opened_prefix().opened_prefix)>> : std::true_type {}; template inline constexpr bool has_opened_prefix_v = has_opened_prefix::value; template struct has_opened_lane : std::false_type {}; template struct has_opened_lane().opened_lane)>> : std::true_type {}; template inline constexpr bool has_opened_lane_v = has_opened_lane::value; template struct has_member_dpf_key : std::false_type {}; template struct has_member_dpf_key().dpf_key)>> : std::true_type {}; template auto spine_key_of(Held && held) { using H = std::decay_t; if constexpr (has_member_dpf_key::value) return std::move(held.dpf_key); else if constexpr (has_nested_key_v) return std::move(held.key); else return std::move(held); } template void note_opened(dpf3_opened & learned, const Held & held) { if constexpr (has_opened_prefix_v>) { learned.opened_prefix = held.opened_prefix; if constexpr (has_opened_lane_v>) { learned.opened_lane = held.opened_lane; learned.lane_opened = true; } } } template std::optional> 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; using Key1 = spine_key1; using node = typename dpf::tree_traits::node; constexpr bool V = true; // dist point_party always opens correction seeds if (self == role::p2) { dist::deal_point(net); dist::deal_point(net); const auto pi = net.recv_from(role::p0, net::msg::delta); auto key_b0 = recv_key(net, role::p0); auto key_a1 = recv_key(net, role::p1); detail::dpf3_impl::vdpf_plus_key plus_a1{std::move(key_a1), pi.pi_a}; detail::dpf3_impl::vdpf_plus_key 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(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(role::p0, net::msg::delta); my_a = sh.t1; my_b = sh.t3; t.t1 = sh.t1; t.t3 = sh.t3; } dpf3_opened 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(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(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 plus_a0{std::move(key_a0), pi.pi_a}; detail::dpf3_impl::vdpf_plus_key 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(on1)(std::move(k1)); if constexpr (RevealPoint) return learned; return std::nullopt; } auto out_a1 = dist::point_party(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(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(role::p0, net::msg::delta); send_key(net, role::p2, key_a1); detail::dpf3_impl::vdpf_plus_key plus_a1{std::move(key_a1), pi.pi_a}; detail::dpf3_impl::vdpf_plus_key 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(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 [[nodiscard]] std::optional 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, false, RevealPoint>(net, self, x0, x1, beta, std::forward(on1), std::forward(on2), std::forward(on3)); if (!opened) return std::nullopt; return opened->opened_prefix; } /// @brief What an updatable (2,3) keygen reconstructs when `RevealPoint`. template 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 [[nodiscard]] std::optional> 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, true, RevealPoint>(net, self, x0, x1, beta, std::forward(on1), std::forward(on2), std::forward(on3)); if (!opened) return std::nullopt; return dpf3_updatable_opened{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 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(alpha, X{})); using Msg = dpf3_fig10_msg; 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(alpha, dA); msg.patch_b = detail::dpf3_impl::make_leaf_patch(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(role::p0, net::msg::delta); detail_dist_dpf3::apply_fig10(key, msg); return; } msg = net.recv_from(role::p0, net::msg::delta); detail_dist_dpf3::apply_fig10(key, msg); } } // namespace party } // namespace dpf #endif // LIBDPF_PARTY_DIST_DPF3_HPP__