/// @file dpf/dpf_key.hpp /// @brief The DPF key, its correction words, and interior traversal. /// @author Ryan Henry /// @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 #include #include #include #include #include #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 { template auto async_assign_wildcard_output(PeerT & peer, DpfKey & dpf, OutputType && output_share, CompletionToken && token); } #endif template HEDLEY_WARN_UNUSED_RESULT auto make_dpfargs(InputT && x, OutputT && y = dpf::bit::one, OutputTs && ...ys); template struct dpfargs final { using input_type = InputT; using output_type = std::tuple; 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 friend auto make_dpfargs(I &&, O &&, Os && ...); // friend auto make_dpfargs(InputT && x, OutputT && y, OutputTs && ...ys) }; template HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_WARN_UNUSED_RESULT auto make_dpfargs(InputT && x, OutputT && y, OutputTs && ...ys) { return dpfargs, std::decay_t, std::decay_t...> { std::forward(x), std::make_tuple(std::forward(y), std::forward(ys)...) }; } HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") template using root_sampler_t = std::add_pointer_t; 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 struct classic_dpf_key_impl { public: using interior_prg = InteriorPRG; using tree = dpf::tree_traits; 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; using raw_input_type = InputT; using integral_type = utils::integral_type_from_bitlength_t, utils::bitlength_of_v>; using outputs_tuple = std::tuple; template using output_type_t = std::tuple_element_t; using concrete_outputs_tuple = std::tuple, concrete_type_t...>; template using concrete_output_type = std::tuple_element_t; using offset_type = offset_wrapper; // 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; using beaver_tuple = dpf::beaver_tuple_t; using leaf_wrapper_tuple = std::tuple, dpf::leaf_wrapper...>; HEDLEY_PRAGMA(GCC diagnostic pop) static constexpr std::size_t outputs_per_leaf = dpf::outputs_per_leaf_v; static constexpr std::size_t lg_outputs_per_leaf = dpf::lg_outputs_per_leaf_v; static constexpr std::size_t depth = utils::bitlength_of_v - lg_outputs_per_leaf; static constexpr auto msb_mask = utils::msb_of_v; // ----------------------------------------------------------------------- // 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; 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, OutputT>, dpf::detail::incr::placed, OutputTs>...>; public: using meta_array = std::array; static constexpr meta_array meta = dpf::detail::incr::build_meta(); static constexpr std::size_t deepest_output = 0; template static constexpr std::size_t lg_outputs_per_leaf_of = meta[I].lg_opl; template static constexpr std::size_t outputs_per_leaf_of = std::size_t{1} << lg_outputs_per_leaf_of; HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using correction_words_array = std::array; HEDLEY_PRAGMA(GCC diagnostic pop) using correction_advice_array = std::array; template 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::emplace(output, root, correction_words, correction_advice, leaves, beavers, offset_share); } template 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::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 == dpf::utils::bitlength_of_output_v) && ...), "all output types must be the same length"); HEDLEY_PRAGMA(GCC diagnostic pop) static_assert(std::conjunction_v, std::is_trivially_copyable...>, "all output types must be trivially copyable"); static_assert(std::conjunction_v, std::is_standard_layout...>, "all output types must be standard layout"); // static_assert(std::has_unique_object_representations_v); 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) : root_{root}, correction_words_{correction_words}, correction_advice_{correction_advice}, mutable_wildcard_mask_{dpf::utils::make_bitset(dpf::is_wildcard_v, dpf::is_wildcard_v...)}, leaf_nodes(get_wrappers(leaves, beavers)), common_part_hash_{utils::get_common_part_hash(correction_words_, correction_advice_, leaf_nodes, wildcard_mask)}, offset_x{offset_share} { } 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 HEDLEY_ALWAYS_INLINE const auto & leaf() const { if constexpr (dpf::is_wildcard_v>) { return std::get(leaf_nodes).raw_leaf(); } else { return std::get(leaf_nodes).get(); } } template HEDLEY_ALWAYS_INLINE const auto & beaver() const { return std::get(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 auto async_assign_leaf(PeerT & peer, OutputType && output_share, CompletionToken && token) { return dpf::asio::async_assign_wildcard_output( peer, *this, std::forward(output_share), std::forward(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 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; HEDLEY_PRAGMA(GCC diagnostic pop) // Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β. return dpf::subtract_leaf( dpf::get_if_lo_bit(correction_word, node), make_leaf_mask_inner(unset_lo_2bits(node))); } template HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE auto traverse_exterior(const interior_node & node) const noexcept { return traverse_exterior(node, std::get(leaf_nodes).get()); } leaf_wrapper_tuple leaf_nodes; offset_type offset_x; static constexpr std::array wildcard_mask{dpf::is_wildcard_v, dpf::is_wildcard_v...}; 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, 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 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 struct cmp_wild_state { }; template struct cmp_wild_state { std::array value_cw_coeff{}; ValueCwWord cw_last_coeff{0}; std::array tail_coeff{}; std::array prefix_cw_coeff{}; bool assigned{false}; }; template struct cmp_storage { using value_cw_word = ValueCwWord; using value_cw_array = std::array; using tail_array = std::array; static constexpr std::size_t prefix_cw_len = Idcf ? Depth + 1 : 0; using prefix_cw_array = std::array; 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(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(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(tail_[i]); } HEDLEY_NO_THROW uint64_t cmp_addend() const noexcept { return static_cast(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(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(value_cw_[i]); const uint64_t c = static_cast(wild_.value_cw_coeff[i]); value_cw_[i] = static_cast((base + c * delta) & mask); } if constexpr (Blocked) { for (std::size_t i = 0; i < TailLen; ++i) { const uint64_t base = static_cast(tail_[i]); const uint64_t c = static_cast(wild_.tail_coeff[i]); tail_[i] = static_cast((base + c * delta) & mask); } } const uint64_t lbase = static_cast(cw_last_); const uint64_t lc = static_cast(wild_.cw_last_coeff); cw_last_ = static_cast((lbase + lc * delta) & mask); if constexpr (Idcf) { for (std::size_t i = 0; i < prefix_cw_len; ++i) { const uint64_t base = static_cast(prefix_cw_[i]); const uint64_t c = static_cast(wild_.prefix_cw_coeff[i]); prefix_cw_[i] = static_cast((base + c * delta) & mask); } } cmp_addend_ = static_cast(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( 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_{}; value_cw_word cw_last_{0}; value_cw_word cmp_addend_{0}; tail_array tail_{}; prefix_cw_array prefix_cw_{}; cmp_wild_state wild_{}; }; /// @brief Multi-level / comparison DPF key body. `PlacedTuple` is a tuple of /// `placed` 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 struct incr_key_base { public: using interior_prg = InteriorPRG; using exterior_prg = ExteriorPRG; using tree = dpf::tree_traits; using interior_node = typename InteriorPRG::block_type; using exterior_node = typename ExteriorPRG::block_type; using input_type = dpf::concrete_type_t; 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; /// @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). Value CWs and /// the addend share are stored in this word. using value_cw_word = utils::integral_type_from_bitlength_t< (CmpOutBits == 0 ? std::size_t{1} : CmpOutBits)>; static constexpr std::size_t num_outputs = std::tuple_size_v; static constexpr std::size_t input_bits = utils::bitlength_of_v; 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; using integral_type = utils::integral_type_from_bitlength_t< input_bits, utils::bitlength_of_v>; 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, "at is shorter than the packing lanes required by an output"); using correction_words_array = std::array; HEDLEY_PRAGMA(GCC diagnostic pop) using correction_advice_array = std::array; using correction_seeds_array = std::array; using value_cw_array = std::array; using tail_array = std::array; static constexpr std::size_t prefix_cw_len = CmpIdcf ? depth + 1 : 0; using prefix_cw_array = std::array; using meta_array = std::array; static constexpr meta_array meta = detail::incr::build_meta(); /// @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 struct output_type_at { using type = void; }; template struct output_type_at> { using type = typename std::tuple_element_t::output_type; }; template using output_type_t = typename output_type_at::type; template using concrete_output_type = concrete_type_t>; template static constexpr std::size_t lg_outputs_per_leaf_of = (num_outputs > 0) ? meta[I].lg_opl : 0; template static constexpr std::size_t outputs_per_leaf_of = std::size_t{1} << lg_outputs_per_leaf_of; private: template static auto wrapper_tuple_t(std::index_sequence) -> std::tuple< dpf::leaf_wrapper, exterior_node>...>; template static auto leaf_tuple_type(std::index_sequence) -> std::tuple< dpf::leaf_node_t>...>; public: using leaf_wrapper_tuple = decltype(wrapper_tuple_t( std::make_index_sequence{})); /// @brief Raw leaf shares (pre-wrapper), matching classic `leaf_tuple` for asio. using leaf_tuple = decltype(leaf_tuple_type( std::make_index_sequence{})); using offset_type = offset_wrapper; template static constexpr auto wildcard_mask_tuple(std::index_sequence) { return std::make_tuple(dpf::is_wildcard_v>...); } static constexpr auto wildcard_mask = wildcard_mask_tuple(std::make_index_sequence{}); 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 : 1; static constexpr std::size_t lg_outputs_per_leaf = (num_outputs > 0) ? lg_outputs_per_leaf_of : 0; template static auto addend_tuple_t(std::index_sequence) -> std::tuple...>; using addend_tuple = decltype(addend_tuple_t( std::make_index_sequence{})); 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}, cmp_store_{cmp, value_cws, static_cast(cw_last_in), static_cast(cmp_addend_in), value_cw_coeff, static_cast(cw_last_coeff_in), tail_in, tail_coeff_in, prefix_in, prefix_coeff_in}, public_addends{std::move(addends)}, 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); } 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 const auto & leaf() const { static_assert(num_outputs > 0, "cmp-only key has no leaves"); if constexpr (dpf::is_wildcard_v>) return std::get(leaf_nodes).raw_leaf(); else return std::get(leaf_nodes).get(); } template const auto & beaver() const { static_assert(num_outputs > 0, "cmp-only key has no beavers"); return std::get(leaf_nodes).beaver(); } #ifdef LIBDPF_HAS_ASIO template 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>, "async_assign_leaf requires a wildcard output slot"); return dpf::asio::async_assign_wildcard_output( peer, *this, std::forward(output_share), std::forward(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 HEDLEY_NO_THROW auto traverse_exterior(const interior_node & node) const noexcept { static_assert(num_outputs > 0, "cmp-only key has no exterior outputs"); using Out = concrete_output_type; 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; HEDLEY_PRAGMA(GCC diagnostic pop) leaf_type mask{}; auto seed_ = utils::to_exterior_node(unset_lo_2bits(node)); if constexpr (IsExtractable) { detail::vdpf::extractable_leaf_prg::eval(seed_, leaf_blocks(mask), static_cast(count), static_cast(pos)); } else { exterior_prg::eval(seed_, leaf_blocks(mask), static_cast(count), static_cast(pos)); } return dpf::subtract_leaf( dpf::get_if_lo_bit(std::get(leaf_nodes).get(), node), mask); } 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 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`), 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` (from `at`) // - a `cmp_channel_tag` (phantom: sets the comparison channel depth) // Classic-shaped packs (all bare) keep the byte-identical single-level layout. // --------------------------------------------------------------------------- namespace detail { template using dpf_key_base_t = std::conditional_t< dpf::detail::incr::is_classic_pack_v, classic_dpf_key_impl, dpf::detail::incr::incr_key_base>, OutputT, OutputTs...>::placed_tuple, dpf::detail::incr::normalize_pack< utils::bitlength_of_v>, OutputT, OutputTs...>::cmp_depth, dpf::detail::incr::normalize_pack< utils::bitlength_of_v>, OutputT, OutputTs...>::cmp_out_bits, dpf::detail::incr::normalize_pack< utils::bitlength_of_v>, OutputT, OutputTs...>::cmp_wild, dpf::detail::incr::normalize_pack< utils::bitlength_of_v>, OutputT, OutputTs...>::cmp_block, dpf::detail::incr::normalize_pack< utils::bitlength_of_v>, OutputT, OutputTs...>::cmp_idcf, dpf::detail::incr::normalize_pack< utils::bitlength_of_v>, OutputT, OutputTs...>::is_verifiable, dpf::detail::incr::normalize_pack< utils::bitlength_of_v>, OutputT, OutputTs...>::is_extractable>>; } // namespace detail template struct dpf_key : detail::dpf_key_base_t< dpf_key, InteriorPRG, ExteriorPRG, InputT, OutputT, OutputTs...> { using base_type = detail::dpf_key_base_t< dpf_key, 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` — `std::conditional_t` would require // that type to be valid even when CmpDepth > 0. template struct plain_assembled_key; template struct plain_assembled_key { using type = dpf::dpf_key>; }; template struct plain_assembled_key { using type = dpf::dpf_key; }; template struct assemble_key; template struct assemble_key { using type = typename plain_assembled_key<(CmpDepth > 0), InteriorPRG, ExteriorPRG, InputT, CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, Ps...>::type; }; template struct assemble_key { using with_cmp = std::conditional_t< (CmpDepth > 0), dpf::dpf_key, dpf::verifiable>, dpf::dpf_key>; using type = with_cmp; }; template struct assemble_key { using type = std::conditional_t< (CmpDepth > 0), dpf::dpf_key, dpf::extractable>, dpf::dpf_key>; }; template struct assemble_key { using type = std::conditional_t< (CmpDepth > 0), dpf::dpf_key, dpf::verifiable, dpf::extractable>, dpf::dpf_key>; }; template struct incr_dpf_key_of; template struct incr_dpf_key_of, CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, IsVerifiable, IsExtractable> { using type = typename assemble_key::type; }; template using incr_dpf_key_of_t = typename incr_dpf_key_of::type; } // namespace incr } // namespace detail template struct pseudorandom_root_sampler { using root_type = typename PRG::block_type; pseudorandom_root_sampler( root_type && seed = dpf::uniform_sample()) : seed_{seed}, counter_{0} { } 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 struct dpf_type { using type = dpf_key, std::decay_t, std::decay_t...>; }; template using dpf_type_t = typename dpf_type::type; } // namespace utils namespace detail { template auto make_dpf_impl(dpfargs args, root_sampler_t && root_sampler = dpf::uniform_sample) { using dpf_type = utils::dpf_type_t; 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) { 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; 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(dpf::get_lo_bit(parent[0])); const bool ctrl1 = static_cast(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(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 template HEDLEY_WARN_UNUSED_RESULT auto make_dpf(dpfargs args, root_sampler_t && root_sampler = dpf::uniform_sample) { static_assert(!is_secret_share_v, "make_dpf: domain point must be plaintext"); static_assert(!is_secret_share_v && (!is_secret_share_v && ...), "make_dpf: payloads must be plaintext"); using dpf_type = utils::dpf_type_t; auto [correction_words, correction_advice, tuple0, tuple1] = detail::make_dpf_impl(args, std::forward>(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 auto make_dpf_random_point_impl(std::index_sequence) { 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 x0 = dpf::uniform_sample(); input_type x1 = static_cast(x - x0); auto keys = make_dpf( x, typename DpfKey::concrete_output_type(1)...); return std::make_tuple(std::move(keys.first), std::move(keys.second), x0, x1); } } // namespace detail template auto make_dpf_random_point() { return detail::make_dpf_random_point_impl( std::make_index_sequence< std::tuple_size_v>{}); } template auto deduce_dpf_type(InputT x, OutputT y = dpf::bit::one, OutputTs ...ys) { return utils::dpf_type{}; } template auto deduce_dpf_type(dpf::dpfargs args) { return utils::dpf_type{}; } #define DEDUCE_DPF_TYPE_T(...) typename decltype(dpf::deduce_dpf_type(__VA_ARGS__))::type; } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_DPF_KEY_HPP__