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

775
include/dpf/dpf3.hpp Normal file
View file

@ -0,0 +1,775 @@
/// @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__