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

129 lines
5.6 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/caller_fold.hpp
/// @brief One-pass caller fold for the full-domain / point / sequence walks.
/// @details `sketch_ref` folds every extractable share into three `fp61`
/// moments. A protocol whose audit lives in a different group (the
/// Express multiplication proof, Pika's Schwartz–Zippel check, a
/// keyword-PIR record XOR) wants the same one-pass hook without a
/// second expansion and without the `fp61` conversion. These helpers
/// take an inlined `fold(index, share)` callable, invoked once per
/// written output in the same loop that writes the buffer. The fold is
/// a template argument, not a virtual call, so it inlines exactly the
/// way `sketch_ref::absorb` does today. `dpf::sketch` stays the
/// weight-1 `fp61` fold; existing call sites are untouched.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
#ifndef LIBDPF_INCLUDE_DPF_CALLER_FOLD_HPP__
#define LIBDPF_INCLUDE_DPF_CALLER_FOLD_HPP__
#include <cstddef>
#include <iterator>
#include <type_traits>
#include <utility>
#include "hedley/hedley.h"
#include "dpf/dpf_key.hpp"
#include "dpf/eval_common.hpp"
#include "dpf/eval_full.hpp"
#include "dpf/eval_point.hpp"
#include "dpf/eval_target.hpp"
#include "dpf/eval_walk.hpp"
namespace dpf
{
namespace detail_fold
{
/// @brief True when `Fold` is callable as `fold(std::size_t, share)` for the
/// group element written by output `I` of `DpfKey`.
template <typename Fold, typename DpfKey, std::size_t I, typename = void>
struct is_index_fold : std::false_type {};
template <typename Fold, typename DpfKey, std::size_t I>
struct is_index_fold<Fold, DpfKey, I,
std::void_t<decltype(std::declval<Fold &>()(
std::declval<std::size_t>(),
std::declval<typename DpfKey::template concrete_output_type<I>>()))>>
: std::true_type {};
template <typename Fold, typename DpfKey, std::size_t I>
inline constexpr bool is_index_fold_v =
is_index_fold<std::decay_t<Fold>, DpfKey, I>::value;
} // namespace detail_fold
// N.B.: the one-pass `eval_full_add_into(buf, key, fold)` hook lives in
// `dpf/eval_walk.hpp` alongside the `rotate` / `sketch_ref` overloads. This
// header adds the remaining fold-carrying walks (allocate-and-fold full eval,
// point eval, and the full-domain keyword-PIR XOR) so every walk that accepts
// a `sketch_ref` also accepts a generic caller fold.
/// @brief Full-domain expansion of output `I`, folding each written share.
/// @details Allocates a fresh buffer (like `eval_full(key)`), folds every point
/// into `fold`, and returns the `(buffer, iterable)` pair so the caller
/// keeps the expanded shares as well as the audit.
/// \complexity One full-domain expansion, `Θ(2^n)`, plus one fold per point.
template <std::size_t I = 0, typename DpfKey, typename Fold,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>
&& detail_fold::is_index_fold_v<Fold, DpfKey, I>, bool> = true>
HEDLEY_WARN_UNUSED_RESULT
auto eval_full_fold(const DpfKey & dpf, Fold fold)
{
auto result = eval_full<I>(dpf);
auto & iter = result.second;
std::size_t i = 0;
for (auto it = std::begin(iter); it != std::end(iter); ++it, ++i)
fold(i, detail_walk::group_value(*it));
return result;
}
/// @brief Evaluate output `I` at `x` and fold the single written share.
/// @details The fold is called once, with index `0`, matching the one output
/// `eval_point` writes.
/// \complexity O(n) time; one interior traversal per level.
template <std::size_t I = 0, typename DpfKey, typename InputT, typename Fold,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>
&& detail_fold::is_index_fold_v<Fold, DpfKey, I>, bool> = true>
auto eval_point_fold(const DpfKey & dpf, InputT && x, Fold fold)
{
auto out = eval_point<I>(dpf, std::forward<InputT>(x));
fold(std::size_t{0}, detail_walk::group_value(*out));
return out;
}
/// @brief Keyword-PIR XOR fold: `⊕ records[i]` over `i` where `DPF_I(i)` is set.
/// @details Runs one full-domain bit expansion of output `I` and, in that same
/// loop, XORs `records[i]` into a running accumulator whenever the
/// party's bit share at `i` is 1 — without ever materializing the bit
/// vector. Each server returns its share of the XOR; the two shares
/// reconstruct (XOR) to `⊕ records[i]` over the 1-set of the DPF, i.e.
/// the matched record for a point key. `records` is indexed by domain
/// point and must cover the domain (or at least the evaluated prefix).
/// \complexity One full-domain expansion, `Θ(2^n)`, plus one XOR per set bit.
template <std::size_t I = 0, typename DpfKey, typename Records,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_WARN_UNUSED_RESULT
auto eval_sequence_xor(const DpfKey & key, const Records & records)
{
using record_type = std::decay_t<decltype(records[std::size_t{0}])>;
auto result = eval_full<I>(key);
auto & iter = result.second;
record_type acc{};
std::size_t i = 0;
const std::size_t n = static_cast<std::size_t>(std::size(records));
for (auto it = std::begin(iter); it != std::end(iter) && i < n; ++it, ++i)
{
if (static_cast<bool>(*it))
acc = static_cast<record_type>(acc ^ records[i]);
}
return acc;
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_CALLER_FOLD_HPP__