/// @file dpf/dcf.hpp /// @brief Comparison-channel specs and GGM path-sum helpers for libdpf. /// @details `lt`/`leq`/`gt`/`geq` (+ `_at`) take `(if_true, if_false=0)`. /// Eval walks the same GGM tree as the DPF (per-level value CWs). /// `eq` / `eq_at` are synonyms for ordinary point placements. /// @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_DCF_HPP__ #define LIBDPF_INCLUDE_DPF_DCF_HPP__ #include #include #include #include #include #include "hedley/hedley.h" #include "simde/simde/x86/avx2.h" #include "dpf/utils.hpp" #include "dpf/bit.hpp" #include "dpf/xor_wrapper.hpp" #include "dpf/twiddle.hpp" namespace dpf { /// Comparison kind for the optional DCF channel on a key. enum class cmp_kind : uint8_t { lt = 0, leq = 1, gt = 2, geq = 3 }; enum class cmp_trivial : uint8_t { none = 0, always_true = 1, always_false = 2 }; namespace detail { namespace dcf_impl { template Beta default_false() noexcept { if constexpr (std::is_same_v) return dpf::bit::zero; else return Beta{}; } template uint64_t beta_delta_u64(const Beta & if_true, const Beta & if_false, uint64_t mask) noexcept { if constexpr (std::is_same_v) { const uint64_t t = static_cast(if_true) ? 1ULL : 0ULL; const uint64_t f = static_cast(if_false) ? 1ULL : 0ULL; return (t ^ f) & mask; } else if constexpr (dpf::utils::is_xor_wrapper_v) { return (static_cast(if_true) ^ static_cast(if_false)) & mask; } else { return (static_cast(if_true) - static_cast(if_false)) & mask; } } template uint64_t beta_to_u64_simple(const Beta & beta, uint64_t mask) noexcept { if constexpr (std::is_same_v) return (static_cast(beta) ? 1ULL : 0ULL) & mask; else return static_cast(beta) & mask; } template Beta sub_beta(const Beta & a, const Beta & b) noexcept { if constexpr (std::is_same_v) return dpf::bit{static_cast(a) ^ static_cast(b)}; else if constexpr (dpf::utils::is_xor_wrapper_v) return Beta{static_cast(a) ^ static_cast(b)}; else return static_cast(a - b); } template Beta u64_to_beta(uint64_t v) noexcept { if constexpr (std::is_same_v) return dpf::bit{static_cast(v & 1u)}; else return static_cast(v); } inline uint64_t default_mask_for_bits(std::size_t out_bits) noexcept { if (out_bits >= 64) return ~0ULL; if (out_bits == 0) return 0ULL; return (1ULL << out_bits) - 1ULL; } HEDLEY_ALWAYS_INLINE uint64_t neg_m(uint64_t x, uint64_t mask) noexcept { return (0ULL - x) & mask; } HEDLEY_ALWAYS_INLINE uint64_t sgn_m(uint8_t t1, uint64_t x, uint64_t mask) noexcept { return t1 ? neg_m(x, mask) : x; } /// Convert a GGM node to a group element (low 64 bits, control bits cleared). HEDLEY_ALWAYS_INLINE uint64_t convert_node(simde__m128i n, uint64_t mask) noexcept { return static_cast( simde_mm_cvtsi128_si64(dpf::unset_lo_2bits(n))) & mask; } /// Draw the group-width blind `r` used to split the `cmp_addend` share. /// `sample` yields one interior block; only `popcount(mask)` live bits are /// kept, so the blind (and thus the addend share) never needs a full padded /// `uint64_t` on the wire. Dealer and Doerner–Shelat gen call this with the /// same block source so their keys stay byte-identical (matched tapes). template HEDLEY_ALWAYS_INLINE uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept { return convert_node(dpf::unset_lo_2bits(sample()), mask); } /// One level of value CW on GGM children. Updates running `Va`. /// `ai` is the keep-path bit of the (effective) threshold. inline uint64_t make_value_cw(simde__m128i c0L, simde__m128i c0R, simde__m128i c1L, simde__m128i c1R, uint8_t t0, uint8_t t1, int ai, uint64_t & Va, uint64_t beta, uint64_t mask) noexcept { (void)t0; uint64_t v0K, v1K, v0Lo, v1Lo; if (ai == 0) { v0K = convert_node(c0L, mask); v1K = convert_node(c1L, mask); v0Lo = convert_node(c0R, mask); v1Lo = convert_node(c1R, mask); } else { v0K = convert_node(c0R, mask); v1K = convert_node(c1R, mask); v0Lo = convert_node(c0L, mask); v1Lo = convert_node(c1L, mask); } uint64_t vcw = sgn_m(t1, (v1Lo + neg_m(v0Lo, mask) + neg_m(Va, mask)) & mask, mask); // Lose-left (ai==1) is the x<α diverge: plant β there. if (ai == 1) vcw = (vcw + sgn_m(t1, beta, mask)) & mask; Va = (Va + neg_m(v1K, mask) + v0K + sgn_m(t1, vcw, mask)) & mask; return vcw; } /// Final leaf value CW. `on_path` is the payload reconstructed when the query /// stays on α's path through all levels (0 for strict lt/geq; β for leq/gt). inline uint64_t make_final_cw(simde__m128i s0, simde__m128i s1, uint8_t t1, uint64_t Va, uint64_t mask, uint64_t on_path = 0) noexcept { uint64_t c0 = convert_node(s0, mask); uint64_t c1 = convert_node(s1, mask); return sgn_m(t1, (c1 + neg_m(c0, mask) + neg_m(Va, mask) + on_path) & mask, mask); } } // namespace dcf_impl /// Comparison metadata on an incremental key (value CWs live on the key). /// Payload δ = if_true − if_false is dealer-known and baked into `value_cw` / /// `cw_last` only — never stored clear on the key (traditional DPF hiding). /// The second output value (`if_false`) is held as a per-party additive share /// on the key (`cmp_addend`), not as a public constant. struct cmp_meta { int nbits = 0; // comparison prefix length uint64_t mask = 0; cmp_kind kind = cmp_kind::lt; cmp_trivial trivial = cmp_trivial::none; bool eval_as_ge = false; // invert path-sum (geq / gt) bool include_eq = false; // plant δ on the α-path leaf (leq / gt) bool active = false; bool empty() const noexcept { return !active; } }; /// Backward-compatible alias while call sites migrate. using cmp_channel = cmp_meta; } // namespace detail // --------------------------------------------------------------------------- // Comparison specs: lt/leq/gt/geq (+ _at) // --------------------------------------------------------------------------- template struct cmp_pack { static constexpr bool is_cmp = true; static constexpr cmp_kind kind = Kind; static constexpr std::size_t prefix = 0; using beta_type = Beta; Beta if_true; Beta if_false; explicit cmp_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template struct cmp_at_pack { static constexpr bool is_cmp = true; static constexpr cmp_kind kind = Kind; static constexpr std::size_t prefix = N; using beta_type = Beta; Beta if_true; Beta if_false; explicit cmp_at_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template inline auto lt(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_pack>(std::move(t), std::move(f)); } template inline auto leq(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_pack>(std::move(t), std::move(f)); } template inline auto gt(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_pack>(std::move(t), std::move(f)); } template inline auto geq(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_pack>(std::move(t), std::move(f)); } template inline auto lt_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_at_pack>(std::move(t), std::move(f)); } template inline auto leq_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_at_pack>(std::move(t), std::move(f)); } template inline auto gt_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_at_pack>(std::move(t), std::move(f)); } template inline auto geq_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return cmp_at_pack>(std::move(t), std::move(f)); } // --------------------------------------------------------------------------- // Equality specs: eq / eq_at // --------------------------------------------------------------------------- template struct eq_pack { static constexpr bool is_eq = true; static constexpr std::size_t prefix = 0; using beta_type = Beta; Beta if_true; Beta if_false; explicit eq_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template struct eq_at_pack { static constexpr bool is_eq = true; static constexpr std::size_t prefix = N; using beta_type = Beta; Beta if_true; Beta if_false; explicit eq_at_pack(Beta t, Beta f = detail::dcf_impl::default_false()) : if_true{std::move(t)}, if_false{std::move(f)} { } }; template inline auto eq(Beta t, Beta f = detail::dcf_impl::default_false>()) { return eq_pack>(std::move(t), std::move(f)); } template inline auto eq_at(Beta t, Beta f = detail::dcf_impl::default_false>()) { return eq_at_pack>(std::move(t), std::move(f)); } template struct is_cmp_spec : std::false_type {}; template struct is_cmp_spec> : std::true_type {}; template struct is_cmp_spec> : std::true_type {}; template inline constexpr bool is_cmp_spec_v = is_cmp_spec::value; template struct is_eq_spec : std::false_type {}; template struct is_eq_spec> : std::true_type {}; template struct is_eq_spec> : std::true_type {}; template inline constexpr bool is_eq_spec_v = is_eq_spec::value; template inline constexpr bool is_dcf_spec_v = is_cmp_spec_v; } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_DCF_HPP__