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

916 lines
39 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/eval_interval.hpp
/// @brief Evaluate every input in a closed interval.
/// @details `[from, to]` is inclusive. The returned iterable yields one
/// share per input, in that order. Pass a named output buffer;
/// this overload binds it as a non-const reference. An interval
/// memoizer is optional and comes after the buffer.
///
/// Eager evaluation requires an assigned input offset: the range is
/// traversed at `offset_x(from)..offset_x(to)`. When the input is
/// still a wildcard, call `defer_eval_interval` instead — that fills
/// a **full-domain** buffer at identity and returns a
/// `deferred_rotated_subinterval` that applies the rotation after
/// `assign_wildcard_input`. Interior-only prep with an assigned
/// input but unassigned leaf is `defer_traverse_interval`.
/// @snippet evaluation/eval_interval.cpp eval-interval
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @author Christopher Jiang <christopher.jiang@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 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_EVAL_INTERVAL_HPP__
#define LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__
#include <portable-snippets/builtin/builtin.h>
#include <portable-snippets/exact-int/exact-int.h>
#include "hedley/hedley.h"
#include <cstddef>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <array>
#include <tuple>
#include <type_traits>
#include <iterator>
#include <utility>
#include "dpf/dpf_key.hpp"
#include "dpf/eval_common.hpp"
#include "dpf/eval_target.hpp"
#include "dpf/output_buffer.hpp"
#include "dpf/interval_memoizer.hpp"
#include "dpf/subinterval_iterable.hpp"
#include "dpf/deferred_rotated_subinterval.hpp"
#include "dpf/verifiable.hpp"
#include "dpf/wildcard.hpp"
namespace dpf
{
namespace internal
{
/// @brief Fold every node at `level_index` of a truncated interval tree into `pi`.
/// @details Once-per-BFS-node absorption: node `i` has prefix
/// `(from_node >> (depth - level_index)) + i`. Matches the contiguous
/// layout built by `eval_interval_interior`.
template <typename DpfKey, typename IntegralT, typename NodeT>
HEDLEY_ALWAYS_INLINE
void fold_interval_level(proof_token & pi, const DpfKey & dpf,
std::size_t level_index, IntegralT from_node, std::size_t nodes_at_level,
const NodeT * curr)
{
if constexpr (!DpfKey::is_verifiable)
return;
if (level_index == 0 || nodes_at_level == 0)
return;
const auto start = static_cast<psnip_uint64_t>(
utils::shift_right(from_node, DpfKey::depth - level_index));
const auto & cs = dpf.correction_seeds()[level_index - 1];
for (std::size_t i = 0; i < nodes_at_level; ++i)
{
detail::vdpf::fold_node(pi, level_index - 1, start + i, curr[i], cs);
}
}
template <typename DpfKey,
typename IntervalMemoizer,
typename IntegralT = typename DpfKey::integral_type>
inline auto eval_interval_interior(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, IntervalMemoizer & memoizer, // NOLINT(runtime/references)
std::size_t to_level = DpfKey::depth, proof_token * pi = nullptr)
{
using dpf_type = DpfKey;
using integral_type = typename DpfKey::integral_type;
using node_type = typename DpfKey::interior_node;
// level_index represents the current level being built
// level_index = 0 => root
// level_index = depth => last layer of interior nodes
// Proving needs every truncated-tree node: a warm memoizer that resumes
// past level 1 would skip upper folds (and basic memoizers discard them).
if (pi != nullptr)
memoizer.clear_assignment();
std::size_t level_index = memoizer.assign_interval(dpf, from_node, to_node);
std::size_t nodes_at_level = memoizer.get_nodes_at_level();
integral_type mask = utils::get_node_mask<dpf_type>(dpf.msb_mask, level_index);
for (; level_index <= to_level; level_index = memoizer.advance_level(), nodes_at_level = memoizer.get_nodes_at_level(), mask>>=1)
{
std::size_t i = 0, j = 0;
bool from_offset = mask & from_node,
to_offset = from_offset ^ (nodes_at_level & 1);
const node_type cw[2] = {
dpf.correction_word(level_index-1, 0),
dpf.correction_word(level_index-1, 1)
};
const bool is_last = dpf_type::tree::is_last_level(level_index - 1,
dpf.depth);
auto *prev = memoizer[level_index-1];
auto *curr = memoizer[level_index];
// process node which only requires a right traversal
if (from_offset == true)
{
curr[i++] = dpf_type::traverse_interior(prev[j++], cw[1], 1, is_last);
}
// process all nodes which require both a left traversal and a right traversal
const std::size_t both_end = nodes_at_level - to_offset;
while (i + 8 <= both_end)
{
alignas(node_type) node_type parents[4];
alignas(node_type) node_type left[4];
alignas(node_type) node_type right[4];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
parents[t] = prev[j + t];
}
dpf_type::traverse_interior01_x4(parents, cw[0], cw[1], left, right,
is_last);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
curr[i + 2 * t] = left[t];
curr[i + 2 * t + 1] = right[t];
}
i += 8;
j += 4;
}
DPF_UNROLL_LOOP
for (; i < both_end;)
{
auto cur_node = prev[j++];
auto kids = dpf_type::traverse_interior01(cur_node, cw[0], cw[1],
is_last);
curr[i++] = kids[0];
curr[i++] = kids[1];
}
// process node which only requires a left traversal
if (to_offset == true)
{
curr[i] = dpf_type::traverse_interior(prev[j], cw[0], 0, is_last);
}
if (pi != nullptr)
{
fold_interval_level(*pi, dpf, level_index, from_node,
nodes_at_level, curr);
}
}
}
template <std::size_t I,
typename DpfKey,
typename OutputBuffer,
typename IntervalMemoizer,
typename IntegralT = typename DpfKey::integral_type>
inline auto eval_interval_exterior(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, OutputBuffer && outbuf, IntervalMemoizer && memoizer,
std::size_t start = 0)
{
assert_not_wildcard_output<I>(dpf);
if (HEDLEY_UNLIKELY(to_node < from_node && to_node != IntegralT{0}))
throw std::runtime_error("to_node<from_node");
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
std::size_t nodes_in_interval = static_cast<std::size_t>(to_node - from_node);
auto *nodes = memoizer[dpf_type::depth];
std::size_t j = 0, k = start;
constexpr bool batch_leaves =
dpf::block_length_of_leaf_v<output_type, typename DpfKey::interior_node> == 1
&& !utils::is_packed_subbyte_v<output_type>
&& dpf_type::outputs_per_leaf == 1;
if constexpr (batch_leaves)
{
using leaf_ret = decltype(dpf.template traverse_exterior<I>(nodes[0]));
while (j + 8 <= nodes_in_interval)
{
leaf_ret leaves[8];
dpf.template traverse_exterior_x8<I>(nodes + j, leaves);
for (std::size_t t = 0; t < 8; ++t, ++k)
{
utils::raw_memcpy(&outbuf[k], &leaves[t], sizeof(output_type));
}
j += 8;
}
}
for (; j < nodes_in_interval; ++j, ++k)
{
// 1-arg member works for classic and verifiable/incr keys; the static
// 2-arg form is classic-only.
auto leaf = dpf.template traverse_exterior<I>(nodes[j]);
if constexpr (utils::is_packed_subbyte_v<output_type>)
{
store_leaf_bytes(outbuf, k, leaf);
}
else
{
utils::raw_memcpy(&outbuf[k*dpf_type::outputs_per_leaf], &leaf,
sizeof(output_type) * dpf_type::outputs_per_leaf);
}
}
}
template <std::size_t I,
typename DpfKey,
typename OutputBuffer,
typename LeafT>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
void store_interval_leaf(OutputBuffer && outbuf, std::size_t k, const LeafT & leaf) noexcept
{
using dpf_type = DpfKey;
using output_type = typename DpfKey::concrete_output_type<I>;
if constexpr (utils::is_packed_subbyte_v<output_type>)
{
store_leaf_bytes(outbuf, k, leaf);
}
else
{
utils::raw_memcpy(&outbuf[k * dpf_type::outputs_per_leaf], &leaf,
sizeof(output_type) * dpf_type::outputs_per_leaf);
}
}
/// @brief One pass over the leaf-level interior nodes. When the selected output
/// indices occupy a contiguous PRG-position range, a single batched
/// `ExteriorPRG::eval` produces every output's leaf mask.
/// @tparam Is is
/// @tparam DpfKey DPF key type
/// @tparam OutputBuffers tuple of output buffers
/// @tparam IntervalMemoizer interval memoizer type
/// @tparam IntegralT integral type
/// @tparam IIs iis
/// @param dpf the DPF key
/// @param from_node the `from_node`
/// @param to_node the `to_node`
/// @param outbufs the named output buffers
/// @param memoizer the memoizer built for this key
/// @param start the start of the range
/// @throws std::runtime_error if `to_node<from_node`
template <std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename IntervalMemoizer,
typename IntegralT,
std::size_t ...IIs>
inline void eval_interval_exterior_fused(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, OutputBuffers && outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>, std::size_t start = 0)
{
assert_not_wildcard_output<Is...>(dpf);
if (HEDLEY_UNLIKELY(to_node < from_node && to_node != IntegralT{0}))
throw std::runtime_error("to_node<from_node");
using node_type = typename DpfKey::exterior_node;
using outputs_tuple = typename DpfKey::concrete_outputs_tuple;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using range = leaf_prg_range<node_type, outputs_tuple, Is...>;
HEDLEY_PRAGMA(GCC diagnostic pop)
std::size_t nodes_in_interval = static_cast<std::size_t>(to_node - from_node);
auto *nodes = memoizer[DpfKey::depth];
auto cws = std::make_tuple(std::get<Is>(dpf.leaf_nodes).get()...);
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto apply_masks = [&](std::size_t k, const node_type & node,
const node_type * HEDLEY_RESTRICT masks)
{
auto apply_output = [&](auto out_index, auto buf_index)
{
constexpr std::size_t out_i = decltype(out_index)::value;
constexpr std::size_t buf_i = decltype(buf_index)::value;
using output_type = typename DpfKey::concrete_output_type<out_i>;
using leaf_type = dpf::leaf_node_t<node_type, output_type>;
constexpr auto pos = block_offset_of_leaf_v<out_i, node_type, outputs_tuple>;
leaf_type mask;
std::memcpy(&mask, masks + (pos - range::pos_min), sizeof(leaf_type));
// Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β.
auto leaf = dpf::subtract_leaf<output_type>(
get_if_lo_bit(std::get<buf_i>(cws), node), mask);
store_interval_leaf<out_i, DpfKey>(utils::get<buf_i>(outbufs), k, leaf);
};
(apply_output(std::integral_constant<std::size_t, Is>{},
std::integral_constant<std::size_t, IIs>{}), ...);
};
std::size_t j = 0, k = start;
if constexpr (range::count == 2 && range::pos_min == 0)
{
for (; j + 4 <= nodes_in_interval; j += 4, k += 4)
{
alignas(node_type) node_type seeds[4];
alignas(node_type) node_type left[4];
alignas(node_type) node_type right[4];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
DpfKey::exterior_prg::eval01_x4(seeds, left, right);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
node_type masks[2] = {left[t], right[t]};
apply_masks(k + t, nodes[j + t], masks);
}
}
}
else if constexpr (range::count == 1)
{
const auto pos = static_cast<psnip_uint32_t>(range::pos_min);
for (; j + 8 <= nodes_in_interval; j += 8, k += 8)
{
alignas(node_type) node_type seeds[8];
alignas(node_type) node_type masks[8];
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Warray-bounds")
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 8; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
HEDLEY_PRAGMA(GCC diagnostic pop)
DpfKey::exterior_prg::eval_x8(seeds, masks, pos);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 8; ++t)
{
apply_masks(k + t, nodes[j + t], &masks[t]);
}
}
for (; j + 4 <= nodes_in_interval; j += 4, k += 4)
{
alignas(node_type) node_type seeds[4];
alignas(node_type) node_type masks[4];
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
seeds[t] = utils::to_exterior_node<node_type>(
unset_lo_2bits(nodes[j + t]));
}
DpfKey::exterior_prg::eval_x4(seeds, masks, pos);
DPF_UNROLL_LOOP
for (std::size_t t = 0; t < 4; ++t)
{
apply_masks(k + t, nodes[j + t], &masks[t]);
}
}
}
DPF_UNROLL_LOOP
for (; j < nodes_in_interval; ++j, ++k)
{
const auto & node = nodes[j];
auto seed = utils::to_exterior_node<node_type>(unset_lo_2bits(node));
std::array<node_type, range::count> masks;
DpfKey::exterior_prg::eval(seed, masks.data(),
static_cast<psnip_uint32_t>(range::count),
static_cast<psnip_uint32_t>(range::pos_min));
apply_masks(k, node, masks.data());
}
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <std::size_t ...Is,
typename DpfKey,
typename OutputBuffers,
typename IntervalMemoizer,
typename IntegralT,
std::size_t ...IIs>
HEDLEY_ALWAYS_INLINE
void eval_interval_exterior_all(const DpfKey & dpf, IntegralT from_node,
IntegralT to_node, OutputBuffers && outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...> idxs, std::size_t start = 0)
{
// Fused exterior needs classic leaf packing (`concrete_outputs_tuple` +
// contiguous PRG lanes). Multi-level / cmp keys use the per-slot walk.
// Extractable keys stretch leaves with `extractable_leaf_prg` (leaf XOF),
// not `exterior_prg`. The fused path expands with AES and breaks the
// programmed packed leaf (cold opens still cancel; hot lanes do not).
if constexpr (!is_multilevel_key_v<DpfKey> && !DpfKey::is_extractable)
{
using node_type = typename DpfKey::exterior_node;
using outputs_tuple = typename DpfKey::concrete_outputs_tuple;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using range = leaf_prg_range<node_type, outputs_tuple, Is...>;
HEDLEY_PRAGMA(GCC diagnostic pop)
if constexpr (range::is_contiguous)
{
eval_interval_exterior_fused<Is...>(dpf, from_node, to_node, outbufs,
memoizer, idxs, start);
return;
}
}
(eval_interval_exterior<Is>(dpf, from_node, to_node,
utils::get<IIs>(outbufs), memoizer, start), ...);
}
template <std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer,
std::size_t ...IIs>
auto eval_interval_impl(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers && outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>, proof_token * pi = nullptr)
{
using dpf_type = DpfKey;
using integral_type = typename DpfKey::integral_type;
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
integral_type from_node = utils::get_from_node<dpf_type>(from),
to_node = utils::get_to_node<dpf_type>(to);
constexpr auto to_int = utils::to_integral_type<InputT>{};
const bool wraps = utils::interval_wraps(
static_cast<integral_type>(to_int(from)),
static_cast<integral_type>(to_int(to)),
utils::bitlength_of_v<InputT>);
auto segs = utils::split_leaf_nodes(from_node, to_node, dpf.depth, wraps);
auto idxs = std::index_sequence<IIs...>{};
std::size_t start = 0;
for (std::size_t s = 0; s < segs.n; ++s)
{
const auto & seg = segs.seg[s];
internal::eval_interval_interior(dpf, seg.from_node, seg.to_node, memoizer,
DpfKey::depth, pi);
eval_interval_exterior_all<Is...>(dpf, seg.from_node, seg.to_node, outbufs,
memoizer, idxs, start);
start += seg.count;
}
}
template <std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer,
std::size_t ...IIs>
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers && outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>, proof_token * pi = nullptr)
{
using dpf_type = DpfKey;
constexpr auto mod_pow_2 = utils::mod_pow_2<InputT>{};
constexpr auto to_integral_t = utils::to_integral_type<InputT>{};
constexpr auto bits = utils::bitlength_of_v<InputT>;
eval_interval_impl<Is...>(dpf, from, to, outbufs, memoizer,
std::make_index_sequence<sizeof...(Is)>(), pi);
// `to_integral_type` widens to at least `size_t`. Subtracting in that
// wider type loses wrap-around of a narrower input domain (e.g. int16
// intervals that increment across 0). Mask back to the domain width so
// `subinterval_iterable` length matches the inclusive [from, to] walk.
auto from_i = to_integral_t(from);
auto span = to_integral_t(to) - from_i;
if constexpr (bits < utils::bitlength_of_v<decltype(span)>)
{
span &= (decltype(span){1} << bits) - 1;
}
auto from_sz = static_cast<std::size_t>(from_i);
auto to_sz = from_sz + static_cast<std::size_t>(span);
return utils::make_tuple(subinterval_iterable(std::begin(utils::get<IIs>(outbufs)), utils::size(utils::get<IIs>(outbufs)), from_sz, to_sz, mod_pow_2(from, dpf_type::lg_outputs_per_leaf), dpf_type::outputs_per_leaf)...);
}
} // namespace internal
/// @name Closed-interval evaluation
/// @tparam I output index
/// @tparam Is the remaining output indices
/// @tparam DpfKey DPF key type
/// @tparam InputT input domain type
/// @param dpf the DPF key
/// @param from the inclusive start of the range
/// @param to the inclusive end of the range
/// @{
/// @brief Write outputs `I, Is...` for `[from, to]` into `outbufs`.
/// @tparam OutputBuffers tuple of output buffers
/// @tparam IntervalMemoizer interval memoizer type
/// @param dpf the DPF key
/// @param from the inclusive start of the range
/// @param to the inclusive end of the range
/// @param outbufs named buffer, or a tuple of buffers when several outputs
/// are selected. Must outlive the returned iterable.
/// @param memoizer workspace sized for at least this interval
/// @param pi proof token folded along the interval, or null
/// @return an iterable over the written outputs
/// \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 <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer = dpf::basic_interval_memoizer<DpfKey>,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs, IntervalMemoizer && memoizer, // NOLINT(runtime/references)
proof_token * pi = nullptr)
{
assert_not_wildcard_output<I, Is...>(dpf);
return internal::eval_interval<I, Is...>(dpf, dpf.offset_x(from), dpf.offset_x(to),
outbufs, memoizer, std::make_index_sequence<1+sizeof...(Is)>(), pi);
}
/// @brief Evaluate `[from, to]` and fold a once-per-BFS-node VDPF proof.
/// \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 <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer = dpf::basic_interval_memoizer<DpfKey>,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs, IntervalMemoizer && memoizer, prove_ref pr) // NOLINT(runtime/references)
{
static_assert(DpfKey::is_verifiable,
"eval_interval(..., prove(π)): key must carry dpf::verifiable");
detail::vdpf::init_proof(pr.token, dpf);
auto out = eval_interval<I, Is...>(dpf, from, to, outbufs,
std::forward<IntervalMemoizer>(memoizer), &pr.token);
detail::vdpf::fold_output_binding(pr.token, dpf);
return out;
}
/// @brief Evaluate `[from, to]` and fold each written output into a sketch.
/// \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 <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer = dpf::basic_interval_memoizer<DpfKey>,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs, IntervalMemoizer && memoizer, sketch_ref & sk) // NOLINT(runtime/references)
{
static_assert(DpfKey::is_extractable,
"eval_interval(..., sketch(σ)): key must carry dpf::extractable");
auto ret = eval_interval<I, Is...>(dpf, from, to, outbufs,
std::forward<IntervalMemoizer>(memoizer));
if constexpr (sizeof...(Is) == 0)
{
for (std::size_t k = 0; k < utils::size(outbufs); ++k)
sk.absorb(outbufs[k]);
}
return ret;
}
/// @brief Evaluate `[from, to]` into `outbufs`, allocating a basic interval memoizer.
/// @tparam OutputBuffers tuple of output buffers
/// @param dpf the DPF key
/// @param from the inclusive start of the range
/// @param to the inclusive end of the range
/// @param outbufs the named output buffers
/// @return an iterable over the written outputs
/// \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 <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<!std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<OutputBuffers>>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs) // NOLINT(runtime/references)
{
return eval_interval<I, Is...>(dpf, from, to, outbufs,
dpf::make_basic_interval_memoizer(dpf, from, to));
}
/// @brief Evaluate `[from, to]` into `outbufs` and fold a VDPF proof.
/// \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 <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<!std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<OutputBuffers>>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs, prove_ref pr) // NOLINT(runtime/references)
{
static_assert(DpfKey::is_verifiable,
"eval_interval(..., prove(π)): key must carry dpf::verifiable");
return eval_interval<I, Is...>(dpf, from, to, outbufs,
dpf::make_basic_interval_memoizer(dpf, from, to), pr);
}
/// @brief Evaluate `[from, to]` with a caller-supplied memoizer.
/// @tparam IntervalMemoizer interval memoizer type
/// @param dpf the DPF key
/// @param from the inclusive start of the range
/// @param to the inclusive end of the range
/// @param memoizer the memoizer built for this key
/// @return `std::pair` of a new buffer (or tuple of buffers) and an iterable
/// into that 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 <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<IntervalMemoizer>>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
IntervalMemoizer && memoizer)
{
auto outbufs = utils::make_tuple(
make_output_buffer_for_interval<I>(dpf, from, to),
make_output_buffer_for_interval<Is>(dpf, from, to)...);
// moving `outbufs` is allowed as the `outbufs` are `std::vectors`
// the underlying data remains on the heap
// and thus the data the iterable refers to is still valid
auto iterable = eval_interval<I, Is...>(dpf, from, to, outbufs, memoizer);
return std::make_pair(std::move(outbufs), std::move(iterable));
}
/// @brief Evaluate `[from, to]` with a memoizer and fold a VDPF proof.
/// \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 <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<IntervalMemoizer>>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to,
IntervalMemoizer && memoizer, prove_ref pr)
{
static_assert(DpfKey::is_verifiable,
"eval_interval(..., prove(π)): key must carry dpf::verifiable");
auto outbufs = utils::make_tuple(
make_output_buffer_for_interval<I>(dpf, from, to),
make_output_buffer_for_interval<Is>(dpf, from, to)...);
auto iterable = eval_interval<I, Is...>(dpf, from, to, outbufs, memoizer, pr);
return std::make_pair(std::move(outbufs), std::move(iterable));
}
/// @brief Evaluate `[from, to]`, allocating a basic interval memoizer and a buffer.
/// @return `std::pair` of a new buffer (or tuple of buffers) and an iterable
/// into that 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 <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
std::enable_if_t<looks_like_dpf_key_v<DpfKey> && !is_multilevel_key_v<DpfKey>, bool> = true>
HEDLEY_ALWAYS_INLINE
auto eval_interval(const DpfKey & dpf, InputT from, InputT to)
{
return eval_interval<I, Is...>(dpf, from, to,
dpf::make_basic_interval_memoizer(dpf, from, to));
}
/// @brief Fold every truncated-tree node of `[from, to]` into `pi`.
/// @details Once per BFS node — same absorption as `eval_interval(..., prove(π))`.
/// Caller must `init_proof` first, or use `prove_interval` below.
template <typename KeyT, typename InputT>
void prove_fold_interval(const KeyT & key, InputT from, InputT to,
proof_token & pi)
{
static_assert(KeyT::is_verifiable,
"prove_fold_interval: key must carry dpf::verifiable");
using dpf_type = KeyT;
using input_type = typename KeyT::input_type;
using integral_type = typename KeyT::integral_type;
auto from_x = key.offset_x(static_cast<input_type>(from));
auto to_x = key.offset_x(static_cast<input_type>(to));
utils::flip_msb_if_signed_integral(from_x);
utils::flip_msb_if_signed_integral(to_x);
integral_type from_node = utils::get_from_node<dpf_type>(from_x);
integral_type to_node = utils::get_to_node<dpf_type>(to_x);
constexpr auto to_int = utils::to_integral_type<input_type>{};
const bool wraps = utils::interval_wraps(
static_cast<integral_type>(to_int(from_x)),
static_cast<integral_type>(to_int(to_x)),
utils::bitlength_of_v<input_type>);
auto segs = utils::split_leaf_nodes(from_node, to_node, key.depth, wraps);
auto memo = make_basic_interval_memoizer(key, from, to);
for (std::size_t s = 0; s < segs.n; ++s)
{
const auto & seg = segs.seg[s];
internal::eval_interval_interior(key, seg.from_node, seg.to_node, memo,
KeyT::depth, &pi);
}
}
/// @brief Initialise `pr.token` and fold `[from, to]` once per BFS node.
/// @tparam KeyT verifiable key
/// @tparam InputT input domain type
/// @param key the party key
/// @param from inclusive start
/// @param to inclusive end
/// @param pr proof token replaced with the interval fold
template <typename KeyT, typename InputT>
void prove_interval(const KeyT & key, InputT from, InputT to, prove_ref pr)
{
detail::vdpf::init_proof(pr.token, key);
prove_fold_interval(key, from, to, pr.token);
detail::vdpf::fold_output_binding(pr.token, key);
}
/// @brief Initialise `pr.token` and fold the full domain once per BFS node.
/// @tparam KeyT verifiable key
/// @param key the party key
/// @param pr proof token replaced with the full-domain fold
template <typename KeyT>
void prove_full(const KeyT & key, prove_ref pr)
{
static_assert(KeyT::is_verifiable,
"prove_full: key must carry dpf::verifiable");
using input_type = typename KeyT::input_type;
prove_interval(key, std::numeric_limits<input_type>::min(),
std::numeric_limits<input_type>::max(), pr);
}
/// @}
namespace internal
{
template <std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer,
std::size_t ...IIs>
auto defer_eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs, IntervalMemoizer && memoizer,
std::index_sequence<IIs...>)
{
using dpf_type = DpfKey;
using input_type = typename dpf_type::input_type;
const auto min = std::numeric_limits<input_type>::min();
const auto max = std::numeric_limits<input_type>::max();
// Full-domain identity traversal (offset unknown). Same interior/exterior
// path as eager eval over `[min, max]` without folding `offset_x`.
eval_interval_impl<Is...>(dpf, min, max, outbufs, memoizer,
std::make_index_sequence<sizeof...(Is)>());
return utils::make_tuple(
deferred_rotated_subinterval(dpf,
std::begin(utils::get<IIs>(outbufs)),
std::end(utils::get<IIs>(outbufs)),
from, to,
dpf_type::outputs_per_leaf)...);
}
} // namespace internal
/// @name Deferred (pre-assign) evaluation
/// @{
/// @brief Full-domain eval while the input offset is still unset.
/// @details Requires a wildcard input that is not yet ready, and assigned
/// leaf outputs `I, Is...`. `outbufs` must be sized for the **full**
/// input domain (`make_output_buffer_for_full`). After
/// `assign_wildcard_input`, call `.get()` on each returned view.
/// @return one `deferred_rotated_subinterval` per selected output
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>, bool> = true>
auto defer_eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs, IntervalMemoizer && memoizer) // NOLINT(runtime/references)
{
static_assert(is_wildcard_v<typename DpfKey::raw_input_type>,
"defer_eval_interval: key input must be a wildcard_value");
assert_wildcard_input(dpf);
assert_not_wildcard_output<I, Is...>(dpf);
return internal::defer_eval_interval<I, Is...>(dpf, from, to, outbufs,
std::forward<IntervalMemoizer>(memoizer),
std::make_index_sequence<1 + sizeof...(Is)>());
}
/// @brief `defer_eval_interval` with a basic full-domain memoizer.
template <std::size_t I = 0,
std::size_t ...Is,
typename DpfKey,
typename InputT,
typename OutputBuffers,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>, bool> = true,
std::enable_if_t<!std::is_base_of_v<
dpf::interval_memoizer_base<unwrap_party_key_t<DpfKey>>,
std::decay_t<OutputBuffers>>, bool> = true>
auto defer_eval_interval(const DpfKey & dpf, InputT from, InputT to,
OutputBuffers & outbufs) // NOLINT(runtime/references)
{
return defer_eval_interval<I, Is...>(dpf, from, to, outbufs,
dpf::make_basic_full_memoizer(dpf));
}
/// @brief Interior-only traverse of `[offset_x(from), offset_x(to)]`.
/// @details Requires an assigned input. Skips exterior so the leaf may still
/// be a wildcard; finish with `eval_interval` / exterior once the
/// leaf is assigned (memoizer retains the interior).
template <typename DpfKey,
typename InputT,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>, bool> = true>
void defer_traverse_interval(const DpfKey & dpf, InputT from, InputT to,
IntervalMemoizer & memoizer) // NOLINT(runtime/references)
{
assert_not_wildcard_input(dpf);
using dpf_type = DpfKey;
using input_type = typename dpf_type::input_type;
using integral_type = typename dpf_type::integral_type;
auto tfrom = dpf.offset_x(from);
auto tto = dpf.offset_x(to);
utils::flip_msb_if_signed_integral(tfrom);
utils::flip_msb_if_signed_integral(tto);
integral_type from_node = utils::get_from_node<dpf_type>(tfrom);
integral_type to_node = utils::get_to_node<dpf_type>(tto);
constexpr auto to_int = utils::to_integral_type<input_type>{};
const bool wraps = utils::interval_wraps(
static_cast<integral_type>(to_int(tfrom)),
static_cast<integral_type>(to_int(tto)),
utils::bitlength_of_v<input_type>);
auto segs = utils::split_leaf_nodes(from_node, to_node, dpf.depth, wraps);
for (std::size_t s = 0; s < segs.n; ++s)
{
const auto & seg = segs.seg[s];
internal::eval_interval_interior(dpf, seg.from_node, seg.to_node,
memoizer);
}
}
/// @brief Interior-only full-domain traverse (input and/or leaf may be unset).
/// @details Fills the memoizer for every interior node. Complete exterior
/// (and any input rotation) after the missing wildcards are assigned.
template <typename DpfKey,
typename IntervalMemoizer,
std::enable_if_t<looks_like_dpf_key_v<DpfKey>
&& !is_multilevel_key_v<DpfKey>, bool> = true>
void defer_traverse_full(const DpfKey & dpf,
IntervalMemoizer & memoizer) // NOLINT(runtime/references)
{
using dpf_type = DpfKey;
using input_type = typename dpf_type::input_type;
using integral_type = typename dpf_type::integral_type;
auto from = std::numeric_limits<input_type>::min();
auto to = std::numeric_limits<input_type>::max();
utils::flip_msb_if_signed_integral(from);
utils::flip_msb_if_signed_integral(to);
integral_type from_node = utils::get_from_node<dpf_type>(from);
integral_type to_node = utils::get_to_node<dpf_type>(to);
internal::eval_interval_interior(dpf, from_node, to_node, memoizer);
}
/// @}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__