Document the new DPF surfaces in one command set, and test the field, half-tree, and multipoint edges.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 23:18:10 -06:00
parent 0d8a5a8131
commit 0dff6df8ed
250 changed files with 12199 additions and 1981 deletions

View file

@ -1,6 +1,5 @@
/// @file dpf/dpf_key.hpp
/// @brief
/// @details
/// @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;
@ -19,6 +18,7 @@
#include <atomic>
#include "dpf/prg_aes.hpp"
#include "dpf/tree_traits.hpp"
#include "dpf/wildcard.hpp"
#include "dpf/twiddle.hpp"
#include "dpf/leaf_node.hpp"
@ -26,6 +26,7 @@
#include "dpf/leaf_wrapper.hpp"
#include "dpf/emplace.hpp"
#include "dpf/placement.hpp"
#include "dpf/verifiable.hpp"
#include "dpf/dcf.hpp"
namespace dpf
@ -88,16 +89,25 @@ auto make_dpfargs(InputT && x, OutputT && y, OutputTs && ...ys)
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
{
/// Classic single-level DPF key body (all outputs bare, at full input width,
/// @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,
@ -108,6 +118,7 @@ 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;
@ -156,9 +167,18 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
static constexpr std::size_t cmp_h = 0;
static constexpr std::size_t cmp_checkpoints = 0;
static constexpr std::size_t cmp_tail = 0;
/// Classic keys are single-level; the unified eval surface keeps routing
/// @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<
@ -178,7 +198,10 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
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>
@ -267,8 +290,9 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
HEDLEY_ALWAYS_INLINE
auto correction_word(std::size_t level, bool direction) const
{
return set_lo_bit(correction_word(level),
(correction_advice_[level] >> direction) & 1);
return tree::pack_cw(correction_word(level),
correction_advice_[level], direction,
tree::is_last_level(level, depth));
}
template <std::size_t I = 0>
@ -318,58 +342,46 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static auto traverse_interior(const interior_node & node,
const interior_node & cw, bool dir) noexcept
const interior_node & cw, bool dir, bool is_last = false) noexcept
{
return dpf::xor_if_lo_bit(
interior_prg::eval(unset_lo_2bits(node), dir), cw, node);
return tree::traverse(node, cw, dir, is_last);
}
/// Expand both children of `node` with one pipelined `eval01`.
/// Equivalent to `traverse_interior(node, cw0, 0)` and
/// `traverse_interior(node, cw1, 1)`, but the two AES-128 blocks share
/// a round loop. Full-domain interval eval uses this at almost every
/// interior parent.
/// @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) noexcept
const interior_node & cw0, const interior_node & cw1,
bool is_last = false) noexcept
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto kids = interior_prg::eval01(unset_lo_2bits(node));
return std::array<interior_node, 2>{
dpf::xor_if_lo_bit(kids[0], cw0, node),
dpf::xor_if_lo_bit(kids[1], cw1, node)
};
HEDLEY_PRAGMA(GCC diagnostic pop)
return tree::traverse01(node, cw0, cw1, is_last);
}
/// Four independent `traverse_interior01` via `InteriorPRG::eval01_x4`.
/// `left[i]` / `right[i]` are the children of `parents[i]`.
/// @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) noexcept
interior_node * HEDLEY_RESTRICT right, bool is_last = false) noexcept
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
alignas(interior_node) interior_node seeds[4];
DPF_UNROLL_LOOP
for (std::size_t i = 0; i < 4; ++i)
{
seeds[i] = unset_lo_2bits(parents[i]);
}
interior_prg::eval01_x4(seeds, left, right);
DPF_UNROLL_LOOP
for (std::size_t i = 0; i < 4; ++i)
{
left[i] = dpf::xor_if_lo_bit(left[i], cw0, parents[i]);
right[i] = dpf::xor_if_lo_bit(right[i], cw1, parents[i]);
}
HEDLEY_PRAGMA(GCC diagnostic pop)
tree::traverse01_x4(parents, cw0, cw1, left, right, is_last);
}
template <std::size_t I = 0,
@ -383,11 +395,11 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
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)));
HEDLEY_PRAGMA(GCC diagnostic pop)
}
template <std::size_t I = 0>
@ -414,9 +426,12 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
{
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);
@ -439,16 +454,21 @@ namespace detail
namespace incr
{
/// Comparison-channel storage. Value CWs, `cw_last`, and the `cmp_addend`
/// @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.
/// Extra per-level δ-coefficients kept only for wildcard comparison payloads
/// @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 { };
@ -520,6 +540,10 @@ struct cmp_storage
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_; }
@ -553,9 +577,11 @@ struct cmp_storage
return true;
}
/// Patch the (public) value CWs / `cw_last` in place for a resolved δ and
/// @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,
@ -595,6 +621,117 @@ struct cmp_storage
}
}
/// @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 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_{};
@ -605,33 +742,46 @@ struct cmp_storage
cmp_wild_state<Depth, value_cw_word, Wild, TailLen, Idcf> wild_{};
};
/// Multi-level / comparison DPF key body. `PlacedTuple` is a tuple of
/// @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>
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 placed_tuple = PlacedTuple;
using node_type = exterior_node;
static constexpr std::size_t cmp_depth = CmpDepth;
/// Comparison output group width in bits (0 when there is no cmp channel).
/// @brief Comparison output group width in bits (0 when there is no cmp channel).
static constexpr std::size_t cmp_out_bits = CmpOutBits;
/// True when the comparison payload is an unassigned wildcard.
/// @brief True when the comparison payload is an unassigned wildcard.
static constexpr bool cmp_is_wildcard = CmpWild;
/// 0 = per-level path-sum. `B >= 1` = blocked checkpoints of width `B`.
/// @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>;
/// Residual tail width. 2 only when dropping those levels does not cut an
/// @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)
@ -646,9 +796,7 @@ struct incr_key_base
(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);
/// Multi-level / comparison keys route through the slot-aware eval path.
static constexpr bool is_multilevel = true;
/// Narrowest unsigned word that holds `cmp_out_bits` bits (1 byte for a
/// @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). Value CWs and
/// the addend share are stored in this word.
using value_cw_word = utils::integral_type_from_bitlength_t<
@ -667,11 +815,15 @@ struct incr_key_base
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;
@ -680,6 +832,26 @@ struct incr_key_base
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.
static constexpr bool is_multilevel = [] {
if constexpr (CmpDepth > 0)
return true;
if constexpr (num_outputs == 0)
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
{
@ -714,7 +886,7 @@ struct incr_key_base
public:
using leaf_wrapper_tuple = decltype(wrapper_tuple_t(
std::make_index_sequence<num_outputs>{}));
/// Raw leaf shares (pre-wrapper), matching classic `leaf_tuple` for asio.
/// @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>;
@ -739,7 +911,7 @@ struct incr_key_base
}
}();
/// First output (source order) whose prefix equals `deepest_prefix`.
/// @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};
@ -754,7 +926,7 @@ struct incr_key_base
}
}();
/// Classic-shaped packing traits for deepest-group interval/sequence APIs.
/// @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 =
@ -775,7 +947,8 @@ struct incr_key_base
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 = {})
prefix_cw_array prefix_coeff_in = {},
correction_seeds_array correction_seeds = {})
: leaf_nodes{std::move(leaves)},
offset_x{offset_share},
cmp_store_{cmp, value_cws,
@ -788,8 +961,9 @@ struct incr_key_base
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_advice_, leaf_nodes, wildcard_mask, correction_seeds_)}
{ }
incr_key_base(const incr_key_base &) = default;
@ -806,17 +980,63 @@ struct incr_key_base
{
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);
}
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); }
/// Party-local share of the constant absorb (`if_false`, or
/// @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
@ -834,8 +1054,9 @@ struct incr_key_base
}
auto correction_word(std::size_t level, bool direction) const
{
return set_lo_bit(correction_word(level),
(correction_advice_[level] >> direction) & 1);
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(); }
@ -879,25 +1100,19 @@ struct incr_key_base
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
static auto traverse_interior(const interior_node & node,
const interior_node & cw, bool dir) noexcept
const interior_node & cw, bool dir, bool is_last = false) noexcept
{
return dpf::xor_if_lo_bit(
interior_prg::eval(unset_lo_2bits(node), dir), cw, node);
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) noexcept
const interior_node & cw0, const interior_node & cw1,
bool is_last = false) noexcept
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
auto kids = interior_prg::eval01(unset_lo_2bits(node));
return std::array<interior_node, 2>{
dpf::xor_if_lo_bit(kids[0], cw0, node),
dpf::xor_if_lo_bit(kids[1], cw1, node)};
HEDLEY_PRAGMA(GCC diagnostic pop)
return tree::traverse01(node, cw0, cw1, is_last);
}
HEDLEY_NO_THROW
@ -905,22 +1120,9 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
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) noexcept
interior_node * HEDLEY_RESTRICT right, bool is_last = false) noexcept
{
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
alignas(interior_node) interior_node seeds[4];
DPF_UNROLL_LOOP
for (std::size_t i = 0; i < 4; ++i)
seeds[i] = unset_lo_2bits(parents[i]);
interior_prg::eval01_x4(seeds, left, right);
DPF_UNROLL_LOOP
for (std::size_t i = 0; i < 4; ++i)
{
left[i] = dpf::xor_if_lo_bit(left[i], cw0, parents[i]);
right[i] = dpf::xor_if_lo_bit(right[i], cw1, parents[i]);
}
HEDLEY_PRAGMA(GCC diagnostic pop)
tree::traverse01_x4(parents, cw0, cw1, left, right, is_last);
}
template <std::size_t I = 0>
@ -932,33 +1134,49 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
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));
exterior_prg::eval(seed_, leaf_blocks<exterior_node>(mask),
static_cast<psnip_uint32_t>(count),
static_cast<psnip_uint32_t>(pos));
// Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β.
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));
}
return dpf::subtract_leaf<Out>(
dpf::get_if_lo_bit(std::get<I>(leaf_nodes).get(), node), mask);
}
leaf_wrapper_tuple leaf_nodes;
offset_type offset_x;
/// Public `if_false` addends for `eq` / `eq_at` slots.
/// @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(); }
/// True once a wildcard comparison payload has been assigned (always true
/// @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(); }
/// Patch the value CWs / `cw_last` for a resolved payload δ and install
/// @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);
@ -971,6 +1189,7 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
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
@ -1013,7 +1232,13 @@ using dpf_key_base_t = std::conditional_t<
OutputT, OutputTs...>::cmp_block,
dpf::detail::incr::normalize_pack<
utils::bitlength_of_v<dpf::concrete_type_t<InputT>>,
OutputT, OutputTs...>::cmp_idcf>>;
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
@ -1038,44 +1263,122 @@ namespace detail
namespace incr
{
// Assemble the public dpf_key type for a (PlacedTuple, CmpDepth) pair by
// expanding the placed slots into the output pack and appending the phantom
// cmp tag when a comparison channel is present.
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
// 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 <std::size_t CmpOutBits, bool CmpWild, std::size_t CmpBlock,
bool CmpIdcf, typename InteriorPRG,
typename ExteriorPRG, typename InputT, typename ...Ps>
struct assemble_key<0, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, InteriorPRG,
ExteriorPRG, InputT, Ps...>
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, 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 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 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>
CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, IsVerifiable, IsExtractable>
{
using type = typename assemble_key<CmpDepth, CmpOutBits, CmpWild, CmpBlock,
CmpIdcf, InteriorPRG, ExteriorPRG, InputT, Ps...>::type;
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 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>::type;
InputT, PlacedTuple, CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf,
IsVerifiable, IsExtractable>::type;
} // namespace incr
} // namespace detail
@ -1167,10 +1470,12 @@ auto make_dpf_impl(dpfargs<InputT, OutputT, OutputTs...> args, root_sampler_t<In
utils::flip_msb_if_signed_integral(x);
const interior_node root[2] = {
dpf::unset_lo_bit(root_sampler()),
dpf::set_lo_bit(root_sampler())
};
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")
@ -1179,32 +1484,29 @@ HEDLEY_PRAGMA(GCC diagnostic pop)
correction_advice_array correction_advice;
interior_node parent[2] = { root[0], root[1] };
bool advice[2];
for (std::size_t level = 0; level < depth; ++level, mask >>= 1)
{
bool bit = !!(mask & x);
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]));
advice[0] = dpf::get_lo_bit_and_clear_lo_2bits(parent[0]);
advice[1] = dpf::get_lo_bit_and_clear_lo_2bits(parent[1]);
const auto child0 = tree::expand(parent[0], is_last);
const auto child1 = tree::expand(parent[1], is_last);
auto child0 = InteriorPRG::eval01(parent[0]);
auto child1 = InteriorPRG::eval01(parent[1]);
interior_node child[2] = {
child0[0] ^ child1[0],
child0[1] ^ child1[1]
};
interior_node cw{};
psnip_uint8_t advice = 0;
tree::make_cw(cw, advice, child0, child1, parent[0], parent[1], bit,
is_last);
bool t[2] = {
static_cast<bool>(dpf::get_lo_bit(child[0]) ^ !bit),
static_cast<bool>(dpf::get_lo_bit(child[1]) ^ bit)
};
auto cw = dpf::set_lo_bit(child[!bit], t[bit]);
parent[0] = dpf::xor_if(child0[bit], cw, advice[0]);
parent[1] = dpf::xor_if(child1[bit], cw, advice[1]);
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] = child[!bit];
correction_advice[level] = static_cast<psnip_uint8_t>(t[1] << 1) | t[0];
correction_words[level] = cw;
correction_advice[level] = advice;
}
bool sign0 = dpf::get_lo_bit(parent[0]);