libdpf/include/dpf/interval.hpp

1309 lines
54 KiB
C++
Raw Normal View History

/// @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__