2026-09-24 20:44:07 -06:00
/// @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–
2026-09-28 05:59:19 -06:00
/// 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.
2026-09-24 20:44:07 -06:00
/// 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"
2026-09-28 05:59:19 -06:00
# include "dpf/grow.hpp"
# include "dpf/grow_ds.hpp"
2026-09-24 20:44:07 -06:00
# 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 { } ;
} ;
2026-09-24 23:18:10 -06:00
/// @brief Spec tag and factory. `dpf::ic(p, q, beta)` builds a pack;
2026-09-24 20:44:07 -06:00
/// `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 { } ;
2026-09-24 23:18:10 -06:00
/// @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
2026-09-24 20:44:07 -06:00
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 ;
2026-09-24 23:18:10 -06:00
using share_type = std : : conditional_t <
detail : : cmp_group_info < dpf : : concrete_type_t < Beta > > : : custom ,
detail : : group_elem , uint64_t > ;
2026-09-24 20:44:07 -06:00
2026-09-28 05:59:19 -06:00
/// @brief Inner comparison key. Eval that accepts a `dpf_key` also accepts
/// this object and reads `dpf_key`.
key_type dpf_key ;
2026-09-24 20:44:07 -06:00
uint64_t lo = 0 ;
uint64_t hi = 0 ;
uint64_t input_mask = 0 ;
uint64_t group_mask = 0 ;
2026-09-24 23:18:10 -06:00
/// @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 { } ;
2026-09-24 20:44:07 -06:00
bool assigned = ! wildcard ;
ic_key ( key_type k , uint64_t lo_in , uint64_t hi_in , uint64_t nmask ,
2026-09-24 23:18:10 -06:00
uint64_t gmask , share_type dshare , share_type cshare , share_type dcoeff ,
share_type ccoeff ) noexcept ( std : : is_nothrow_move_constructible_v < key_type > )
2026-09-28 05:59:19 -06:00
: dpf_key ( std : : move ( k ) )
2026-09-24 20:44:07 -06:00
, 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 ;
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-24 20:44:07 -06:00
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
2026-09-24 23:18:10 -06:00
constexpr int correction_s ( uint64_t r , uint64_t p , uint64_t q ,
uint64_t nmask ) noexcept
2026-09-24 20:44:07 -06:00
{
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 ;
2026-09-24 23:18:10 -06:00
return ( ap > aq ? 1 : 0 ) - ( ap > p ? 1 : 0 )
2026-09-24 20:44:07 -06:00
+ ( aq0 > q0 ? 1 : 0 ) + ( aq = = nmask ? 1 : 0 ) ;
2026-09-24 23:18:10 -06:00
}
/// @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 ) ;
2026-09-24 20:44:07 -06:00
}
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 >
2026-09-24 23:18:10 -06:00
HEDLEY_CONST
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 23:18:10 -06:00
HEDLEY_ALWAYS_INLINE
constexpr uint64_t group_mask_of ( ) noexcept
2026-09-24 20:44:07 -06:00
{
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 > ) ;
}
2026-09-24 23:18:10 -06:00
template < std : : size_t Party , typename Key , typename Input , typename Beta ,
typename Share >
2026-09-24 20:44:07 -06:00
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 ,
2026-09-24 23:18:10 -06:00
Share delta_share , Share cr_share , Share delta_coeff , Share cr_coeff )
2026-09-24 20:44:07 -06:00
{
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 > ;
2026-09-24 23:18:10 -06:00
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
{
2026-09-24 20:44:07 -06:00
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 ) ) ;
2026-09-24 23:18:10 -06:00
}
2026-09-24 20:44:07 -06:00
}
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 ) ;
2026-09-24 23:18:10 -06:00
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 ) ) ) ;
}
2026-09-24 20:44:07 -06:00
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 >
2026-09-28 05:59:19 -06:00
auto eval_one ( const IcKey & k , Query & & x , Memo & memo ,
proof_token * pi = nullptr )
2026-09-24 20:44:07 -06:00
{
if ( ! k . assigned )
throw std : : invalid_argument (
" ic eval: wildcard payload not assigned (call assign_cmp) " ) ;
using in_type = typename IcKey : : input_type ;
2026-09-24 23:18:10 -06:00
using beta = typename IcKey : : beta_type ;
2026-09-24 20:44:07 -06:00
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 ) ;
2026-09-24 23:18:10 -06:00
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 ) ;
} ;
2026-09-28 05:59:19 -06:00
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 ) ) ;
2026-09-24 23:18:10 -06:00
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
{
2026-09-24 20:44:07 -06:00
const uint64_t a = opened_u64 (
2026-09-28 05:59:19 -06:00
detail : : incr : : eval_cmp_point_impl ( k . dpf_key ,
input_from_bits < in_type > ( xp ) , memo , pi ) ,
2026-09-24 20:44:07 -06:00
k . group_mask ) ;
const uint64_t b = opened_u64 (
2026-09-28 05:59:19 -06:00
detail : : incr : : eval_cmp_point_impl ( k . dpf_key ,
input_from_bits < in_type > ( xq ) , memo , pi ) ,
2026-09-24 20:44:07 -06:00
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 ) ) ;
2026-09-24 23:18:10 -06:00
}
2026-09-24 20:44:07 -06:00
}
} // namespace ic_impl
} // namespace detail
2026-09-24 23:18:10 -06:00
/// @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`
2026-09-28 05:59:19 -06:00
/// @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.
2026-09-24 20:44:07 -06:00
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 ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-28 05:59:19 -06:00
/// @param tags optional `verifiable` or `extractable` markers
2026-09-24 23:18:10 -06:00
/// @return the two party keys
2026-09-28 05:59:19 -06:00
/// \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`.
2026-09-24 20:44:07 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename RootSampler ,
typename PadRng ,
typename InputT ,
2026-09-28 05:59:19 -06:00
typename Beta ,
typename . . . Tags >
2026-09-24 20:44:07 -06:00
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf_doerner_shelat ( InputT r0 , InputT r1 ,
2026-09-28 05:59:19 -06:00
ds_randomness < RootSampler , PadRng > rng , const ic_pack < Beta > & spec ,
Tags & & . . . tags )
2026-09-24 20:44:07 -06:00
{
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 ,
2026-09-28 05:59:19 -06:00
std : : move ( rng ) , detail : : ic_impl : : inner_lt ( spec ) ,
std : : forward < Tags > ( tags ) . . . ) ;
2026-09-24 20:44:07 -06:00
return detail : : ic_impl : : finish < input_type > ( r_bits , spec , std : : move ( inner ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-28 05:59:19 -06:00
/// @param tags optional `verifiable` or `extractable` markers
2026-09-24 23:18:10 -06:00
/// @return the two party keys
2026-09-28 05:59:19 -06:00
/// \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`.
2026-09-24 20:44:07 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename RootSampler ,
typename PadRng ,
typename InputT ,
2026-09-28 05:59:19 -06:00
typename Beta ,
typename . . . Tags >
2026-09-24 20:44:07 -06:00
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf_doerner_shelat ( arith_input_t , InputT r0 , InputT r1 ,
2026-09-28 05:59:19 -06:00
ds_randomness < RootSampler , PadRng > rng , const ic_pack < Beta > & spec ,
Tags & & . . . tags )
2026-09-24 20:44:07 -06:00
{
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 > (
2026-09-28 05:59:19 -06:00
arith_input , g0 , g1 , std : : move ( rng ) , detail : : ic_impl : : inner_lt ( spec ) ,
std : : forward < Tags > ( tags ) . . . ) ;
2026-09-24 20:44:07 -06:00
return detail : : ic_impl : : finish < input_type > (
detail : : ic_impl : : bits_of ( r ) , spec , std : : move ( inner ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief XOR shares, sampled from the library entropy source.
/// @return the two party keys
2026-09-28 05:59:19 -06:00
/// \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`.
2026-09-24 20:44:07 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
2026-09-28 05:59:19 -06:00
typename Beta ,
typename . . . Tags >
2026-09-24 20:44:07 -06:00
HEDLEY_WARN_UNUSED_RESULT
2026-09-28 05:59:19 -06:00
auto make_dpf_doerner_shelat ( InputT r0 , InputT r1 , const ic_pack < Beta > & spec ,
Tags & & . . . tags )
2026-09-24 20:44:07 -06:00
{
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 > (
2026-09-28 05:59:19 -06:00
std : : move ( r0 ) , std : : move ( r1 ) , rng , spec ,
std : : forward < Tags > ( tags ) . . . ) ;
2026-09-24 20:44:07 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief Additive shares, sampled from the library entropy source.
/// @return the two party keys
2026-09-28 05:59:19 -06:00
/// \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`.
2026-09-24 20:44:07 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
2026-09-28 05:59:19 -06:00
typename Beta ,
typename . . . Tags >
2026-09-24 20:44:07 -06:00
HEDLEY_WARN_UNUSED_RESULT
auto make_dpf_doerner_shelat ( arith_input_t , InputT r0 , InputT r1 ,
2026-09-28 05:59:19 -06:00
const ic_pack < Beta > & spec , Tags & & . . . tags )
2026-09-24 20:44:07 -06:00
{
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 > (
2026-09-28 05:59:19 -06:00
arith_input , std : : move ( r0 ) , std : : move ( r1 ) , rng , spec ,
std : : forward < Tags > ( tags ) . . . ) ;
2026-09-24 20:44:07 -06:00
}
2026-09-24 23:18:10 -06:00
/// @}
/// @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
2026-09-24 20:44:07 -06:00
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 " ) ;
2026-09-24 23:18:10 -06:00
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 ) ;
2026-09-28 05:59:19 -06:00
assign_cmp ( k0 . dpf_key , k1 . dpf_key ,
2026-09-24 23:18:10 -06:00
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
{
2026-09-24 20:44:07 -06:00
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 ) ;
2026-09-28 05:59:19 -06:00
assign_cmp ( k0 . dpf_key , k1 . dpf_key ,
2026-09-24 20:44:07 -06:00
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 ;
2026-09-24 23:18:10 -06:00
}
2026-09-24 20:44:07 -06:00
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-28 05:59:19 -06:00
/// \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.
2026-09-24 20:44:07 -06:00
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 ) ;
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-28 05:59:19 -06:00
/// \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.
2026-09-24 20:44:07 -06:00
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 " ) ;
}
}
2026-09-24 23:18:10 -06:00
/// @brief Inclusive interval `[from, to]`, with a fresh path memoizer.
2026-09-28 05:59:19 -06:00
/// \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.
2026-09-24 20:44:07 -06:00
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 ) ;
}
2026-09-24 23:18:10 -06:00
/// @}
/// @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
2026-09-28 05:59:19 -06:00
/// \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.
2026-09-24 20:44:07 -06:00
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 ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief Evaluate `[begin, end)`, with a fresh path memoizer.
2026-09-28 05:59:19 -06:00
/// \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.
2026-09-24 20:44:07 -06:00
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 ) ;
}
2026-09-24 23:18:10 -06:00
/// @}
/// @brief Buffer of `n` interval shares.
/// @tparam IcKey interval-containment key type
/// @param n the `n`
/// @return Buffer of `n` interval shares
2026-09-24 20:44:07 -06:00
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 ) ;
}
2026-09-24 23:18:10 -06:00
/// @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`
2026-09-24 20:44:07 -06:00
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 ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @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
/// @{
2026-09-28 05:59:19 -06:00
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
2026-09-24 23:18:10 -06:00
/// @brief XOR mask. `r0 XOR r1` is the secret mask. Each query is
2026-09-24 20:44:07 -06:00
/// returned already combined into the interval share.
2026-09-28 05:59:19 -06:00
/// @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.
2026-09-24 20:44:07 -06:00
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 ) ,
2026-09-28 05:59:19 -06:00
std : : move ( rng ) , spec , dpf : : verifiable { } ) ;
detail : : ic_impl : : geneval_ic_eval ( out , keys . first , keys . second , begin , end ) ;
2026-09-24 20:44:07 -06:00
return out ;
}
2026-09-24 23:18:10 -06:00
/// @brief Additive mask. `r0 + r1` is the secret mask.
2026-09-24 20:44:07 -06:00
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 ) ,
2026-09-28 05:59:19 -06:00
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 > )
2026-09-24 20:44:07 -06:00
{
2026-09-28 05:59:19 -06:00
// Wildcard: keep cr_coeff; concrete cr_share is filled by assign_cmp.
c0 = k0 . cr_share ;
c1 = k1 . cr_share ;
2026-09-24 20:44:07 -06:00
}
2026-09-28 05:59:19 -06:00
else if constexpr ( detail : : cmp_group_info < Beta > : : custom )
2026-09-24 20:44:07 -06:00
{
2026-09-28 05:59:19 -06:00
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 ) ;
2026-09-24 20:44:07 -06:00
}
2026-09-28 05:59:19 -06:00
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 ) ) ;
2026-09-24 20:44:07 -06:00
}
2026-09-28 05:59:19 -06:00
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 ) ) ;
}
2026-09-24 23:18:10 -06:00
2026-09-24 20:44:07 -06:00
} // namespace dpf
# endif // LIBDPF_INCLUDE_DPF_INTERVAL_HPP__