/// @file dpf/dpf_key.hpp /// @brief /// @details /// @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/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/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)...) }; } template using root_sampler_t = std::add_pointer_t; namespace detail { /// 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. template struct classic_dpf_key_impl { public: using interior_prg = 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; 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; /// Classic keys are single-level; the unified eval surface keeps routing /// them through the classic `eval_*` fast paths (see `is_multilevel_key`). static constexpr bool is_multilevel = false; 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; using correction_words_array = std::array; 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 set_lo_bit(correction_word(level), (correction_advice_[level] >> direction) & 1); } 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) noexcept { return dpf::xor_if_lo_bit( interior_prg::eval(unset_lo_2bits(node), dir), cw, node); } /// 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. 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 { HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto kids = interior_prg::eval01(unset_lo_2bits(node)); return std::array{ dpf::xor_if_lo_bit(kids[0], cw0, node), dpf::xor_if_lo_bit(kids[1], cw1, node) }; HEDLEY_PRAGMA(GCC diagnostic pop) } /// Four independent `traverse_interior01` via `InteriorPRG::eval01_x4`. /// `left[i]` / `right[i]` are the children of `parents[i]`. 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 { 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) } 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; // 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))); HEDLEY_PRAGMA(GCC diagnostic pop) } 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) { return std::make_tuple( dpf::leaf_wrapper, exterior_node>(leaf, beaver)... ); }, 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 { /// 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 /// (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. 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 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; } /// 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). 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; } } 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_{}; }; /// Multi-level / comparison DPF key body. `PlacedTuple` is a tuple of /// `placed` slots; `CmpDepth > 0` activates the comparison channel. template struct incr_key_base { public: using interior_prg = InteriorPRG; using exterior_prg = ExteriorPRG; 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; /// 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. static constexpr bool cmp_is_wildcard = CmpWild; /// 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 std::size_t max_output_level = detail::incr::max_tree_level_v; /// 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); /// 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 /// 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"); 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; using correction_advice_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(); 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{})); /// 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; } }(); /// 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}; } }(); /// 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 = {}) : 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}, common_part_hash_{utils::get_common_part_hash(correction_words_, correction_advice_, leaf_nodes, wildcard_mask)} { } 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 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 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 /// `δ + if_false` when `eval_as_ge`). Reconstructs with the peer share. 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 set_lo_bit(correction_word(level), (correction_advice_[level] >> direction) & 1); } 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) noexcept { return dpf::xor_if_lo_bit( interior_prg::eval(unset_lo_2bits(node), dir), cw, 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 { HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto kids = interior_prg::eval01(unset_lo_2bits(node)); return std::array{ dpf::xor_if_lo_bit(kids[0], cw0, node), dpf::xor_if_lo_bit(kids[1], cw1, node)}; HEDLEY_PRAGMA(GCC diagnostic pop) } 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 { 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) } 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; using leaf_type = dpf::leaf_node_t; leaf_type mask{}; auto seed_ = utils::to_exterior_node(unset_lo_2bits(node)); exterior_prg::eval(seed_, leaf_blocks(mask), static_cast(count), static_cast(pos)); // Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β. 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; /// 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 /// 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 /// this party's `cmp_addend` share. Only valid for wildcard cmp keys; see /// the free `dpf::assign_cmp`. No tree re-walk / re-PRG. 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_; 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>>; } // 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) pair by // expanding the placed slots into the output pack and appending the phantom // cmp tag when a comparison channel is present. template struct assemble_key { using type = dpf::dpf_key>; }; template struct assemble_key<0, CmpOutBits, CmpWild, CmpBlock, CmpIdcf, InteriorPRG, ExteriorPRG, InputT, Ps...> { using type = dpf::dpf_key; }; template struct incr_dpf_key_of; template struct incr_dpf_key_of, CmpDepth, CmpOutBits, CmpWild, CmpBlock, CmpIdcf> { 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); const interior_node root[2] = { dpf::unset_lo_bit(root_sampler()), dpf::set_lo_bit(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] }; bool advice[2]; for (std::size_t level = 0; level < depth; ++level, mask >>= 1) { bool bit = !!(mask & x); advice[0] = dpf::get_lo_bit_and_clear_lo_2bits(parent[0]); advice[1] = dpf::get_lo_bit_and_clear_lo_2bits(parent[1]); auto child0 = InteriorPRG::eval01(parent[0]); auto child1 = InteriorPRG::eval01(parent[1]); interior_node child[2] = { child0[0] ^ child1[0], child0[1] ^ child1[1] }; bool t[2] = { static_cast(dpf::get_lo_bit(child[0]) ^ !bit), static_cast(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]); correction_words[level] = child[!bit]; correction_advice[level] = static_cast(t[1] << 1) | t[0]; } 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__