/// @file dpf/dpf3.hpp /// @brief Three-evaluator (2,3) point DPF after ePrint 2024/1658. /// @details Each party key is a pair of two-party VDPF+ keys. Evaluation is /// two ordinary walks, an XOR, and a party-index scale in `fp61`. /// Reconstruction is Shamir interpolation. /// @note Following Zyskind, Yanai, and Pentland, ePrint 2024/1658, Figure 3: each evaluator holds one key from each of two (2,2)-VDPF+ instances. Their evaluation section records about 2× the key size of one two-party DPF. /// /// **Updatable keys** (`dpf::updatable`) keep beaver-backed XOR leaves /// so `update_payload` can rewrite `β` with four leaf patches and a /// refresh of the public offsets `π` — `O(λ)`, independent of the /// domain — without moving `α`. Non-updatable keys bake the leaf; /// calling `update_payload` on them throws. /// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref license) for details. #ifndef LIBDPF_INCLUDE_DPF_DPF3_HPP__ #define LIBDPF_INCLUDE_DPF_DPF3_HPP__ #include #include #include #include #include #include #include #include #include "hedley/hedley.h" #include "dpf/eval_full.hpp" #include "dpf/eval_interval.hpp" #include "dpf/eval_point.hpp" #include "dpf/eval_sequence.hpp" #include "dpf/fp61.hpp" #include "dpf/leaf_node.hpp" #include "dpf/path_memoizer.hpp" #include "dpf/placement.hpp" #include "dpf/prg_aes.hpp" #include "dpf/random.hpp" #include "dpf/shamir3.hpp" #include "dpf/utils.hpp" #include "dpf/verifiable.hpp" #include "dpf/wildcard.hpp" #include "dpf/xor_wrapper.hpp" namespace dpf { /// @brief Phantom tag selecting the three-evaluator point construction. struct dpf3_t { static constexpr bool is_dpf3_tag = true; }; inline constexpr dpf3_t dpf3{}; namespace detail { namespace dpf3_impl { using xor61 = shamir3::xor61; template struct vdpf_plus_key { using inner_type = Inner; using input_type = typename Inner::input_type; /// @brief Inner two-party spine. Eval that accepts a `dpf_key` also accepts /// this object and reads `dpf_key`. Inner dpf_key{}; xor61 offset{}; }; template > HEDLEY_WARN_UNUSED_RESULT xor61 eval_plus(const vdpf_plus_key & key, typename Inner::input_type x, PathMemoizer && path = PathMemoizer{}) { const auto y = *dpf::eval_point(key.dpf_key, x, std::forward(path)); if constexpr (is_secret_share_v>) return xor61{y.raw()} + key.offset; else return xor61{static_cast(y)} + key.offset; } template > HEDLEY_WARN_UNUSED_RESULT xor61 eval_plus(const vdpf_plus_key & key, typename Inner::input_type x, prove_ref pr, PathMemoizer && path = PathMemoizer{}) { const auto y = *dpf::eval_point(key.dpf_key, x, pr, std::forward(path)); // Re-bind the public offset (refreshed by `update_payload`). const auto off = static_cast(key.offset); detail::vdpf::fold_bytes(pr.token, 0x50, &off, sizeof(off)); if constexpr (is_secret_share_v>) return xor61{y.raw()} + key.offset; else return xor61{static_cast(y)} + key.offset; } template xor61 xor61_from_buf_elem(const Buf & e) { if constexpr (is_secret_share_v>) return xor61{e.raw()}; else return xor61{static_cast(e)}; } template void combine_spine_bufs(const KeyT & key, const BufA & a, const BufB & b, std::vector & out) { const std::size_t n = a.size(); out.resize(n); const fp61 scale{static_cast(KeyT::party)}; for (std::size_t i = 0; i < n; ++i) { const xor61 y = xor61_from_buf_elem(a[i]) + key.a.offset + xor61_from_buf_elem(b[i]) + key.b.offset; out[i] = shamir3::xor_scale(y, scale); } } template HEDLEY_WARN_UNUSED_RESULT xor61 peel(const Inner & key, typename Inner::input_type x) { const auto y = *dpf::eval_point(key, x); if constexpr (is_secret_share_v>) return xor61{y.raw()}; else return xor61{static_cast(y)}; } struct tau_quad { xor61 t0{}; xor61 t1{}; xor61 t2{}; xor61 t3{}; }; inline tau_quad sample_taus(fp61 beta) { const auto shares = shamir3::share_secret(beta); const fp61 s1 = shamir3::unscale(shares[0]); const fp61 s2 = shamir3::unscale(shares[1]); const fp61 s3 = shamir3::unscale(shares[2]); tau_quad t{}; for (int attempt = 0; attempt < 16; ++attempt) { t.t0 = xor61{uniform_sample() & fp61_mod}; t.t2 = shamir3::field_xor(s1, t.t0); t.t1 = shamir3::field_xor(s2, t.t2); t.t3 = shamir3::field_xor(s3, t.t1); const auto ok = [](xor61 w) { return (static_cast(w) & fp61_mod) != fp61_mod; }; if (ok(t.t0) && ok(t.t1) && ok(t.t2) && ok(t.t3)) return t; } throw std::runtime_error("make_dpf3: embed resampling failed"); } template void patch_leaf_xor(Key & key, typename Key::input_type alpha, Output delta) { using node = typename Key::exterior_node; using concrete = dpf::concrete_type_t; auto & wrap = std::get<0>(key.leaf_nodes); auto & leaf = wrap.raw_leaf(); leaf = dpf::add_leaf(leaf, dpf::make_naked_leaf(alpha, concrete{delta})); } template void assign_wildcard_pair(K0 & k0, K1 & k1, Share0 share0, Share1 share1) { auto & w0 = std::get<0>(k0.leaf_nodes); auto & w1 = std::get<0>(k1.leaf_nodes); if (w0.is_ready()) w0.begin_update(); if (w1.is_ready()) w1.begin_update(); const auto b0 = w0.compute_and_get_blinded_output_share(share0); const auto b1 = w1.compute_and_get_blinded_output_share(share1); const auto l0 = w0.compute_and_get_leaf_share(b1); const auto l1 = w1.compute_and_get_leaf_share(b0); w0.reconstruct_correction_word(l1); w1.reconstruct_correction_word(l0); } template void assign_xor_payload(K0 & k0, K1 & k1, xor61 payload) { const xor61 s0{uniform_sample()}; const xor61 s1 = payload + s0; assign_wildcard_pair(k0, k1, s0, s1); } } // namespace dpf3_impl } // namespace detail /// @brief One evaluator's key in a (2,3) point DPF. /// @tparam Party party index in `{1, 2, 3}` /// @tparam PlusA VDPF+ key type for instance A /// @tparam PlusB VDPF+ key type for instance B template struct dpf3_key { static_assert(Party >= 1 && Party <= 3, "dpf3 party is 1, 2, or 3"); static constexpr int party = Party; static constexpr bool is_dpf3 = true; using input_type = typename PlusA::input_type; using plus_a_type = PlusA; using plus_b_type = PlusB; PlusA a{}; PlusB b{}; bool verifiable = false; bool extractable = false; bool updatable = false; }; namespace detail { namespace dpf3_impl { /// @brief Open the XOR-shared point the same way local DS walks it. template HEDLEY_WARN_UNUSED_RESULT InputT open_xor_point(InputT x0, InputT x1) { utils::flip_msb_if_signed_integral(x0); constexpr auto to_int = utils::to_integral_type{}; using I = decltype(to_int(x0)); return utils::make_from_integral_value{}( static_cast(to_int(x0) ^ to_int(x1))); } /// @brief Pack Fig-3 party keys from two completed two-party spines + `τ`. /// @details Computes public `π` from peels of the party-0 halves at `α`. /// Parameter names avoid `B0`/`B1` (termios baud macros). template auto assemble_from_spines(KeyA0 key_a0, KeyA1 key_a1, KeyB0 key_b0, KeyB1 key_b1, tau_quad t, Input alpha) { using X = xor61; const X yA0 = peel(key_a0, alpha); const X yB0 = peel(key_b0, alpha); const X piA = t.t0 + yA0; const X piB = t.t2 + yB0; using PlusA0 = vdpf_plus_key; using PlusA1 = vdpf_plus_key; using PlusB0 = vdpf_plus_key; using PlusB1 = vdpf_plus_key; PlusA0 plus_a0{std::move(key_a0), piA}; PlusA1 plus_a1{std::move(key_a1), piA}; PlusB0 plus_b0{std::move(key_b0), piB}; PlusB1 plus_b1{std::move(key_b1), piB}; dpf3_key<1, PlusA0, PlusB0> k1{plus_a0, plus_b0, Verifiable, Extractable, Updatable}; dpf3_key<2, PlusA1, PlusB0> k2{plus_a1, plus_b0, Verifiable, Extractable, Updatable}; dpf3_key<3, PlusA1, PlusB1> k3{plus_a1, plus_b1, Verifiable, Extractable, Updatable}; return std::make_tuple(std::move(k1), std::move(k2), std::move(k3)); } template auto make_point3(Input alpha, fp61 beta) { using X = xor61; const tau_quad t = sample_taus(beta); const X payload_a = t.t0 + t.t1; const X payload_b = t.t2 + t.t3; if constexpr (Updatable) { auto A = [&] { if constexpr (Verifiable) return dpf::make_dpf(alpha, dpf::wildcard_value{}, dpf::verifiable{}); else return dpf::make_dpf(alpha, dpf::wildcard_value{}); }(); auto B = [&] { if constexpr (Verifiable) return dpf::make_dpf(alpha, dpf::wildcard_value{}, dpf::verifiable{}); else return dpf::make_dpf(alpha, dpf::wildcard_value{}); }(); assign_xor_payload(A.first, A.second, payload_a); assign_xor_payload(B.first, B.second, payload_b); return assemble_from_spines( std::move(A.first), std::move(A.second), std::move(B.first), std::move(B.second), t, alpha); } else { auto A = [&] { if constexpr (Verifiable) return dpf::make_dpf(alpha, payload_a, dpf::verifiable{}); else return dpf::make_dpf(alpha, payload_a); }(); auto B = [&] { if constexpr (Verifiable) return dpf::make_dpf(alpha, payload_b, dpf::verifiable{}); else return dpf::make_dpf(alpha, payload_b); }(); return assemble_from_spines( std::move(A.first), std::move(A.second), std::move(B.first), std::move(B.second), t, alpha); } } /// @brief Flags carried by `verifiable` / `extractable` / `updatable` tags. /// @details Any subset, any order. A repeated tag is rejected. template struct tag_flags { static constexpr bool verifiable = (is_verifiable_tag_v> || ...); static constexpr bool extractable = (is_extractable_tag_v> || ...); static constexpr bool updatable = (is_updatable_tag_v> || ...); static constexpr bool known = ((is_verifiable_tag_v> || is_extractable_tag_v> || is_updatable_tag_v>) && ...); static constexpr std::size_t counted = static_cast(verifiable) + static_cast(extractable) + static_cast(updatable); }; template auto make_tagged(Input alpha, fp61 beta) { return make_point3(alpha, beta); } /// @brief Read the four planted τ strings from live VDPF+ evaluations at `α`. template tau_quad read_taus(const K1 & k1, const K2 & k2, const K3 & k3, Input alpha) { tau_quad t{}; t.t0 = peel(k1.a.dpf_key, alpha) + k1.a.offset; t.t1 = peel(k2.a.dpf_key, alpha) + k2.a.offset; t.t2 = peel(k1.b.dpf_key, alpha) + k1.b.offset; t.t3 = peel(k3.b.dpf_key, alpha) + k3.b.offset; return t; } template void refresh_offset(Plus & plus, Input alpha, xor61 target_delta0) { plus.offset = target_delta0 + peel(plus.dpf_key, alpha); } /// @brief XOR a precomputed naked-leaf patch onto a ready inner key. template void apply_leaf_patch(Key & key, const Leaf & patch) { using concrete = dpf::concrete_type_t>; auto & wrap = std::get<0>(key.leaf_nodes); auto & leaf = wrap.raw_leaf(); leaf = dpf::add_leaf(leaf, patch); } /// @brief Build the naked-leaf Fig-10 patch for payload difference `delta`. template auto make_leaf_patch(Input alpha, xor61 delta) { using node = typename Key::exterior_node; using concrete = dpf::concrete_type_t>; return dpf::make_naked_leaf(alpha, concrete{delta}); } } // namespace dpf3_impl } // namespace detail /// @brief Generate three (2,3) point keys for `f(α) = β`. /// @details Optional tags are `verifiable`, `extractable`, and `updatable`, /// in any order. `extractable` is the outer fp61 sketch flag; inner /// XOR keys stay ordinary. `updatable` keeps beaver leaves so /// `update_payload` can rewrite `β`. /// @note Following Zyskind, Yanai, and Pentland, ePrint 2024/1658, Figure 3: two (2,2)-VDPF+ spines and two walks. /// \complexity O(n) time. Two `make_dpf` spines (A and B), each the point-keygen loop, plus a constant number of `eval_point` peels in `assemble_from_spines`. No messages. template HEDLEY_WARN_UNUSED_RESULT auto make_dpf3(InputT alpha, fp61 beta, Tags ...tags) { using flags = detail::dpf3_impl::tag_flags; static_assert(flags::known, "make_dpf3 tags are verifiable, extractable, updatable"); static_assert(sizeof...(Tags) == flags::counted, "make_dpf3: repeated tag"); (void)std::initializer_list{((void)tags, 0)...}; return detail::dpf3_impl::make_tagged(alpha, beta); } /// @brief Evaluate one party's (2,3) key at `x`. /// \complexity O(n) time. n is `depth`. One interior traversal per level from the memoizer resume index through the leaf. Extra space is the path memoizer (O(n) nodes, or one node if it does not memoize). A proof token adds one fold per level walked. template , typename PathB = basic_path_memoizer< typename KeyT::plus_b_type::inner_type>, std::enable_if_t::is_dpf3, int> = 0> HEDLEY_WARN_UNUSED_RESULT fp61 eval_point(const KeyT & key, Query && x, PathA && path_a = PathA{}, PathB && path_b = PathB{}) { const auto qx = static_cast(x); const auto ya = detail::dpf3_impl::eval_plus(key.a, qx, std::forward(path_a)); const auto yb = detail::dpf3_impl::eval_plus(key.b, qx, std::forward(path_b)); return shamir3::xor_scale(ya + yb, fp61{static_cast(KeyT::party)}); } /// @brief Evaluate and fold an inner proof token from each VDPF+. /// \complexity O(n) time. n is `depth`. One interior traversal per level from the memoizer resume index through the leaf. Extra space is the path memoizer (O(n) nodes, or one node if it does not memoize). A proof token adds one fold per level walked. template , typename PathB = basic_path_memoizer< typename KeyT::plus_b_type::inner_type>, std::enable_if_t::is_dpf3, int> = 0> HEDLEY_WARN_UNUSED_RESULT fp61 eval_point(const KeyT & key, Query && x, prove_ref pr, PathA && path_a = PathA{}, PathB && path_b = PathB{}) { if (!key.verifiable) throw std::invalid_argument("eval_point(prove): key is not verifiable"); proof_token pa{}, pb{}; const auto qx = static_cast(x); const auto ya = detail::dpf3_impl::eval_plus(key.a, qx, prove(pa), std::forward(path_a)); const auto yb = detail::dpf3_impl::eval_plus(key.b, qx, prove(pb), std::forward(path_b)); pr.token = detail::vdpf::xor_proof(pa, pb); return shamir3::xor_scale(ya + yb, fp61{static_cast(KeyT::party)}); } /// @brief Full-domain (2,3) eval: expand each spine once, then XOR and scale. /// \complexity Same expansion as `eval_interval` on the whole domain. L = 2^{n - lg(outputs_per_leaf)} leaf nodes, n = `depth`. Time Θ(L) interior traversals. The output buffer stores one slot per domain point (2^n). template ::is_dpf3, int> = 0> HEDLEY_WARN_UNUSED_RESULT std::vector eval_full(const KeyT & key) { auto buf_a = dpf::make_output_buffer_for_full(key.a.dpf_key); auto buf_b = dpf::make_output_buffer_for_full(key.b.dpf_key); dpf::eval_full(key.a.dpf_key, buf_a); dpf::eval_full(key.b.dpf_key, buf_b); std::vector out; detail::dpf3_impl::combine_spine_bufs(key, buf_a, buf_b, out); return out; } /// @brief Add a (2,3) full-domain expansion into a caller's share vector. /// @details `buf[i] += eval_full(key)[i]` for every slot. Shamir shares are /// linear, so summing appends per party and reconstructing any two /// recovers the running total. A (2,3) ledger folds each append with /// this call instead of an `eval_point` loop. /// \complexity Same expansion as `eval_full` on the (2,3) key. template ::is_dpf3, int> = 0> void eval_full_add_into(Buffer & buf, const KeyT & key) // NOLINT(runtime/references) { const auto full = eval_full(key); const std::size_t n = std::min(full.size(), buf.size()); for (std::size_t i = 0; i < n; ++i) buf[i] = buf[i] + full[i]; } /// @brief Dot a (2,3) full-domain expansion with a public table. /// @details `sum_i eval_full(key)[i] * weights[i]`. Shamir shares are linear, /// so any two parties' dots reconstruct the table entry at `α` when /// the payload is `1`. A three-server PIR is this call per server. /// \complexity Same expansion as `eval_full` on the (2,3) key, plus one /// multiply-add per domain point. template ::is_dpf3, int> = 0> HEDLEY_WARN_UNUSED_RESULT fp61 eval_full_inner_product(const KeyT & key, const Weights & weights) { const auto full = eval_full(key); fp61 acc{}; const std::size_t n = std::min(full.size(), weights.size()); for (std::size_t i = 0; i < n; ++i) { const auto & w = weights[i]; if constexpr (std::is_same_v, fp61>) acc = acc + full[i] * w; else acc = acc + full[i] * fp61{static_cast(w)}; } return acc; } /// @brief Interval (2,3) eval into an `fp61` buffer. /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template ::is_dpf3, int> = 0> HEDLEY_WARN_UNUSED_RESULT std::vector eval_interval(const KeyT & key, LaneT from, LaneT to) { auto buf_a = dpf::make_output_buffer(key.a.dpf_key, from, to); auto buf_b = dpf::make_output_buffer(key.b.dpf_key, from, to); dpf::eval_interval(key.a.dpf_key, from, to, buf_a); dpf::eval_interval(key.b.dpf_key, from, to, buf_b); std::vector out; detail::dpf3_impl::combine_spine_bufs(key, buf_a, buf_b, out); return out; } /// @brief Pack an evaluation as a typed Shamir share. template = 0> HEDLEY_NO_THROW HEDLEY_CONST HEDLEY_ALWAYS_INLINE constexpr shamir3::share as_share(const KeyT &, fp61 y) noexcept { return shamir3::share{KeyT::party, y}; } /// @brief The same evaluation as a party-tagged (2,3) Shamir share. /// @details `dpf3` parties are `1`, `2`, `3`. The typed share's party is one less. template = 0> HEDLEY_NO_THROW HEDLEY_CONST HEDLEY_ALWAYS_INLINE constexpr shamir_share(KeyT::party - 1)> as_shamir_share(const KeyT &, fp61 y) noexcept { return shamir_share(KeyT::party - 1)>::from_raw(y); } /// @brief Reconstruct from any two (2,3) evaluation shares. HEDLEY_WARN_UNUSED_RESULT inline fp61 reconstruct(shamir3::share a, shamir3::share b) { return shamir3::reconstruct(a, b); } /// @brief Reconstruct from all three shares. HEDLEY_WARN_UNUSED_RESULT inline fp61 reconstruct(shamir3::share a, shamir3::share b, shamir3::share c) { return shamir3::reconstruct(a, b, c); } /// @brief Three-party proof token: two inner tokens plus the public offsets. struct dpf3_proof { proof_token a{}; proof_token b{}; shamir3::xor61 offset_a{}; shamir3::xor61 offset_b{}; }; /// @brief Build a three-party proof at `x`. template = 0> HEDLEY_WARN_UNUSED_RESULT dpf3_proof prove_dpf3(const KeyT & key, Query && x) { if (!key.verifiable) throw std::invalid_argument("prove_dpf3: key is not verifiable"); dpf3_proof out{}; out.offset_a = key.a.offset; out.offset_b = key.b.offset; const auto qx = static_cast(x); std::ignore = detail::dpf3_impl::eval_plus(key.a, qx, prove(out.a)); std::ignore = detail::dpf3_impl::eval_plus(key.b, qx, prove(out.b)); return out; } /// @brief Verify three (2,3) proofs agree on offsets and inner tokens. HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_WARN_UNUSED_RESULT bool verify_dpf3(const dpf3_proof & p1, const dpf3_proof & p2, const dpf3_proof & p3) noexcept { if (p1.offset_a != p2.offset_a || p2.offset_a != p3.offset_a) return false; if (p1.offset_b != p2.offset_b || p2.offset_b != p3.offset_b) return false; if (!verify(p1.a, p2.a)) return false; if (!verify(p2.a, p3.a)) return false; if (!verify(p1.b, p2.b)) return false; if (!verify(p2.b, p3.b)) return false; return true; } /// @brief In-place payload update of three updatable (2,3) keys (Fig. 10). /// @details Reads the live `τ` strings from evaluations at `α`, samples a /// fresh Shamir split of `β'`, patches the four inner XOR leaves by /// the payload difference, refreshes `π`, and leaves the tree path /// untouched. Requires keys generated with `dpf::updatable`. /// @tparam K1 party-1 key type /// @tparam K2 party-2 key type /// @tparam K3 party-3 key type /// @tparam InputT input domain type /// @param k1 party 1 key /// @param k2 party 2 key /// @param k3 party 3 key /// @param alpha the same secret point /// @param beta_new the new payload /// @throws std::invalid_argument if any key is not updatable template void update_payload(K1 & k1, K2 & k2, K3 & k3, InputT alpha, fp61 beta_new) { static_assert(K1::is_dpf3 && K2::is_dpf3 && K3::is_dpf3, "dpf3 keys"); if (!k1.updatable || !k2.updatable || !k3.updatable) throw std::invalid_argument( "update_payload: keys were not generated with dpf::updatable"); using X = detail::dpf3_impl::xor61; const auto told = detail::dpf3_impl::read_taus(k1, k2, k3, alpha); 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); // After Beaver assign both parties hold the same leaf CW. Patch every // copy of each spine's CW by the payload difference (Fig. 10). // A0 on p1; A1 on p2 and p3. detail::dpf3_impl::patch_leaf_xor(k1.a.dpf_key, alpha, dA); detail::dpf3_impl::patch_leaf_xor(k2.a.dpf_key, alpha, dA); detail::dpf3_impl::patch_leaf_xor(k3.a.dpf_key, alpha, dA); // B0 on p1 and p2; B1 on p3. detail::dpf3_impl::patch_leaf_xor(k1.b.dpf_key, alpha, dB); detail::dpf3_impl::patch_leaf_xor(k2.b.dpf_key, alpha, dB); detail::dpf3_impl::patch_leaf_xor(k3.b.dpf_key, alpha, dB); // π is public and identical on both halves of each VDPF+. // Leaf patches and the refreshed offset are re-bound on the next prove // (`init_proof` folds the leaf CW; `eval_plus` folds the offset). detail::dpf3_impl::refresh_offset(k1.a, alpha, tnew.t0); k2.a.offset = k1.a.offset; k3.a.offset = k1.a.offset; detail::dpf3_impl::refresh_offset(k1.b, alpha, tnew.t2); k2.b.offset = k1.b.offset; k3.b.offset = k1.b.offset; } /// @brief Weight-1 sketch over three Shamir full-domain vectors (ungated). /// @details Prefer the key-taking overload, which enforces `dpf::extractable`. template HEDLEY_WARN_UNUSED_RESULT bool sketch_verify3(const std::vector & s1, const std::vector & s2, const std::vector & s3, RRange && challenges) { if (s1.size() != s2.size() || s2.size() != s3.size()) return false; std::vector opened; opened.reserve(s1.size()); std::vector rs; rs.reserve(s1.size()); std::size_t i = 0; for (auto && r : challenges) { if (i >= s1.size()) return false; opened.push_back(shamir3::reconstruct( shamir3::share{1, s1[i]}, shamir3::share{2, s2[i]}, shamir3::share{3, s3[i]})); rs.push_back(r); ++i; } if (i != s1.size()) return false; sketch_share sk = sketch_fold(opened, rs); sketch_share zero{}; return sketch_verify(sk, zero); } /// @brief Weight-1 sketch gated on extractable (2,3) keys. /// @throws std::invalid_argument if any key lacks `dpf::extractable` template HEDLEY_WARN_UNUSED_RESULT bool sketch_verify3(const K1 & k1, const K2 & k2, const K3 & k3, const std::vector & s1, const std::vector & s2, const std::vector & s3, RRange && challenges) { static_assert(K1::is_dpf3 && K2::is_dpf3 && K3::is_dpf3, "dpf3 keys"); if (!k1.extractable || !k2.extractable || !k3.extractable) throw std::invalid_argument( "sketch_verify3: keys were not generated with dpf::extractable"); return sketch_verify3(s1, s2, s3, std::forward(challenges)); } /// @brief Point proofs plus weight-1 on the opened Shamir full-domain vector. /// @details Completes the paper's three-party statistic after inner verifies. /// Ungated; prefer the key-taking overload for extractable keys. template HEDLEY_WARN_UNUSED_RESULT bool verify_dpf3(const dpf3_proof & p1, const dpf3_proof & p2, const dpf3_proof & p3, const std::vector & s1, const std::vector & s2, const std::vector & s3, RRange && challenges) { if (!verify_dpf3(p1, p2, p3)) return false; return sketch_verify3(s1, s2, s3, std::forward(challenges)); } /// @brief Verifiable + extractable check: proofs then gated weight-1 sketch. /// @throws std::invalid_argument if any key lacks `dpf::extractable` template HEDLEY_WARN_UNUSED_RESULT bool verify_dpf3(const K1 & k1, const K2 & k2, const K3 & k3, const dpf3_proof & p1, const dpf3_proof & p2, const dpf3_proof & p3, const std::vector & s1, const std::vector & s2, const std::vector & s3, RRange && challenges) { static_assert(K1::is_dpf3 && K2::is_dpf3 && K3::is_dpf3, "dpf3 keys"); if (!k1.extractable || !k2.extractable || !k3.extractable) throw std::invalid_argument( "verify_dpf3: keys were not generated with dpf::extractable"); if (!k1.verifiable || !k2.verifiable || !k3.verifiable) throw std::invalid_argument( "verify_dpf3: keys were not generated with dpf::verifiable"); return verify_dpf3(p1, p2, p3, s1, s2, s3, std::forward(challenges)); } /// @brief Fresh three-party keys at the same point (new trees — not an update). /// @details Same tags as `make_dpf3`. Use when the key was not generated /// `updatable`, or when the dealer chooses to re-key. Moving `α` /// also requires this path. template HEDLEY_WARN_UNUSED_RESULT auto remake_dpf3(InputT alpha, fp61 beta_new, Tags ...tags) { return make_dpf3(alpha, beta_new, tags...); } } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_DPF3_HPP__