2026-09-24 14:08:32 -06:00
/// @file dpf/geneval.hpp
/// @brief Fused generation and evaluation (Doerner– Shelat on the eval trie).
/// @details `make_dpf` / `make_dpf_doerner_shelat` build a reusable key, then
/// `eval_*` walks it. `geneval_*` does both at once: one correction
/// word per level, opened from the XOR-reduction of the nodes the
/// public query actually expands. While the secret path's parent is
/// still in that trie the word matches the reusable key byte for
/// byte (same roots, same Beaver tape). After the path leaves, the
/// word is uniform and later outputs still reconstruct — off-path
/// nodes are identical across the two parties, so a dummy word
/// cancels.
///
2026-09-24 20:44:07 -06:00
/// Default calls take XOR shares of the point. Tagged with
2026-09-28 05:59:19 -06:00
/// `arith_input`, the point is the ring sum of the two shares. A
/// beaver ripple-carry converts those shares to XOR shares of the
/// sum bits before the walk, so the words match `make_dpf` on that
/// sum. The sum is not opened.
2026-09-24 15:16:21 -06:00
///
/// `geneval_cmp` is the comparison-channel form. The value-correction
/// word is a function of the secret path at every level, so the walk
/// stays live for the whole depth and the opened words match a
/// Doerner– Shelat comparison key. Prefix shares are
/// `eval_point(cmp, ...)` at each endpoint. Piecewise-cubic evaluation
/// on top of that is `grotto::geneval_offset_horner`.
2026-09-28 05:59:19 -06:00
/// @note The per-level correction opening follows Jack Doerner and abhi shelat, CCS 2017 (ePrint 2017/827). They return a reusable key. This function opens a word only for nodes on the public query trie and, with a local pad tape, sends nothing.
2026-09-24 14:08:32 -06:00
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
# ifndef LIBDPF_INCLUDE_DPF_GENEVAL_HPP__
# define LIBDPF_INCLUDE_DPF_GENEVAL_HPP__
# include <algorithm>
# include <cstddef>
# include <cstdint>
# include <cstring>
# include <iterator>
# include <stdexcept>
# include <tuple>
# include <type_traits>
# include <utility>
# include <vector>
# include "hedley/hedley.h"
# include "simde/simde/x86/avx2.h"
# include "dpf/aligned_allocator.hpp"
# include "dpf/doerner_shelat.hpp"
2026-09-24 15:16:21 -06:00
# include "dpf/eval_target.hpp"
2026-09-24 14:08:32 -06:00
# include "dpf/leaf_node.hpp"
2026-09-28 05:59:19 -06:00
# include "dpf/verifiable.hpp"
2026-09-24 14:08:32 -06:00
namespace dpf
{
2026-09-24 23:18:10 -06:00
/// @brief Shares and the correction words opened along the query trie.
/// @details `correction_words[i]` / `correction_advice[i]` match a reusable key at
2026-09-24 14:08:32 -06:00
/// the same target for every `i < live_levels`. `leaf_live` means the
/// target's leaf was in the trie, so `leaf` is that key's leaf word.
2026-09-24 23:18:10 -06:00
/// @tparam Output output
/// @tparam Leaf leaf
2026-09-24 14:08:32 -06:00
template < typename Output , typename Leaf >
struct geneval_result
{
std : : vector < Output > party0 ;
std : : vector < Output > party1 ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
2026-09-24 14:08:32 -06:00
std : : vector < simde__m128i , aligned_allocator < simde__m128i > > correction_words ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic pop )
2026-09-24 14:08:32 -06:00
std : : vector < uint8_t > correction_advice ;
std : : size_t live_levels = 0 ;
bool leaf_live = false ;
Leaf leaf { } ;
2026-09-28 05:59:19 -06:00
/// @brief Party 0 / 1 VDPF tokens over the live eval trie (empty when unused).
proof_token proof0 { } ;
proof_token proof1 { } ;
2026-09-24 14:08:32 -06:00
} ;
namespace detail
{
template < typename T >
HEDLEY_ALWAYS_INLINE
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
T geneval_mod_add ( T a , T b ) noexcept
{
using U = std : : make_unsigned_t < T > ;
U sum = static_cast < U > ( static_cast < U > ( a ) + static_cast < U > ( b ) ) ;
T out ;
std : : memcpy ( & out , & sum , sizeof ( out ) ) ;
return out ;
}
template < typename T >
T geneval_flipped ( T x )
{
utils : : flip_msb_if_signed_integral ( x ) ;
return x ;
}
2026-09-24 23:18:10 -06:00
/// @brief Leaf-node id of an already MSB-flipped input. The id is the high
2026-09-24 14:08:32 -06:00
/// `depth` bits; the low `lg(outputs_per_leaf)` bits select the lane.
2026-09-24 23:18:10 -06:00
/// @tparam Dpf dpf
/// @param x the `x`
/// @return Leaf-node id of an already MSB-flipped input
2026-09-24 14:08:32 -06:00
template < typename Dpf >
uint64_t geneval_leaf_id ( typename Dpf : : input_type x )
{
return static_cast < uint64_t > ( utils : : get_from_node < Dpf > ( x ) ) ;
}
inline uint64_t geneval_prefix ( uint64_t leaf , std : : size_t depth , std : : size_t bits )
{
if ( bits = = 0 )
return 0 ;
if ( bits > = depth )
return leaf ;
return leaf > > ( depth - bits ) ;
}
inline bool geneval_any_prefix ( const std : : vector < uint64_t > & leaves ,
std : : size_t depth , uint64_t id , std : : size_t bits )
{
if ( leaves . empty ( ) )
return false ;
if ( bits = = 0 )
return true ;
const std : : size_t sh = depth - bits ;
const uint64_t lo = ( sh > = 64 ) ? 0 : ( id < < sh ) ;
auto it = std : : lower_bound ( leaves . begin ( ) , leaves . end ( ) , lo ) ;
if ( it = = leaves . end ( ) )
return false ;
return geneval_prefix ( * it , depth , bits ) = = id ;
}
template < typename Output , typename Leaf >
geneval_result < Output , Leaf > geneval_empty_result ( )
{
geneval_result < Output , Leaf > out ;
std : : memset ( & out . leaf , 0 , sizeof ( out . leaf ) ) ;
return out ;
}
template < typename InteriorPRG ,
typename ExteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
2026-09-24 23:18:10 -06:00
auto geneval_run ( bool arith , bool arith_out , InputT x0 , InputT x1 ,
2026-09-24 20:44:07 -06:00
const std : : vector < InputT > & queries , RootSampler & root_sampler ,
2026-09-24 23:18:10 -06:00
PadRng & pads , OutputT y0 , OutputT y1 = OutputT { } )
2026-09-24 14:08:32 -06:00
{
static_assert ( std : : is_integral_v < InputT > ,
" geneval input shares are an integral domain " ) ;
static_assert ( ! dpf : : is_wildcard_v < OutputT > ,
" geneval output is concrete; assign a wildcard leaf on a key " ) ;
static_assert ( utils : : bitlength_of_v < InputT > < = 64 ,
" geneval leaf ids are 64-bit " ) ;
using dpf_type = utils : : dpf_type_t < InteriorPRG , ExteriorPRG , InputT , OutputT > ;
using node = typename dpf_type : : interior_node ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
2026-09-24 14:08:32 -06:00
using leaf_node = leaf_node_t < node , OutputT > ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic pop )
using outputs_tuple = std : : tuple < OutputT > ;
2026-09-24 14:08:32 -06:00
constexpr std : : size_t depth = dpf_type : : depth ;
if ( queries . empty ( ) )
return geneval_empty_result < OutputT , leaf_node > ( ) ;
if ( queries . size ( ) > ( std : : size_t { 1 } < < 22 ) )
throw std : : length_error ( " geneval query is too large " ) ;
2026-09-24 20:44:07 -06:00
local_cw_protocol < PadRng > proto { pads } ;
2026-09-24 14:08:32 -06:00
InputT x0c = x0 ;
InputT x1c = x1 ;
2026-09-24 20:44:07 -06:00
proto . encode_walk_shares ( x0c , x1c , arith ) ;
2026-09-28 05:59:19 -06:00
// Keep the secret path on share-bits. Do not form a clear alpha for leaf
// placement, live levels, or correction seeds.
2026-09-24 14:08:32 -06:00
std : : vector < InputT > flipped ;
flipped . reserve ( queries . size ( ) ) ;
std : : vector < uint64_t > leaves ;
leaves . reserve ( queries . size ( ) ) ;
for ( const InputT & q : queries )
{
InputT fq = geneval_flipped ( q ) ;
flipped . push_back ( fq ) ;
leaves . push_back ( geneval_leaf_id < dpf_type > ( fq ) ) ;
}
std : : vector < uint64_t > unique_leaves = leaves ;
std : : sort ( unique_leaves . begin ( ) , unique_leaves . end ( ) ) ;
unique_leaves . erase ( std : : unique ( unique_leaves . begin ( ) , unique_leaves . end ( ) ) ,
unique_leaves . end ( ) ) ;
if ( unique_leaves . size ( ) > ( std : : size_t { 1 } < < 20 ) )
throw std : : length_error ( " geneval trie is too large " ) ;
constexpr auto to_int = utils : : to_integral_type < InputT > { } ;
2026-09-24 23:18:10 -06:00
using tree = dpf : : tree_traits < InteriorPRG > ;
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
node roots [ 2 ] ;
HEDLEY_PRAGMA ( GCC diagnostic pop )
tree : : root_init ( roots , [ & ] ( ) - > node {
return static_cast < node > ( root_sampler ( ) ) ;
} ) ;
const node root0 = roots [ 0 ] ;
const node root1 = roots [ 1 ] ;
2026-09-24 14:08:32 -06:00
struct slot
{
uint64_t id ;
node s0 ;
node s1 ;
} ;
std : : vector < slot > frontier ;
frontier . push_back ( slot { 0 , root0 , root1 } ) ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
2026-09-24 14:08:32 -06:00
geneval_result < OutputT , leaf_node > result ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic pop )
2026-09-24 14:08:32 -06:00
std : : memset ( & result . leaf , 0 , sizeof ( result . leaf ) ) ;
result . correction_words . reserve ( depth ) ;
result . correction_advice . reserve ( depth ) ;
2026-09-28 05:59:19 -06:00
result . proof0 = detail : : vdpf : : zero_proof ( ) ;
result . proof1 = detail : : vdpf : : zero_proof ( ) ;
2026-09-24 14:08:32 -06:00
auto mask = dpf_type : : msb_mask ;
bool still_live = true ;
2026-09-28 05:59:19 -06:00
uint64_t secret_prefix = 0 ;
2026-09-24 14:08:32 -06:00
for ( std : : size_t level = 0 ; level < depth ; + + level , mask > > = 1 )
{
const uint8_t bit0 = static_cast < uint8_t > ( ! ! ( to_int ( mask ) & to_int ( x0c ) ) ) ;
const uint8_t bit1 = static_cast < uint8_t > ( ! ! ( to_int ( mask ) & to_int ( x1c ) ) ) ;
2026-09-28 05:59:19 -06:00
const uint64_t parent_id = secret_prefix ;
2026-09-24 23:18:10 -06:00
const bool is_last = tree : : is_last_level ( level , depth ) ;
2026-09-24 14:08:32 -06:00
node L0 = simde_mm_setzero_si128 ( ) ;
node R0 = simde_mm_setzero_si128 ( ) ;
node L1 = simde_mm_setzero_si128 ( ) ;
node R1 = simde_mm_setzero_si128 ( ) ;
bool level_live = false ;
struct exp
{
uint64_t id ;
node s0 , s1 , L0 , R0 , L1 , R1 ;
} ;
std : : vector < exp > exps ;
exps . reserve ( frontier . size ( ) ) ;
for ( const slot & n : frontier )
{
if ( n . id = = parent_id )
level_live = true ;
2026-09-24 23:18:10 -06:00
const auto c0 = tree : : expand ( n . s0 , is_last ) ;
const auto c1 = tree : : expand ( n . s1 , is_last ) ;
2026-09-24 14:08:32 -06:00
L0 = ds_xor ( L0 , c0 [ 0 ] ) ;
R0 = ds_xor ( R0 , c0 [ 1 ] ) ;
L1 = ds_xor ( L1 , c1 [ 0 ] ) ;
R1 = ds_xor ( R1 , c1 [ 1 ] ) ;
exps . push_back ( exp { n . id , n . s0 , n . s1 , c0 [ 0 ] , c0 [ 1 ] , c1 [ 0 ] , c1 [ 1 ] } ) ;
}
node cw ;
uint8_t advice ;
if ( still_live & & level_live )
{
auto blinds = proto . prepare_level ( L0 , R0 , bit0 , L1 , R1 , bit1 ) ;
auto opened = proto . open_cw ( blinds ) ;
cw = opened . first ;
advice = opened . second ;
2026-09-24 23:18:10 -06:00
if constexpr ( tree : : is_half_tree )
{
if ( ! is_last )
advice = 0 ;
}
2026-09-24 14:08:32 -06:00
+ + result . live_levels ;
}
else
{
still_live = false ;
cw = pads . block ( ) ;
2026-09-24 23:18:10 -06:00
if constexpr ( tree : : is_half_tree )
{
if ( ! is_last )
{
advice = 0 ;
}
else
{
const uint8_t t0 = static_cast < uint8_t > ( pads . bit ( ) & 1u ) ;
const uint8_t t1 = static_cast < uint8_t > ( pads . bit ( ) & 1u ) ;
advice = static_cast < uint8_t > ( ( t1 < < 1 ) | t0 ) ;
}
}
else
{
const uint8_t t0 = static_cast < uint8_t > ( pads . bit ( ) & 1u ) ;
const uint8_t t1 = static_cast < uint8_t > ( pads . bit ( ) & 1u ) ;
advice = static_cast < uint8_t > ( ( t1 < < 1 ) | t0 ) ;
}
2026-09-24 14:08:32 -06:00
}
result . correction_words . push_back ( cw ) ;
result . correction_advice . push_back ( advice ) ;
2026-09-24 23:18:10 -06:00
const node cw0 = tree : : pack_cw ( cw , advice , false , is_last ) ;
const node cw1 = tree : : pack_cw ( cw , advice , true , is_last ) ;
2026-09-24 14:08:32 -06:00
const std : : size_t child_bits = level + 1 ;
2026-09-28 05:59:19 -06:00
const uint8_t secret_bit = static_cast < uint8_t > ( ( bit0 ^ bit1 ) & 1u ) ;
secret_prefix = ( secret_prefix < < 1 ) | secret_bit ;
2026-09-24 14:08:32 -06:00
std : : vector < slot > next ;
next . reserve ( exps . size ( ) * 2 ) ;
for ( const exp & e : exps )
{
const uint64_t left = e . id < < 1 ;
const uint64_t right = left | 1ull ;
if ( geneval_any_prefix ( unique_leaves , depth , left , child_bits ) )
{
next . push_back ( slot { left ,
dpf : : xor_if_lo_bit ( e . L0 , cw0 , e . s0 ) ,
dpf : : xor_if_lo_bit ( e . L1 , cw0 , e . s1 ) } ) ;
}
if ( geneval_any_prefix ( unique_leaves , depth , right , child_bits ) )
{
next . push_back ( slot { right ,
dpf : : xor_if_lo_bit ( e . R0 , cw1 , e . s0 ) ,
dpf : : xor_if_lo_bit ( e . R1 , cw1 , e . s1 ) } ) ;
}
}
2026-09-28 05:59:19 -06:00
// Fold every live child into both parties' VDPF tokens.
if ( ! next . empty ( ) )
{
cs_block cs { } ;
bool have_cs = false ;
for ( const slot & c : next )
{
if ( c . id = = secret_prefix )
{
// Prefix is the share-bit path accumulated above — not a
// fresh xor_input_shares of the point for leaf placement.
cs = detail : : vdpf : : make_cs ( level , c . id , c . s0 , c . s1 ) ;
have_cs = true ;
break ;
}
}
if ( ! have_cs )
cs = detail : : vdpf : : make_cs ( level , next [ 0 ] . id , next [ 0 ] . s0 ,
next [ 0 ] . s1 ) ;
for ( const slot & c : next )
{
detail : : vdpf : : fold_node ( result . proof0 , level , c . id , c . s0 , cs ) ;
detail : : vdpf : : fold_node ( result . proof1 , level , c . id , c . s1 , cs ) ;
}
}
2026-09-24 14:08:32 -06:00
frontier = std : : move ( next ) ;
}
2026-09-28 05:59:19 -06:00
const uint64_t secret_leaf = secret_prefix ;
2026-09-24 14:08:32 -06:00
result . leaf_live = geneval_any_prefix ( unique_leaves , depth , secret_leaf , depth ) ;
if ( result . leaf_live )
{
const slot * on = nullptr ;
for ( const slot & n : frontier )
{
if ( n . id = = secret_leaf )
{
on = & n ;
break ;
}
}
if ( on = = nullptr )
throw std : : logic_error ( " geneval: secret leaf missing from trie " ) ;
2026-09-24 23:18:10 -06:00
if ( arith_out )
{
const uint8_t t0 = static_cast < uint8_t > ( dpf : : get_lo_bit ( on - > s0 ) ) ;
const uint8_t t1 = static_cast < uint8_t > ( dpf : : get_lo_bit ( on - > s1 ) ) ;
result . leaf = proto . template open_arith_leaf < ExteriorPRG , 0 , outputs_tuple > (
dpf : : unset_lo_2bits ( on - > s0 ) , dpf : : unset_lo_2bits ( on - > s1 ) , t0 , t1 ,
2026-09-28 05:59:19 -06:00
y0 , y1 , std : : size_t { 0 } , x0c , x1c ) ;
2026-09-24 23:18:10 -06:00
}
else
{
2026-09-28 05:59:19 -06:00
// Mux / reconstruct only inside the leaf protocol hook.
proto . open_leaf_group ( x0c , x1c , [ & ] ( InputT sx0 , InputT sx1 ) {
const InputT x = utils : : xor_input_shares ( sx0 , sx1 ) ;
const bool sign0 = dpf : : get_lo_bit ( on - > s0 ) ;
auto built = dpf : : make_leaves < ExteriorPRG > ( x ,
dpf : : unset_lo_2bits ( on - > s0 ) , dpf : : unset_lo_2bits ( on - > s1 ) ,
sign0 , std : : size_t { 0 } , y0 ) ;
result . leaf = std : : get < 0 > ( built . first . first ) ;
} ) ;
2026-09-24 23:18:10 -06:00
}
2026-09-24 14:08:32 -06:00
}
result . party0 . reserve ( flipped . size ( ) ) ;
result . party1 . reserve ( flipped . size ( ) ) ;
for ( std : : size_t i = 0 ; i < flipped . size ( ) ; + + i )
{
const uint64_t id = leaves [ i ] ;
const slot * n = nullptr ;
for ( const slot & s : frontier )
{
if ( s . id = = id )
{
n = & s ;
break ;
}
}
if ( n = = nullptr )
throw std : : logic_error ( " geneval: query leaf missing from trie " ) ;
auto share0 = dpf_type : : template traverse_exterior < 0 > ( n - > s0 , result . leaf ) ;
auto share1 = dpf_type : : template traverse_exterior < 0 > ( n - > s1 , result . leaf ) ;
const auto lane = static_cast < std : : size_t > ( to_int ( flipped [ i ] ) ) ;
result . party0 . push_back ( extract_leaf < node , OutputT > ( share0 , lane ) ) ;
result . party1 . push_back ( extract_leaf < node , OutputT > ( share1 , lane ) ) ;
}
return result ;
}
2026-09-24 23:18:10 -06:00
template < typename InteriorPRG ,
typename ExteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
auto geneval_run ( bool arith , InputT x0 , InputT x1 ,
const std : : vector < InputT > & queries , RootSampler & root_sampler ,
PadRng & pads , OutputT y )
{
return geneval_run < InteriorPRG , ExteriorPRG > ( arith , false , x0 , x1 , queries ,
root_sampler , pads , y , OutputT { } ) ;
}
2026-09-24 20:44:07 -06:00
template < typename InteriorPRG ,
typename ExteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
auto geneval_run ( InputT x0 , InputT x1 , const std : : vector < InputT > & queries ,
RootSampler & root_sampler , PadRng & pads , OutputT y )
{
2026-09-24 23:18:10 -06:00
return geneval_run < InteriorPRG , ExteriorPRG > ( false , false , x0 , x1 , queries ,
root_sampler , pads , y , OutputT { } ) ;
2026-09-24 20:44:07 -06:00
}
2026-09-24 14:08:32 -06:00
template < typename InputT >
InputT geneval_from_bits ( uint64_t bits )
{
using U = std : : make_unsigned_t < InputT > ;
U u = static_cast < U > ( bits ) ;
InputT out ;
std : : memcpy ( & out , & u , sizeof ( out ) ) ;
return out ;
}
template < typename InputT >
bool geneval_out_of_order ( InputT from , InputT to )
{
// Numeric order. An unsigned compare of a signed value treats a negative
// `from` as larger than a positive `to`, and would reject `[-1, 1]`.
if constexpr ( std : : is_signed_v < InputT > )
return from > to ;
else
return utils : : to_integral_type < InputT > { } ( from )
> utils : : to_integral_type < InputT > { } ( to ) ;
}
template < typename InputT >
std : : vector < InputT > geneval_full_domain ( )
{
constexpr std : : size_t bitlen = utils : : bitlength_of_v < InputT > ;
if ( bitlen > 20 )
throw std : : length_error ( " geneval_full domain is too large " ) ;
const uint64_t n = uint64_t { 1 } < < bitlen ;
std : : vector < InputT > qs ( static_cast < std : : size_t > ( n ) ) ;
// Index `i` is the input's bit pattern, including the sign bit. A
// narrowing cast of `i` to a signed type is implementation-defined.
for ( uint64_t i = 0 ; i < n ; + + i )
qs [ static_cast < std : : size_t > ( i ) ] = geneval_from_bits < InputT > ( i ) ;
return qs ;
}
template < typename InputT >
std : : vector < InputT > geneval_inclusive ( InputT from , InputT to )
{
if ( geneval_out_of_order ( from , to ) )
{
throw std : : invalid_argument ( " geneval_interval: from > to " ) ;
}
std : : vector < InputT > qs ;
InputT q = from ;
const InputT one = utils : : make_from_integral_value < InputT > { } ( 1 ) ;
for ( ; ; )
{
qs . push_back ( q ) ;
if ( q = = to )
break ;
q = geneval_mod_add ( q , one ) ;
if ( qs . size ( ) > ( std : : size_t { 1 } < < 22 ) )
throw std : : length_error ( " geneval_interval is too large " ) ;
}
return qs ;
}
} // namespace detail
2026-09-24 23:18:10 -06:00
/// @name Point geneval
/// @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 OutputT output type
/// @tparam RootSampler sampler for the Doerner– Shelat root seed
/// @tparam PadRng pad stream for the Doerner– Shelat protocol
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @param query the query point
/// @param rng the Doerner– Shelat randomness tapes
/// @{
/// @brief The secret point is `x0 XOR x1`.
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @param query the query point
/// @param rng the Doerner– Shelat randomness tapes
/// @param y the payload
/// @return the opened shares and correction words
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
auto geneval_point ( InputT x0 , InputT x1 , InputT query ,
ds_randomness < RootSampler , PadRng > rng , OutputT y )
{
2026-09-24 23:18:10 -06:00
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( false , false , x0 , x1 ,
std : : vector < InputT > { query } , rng . root , rng . pad , y , OutputT { } ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief The secret point is `x0 + x1`.
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @param query the query point
/// @param rng the Doerner– Shelat randomness tapes
/// @param y the payload
/// @return the opened shares and correction words
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
2026-09-24 20:44:07 -06:00
auto geneval_point ( arith_input_t , InputT x0 , InputT x1 , InputT query ,
2026-09-24 14:08:32 -06:00
ds_randomness < RootSampler , PadRng > rng , OutputT y )
{
2026-09-24 23:18:10 -06:00
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( true , false , x0 , x1 ,
std : : vector < InputT > { query } , rng . root , rng . pad , y , OutputT { } ) ;
}
/// @brief XOR-index shares, additively shared payload `y0 + y1 = β`.
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @param query the query point
/// @param rng the Doerner– Shelat randomness tapes
/// @param y0 party 0's share of the payload
/// @param y1 party 1's share of the payload
/// @return the opened shares and correction words
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 23:18:10 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
auto geneval_point ( arith_output_t , InputT x0 , InputT x1 , InputT query ,
ds_randomness < RootSampler , PadRng > rng , OutputT y0 , OutputT y1 )
{
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( false , true , x0 , x1 ,
std : : vector < InputT > { query } , rng . root , rng . pad , y0 , y1 ) ;
}
/// @brief Additive index and additive payload shares.
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @param query the query point
/// @param rng the Doerner– Shelat randomness tapes
/// @param y0 party 0's share of the payload
/// @param y1 party 1's share of the payload
/// @return the opened shares and correction words
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 23:18:10 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
auto geneval_point ( arith_input_t , arith_output_t , InputT x0 , InputT x1 ,
InputT query , ds_randomness < RootSampler , PadRng > rng , OutputT y0 , OutputT y1 )
{
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( true , true , x0 , x1 ,
std : : vector < InputT > { query } , rng . root , rng . pad , y0 , y1 ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-24 23:18:10 -06:00
/// @}
/// @brief Geneval on the inclusive interval `[from, to]`.
/// @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 OutputT output type
/// @tparam RootSampler sampler for the Doerner– Shelat root seed
/// @tparam PadRng pad stream for the Doerner– Shelat protocol
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param from the inclusive start of the range
/// @param to the `to`
/// @param rng the Doerner– Shelat randomness tapes
/// @param y the `y`
/// @return Geneval on the inclusive interval `[from, to]`
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
2026-09-24 20:44:07 -06:00
auto geneval_interval ( InputT x0 , InputT x1 , InputT from , InputT to ,
2026-09-24 14:08:32 -06:00
ds_randomness < RootSampler , PadRng > rng , OutputT y )
{
2026-09-24 20:44:07 -06:00
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( false , x0 , x1 ,
detail : : geneval_inclusive ( from , to ) , rng . root , rng . pad , y ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
2026-09-24 20:44:07 -06:00
auto geneval_interval ( arith_input_t , InputT x0 , InputT x1 , InputT from ,
2026-09-24 14:08:32 -06:00
InputT to , ds_randomness < RootSampler , PadRng > rng , OutputT y )
{
2026-09-24 20:44:07 -06:00
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( true , x0 , x1 ,
detail : : geneval_inclusive ( from , to ) , rng . root , rng . pad , y ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-28 05:59:19 -06:00
/// @brief Geneval on the whole domain.
/// @details Materializes the query list. Domains wider than 20 bits refuse so
/// a caller does not allocate a `2^n` vector by accident. Prefer the
/// buffer overloads when writing into a pre-sized output scratch.
2026-09-24 23:18:10 -06:00
/// @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 OutputT output type
/// @tparam RootSampler sampler for the Doerner– Shelat root seed
/// @tparam PadRng pad stream for the Doerner– Shelat protocol
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param rng the Doerner– Shelat randomness tapes
/// @param y the `y`
/// @return Geneval on the whole domain
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
2026-09-24 20:44:07 -06:00
typename PadRng >
2026-09-24 14:08:32 -06:00
HEDLEY_WARN_UNUSED_RESULT
2026-09-24 20:44:07 -06:00
auto geneval_full ( InputT x0 , InputT x1 ,
ds_randomness < RootSampler , PadRng > rng , OutputT y )
2026-09-24 14:08:32 -06:00
{
2026-09-24 20:44:07 -06:00
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( false , x0 , x1 ,
2026-09-24 14:08:32 -06:00
detail : : geneval_full_domain < InputT > ( ) , rng . root , rng . pad , y ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
2026-09-24 20:44:07 -06:00
auto geneval_full ( arith_input_t , InputT x0 , InputT x1 ,
2026-09-24 14:08:32 -06:00
ds_randomness < RootSampler , PadRng > rng , OutputT y )
{
2026-09-24 20:44:07 -06:00
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( true , x0 , x1 ,
detail : : geneval_full_domain < InputT > ( ) , rng . root , rng . pad , y ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief Geneval on a public sequence, in the order given.
/// @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 OutputT output type
/// @tparam RootSampler sampler for the Doerner– Shelat root seed
/// @tparam PadRng pad stream for the Doerner– Shelat protocol
/// @tparam ForwardIterator forward iterator type
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param begin the iterator to the first query
/// @param end the iterator past the last query
/// @param rng the Doerner– Shelat randomness tapes
/// @param y the `y`
/// @return Geneval on a public sequence, in the order given
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng ,
2026-09-24 20:44:07 -06:00
typename ForwardIterator >
2026-09-24 14:08:32 -06:00
HEDLEY_WARN_UNUSED_RESULT
2026-09-24 20:44:07 -06:00
auto geneval_sequence ( InputT x0 , InputT x1 , ForwardIterator begin ,
ForwardIterator end , ds_randomness < RootSampler , PadRng > rng , OutputT y )
2026-09-24 14:08:32 -06:00
{
std : : vector < InputT > qs ( begin , end ) ;
2026-09-24 20:44:07 -06:00
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( false , x0 , x1 ,
std : : move ( qs ) , rng . root , rng . pad , y ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-28 05:59:19 -06:00
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng ,
typename ForwardIterator >
HEDLEY_WARN_UNUSED_RESULT
2026-09-24 20:44:07 -06:00
auto geneval_sequence ( arith_input_t , InputT x0 , InputT x1 ,
2026-09-24 14:08:32 -06:00
ForwardIterator begin , ForwardIterator end ,
ds_randomness < RootSampler , PadRng > rng , OutputT y )
{
2026-09-24 20:44:07 -06:00
std : : vector < InputT > qs ( begin , end ) ;
return detail : : geneval_run < InteriorPRG , ExteriorPRG > ( true , x0 , x1 ,
std : : move ( qs ) , rng . root , rng . pad , y ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief Opened comparison key material and one prefix share per endpoint.
/// @details `live_levels` is the full depth: a comparison value word depends on the
2026-09-24 15:16:21 -06:00
/// secret path at every level, so there is no early dummy-word tail.
struct geneval_cmp_result
{
std : : vector < uint64_t > party0 ;
std : : vector < uint64_t > party1 ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
2026-09-24 15:16:21 -06:00
std : : vector < simde__m128i , aligned_allocator < simde__m128i > > correction_words ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic pop )
2026-09-24 15:16:21 -06:00
std : : vector < uint8_t > correction_advice ;
std : : vector < uint64_t > value_cw ;
2026-09-24 20:44:07 -06:00
std : : vector < uint64_t > tail_cw ;
2026-09-24 15:16:21 -06:00
uint64_t cw_last = 0 ;
uint64_t addend0 = 0 ;
uint64_t addend1 = 0 ;
uint64_t mask = 0 ;
std : : size_t live_levels = 0 ;
2026-09-28 05:59:19 -06:00
proof_token proof0 { } ;
proof_token proof1 { } ;
2026-09-24 15:16:21 -06:00
} ;
2026-09-24 23:18:10 -06:00
/// @name Comparison geneval
/// @tparam InputT input domain type
/// @tparam ForwardIterator forward iterator type
/// @tparam RootSampler sampler for the Doerner– Shelat root seed
/// @tparam PadRng pad stream for the Doerner– Shelat protocol
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @param begin the iterator to the first query
/// @param end the iterator past the last query
/// @param rng the Doerner– Shelat randomness tapes
/// @return the opened comparison shares
/// @{
/// @brief `x0 XOR x1` is the secret point, in the same share convention as
/// `geneval_point`. `spec` is an `lt` / `leq` / `gt` / `geq` pack. Each
/// endpoint is returned in order as the two parties' `eval_point(cmp, ...)`
/// shares. An empty range opens nothing.
/// @tparam Spec comparison or interval specification
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @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 comparison specification
2026-09-24 15:16:21 -06:00
template < typename InputT ,
typename ForwardIterator ,
typename RootSampler ,
typename PadRng ,
typename Spec >
HEDLEY_WARN_UNUSED_RESULT
geneval_cmp_result geneval_cmp ( InputT x0 , InputT x1 ,
ForwardIterator begin , ForwardIterator end ,
ds_randomness < RootSampler , PadRng > rng , Spec spec )
{
geneval_cmp_result out ;
if ( begin = = end )
return out ;
auto keys = make_dpf_doerner_shelat ( std : : move ( x0 ) , std : : move ( x1 ) ,
2026-09-28 05:59:19 -06:00
std : : move ( rng ) , std : : move ( spec ) , dpf : : verifiable { } ) ;
2026-09-24 15:16:21 -06:00
const auto & k0 = keys . first ;
const auto & k1 = keys . second ;
using key_type = std : : decay_t < decltype ( k0 ) > ;
constexpr std : : size_t depth = key_type : : depth ;
out . live_levels = depth ;
out . mask = k0 . cmp ( ) . mask ;
out . cw_last = k0 . cw_last ( ) ;
out . addend0 = k0 . cmp_addend ( ) . raw ( ) ;
out . addend1 = k1 . cmp_addend ( ) . raw ( ) ;
out . correction_words . resize ( depth ) ;
out . correction_advice . resize ( depth ) ;
2026-09-24 20:44:07 -06:00
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 . 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 . tail_cw ( z ) ;
}
else
out . value_cw . resize ( depth ) ;
2026-09-24 15:16:21 -06:00
for ( std : : size_t level = 0 ; level < depth ; + + level )
{
out . correction_words [ level ] = k0 . correction_word ( level ) ;
out . correction_advice [ level ] = static_cast < uint8_t > ( k0 . correction_advice ( level ) ) ;
2026-09-24 20:44:07 -06:00
if constexpr ( key_type : : cmp_block = = 0 )
out . value_cw [ level ] = k0 . value_cw ( level ) ;
}
2026-09-28 05:59:19 -06:00
detail : : vdpf : : init_proof ( out . proof0 , k0 ) ;
detail : : vdpf : : init_proof ( out . proof1 , k1 ) ;
auto path0 = make_basic_path_memoizer ( k0 ) ;
auto path1 = make_basic_path_memoizer ( k1 ) ;
2026-09-24 20:44:07 -06:00
for ( auto it = begin ; it ! = end ; + + it )
{
2026-09-28 05:59:19 -06:00
out . party0 . push_back (
detail : : incr : : eval_cmp_point_impl ( k0 , * it , path0 , & out . proof0 ) . raw ( ) ) ;
out . party1 . push_back (
detail : : incr : : eval_cmp_point_impl ( k1 , * it , path1 , & out . proof1 ) . raw ( ) ) ;
2026-09-24 20:44:07 -06:00
}
2026-09-28 05:59:19 -06:00
detail : : vdpf : : fold_output_binding ( out . proof0 , k0 ) ;
detail : : vdpf : : fold_output_binding ( out . proof1 , k1 ) ;
2026-09-24 20:44:07 -06:00
return out ;
}
2026-09-24 23:18:10 -06:00
/// @brief Additive shares of the point (`x0 + x1`).
/// @tparam Spec comparison or interval specification
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @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 comparison specification
2026-09-24 20:44:07 -06:00
template < typename InputT ,
typename ForwardIterator ,
typename RootSampler ,
typename PadRng ,
typename Spec >
HEDLEY_WARN_UNUSED_RESULT
geneval_cmp_result geneval_cmp ( arith_input_t , InputT x0 , InputT x1 ,
ForwardIterator begin , ForwardIterator end ,
ds_randomness < RootSampler , PadRng > rng , Spec spec )
{
geneval_cmp_result out ;
if ( begin = = end )
return out ;
auto keys = make_dpf_doerner_shelat ( arith_input , std : : move ( x0 ) , std : : move ( x1 ) ,
2026-09-28 05:59:19 -06:00
std : : move ( rng ) , std : : move ( spec ) , dpf : : verifiable { } ) ;
2026-09-24 20:44:07 -06:00
const auto & k0 = keys . first ;
const auto & k1 = keys . second ;
using key_type = std : : decay_t < decltype ( k0 ) > ;
constexpr std : : size_t depth = key_type : : depth ;
out . live_levels = depth ;
out . mask = k0 . cmp ( ) . mask ;
out . cw_last = k0 . cw_last ( ) ;
out . addend0 = k0 . cmp_addend ( ) . raw ( ) ;
out . addend1 = k1 . 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 . 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 . tail_cw ( z ) ;
}
else
out . value_cw . resize ( depth ) ;
for ( std : : size_t level = 0 ; level < depth ; + + level )
{
out . correction_words [ level ] = k0 . correction_word ( level ) ;
out . correction_advice [ level ] = static_cast < uint8_t > ( k0 . correction_advice ( level ) ) ;
if constexpr ( key_type : : cmp_block = = 0 )
out . value_cw [ level ] = k0 . value_cw ( level ) ;
2026-09-24 15:16:21 -06:00
}
2026-09-28 05:59:19 -06:00
detail : : vdpf : : init_proof ( out . proof0 , k0 ) ;
detail : : vdpf : : init_proof ( out . proof1 , k1 ) ;
auto path0 = make_basic_path_memoizer ( k0 ) ;
auto path1 = make_basic_path_memoizer ( k1 ) ;
2026-09-24 15:16:21 -06:00
for ( auto it = begin ; it ! = end ; + + it )
{
2026-09-28 05:59:19 -06:00
out . party0 . push_back (
detail : : incr : : eval_cmp_point_impl ( k0 , * it , path0 , & out . proof0 ) . raw ( ) ) ;
out . party1 . push_back (
detail : : incr : : eval_cmp_point_impl ( k1 , * it , path1 , & out . proof1 ) . raw ( ) ) ;
2026-09-24 15:16:21 -06:00
}
2026-09-28 05:59:19 -06:00
detail : : vdpf : : fold_output_binding ( out . proof0 , k0 ) ;
detail : : vdpf : : fold_output_binding ( out . proof1 , k1 ) ;
2026-09-24 15:16:21 -06:00
return out ;
}
2026-09-24 23:18:10 -06:00
/// @brief `gt(beta)` on XOR shares of the point. `if_false` is 0.
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @param begin the iterator to the first query
/// @param end the iterator past the last query
/// @param rng the Doerner– Shelat randomness tapes
/// @param beta the true payload
2026-09-24 15:16:21 -06:00
template < typename InputT ,
typename ForwardIterator ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
geneval_cmp_result geneval_cmp ( InputT x0 , InputT x1 ,
ForwardIterator begin , ForwardIterator end ,
ds_randomness < RootSampler , PadRng > rng , uint64_t beta )
{
return geneval_cmp ( std : : move ( x0 ) , std : : move ( x1 ) , begin , end ,
std : : move ( rng ) , dpf : : gt ( beta ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief `gt(beta)` on additive shares of the point. `if_false` is 0.
/// @param x0 party 0's share of the secret point
/// @param x1 party 1's share of the secret point
/// @param begin the iterator to the first query
/// @param end the iterator past the last query
/// @param rng the Doerner– Shelat randomness tapes
/// @param beta the true payload
2026-09-24 20:44:07 -06:00
template < typename InputT ,
typename ForwardIterator ,
typename RootSampler ,
typename PadRng >
HEDLEY_WARN_UNUSED_RESULT
geneval_cmp_result geneval_cmp ( arith_input_t , InputT x0 , InputT x1 ,
ForwardIterator begin , ForwardIterator end ,
ds_randomness < RootSampler , PadRng > rng , uint64_t beta )
{
return geneval_cmp ( arith_input , std : : move ( x0 ) , std : : move ( x1 ) , begin , end ,
std : : move ( rng ) , dpf : : gt ( beta ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @}
2026-09-28 05:59:19 -06:00
namespace detail
{
/// @brief Copy party shares from a geneval result into caller buffers.
template < typename Result , typename Buf0 , typename Buf1 >
void geneval_fill_buffers ( const Result & r , Buf0 & buf0 , Buf1 & buf1 )
{
const std : : size_t n = r . party0 . size ( ) ;
if ( utils : : size ( buf0 ) < n | | utils : : size ( buf1 ) < n )
throw std : : length_error ( " geneval: output buffer is too small " ) ;
for ( std : : size_t i = 0 ; i < n ; + + i )
{
buf0 [ i ] = r . party0 [ i ] ;
buf1 [ i ] = r . party1 [ i ] ;
}
}
} // namespace detail
/// @name Geneval into caller buffers
/// @details Thin overloads that run the same trie walk, then copy party shares
/// into `buf0` / `buf1` (same layout as `eval_interval` / `eval_sequence`
/// output buffers). Memoizer arguments for the fused trie are internal;
/// path memoizers live on `geneval_cmp` / `geneval_ic`.
/// @{
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng ,
typename Buf0 ,
typename Buf1 >
HEDLEY_WARN_UNUSED_RESULT
auto geneval_point ( InputT x0 , InputT x1 , InputT query ,
ds_randomness < RootSampler , PadRng > rng , OutputT y , Buf0 & buf0 , Buf1 & buf1 )
{
auto r = geneval_point < InteriorPRG , ExteriorPRG > ( std : : move ( x0 ) ,
std : : move ( x1 ) , query , std : : move ( rng ) , std : : move ( y ) ) ;
detail : : geneval_fill_buffers ( r , buf0 , buf1 ) ;
return r ;
}
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng ,
typename Buf0 ,
typename Buf1 >
HEDLEY_WARN_UNUSED_RESULT
auto geneval_interval ( InputT x0 , InputT x1 , InputT from , InputT to ,
ds_randomness < RootSampler , PadRng > rng , OutputT y , Buf0 & buf0 , Buf1 & buf1 )
{
auto r = geneval_interval < InteriorPRG , ExteriorPRG > ( std : : move ( x0 ) ,
std : : move ( x1 ) , from , to , std : : move ( rng ) , std : : move ( y ) ) ;
detail : : geneval_fill_buffers ( r , buf0 , buf1 ) ;
return r ;
}
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng ,
typename Buf0 ,
typename Buf1 >
HEDLEY_WARN_UNUSED_RESULT
auto geneval_full ( InputT x0 , InputT x1 ,
ds_randomness < RootSampler , PadRng > rng , OutputT y , Buf0 & buf0 , Buf1 & buf1 )
{
auto r = geneval_full < InteriorPRG , ExteriorPRG > ( std : : move ( x0 ) ,
std : : move ( x1 ) , std : : move ( rng ) , std : : move ( y ) ) ;
detail : : geneval_fill_buffers ( r , buf0 , buf1 ) ;
return r ;
}
/// \complexity O(n F) PRG expansions. n is `depth`. Each level expands every frontier node (two `expand` calls, one per share) and, while the secret path is live, one `prepare_level`. F is at most the number of distinct query leaves; the function rejects more than 2^20. Setup sorts the q query ids.
/// \rounds No sockets. While the path is live, each level calls `prepare_level`, which samples one `ds_cw_pads` and two `ds_and_pads` and then `open_cw`.
/// \communication none in this function.
/// \preprocessing The `ds_randomness` tape: per live level, `ds_sample_cw` draws two 128-bit blocks and two bits, and each of the two AND pads is one `ds_sample_and`.
template < typename InteriorPRG = dpf : : prg : : aes128 ,
typename ExteriorPRG = InteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename PadRng ,
typename ForwardIterator ,
typename Buf0 ,
typename Buf1 >
HEDLEY_WARN_UNUSED_RESULT
auto geneval_sequence ( InputT x0 , InputT x1 , ForwardIterator begin ,
ForwardIterator end , ds_randomness < RootSampler , PadRng > rng , OutputT y ,
Buf0 & buf0 , Buf1 & buf1 )
{
auto r = geneval_sequence < InteriorPRG , ExteriorPRG > ( std : : move ( x0 ) ,
std : : move ( x1 ) , begin , end , std : : move ( rng ) , std : : move ( y ) ) ;
detail : : geneval_fill_buffers ( r , buf0 , buf1 ) ;
return r ;
}
/// @}
2026-09-24 14:08:32 -06:00
} // namespace dpf
# endif // LIBDPF_INCLUDE_DPF_GENEVAL_HPP__