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

1795 lines
70 KiB
C++
Raw Permalink 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/dpf_key.hpp
/// @brief The DPF key, its correction words, and interior traversal.
/// @author Ryan Henry <ryan.henry@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_DPF_KEY_HPP__
#define LIBDPF_INCLUDE_DPF_DPF_KEY_HPP__
#include "hedley/hedley.h"
#include <cstddef>
#include <utility>
#include <tuple>
#include <array>
#include <bitset>
#include <atomic>
#include "dpf/experiment_note.hpp"
#include "dpf/prg_aes.hpp"
#include "dpf/tree_traits.hpp"
#include "dpf/wildcard.hpp"
#include "dpf/twiddle.hpp"
#include "dpf/leaf_node.hpp"
#include "dpf/offset_wrapper.hpp"
#include "dpf/leaf_wrapper.hpp"
#include "dpf/emplace.hpp"
#include "dpf/placement.hpp"
#include "dpf/verifiable.hpp"
#include "dpf/dcf.hpp"
namespace dpf
{
#ifdef LIBDPF_HAS_ASIO
namespace asio
{
/// @brief Exchange a wildcard output share and install the opened leaf.
/// @see dpf::leaf_wrapper
/// \complexity O(leaf bytes) for the Beaver leaf arithmetic, plus the transfers.
/// \rounds 2. Write/read the blinded output share, then write/read the leaf share (`async_assign_wildcard_output`).
/// \communication `sizeof(output_type)` plus `sizeof(leaf_type)` each way.
/// \preprocessing The leaf Beaver triple (`vector_blind`, `output_blind`, `blinded_vector`) was stored at keygen.
template <std::size_t I,
typename PeerT,
typename DpfKey,
typename OutputType,
typename CompletionToken>
auto async_assign_wildcard_output(PeerT & peer, DpfKey & dpf,
OutputType && output_share, CompletionToken && token);
}
#endif
template <typename InputT,
typename OutputT = dpf::bit,
typename ...OutputTs>
HEDLEY_WARN_UNUSED_RESULT
auto make_dpfargs(InputT && x, OutputT && y = dpf::bit::one, OutputTs && ...ys);
template <typename InputT,
typename OutputT,
typename ...OutputTs>
struct dpfargs final
{
using input_type = InputT;
using output_type = std::tuple<OutputT, OutputTs...>;
dpfargs() = delete;
dpfargs(dpfargs &&) = default;
dpfargs(const dpfargs &) = default;
input_type x;
output_type y;
private:
dpfargs(input_type x_, output_type y_) { x = x_; y = y_; }
template <typename I, typename O, typename ...Os>
friend auto make_dpfargs(I &&, O &&, Os && ...);
// friend auto make_dpfargs(InputT && x, OutputT && y, OutputTs && ...ys)
};
template <typename InputT,
typename OutputT,
typename ...OutputTs>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_WARN_UNUSED_RESULT
auto make_dpfargs(InputT && x, OutputT && y, OutputTs && ...ys)
{
return dpfargs<std::decay_t<InputT>,
std::decay_t<OutputT>,
std::decay_t<OutputTs>...>
{ std::forward<InputT>(x),
std::make_tuple(std::forward<OutputT>(y),
std::forward<OutputTs>(ys)...) };
}
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
template <typename InteriorPRG>
using root_sampler_t = std::add_pointer_t<typename InteriorPRG::block_type()>;
HEDLEY_PRAGMA(GCC diagnostic pop)
namespace detail
{
/// @brief Classic single-level DPF key body (all outputs bare, at full input width,
/// equal widths, no comparison channel). `Derived` is the public `dpf_key`
/// specialization that inherits this body — threaded through only so that
/// `emplace`/`emplace_back` construct the public key type.
/// @tparam Derived derived
/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128`
/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG`
/// @tparam InputT input domain type
/// @tparam OutputT output type
/// @tparam OutputTs output ts
template <typename Derived,
typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs>
struct classic_dpf_key_impl
{
public:
using interior_prg = InteriorPRG;
using tree = dpf::tree_traits<InteriorPRG>;
using interior_node = typename InteriorPRG::block_type;
using exterior_prg = ExteriorPRG;
using exterior_node = typename ExteriorPRG::block_type;
using input_type = dpf::concrete_type_t<InputT>;
using raw_input_type = InputT;
using integral_type = utils::integral_type_from_bitlength_t<utils::bitlength_of_v<input_type>, utils::bitlength_of_v<std::size_t>>;
using outputs_tuple = std::tuple<OutputT, OutputTs...>;
template <std::size_t I>
using output_type_t = std::tuple_element_t<I, outputs_tuple>;
using concrete_outputs_tuple
= std::tuple<concrete_type_t<OutputT>, concrete_type_t<OutputTs>...>;
template <std::size_t I>
using concrete_output_type = std::tuple_element_t<I, concrete_outputs_tuple>;
using offset_type = offset_wrapper<InputT>; // N.B.: `InputT`, not `input_type`
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using leaf_tuple = dpf::leaf_tuple_t<exterior_node, OutputT, OutputTs...>;
using beaver_tuple = dpf::beaver_tuple_t<exterior_node, OutputT, OutputTs...>;
using leaf_wrapper_tuple = std::tuple<dpf::leaf_wrapper<OutputT, exterior_node>, dpf::leaf_wrapper<OutputTs, exterior_node>...>;
HEDLEY_PRAGMA(GCC diagnostic pop)
static constexpr std::size_t outputs_per_leaf = dpf::outputs_per_leaf_v<OutputT, exterior_node>;
static constexpr std::size_t lg_outputs_per_leaf = dpf::lg_outputs_per_leaf_v<OutputT, exterior_node>;
static constexpr std::size_t depth
= utils::bitlength_of_v<input_type> - lg_outputs_per_leaf;
static constexpr auto msb_mask = utils::msb_of_v<input_type>;
// -----------------------------------------------------------------------
// Slot-meta foundation (unified with the multi-level `incr_key_base`).
// Every key carries a `slot_meta` table; for a classic (single-level,
// equal-width, no-cmp) pack all slots sit at `prefix == bitlen` and the
// packing positions match the classic `leaf_prg` layout. These are all
// `static constexpr`, so the object layout is unchanged.
// -----------------------------------------------------------------------
static constexpr std::size_t num_outputs = 1 + sizeof...(OutputTs);
static constexpr std::size_t cmp_depth = 0;
static constexpr std::size_t cmp_out_bits = 0;
static constexpr std::size_t cmp_block = 0;
static constexpr bool cmp_idcf = false;
static constexpr std::size_t cmp_q = 0;
static constexpr std::size_t cmp_h = 0;
static constexpr std::size_t cmp_checkpoints = 0;
static constexpr std::size_t cmp_tail = 0;
/// @brief Classic keys are single-level; the unified eval surface keeps routing
/// them through the classic `eval_*` fast paths (see `is_multilevel_key`).
/// @see `is_multilevel_key`
static constexpr bool is_multilevel = false;
static constexpr bool is_verifiable = false;
static constexpr bool is_extractable = false;
using correction_seeds_array = std::array<cs_block, 0>;
const correction_seeds_array & correction_seeds() const
{
static const correction_seeds_array empty{};
return empty;
}
private:
using meta_placed_tuple = std::tuple<
dpf::detail::incr::placed<utils::bitlength_of_v<input_type>, OutputT>,
dpf::detail::incr::placed<utils::bitlength_of_v<input_type>,
OutputTs>...>;
public:
using meta_array = std::array<dpf::detail::incr::slot_meta, num_outputs>;
static constexpr meta_array meta =
dpf::detail::incr::build_meta<exterior_node, meta_placed_tuple>();
static constexpr std::size_t deepest_output = 0;
template <std::size_t I>
static constexpr std::size_t lg_outputs_per_leaf_of = meta[I].lg_opl;
template <std::size_t I>
static constexpr std::size_t outputs_per_leaf_of =
std::size_t{1} << lg_outputs_per_leaf_of<I>;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using correction_words_array = std::array<interior_node, depth>;
HEDLEY_PRAGMA(GCC diagnostic pop)
using correction_advice_array = std::array<psnip_uint8_t, depth>;
template <typename Emplaceable>
HEDLEY_ALWAYS_INLINE
static void emplace(Emplaceable & output,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
utils::dpf_emplacer<Derived, Emplaceable>::emplace(output, root, correction_words, correction_advice, leaves, beavers, offset_share);
}
template <typename EmplaceableContainer>
HEDLEY_ALWAYS_INLINE
static void emplace_back(EmplaceableContainer & output,
const interior_node & root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
const input_type & offset_share)
{
utils::dpf_back_emplacer<Derived, EmplaceableContainer>::emplace_back(output, root, correction_words, correction_advice, leaves, beavers, offset_share);
}
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
static_assert(((dpf::utils::bitlength_of_output_v<OutputT, exterior_node>
== dpf::utils::bitlength_of_output_v<OutputTs, exterior_node>) && ...),
"all output types must be the same length");
HEDLEY_PRAGMA(GCC diagnostic pop)
static_assert(std::conjunction_v<std::is_trivially_copyable<OutputT>,
std::is_trivially_copyable<OutputTs>...>,
"all output types must be trivially copyable");
static_assert(std::conjunction_v<std::is_standard_layout<OutputT>,
std::is_standard_layout<OutputTs>...>,
"all output types must be standard layout");
// static_assert(std::has_unique_object_representations_v<input_type>);
HEDLEY_ALWAYS_INLINE
constexpr classic_dpf_key_impl(interior_node root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
const leaf_tuple & leaves,
const beaver_tuple & beavers,
input_type offset_share)
: leaf_nodes(get_wrappers(leaves, beavers)),
offset_x{offset_share},
root_{root},
correction_words_{correction_words},
correction_advice_{correction_advice},
mutable_wildcard_mask_{dpf::utils::make_bitset(dpf::is_wildcard_v<OutputT>,
dpf::is_wildcard_v<OutputTs>...)},
common_part_hash_{utils::get_common_part_hash(correction_words_, correction_advice_, leaf_nodes, wildcard_mask)}
{ }
classic_dpf_key_impl(const classic_dpf_key_impl &) = default;
classic_dpf_key_impl(classic_dpf_key_impl &&) = default;
classic_dpf_key_impl & operator=(const classic_dpf_key_impl &) = default;
classic_dpf_key_impl & operator=(classic_dpf_key_impl &&) = default;
const interior_node & root() const { return root_; }
const correction_words_array & correction_words() const { return correction_words_; }
const correction_advice_array & correction_advice() const { return correction_advice_; }
const digest_type & common_part_hash() const { return common_part_hash_; }
std::string wildcard_bitmask() const
{
return mutable_wildcard_mask_.to_string();
}
HEDLEY_ALWAYS_INLINE
const interior_node & correction_word(std::size_t level) const
{
return correction_words_[level];
}
HEDLEY_ALWAYS_INLINE
psnip_uint8_t correction_advice(std::size_t level) const
{
return correction_advice_[level];
}
HEDLEY_ALWAYS_INLINE
auto correction_word(std::size_t level, bool direction) const
{
return tree::pack_cw(correction_word(level),
correction_advice_[level], direction,
tree::is_last_level(level, depth));
}
template <std::size_t I = 0>
HEDLEY_ALWAYS_INLINE
const auto & leaf() const
{
if constexpr (dpf::is_wildcard_v<output_type_t<I>>)
{
return std::get<I>(leaf_nodes).raw_leaf();
}
else
{
return std::get<I>(leaf_nodes).get();
}
}
template <std::size_t I = 0>
HEDLEY_ALWAYS_INLINE
const auto & beaver() const
{
return std::get<I>(leaf_nodes).beaver();
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
constexpr bool is_wildcard(std::size_t i) const noexcept
{
return wildcard_mask[i];
}
#ifdef LIBDPF_HAS_ASIO
template <std::size_t I = 0,
typename PeerT,
typename OutputType,
typename CompletionToken>
auto async_assign_leaf(PeerT & peer, OutputType && output_share,
CompletionToken && token)
{
return dpf::asio::async_assign_wildcard_output<I>(
peer, *this, std::forward<OutputType>(output_share),
std::forward<CompletionToken>(token));
}
#endif
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static auto traverse_interior(const interior_node & node,
const interior_node & cw, bool dir, bool is_last = false) noexcept
{
return tree::traverse(node, cw, dir, is_last);
}
/// @brief Expand both children of `node` with one pipelined expand.
/// @details Equivalent to `traverse_interior(node, cw0, 0)` and
/// `traverse_interior(node, cw1, 1)`. Full-domain interval eval uses this
/// at almost every interior parent.
/// @param node the GGM node
/// @param cw0 correction word for the left child
/// @param cw1 correction word for the right child
/// @param is_last whether this is the last interior level
/// @return both children of `node`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static auto traverse_interior01(const interior_node & node,
const interior_node & cw0, const interior_node & cw1,
bool is_last = false) noexcept
{
return tree::traverse01(node, cw0, cw1, is_last);
}
/// @brief Four independent `traverse_interior01` via traits batched expand.
/// @details `left[i]` / `right[i]` are the children of `parents[i]`.
/// @param parents the `parents`
/// @param cw0 the `cw0`
/// @param cw1 the `cw1`
/// @param left the `left`
/// @param right the `right`
/// @param is_last the `is_last`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
static void traverse_interior01_x4(const interior_node * HEDLEY_RESTRICT parents,
const interior_node & cw0, const interior_node & cw1,
interior_node * HEDLEY_RESTRICT left,
interior_node * HEDLEY_RESTRICT right, bool is_last = false) noexcept
{
tree::traverse01_x4(parents, cw0, cw1, left, right, is_last);
}
template <std::size_t I = 0,
typename LeafT>
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static auto traverse_exterior(const interior_node & node,
const LeafT & correction_word) noexcept
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using output_type = std::tuple_element_t<I, concrete_outputs_tuple>;
HEDLEY_PRAGMA(GCC diagnostic pop)
// Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β.
return dpf::subtract_leaf<output_type>(
dpf::get_if_lo_bit(correction_word, node),
make_leaf_mask_inner<exterior_prg, I, concrete_outputs_tuple>(unset_lo_2bits(node)));
}
template <std::size_t I = 0>
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
auto traverse_exterior(const interior_node & node) const noexcept
{
return traverse_exterior<I>(node, std::get<I>(leaf_nodes).get());
}
/// @brief Eight one-block leaves. One `eval_x8` instead of eight `eval` calls.
template <std::size_t I = 0, typename Out>
void traverse_exterior_x8(const interior_node * HEDLEY_RESTRICT nodes,
Out * HEDLEY_RESTRICT out) const noexcept
{
using output_type = std::tuple_element_t<I, concrete_outputs_tuple>;
using block = typename exterior_prg::block_type;
alignas(64) block seeds[8];
alignas(64) block masks[8];
for (std::size_t t = 0; t < 8; ++t)
seeds[t] = utils::to_exterior_node<block>(unset_lo_2bits(nodes[t]));
constexpr auto pos = dpf::block_offset_of_leaf_v<I, block,
concrete_outputs_tuple>;
exterior_prg::eval_x8(seeds, masks, static_cast<psnip_uint32_t>(pos));
const auto & cw = std::get<I>(leaf_nodes).get();
for (std::size_t t = 0; t < 8; ++t)
{
encode_curve_leaf_mask<concrete_type_t<output_type>>(masks[t]);
out[t] = dpf::subtract_leaf<output_type>(
dpf::get_if_lo_bit(cw, nodes[t]), masks[t]);
}
}
leaf_wrapper_tuple leaf_nodes;
offset_type offset_x;
static constexpr std::array<bool, sizeof...(OutputTs)+1> wildcard_mask{dpf::is_wildcard_v<OutputT>,
dpf::is_wildcard_v<OutputTs>...};
private:
static auto get_wrappers(const leaf_tuple & leaves,
const beaver_tuple & beavers)
{
outputs_tuple tmp{};
return std::apply([&beavers, &tmp](auto & ...leaf)
{
return std::apply([&leaf..., &tmp](auto & ...beaver)
{
return std::apply([&leaf..., &beaver...](auto & ...foo)
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
return std::make_tuple(
dpf::leaf_wrapper<std::decay_t<decltype(foo)>, exterior_node>(leaf, beaver)...
);
HEDLEY_PRAGMA(GCC diagnostic pop)
}, tmp);
}, beavers);
}, leaves);
}
interior_node root_;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
correction_words_array correction_words_;
HEDLEY_PRAGMA(GCC diagnostic pop)
correction_advice_array correction_advice_;
std::bitset<sizeof...(OutputTs)+1> mutable_wildcard_mask_;
digest_type common_part_hash_;
}; // struct classic_dpf_key_impl
} // namespace detail
namespace detail
{
namespace incr
{
/// @brief Comparison-channel storage. Value CWs, `cw_last`, and the `cmp_addend`
/// share are held at the comparison group width (`ValueCwWord`), not a full
/// padded `uint64_t` per level: a bit comparison carries 1 byte/level, a
/// `uint16_t` payload 2 bytes/level, etc. Arithmetic still runs in `uint64_t`
/// (masked); the narrow word is only the on-key / on-wire representation.
/// @details Extra per-level δ-coefficients kept only for wildcard comparison payloads
/// (empty for concrete cmp keys, so their layout is unchanged). The value CWs
/// / `cw_last` are affine in the payload δ, so after keygen with δ = 0 the
/// concrete values are `base[i] + coeff[i]·δ`; `assign_cmp` patches them in
/// place with no tree re-walk / re-PRG.
/// @tparam Depth depth
/// @tparam ValueCwWord value cw word
/// @tparam Wild whether the payload is a wildcard
/// @tparam TailLen tail len
/// @tparam Idcf idcf
template <std::size_t Depth, typename ValueCwWord, bool Wild,
std::size_t TailLen = 0, bool Idcf = false>
struct cmp_wild_state { };
template <std::size_t Depth, typename ValueCwWord, std::size_t TailLen, bool Idcf>
struct cmp_wild_state<Depth, ValueCwWord, true, TailLen, Idcf>
{
std::array<ValueCwWord, Depth> value_cw_coeff{};
ValueCwWord cw_last_coeff{0};
std::array<ValueCwWord, TailLen> tail_coeff{};
std::array<ValueCwWord, Idcf ? Depth + 1 : 0> prefix_cw_coeff{};
bool assigned{false};
};
template <std::size_t Depth, typename ValueCwWord, bool Wild = false,
std::size_t TailLen = 0, bool Blocked = false, bool Idcf = false>
struct cmp_storage
{
using value_cw_word = ValueCwWord;
using value_cw_array = std::array<value_cw_word, Depth>;
using tail_array = std::array<value_cw_word, TailLen>;
static constexpr std::size_t prefix_cw_len = Idcf ? Depth + 1 : 0;
using prefix_cw_array = std::array<value_cw_word, prefix_cw_len>;
cmp_storage() = default;
cmp_storage(detail::cmp_meta cmp, value_cw_array value_cws,
value_cw_word cw_last_in, value_cw_word cmp_addend_in,
tail_array tail = {}, tail_array tail_coeff = {},
prefix_cw_array prefix = {}, prefix_cw_array prefix_coeff = {})
: cmp_{cmp}, value_cw_{value_cws}, cw_last_{cw_last_in},
cmp_addend_{cmp_addend_in}, tail_{tail}, prefix_cw_{prefix}
{
if constexpr (Wild && Blocked)
wild_.tail_coeff = tail_coeff;
else
(void)tail_coeff;
if constexpr (Wild && Idcf)
wild_.prefix_cw_coeff = prefix_coeff;
else
(void)prefix_coeff;
}
cmp_storage(detail::cmp_meta cmp, value_cw_array value_cws,
value_cw_word cw_last_in, value_cw_word cmp_addend_in,
value_cw_array coeff, value_cw_word cw_last_coeff,
tail_array tail = {}, tail_array tail_coeff = {},
prefix_cw_array prefix = {}, prefix_cw_array prefix_coeff = {})
: cmp_{cmp}, value_cw_{value_cws}, cw_last_{cw_last_in},
cmp_addend_{cmp_addend_in}, tail_{tail}, prefix_cw_{prefix}
{
if constexpr (Wild)
{
wild_.value_cw_coeff = coeff;
wild_.cw_last_coeff = cw_last_coeff;
if constexpr (Blocked)
wild_.tail_coeff = tail_coeff;
if constexpr (Idcf)
wild_.prefix_cw_coeff = prefix_coeff;
}
else
{
(void)coeff;
(void)cw_last_coeff;
(void)tail_coeff;
(void)prefix_coeff;
}
}
const value_cw_array & value_cw() const { return value_cw_; }
uint64_t value_cw(std::size_t level) const
{
return static_cast<uint64_t>(value_cw_[level]);
}
HEDLEY_NO_THROW
value_cw_word cw_last_word() const noexcept { return cw_last_; }
HEDLEY_NO_THROW
value_cw_word cmp_addend_word() const noexcept { return cmp_addend_; }
HEDLEY_NO_THROW
uint64_t cw_last() const noexcept { return static_cast<uint64_t>(cw_last_); }
HEDLEY_NO_THROW
const tail_array & tail_cw() const noexcept { return tail_; }
uint64_t tail_cw(std::size_t i) const
{
return static_cast<uint64_t>(tail_[i]);
}
HEDLEY_NO_THROW
uint64_t cmp_addend() const noexcept
{
return static_cast<uint64_t>(cmp_addend_);
}
HEDLEY_NO_THROW
const detail::cmp_meta & cmp() const noexcept { return cmp_; }
HEDLEY_NO_THROW
bool has_cmp() const noexcept { return cmp_.active; }
HEDLEY_NO_THROW
const prefix_cw_array & prefix_cws() const noexcept { return prefix_cw_; }
uint64_t prefix_cw(std::size_t i) const
{
return static_cast<uint64_t>(prefix_cw_[i]);
}
static constexpr bool is_wildcard = Wild;
HEDLEY_NO_THROW
bool cmp_assigned() const noexcept
{
if constexpr (Wild)
return wild_.assigned;
else
return true;
}
/// @brief Patch the (public) value CWs / `cw_last` in place for a resolved δ and
/// install this party's `cmp_addend` share. No-op on the CWs when there is
/// no wildcard coefficient table (trivial domain-edge cmp).
/// @param delta the payload difference `if_true - if_false`
/// @param addend_share the `addend_share`
void assign_cmp_delta(uint64_t delta, uint64_t addend_share)
{
static_assert(Wild,
"assign_cmp on a key whose comparison payload is not a wildcard");
if constexpr (Wild)
{
const uint64_t mask = cmp_.mask;
for (std::size_t i = 0; i < Depth; ++i)
{
const uint64_t base = static_cast<uint64_t>(value_cw_[i]);
const uint64_t c = static_cast<uint64_t>(wild_.value_cw_coeff[i]);
value_cw_[i] = static_cast<value_cw_word>((base + c * delta) & mask);
}
if constexpr (Blocked)
{
for (std::size_t i = 0; i < TailLen; ++i)
{
const uint64_t base = static_cast<uint64_t>(tail_[i]);
const uint64_t c = static_cast<uint64_t>(wild_.tail_coeff[i]);
tail_[i] = static_cast<value_cw_word>((base + c * delta) & mask);
}
}
const uint64_t lbase = static_cast<uint64_t>(cw_last_);
const uint64_t lc = static_cast<uint64_t>(wild_.cw_last_coeff);
cw_last_ = static_cast<value_cw_word>((lbase + lc * delta) & mask);
if constexpr (Idcf)
{
for (std::size_t i = 0; i < prefix_cw_len; ++i)
{
const uint64_t base = static_cast<uint64_t>(prefix_cw_[i]);
const uint64_t c = static_cast<uint64_t>(wild_.prefix_cw_coeff[i]);
prefix_cw_[i] = static_cast<value_cw_word>((base + c * delta) & mask);
}
}
cmp_addend_ = static_cast<value_cw_word>(addend_share & mask);
wild_.assigned = true;
}
}
/// @brief Overwrite the public final correction and this party's addend. Used when
/// the group element does not fit in the `uint64_t` the constructor takes.
/// @param last the past-the-end element of the range
/// @param addend the additive share of the off-point payload
/// @param last_coeff the `last_coeff`
void set_scalars(value_cw_word last, value_cw_word addend,
value_cw_word last_coeff)
{
cw_last_ = last;
cmp_addend_ = addend;
if constexpr (Wild)
wild_.cw_last_coeff = last_coeff;
else
(void)last_coeff;
}
/// @brief `base + coeff · δ` in `delta`'s group, then install `addend`.
/// @param delta the payload difference `if_true - if_false`
/// @param addend the additive share of the off-point payload
void assign_group(const detail::group_elem & delta, value_cw_word addend)
{
static_assert(Wild,
"assign_cmp on a key whose comparison payload is not a wildcard");
if constexpr (Wild)
{
auto mix = [&](value_cw_word base_w, value_cw_word coeff_w) {
const auto base = detail::group_from_word(base_w, delta);
const auto coeff = detail::group_from_word(coeff_w, delta);
return detail::group_to_word<value_cw_word>(
detail::group_add(base, detail::group_mul(coeff, delta)));
};
for (std::size_t i = 0; i < Depth; ++i)
value_cw_[i] = mix(value_cw_[i], wild_.value_cw_coeff[i]);
if constexpr (Blocked)
{
for (std::size_t i = 0; i < TailLen; ++i)
tail_[i] = mix(tail_[i], wild_.tail_coeff[i]);
}
cw_last_ = mix(cw_last_, wild_.cw_last_coeff);
if constexpr (Idcf)
{
for (std::size_t i = 0; i < prefix_cw_len; ++i)
prefix_cw_[i] = mix(prefix_cw_[i], wild_.prefix_cw_coeff[i]);
}
cmp_addend_ = addend;
wild_.assigned = true;
}
}
/// @brief `mix(base, coeff)` at every value CW, then install `addend`.
/// @tparam Mix word rewriter
/// @param mix combines one stored base word with its integer coefficient word
/// @param addend this party's share of the constant payload
template <typename Mix>
void assign_payload(Mix mix, value_cw_word addend)
{
static_assert(Wild,
"assign_cmp on a key whose comparison payload is not a wildcard");
if constexpr (Wild)
{
for (std::size_t i = 0; i < Depth; ++i)
value_cw_[i] = mix(value_cw_[i], wild_.value_cw_coeff[i]);
if constexpr (Blocked)
{
for (std::size_t i = 0; i < TailLen; ++i)
tail_[i] = mix(tail_[i], wild_.tail_coeff[i]);
}
cw_last_ = mix(cw_last_, wild_.cw_last_coeff);
if constexpr (Idcf)
{
for (std::size_t i = 0; i < prefix_cw_len; ++i)
prefix_cw_[i] = mix(prefix_cw_[i], wild_.prefix_cw_coeff[i]);
}
cmp_addend_ = addend;
wild_.assigned = true;
}
}
/// @brief Per-level δ coefficients for a wildcard comparison. Empty when the
/// payload is concrete.
/// @return the coefficient table
const value_cw_array & value_cw_coeff() const noexcept
{
if constexpr (Wild)
return wild_.value_cw_coeff;
else
{
static const value_cw_array empty{};
return empty;
}
}
/// @brief Coefficient of δ in `cw_last`. Zero when the payload is concrete.
/// @return the final coefficient word
HEDLEY_NO_THROW
value_cw_word cw_last_coeff_word() const noexcept
{
if constexpr (Wild)
return wild_.cw_last_coeff;
else
return value_cw_word{};
}
/// @brief Tail δ coefficients for a blocked wildcard comparison.
/// @return the tail coefficient table
const tail_array & tail_coeff() const noexcept
{
if constexpr (Wild)
return wild_.tail_coeff;
else
{
static const tail_array empty{};
return empty;
}
}
/// @brief Prefix δ coefficients for an iDCF wildcard comparison.
/// @return the prefix coefficient table
const prefix_cw_array & prefix_cw_coeff() const noexcept
{
if constexpr (Wild)
return wild_.prefix_cw_coeff;
else
{
static const prefix_cw_array empty{};
return empty;
}
}
/// @brief Mark whether a wildcard comparison payload has been assigned.
/// @param assigned true once `assign_cmp` has run
HEDLEY_NO_THROW
void set_assigned(bool assigned) noexcept
{
if constexpr (Wild)
wild_.assigned = assigned;
else
(void)assigned;
}
private:
detail::cmp_meta cmp_{};
value_cw_array value_cw_{};
value_cw_word cw_last_{0};
value_cw_word cmp_addend_{0};
tail_array tail_{};
prefix_cw_array prefix_cw_{};
cmp_wild_state<Depth, value_cw_word, Wild, TailLen, Idcf> wild_{};
};
/// @brief Multi-level / comparison DPF key body. `PlacedTuple` is a tuple of
/// `placed<N, T>` slots; `CmpDepth > 0` activates the comparison channel.
/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128`
/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG`
/// @tparam InputT input domain type
/// @tparam PlacedTuple placed tuple
/// @tparam CmpDepth cmp depth
/// @tparam CmpOutBits cmp out bits
/// @tparam CmpWild cmp wild
/// @tparam CmpBlock cmp block
/// @tparam CmpIdcf cmp idcf
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename PlacedTuple, std::size_t CmpDepth = 0,
std::size_t CmpOutBits = 0, bool CmpWild = false,
std::size_t CmpBlock = 0, bool CmpIdcf = false,
bool IsVerifiable = false, bool IsExtractable = false>
struct incr_key_base
{
public:
using interior_prg = InteriorPRG;
using exterior_prg = ExteriorPRG;
using tree = dpf::tree_traits<InteriorPRG>;
using interior_node = typename InteriorPRG::block_type;
using exterior_node = typename ExteriorPRG::block_type;
using input_type = dpf::concrete_type_t<InputT>;
using raw_input_type = InputT;
using placed_tuple = PlacedTuple;
using node_type = exterior_node;
static constexpr std::size_t cmp_depth = CmpDepth;
/// @brief Comparison output group width in bits (0 when there is no cmp channel).
static constexpr std::size_t cmp_out_bits = CmpOutBits;
/// @brief True when the comparison payload is an unassigned wildcard.
static constexpr bool cmp_is_wildcard = CmpWild;
/// @brief 0 = per-level path-sum. `B >= 1` = blocked checkpoints of width `B`.
static constexpr std::size_t cmp_block = CmpBlock;
static constexpr bool cmp_idcf = CmpIdcf;
static constexpr bool is_verifiable = IsVerifiable;
static constexpr bool is_extractable = IsExtractable;
static constexpr std::size_t max_output_level =
detail::incr::max_tree_level_v<node_type, PlacedTuple>;
/// @brief Residual tail width. 2 only when dropping those levels does not cut an
/// output and the comparison itself is what sets the tree height.
static constexpr std::size_t cmp_q = [] {
if (CmpBlock == 0 || CmpDepth <= 2)
return std::size_t{0};
if (max_output_level > CmpDepth - 2)
return std::size_t{0};
return std::size_t{2};
}();
static constexpr std::size_t cmp_h =
(CmpBlock == 0) ? CmpDepth : (CmpDepth - cmp_q);
static constexpr std::size_t cmp_checkpoints =
(CmpBlock == 0 || cmp_h == 0) ? 0 : (cmp_h + CmpBlock - 1) / CmpBlock;
static constexpr std::size_t cmp_tail =
(CmpBlock == 0 || cmp_q == 0) ? 0 : (std::size_t{1} << cmp_q);
/// @brief Narrowest unsigned word that holds `cmp_out_bits` bits (1 byte for a
/// bit / ≤8-bit payload, 2 for ≤16, 4 for ≤32, 8 for ≤64). Payloads wider
/// than 256 bits are stored as their raw bytes. Value CWs and the addend
/// share use this word.
static constexpr std::size_t cmp_word_bits =
CmpOutBits == 0 ? std::size_t{1} : CmpOutBits;
using value_cw_word = std::conditional_t<
(cmp_word_bits <= 256),
utils::integral_type_from_bitlength_t<cmp_word_bits>,
std::array<std::uint8_t, (cmp_word_bits + 7) / 8>>;
static constexpr std::size_t num_outputs = std::tuple_size_v<PlacedTuple>;
static constexpr std::size_t input_bits = utils::bitlength_of_v<input_type>;
static constexpr std::size_t depth = std::max(max_output_level,
(CmpBlock == 0) ? CmpDepth : cmp_h);
static constexpr std::size_t value_cw_len =
(CmpBlock == 0) ? depth
: (cmp_checkpoints == 0 ? std::size_t{1} : cmp_checkpoints);
static constexpr auto msb_mask = utils::msb_of_v<input_type>;
using integral_type = utils::integral_type_from_bitlength_t<
input_bits, utils::bitlength_of_v<std::size_t>>;
static_assert(num_outputs > 0 || CmpDepth > 0,
"incremental DPF needs at least one output or a comparison channel");
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
static_assert(detail::incr::all_prefixes_ok_v<node_type, PlacedTuple>,
"at<N> is shorter than the packing lanes required by an output");
using correction_words_array = std::array<interior_node, depth>;
HEDLEY_PRAGMA(GCC diagnostic pop)
using correction_advice_array = std::array<psnip_uint8_t, depth>;
using correction_seeds_array = std::array<cs_block, IsVerifiable ? depth : 0>;
using value_cw_array = std::array<value_cw_word, value_cw_len>;
using tail_array = std::array<value_cw_word, cmp_tail>;
static constexpr std::size_t prefix_cw_len = CmpIdcf ? depth + 1 : 0;
using prefix_cw_array = std::array<value_cw_word, prefix_cw_len>;
using meta_array = std::array<detail::incr::slot_meta, num_outputs>;
static constexpr meta_array meta =
detail::incr::build_meta<node_type, PlacedTuple>();
/// @brief Multi-level / comparison keys route through the slot-aware eval path.
/// Classic-shaped packs (every slot at full input width, no cmp) keep the
/// classic `eval_*` fast path even when verifiable/extractable phantoms are
/// present. `eq` / `eq_at` with a public `if_false` addend also take the
/// slot-aware path so party 0 can absorb that addend.
static constexpr bool is_multilevel = [] {
if constexpr (CmpDepth > 0)
return true;
if constexpr (num_outputs == 0)
return true;
else if constexpr (detail::incr::any_public_addend_v<PlacedTuple>)
return true;
else
{
for (std::size_t i = 0; i < num_outputs; ++i)
{
if (meta[i].prefix != input_bits)
return true;
}
return false;
}
}();
template <std::size_t I, typename = void>
struct output_type_at
{
using type = void;
};
template <std::size_t I>
struct output_type_at<I, std::enable_if_t<(I < num_outputs)>>
{
using type = typename std::tuple_element_t<I, PlacedTuple>::output_type;
};
template <std::size_t I>
using output_type_t = typename output_type_at<I>::type;
template <std::size_t I>
using concrete_output_type = concrete_type_t<output_type_t<I>>;
private:
template <std::size_t... Is>
static auto outputs_tuple_type(std::index_sequence<Is...>)
-> std::tuple<output_type_t<Is>...>;
template <std::size_t... Is>
static auto concrete_outputs_tuple_type(std::index_sequence<Is...>)
-> std::tuple<concrete_output_type<Is>...>;
public:
using outputs_tuple = decltype(outputs_tuple_type(
std::make_index_sequence<num_outputs>{}));
using concrete_outputs_tuple = decltype(concrete_outputs_tuple_type(
std::make_index_sequence<num_outputs>{}));
template <std::size_t I>
static constexpr std::size_t lg_outputs_per_leaf_of =
(num_outputs > 0) ? meta[I].lg_opl : 0;
template <std::size_t I>
static constexpr std::size_t outputs_per_leaf_of = std::size_t{1}
<< lg_outputs_per_leaf_of<I>;
private:
template <std::size_t... Is>
static auto wrapper_tuple_t(std::index_sequence<Is...>)
-> std::tuple<
dpf::leaf_wrapper<output_type_t<Is>, exterior_node>...>;
template <std::size_t... Is>
static auto leaf_tuple_type(std::index_sequence<Is...>)
-> std::tuple<
dpf::leaf_node_t<exterior_node, concrete_output_type<Is>>...>;
public:
using leaf_wrapper_tuple = decltype(wrapper_tuple_t(
std::make_index_sequence<num_outputs>{}));
/// @brief Raw leaf shares (pre-wrapper), matching classic `leaf_tuple` for asio.
using leaf_tuple = decltype(leaf_tuple_type(
std::make_index_sequence<num_outputs>{}));
using offset_type = offset_wrapper<InputT>;
template <std::size_t... Is>
static constexpr auto wildcard_mask_tuple(std::index_sequence<Is...>)
{
return std::make_tuple(dpf::is_wildcard_v<output_type_t<Is>>...);
}
static constexpr auto wildcard_mask =
wildcard_mask_tuple(std::make_index_sequence<num_outputs>{});
template <std::size_t... Is>
static constexpr std::array<bool, num_outputs>
wildcard_mask_array(std::index_sequence<Is...>)
{
return {{std::get<Is>(wildcard_mask)...}};
}
static constexpr auto wildcard_bits =
wildcard_mask_array(std::make_index_sequence<num_outputs>{});
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
constexpr bool is_wildcard(std::size_t i) const noexcept
{
return i < num_outputs && wildcard_bits[i];
}
static constexpr std::size_t deepest_prefix = [] {
if constexpr (num_outputs == 0)
return CmpDepth;
else
{
std::size_t m = 0;
for (std::size_t i = 0; i < num_outputs; ++i)
m = std::max(m, meta[i].prefix);
return m;
}
}();
/// @brief First output (source order) whose prefix equals `deepest_prefix`.
static constexpr std::size_t deepest_output = [] {
if constexpr (num_outputs == 0)
return std::size_t{0};
else
{
for (std::size_t i = 0; i < num_outputs; ++i)
{
if (meta[i].prefix == deepest_prefix)
return i;
}
return std::size_t{0};
}
}();
/// @brief Classic-shaped packing traits for deepest-group interval/sequence APIs.
static constexpr std::size_t outputs_per_leaf =
(num_outputs > 0) ? outputs_per_leaf_of<deepest_output> : 1;
static constexpr std::size_t lg_outputs_per_leaf =
(num_outputs > 0) ? lg_outputs_per_leaf_of<deepest_output> : 0;
template <std::size_t... Is>
static auto addend_tuple_t(std::index_sequence<Is...>)
-> std::tuple<output_type_t<Is>...>;
using addend_tuple = decltype(addend_tuple_t(
std::make_index_sequence<num_outputs>{}));
static value_cw_word cw_word_from_u64(std::uint64_t v)
{
if constexpr (std::is_integral_v<value_cw_word>)
return static_cast<value_cw_word>(v);
else
return value_cw_word{};
}
incr_key_base(interior_node root,
const correction_words_array & correction_words,
const correction_advice_array & correction_advice,
leaf_wrapper_tuple leaves, input_type offset_share,
detail::cmp_meta cmp = {}, value_cw_array value_cws = {},
uint64_t cw_last_in = 0, uint64_t cmp_addend_in = 0,
addend_tuple addends = {}, value_cw_array value_cw_coeff = {},
uint64_t cw_last_coeff_in = 0, tail_array tail_in = {},
tail_array tail_coeff_in = {}, prefix_cw_array prefix_in = {},
prefix_cw_array prefix_coeff_in = {},
correction_seeds_array correction_seeds = {})
: leaf_nodes{std::move(leaves)},
offset_x{offset_share},
public_addends{std::move(addends)},
cmp_store_{cmp, value_cws,
cw_word_from_u64(cw_last_in),
cw_word_from_u64(cmp_addend_in),
value_cw_coeff,
cw_word_from_u64(cw_last_coeff_in),
tail_in, tail_coeff_in, prefix_in, prefix_coeff_in},
root_{root},
correction_words_{correction_words},
correction_advice_{correction_advice},
correction_seeds_{correction_seeds},
common_part_hash_{utils::get_common_part_hash(correction_words_,
correction_advice_, leaf_nodes, wildcard_mask, correction_seeds_)}
{ }
incr_key_base(const incr_key_base &) = default;
incr_key_base(incr_key_base &&) = default;
incr_key_base & operator=(const incr_key_base &) = default;
incr_key_base & operator=(incr_key_base &&) = default;
const interior_node & root() const { return root_; }
const correction_words_array & correction_words() const
{
return correction_words_;
}
const correction_advice_array & correction_advice() const
{
return correction_advice_;
}
const correction_seeds_array & correction_seeds() const
{
return correction_seeds_;
}
const value_cw_array & value_cw() const { return cmp_store_.value_cw(); }
HEDLEY_NO_THROW
uint64_t cw_last() const noexcept { return cmp_store_.cw_last(); }
HEDLEY_NO_THROW
value_cw_word cw_last_word() const noexcept { return cmp_store_.cw_last_word(); }
HEDLEY_NO_THROW
value_cw_word cmp_addend_word() const noexcept
{
return cmp_store_.cmp_addend_word();
}
void set_cmp_scalars(value_cw_word last, value_cw_word addend,
value_cw_word last_coeff)
{
cmp_store_.set_scalars(last, addend, last_coeff);
}
/// @brief Mark whether a wildcard comparison payload has been assigned.
/// @param assigned true once `assign_cmp` has run
HEDLEY_NO_THROW
void set_cmp_assigned(bool assigned) noexcept
{
cmp_store_.set_assigned(assigned);
}
const value_cw_array & value_cw_coeff() const noexcept
{
return cmp_store_.value_cw_coeff();
}
HEDLEY_NO_THROW
value_cw_word cw_last_coeff_word() const noexcept
{
return cmp_store_.cw_last_coeff_word();
}
const tail_array & tail_coeff() const noexcept
{
return cmp_store_.tail_coeff();
}
const prefix_cw_array & prefix_cw_coeff() const noexcept
{
return cmp_store_.prefix_cw_coeff();
}
void assign_cmp_group(const detail::group_elem & delta, value_cw_word addend)
{
cmp_store_.assign_group(delta, addend);
}
template <typename Mix>
void assign_cmp_payload(Mix mix, value_cw_word addend)
{
cmp_store_.assign_payload(std::move(mix), addend);
}
HEDLEY_NO_THROW
const prefix_cw_array & prefix_cws() const noexcept
{
return cmp_store_.prefix_cws();
}
uint64_t prefix_cw(std::size_t i) const { return cmp_store_.prefix_cw(i); }
/// @brief Party-local share of the constant absorb (`if_false`, or
/// `δ + if_false` when `eval_as_ge`). Reconstructs with the peer share.
/// @return Party-local share of the constant absorb (`if_false`, or `δ + if_false` when
/// `eval_as_ge`)
HEDLEY_NO_THROW
uint64_t cmp_addend() const noexcept { return cmp_store_.cmp_addend(); }
HEDLEY_NO_THROW
const detail::cmp_meta & cmp() const noexcept { return cmp_store_.cmp(); }
const digest_type & common_part_hash() const { return common_part_hash_; }
const leaf_wrapper_tuple & leaves() const { return leaf_nodes; }
const interior_node & correction_word(std::size_t level) const
{
return correction_words_[level];
}
psnip_uint8_t correction_advice(std::size_t level) const
{
return correction_advice_[level];
}
auto correction_word(std::size_t level, bool direction) const
{
return tree::pack_cw(correction_word(level),
correction_advice_[level], direction,
tree::is_last_level(level, depth));
}
uint64_t value_cw(std::size_t level) const { return cmp_store_.value_cw(level); }
const tail_array & tail_cw() const { return cmp_store_.tail_cw(); }
uint64_t tail_cw(std::size_t i) const { return cmp_store_.tail_cw(i); }
template <std::size_t I = 0>
const auto & leaf() const
{
static_assert(num_outputs > 0, "cmp-only key has no leaves");
if constexpr (dpf::is_wildcard_v<output_type_t<I>>)
return std::get<I>(leaf_nodes).raw_leaf();
else
return std::get<I>(leaf_nodes).get();
}
template <std::size_t I = 0>
const auto & beaver() const
{
static_assert(num_outputs > 0, "cmp-only key has no beavers");
return std::get<I>(leaf_nodes).beaver();
}
#ifdef LIBDPF_HAS_ASIO
template <std::size_t I = 0,
typename PeerT,
typename OutputType,
typename CompletionToken>
auto async_assign_leaf(PeerT & peer, OutputType && output_share,
CompletionToken && token)
{
static_assert(num_outputs > 0, "cmp-only key has no leaves");
static_assert(dpf::is_wildcard_v<output_type_t<I>>,
"async_assign_leaf requires a wildcard output slot");
return dpf::asio::async_assign_wildcard_output<I>(
peer, *this, std::forward<OutputType>(output_share),
std::forward<CompletionToken>(token));
}
#endif
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static auto traverse_interior(const interior_node & node,
const interior_node & cw, bool dir, bool is_last = false) noexcept
{
return tree::traverse(node, cw, dir, is_last);
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static auto traverse_interior01(const interior_node & node,
const interior_node & cw0, const interior_node & cw1,
bool is_last = false) noexcept
{
return tree::traverse01(node, cw0, cw1, is_last);
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
static void traverse_interior01_x4(const interior_node * HEDLEY_RESTRICT parents,
const interior_node & cw0, const interior_node & cw1,
interior_node * HEDLEY_RESTRICT left,
interior_node * HEDLEY_RESTRICT right, bool is_last = false) noexcept
{
tree::traverse01_x4(parents, cw0, cw1, left, right, is_last);
}
template <std::size_t I = 0, typename LeafT>
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static auto traverse_exterior(const interior_node & node,
const LeafT & correction_word) noexcept
{
static_assert(num_outputs > 0, "cmp-only key has no exterior outputs");
using Out = concrete_output_type<I>;
constexpr auto pos =
meta[I].pos_base + meta[I].index_in_group * meta[I].block_len;
constexpr auto count = meta[I].block_len;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
using leaf_type = dpf::leaf_node_t<exterior_node, Out>;
HEDLEY_PRAGMA(GCC diagnostic pop)
leaf_type mask{};
auto seed_ =
utils::to_exterior_node<exterior_node>(unset_lo_2bits(node));
if constexpr (IsExtractable)
{
detail::vdpf::extractable_leaf_prg<exterior_prg>::eval(seed_,
leaf_blocks<exterior_node>(mask),
static_cast<psnip_uint32_t>(count),
static_cast<psnip_uint32_t>(pos));
}
else
{
exterior_prg::eval(seed_, leaf_blocks<exterior_node>(mask),
static_cast<psnip_uint32_t>(count),
static_cast<psnip_uint32_t>(pos));
}
encode_curve_leaf_mask<Out>(mask);
return dpf::subtract_leaf<Out>(
dpf::get_if_lo_bit(correction_word, node), mask);
}
template <std::size_t I = 0>
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
auto traverse_exterior(const interior_node & node) const noexcept
{
static_assert(num_outputs > 0, "cmp-only key has no exterior outputs");
return traverse_exterior<I>(node, std::get<I>(leaf_nodes).get());
}
/// @brief Eight one-block leaves, or eight scalar leaves when the PRG is special.
template <std::size_t I = 0, typename Out>
void traverse_exterior_x8(const interior_node * HEDLEY_RESTRICT nodes,
Out * HEDLEY_RESTRICT out) const noexcept
{
const auto & cw = std::get<I>(leaf_nodes).get();
if constexpr (IsExtractable || meta[I].block_len != 1)
{
for (std::size_t t = 0; t < 8; ++t)
out[t] = traverse_exterior<I>(nodes[t], cw);
return;
}
else
{
using OutT = concrete_output_type<I>;
using block = exterior_node;
alignas(64) block seeds[8];
alignas(64) block masks[8];
constexpr auto pos = meta[I].pos_base
+ meta[I].index_in_group * meta[I].block_len;
for (std::size_t t = 0; t < 8; ++t)
seeds[t] = utils::to_exterior_node<block>(unset_lo_2bits(nodes[t]));
exterior_prg::eval_x8(seeds, masks, static_cast<psnip_uint32_t>(pos));
for (std::size_t t = 0; t < 8; ++t)
{
encode_curve_leaf_mask<OutT>(masks[t]);
out[t] = dpf::subtract_leaf<OutT>(
dpf::get_if_lo_bit(cw, nodes[t]), masks[t]);
}
}
}
leaf_wrapper_tuple leaf_nodes;
offset_type offset_x;
/// @brief Public `if_false` addends for `eq` / `eq_at` slots.
addend_tuple public_addends{};
HEDLEY_NO_THROW
bool has_cmp() const noexcept { return cmp_store_.has_cmp(); }
/// @brief True once a wildcard comparison payload has been assigned (always true
/// for concrete cmp keys and for keys without a comparison channel).
/// @return True once a wildcard comparison payload has been assigned (always true for concrete
/// cmp keys and for keys without a comparison channel)
HEDLEY_NO_THROW
bool cmp_assigned() const noexcept { return cmp_store_.cmp_assigned(); }
/// @brief Patch the value CWs / `cw_last` for a resolved payload δ and install
/// this party's `cmp_addend` share. Only valid for wildcard cmp keys; see
/// the free `dpf::assign_cmp`. No tree re-walk / re-PRG.
/// @param delta the payload difference `if_true - if_false`
/// @param addend_share the `addend_share`
void assign_cmp_delta(uint64_t delta, uint64_t addend_share)
{
cmp_store_.assign_cmp_delta(delta, addend_share);
}
private:
cmp_storage<value_cw_len, value_cw_word, CmpWild, cmp_tail, (CmpBlock > 0),
CmpIdcf>
cmp_store_{};
interior_node root_;
correction_words_array correction_words_;
correction_advice_array correction_advice_;
correction_seeds_array correction_seeds_{};
digest_type common_part_hash_;
}; // struct incr_key_base
} // namespace incr
} // namespace detail
// ---------------------------------------------------------------------------
// Unified `dpf_key`: one key type for classic, multi-level (`at<N>`), and
// comparison (`cmp_channel_tag`) packs. Each of OutputT/OutputTs is one of:
// - a bare output type (planted at full input bitlength)
// - a `detail::incr::placed<N,T>` (from `at<N>`)
// - a `cmp_channel_tag<Depth>` (phantom: sets the comparison channel depth)
// Classic-shaped packs (all bare) keep the byte-identical single-level layout.
// ---------------------------------------------------------------------------
namespace detail
{
template <typename Derived, typename InteriorPRG, typename ExteriorPRG,
typename InputT, typename OutputT, typename ...OutputTs>
using dpf_key_base_t = std::conditional_t<
dpf::detail::incr::is_classic_pack_v<OutputT, OutputTs...>,
classic_dpf_key_impl<Derived, InteriorPRG, ExteriorPRG, InputT,
OutputT, OutputTs...>,
dpf::detail::incr::incr_key_base<InteriorPRG, ExteriorPRG, InputT,
typename dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::placed_tuple,
dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::cmp_depth,
dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::cmp_out_bits,
dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::cmp_wild,
dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::cmp_block,
dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::cmp_idcf,
dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::is_verifiable,
dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::is_extractable>>;
} // namespace detail
template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs>
struct dpf_key
: detail::dpf_key_base_t<
dpf_key<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>,
InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>
{
using base_type = detail::dpf_key_base_t<
dpf_key<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>,
InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>;
using base_type::base_type;
};
namespace detail
{
namespace incr
{
// Assemble the public dpf_key type for a (PlacedTuple, CmpDepth, flags) pack by
// expanding the placed slots into the output pack and appending phantom tags.
//
// `dpf_key` always takes an output type. A comparison-only key (empty placed
// pack, CmpDepth > 0) is `dpf_key<..., cmp_channel_tag<...>>`. The no-comparison
// form is a separate specialization so an empty pack is not named as
// `dpf_key<Interior, Exterior, Input>` — `std::conditional_t` would require
// that type to be valid even when CmpDepth > 0.
template <bool WithCmp, typename InteriorPRG, typename ExteriorPRG,
typename InputT, std::size_t CmpDepth, std::size_t CmpOutBits,
bool CmpWild, std::size_t CmpBlock, bool CmpIdcf, typename ...Ps>
struct plain_assembled_key;
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
std::size_t CmpDepth, std::size_t CmpOutBits, bool CmpWild,
std::size_t CmpBlock, bool CmpIdcf, typename ...Ps>
struct plain_assembled_key<true, InteriorPRG, ExteriorPRG, InputT,
CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, Ps...>
{
using type = dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, Ps...,
dpf::cmp_channel_tag<CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf>>;
};
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
std::size_t CmpDepth, std::size_t CmpOutBits, bool CmpWild,
std::size_t CmpBlock, bool CmpIdcf, typename P0, typename ...Ps>
struct plain_assembled_key<false, InteriorPRG, ExteriorPRG, InputT,
CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, P0, Ps...>
{
using type = dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, P0, Ps...>;
};
template <std::size_t CmpDepth, std::size_t CmpOutBits, bool CmpWild,
std::size_t CmpBlock, bool CmpIdcf, bool IsVerifiable,
bool IsExtractable, typename InteriorPRG, typename ExteriorPRG,
typename InputT, typename ...Ps>
struct assemble_key;
template <std::size_t CmpDepth, std::size_t CmpOutBits, bool CmpWild,
std::size_t CmpBlock, bool CmpIdcf, typename InteriorPRG,
typename ExteriorPRG, typename InputT, typename ...Ps>
struct assemble_key<CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, false,
false, InteriorPRG, ExteriorPRG, InputT, Ps...>
{
using type = typename plain_assembled_key<(CmpDepth > 0),
InteriorPRG, ExteriorPRG, InputT, CmpDepth, CmpOutBits, CmpWild,
CmpBlock, CmpIdcf, Ps...>::type;
};
template <std::size_t CmpDepth, std::size_t CmpOutBits, bool CmpWild,
std::size_t CmpBlock, bool CmpIdcf, typename InteriorPRG,
typename ExteriorPRG, typename InputT, typename ...Ps>
struct assemble_key<CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, true,
false, InteriorPRG, ExteriorPRG, InputT, Ps...>
{
using with_cmp = std::conditional_t<
(CmpDepth > 0),
dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, Ps...,
dpf::cmp_channel_tag<CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf>,
dpf::verifiable>,
dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, Ps..., dpf::verifiable>>;
using type = with_cmp;
};
template <std::size_t CmpDepth, std::size_t CmpOutBits, bool CmpWild,
std::size_t CmpBlock, bool CmpIdcf, typename InteriorPRG,
typename ExteriorPRG, typename InputT, typename ...Ps>
struct assemble_key<CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, false,
true, InteriorPRG, ExteriorPRG, InputT, Ps...>
{
using type = std::conditional_t<
(CmpDepth > 0),
dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, Ps...,
dpf::cmp_channel_tag<CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf>,
dpf::extractable>,
dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, Ps..., dpf::extractable>>;
};
template <std::size_t CmpDepth, std::size_t CmpOutBits, bool CmpWild,
std::size_t CmpBlock, bool CmpIdcf, typename InteriorPRG,
typename ExteriorPRG, typename InputT, typename ...Ps>
struct assemble_key<CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, true,
true, InteriorPRG, ExteriorPRG, InputT, Ps...>
{
using type = std::conditional_t<
(CmpDepth > 0),
dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, Ps...,
dpf::cmp_channel_tag<CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf>,
dpf::verifiable, dpf::extractable>,
dpf::dpf_key<InteriorPRG, ExteriorPRG, InputT, Ps...,
dpf::verifiable, dpf::extractable>>;
};
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename PlacedTuple, std::size_t CmpDepth, std::size_t CmpOutBits = 0,
bool CmpWild = false, std::size_t CmpBlock = 0, bool CmpIdcf = false,
bool IsVerifiable = false, bool IsExtractable = false>
struct incr_dpf_key_of;
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename ...Ps, std::size_t CmpDepth, std::size_t CmpOutBits,
bool CmpWild, std::size_t CmpBlock, bool CmpIdcf,
bool IsVerifiable, bool IsExtractable>
struct incr_dpf_key_of<InteriorPRG, ExteriorPRG, InputT, std::tuple<Ps...>,
CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, IsVerifiable, IsExtractable>
{
using type = typename assemble_key<CmpDepth, CmpOutBits, CmpWild, CmpBlock,
CmpIdcf, IsVerifiable, IsExtractable, InteriorPRG, ExteriorPRG, InputT,
Ps...>::type;
};
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
typename PlacedTuple, std::size_t CmpDepth, std::size_t CmpOutBits = 0,
bool CmpWild = false, std::size_t CmpBlock = 0, bool CmpIdcf = false,
bool IsVerifiable = false, bool IsExtractable = false>
using incr_dpf_key_of_t = typename incr_dpf_key_of<InteriorPRG, ExteriorPRG,
InputT, PlacedTuple, CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf,
IsVerifiable, IsExtractable>::type;
} // namespace incr
} // namespace detail
/// @brief One uniform interior node. The default root draw for distributed dealers.
template <typename Node>
struct uniform_node_sampler
{
Node operator()() const
{
return dpf::uniform_sample<Node>();
}
};
template <typename PRG>
struct pseudorandom_root_sampler
{
using root_type = typename PRG::block_type;
pseudorandom_root_sampler(
root_type && seed = dpf::uniform_sample<root_type>())
: seed_{seed}, counter_{0}
{
note_experiment_seed("pseudorandom_root_sampler", seed_);
}
root_type operator()(psnip_uint32_t i) const
{
return PRG::eval(seed_, i);
}
root_type operator()()
{
return this->operator()(counter_.fetch_add(1));
}
const root_type & seed() const { return seed_; }
psnip_uint32_t count() const { return counter_; }
private:
root_type seed_;
std::atomic_uint32_t counter_;
};
namespace utils
{
template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs>
struct dpf_type
{
using type = dpf_key<InteriorPRG, ExteriorPRG,
std::decay_t<InputT>,
std::decay_t<OutputT>,
std::decay_t<OutputTs>...>;
};
template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs>
using dpf_type_t = typename dpf_type<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>::type;
} // namespace utils
namespace detail
{
template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs>
auto make_dpf_impl(dpfargs<InputT, OutputT, OutputTs...> args, root_sampler_t<InteriorPRG> && root_sampler = dpf::uniform_sample<typename InteriorPRG::block_type>)
{
using dpf_type = utils::dpf_type_t<InteriorPRG, ExteriorPRG, InputT,
OutputT, OutputTs...>;
using interior_node = typename dpf_type::interior_node;
using input_type = typename dpf_type::input_type;
using correction_words_array = typename dpf_type::correction_words_array;
using correction_advice_array = typename dpf_type::correction_advice_array;
constexpr auto depth = dpf_type::depth;
auto mask = dpf_type::msb_mask;
input_type x, x0{}, x1{};
if constexpr (dpf::is_wildcard_v<InputT>)
{
auto sampled = args.x();
x = std::get<0>(sampled);
x0 = std::get<1>(sampled).raw();
x1 = std::get<2>(sampled).raw();
}
else
{
x = args.x;
}
utils::flip_msb_if_signed_integral(x);
using tree = dpf::tree_traits<InteriorPRG>;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
interior_node root[2];
HEDLEY_PRAGMA(GCC diagnostic pop)
tree::root_init(root, root_sampler);
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
correction_words_array correction_words;
HEDLEY_PRAGMA(GCC diagnostic pop)
correction_advice_array correction_advice;
interior_node parent[2] = { root[0], root[1] };
for (std::size_t level = 0; level < depth; ++level, mask >>= 1)
{
const bool bit = !!(mask & x);
const bool is_last = tree::is_last_level(level, depth);
const bool ctrl0 = static_cast<bool>(dpf::get_lo_bit(parent[0]));
const bool ctrl1 = static_cast<bool>(dpf::get_lo_bit(parent[1]));
const auto child0 = tree::expand(parent[0], is_last);
const auto child1 = tree::expand(parent[1], is_last);
interior_node cw{};
psnip_uint8_t advice = 0;
tree::make_cw(cw, advice, child0, child1, parent[0], parent[1], bit,
is_last);
parent[0] = tree::advance(parent[0], child0, cw, advice, bit, ctrl0,
is_last);
parent[1] = tree::advance(parent[1], child1, cw, advice, bit, ctrl1,
is_last);
correction_words[level] = cw;
correction_advice[level] = advice;
}
bool sign0 = dpf::get_lo_bit(parent[0]);
// bool sign1 = dpf::get_lo_bit(parent[1]);
auto [pair0, pair1] = std::apply([&x, &parent, &sign0](auto && ...ys)
{
return dpf::make_leaves<ExteriorPRG>(x,
dpf::unset_lo_2bits(parent[0]),
dpf::unset_lo_2bits(parent[1]),
sign0, std::size_t{0}, ys...); }, args.y);
auto && [leaves0, beavers0] = pair0;
auto && [leaves1, beavers1] = pair1;
return std::make_tuple(correction_words, correction_advice,
std::make_tuple(root[0], leaves0, beavers0, x0),
std::make_tuple(root[1], leaves1, beavers1, x1));
} // make_dpf_impl
} // namespace detail
/// @brief Dealer keygen. Returns the two party keys.
/// @param args plaintext point and payloads
/// @param root_sampler draws the interior roots
/// @return a `party_key` pair
/// @note Signed domains flip the MSB before the walk (`flip_msb_if_signed_integral`).
/// @see dpf::eval_point
/// \complexity O(n) time and O(n) key size. n is the domain bitlength (`depth`). The loop does two interior PRG expansions and writes one correction word per level, then builds one exterior leaf per output.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT = dpf::bit,
typename ...OutputTs>
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf(dpfargs<InputT, OutputT, OutputTs...> args, root_sampler_t<InteriorPRG> && root_sampler = dpf::uniform_sample<typename InteriorPRG::block_type>)
{
static_assert(!is_secret_share_v<InputT>,
"make_dpf: domain point must be plaintext");
static_assert(!is_secret_share_v<OutputT>
&& (!is_secret_share_v<OutputTs> && ...),
"make_dpf: payloads must be plaintext");
using dpf_type = utils::dpf_type_t<InteriorPRG, ExteriorPRG, InputT,
OutputT, OutputTs...>;
auto [correction_words, correction_advice,
tuple0, tuple1] = detail::make_dpf_impl<InteriorPRG, ExteriorPRG>(args,
std::forward<root_sampler_t<InteriorPRG>>(root_sampler));
auto & [root0, leaves0, beavers0, offset0] = tuple0;
auto & [root1, leaves1, beavers1, offset1] = tuple1;
return dpf::make_party_key_pair(
dpf_type{root0, correction_words, correction_advice,
leaves0, beavers0, offset0},
dpf_type{root1, correction_words, correction_advice,
leaves1, beavers1, offset1});
} // make_dpf
// Convenience `make_dpf(x, y...)` lives in incremental.hpp so `at<>` and
// mixed-width packs share one entry point with the classic path.
namespace detail
{
template <typename DpfKey,
std::size_t ...Is>
auto make_dpf_random_point_impl(std::index_sequence<Is...>)
{
using input_type = typename DpfKey::input_type;
using interior_prg = typename DpfKey::interior_prg;
using exterior_prg = typename DpfKey::exterior_prg;
input_type x = dpf::uniform_sample<input_type>();
input_type x0 = dpf::uniform_sample<input_type>();
input_type x1 = static_cast<input_type>(x - x0);
auto keys = make_dpf<interior_prg, exterior_prg>(
x, typename DpfKey::concrete_output_type<Is>(1)...);
return std::make_tuple(std::move(keys.first), std::move(keys.second),
x0, x1);
}
} // namespace detail
template <typename DpfKey>
auto make_dpf_random_point()
{
return detail::make_dpf_random_point_impl<DpfKey>(
std::make_index_sequence<
std::tuple_size_v<typename DpfKey::outputs_tuple>>{});
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT = dpf::bit,
typename ...OutputTs>
auto deduce_dpf_type(InputT x, OutputT y = dpf::bit::one, OutputTs ...ys)
{
return utils::dpf_type<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>{};
}
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename OutputT = dpf::bit,
typename ...OutputTs>
auto deduce_dpf_type(dpf::dpfargs<InputT, OutputT, OutputTs...> args)
{
return utils::dpf_type<InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...>{};
}
#define DEDUCE_DPF_TYPE_T(...) typename decltype(dpf::deduce_dpf_type(__VA_ARGS__))::type;
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_DPF_KEY_HPP__