libdpf/include/dpf/dpf3.hpp
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

775 lines
30 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// @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 <array>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#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 <typename Inner>
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 <typename Inner, typename PathMemoizer = basic_path_memoizer<Inner>>
HEDLEY_WARN_UNUSED_RESULT
xor61 eval_plus(const vdpf_plus_key<Inner> & key, typename Inner::input_type x,
PathMemoizer && path = PathMemoizer{})
{
const auto y = *dpf::eval_point(key.dpf_key, x,
std::forward<PathMemoizer>(path));
if constexpr (is_secret_share_v<std::decay_t<decltype(y)>>)
return xor61{y.raw()} + key.offset;
else
return xor61{static_cast<std::uint64_t>(y)} + key.offset;
}
template <typename Inner, typename PathMemoizer = basic_path_memoizer<Inner>>
HEDLEY_WARN_UNUSED_RESULT
xor61 eval_plus(const vdpf_plus_key<Inner> & 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<PathMemoizer>(path));
// Re-bind the public offset (refreshed by `update_payload`).
const auto off = static_cast<std::uint64_t>(key.offset);
detail::vdpf::fold_bytes(pr.token, 0x50, &off, sizeof(off));
if constexpr (is_secret_share_v<std::decay_t<decltype(y)>>)
return xor61{y.raw()} + key.offset;
else
return xor61{static_cast<std::uint64_t>(y)} + key.offset;
}
template <typename Buf>
xor61 xor61_from_buf_elem(const Buf & e)
{
if constexpr (is_secret_share_v<std::decay_t<Buf>>)
return xor61{e.raw()};
else
return xor61{static_cast<std::uint64_t>(e)};
}
template <typename KeyT, typename BufA, typename BufB>
void combine_spine_bufs(const KeyT & key, const BufA & a, const BufB & b,
std::vector<fp61> & out)
{
const std::size_t n = a.size();
out.resize(n);
const fp61 scale{static_cast<std::uint64_t>(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 <typename Inner>
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<std::decay_t<decltype(y)>>)
return xor61{y.raw()};
else
return xor61{static_cast<std::uint64_t>(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<std::uint64_t>() & 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<std::uint64_t>(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 <typename Key, typename Output = xor61>
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<Output>;
auto & wrap = std::get<0>(key.leaf_nodes);
auto & leaf = wrap.raw_leaf();
leaf = dpf::add_leaf<concrete>(leaf,
dpf::make_naked_leaf<node>(alpha, concrete{delta}));
}
template <typename K0, typename K1, typename Share0, typename Share1>
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 <typename K0, typename K1>
void assign_xor_payload(K0 & k0, K1 & k1, xor61 payload)
{
const xor61 s0{uniform_sample<std::uint64_t>()};
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 <int Party, typename PlusA, typename PlusB>
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 <typename InputT>
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<InputT>{};
using I = decltype(to_int(x0));
return utils::make_from_integral_value<InputT>{}(
static_cast<I>(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 <bool Verifiable, bool Extractable, bool Updatable, typename KeyA0,
typename KeyA1, typename KeyB0, typename KeyB1, typename Input>
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<KeyA0>;
using PlusA1 = vdpf_plus_key<KeyA1>;
using PlusB0 = vdpf_plus_key<KeyB0>;
using PlusB1 = vdpf_plus_key<KeyB1>;
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 <typename Input, typename InteriorPRG, typename ExteriorPRG,
bool Verifiable, bool Extractable, bool Updatable>
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<InteriorPRG, ExteriorPRG>(alpha,
dpf::wildcard_value<X>{}, dpf::verifiable{});
else
return dpf::make_dpf<InteriorPRG, ExteriorPRG>(alpha,
dpf::wildcard_value<X>{});
}();
auto B = [&] {
if constexpr (Verifiable)
return dpf::make_dpf<InteriorPRG, ExteriorPRG>(alpha,
dpf::wildcard_value<X>{}, dpf::verifiable{});
else
return dpf::make_dpf<InteriorPRG, ExteriorPRG>(alpha,
dpf::wildcard_value<X>{});
}();
assign_xor_payload(A.first, A.second, payload_a);
assign_xor_payload(B.first, B.second, payload_b);
return assemble_from_spines<Verifiable, Extractable, Updatable>(
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<InteriorPRG, ExteriorPRG>(alpha, payload_a,
dpf::verifiable{});
else
return dpf::make_dpf<InteriorPRG, ExteriorPRG>(alpha, payload_a);
}();
auto B = [&] {
if constexpr (Verifiable)
return dpf::make_dpf<InteriorPRG, ExteriorPRG>(alpha, payload_b,
dpf::verifiable{});
else
return dpf::make_dpf<InteriorPRG, ExteriorPRG>(alpha, payload_b);
}();
return assemble_from_spines<Verifiable, Extractable, Updatable>(
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 <typename ...Tags>
struct tag_flags
{
static constexpr bool verifiable =
(is_verifiable_tag_v<std::decay_t<Tags>> || ...);
static constexpr bool extractable =
(is_extractable_tag_v<std::decay_t<Tags>> || ...);
static constexpr bool updatable =
(is_updatable_tag_v<std::decay_t<Tags>> || ...);
static constexpr bool known = ((is_verifiable_tag_v<std::decay_t<Tags>>
|| is_extractable_tag_v<std::decay_t<Tags>>
|| is_updatable_tag_v<std::decay_t<Tags>>) && ...);
static constexpr std::size_t counted =
static_cast<std::size_t>(verifiable)
+ static_cast<std::size_t>(extractable)
+ static_cast<std::size_t>(updatable);
};
template <bool Verifiable, bool Extractable, bool Updatable,
typename InteriorPRG, typename ExteriorPRG, typename Input>
auto make_tagged(Input alpha, fp61 beta)
{
return make_point3<Input, InteriorPRG, ExteriorPRG, Verifiable, Extractable,
Updatable>(alpha, beta);
}
/// @brief Read the four planted τ strings from live VDPF+ evaluations at `α`.
template <typename K1, typename K2, typename K3, typename Input>
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 <typename Plus, typename Input>
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 <typename Key, typename Leaf>
void apply_leaf_patch(Key & key, const Leaf & patch)
{
using concrete = dpf::concrete_type_t<typename Key::template output_type_t<0>>;
auto & wrap = std::get<0>(key.leaf_nodes);
auto & leaf = wrap.raw_leaf();
leaf = dpf::add_leaf<concrete>(leaf, patch);
}
/// @brief Build the naked-leaf Fig-10 patch for payload difference `delta`.
template <typename Key, typename Input>
auto make_leaf_patch(Input alpha, xor61 delta)
{
using node = typename Key::exterior_node;
using concrete = dpf::concrete_type_t<typename Key::template output_type_t<0>>;
return dpf::make_naked_leaf<node>(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 <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename ...Tags>
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf3(InputT alpha, fp61 beta, Tags ...tags)
{
using flags = detail::dpf3_impl::tag_flags<Tags...>;
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<int>{((void)tags, 0)...};
return detail::dpf3_impl::make_tagged<flags::verifiable, flags::extractable,
flags::updatable, InteriorPRG, ExteriorPRG>(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 KeyT, typename Query,
typename PathA = basic_path_memoizer<
typename KeyT::plus_a_type::inner_type>,
typename PathB = basic_path_memoizer<
typename KeyT::plus_b_type::inner_type>,
std::enable_if_t<std::decay_t<KeyT>::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<typename KeyT::input_type>(x);
const auto ya = detail::dpf3_impl::eval_plus(key.a, qx,
std::forward<PathA>(path_a));
const auto yb = detail::dpf3_impl::eval_plus(key.b, qx,
std::forward<PathB>(path_b));
return shamir3::xor_scale(ya + yb,
fp61{static_cast<std::uint64_t>(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 KeyT, typename Query,
typename PathA = basic_path_memoizer<
typename KeyT::plus_a_type::inner_type>,
typename PathB = basic_path_memoizer<
typename KeyT::plus_b_type::inner_type>,
std::enable_if_t<std::decay_t<KeyT>::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<typename KeyT::input_type>(x);
const auto ya = detail::dpf3_impl::eval_plus(key.a, qx, prove(pa),
std::forward<PathA>(path_a));
const auto yb = detail::dpf3_impl::eval_plus(key.b, qx, prove(pb),
std::forward<PathB>(path_b));
pr.token = detail::vdpf::xor_proof(pa, pb);
return shamir3::xor_scale(ya + yb,
fp61{static_cast<std::uint64_t>(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 <typename KeyT,
std::enable_if_t<std::decay_t<KeyT>::is_dpf3, int> = 0>
HEDLEY_WARN_UNUSED_RESULT
std::vector<fp61> 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<fp61> 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 <typename KeyT, typename Buffer,
std::enable_if_t<std::decay_t<KeyT>::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<std::size_t>(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 <typename KeyT, typename Weights,
std::enable_if_t<std::decay_t<KeyT>::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<std::size_t>(full.size(), weights.size());
for (std::size_t i = 0; i < n; ++i)
{
const auto & w = weights[i];
if constexpr (std::is_same_v<std::decay_t<decltype(w)>, fp61>)
acc = acc + full[i] * w;
else
acc = acc + full[i] * fp61{static_cast<std::uint64_t>(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 <typename KeyT, typename LaneT,
std::enable_if_t<std::decay_t<KeyT>::is_dpf3, int> = 0>
HEDLEY_WARN_UNUSED_RESULT
std::vector<fp61> 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<fp61> 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 <typename KeyT, std::enable_if_t<KeyT::is_dpf3, int> = 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 <typename KeyT, std::enable_if_t<KeyT::is_dpf3, int> = 0>
HEDLEY_NO_THROW
HEDLEY_CONST
HEDLEY_ALWAYS_INLINE
constexpr shamir_share<fp61, static_cast<std::size_t>(KeyT::party - 1)>
as_shamir_share(const KeyT &, fp61 y) noexcept
{
return shamir_share<fp61, static_cast<std::size_t>(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 <typename KeyT, typename Query,
std::enable_if_t<KeyT::is_dpf3, int> = 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<typename KeyT::input_type>(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 <typename K1, typename K2, typename K3, typename InputT>
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 <typename RRange>
HEDLEY_WARN_UNUSED_RESULT
bool sketch_verify3(const std::vector<fp61> & s1, const std::vector<fp61> & s2,
const std::vector<fp61> & s3, RRange && challenges)
{
if (s1.size() != s2.size() || s2.size() != s3.size())
return false;
std::vector<fp61> opened;
opened.reserve(s1.size());
std::vector<fp61> 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 <typename K1, typename K2, typename K3, typename RRange>
HEDLEY_WARN_UNUSED_RESULT
bool sketch_verify3(const K1 & k1, const K2 & k2, const K3 & k3,
const std::vector<fp61> & s1, const std::vector<fp61> & s2,
const std::vector<fp61> & 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<RRange>(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 <typename RRange>
HEDLEY_WARN_UNUSED_RESULT
bool verify_dpf3(const dpf3_proof & p1, const dpf3_proof & p2,
const dpf3_proof & p3, const std::vector<fp61> & s1,
const std::vector<fp61> & s2, const std::vector<fp61> & s3,
RRange && challenges)
{
if (!verify_dpf3(p1, p2, p3))
return false;
return sketch_verify3(s1, s2, s3, std::forward<RRange>(challenges));
}
/// @brief Verifiable + extractable check: proofs then gated weight-1 sketch.
/// @throws std::invalid_argument if any key lacks `dpf::extractable`
template <typename K1, typename K2, typename K3, typename RRange>
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<fp61> & s1, const std::vector<fp61> & s2,
const std::vector<fp61> & 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<RRange>(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 <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename ...Tags>
HEDLEY_WARN_UNUSED_RESULT
auto remake_dpf3(InputT alpha, fp61 beta_new, Tags ...tags)
{
return make_dpf3<InteriorPRG, ExteriorPRG>(alpha, beta_new, tags...);
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_DPF3_HPP__