/// @file dpf/placement.hpp /// @brief Prefix placement (`at`), phantom cmp tag, and slot-meta machinery. /// @details Shared by `dpf_key` (unified key) and `incremental.hpp` (gen/eval). /// Holds the pure type-level pieces so `dpf_key.hpp` can build a /// `slot_meta` table for multi-level / cmp keys without depending on /// the generation / evaluation code that lives in `incremental.hpp`. /// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license. #ifndef LIBDPF_INCLUDE_DPF_PLACEMENT_HPP__ #define LIBDPF_INCLUDE_DPF_PLACEMENT_HPP__ #include #include #include #include #include #include #include "dpf/utils.hpp" #include "dpf/leaf_node.hpp" #include "dpf/wildcard.hpp" namespace dpf { // --------------------------------------------------------------------------- // Public placement sugar: `at(y, ys...)` // --------------------------------------------------------------------------- template struct at_pack { static constexpr std::size_t prefix = N; using outputs_tuple = std::tuple; outputs_tuple values; at_pack() = delete; explicit at_pack(OutputT y, OutputTs ...ys) : values{std::move(y), std::move(ys)...} { } }; template struct at_fn { template constexpr auto operator()(OutputT y, OutputTs ...ys) const { return at_pack(std::move(y), std::move(ys)...); } }; template inline constexpr at_fn at{}; template struct is_at : std::false_type {}; template struct is_at> : std::true_type {}; template inline constexpr bool is_at_v = is_at::value; /// @brief Phantom pack element for a key's comparison (DCF) channel. Not a leaf: it /// only records the cmp prefix depth in the key's type. `Depth` is the number /// of tree levels the comparison walks (0 is reserved for "no cmp"). /// @details `OutBits` is the comparison output group width (bits of the β payload), /// so the value CWs / addend can be stored at group width instead of a full /// padded `uint64_t` per level. /// @tparam Depth depth /// @tparam OutBits out bits /// @tparam Wild whether the payload is a wildcard /// @tparam BlockWidth checkpoint spacing, in levels /// @tparam Incremental whether a final correction is stored at every depth template struct cmp_channel_tag { static constexpr std::size_t depth = Depth; static constexpr std::size_t out_bits = OutBits; /// @brief True when the comparison payload (β) is a wildcard to be assigned /// after keygen. Concrete (non-wildcard) cmp keys keep `Wild == false` /// so their layout / type name is unchanged. static constexpr bool wild = Wild; /// @brief 0 keeps the per-level path-sum. `B >= 1` selects blocked checkpoints /// of target width `B`. static constexpr std::size_t block_width = BlockWidth; /// @brief Save a final correction at every depth (`idcf`). static constexpr bool incremental = Incremental; }; template struct is_cmp_channel_tag : std::false_type {}; template struct is_cmp_channel_tag> : std::true_type {}; template inline constexpr bool is_cmp_channel_tag_v = is_cmp_channel_tag>::value; /// Phantom pack element: key carries per-level VDPF correction seeds. struct verifiable { static constexpr bool is_verifiable_tag = true; }; /// Phantom pack element: ROM leaf stretch + extractability checks. struct extractable { static constexpr bool is_extractable_tag = true; }; /// @brief Phantom pack element: keep a beaver-backed leaf so the payload can /// be rewritten without regenerating the path to `α`. struct updatable { static constexpr bool is_updatable_tag = true; }; template struct is_verifiable_tag : std::false_type { }; template <> struct is_verifiable_tag : std::true_type { }; template inline constexpr bool is_verifiable_tag_v = is_verifiable_tag>::value; template struct is_extractable_tag : std::false_type { }; template <> struct is_extractable_tag : std::true_type { }; template inline constexpr bool is_extractable_tag_v = is_extractable_tag>::value; template struct is_updatable_tag : std::false_type { }; template <> struct is_updatable_tag : std::true_type { }; template inline constexpr bool is_updatable_tag_v = is_updatable_tag>::value; /// @brief Compile-time switch for verifiable / extractable / output-MAC checks. /// @details Default `auth_profile<>` is semi-honest: no correction seeds and /// no leaf XOF. `verifiable` and `extractable` become phantom tags /// for `make_dpf_profile`. `output_mac` stays on the profile for /// Beaver and carry sessions; key generation does not consume it. template struct auth_profile { static constexpr bool verifiable = Verifiable; static constexpr bool extractable = Extractable; static constexpr bool output_mac = OutputMac; }; using semi_honest = auth_profile; using checked = auth_profile; /// @brief Tuple of phantom tags requested by `Profile` (may be empty). /// @tparam Profile an `auth_profile` specialization /// @return `verifiable` and/or `extractable`, or an empty tuple. /// `Profile::output_mac` is not a key tag. template HEDLEY_CONST HEDLEY_NO_THROW constexpr auto key_tags_tuple() noexcept { if constexpr (Profile::verifiable && Profile::extractable) return std::tuple{verifiable{}, extractable{}}; else if constexpr (Profile::verifiable) return std::tuple{verifiable{}}; else if constexpr (Profile::extractable) return std::tuple{extractable{}}; else return std::tuple{}; } /// @brief Alias for `key_tags_tuple` (use with `std::apply` / `make_dpf_profile`). /// @tparam Profile an `auth_profile` specialization /// @return the same tuple as `key_tags_tuple()` /// @see key_tags_tuple template HEDLEY_CONST HEDLEY_NO_THROW constexpr auto key_tags() noexcept { return key_tags_tuple(); } namespace detail { namespace incr { // --------------------------------------------------------------------------- // A concrete placed output: an output type `OutputT` planted at prefix `N`. // --------------------------------------------------------------------------- /// @brief Default: placed payload type is the stored type. Specialized for /// `arith_beta` in `doerner_shelat.hpp` so the key leaf type is `T`. /// @tparam T value type template struct unwrap_placed_output { using type = T; }; template struct placed { static constexpr std::size_t prefix = N; /// @brief When true, party 0 absorbs `addend` (eq / eq_at if_false) at eval. static constexpr bool has_public_addend = PublicAddend; /// @brief Stored argument type (may be `arith_beta`). using stored_type = OutputT; /// @brief Key / leaf payload type (`T` when stored is `arith_beta`). using output_type = typename unwrap_placed_output::type; OutputT value; output_type addend{}; // public if_false for eq(...); party 0 absorbs at eval }; template struct is_placed : std::false_type {}; template struct is_placed> : std::true_type {}; template inline constexpr bool is_placed_v = is_placed>::value; template struct any_public_addend : std::false_type {}; template struct any_public_addend> : std::bool_constant<(Ps::has_public_addend || ...)> {}; template inline constexpr bool any_public_addend_v = any_public_addend::value; /// @brief Heavy-hitters incremental point function: one payload per prefix length. /// @details `levels[i]` is the bit length of slot `i`. /// @tparam LevelSeq level seq /// @tparam Betas betas template struct idpf_pack; template struct idpf_pack, Betas...> { static constexpr bool is_idpf = true; static constexpr std::size_t n = sizeof...(Levels); static constexpr std::array levels{ Levels...}; std::tuple values; }; template struct is_idpf : std::false_type {}; template struct is_idpf, Betas...>> : std::true_type {}; template inline constexpr bool is_idpf_v = is_idpf>::value; template inline constexpr std::size_t out_bits_v = utils::bitlength_of_output_v, NodeT>; template inline constexpr std::size_t lg_opl_v = dpf::lg_outputs_per_leaf_v, NodeT>; template inline constexpr std::size_t level_of_v = N - lg_opl_v; template inline constexpr bool prefix_ok_v = (N >= lg_opl_v); // --------------------------------------------------------------------------- // Per-output slot metadata (packing / tree-level table). // --------------------------------------------------------------------------- struct slot_meta { std::size_t prefix; std::size_t tree_level; std::size_t pos_base; std::size_t group_id; std::size_t index_in_group; std::size_t out_bits; std::size_t lg_opl; std::size_t block_len; }; template constexpr std::size_t max_tree_level_impl(std::index_sequence) { std::size_t m = 0; ((m = std::max(m, level_of_v::prefix, typename std::tuple_element_t::output_type>)), ...); return m; } template inline constexpr std::size_t max_tree_level_v = max_tree_level_impl( std::make_index_sequence>{}); template constexpr bool all_prefixes_ok_impl(std::index_sequence) { return (prefix_ok_v::prefix, typename std::tuple_element_t::output_type> && ...); } template inline constexpr bool all_prefixes_ok_v = all_prefixes_ok_impl( std::make_index_sequence>{}); template constexpr void fill_slot_basics( std::array> & meta) { using P = std::tuple_element_t; using O = typename P::output_type; meta[I].prefix = P::prefix; meta[I].lg_opl = lg_opl_v; meta[I].out_bits = out_bits_v; meta[I].tree_level = P::prefix - meta[I].lg_opl; meta[I].block_len = dpf::block_length_of_leaf_v, NodeT>; meta[I].pos_base = 0; meta[I].group_id = 0; meta[I].index_in_group = 0; } template constexpr void fill_all_basics( std::array> & meta, std::index_sequence) { (fill_slot_basics(meta), ...); } template constexpr auto build_meta() { constexpr std::size_t n = std::tuple_size_v; std::array meta{}; fill_all_basics(meta, std::make_index_sequence{}); std::size_t next_gid = 0; for (std::size_t i = 0; i < n; ++i) { std::size_t gid = static_cast(-1); for (std::size_t j = 0; j < i; ++j) { if (meta[j].prefix == meta[i].prefix && meta[j].out_bits == meta[i].out_bits) { gid = meta[j].group_id; break; } } if (gid == static_cast(-1)) gid = next_gid++; meta[i].group_id = gid; } const std::size_t ngroups = next_gid; std::array group_count{}; for (std::size_t i = 0; i < n; ++i) group_count[i] = 0; for (std::size_t i = 0; i < n; ++i) meta[i].index_in_group = group_count[meta[i].group_id]++; std::array g_level{}; std::array g_blocks{}; std::array g_first{}; for (std::size_t g = 0; g < ngroups; ++g) { g_blocks[g] = 0; g_first[g] = n; g_level[g] = 0; } for (std::size_t i = 0; i < n; ++i) { const auto g = meta[i].group_id; g_level[g] = meta[i].tree_level; g_blocks[g] += meta[i].block_len; if (i < g_first[g]) g_first[g] = i; } constexpr std::size_t depth = max_tree_level_v; std::array g_done{}; for (std::size_t g = 0; g < ngroups; ++g) g_done[g] = false; for (std::size_t level = 0; level <= depth; ++level) { std::size_t cursor = (level == depth) ? 0 : 2; for (;;) { std::size_t best_g = n; std::size_t best_first = n; for (std::size_t g = 0; g < ngroups; ++g) { if (g_done[g] || g_level[g] != level) continue; if (g_first[g] < best_first) { best_first = g_first[g]; best_g = g; } } if (best_g == n) break; for (std::size_t i = 0; i < n; ++i) { if (meta[i].group_id == best_g) meta[i].pos_base = cursor; } cursor += g_blocks[best_g]; g_done[best_g] = true; } } return meta; } template constexpr auto build_group_order(const MetaArray & meta, std::size_t n) { std::array order{}; std::array used{}; for (std::size_t g = 0; g < NGroups; ++g) used[g] = false; for (std::size_t k = 0; k < NGroups; ++k) { std::size_t best = NGroups; std::size_t best_lvl = static_cast(-1); std::size_t best_first = n; for (std::size_t g = 0; g < NGroups; ++g) { if (used[g]) continue; std::size_t lvl = 0, first = n; for (std::size_t i = 0; i < n; ++i) { if (meta[i].group_id == g) { lvl = meta[i].tree_level; if (i < first) first = i; } } if (lvl < best_lvl || (lvl == best_lvl && first < best_first)) { best_lvl = lvl; best_first = first; best = g; } } order[k] = best; used[best] = true; } return order; } template constexpr InputT lane_input(InputT x, std::size_t prefix, std::size_t bitlen) { if (prefix >= bitlen) return x; // Shift on the integral representation so this works for `modint`, // `keyword` (whose `>>` yields a parent `modint`, not the keyword), and // signed/bitstring inputs. Reconstruct the input type from the shifted // integral value via `make_from_integral_value` (a friend of `keyword`). constexpr auto to_int = utils::to_integral_type{}; using FromI = typename utils::make_from_integral_value::integral_type; const auto shifted = static_cast(to_int(x) >> (bitlen - prefix)); return utils::make_from_integral_value{}(shifted); } // Concatenate index sequences template struct cat_seq; template <> struct cat_seq<> { using type = std::index_sequence<>; }; template struct cat_seq> { using type = std::index_sequence; }; template struct cat_seq, std::index_sequence, Rest...> { using type = typename cat_seq, Rest...>::type; }; template using cat_seq_t = typename cat_seq::type; template using keep_if_group = std::conditional_t< MetaHolder::value[I].group_id == G, std::index_sequence, std::index_sequence<>>; template struct filter_group; template struct filter_group> { using type = cat_seq_t...>; }; template using filter_group_t = typename filter_group>::type; template struct meta_holder { static constexpr auto value = KeyT::meta; }; // --------------------------------------------------------------------------- // Normalize a `dpf_key` output pack into (PlacedTuple, CmpDepth). // // Each pack element is one of: // - a bare output `T` -> placed // - a `placed` -> placed (from `at`) // - a `cmp_channel_tag` -> not a leaf; contributes Depth to CmpDepth // --------------------------------------------------------------------------- template struct normalize_one { using placed_tuple = std::tuple>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; static constexpr bool is_verifiable = false; static constexpr bool is_extractable = false; }; template struct normalize_one> { using placed_tuple = std::tuple>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; static constexpr bool is_verifiable = false; static constexpr bool is_extractable = false; }; template struct normalize_one> { using placed_tuple = std::tuple<>; static constexpr std::size_t cmp_depth = Depth; static constexpr std::size_t cmp_out_bits = OutBits; static constexpr bool cmp_wild = Wild; static constexpr std::size_t cmp_block = Block; static constexpr bool cmp_idcf = Incremental; static constexpr bool is_verifiable = false; static constexpr bool is_extractable = false; }; template struct normalize_one { using placed_tuple = std::tuple<>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; static constexpr bool is_verifiable = true; static constexpr bool is_extractable = false; }; template struct normalize_one { using placed_tuple = std::tuple<>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; static constexpr bool is_verifiable = false; static constexpr bool is_extractable = true; }; template struct normalize_one { using placed_tuple = std::tuple<>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; static constexpr bool is_verifiable = false; static constexpr bool is_extractable = false; }; template struct normalize_pack { using placed_tuple = decltype(std::tuple_cat( std::declval::placed_tuple>()...)); static constexpr std::size_t cmp_depth = (std::size_t{0} + ... + normalize_one::cmp_depth); static constexpr std::size_t cmp_out_bits = (std::size_t{0} + ... + normalize_one::cmp_out_bits); static constexpr bool cmp_wild = (false || ... || normalize_one::cmp_wild); static constexpr std::size_t cmp_block = (std::size_t{0} + ... + normalize_one::cmp_block); static constexpr bool cmp_idcf = (false || ... || normalize_one::cmp_idcf); static constexpr bool is_verifiable = (false || ... || normalize_one::is_verifiable); static constexpr bool is_extractable = (false || ... || normalize_one::is_extractable); }; template struct normalize_pack { using placed_tuple = std::tuple<>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; static constexpr std::size_t cmp_block = 0; static constexpr bool cmp_idcf = false; static constexpr bool is_verifiable = false; static constexpr bool is_extractable = false; }; /// @brief True iff the pack is "classic-shaped": every element is a bare output (no /// `placed<>` from `at<>`, no `cmp_channel_tag<>`, and no verifiable/extractable). template inline constexpr bool is_classic_pack_v = !((is_placed_v || is_cmp_channel_tag_v || is_verifiable_tag_v || is_extractable_tag_v || is_updatable_tag_v) || ...); } // namespace incr } // namespace detail /// @brief Sparse heavy-hitters IDPF. Slot `i` is the point function on prefix /// `Levels[i]`, evaluated with `out`. /// @tparam Levels levels /// @tparam Betas betas /// @param betas the `betas` /// @return Sparse heavy-hitters IDPF template auto idpf_at(Betas ...betas) { static_assert(sizeof...(Levels) == sizeof...(Betas), "idpf_at: one payload per prefix length"); return detail::incr::idpf_pack, std::decay_t...>{ std::tuple...>{std::move(betas)...}}; } template auto idpf_from_seq(std::index_sequence, Betas ...betas) { return idpf_at<(I + 1)...>(std::move(betas)...); } /// @brief Consecutive prefixes of length 1, 2, …, `sizeof...(Betas)`. /// @tparam Betas betas /// @param betas the `betas` /// @return Consecutive prefixes of length 1, 2, …, `sizeof...(Betas)` template auto idpf(Betas ...betas) { return idpf_from_seq(std::make_index_sequence{}, std::move(betas)...); } } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_PLACEMENT_HPP__