/// @file dpf/blocked_dcf.hpp /// @brief Blocked-checkpoint comparison: one ring word per block of levels. /// @details The seed spine stays dense. Ring words are published only at a /// public checkpoint schedule. Point eval expands parked siblings /// up to the next checkpoint; a full-domain memoizer already holds /// those nodes. `q` tail bits, when the comparison sets the key /// depth, are a residual table on the node at height `h`. /// @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_BLOCKED_DCF_HPP__ #define LIBDPF_INCLUDE_DPF_BLOCKED_DCF_HPP__ #include #include #include #include #include #include #include "hedley/hedley.h" #include "dpf/dcf.hpp" #include "dpf/path_memoizer.hpp" #include "dpf/twiddle.hpp" #include "dpf/utils.hpp" namespace dpf { namespace detail { namespace blocked { template struct schedule { static constexpr std::size_t count = (B == 0 || H == 0) ? 0 : (H + B - 1) / B; static constexpr auto depths = [] { std::array cs{}; if constexpr (count == 0) return cs; const std::size_t base = H / count; const std::size_t extra = H % count; std::size_t acc = 0; for (std::size_t i = 0; i < count; ++i) { acc += base + (i < extra ? 1 : 0); cs[i] = acc; } return cs; }(); HEDLEY_CONST HEDLEY_NO_THROW static constexpr bool contains(std::size_t depth) noexcept { for (std::size_t i = 0; i < count; ++i) { if (depths[i] == depth) return true; } return false; } HEDLEY_CONST HEDLEY_NO_THROW static constexpr std::size_t index(std::size_t depth) noexcept { for (std::size_t i = 0; i < count; ++i) { if (depths[i] == depth) return i; } return static_cast(-1); } }; template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW uint64_t rho_of(const Node & node, uint64_t mask) noexcept { auto kids = PRG::eval01(dpf::unset_lo_2bits(node)); return dcf_impl::convert_node(kids[0], mask); } HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr int control_sign(uint8_t t0, uint8_t t1) noexcept { return static_cast(t0) - static_cast(t1); } HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr uint64_t mul_sgn(int sgn, uint64_t v, uint64_t mask) noexcept { if (sgn > 0) return v & mask; if (sgn < 0) return dcf_impl::neg_m(v, mask); return 0; } /// Group element `sgn` (`+1`, `-1`, or `0`) used as an `assign_cmp` coefficient. HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr uint64_t sgn_coeff(int sgn, uint64_t mask) noexcept { if (sgn > 0) return 1ULL & mask; if (sgn < 0) return dcf_impl::neg_m(1ULL, mask); return 0; } template HEDLEY_NO_THROW uint64_t checkpoint_word(const Node & n0, const Node & n1, uint64_t beta, uint64_t mask) noexcept { const int sgn = control_sign( static_cast(dpf::get_lo_bit(n0)), static_cast(dpf::get_lo_bit(n1))); const uint64_t r0 = rho_of(n0, mask); const uint64_t r1 = rho_of(n1, mask); const uint64_t inner = (beta + dcf_impl::neg_m(r0, mask) + r1) & mask; return mul_sgn(sgn, inner, mask); } template HEDLEY_NO_THROW uint64_t checkpoint_coeff(const Node & n0, const Node & n1, uint64_t mask) noexcept { return sgn_coeff(control_sign( static_cast(dpf::get_lo_bit(n0)), static_cast(dpf::get_lo_bit(n1))), mask); } template HEDLEY_NON_NULL(4) HEDLEY_NO_THROW void suffix_masks(const Node & seed, std::size_t q, uint64_t mask, uint64_t * out) noexcept { Node cur[4]{}; Node nxt[8]{}; cur[0] = seed; std::size_t n = 1; for (std::size_t lvl = 0; lvl < q; ++lvl) { std::size_t m = 0; for (std::size_t i = 0; i < n; ++i) { auto kids = PRG::eval01(dpf::unset_lo_2bits(cur[i])); nxt[m++] = kids[0]; nxt[m++] = kids[1]; } for (std::size_t i = 0; i < m; ++i) cur[i] = nxt[i]; n = m; } for (std::size_t i = 0; i < n; ++i) out[i] = dcf_impl::convert_node(cur[i], mask); } template HEDLEY_NON_NULL(8) HEDLEY_NO_THROW void tail_words(const Node & n0, const Node & n1, uint64_t beta, uint64_t mask, bool include_eq, uint64_t suffix, std::size_t q, uint64_t * words, uint64_t * coeffs) noexcept { uint64_t u0[4]{}; uint64_t u1[4]{}; suffix_masks(n0, q, mask, u0); suffix_masks(n1, q, mask, u1); const int sgn = control_sign( static_cast(dpf::get_lo_bit(n0)), static_cast(dpf::get_lo_bit(n1))); const uint64_t coeff = sgn_coeff(sgn, mask); const std::size_t n = std::size_t{1} << q; for (std::size_t z = 0; z < n; ++z) { const bool pred = include_eq ? (z <= suffix) : (z < suffix); const uint64_t inner = ((pred ? beta : 0ULL) + dcf_impl::neg_m(u0[z], mask) + u1[z]) & mask; words[z] = mul_sgn(sgn, inner, mask); if (coeffs != nullptr) coeffs[z] = pred ? coeff : 0ULL; } } template HEDLEY_NO_THROW uint64_t add_membership(uint64_t acc, const typename KeyT::interior_node & node, uint64_t word, uint64_t mask, int party) noexcept { using prg = typename KeyT::interior_prg; const uint8_t t = static_cast(dpf::get_lo_bit(node)); const uint64_t y = (rho_of(node, mask) + (t ? word : 0ULL)) & mask; return (acc + (party ? dcf_impl::neg_m(y, mask) : y)) & mask; } template uint64_t add_frontier(uint64_t acc, const typename KeyT::interior_node & seed, std::size_t from_depth, std::size_t to_depth, const KeyT & dpf, uint64_t word, uint64_t mask, int party) { using node = typename KeyT::interior_node; std::vector cur; std::vector nxt; cur.push_back(seed); for (std::size_t lvl = from_depth; lvl < to_depth; ++lvl) { nxt.clear(); nxt.reserve(cur.size() * 2); const node cw0 = dpf.correction_word(lvl, false); const node cw1 = dpf.correction_word(lvl, true); for (const node & fs : cur) { auto kids = KeyT::traverse_interior01(fs, cw0, cw1); nxt.push_back(kids[0]); nxt.push_back(kids[1]); } cur.swap(nxt); } for (const node & fs : cur) acc = add_membership(acc, fs, word, mask, party); return acc; } template HEDLEY_CONST HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr uint64_t query_suffix(InputT tx, std::size_t nbits, std::size_t q) noexcept { if (q == 0) return 0; uint64_t z = 0; auto bit_mask = KeyT::msb_mask >> (nbits - q); for (std::size_t j = 0; j < q; ++j, bit_mask >>= 1) z = (z << 1) | static_cast(!!(bit_mask & tx)); return z; } template HEDLEY_NO_THROW uint64_t finish_share(const KeyT & dpf, uint64_t suffix, uint64_t acc, const typename KeyT::interior_node & at_h, int party) noexcept { using namespace dcf_impl; using prg = typename KeyT::interior_prg; const auto & ch = dpf.cmp(); const uint64_t mask = ch.mask; constexpr std::size_t q = KeyT::cmp_q; constexpr std::size_t h = KeyT::cmp_h; if constexpr (q == 0) { if (ch.include_eq) { constexpr auto wi = schedule::index(h); acc = add_membership(acc, at_h, dpf.value_cw(wi), mask, party); } (void)suffix; } else { const uint64_t z = suffix; uint64_t u[4]{}; suffix_masks(at_h, q, mask, u); const uint8_t t = static_cast(dpf::get_lo_bit(at_h)); const uint64_t y = (u[z] + (t ? dpf.tail_cw(z) : 0ULL)) & mask; acc = (acc + (party ? neg_m(y, mask) : y)) & mask; } if (ch.eval_as_ge) acc = neg_m(acc, mask); const uint64_t add = [&]() -> uint64_t { if constexpr (is_party_key_v) return dpf.cmp_addend().raw(); else return dpf.cmp_addend(); }(); return (acc + add) & mask; } template uint64_t eval_share(const KeyT & dpf, InputT tx, PathMemoizer & path) { using node = typename KeyT::interior_node; const auto & ch = dpf.cmp(); const uint64_t mask = ch.mask; const uint64_t add = [&]() -> uint64_t { if constexpr (is_party_key_v) return dpf.cmp_addend().raw(); else return dpf.cmp_addend(); }(); if (ch.trivial == cmp_trivial::always_true || ch.trivial == cmp_trivial::always_false) return add & mask; constexpr std::size_t h = KeyT::cmp_h; using sched = schedule; const std::size_t nbits = static_cast(ch.nbits); const int party = dpf::get_lo_bit(dpf.root()) ? 1 : 0; dpf::detail::ensure_level(dpf, tx, path, h); struct parked { node seed; std::size_t depth; }; parked pend[128]; std::size_t npend = 0; uint64_t acc = 0; auto bit_mask = KeyT::msb_mask; for (std::size_t level = 0; level < h; ++level, bit_mask >>= 1) { const bool xi = !!(bit_mask & tx); const node & parent = path[level]; const node right = KeyT::traverse_interior(parent, dpf.correction_word(level, true), true); if (!xi) { pend[npend].seed = right; pend[npend].depth = level + 1; ++npend; } const std::size_t c = level + 1; if (sched::contains(c)) { const uint64_t word = dpf.value_cw(sched::index(c)); for (std::size_t p = 0; p < npend; ++p) { acc = add_frontier(acc, pend[p].seed, pend[p].depth, c, dpf, word, mask, party); } npend = 0; } } const uint64_t suffix = query_suffix(tx, nbits, KeyT::cmp_q); return finish_share(dpf, suffix, acc, path[h], party); } template HEDLEY_NO_THROW bool memo_has(const Memo & memo, Integral prefix, std::size_t depth, Integral from_lane, Integral to_excl) noexcept { const auto shift = KeyT::cmp_depth - depth; const auto from_p = from_lane >> shift; if (prefix < from_p) return false; const auto idx = static_cast(prefix - from_p); const auto count = memo.get_nodes_at_level(depth, from_lane, to_excl); return idx < count; } template const typename KeyT::interior_node & memo_node(const Memo & memo, Integral prefix, std::size_t depth, Integral from_lane) { const auto shift = KeyT::cmp_depth - depth; const auto from_p = from_lane >> shift; const auto idx = static_cast(prefix - from_p); return memo[depth][idx]; } template uint64_t eval_share_memo(const KeyT & dpf, Integral lane, Integral from_lane, Integral to_excl, const Memo & memo) { using node = typename KeyT::interior_node; const auto & ch = dpf.cmp(); const uint64_t mask = ch.mask; const uint64_t add = [&]() -> uint64_t { if constexpr (is_party_key_v) return dpf.cmp_addend().raw(); else return dpf.cmp_addend(); }(); if (ch.trivial == cmp_trivial::always_true || ch.trivial == cmp_trivial::always_false) return add & mask; constexpr std::size_t h = KeyT::cmp_h; using sched = schedule; const int party = dpf::get_lo_bit(dpf.root()) ? 1 : 0; struct parked { node seed; Integral prefix; std::size_t depth; }; parked pend[128]; std::size_t npend = 0; uint64_t acc = 0; Integral path_pref = 0; const std::size_t nbits = static_cast(ch.nbits); for (std::size_t level = 0; level < h; ++level) { const bool xi = ((lane >> (nbits - 1 - level)) & Integral{1}) != 0; const node & parent = memo_node(memo, path_pref, level, from_lane); const Integral sib = static_cast((path_pref << 1) | Integral{1}); if (!xi) { const node right = KeyT::traverse_interior(parent, dpf.correction_word(level, true), true); pend[npend].seed = right; pend[npend].prefix = sib; pend[npend].depth = level + 1; ++npend; } path_pref = static_cast((path_pref << 1) | Integral{xi ? 1 : 0}); const std::size_t c = level + 1; if (!sched::contains(c)) continue; const uint64_t word = dpf.value_cw(sched::index(c)); for (std::size_t p = 0; p < npend; ++p) { const std::size_t extra = c - pend[p].depth; const Integral leftmost = static_cast(pend[p].prefix << extra); const Integral rightmost = static_cast( leftmost + static_cast((Integral{1} << extra) - 1)); const bool covered = memo_has(memo, leftmost, c, from_lane, to_excl) && memo_has(memo, rightmost, c, from_lane, to_excl); if (covered && extra < 16) { const Integral nleaf = static_cast(Integral{1} << extra); for (Integral k = 0; k < nleaf; ++k) { const auto pref = static_cast(leftmost + k); acc = add_membership(acc, memo_node(memo, pref, c, from_lane), word, mask, party); } } else { acc = add_frontier(acc, pend[p].seed, pend[p].depth, c, dpf, word, mask, party); } } npend = 0; } const node & at_h = memo_node(memo, path_pref, h, from_lane); const uint64_t suffix = static_cast(lane) & (KeyT::cmp_q == 0 ? 0ULL : ((1ULL << KeyT::cmp_q) - 1ULL)); return finish_share(dpf, suffix, acc, at_h, party); } } // namespace blocked } // namespace detail } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_BLOCKED_DCF_HPP__