libdpf/include/dpf/interval.hpp
Ryan Henry 0d22946a0e Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-28 05:59:19 -06:00

1308 lines
54 KiB
C++
Raw Permalink 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/interval.hpp
/// @brief Public-bound interval containment on one comparison key.
/// @details `make_dpf(r, ic(p, q, β))` hides the mask `r` and the payload `β`.
/// The bounds are public. Reconstruction is `β` when
/// `p ≤ (x − r) mod 2^n ≤ q`, and the false payload otherwise.
///
/// The key is one `lt` comparison at `γ = r − 1`, the Boyle–Chandran–
/// Gilboa–Gupta–Ishai–Kumar–Rathee reduction (EUROCRYPT 2021, Fig. 3;
/// ePrint 2020/1392). Their Section 4.1 is one DCF for a public interval,
/// where the earlier gate used about two.
/// Evaluation walks that key at the two public shifts of `x` and adds
/// a secret-shared correction. Seed corrections, advice bits, leaves,
/// and the path memoizer stay single-path.
/// @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_INTERVAL_HPP__
#define LIBDPF_INCLUDE_DPF_INTERVAL_HPP__
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include "hedley/hedley.h"
#include "dpf/dcf.hpp"
#include "dpf/eval_unified.hpp"
#include "dpf/geneval.hpp"
#include "dpf/grow.hpp"
#include "dpf/grow_ds.hpp"
#include "dpf/incremental.hpp"
#include "dpf/output_buffer.hpp"
#include "dpf/secret_share.hpp"
#include "dpf/utils.hpp"
namespace dpf
{
template <typename Beta>
struct ic_pack
{
static constexpr bool is_ic = true;
using beta_type = Beta;
uint64_t lo = 0;
uint64_t hi = 0;
Beta if_true{};
Beta if_false{};
};
/// @brief Spec tag and factory. `dpf::ic(p, q, beta)` builds a pack;
/// `eval_point(dpf::ic, key, x)` evaluates it.
struct ic_fn
{
template <typename Lo, typename Hi, typename Beta>
HEDLEY_WARN_UNUSED_RESULT
ic_pack<std::decay_t<Beta>> operator()(Lo lo, Hi hi, Beta t,
Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>()) const
{
ic_pack<std::decay_t<Beta>> spec;
spec.lo = static_cast<uint64_t>(lo);
spec.hi = static_cast<uint64_t>(hi);
spec.if_true = std::move(t);
spec.if_false = std::move(f);
return spec;
}
};
inline constexpr ic_fn ic{};
template <typename T>
struct is_ic_key : std::false_type {};
/// @brief One party's interval key: the inner comparison, the public bounds, and the
/// secret correction shares.
/// @tparam Party party index, `0` or `1`
/// @tparam Key key type
/// @tparam Input input domain type
/// @tparam Beta payload type
template <std::size_t Party, typename Key, typename Input, typename Beta>
struct ic_key
{
static constexpr std::size_t party = Party;
static constexpr bool wildcard = Key::cmp_is_wildcard;
using input_type = Input;
using key_type = party_key<Party, Key>;
using beta_type = Beta;
using share_type = std::conditional_t<
detail::cmp_group_info<dpf::concrete_type_t<Beta>>::custom,
detail::group_elem, uint64_t>;
/// @brief Inner comparison key. Eval that accepts a `dpf_key` also accepts
/// this object and reads `dpf_key`.
key_type dpf_key;
uint64_t lo = 0;
uint64_t hi = 0;
uint64_t input_mask = 0;
uint64_t group_mask = 0;
/// @brief Share of `δ`. Public `c_x ∈ {-1,0,1}` scales it locally.
share_type delta_share{};
/// @brief Share of `δ · c_r + if_false`.
share_type cr_share{};
/// @brief Wildcard only: shares of `1` and of `c_r`, scaled by `δ` in `assign_cmp`.
share_type delta_coeff{};
share_type cr_coeff{};
bool assigned = !wildcard;
ic_key(key_type k, uint64_t lo_in, uint64_t hi_in, uint64_t nmask,
uint64_t gmask, share_type dshare, share_type cshare, share_type dcoeff,
share_type ccoeff) noexcept(std::is_nothrow_move_constructible_v<key_type>)
: dpf_key(std::move(k))
, lo(lo_in)
, hi(hi_in)
, input_mask(nmask)
, group_mask(gmask)
, delta_share(dshare)
, cr_share(cshare)
, delta_coeff(dcoeff)
, cr_coeff(ccoeff)
{}
};
template <std::size_t Party, typename Key, typename Input, typename Beta>
struct is_ic_key<ic_key<Party, Key, Input, Beta>> : std::true_type {};
template <typename T>
inline constexpr bool is_ic_key_v = is_ic_key<std::decay_t<T>>::value;
namespace detail
{
namespace ic_impl
{
template <typename Input>
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t input_mask_of() noexcept
{
constexpr auto n = utils::bitlength_of_v<Input>;
if constexpr (n >= 64)
return ~uint64_t{0};
else
return (uint64_t{1} << n) - 1ULL;
}
template <typename Input>
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t bits_of(Input x) noexcept
{
constexpr auto to_int = utils::to_integral_type<Input>{};
return static_cast<uint64_t>(to_int(x)) & input_mask_of<Input>();
}
template <typename Input>
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr Input input_from_bits(uint64_t u) noexcept
{
return static_cast<Input>(u & input_mask_of<Input>());
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t embed_small(int s, uint64_t mask) noexcept
{
if (s >= 0)
return static_cast<uint64_t>(s) & mask;
return dcf_impl::neg_m(static_cast<uint64_t>(-s), mask);
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t mul_mask(uint64_t a, uint64_t b, uint64_t mask) noexcept
{
return static_cast<uint64_t>(static_cast<unsigned __int128>(a) * b) & mask;
}
/// @brief Public integer in Fig. 3, before it is embedded in the payload group.
/// @param r the `r`
/// @param p the `p`
/// @param q the `q`
/// @param nmask the mask of the live input bits
/// @return Public integer in Fig. 3, before it is embedded in the payload group
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr int correction_s(uint64_t r, uint64_t p, uint64_t q,
uint64_t nmask) noexcept
{
const uint64_t aq = (q + r) & nmask;
const uint64_t ap = (p + r) & nmask;
const uint64_t q0 = (q + 1ULL) & nmask;
const uint64_t aq0 = (q0 + r) & nmask;
return (ap > aq ? 1 : 0) - (ap > p ? 1 : 0)
+ (aq0 > q0 ? 1 : 0) + (aq == nmask ? 1 : 0);
}
/// @brief Dealer correction in Fig. 3, as an element of the payload group.
/// @param r the `r`
/// @param p the `p`
/// @param q the `q`
/// @param nmask the mask of the live input bits
/// @param gmask the mask of the live payload bits
/// @return Dealer correction in Fig. 3, as an element of the payload group
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t correction(uint64_t r, uint64_t p, uint64_t q,
uint64_t nmask, uint64_t gmask) noexcept
{
return embed_small(correction_s(r, p, q, nmask), gmask);
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr int public_cx(uint64_t x, uint64_t p, uint64_t q, uint64_t nmask) noexcept
{
const uint64_t q0 = (q + 1ULL) & nmask;
return (x > p ? 1 : 0) - (x > q0 ? 1 : 0);
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t shift_p(uint64_t x, uint64_t p, uint64_t nmask) noexcept
{
return (x + (nmask - p)) & nmask;
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t shift_q0(uint64_t x, uint64_t q, uint64_t nmask) noexcept
{
const uint64_t q0 = (q + 1ULL) & nmask;
return (x + (nmask - q0)) & nmask;
}
template <typename T>
HEDLEY_PURE
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
uint64_t opened_u64(const T & v, uint64_t mask) noexcept
{
if constexpr (is_secret_share_v<std::decay_t<T>>)
return dcf_impl::beta_to_u64_simple(v.raw(), mask);
else
return dcf_impl::beta_to_u64_simple(v, mask);
}
inline void split_target(uint64_t target, uint64_t mask,
uint64_t & s0, uint64_t & s1)
{
const uint64_t blind = dcf_impl::sample_addend_blind(mask,
[] { return dpf::uniform_sample<simde__m128i>(); });
incr::split_cmp_addend(target, mask, blind, s0, s1);
}
template <typename Input>
void check_input()
{
static_assert(std::is_unsigned_v<Input> && !std::is_same_v<Input, bool>,
"ic: input type must be an unsigned integer of at most 64 bits");
static_assert(utils::bitlength_of_v<Input> <= 64,
"ic: input type must be an unsigned integer of at most 64 bits");
static_assert(utils::bitlength_of_v<Input> > 0,
"ic: input type must be an unsigned integer of at most 64 bits");
}
template <typename Input, typename Beta>
void check_bounds(const ic_pack<Beta> & spec)
{
const uint64_t nmask = input_mask_of<Input>();
if (spec.lo > nmask || spec.hi > nmask)
throw std::invalid_argument("ic: bound does not fit in the input domain");
if (spec.lo > spec.hi)
throw std::invalid_argument("ic: require lo <= hi (the interval does not wrap)");
}
template <typename Beta>
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t group_mask_of() noexcept
{
using B = concrete_type_t<Beta>;
if constexpr (std::is_same_v<B, dpf::bit>)
return 1ULL;
else
return dcf_impl::default_mask_for_bits(utils::bitlength_of_v<B>);
}
template <std::size_t Party, typename Key, typename Input, typename Beta,
typename Share>
ic_key<Party, Key, Input, Beta> make_side(party_key<Party, Key> key,
uint64_t lo, uint64_t hi, uint64_t nmask, uint64_t gmask,
Share delta_share, Share cr_share, Share delta_coeff, Share cr_coeff)
{
return ic_key<Party, Key, Input, Beta>(std::move(key), lo, hi, nmask, gmask,
delta_share, cr_share, delta_coeff, cr_coeff);
}
template <typename Input, typename Beta, typename Pair>
auto finish(uint64_t r_bits, const ic_pack<Beta> & spec, Pair && inner)
{
using in_type = std::decay_t<Input>;
using party0 = std::decay_t<decltype(inner.first)>;
using raw_key = typename party0::key_type;
using out_beta = concrete_type_t<Beta>;
const uint64_t nmask = input_mask_of<in_type>();
const uint64_t gmask = group_mask_of<Beta>();
constexpr bool wild = is_wildcard_v<Beta>;
if constexpr (detail::cmp_group_info<Beta>::custom)
{
using prg = typename raw_key::interior_prg;
const auto layout = detail::group_layout<out_beta>();
auto delta = detail::group_zero(layout);
auto fval = detail::group_zero(layout);
if constexpr (!wild)
{
delta = detail::group_sub(detail::group_from_beta(spec.if_true),
detail::group_from_beta(spec.if_false));
fval = detail::group_from_beta(spec.if_false);
}
const auto cr = detail::group_scalar(
correction_s(r_bits, spec.lo, spec.hi, nmask), layout);
auto splitg = [&](const detail::group_elem & target,
detail::group_elem & a, detail::group_elem & b) {
const auto blind = detail::group_from_node<prg>(
dpf::uniform_sample<typename raw_key::interior_node>(), layout);
a = blind;
b = detail::group_sub(target, blind);
};
detail::group_elem d0{}, d1{}, c0{}, c1{}, dc0{}, dc1{}, cc0{}, cc1{};
if constexpr (wild)
{
splitg(detail::group_one(layout), dc0, dc1);
splitg(cr, cc0, cc1);
}
else
{
splitg(delta, d0, d1);
splitg(detail::group_add(detail::group_mul(delta, cr), fval), c0, c1);
}
auto k0 = make_side<0, raw_key, in_type, out_beta>(std::move(inner.first),
spec.lo, spec.hi, nmask, gmask, d0, c0, dc0, cc0);
auto k1 = make_side<1, raw_key, in_type, out_beta>(std::move(inner.second),
spec.lo, spec.hi, nmask, gmask, d1, c1, dc1, cc1);
return std::make_pair(std::move(k0), std::move(k1));
}
else
{
uint64_t delta = 0;
uint64_t fval = 0;
if constexpr (!wild)
{
delta = dcf_impl::beta_delta_u64(spec.if_true, spec.if_false, gmask);
fval = dcf_impl::beta_to_u64_simple(spec.if_false, gmask);
}
const uint64_t cr = correction(r_bits, spec.lo, spec.hi, nmask, gmask);
uint64_t d0 = 0, d1 = 0, c0 = 0, c1 = 0;
uint64_t dc0 = 0, dc1 = 0, cc0 = 0, cc1 = 0;
if constexpr (wild)
{
split_target(1ULL & gmask, gmask, dc0, dc1);
split_target(cr, gmask, cc0, cc1);
}
else
{
split_target(delta, gmask, d0, d1);
const uint64_t absorb =
(mul_mask(delta, cr, gmask) + fval) & gmask;
split_target(absorb, gmask, c0, c1);
}
auto k0 = make_side<0, raw_key, in_type, out_beta>(std::move(inner.first),
spec.lo, spec.hi, nmask, gmask, d0, c0, dc0, cc0);
auto k1 = make_side<1, raw_key, in_type, out_beta>(std::move(inner.second),
spec.lo, spec.hi, nmask, gmask, d1, c1, dc1, cc1);
return std::make_pair(std::move(k0), std::move(k1));
}
}
template <typename Beta>
auto inner_lt(const ic_pack<Beta> & spec)
{
using B = std::decay_t<Beta>;
if constexpr (is_wildcard_v<B>)
return lt(spec.if_true, spec.if_false);
else if constexpr (detail::cmp_group_info<B>::custom)
{
const auto layout = detail::group_layout<concrete_type_t<B>>();
const auto delta = detail::group_sub(
detail::group_from_beta(spec.if_true),
detail::group_from_beta(spec.if_false));
return lt(detail::group_to_beta<B>(delta),
detail::group_to_beta<B>(detail::group_zero(layout)));
}
else
{
const uint64_t gmask = group_mask_of<B>();
const uint64_t delta =
dcf_impl::beta_delta_u64(spec.if_true, spec.if_false, gmask);
return lt(dcf_impl::u64_to_beta<B>(delta), dcf_impl::u64_to_beta<B>(0));
}
}
template <typename Input>
Input gamma_of(Input r)
{
const uint64_t nmask = input_mask_of<Input>();
const uint64_t ru = bits_of(r);
return input_from_bits<Input>((ru - 1ULL) & nmask);
}
template <typename IcKey, typename Query, typename Memo>
auto eval_one(const IcKey & k, Query && x, Memo & memo,
proof_token * pi = nullptr)
{
if (!k.assigned)
throw std::invalid_argument(
"ic eval: wildcard payload not assigned (call assign_cmp)");
using in_type = typename IcKey::input_type;
using beta = typename IcKey::beta_type;
const uint64_t xu = bits_of(in_type(std::forward<Query>(x)));
const uint64_t xp = shift_p(xu, k.lo, k.input_mask);
const uint64_t xq = shift_q0(xu, k.hi, k.input_mask);
if constexpr (detail::cmp_group_info<beta>::custom)
{
auto opened = [](const auto & v) {
if constexpr (is_secret_share_v<std::decay_t<decltype(v)>>)
return detail::group_from_beta(v.raw());
else
return detail::group_from_beta(v);
};
const auto a = opened(detail::incr::eval_cmp_point_impl<beta>(k.dpf_key,
input_from_bits<in_type>(xp), memo, pi));
const auto b = opened(detail::incr::eval_cmp_point_impl<beta>(k.dpf_key,
input_from_bits<in_type>(xq), memo, pi));
const int cx = public_cx(xu, k.lo, k.hi, k.input_mask);
auto scaled = detail::group_zero(a);
if (cx == 1)
scaled = k.delta_share;
else if (cx == -1)
scaled = detail::group_neg(k.delta_share);
const auto y = detail::group_add(detail::group_add(
detail::group_add(detail::group_neg(a), b), k.cr_share), scaled);
return make_eval_cmp_result<typename IcKey::key_type>(
detail::group_to_beta<beta>(y));
}
else
{
const uint64_t a = opened_u64(
detail::incr::eval_cmp_point_impl(k.dpf_key,
input_from_bits<in_type>(xp), memo, pi),
k.group_mask);
const uint64_t b = opened_u64(
detail::incr::eval_cmp_point_impl(k.dpf_key,
input_from_bits<in_type>(xq), memo, pi),
k.group_mask);
const int cx = public_cx(xu, k.lo, k.hi, k.input_mask);
uint64_t scaled = 0;
if (cx == 1)
scaled = k.delta_share & k.group_mask;
else if (cx == -1)
scaled = dcf_impl::neg_m(k.delta_share, k.group_mask);
const uint64_t y = (dcf_impl::neg_m(a, k.group_mask) + b + k.cr_share
+ scaled) & k.group_mask;
return make_eval_cmp_result<typename IcKey::key_type>(
dcf_impl::u64_to_beta<typename IcKey::beta_type>(y));
}
}
} // namespace ic_impl
} // namespace detail
/// @brief Dealer key for public bounds `spec` and secret mask `r`.
/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128`
/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG`
/// @tparam InputT input domain type
/// @tparam Beta payload type
/// @param r the secret input mask
/// @param spec the public bounds and payloads
/// @return Dealer key for public bounds `spec` and secret mask `r`
/// @note Following Boyle, Chandran, Gilboa, Gupta, Ishai, Kumar, and Rathee, EUROCRYPT 2021, Fig. 3 (ePrint 2020/1392): one comparison key, evaluated at two public shifts.
/// \complexity O(n) time and O(n) key size. n is the domain bitlength (`depth`). The loop does two interior PRG expansions and writes one correction word per level, then builds one exterior leaf per output.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename Beta>
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf(InputT && r, const ic_pack<Beta> & spec)
{
using input_type = std::decay_t<InputT>;
detail::ic_impl::check_input<input_type>();
detail::ic_impl::check_bounds<input_type>(spec);
const uint64_t r_bits = detail::ic_impl::bits_of(input_type(r));
const input_type gamma = detail::ic_impl::gamma_of(input_type(r));
auto inner = make_dpf<InteriorPRG, ExteriorPRG>(gamma,
detail::ic_impl::inner_lt(spec));
return detail::ic_impl::finish<input_type>(r_bits, spec, std::move(inner));
}
/// @name Doerner–Shelat interval keys
/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128`
/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG`
/// @tparam InputT input domain type
/// @tparam Beta payload type
/// @param r0 party 0's share of the mask
/// @param r1 party 1's share of the mask
/// @param spec the public bounds and payloads
/// @{
/// @brief XOR shares. `r0 XOR r1` is the secret mask.
/// @tparam RootSampler sampler for the Doerner–Shelat root seed
/// @tparam PadRng pad stream for the Doerner–Shelat protocol
/// @param r0 party 0's share of the mask
/// @param r1 party 1's share of the mask
/// @param rng the Doerner–Shelat randomness tapes
/// @param spec the public bounds and payloads
/// @param tags optional `verifiable` or `extractable` markers
/// @return the two party keys
/// \complexity O(n) time. One `ds_advance_level` per level: two PRG expansions and one `prepare_level`. n is `depth`.
/// \rounds No sockets. This is the in-process transcript. A networked walk is `dpf::party::dist::point_party`.
/// \communication none here. `local_cw_protocol` opens the correction word locally.
/// \preprocessing Per level, `prepare_level` draws one `ds_cw_pads` (two parties × a 128-bit rand, a 128-bit gamma, and a bit) and two `ds_and_pads`. Arithmetic inputs also run a carry chain of n-1 bit-AND triples in `encode_walk_shares`.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename RootSampler,
typename PadRng,
typename InputT,
typename Beta,
typename ...Tags>
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf_doerner_shelat(InputT r0, InputT r1,
ds_randomness<RootSampler, PadRng> rng, const ic_pack<Beta> & spec,
Tags && ...tags)
{
using input_type = std::decay_t<InputT>;
detail::ic_impl::check_input<input_type>();
detail::ic_impl::check_bounds<input_type>(spec);
const input_type r = utils::xor_input_shares(r0, r1);
const uint64_t r_bits = detail::ic_impl::bits_of(r);
const input_type gamma = detail::ic_impl::gamma_of(r);
const input_type g0 = r0;
const input_type g1 = utils::xor_input_shares(g0, gamma);
auto inner = make_dpf_doerner_shelat<InteriorPRG, ExteriorPRG>(g0, g1,
std::move(rng), detail::ic_impl::inner_lt(spec),
std::forward<Tags>(tags)...);
return detail::ic_impl::finish<input_type>(r_bits, spec, std::move(inner));
}
/// @brief Additive shares. `r0 + r1` is the secret mask; γ = (r0 + r1) − 1.
/// @tparam RootSampler sampler for the Doerner–Shelat root seed
/// @tparam PadRng pad stream for the Doerner–Shelat protocol
/// @param r0 party 0's share of the mask
/// @param r1 party 1's share of the mask
/// @param rng the Doerner–Shelat randomness tapes
/// @param spec the public bounds and payloads
/// @param tags optional `verifiable` or `extractable` markers
/// @return the two party keys
/// \complexity O(n) time. One `ds_advance_level` per level: two PRG expansions and one `prepare_level`. n is `depth`.
/// \rounds No sockets. This is the in-process transcript. A networked walk is `dpf::party::dist::point_party`.
/// \communication none here. `local_cw_protocol` opens the correction word locally.
/// \preprocessing Per level, `prepare_level` draws one `ds_cw_pads` (two parties × a 128-bit rand, a 128-bit gamma, and a bit) and two `ds_and_pads`. Arithmetic inputs also run a carry chain of n-1 bit-AND triples in `encode_walk_shares`.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename RootSampler,
typename PadRng,
typename InputT,
typename Beta,
typename ...Tags>
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf_doerner_shelat(arith_input_t, InputT r0, InputT r1,
ds_randomness<RootSampler, PadRng> rng, const ic_pack<Beta> & spec,
Tags && ...tags)
{
using input_type = std::decay_t<InputT>;
detail::ic_impl::check_input<input_type>();
detail::ic_impl::check_bounds<input_type>(spec);
const uint64_t nmask = detail::ic_impl::input_mask_of<input_type>();
const uint64_t r_bits =
(detail::ic_impl::bits_of(r0) + detail::ic_impl::bits_of(r1)) & nmask;
const input_type r = detail::ic_impl::input_from_bits<input_type>(r_bits);
// Additive shares of γ = r − 1: (r0 − 1, r1).
const input_type g0 = detail::ic_impl::input_from_bits<input_type>(
(detail::ic_impl::bits_of(r0) - 1ULL) & nmask);
const input_type g1 = r1;
auto inner = make_dpf_doerner_shelat<InteriorPRG, ExteriorPRG>(
arith_input, g0, g1, std::move(rng), detail::ic_impl::inner_lt(spec),
std::forward<Tags>(tags)...);
return detail::ic_impl::finish<input_type>(
detail::ic_impl::bits_of(r), spec, std::move(inner));
}
/// @brief XOR shares, sampled from the library entropy source.
/// @return the two party keys
/// \complexity O(n) time. One `ds_advance_level` per level: two PRG expansions and one `prepare_level`. n is `depth`.
/// \rounds No sockets. This is the in-process transcript. A networked walk is `dpf::party::dist::point_party`.
/// \communication none here. `local_cw_protocol` opens the correction word locally.
/// \preprocessing Per level, `prepare_level` draws one `ds_cw_pads` (two parties × a 128-bit rand, a 128-bit gamma, and a bit) and two `ds_and_pads`. Arithmetic inputs also run a carry chain of n-1 bit-AND triples in `encode_walk_shares`.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename Beta,
typename ...Tags>
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf_doerner_shelat(InputT r0, InputT r1, const ic_pack<Beta> & spec,
Tags && ...tags)
{
using block = typename InteriorPRG::block_type;
ds_randomness<block (*)(), detail::urandom_pad_rng> rng{
dpf::uniform_sample<block>, {}};
return make_dpf_doerner_shelat<InteriorPRG, ExteriorPRG>(
std::move(r0), std::move(r1), rng, spec,
std::forward<Tags>(tags)...);
}
/// @brief Additive shares, sampled from the library entropy source.
/// @return the two party keys
/// \complexity O(n) time. One `ds_advance_level` per level: two PRG expansions and one `prepare_level`. n is `depth`.
/// \rounds No sockets. This is the in-process transcript. A networked walk is `dpf::party::dist::point_party`.
/// \communication none here. `local_cw_protocol` opens the correction word locally.
/// \preprocessing Per level, `prepare_level` draws one `ds_cw_pads` (two parties × a 128-bit rand, a 128-bit gamma, and a bit) and two `ds_and_pads`. Arithmetic inputs also run a carry chain of n-1 bit-AND triples in `encode_walk_shares`.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename InputT,
typename Beta,
typename ...Tags>
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf_doerner_shelat(arith_input_t, InputT r0, InputT r1,
const ic_pack<Beta> & spec, Tags && ...tags)
{
using block = typename InteriorPRG::block_type;
ds_randomness<block (*)(), detail::urandom_pad_rng> rng{
dpf::uniform_sample<block>, {}};
return make_dpf_doerner_shelat<InteriorPRG, ExteriorPRG>(
arith_input, std::move(r0), std::move(r1), rng, spec,
std::forward<Tags>(tags)...);
}
/// @}
/// @brief Open a wildcard interval payload onto an existing key pair.
/// @tparam Key key type
/// @tparam Input input domain type
/// @tparam Beta payload type
/// @tparam Payload concrete payload type
/// @param k0 the `k0`
/// @param k1 the `k1`
/// @param if_true the payload on a true comparison
/// @param if_false the payload on a false comparison
template <typename Key, typename Input, typename Beta, typename Payload>
void assign_cmp(ic_key<0, Key, Input, Beta> & k0,
ic_key<1, Key, Input, Beta> & k1, const Payload & if_true,
const Payload & if_false = Payload{})
{
static_assert(Key::cmp_is_wildcard,
"assign_cmp: interval payload is not a wildcard");
if constexpr (detail::cmp_group_info<dpf::concrete_type_t<Beta>>::custom)
{
using prg = typename Key::interior_prg;
const auto layout = detail::group_layout<dpf::concrete_type_t<Beta>>();
const auto delta = detail::group_sub(
detail::group_from_beta(if_true), detail::group_from_beta(if_false));
const auto fval = detail::group_from_beta(if_false);
assign_cmp(k0.dpf_key, k1.dpf_key,
detail::group_to_beta<Beta>(delta),
detail::group_to_beta<Beta>(detail::group_zero(layout)));
k0.delta_share = detail::group_mul(k0.delta_coeff, delta);
k1.delta_share = detail::group_mul(k1.delta_coeff, delta);
k0.cr_share = detail::group_mul(k0.cr_coeff, delta);
k1.cr_share = detail::group_mul(k1.cr_coeff, delta);
const auto blind = detail::group_from_node<prg>(
dpf::uniform_sample<typename Key::interior_node>(), layout);
const auto f1 = detail::group_sub(fval, blind);
k0.cr_share = detail::group_add(k0.cr_share, blind);
k1.cr_share = detail::group_add(k1.cr_share, f1);
k0.assigned = true;
k1.assigned = true;
return;
}
else
{
const uint64_t mask = k0.group_mask;
const uint64_t delta =
detail::dcf_impl::beta_delta_u64(if_true, if_false, mask);
const uint64_t fval =
detail::dcf_impl::beta_to_u64_simple(if_false, mask);
assign_cmp(k0.dpf_key, k1.dpf_key,
detail::dcf_impl::u64_to_beta<Beta>(delta),
detail::dcf_impl::u64_to_beta<Beta>(0));
k0.delta_share = detail::ic_impl::mul_mask(k0.delta_coeff, delta, mask);
k1.delta_share = detail::ic_impl::mul_mask(k1.delta_coeff, delta, mask);
k0.cr_share = detail::ic_impl::mul_mask(k0.cr_coeff, delta, mask);
k1.cr_share = detail::ic_impl::mul_mask(k1.cr_coeff, delta, mask);
uint64_t f0 = 0, f1 = 0;
detail::ic_impl::split_target(fval, mask, f0, f1);
k0.cr_share = (k0.cr_share + f0) & mask;
k1.cr_share = (k1.cr_share + f1) & mask;
k0.assigned = true;
k1.assigned = true;
}
}
/// @brief Point evaluation. `memo` is a path memoizer for the inner comparison key.
/// @tparam IcKey interval-containment key type
/// @tparam Query query point type
/// @tparam Memo path memoizer type
/// @param key the key to evaluate
/// @param x the `x`
/// @param memo the memoizer reused across queries
/// @return Point evaluation
/// \complexity Two comparison point-walks (`eval_cmp_point_impl`), each O(n) interior steps, plus O(1) group arithmetic. n is the key depth. A path memoizer reuses a shared prefix.
template <typename IcKey, typename Query,
typename Memo = basic_path_memoizer<typename IcKey::key_type>,
typename = std::enable_if_t<is_ic_key_v<IcKey>>>
HEDLEY_WARN_UNUSED_RESULT
auto eval_point(ic_fn, const IcKey & key, Query && x, Memo && memo = Memo{})
{
return detail::ic_impl::eval_one(key, std::forward<Query>(x), memo);
}
/// @name Interval evaluation
/// @tparam IcKey interval-containment key type
/// @tparam Lane input-domain lane type
/// @tparam Buffer output buffer type
/// @param key the interval key
/// @param from the inclusive start of the range
/// @param to the inclusive end of the range
/// @param buf the output buffer
/// @throws std::invalid_argument if `to < from`
/// @{
/// @brief Inclusive interval `[from, to]` on the input domain.
/// @tparam Memo path memoizer type
/// @param key the interval key
/// @param from the inclusive start of the range
/// @param to the inclusive end of the range
/// @param buf the output buffer
/// @param memo the memoizer reused across queries
/// \complexity One `eval_one` per input from `from` through `to`. Each `eval_one` is two comparison point-walks, so this is not the truncated-tree interval walk. A path memoizer reuses prefixes across those walks. Counted the `for (x = a; x != b; ++x)` loop.
template <typename IcKey, typename Lane, typename Buffer, typename Memo,
typename = std::enable_if_t<is_ic_key_v<IcKey>>>
void eval_interval(ic_fn, const IcKey & key, Lane from, Lane to,
Buffer && buf, Memo && memo)
{
using in_type = typename IcKey::input_type;
const uint64_t nmask = key.input_mask;
const uint64_t a = detail::ic_impl::bits_of(in_type(from));
const uint64_t b = detail::ic_impl::bits_of(in_type(to));
if (a > b)
throw std::invalid_argument("ic interval: to < from");
std::size_t i = 0;
for (uint64_t x = a;; ++x)
{
buf[i++] = detail::ic_impl::eval_one(key,
detail::ic_impl::input_from_bits<in_type>(x), memo);
if (x == b)
break;
if (x == nmask)
throw std::invalid_argument("ic interval: to < from");
}
}
/// @brief Inclusive interval `[from, to]`, with a fresh path memoizer.
/// \complexity One `eval_one` per input from `from` through `to`. Each `eval_one` is two comparison point-walks, so this is not the truncated-tree interval walk. A path memoizer reuses prefixes across those walks. Counted the `for (x = a; x != b; ++x)` loop.
template <typename IcKey, typename Lane, typename Buffer,
typename = std::enable_if_t<is_ic_key_v<IcKey>>>
void eval_interval(ic_fn, const IcKey & key, Lane from, Lane to, Buffer && buf)
{
basic_path_memoizer<typename IcKey::key_type> memo;
eval_interval(ic, key, from, to, std::forward<Buffer>(buf), memo);
}
/// @}
/// @name Sequence evaluation
/// @tparam IcKey interval-containment key type
/// @tparam Iter iterator type
/// @tparam Buffer output buffer type
/// @param key the interval key
/// @param begin the iterator to the first query
/// @param end the iterator past the last query
/// @param buf the output buffer
/// @{
/// @brief Evaluate the points in `[begin, end)`.
/// @tparam Memo path memoizer type
/// @param key the interval key
/// @param begin the iterator to the first query
/// @param end the iterator past the last query
/// @param buf the output buffer
/// @param memo the memoizer reused across queries
/// \complexity One `eval_one` per input from `from` through `to`. Each `eval_one` is two comparison point-walks, so this is not the truncated-tree interval walk. A path memoizer reuses prefixes across those walks. Counted the `for (x = a; x != b; ++x)` loop.
template <typename IcKey, typename Iter, typename Buffer, typename Memo,
typename = std::enable_if_t<is_ic_key_v<IcKey>>>
void eval_sequence(ic_fn, const IcKey & key, Iter begin, Iter end,
Buffer && buf, Memo && memo)
{
std::size_t i = 0;
for (auto it = begin; it != end; ++it, ++i)
buf[i] = detail::ic_impl::eval_one(key, *it, memo);
}
/// @brief Evaluate `[begin, end)`, with a fresh path memoizer.
/// \complexity One `eval_one` per input from `from` through `to`. Each `eval_one` is two comparison point-walks, so this is not the truncated-tree interval walk. A path memoizer reuses prefixes across those walks. Counted the `for (x = a; x != b; ++x)` loop.
template <typename IcKey, typename Iter, typename Buffer,
typename = std::enable_if_t<is_ic_key_v<IcKey>>>
void eval_sequence(ic_fn, const IcKey & key, Iter begin, Iter end, Buffer && buf)
{
basic_path_memoizer<typename IcKey::key_type> memo;
eval_sequence(ic, key, begin, end, std::forward<Buffer>(buf), memo);
}
/// @}
/// @brief Buffer of `n` interval shares.
/// @tparam IcKey interval-containment key type
/// @param n the `n`
/// @return Buffer of `n` interval shares
template <typename IcKey, typename = std::enable_if_t<is_ic_key_v<IcKey>>>
HEDLEY_WARN_UNUSED_RESULT
auto make_output_buffer(ic_fn, const IcKey &, std::size_t n)
{
using beta = typename IcKey::beta_type;
using elem = cmp_buffer_elem_t<typename IcKey::key_type, beta>;
return output_buffer<elem>(n);
}
/// @brief Buffer large enough for the inclusive interval `[from, to]`.
/// @tparam IcKey interval-containment key type
/// @tparam Lane input-domain lane type
/// @param key the `key`
/// @param from the inclusive start of the range
/// @param to the `to`
/// @return Buffer large enough for the inclusive interval `[from, to]`
/// @throws std::invalid_argument if `to < from`
template <typename IcKey, typename Lane,
typename = std::enable_if_t<is_ic_key_v<IcKey>>>
HEDLEY_WARN_UNUSED_RESULT
auto make_output_buffer(ic_fn, const IcKey & key, Lane from, Lane to)
{
using in_type = typename IcKey::input_type;
const uint64_t a = detail::ic_impl::bits_of(in_type(from));
const uint64_t b = detail::ic_impl::bits_of(in_type(to));
if (a > b)
throw std::invalid_argument("ic interval: to < from");
const uint64_t n = b - a + 1ULL;
return make_output_buffer(ic, key, static_cast<std::size_t>(n));
}
/// @name Interval geneval
/// @tparam InputT input domain type
/// @tparam Iter iterator type
/// @tparam RootSampler sampler for the Doerner–Shelat root seed
/// @tparam PadRng pad stream for the Doerner–Shelat protocol
/// @tparam Beta payload type
/// @param r0 party 0's share of the mask
/// @param r1 party 1's share of the mask
/// @param begin the iterator to the first query
/// @param end the iterator past the last query
/// @param rng the Doerner–Shelat randomness tapes
/// @param spec the public bounds and payloads
/// @return the opened party shares
/// @{
namespace detail
{
namespace ic_impl
{
/// @brief Fill `out` from a verifiable IC key pair, reusing one path memoizer.
template <typename IcKey0, typename IcKey1, typename Iter>
void geneval_ic_eval(geneval_cmp_result & out, const IcKey0 & k0,
const IcKey1 & k1, Iter begin, Iter end)
{
using key_type = unwrap_party_key_t<typename IcKey0::key_type>;
constexpr std::size_t depth = key_type::depth;
out.live_levels = depth;
out.mask = k0.dpf_key.cmp().mask;
out.cw_last = k0.dpf_key.cw_last();
out.addend0 = k0.dpf_key.cmp_addend().raw();
out.addend1 = k1.dpf_key.cmp_addend().raw();
out.correction_words.resize(depth);
out.correction_advice.resize(depth);
if constexpr (key_type::cmp_block > 0)
{
out.value_cw.resize(key_type::cmp_checkpoints);
for (std::size_t i = 0; i < key_type::cmp_checkpoints; ++i)
out.value_cw[i] = k0.dpf_key.value_cw(i);
out.tail_cw.resize(key_type::cmp_tail);
for (std::size_t z = 0; z < key_type::cmp_tail; ++z)
out.tail_cw[z] = k0.dpf_key.tail_cw(z);
}
else
out.value_cw.resize(depth);
for (std::size_t level = 0; level < depth; ++level)
{
out.correction_words[level] = k0.dpf_key.correction_word(level);
out.correction_advice[level] =
static_cast<uint8_t>(k0.dpf_key.correction_advice(level));
if constexpr (key_type::cmp_block == 0)
out.value_cw[level] = k0.dpf_key.value_cw(level);
}
// Empty query lists keep default-constructed (zero) tokens. `verify`
// rejects the all-zero token, so an empty geneval does not verify as
// two matching zeros.
detail::vdpf::init_proof(out.proof0, k0.dpf_key);
detail::vdpf::init_proof(out.proof1, k1.dpf_key);
auto path0 = make_basic_path_memoizer(k0.dpf_key);
auto path1 = make_basic_path_memoizer(k1.dpf_key);
for (auto it = begin; it != end; ++it)
{
out.party0.push_back(opened_u64(
eval_one(k0, *it, path0, &out.proof0), out.mask));
out.party1.push_back(opened_u64(
eval_one(k1, *it, path1, &out.proof1), out.mask));
}
detail::vdpf::fold_output_binding(out.proof0, k0.dpf_key);
detail::vdpf::fold_output_binding(out.proof1, k1.dpf_key);
}
} // namespace ic_impl
} // namespace detail
/// @brief XOR mask. `r0 XOR r1` is the secret mask. Each query is
/// returned already combined into the interval share.
/// @details Builds a verifiable inner comparison key and folds correction
/// seeds into `proof0` / `proof1` while reusing one path memoizer per party.
/// An empty query range leaves both tokens zero; those do not verify.
template <typename InputT, typename Iter, typename RootSampler, typename PadRng,
typename Beta>
HEDLEY_WARN_UNUSED_RESULT
geneval_cmp_result geneval_ic(InputT r0, InputT r1, Iter begin, Iter end,
ds_randomness<RootSampler, PadRng> rng, const ic_pack<Beta> & spec)
{
static_assert(!is_wildcard_v<Beta>,
"geneval_ic: payload must be concrete (assign_cmp is a separate step)");
geneval_cmp_result out;
if (begin == end)
return out;
auto keys = make_dpf_doerner_shelat(std::move(r0), std::move(r1),
std::move(rng), spec, dpf::verifiable{});
detail::ic_impl::geneval_ic_eval(out, keys.first, keys.second, begin, end);
return out;
}
/// @brief Additive mask. `r0 + r1` is the secret mask.
template <typename InputT, typename Iter, typename RootSampler, typename PadRng,
typename Beta>
HEDLEY_WARN_UNUSED_RESULT
geneval_cmp_result geneval_ic(arith_input_t, InputT r0, InputT r1, Iter begin,
Iter end, ds_randomness<RootSampler, PadRng> rng, const ic_pack<Beta> & spec)
{
static_assert(!is_wildcard_v<Beta>,
"geneval_ic: payload must be concrete (assign_cmp is a separate step)");
geneval_cmp_result out;
if (begin == end)
return out;
auto keys = make_dpf_doerner_shelat(arith_input, std::move(r0), std::move(r1),
std::move(rng), spec, dpf::verifiable{});
detail::ic_impl::geneval_ic_eval(out, keys.first, keys.second, begin, end);
return out;
}
/// @}
// ---------------------------------------------------------------------------
// Grow adaptations: run on the inner `dpf_key`, then refresh `cr_share` from
// the secret mask `r` when depth changes (`add_output` copies the fields).
// ---------------------------------------------------------------------------
template <std::size_t P0, std::size_t P1, typename Key, typename Input,
typename Beta, typename InputT, typename... Specs>
HEDLEY_WARN_UNUSED_RESULT
auto extend(const ic_key<P0, Key, Input, Beta> & k0,
const ic_key<P1, Key, Input, Beta> & k1, uint64_t r, bool bit, InputT x,
Specs &&... specs)
{
auto inner = dpf::extend(k0.dpf_key, k1.dpf_key, bit, x,
std::forward<Specs>(specs)...);
using new_raw = typename std::decay_t<decltype(inner.first)>::key_type;
const uint64_t nmask = detail::ic_impl::input_mask_of<Input>();
const uint64_t gmask = k0.group_mask;
using share_t = typename ic_key<P0, new_raw, Input, Beta>::share_type;
share_t c0{};
share_t c1{};
if constexpr (is_wildcard_v<Beta>)
{
// Wildcard: keep cr_coeff; concrete cr_share is filled by assign_cmp.
c0 = k0.cr_share;
c1 = k1.cr_share;
}
else if constexpr (detail::cmp_group_info<Beta>::custom)
{
using prg = typename new_raw::interior_prg;
const auto layout = detail::group_layout<concrete_type_t<Beta>>();
const auto cr_g = detail::group_scalar(
detail::ic_impl::correction_s(r, k0.lo, k0.hi, nmask), layout);
const auto target = detail::group_add(
detail::group_mul(k0.delta_share, cr_g), // wrong: need open δ
detail::group_zero(layout));
(void)target;
// Reconstruct δ = d0+d1, if_false from old cr, then new cr = δ·c_r + f.
const auto delta = detail::group_add(k0.delta_share, k1.delta_share);
const auto old_cr_open = detail::group_add(k0.cr_share, k1.cr_share);
const auto old_cr_term = detail::group_mul(delta,
detail::group_scalar(
detail::ic_impl::correction_s(r, k0.lo, k0.hi, k0.input_mask),
layout));
const auto if_false = detail::group_sub(old_cr_open, old_cr_term);
const auto new_target =
detail::group_add(detail::group_mul(delta, cr_g), if_false);
const auto blind = detail::group_from_node<prg>(
dpf::uniform_sample<typename new_raw::interior_node>(), layout);
c0 = blind;
c1 = detail::group_sub(new_target, blind);
}
else
{
const uint64_t delta =
(static_cast<uint64_t>(k0.delta_share)
+ static_cast<uint64_t>(k1.delta_share))
& gmask;
const uint64_t old_cr_open =
(static_cast<uint64_t>(k0.cr_share)
+ static_cast<uint64_t>(k1.cr_share))
& gmask;
const uint64_t old_cr_term = detail::ic_impl::mul_mask(delta,
detail::ic_impl::correction(r, k0.lo, k0.hi, k0.input_mask, gmask),
gmask);
const uint64_t if_false =
(old_cr_open + detail::dcf_impl::neg_m(old_cr_term, gmask)) & gmask;
const uint64_t cr =
detail::ic_impl::correction(r, k0.lo, k0.hi, nmask, gmask);
const uint64_t target =
(detail::ic_impl::mul_mask(delta, cr, gmask) + if_false) & gmask;
const uint64_t blind = detail::dcf_impl::sample_addend_blind(gmask,
[] {
return dpf::uniform_sample<typename new_raw::interior_node>();
});
uint64_t a0 = 0, a1 = 0;
detail::incr::split_cmp_addend(target, gmask, blind, a0, a1);
c0 = static_cast<share_t>(a0);
c1 = static_cast<share_t>(a1);
}
auto out0 = detail::ic_impl::make_side<P0, new_raw, Input, Beta>(
std::move(inner.first), k0.lo, k0.hi, nmask, gmask, k0.delta_share, c0,
k0.delta_coeff, k0.cr_coeff);
auto out1 = detail::ic_impl::make_side<P1, new_raw, Input, Beta>(
std::move(inner.second), k1.lo, k1.hi, nmask, gmask, k1.delta_share, c1,
k1.delta_coeff, k1.cr_coeff);
out0.assigned = k0.assigned;
out1.assigned = k1.assigned;
return std::make_pair(std::move(out0), std::move(out1));
}
template <std::size_t P0, std::size_t P1, typename Key, typename Input,
typename Beta, typename InputT, typename... Specs>
HEDLEY_WARN_UNUSED_RESULT
auto extend(const ic_key<P0, Key, Input, Beta> & k0,
const ic_key<P1, Key, Input, Beta> & k1, uint64_t r, InputT x,
Specs &&... specs)
{
const bool bit = detail::grow_impl::bit_at(
static_cast<typename Key::input_type>(x), Key::depth);
return extend(k0, k1, r, bit, x, std::forward<Specs>(specs)...);
}
template <std::size_t P0, std::size_t P1, typename Key, typename Input,
typename Beta, typename InputT, typename... Specs>
HEDLEY_WARN_UNUSED_RESULT
auto add_output(const ic_key<P0, Key, Input, Beta> & k0,
const ic_key<P1, Key, Input, Beta> & k1, InputT x, Specs &&... specs)
{
auto inner = dpf::add_output(k0.dpf_key, k1.dpf_key, x,
std::forward<Specs>(specs)...);
using new_raw = typename std::decay_t<decltype(inner.first)>::key_type;
auto out0 = detail::ic_impl::make_side<P0, new_raw, Input, Beta>(
std::move(inner.first), k0.lo, k0.hi, k0.input_mask, k0.group_mask,
k0.delta_share, k0.cr_share, k0.delta_coeff, k0.cr_coeff);
auto out1 = detail::ic_impl::make_side<P1, new_raw, Input, Beta>(
std::move(inner.second), k1.lo, k1.hi, k1.input_mask, k1.group_mask,
k1.delta_share, k1.cr_share, k1.delta_coeff, k1.cr_coeff);
out0.assigned = k0.assigned;
out1.assigned = k1.assigned;
return std::make_pair(std::move(out0), std::move(out1));
}
template <std::size_t P0, std::size_t P1, typename Key, typename Input,
typename Beta, typename Memo0, typename Memo1, typename InputT,
typename... Specs>
HEDLEY_WARN_UNUSED_RESULT
auto extend(const ic_key<P0, Key, Input, Beta> & k0,
const ic_key<P1, Key, Input, Beta> & k1, uint64_t r, Memo0 & m0, Memo1 & m1,
bool bit, InputT x, Specs &&... specs)
{
auto inner = dpf::extend(k0.dpf_key, k1.dpf_key, m0, m1, bit, x,
std::forward<Specs>(specs)...);
using new_raw = typename std::decay_t<decltype(inner.first)>::key_type;
const uint64_t nmask = detail::ic_impl::input_mask_of<Input>();
const uint64_t gmask = k0.group_mask;
using share_t = typename ic_key<P0, new_raw, Input, Beta>::share_type;
share_t c0{};
share_t c1{};
if constexpr (is_wildcard_v<Beta>)
{
c0 = k0.cr_share;
c1 = k1.cr_share;
}
else if constexpr (detail::cmp_group_info<Beta>::custom)
{
using prg = typename new_raw::interior_prg;
const auto layout = detail::group_layout<concrete_type_t<Beta>>();
const auto cr_g = detail::group_scalar(
detail::ic_impl::correction_s(r, k0.lo, k0.hi, nmask), layout);
const auto delta = detail::group_add(k0.delta_share, k1.delta_share);
const auto old_cr_open = detail::group_add(k0.cr_share, k1.cr_share);
const auto old_cr_term = detail::group_mul(delta,
detail::group_scalar(
detail::ic_impl::correction_s(r, k0.lo, k0.hi, k0.input_mask),
layout));
const auto if_false = detail::group_sub(old_cr_open, old_cr_term);
const auto new_target =
detail::group_add(detail::group_mul(delta, cr_g), if_false);
const auto blind = detail::group_from_node<prg>(
dpf::uniform_sample<typename new_raw::interior_node>(), layout);
c0 = blind;
c1 = detail::group_sub(new_target, blind);
}
else
{
const uint64_t delta =
(static_cast<uint64_t>(k0.delta_share)
+ static_cast<uint64_t>(k1.delta_share))
& gmask;
const uint64_t old_cr_open =
(static_cast<uint64_t>(k0.cr_share)
+ static_cast<uint64_t>(k1.cr_share))
& gmask;
const uint64_t old_cr_term = detail::ic_impl::mul_mask(delta,
detail::ic_impl::correction(r, k0.lo, k0.hi, k0.input_mask, gmask),
gmask);
const uint64_t if_false =
(old_cr_open + detail::dcf_impl::neg_m(old_cr_term, gmask)) & gmask;
const uint64_t cr =
detail::ic_impl::correction(r, k0.lo, k0.hi, nmask, gmask);
const uint64_t target =
(detail::ic_impl::mul_mask(delta, cr, gmask) + if_false) & gmask;
const uint64_t blind = detail::dcf_impl::sample_addend_blind(gmask,
[] {
return dpf::uniform_sample<typename new_raw::interior_node>();
});
uint64_t a0 = 0, a1 = 0;
detail::incr::split_cmp_addend(target, gmask, blind, a0, a1);
c0 = static_cast<share_t>(a0);
c1 = static_cast<share_t>(a1);
}
auto out0 = detail::ic_impl::make_side<P0, new_raw, Input, Beta>(
std::move(inner.first), k0.lo, k0.hi, nmask, gmask, k0.delta_share, c0,
k0.delta_coeff, k0.cr_coeff);
auto out1 = detail::ic_impl::make_side<P1, new_raw, Input, Beta>(
std::move(inner.second), k1.lo, k1.hi, nmask, gmask, k1.delta_share, c1,
k1.delta_coeff, k1.cr_coeff);
out0.assigned = k0.assigned;
out1.assigned = k1.assigned;
return std::make_pair(std::move(out0), std::move(out1));
}
template <std::size_t P0, std::size_t P1, typename Key, typename Input,
typename Beta, typename Memo0, typename Memo1, typename InputT,
typename... Specs>
HEDLEY_WARN_UNUSED_RESULT
auto add_output(const ic_key<P0, Key, Input, Beta> & k0,
const ic_key<P1, Key, Input, Beta> & k1, Memo0 & m0, Memo1 & m1, InputT x,
Specs &&... specs)
{
auto inner = dpf::add_output(k0.dpf_key, k1.dpf_key, m0, m1, x,
std::forward<Specs>(specs)...);
using new_raw = typename std::decay_t<decltype(inner.first)>::key_type;
auto out0 = detail::ic_impl::make_side<P0, new_raw, Input, Beta>(
std::move(inner.first), k0.lo, k0.hi, k0.input_mask, k0.group_mask,
k0.delta_share, k0.cr_share, k0.delta_coeff, k0.cr_coeff);
auto out1 = detail::ic_impl::make_side<P1, new_raw, Input, Beta>(
std::move(inner.second), k1.lo, k1.hi, k1.input_mask, k1.group_mask,
k1.delta_share, k1.cr_share, k1.delta_coeff, k1.cr_coeff);
out0.assigned = k0.assigned;
out1.assigned = k1.assigned;
return std::make_pair(std::move(out0), std::move(out1));
}
template <std::size_t P0, std::size_t P1, typename Key, typename Input,
typename Beta, typename Memo0, typename Memo1, typename InputT,
typename CwProtocol, typename... Specs>
HEDLEY_WARN_UNUSED_RESULT
auto extend_ds(const ic_key<P0, Key, Input, Beta> & k0,
const ic_key<P1, Key, Input, Beta> & k1, uint64_t r, Memo0 & m0, Memo1 & m1,
InputT x0, InputT x1, CwProtocol & proto, Specs &&... specs)
{
auto inner = dpf::extend_ds(k0.dpf_key, k1.dpf_key, m0, m1, x0, x1, proto,
std::forward<Specs>(specs)...);
using new_raw = typename std::decay_t<decltype(inner.first)>::key_type;
const uint64_t nmask = detail::ic_impl::input_mask_of<Input>();
const uint64_t gmask = k0.group_mask;
using share_t = typename ic_key<P0, new_raw, Input, Beta>::share_type;
share_t c0{};
share_t c1{};
if constexpr (is_wildcard_v<Beta>)
{
c0 = k0.cr_share;
c1 = k1.cr_share;
}
else if constexpr (detail::cmp_group_info<Beta>::custom)
{
using prg = typename new_raw::interior_prg;
const auto layout = detail::group_layout<concrete_type_t<Beta>>();
const auto cr_g = detail::group_scalar(
detail::ic_impl::correction_s(r, k0.lo, k0.hi, nmask), layout);
const auto delta = detail::group_add(k0.delta_share, k1.delta_share);
const auto old_cr_open = detail::group_add(k0.cr_share, k1.cr_share);
const auto old_cr_term = detail::group_mul(delta,
detail::group_scalar(
detail::ic_impl::correction_s(r, k0.lo, k0.hi, k0.input_mask),
layout));
const auto if_false = detail::group_sub(old_cr_open, old_cr_term);
const auto new_target =
detail::group_add(detail::group_mul(delta, cr_g), if_false);
const auto blind = detail::group_from_node<prg>(
dpf::uniform_sample<typename new_raw::interior_node>(), layout);
c0 = blind;
c1 = detail::group_sub(new_target, blind);
}
else
{
const uint64_t delta =
(static_cast<uint64_t>(k0.delta_share)
+ static_cast<uint64_t>(k1.delta_share))
& gmask;
const uint64_t old_cr_open =
(static_cast<uint64_t>(k0.cr_share)
+ static_cast<uint64_t>(k1.cr_share))
& gmask;
const uint64_t old_cr_term = detail::ic_impl::mul_mask(delta,
detail::ic_impl::correction(r, k0.lo, k0.hi, k0.input_mask, gmask),
gmask);
const uint64_t if_false =
(old_cr_open + detail::dcf_impl::neg_m(old_cr_term, gmask)) & gmask;
const uint64_t cr =
detail::ic_impl::correction(r, k0.lo, k0.hi, nmask, gmask);
const uint64_t target =
(detail::ic_impl::mul_mask(delta, cr, gmask) + if_false) & gmask;
const uint64_t blind = detail::dcf_impl::sample_addend_blind(gmask,
[] {
return dpf::uniform_sample<typename new_raw::interior_node>();
});
uint64_t a0 = 0, a1 = 0;
detail::incr::split_cmp_addend(target, gmask, blind, a0, a1);
c0 = static_cast<share_t>(a0);
c1 = static_cast<share_t>(a1);
}
auto out0 = detail::ic_impl::make_side<P0, new_raw, Input, Beta>(
std::move(inner.first), k0.lo, k0.hi, nmask, gmask, k0.delta_share, c0,
k0.delta_coeff, k0.cr_coeff);
auto out1 = detail::ic_impl::make_side<P1, new_raw, Input, Beta>(
std::move(inner.second), k1.lo, k1.hi, nmask, gmask, k1.delta_share, c1,
k1.delta_coeff, k1.cr_coeff);
out0.assigned = k0.assigned;
out1.assigned = k1.assigned;
return std::make_pair(std::move(out0), std::move(out1));
}
template <std::size_t P0, std::size_t P1, typename Key, typename Input,
typename Beta, typename Memo0, typename Memo1, typename InputT,
typename CwProtocol, typename... Specs>
HEDLEY_WARN_UNUSED_RESULT
auto add_output_ds(const ic_key<P0, Key, Input, Beta> & k0,
const ic_key<P1, Key, Input, Beta> & k1, Memo0 & m0, Memo1 & m1, InputT x0,
InputT x1, CwProtocol & proto, Specs &&... specs)
{
auto inner = dpf::add_output_ds(k0.dpf_key, k1.dpf_key, m0, m1, x0, x1, proto,
std::forward<Specs>(specs)...);
using new_raw = typename std::decay_t<decltype(inner.first)>::key_type;
auto out0 = detail::ic_impl::make_side<P0, new_raw, Input, Beta>(
std::move(inner.first), k0.lo, k0.hi, k0.input_mask, k0.group_mask,
k0.delta_share, k0.cr_share, k0.delta_coeff, k0.cr_coeff);
auto out1 = detail::ic_impl::make_side<P1, new_raw, Input, Beta>(
std::move(inner.second), k1.lo, k1.hi, k1.input_mask, k1.group_mask,
k1.delta_share, k1.cr_share, k1.delta_coeff, k1.cr_coeff);
out0.assigned = k0.assigned;
out1.assigned = k1.assigned;
return std::make_pair(std::move(out0), std::move(out1));
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_INTERVAL_HPP__