libdpf/include/dpf/dcf.hpp
Ryan Henry e4e666f459 Initial import of libdpf.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 14:08:32 -06:00

352 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// @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 <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#include <limits>
#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 <typename Beta>
Beta default_false() noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return dpf::bit::zero;
else
return Beta{};
}
template <typename Beta>
uint64_t beta_delta_u64(const Beta & if_true, const Beta & if_false,
uint64_t mask) noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
{
const uint64_t t = static_cast<bool>(if_true) ? 1ULL : 0ULL;
const uint64_t f = static_cast<bool>(if_false) ? 1ULL : 0ULL;
return (t ^ f) & mask;
}
else if constexpr (dpf::utils::is_xor_wrapper_v<Beta>)
{
return (static_cast<uint64_t>(if_true) ^ static_cast<uint64_t>(if_false))
& mask;
}
else
{
return (static_cast<uint64_t>(if_true)
- static_cast<uint64_t>(if_false)) & mask;
}
}
template <typename Beta>
uint64_t beta_to_u64_simple(const Beta & beta, uint64_t mask) noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return (static_cast<bool>(beta) ? 1ULL : 0ULL) & mask;
else
return static_cast<uint64_t>(beta) & mask;
}
template <typename Beta>
Beta sub_beta(const Beta & a, const Beta & b) noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return dpf::bit{static_cast<bool>(a) ^ static_cast<bool>(b)};
else if constexpr (dpf::utils::is_xor_wrapper_v<Beta>)
return Beta{static_cast<uint64_t>(a) ^ static_cast<uint64_t>(b)};
else
return static_cast<Beta>(a - b);
}
template <typename Beta>
Beta u64_to_beta(uint64_t v) noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return dpf::bit{static_cast<bool>(v & 1u)};
else
return static_cast<Beta>(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<uint64_t>(
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 <typename BlockSampler>
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 <cmp_kind Kind, typename Beta>
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<Beta>())
: if_true{std::move(t)}, if_false{std::move(f)} { }
};
template <std::size_t N, cmp_kind Kind, typename Beta>
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<Beta>())
: if_true{std::move(t)}, if_false{std::move(f)} { }
};
template <typename Beta>
inline auto lt(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_pack<cmp_kind::lt, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <typename Beta>
inline auto leq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_pack<cmp_kind::leq, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <typename Beta>
inline auto gt(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_pack<cmp_kind::gt, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <typename Beta>
inline auto geq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_pack<cmp_kind::geq, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto lt_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_at_pack<N, cmp_kind::lt, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto leq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_at_pack<N, cmp_kind::leq, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto gt_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_at_pack<N, cmp_kind::gt, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto geq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return cmp_at_pack<N, cmp_kind::geq, std::decay_t<Beta>>(std::move(t), std::move(f));
}
// ---------------------------------------------------------------------------
// Equality specs: eq / eq_at
// ---------------------------------------------------------------------------
template <typename Beta>
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<Beta>())
: if_true{std::move(t)}, if_false{std::move(f)} { }
};
template <std::size_t N, typename Beta>
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<Beta>())
: if_true{std::move(t)}, if_false{std::move(f)} { }
};
template <typename Beta>
inline auto eq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return eq_pack<std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <std::size_t N, typename Beta>
inline auto eq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
{
return eq_at_pack<N, std::decay_t<Beta>>(std::move(t), std::move(f));
}
template <typename T> struct is_cmp_spec : std::false_type {};
template <cmp_kind K, typename B> struct is_cmp_spec<cmp_pack<K, B>> : std::true_type {};
template <std::size_t N, cmp_kind K, typename B>
struct is_cmp_spec<cmp_at_pack<N, K, B>> : std::true_type {};
template <typename T>
inline constexpr bool is_cmp_spec_v = is_cmp_spec<T>::value;
template <typename T> struct is_eq_spec : std::false_type {};
template <typename B> struct is_eq_spec<eq_pack<B>> : std::true_type {};
template <std::size_t N, typename B>
struct is_eq_spec<eq_at_pack<N, B>> : std::true_type {};
template <typename T>
inline constexpr bool is_eq_spec_v = is_eq_spec<T>::value;
template <typename T>
inline constexpr bool is_dcf_spec_v = is_cmp_spec_v<T>;
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_DCF_HPP__