353 lines
11 KiB
C++
353 lines
11 KiB
C++
|
|
/// @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__
|