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:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
775
include/dpf/dpf3.hpp
Normal file
775
include/dpf/dpf3.hpp
Normal 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__
|
||||
Loading…
Add table
Add a link
Reference in a new issue