2026-09-24 14:08:32 -06:00
/// @file grotto/offset_horner.hpp
/// @brief Noninteractive cubic evaluation after the public offset is opened.
2026-09-28 05:59:19 -06:00
/// @details The dealer keys one comparison at `center` whose payload is the
/// vector `1, center, center^2, center^3` in Z/2^64. The seed spine
/// is stored once; the value words grow with the degree. After the parties open
2026-09-24 15:16:21 -06:00
/// `eta`, each party shifts the knots by `eta`, inserts the domain
/// minimum and the public carry threshold, and sorts. The
2026-09-24 14:08:32 -06:00
/// sign-respecting segment walk then returns additive shares of
2026-09-24 15:16:21 -06:00
/// `center^m` on the refined piece that contains `center`. On each
/// refined piece the wrapped input is `center + kappa` for a public
/// `kappa`: `eta` on the side that does not overflow, and
/// `eta ∓ 2^n` on the side that does. A public binomial shift by that
/// `kappa`, dotted with the segment shares, is a share of the
/// polynomial at the wrapped group element. No further round.
2026-09-24 14:08:32 -06:00
///
2026-09-24 15:16:21 -06:00
/// Domains of 63 bits or more are already the ring Z/2^64, so the
/// carry adjustment is the identity there. `lift` sign-extends a
/// signed domain element and zero-extends an unsigned one.
2026-09-24 14:08:32 -06:00
///
/// `offset_horner_at_x_plus_r` is the wiring from the reconstruction
/// the parties already do: `eta = x - r` and `center = 2r`.
2026-09-28 05:59:19 -06:00
/// @note Storrier, Vadapalli, Lyons, and Henry (ePrint 2023/108) evaluate a public piecewise polynomial from one point key by prefix parity. This dealer keys one comparison of a secret center, with a `dpf::vec` of the powers as the payload.
2026-09-24 14:08:32 -06:00
# ifndef LIBDPF_INCLUDE_GROTTO_OFFSET_HORNER_HPP__
# define LIBDPF_INCLUDE_GROTTO_OFFSET_HORNER_HPP__
2026-09-24 20:44:07 -06:00
# include "hedley/hedley.h"
2026-09-24 14:08:32 -06:00
# include <algorithm>
# include <array>
# include <cstddef>
# include <cstdint>
2026-09-24 15:16:21 -06:00
# include <limits>
# include <optional>
2026-09-24 14:08:32 -06:00
# include <stdexcept>
# include <tuple>
# include <type_traits>
# include <utility>
2026-09-28 05:59:19 -06:00
# include <variant>
2026-09-24 14:08:32 -06:00
# include <vector>
# include "dpf.hpp"
# include "grotto/prefix_parity.hpp"
namespace grotto
{
2026-09-28 05:59:19 -06:00
/// \complexity One modular add in the input group (cast to the unsigned width). `Θ(1)`.
/// @see grotto::offset_horner_at_x_plus_r
2026-09-24 14:08:32 -06:00
inline constexpr std : : size_t offset_horner_max_degree = 3 ;
template < typename T >
2026-09-28 05:59:19 -06:00
HEDLEY_CONST
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-28 05:59:19 -06:00
constexpr T offset_horner_group_add ( T a , T b ) noexcept
2026-09-24 14:08:32 -06:00
{
using u = std : : make_unsigned_t < T > ;
return static_cast < T > ( static_cast < u > ( static_cast < u > ( a ) + static_cast < u > ( b ) ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity One modular subtract in the input group. `Θ(1)`.
/// @see grotto::offset_horner_group_add
2026-09-24 14:08:32 -06:00
template < typename T >
2026-09-28 05:59:19 -06:00
HEDLEY_CONST
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-28 05:59:19 -06:00
constexpr T offset_horner_group_sub ( T a , T b ) noexcept
2026-09-24 14:08:32 -06:00
{
using u = std : : make_unsigned_t < T > ;
return static_cast < T > ( static_cast < u > ( static_cast < u > ( a ) - static_cast < u > ( b ) ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief `eta = x - r` and `center = 2r`, both in the input group.
/// @tparam T value type
2026-09-24 14:08:32 -06:00
template < typename T >
struct offset_horner_x_plus_r
{
T eta { } ;
T center { } ;
} ;
2026-09-28 05:59:19 -06:00
/// \complexity Two group operations: `eta = x - r`, `center = 2r`. `Θ(1)`.
/// @param x secret input share or value, in the input group
/// @param r mask, in the input group
/// @return `eta` and `center`
/// @see grotto::make_offset_horner_keys
2026-09-24 14:08:32 -06:00
template < typename T >
2026-09-28 05:59:19 -06:00
HEDLEY_CONST
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-28 05:59:19 -06:00
constexpr offset_horner_x_plus_r < T > offset_horner_at_x_plus_r ( T x , T r ) noexcept
2026-09-24 14:08:32 -06:00
{
return offset_horner_x_plus_r < T > {
offset_horner_group_sub ( x , r ) ,
offset_horner_group_add ( r , r ) } ;
}
2026-09-28 05:59:19 -06:00
template < typename InputT , std : : size_t Degree = offset_horner_max_degree ,
bool Verifiable = false >
2026-09-24 14:08:32 -06:00
struct offset_horner_keys ;
namespace offset_horner_detail
{
inline constexpr uint64_t binom [ 4 ] [ 4 ] = {
{ 1 , 0 , 0 , 0 } ,
{ 1 , 1 , 0 , 0 } ,
{ 1 , 2 , 1 , 0 } ,
{ 1 , 3 , 3 , 1 } ,
} ;
template < typename T >
2026-09-28 05:59:19 -06:00
HEDLEY_CONST
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-28 05:59:19 -06:00
constexpr uint64_t lift ( T v ) noexcept
2026-09-24 14:08:32 -06:00
{
if constexpr ( std : : is_signed_v < T > )
return static_cast < uint64_t > ( static_cast < std : : int64_t > ( v ) ) ;
else
return static_cast < uint64_t > ( v ) ;
}
template < std : : size_t Degree >
2026-09-28 05:59:19 -06:00
HEDLEY_PURE
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-28 05:59:19 -06:00
constexpr uint64_t horner_at ( const std : : array < uint64_t , Degree + 1 > & coeff , uint64_t point ) noexcept
2026-09-24 14:08:32 -06:00
{
uint64_t acc = coeff [ Degree ] ;
for ( std : : size_t k = Degree ; k - - > 0 ; )
acc = acc * point + coeff [ k ] ;
return acc ;
}
template < std : : size_t Degree >
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
void fill_payloads ( uint64_t base , uint64_t ( & payload ) [ Degree + 1 ] ) noexcept
{
uint64_t pow = 1 ;
for ( std : : size_t m = 0 ; m < = Degree ; + + m )
{
payload [ m ] = pow ;
pow * = base ;
}
}
2026-09-28 05:59:19 -06:00
template < typename InputT , std : : size_t Degree >
dpf : : vec < uint64_t , Degree + 1 > payload_vec ( const uint64_t ( & payload ) [ Degree + 1 ] )
{
dpf : : vec < uint64_t , Degree + 1 > v ;
for ( std : : size_t m = 0 ; m < = Degree ; + + m )
v [ m ] = payload [ m ] ;
return v ;
}
template < typename InputT , std : : size_t Degree >
auto make_power_key ( InputT center , const uint64_t ( & payload ) [ Degree + 1 ] , std : : false_type )
2026-09-24 14:08:32 -06:00
{
2026-09-28 05:59:19 -06:00
return dpf : : make_dpf ( center , dpf : : gt ( payload_vec < InputT , Degree > ( payload ) ) ) ;
}
template < typename InputT , std : : size_t Degree >
auto make_power_key ( InputT center , const uint64_t ( & payload ) [ Degree + 1 ] , std : : true_type )
{
return dpf : : make_dpf ( center , dpf : : gt ( payload_vec < InputT , Degree > ( payload ) ) ,
dpf : : verifiable { } ) ;
2026-09-24 14:08:32 -06:00
}
template < typename InputT >
void check_knots ( const std : : vector < InputT > & knots , std : : size_t coeff_rows )
{
if ( knots . empty ( ) | | knots . size ( ) ! = coeff_rows )
throw std : : invalid_argument ( " offset horner: knots and coefficient rows differ " ) ;
for ( std : : size_t i = 1 ; i < knots . size ( ) ; + + i )
{
if ( ! ( knots [ i - 1 ] < knots [ i ] ) )
throw std : : invalid_argument ( " offset horner: knots must be strictly increasing " ) ;
}
}
template < std : : size_t Degree , typename InputT >
struct shifted_piece
{
InputT knot { } ;
std : : array < uint64_t , Degree + 1 > coeff { } ;
} ;
template < std : : size_t Degree , typename InputT >
std : : vector < shifted_piece < Degree , InputT > > shift_and_sort (
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
InputT eta )
{
std : : vector < shifted_piece < Degree , InputT > > rows ( knots . size ( ) ) ;
for ( std : : size_t i = 0 ; i < knots . size ( ) ; + + i )
{
rows [ i ] . knot = offset_horner_group_sub ( knots [ i ] , eta ) ;
rows [ i ] . coeff = coeff [ i ] ;
}
std : : sort ( rows . begin ( ) , rows . end ( ) ,
[ ] ( const shifted_piece < Degree , InputT > & a , const shifted_piece < Degree , InputT > & b ) {
return a . knot < b . knot ;
} ) ;
return rows ;
}
2026-09-24 15:16:21 -06:00
inline std : : vector < uint64_t > segments_from_prefixes (
const std : : vector < uint64_t > & prefix , uint64_t wrap_share , uint64_t mask )
2026-09-24 14:08:32 -06:00
{
using namespace dpf : : detail : : dcf_impl ;
2026-09-24 15:16:21 -06:00
const std : : size_t n = prefix . size ( ) ;
2026-09-24 14:08:32 -06:00
if ( n = = 1 )
return std : : vector < uint64_t > { wrap_share & mask } ;
std : : vector < uint64_t > seg ( n ) ;
for ( std : : size_t i = 0 ; i < n ; + + i )
{
const uint64_t nxt = prefix [ ( i + 1 ) % n ] ;
seg [ i ] = ( nxt + neg_m ( prefix [ i ] , mask ) ) & mask ;
}
seg [ n - 1 ] = ( seg [ n - 1 ] + wrap_share ) & mask ;
return seg ;
}
2026-09-28 05:59:19 -06:00
/// @brief One segment walk whose comparison payload is `N` lanes.
/// Lane `m` matches a per-power `segments_of` on `gt(payload[m])`.
template < std : : size_t N , typename Key , typename InputT >
std : : array < std : : vector < uint64_t > , N > segments_lanes (
const Key & key , const std : : vector < InputT > & knots ,
const std : : array < uint64_t , N > & wrap_party , dpf : : proof_token * pi = nullptr )
{
using Vec = dpf : : vec < std : : uint64_t , N > ;
const std : : size_t n = knots . size ( ) ;
std : : array < std : : vector < uint64_t > , N > out ;
for ( auto & row : out )
row . assign ( n , 0 ) ;
if ( n = = 1 )
{
if ( pi ! = nullptr )
{
dpf : : detail : : vdpf : : init_proof ( * pi , key ) ;
dpf : : detail : : vdpf : : fold_output_binding ( * pi , key ) ;
}
const uint64_t mask = key . cmp ( ) . mask ;
for ( std : : size_t m = 0 ; m < N ; + + m )
out [ m ] [ 0 ] = wrap_party [ m ] & mask ;
return out ;
}
if ( pi ! = nullptr )
{
if constexpr ( ! Key : : is_verifiable )
throw std : : invalid_argument (
" offset horner: proof token requires a verifiable key " ) ;
dpf : : detail : : vdpf : : init_proof ( * pi , key ) ;
}
const uint64_t mask = key . cmp ( ) . mask ;
auto path = dpf : : make_basic_path_memoizer ( key ) ;
std : : vector < Vec > prefixes ( n ) ;
for ( std : : size_t which = 0 ; which < n ; + + which )
{
auto tx = key . offset_x ( knots [ which ] ) ;
dpf : : utils : : flip_msb_if_signed_integral ( tx ) ;
prefixes [ which ] = dpf : : detail : : incr : : eval_payload_path_sum < Vec > (
key , tx , path , false , 0 , pi ) ;
}
if ( pi ! = nullptr )
dpf : : detail : : vdpf : : fold_output_binding ( * pi , key ) ;
for ( std : : size_t m = 0 ; m < N ; + + m )
{
std : : vector < uint64_t > prefix ( n ) ;
for ( std : : size_t i = 0 ; i < n ; + + i )
prefix [ i ] = prefixes [ i ] [ m ] ;
out [ m ] = segments_from_prefixes ( prefix , wrap_party [ m ] , mask ) ;
}
return out ;
}
/// @brief Widest lane count stored in one offset comparison (degree 16).
inline constexpr std : : size_t lane_key_max = 17 ;
template < typename InputT , std : : size_t N , bool Verifiable >
struct lane_key_slot
{
static constexpr std : : size_t lanes = N ;
using key_pair = std : : conditional_t < Verifiable ,
decltype ( dpf : : make_dpf ( std : : declval < InputT > ( ) ,
dpf : : idcf ( dpf : : gt ( dpf : : vec < uint64_t , N > { } ) ) , dpf : : verifiable { } ) ) ,
decltype ( dpf : : make_dpf ( std : : declval < InputT > ( ) ,
dpf : : idcf ( dpf : : gt ( dpf : : vec < uint64_t , N > { } ) ) ) ) > ;
key_pair keys ;
explicit lane_key_slot ( key_pair k )
: keys ( std : : move ( k ) ) { }
} ;
template < typename InputT , bool Verifiable , typename Seq >
struct lane_key_variant ;
template < typename InputT , bool Verifiable , std : : size_t . . . I >
struct lane_key_variant < InputT , Verifiable , std : : index_sequence < I . . . > >
{
using type = std : : variant < std : : monostate ,
lane_key_slot < InputT , I + 1 , Verifiable > . . . > ;
} ;
template < typename InputT , bool Verifiable >
using lane_keys = typename lane_key_variant < InputT , Verifiable ,
std : : make_index_sequence < lane_key_max > > : : type ;
template < typename InputT , std : : size_t N , bool Verifiable >
lane_key_slot < InputT , N , Verifiable > make_lane_slot (
InputT center , const uint64_t * payload )
{
dpf : : vec < uint64_t , N > v ;
for ( std : : size_t i = 0 ; i < N ; + + i )
v [ i ] = payload [ i ] ;
if constexpr ( Verifiable )
return lane_key_slot < InputT , N , Verifiable > {
dpf : : make_dpf ( center , dpf : : idcf ( dpf : : gt ( v ) ) , dpf : : verifiable { } ) } ;
else
return lane_key_slot < InputT , N , Verifiable > {
dpf : : make_dpf ( center , dpf : : idcf ( dpf : : gt ( v ) ) ) } ;
}
template < typename InputT , bool Verifiable , std : : size_t N >
void emplace_lane_keys ( lane_keys < InputT , Verifiable > & out ,
InputT center , const uint64_t * payload , std : : size_t n )
{
if ( n = = N )
out . template emplace < lane_key_slot < InputT , N , Verifiable > > (
make_lane_slot < InputT , N , Verifiable > ( center , payload ) ) ;
else if constexpr ( N > 1 )
emplace_lane_keys < InputT , Verifiable , N - 1 > ( out , center , payload , n ) ;
}
template < typename InputT , bool Verifiable >
lane_keys < InputT , Verifiable > make_lane_keys (
InputT center , const uint64_t * payload , std : : size_t n )
{
if ( n = = 0 | | n > lane_key_max )
throw std : : invalid_argument ( " offset comparison: lane count out of range " ) ;
lane_keys < InputT , Verifiable > out ;
emplace_lane_keys < InputT , Verifiable , lane_key_max > ( out , center , payload , n ) ;
return out ;
}
template < std : : size_t Party , typename Slot , typename InputT >
std : : vector < std : : vector < uint64_t > > segments_of_slot (
const Slot & slot , const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , 2 > > & wrap , dpf : : proof_token * pi )
{
constexpr std : : size_t N = Slot : : lanes ;
if ( wrap . size ( ) < N )
throw std : : invalid_argument ( " offset comparison: wrap shares shorter than the payload " ) ;
std : : array < uint64_t , N > wrap_party { } ;
for ( std : : size_t m = 0 ; m < N ; + + m )
wrap_party [ m ] = wrap [ m ] [ Party ] ;
const auto seg = segments_lanes < N > ( std : : get < Party > ( slot . keys ) , knots , wrap_party , pi ) ;
std : : vector < std : : vector < uint64_t > > out ( knots . size ( ) , std : : vector < uint64_t > ( N , 0 ) ) ;
for ( std : : size_t m = 0 ; m < N ; + + m )
for ( std : : size_t i = 0 ; i < knots . size ( ) ; + + i )
out [ i ] [ m ] = seg [ m ] [ i ] ;
return out ;
}
template < std : : size_t Party , typename Keys , typename InputT >
std : : vector < std : : vector < uint64_t > > lane_segments (
const Keys & keys ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , 2 > > & wrap , dpf : : proof_token * pi )
{
return std : : visit ( [ & ] ( const auto & slot ) - > std : : vector < std : : vector < uint64_t > > {
using Slot = std : : decay_t < decltype ( slot ) > ;
if constexpr ( std : : is_same_v < Slot , std : : monostate > )
throw std : : invalid_argument ( " offset comparison: missing key " ) ;
else
return segments_of_slot < Party > ( slot , knots , wrap , pi ) ;
} , keys ) ;
}
inline void replicate_proof ( dpf : : proof_token * tokens , std : : size_t n )
{
if ( tokens = = nullptr | | n = = 0 )
return ;
for ( std : : size_t m = 1 ; m < n ; + + m )
tokens [ m ] = tokens [ 0 ] ;
}
2026-09-24 15:16:21 -06:00
template < typename Key , typename InputT >
std : : vector < uint64_t > segments_of ( const Key & key , const std : : vector < InputT > & knots ,
2026-09-28 05:59:19 -06:00
uint64_t wrap_share , dpf : : proof_token * pi = nullptr )
2026-09-24 14:08:32 -06:00
{
2026-09-24 15:16:21 -06:00
const std : : size_t n = knots . size ( ) ;
if ( n = = 1 )
2026-09-28 05:59:19 -06:00
{
// One-segment short circuit (domains ≥ 63 bits): no prefix walk.
// Bind leaf / value words so tokens are non-zero and verify.
if ( pi ! = nullptr )
{
dpf : : detail : : vdpf : : init_proof ( * pi , key ) ;
dpf : : detail : : vdpf : : fold_output_binding ( * pi , key ) ;
}
2026-09-24 15:16:21 -06:00
return std : : vector < uint64_t > { wrap_share & key . cmp ( ) . mask } ;
2026-09-28 05:59:19 -06:00
}
2026-09-24 15:16:21 -06:00
std : : vector < uint64_t > prefix ( n ) ;
2026-09-28 05:59:19 -06:00
signed_prefix_parities_into ( key , knots . data ( ) , n , prefix . data ( ) , pi ) ;
2026-09-24 15:16:21 -06:00
return segments_from_prefixes ( prefix , wrap_share , key . cmp ( ) . mask ) ;
}
template < typename T >
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 15:16:21 -06:00
int64_t math_lift ( T value ) noexcept
{
if constexpr ( std : : is_signed_v < T > )
return static_cast < int64_t > ( value ) ;
else
return static_cast < int64_t > ( lift ( value ) ) ;
}
template < typename T >
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 15:16:21 -06:00
T domain_min ( ) noexcept
{
if constexpr ( std : : is_signed_v < T > )
return std : : numeric_limits < T > : : min ( ) ;
else
return T { 0 } ;
}
2026-09-24 23:18:10 -06:00
/// @brief Public center-space cut where `center + eta` crosses the domain end.
/// @details Empty when that cut is outside the domain, including `eta == 0`.
/// @tparam T value type
2026-09-28 05:59:19 -06:00
/// @param eta public offset `eta = x - r`
2026-09-24 23:18:10 -06:00
/// @return Public center-space cut where `center + eta` crosses the domain end
2026-09-24 15:16:21 -06:00
template < typename T >
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 15:16:21 -06:00
std : : optional < T > carry_threshold ( T eta ) noexcept
{
constexpr unsigned bits = dpf : : utils : : bitlength_of_v < T > ;
2026-09-28 05:59:19 -06:00
if constexpr ( bits > 62 )
2026-09-24 15:16:21 -06:00
return std : : nullopt ;
else
2026-09-24 14:08:32 -06:00
{
2026-09-28 05:59:19 -06:00
const int64_t mod = int64_t { 1 } < < bits ;
const int64_t half = mod > > 1 ;
const int64_t ez = math_lift ( eta ) ;
if constexpr ( std : : is_signed_v < T > )
{
if ( ez > 0 )
return static_cast < T > ( half - ez ) ;
if ( ez < 0 )
return static_cast < T > ( - half - ez ) ;
2026-09-24 15:16:21 -06:00
return std : : nullopt ;
2026-09-28 05:59:19 -06:00
}
else
{
if ( ez = = 0 )
return std : : nullopt ;
return static_cast < T > ( mod - ez ) ;
}
2026-09-24 15:16:21 -06:00
}
}
2026-09-24 23:18:10 -06:00
/// @brief `center + kappa` is the wrapped representative, as a mathematical integer.
/// @tparam T value type
2026-09-28 05:59:19 -06:00
/// @param left left endpoint of the piece, in the input group
/// @param eta public offset `eta = x - r`
2026-09-24 23:18:10 -06:00
/// @return `center + kappa` is the wrapped representative, as a mathematical integer
2026-09-24 15:16:21 -06:00
template < typename T >
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 15:16:21 -06:00
int64_t kappa_for ( T left , T eta ) noexcept
{
constexpr unsigned bits = dpf : : utils : : bitlength_of_v < T > ;
const int64_t ez = math_lift ( eta ) ;
2026-09-28 05:59:19 -06:00
if constexpr ( bits > 62 )
2026-09-24 15:16:21 -06:00
return ez ;
else
{
2026-09-28 05:59:19 -06:00
const int64_t mod = int64_t { 1 } < < bits ;
const int64_t left_i = math_lift ( left ) ;
if constexpr ( std : : is_signed_v < T > )
{
const int64_t half = mod > > 1 ;
if ( ez > 0 & & left_i > = half - ez )
return ez - mod ;
if ( ez < 0 & & left_i < - half - ez )
return ez + mod ;
return ez ;
}
else
{
if ( ez ! = 0 & & left_i > = mod - ez )
return ez - mod ;
return ez ;
}
2026-09-24 14:08:32 -06:00
}
}
template < std : : size_t Degree , typename InputT >
int piece_index ( InputT point , const std : : vector < InputT > & sorted_knots )
{
const std : : size_t n = sorted_knots . size ( ) ;
if ( n < = 1 )
return 0 ;
for ( std : : size_t i = 0 ; i + 1 < n ; + + i )
{
if ( point > = sorted_knots [ i ] & & point < sorted_knots [ i + 1 ] )
return static_cast < int > ( i ) ;
}
return static_cast < int > ( n - 1 ) ;
}
template < std : : size_t Degree >
std : : array < uint64_t , Degree + 1 > binomial_coefficients (
const std : : array < uint64_t , Degree + 1 > & a , uint64_t center_limb )
{
std : : array < uint64_t , Degree + 1 > c { } ;
uint64_t center_pow [ Degree + 1 ] ;
center_pow [ 0 ] = 1 ;
for ( std : : size_t m = 1 ; m < = Degree ; + + m )
center_pow [ m ] = center_pow [ m - 1 ] * center_limb ;
for ( std : : size_t m = 0 ; m < = Degree ; + + m )
{
for ( std : : size_t k = 0 ; k < = m ; + + k )
c [ k ] + = a [ m ] * binom [ m ] [ k ] * center_pow [ m - k ] ;
}
return c ;
}
2026-09-24 15:16:21 -06:00
template < std : : size_t Degree , typename InputT >
struct prepared_piece
{
InputT knot { } ;
std : : array < uint64_t , Degree + 1 > coeff { } ;
int64_t kappa = 0 ;
} ;
template < std : : size_t Degree , typename InputT >
void insert_cut ( std : : vector < shifted_piece < Degree , InputT > > & rows , InputT point )
{
for ( const auto & row : rows )
{
if ( row . knot = = point )
return ;
}
std : : vector < InputT > knots ;
knots . reserve ( rows . size ( ) ) ;
for ( const auto & row : rows )
knots . push_back ( row . knot ) ;
const int hot = piece_index < Degree > ( point , knots ) ;
shifted_piece < Degree , InputT > extra ;
extra . knot = point ;
extra . coeff = rows [ static_cast < std : : size_t > ( hot ) ] . coeff ;
rows . push_back ( std : : move ( extra ) ) ;
std : : sort ( rows . begin ( ) , rows . end ( ) ,
[ ] ( const shifted_piece < Degree , InputT > & a , const shifted_piece < Degree , InputT > & b ) {
return a . knot < b . knot ;
} ) ;
}
template < std : : size_t Degree , typename InputT >
std : : vector < prepared_piece < Degree , InputT > > prepare_pieces (
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
InputT eta )
{
auto rows = shift_and_sort < Degree > ( knots , coeff , eta ) ;
constexpr unsigned bits = dpf : : utils : : bitlength_of_v < InputT > ;
if ( bits < = 62 )
{
insert_cut < Degree > ( rows , domain_min < InputT > ( ) ) ;
if ( const auto cut = carry_threshold ( eta ) )
insert_cut < Degree > ( rows , * cut ) ;
}
std : : vector < prepared_piece < Degree , InputT > > out ;
out . reserve ( rows . size ( ) ) ;
for ( const auto & row : rows )
{
prepared_piece < Degree , InputT > piece ;
piece . knot = row . knot ;
piece . coeff = row . coeff ;
piece . kappa = kappa_for ( row . knot , eta ) ;
out . push_back ( std : : move ( piece ) ) ;
}
return out ;
}
2026-09-24 23:18:10 -06:00
/// @brief `out[k]` sums to the polynomial at the wrapped input. It is
2026-09-24 15:16:21 -06:00
/// `center^k` times the public binomial coefficient of `kappa`, not a
/// coefficient you Horner-evaluate at `eta`.
2026-09-24 23:18:10 -06:00
/// @tparam Degree degree
2026-09-28 05:59:19 -06:00
/// @param seg per-power segment shares on the refined pieces
2026-09-24 23:18:10 -06:00
/// @param coeff the public coefficient
2026-09-28 05:59:19 -06:00
/// @param kappa public carry of each refined piece
2026-09-24 23:18:10 -06:00
/// @return `out[k]` sums to the polynomial at the wrapped input
2026-09-24 15:16:21 -06:00
template < std : : size_t Degree >
std : : array < uint64_t , Degree + 1 > contributions (
const std : : array < std : : vector < uint64_t > , Degree + 1 > & seg ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
const std : : vector < int64_t > & kappa )
{
std : : array < uint64_t , Degree + 1 > out { } ;
const std : : size_t n = coeff . size ( ) ;
for ( std : : size_t i = 0 ; i < n ; + + i )
{
const auto q = binomial_coefficients < Degree > (
coeff [ i ] , static_cast < uint64_t > ( kappa [ i ] ) ) ;
for ( std : : size_t k = 0 ; k < = Degree ; + + k )
out [ k ] + = seg [ k ] [ i ] * q [ k ] ;
}
return out ;
}
2026-09-24 14:08:32 -06:00
} // namespace offset_horner_detail
2026-09-24 23:18:10 -06:00
/// @brief Both parties' comparison keys and wrap-piece shares for one center.
/// @tparam InputT input domain type
/// @tparam Degree degree
2026-09-28 05:59:19 -06:00
/// @tparam Verifiable when true, keys carry `dpf::verifiable`
template < typename InputT , std : : size_t Degree , bool Verifiable >
2026-09-24 14:08:32 -06:00
struct offset_horner_keys
{
static_assert ( Degree < = offset_horner_max_degree , " offset horner degree is at most 3 " ) ;
static_assert ( std : : is_integral_v < InputT > , " offset horner domain must be an integer group " ) ;
static constexpr std : : size_t degree = Degree ;
2026-09-28 05:59:19 -06:00
static constexpr bool is_verifiable = Verifiable ;
2026-09-24 14:08:32 -06:00
using input_type = InputT ;
2026-09-28 05:59:19 -06:00
using key_pair = std : : conditional_t < Verifiable ,
decltype ( dpf : : make_dpf ( std : : declval < InputT > ( ) ,
dpf : : gt ( dpf : : vec < uint64_t , Degree + 1 > { } ) , dpf : : verifiable { } ) ) ,
decltype ( dpf : : make_dpf ( std : : declval < InputT > ( ) ,
dpf : : gt ( dpf : : vec < uint64_t , Degree + 1 > { } ) ) ) > ;
2026-09-24 14:08:32 -06:00
2026-09-28 05:59:19 -06:00
/// @brief One `gt` at the hidden center. Lane `m` of the payload is `center^m`.
/// `.first` is party 0. The seed spine is stored once.
key_pair keys ;
2026-09-24 23:18:10 -06:00
/// @brief Random additive split of `center^m`, indexed `[power][party]`.
2026-09-24 14:08:32 -06:00
std : : array < std : : array < uint64_t , 2 > , Degree + 1 > wrap_share { } ;
} ;
2026-09-28 05:59:19 -06:00
/// \complexity One `dpf::make_dpf` of a `Degree + 1` lane comparison. `Degree` is at most 3.
/// The seed spine is one key. Value words are `Degree + 1` lanes.
/// \rounds No party interaction.
/// \communication None inside this function. Shipping the returned keys is outside it.
/// \preprocessing One `gt` key and `wrap_share[Degree + 1][2]` words of `uint64_t`.
/// @see grotto::offset_horner_eval
/// @see grotto::offset_poly_eval
2026-09-24 14:08:32 -06:00
template < typename InputT , std : : size_t Degree = offset_horner_max_degree >
2026-09-28 05:59:19 -06:00
offset_horner_keys < InputT , Degree , false > make_offset_horner_keys ( InputT center )
2026-09-24 14:08:32 -06:00
{
using namespace offset_horner_detail ;
uint64_t payload [ Degree + 1 ] ;
fill_payloads < Degree > ( lift ( center ) , payload ) ;
2026-09-28 05:59:19 -06:00
offset_horner_keys < InputT , Degree , false > mat {
make_power_key < InputT , Degree > ( center , payload , std : : false_type { } ) ,
{ } } ;
for ( std : : size_t m = 0 ; m < = Degree ; + + m )
{
const uint64_t blind = dpf : : uniform_sample < uint64_t > ( ) ;
mat . wrap_share [ m ] [ 0 ] = blind ;
mat . wrap_share [ m ] [ 1 ] = payload [ m ] - blind ;
}
return mat ;
}
/// \complexity One `dpf::make_dpf` of a `Degree + 1` lane comparison, with proof tokens. `Degree` is at most 3.
/// \rounds No party interaction.
/// \communication None inside this function.
/// \preprocessing One verifiable `gt` key and `wrap_share[Degree + 1][2]` words of `uint64_t`.
/// @see grotto::offset_horner_eval
template < typename InputT , std : : size_t Degree = offset_horner_max_degree >
offset_horner_keys < InputT , Degree , true > make_offset_horner_keys ( InputT center ,
dpf : : verifiable )
{
using namespace offset_horner_detail ;
uint64_t payload [ Degree + 1 ] ;
fill_payloads < Degree > ( lift ( center ) , payload ) ;
offset_horner_keys < InputT , Degree , true > mat {
make_power_key < InputT , Degree > ( center , payload , std : : true_type { } ) ,
2026-09-24 14:08:32 -06:00
{ } } ;
for ( std : : size_t m = 0 ; m < = Degree ; + + m )
{
const uint64_t blind = dpf : : uniform_sample < uint64_t > ( ) ;
mat . wrap_share [ m ] [ 0 ] = blind ;
mat . wrap_share [ m ] [ 1 ] = payload [ m ] - blind ;
}
return mat ;
}
2026-09-24 23:18:10 -06:00
/// @brief Cleartext binomial coefficients of the selected refined piece in the
2026-09-24 15:16:21 -06:00
/// variable `center`: Horner at `lift(center)` is the polynomial at the
/// wrapped input.
2026-09-24 23:18:10 -06:00
/// @tparam Degree degree
/// @tparam InputT input domain type
2026-09-28 05:59:19 -06:00
/// @param center hidden comparison point in the input group
/// @param knots public breakpoints, strictly increasing
2026-09-24 23:18:10 -06:00
/// @param coeff the public coefficient
2026-09-28 05:59:19 -06:00
/// @param eta public offset `eta = x - r`
2026-09-24 23:18:10 -06:00
/// @return Cleartext binomial coefficients of the selected refined piece in the variable `center`:
/// Horner at `lift(center)` is the polynomial at the wrapped input
2026-09-28 05:59:19 -06:00
/// \complexity Same piece preparation as the online eval (`Θ(P log P)` sort plus the two optional cuts), then `Θ(Degree)` binomial coefficients on the hot piece. No keys.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
2026-09-24 14:08:32 -06:00
template < std : : size_t Degree , typename InputT >
std : : array < uint64_t , Degree + 1 > offset_horner_clear_coefficients (
InputT center ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
InputT eta )
{
using namespace offset_horner_detail ;
check_knots ( knots , coeff . size ( ) ) ;
2026-09-24 15:16:21 -06:00
const auto pieces = prepare_pieces < Degree > ( knots , coeff , eta ) ;
std : : vector < InputT > cuts ;
cuts . reserve ( pieces . size ( ) ) ;
for ( const auto & piece : pieces )
cuts . push_back ( piece . knot ) ;
const int hot = piece_index < Degree > ( center , cuts ) ;
const auto & piece = pieces [ static_cast < std : : size_t > ( hot ) ] ;
return binomial_coefficients < Degree > (
piece . coeff , static_cast < uint64_t > ( piece . kappa ) ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief Cleartext value of the selected piece at the wrapped `center + eta`.
/// @tparam Degree degree
/// @tparam InputT input domain type
2026-09-28 05:59:19 -06:00
/// @param center hidden comparison point in the input group
/// @param knots public breakpoints, strictly increasing
2026-09-24 23:18:10 -06:00
/// @param coeff the public coefficient
2026-09-28 05:59:19 -06:00
/// @param eta public offset `eta = x - r`
2026-09-24 23:18:10 -06:00
/// @return Cleartext value of the selected piece at the wrapped `center + eta`
2026-09-28 05:59:19 -06:00
/// \complexity One `offset_horner_clear_coefficients` plus a Horner loop of `Degree + 1` terms (`Degree ≤ 3`).
/// @see grotto::offset_horner_eval
2026-09-24 14:08:32 -06:00
template < std : : size_t Degree , typename InputT >
uint64_t offset_horner_clear (
InputT center ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
InputT eta )
{
const auto c = offset_horner_clear_coefficients < Degree > ( center , knots , coeff , eta ) ;
2026-09-24 15:16:21 -06:00
return offset_horner_detail : : horner_at < Degree > ( c , offset_horner_detail : : lift ( center ) ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief `Party` selects `.first` or `.second` of each key pair.
/// @tparam Party party index, `0` or `1`
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @tparam KeyPair key pair
/// @param keys the party keys
2026-09-28 05:59:19 -06:00
/// @param wrap_share additive split of the keyed payload, indexed by party
/// @param knots public breakpoints, strictly increasing
2026-09-24 23:18:10 -06:00
/// @param coeff the public coefficient
2026-09-28 05:59:19 -06:00
/// @param eta public offset `eta = x - r`
/// @param tokens proof token folded by the segment walk, or null
2026-09-24 23:18:10 -06:00
/// @return `Party` selects `.first` or `.second` of each key pair
2026-09-28 05:59:19 -06:00
/// @throws std::invalid_argument if the key vector is not the single shared comparison
/// \complexity `prepare_pieces` sorts the knots and may insert two cuts. Then one segment walk of the `Degree + 1` lane payload.
/// Each walk is `signed_prefix_parities` over those `P` endpoints (a DPF path of `bitlength(InputT)` levels, reusing the common prefix).
/// Refined pieces: the knot vector, plus the cuts `prepare` / `prepare_pieces` inserts (domain minimum, and the carry threshold when the input width is at most 62 bits). Call that count `P`.
/// Time is one segment walk. Extra space is the piece vectors, `Θ(P · Degree)` words, with `Degree ≤ 3`.
/// \rounds None. `eta` is an argument; this function does not open it.
/// \communication None.
/// \preprocessing None created here. It reads the one comparison from `make_offset_horner_keys`.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
2026-09-24 15:16:21 -06:00
template < std : : size_t Party , std : : size_t Degree , typename InputT , typename KeyPair >
2026-09-24 14:08:32 -06:00
std : : array < uint64_t , Degree + 1 > offset_horner_coefficient_share (
2026-09-24 15:16:21 -06:00
const std : : vector < KeyPair > & keys ,
const std : : array < std : : array < uint64_t , 2 > , Degree + 1 > & wrap_share ,
2026-09-24 14:08:32 -06:00
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
2026-09-28 05:59:19 -06:00
InputT eta , dpf : : proof_token * tokens = nullptr )
2026-09-24 14:08:32 -06:00
{
static_assert ( Party < 2 , " offset horner party is 0 or 1 " ) ;
using namespace offset_horner_detail ;
check_knots ( knots , coeff . size ( ) ) ;
2026-09-28 05:59:19 -06:00
if ( keys . size ( ) ! = 1 )
throw std : : invalid_argument ( " offset horner: one comparison key " ) ;
2026-09-24 15:16:21 -06:00
const auto pieces = prepare_pieces < Degree > ( knots , coeff , eta ) ;
std : : vector < InputT > shifted ( pieces . size ( ) ) ;
std : : vector < std : : array < uint64_t , Degree + 1 > > ordered ( pieces . size ( ) ) ;
std : : vector < int64_t > kappa ( pieces . size ( ) ) ;
for ( std : : size_t i = 0 ; i < pieces . size ( ) ; + + i )
2026-09-24 14:08:32 -06:00
{
2026-09-24 15:16:21 -06:00
shifted [ i ] = pieces [ i ] . knot ;
ordered [ i ] = pieces [ i ] . coeff ;
kappa [ i ] = pieces [ i ] . kappa ;
2026-09-24 14:08:32 -06:00
}
2026-09-28 05:59:19 -06:00
std : : array < uint64_t , Degree + 1 > wrap_party { } ;
2026-09-24 14:08:32 -06:00
for ( std : : size_t m = 0 ; m < = Degree ; + + m )
2026-09-28 05:59:19 -06:00
wrap_party [ m ] = wrap_share [ m ] [ Party ] ;
dpf : : proof_token * pi = ( tokens ! = nullptr ) ? & tokens [ 0 ] : nullptr ;
const auto & key = std : : get < Party > ( keys . front ( ) ) ;
auto seg = segments_lanes < Degree + 1 > ( key , shifted , wrap_party , pi ) ;
if ( tokens ! = nullptr )
2026-09-24 14:08:32 -06:00
{
2026-09-28 05:59:19 -06:00
for ( std : : size_t m = 1 ; m < = Degree ; + + m )
tokens [ m ] = tokens [ 0 ] ;
2026-09-24 14:08:32 -06:00
}
2026-09-24 15:16:21 -06:00
return contributions < Degree > ( seg , ordered , kappa ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief One party's coefficient shares. `Party` is 0 or 1.
2026-09-28 05:59:19 -06:00
/// \complexity `prepare_pieces` sorts the knots and may insert two cuts. Then one segment walk of the `Degree + 1` lane payload.
/// Each walk is `signed_prefix_parities` over those `P` endpoints (a DPF path of `bitlength(InputT)` levels, reusing the common prefix).
/// Refined pieces: the knot vector, plus the cuts `prepare` / `prepare_pieces` inserts (domain minimum, and the carry threshold when the input width is at most 62 bits). Call that count `P`.
/// Time is one segment walk. Extra space is the piece vectors, `Θ(P · Degree)` words, with `Degree ≤ 3`.
/// \rounds None. `eta` is an argument; this function does not open it.
/// \communication None.
/// \preprocessing None created here. It reads the one comparison from `make_offset_horner_keys`.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
template < std : : size_t Party , std : : size_t Degree , typename InputT ,
bool Verifiable = false >
2026-09-24 15:16:21 -06:00
std : : array < uint64_t , Degree + 1 > offset_horner_coefficient_share (
2026-09-28 05:59:19 -06:00
const offset_horner_keys < InputT , Degree , Verifiable > & mat ,
2026-09-24 15:16:21 -06:00
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
2026-09-28 05:59:19 -06:00
InputT eta , dpf : : proof_token * tokens = nullptr )
2026-09-24 15:16:21 -06:00
{
2026-09-28 05:59:19 -06:00
std : : vector < typename offset_horner_keys < InputT , Degree , Verifiable > : : key_pair > keys {
mat . keys } ;
2026-09-24 15:16:21 -06:00
return offset_horner_coefficient_share < Party , Degree > (
2026-09-28 05:59:19 -06:00
keys , mat . wrap_share , knots , coeff , eta , tokens ) ;
2026-09-24 15:16:21 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief Both parties' Horner shares from one joint Doerner– Shelat generation.
/// @details `center0 XOR center1` is the comparison point, in geneval's share
2026-09-24 15:16:21 -06:00
/// convention (the signed MSB of `center0` is flipped before the XOR, and
/// flipped back here). `eta` is already public.
2026-09-24 23:18:10 -06:00
/// @tparam Degree degree
/// @tparam InputT input domain type
2026-09-24 15:16:21 -06:00
template < std : : size_t Degree , typename InputT >
struct geneval_offset_horner_result
{
static_assert ( Degree < = offset_horner_max_degree , " offset horner degree is at most 3 " ) ;
2026-09-28 05:59:19 -06:00
/// @brief Public offset `eta = x - r`. F_Horner leaks only this.
2026-09-24 15:16:21 -06:00
InputT eta { } ;
std : : array < uint64_t , Degree + 1 > coeff0 { } ;
std : : array < uint64_t , Degree + 1 > coeff1 { } ;
uint64_t value0 = 0 ;
uint64_t value1 = 0 ;
2026-09-28 05:59:19 -06:00
std : : array < dpf : : proof_token , Degree + 1 > proof0 { } ;
std : : array < dpf : : proof_token , Degree + 1 > proof1 { } ;
2026-09-24 15:16:21 -06:00
} ;
2026-09-24 23:18:10 -06:00
/// @brief Logical comparison point for geneval's XOR shares. Matches `make_dpf(P)`
2026-09-24 15:16:21 -06:00
/// when `center1 = P XOR center0` or when `center0 = P` and `center1 = 0`.
2026-09-28 05:59:19 -06:00
/// @details Dealer / joint-simulator helper only. F_Horner does not return the
/// center to the parties; `center^m` stays a shared payload.
2026-09-24 23:18:10 -06:00
/// @tparam InputT input domain type
2026-09-28 05:59:19 -06:00
/// @param center0 party 0 share of the center
/// @param center1 party 1 share of the center
2026-09-24 23:18:10 -06:00
/// @return Logical comparison point for geneval's XOR shares
2026-09-28 05:59:19 -06:00
/// \complexity Two `flip_msb_if_signed_integral` calls and one XOR of the input shares. `Θ(1)`.
/// @note Dealer-side helper. The signed MSB of `center0` is flipped before the XOR and flipped back, matching `make_dpf`.
/// @see grotto::geneval_offset_horner
2026-09-24 15:16:21 -06:00
template < typename InputT >
InputT geneval_offset_horner_center ( InputT center0 , InputT center1 )
{
InputT flipped0 = center0 ;
dpf : : utils : : flip_msb_if_signed_integral ( flipped0 ) ;
InputT mixed = dpf : : utils : : xor_input_shares ( flipped0 , center1 ) ;
dpf : : utils : : flip_msb_if_signed_integral ( mixed ) ;
return mixed ;
}
namespace offset_horner_detail
{
template < std : : size_t Degree , typename InputT , typename Rng >
geneval_offset_horner_result < Degree , InputT > geneval_at (
2026-09-24 20:44:07 -06:00
bool arith , InputT center0 , InputT center1 , InputT center , InputT eta ,
2026-09-24 15:16:21 -06:00
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
Rng rng )
{
check_knots ( knots , coeff . size ( ) ) ;
const auto pieces = prepare_pieces < Degree > ( knots , coeff , eta ) ;
std : : vector < InputT > shifted ( pieces . size ( ) ) ;
std : : vector < std : : array < uint64_t , Degree + 1 > > ordered ( pieces . size ( ) ) ;
std : : vector < int64_t > kappa ( pieces . size ( ) ) ;
for ( std : : size_t i = 0 ; i < pieces . size ( ) ; + + i )
{
shifted [ i ] = pieces [ i ] . knot ;
ordered [ i ] = pieces [ i ] . coeff ;
kappa [ i ] = pieces [ i ] . kappa ;
}
uint64_t payload [ Degree + 1 ] ;
fill_payloads < Degree > ( lift ( center ) , payload ) ;
std : : array < std : : array < uint64_t , 2 > , Degree + 1 > wrap { } ;
std : : array < std : : vector < uint64_t > , Degree + 1 > seg0 ;
std : : array < std : : vector < uint64_t > , Degree + 1 > seg1 ;
2026-09-28 05:59:19 -06:00
std : : array < dpf : : proof_token , Degree + 1 > out_proofs0 { } ;
std : : array < dpf : : proof_token , Degree + 1 > out_proofs1 { } ;
using Vec = dpf : : vec < uint64_t , Degree + 1 > ;
const auto beta = payload_vec < InputT , Degree > ( payload ) ;
auto open_lanes = [ & ] ( auto & & keys ) {
const auto & k0 = keys . first ;
const auto & k1 = keys . second ;
const uint64_t mask = k0 . cmp ( ) . mask ;
dpf : : detail : : vdpf : : init_proof ( out_proofs0 [ 0 ] , k0 ) ;
dpf : : detail : : vdpf : : init_proof ( out_proofs1 [ 0 ] , k1 ) ;
auto path0 = dpf : : make_basic_path_memoizer ( k0 ) ;
auto path1 = dpf : : make_basic_path_memoizer ( k1 ) ;
std : : vector < Vec > pref0 ( shifted . size ( ) ) ;
std : : vector < Vec > pref1 ( shifted . size ( ) ) ;
for ( std : : size_t i = 0 ; i < shifted . size ( ) ; + + i )
{
auto tx0 = k0 . offset_x ( shifted [ i ] ) ;
auto tx1 = k1 . offset_x ( shifted [ i ] ) ;
dpf : : utils : : flip_msb_if_signed_integral ( tx0 ) ;
dpf : : utils : : flip_msb_if_signed_integral ( tx1 ) ;
pref0 [ i ] = dpf : : detail : : incr : : eval_payload_path_sum < Vec > (
k0 , tx0 , path0 , false , 0 , & out_proofs0 [ 0 ] ) ;
pref1 [ i ] = dpf : : detail : : incr : : eval_payload_path_sum < Vec > (
k1 , tx1 , path1 , false , 0 , & out_proofs1 [ 0 ] ) ;
}
dpf : : detail : : vdpf : : fold_output_binding ( out_proofs0 [ 0 ] , k0 ) ;
dpf : : detail : : vdpf : : fold_output_binding ( out_proofs1 [ 0 ] , k1 ) ;
for ( std : : size_t m = 1 ; m < = Degree ; + + m )
{
out_proofs0 [ m ] = out_proofs0 [ 0 ] ;
out_proofs1 [ m ] = out_proofs1 [ 0 ] ;
}
for ( std : : size_t m = 0 ; m < = Degree ; + + m )
{
const uint64_t blind = dpf : : uniform_sample < uint64_t > ( ) ;
wrap [ m ] [ 0 ] = blind ;
wrap [ m ] [ 1 ] = payload [ m ] - blind ;
std : : vector < uint64_t > lane0 ( shifted . size ( ) ) ;
std : : vector < uint64_t > lane1 ( shifted . size ( ) ) ;
for ( std : : size_t i = 0 ; i < shifted . size ( ) ; + + i )
{
lane0 [ i ] = pref0 [ i ] [ m ] ;
lane1 [ i ] = pref1 [ i ] [ m ] ;
}
seg0 [ m ] = segments_from_prefixes ( lane0 , wrap [ m ] [ 0 ] , mask ) ;
seg1 [ m ] = segments_from_prefixes ( lane1 , wrap [ m ] [ 1 ] , mask ) ;
}
} ;
if ( arith )
open_lanes ( dpf : : make_dpf_doerner_shelat ( dpf : : arith_input , center0 , center1 ,
rng , dpf : : gt ( beta ) , dpf : : verifiable { } ) ) ;
else
open_lanes ( dpf : : make_dpf_doerner_shelat ( center0 , center1 ,
rng , dpf : : gt ( beta ) , dpf : : verifiable { } ) ) ;
2026-09-24 15:16:21 -06:00
geneval_offset_horner_result < Degree , InputT > out ;
out . eta = eta ;
out . coeff0 = contributions < Degree > ( seg0 , ordered , kappa ) ;
out . coeff1 = contributions < Degree > ( seg1 , ordered , kappa ) ;
2026-09-28 05:59:19 -06:00
out . proof0 = out_proofs0 ;
out . proof1 = out_proofs1 ;
2026-09-24 15:16:21 -06:00
for ( uint64_t term : out . coeff0 )
out . value0 + = term ;
for ( uint64_t term : out . coeff1 )
out . value1 + = term ;
2026-09-24 14:08:32 -06:00
return out ;
}
2026-09-24 20:44:07 -06:00
template < std : : size_t Degree , typename InputT , typename Rng >
geneval_offset_horner_result < Degree , InputT > geneval_at (
InputT center0 , InputT center1 , InputT center , InputT eta ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
Rng rng )
{
return geneval_at < Degree > ( false , center0 , center1 , center , eta , knots , coeff ,
std : : move ( rng ) ) ;
}
2026-09-24 15:16:21 -06:00
} // namespace offset_horner_detail
2026-09-24 23:18:10 -06:00
/// @brief Geneval-style offset Horner. The center is XOR-shared as in `geneval_point`.
/// @details Comparison keys are opened with the same local Doerner– Shelat protocol
2026-09-24 15:16:21 -06:00
/// geneval uses for its correction words. The value dot uses the per-piece
/// carry shift and is local.
2026-09-28 05:59:19 -06:00
/// A comparison value word is written on every level of the secret path.
/// The seed spine is generated once for the whole power vector.
2026-09-24 23:18:10 -06:00
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @tparam Rng rng
2026-09-28 05:59:19 -06:00
/// @param center0 party 0 share of the center
/// @param center1 party 1 share of the center
/// @param eta public offset `eta = x - r`
/// @param knots public breakpoints, strictly increasing
2026-09-24 23:18:10 -06:00
/// @param coeff the public coefficient
/// @param rng the Doerner– Shelat randomness tapes
/// @return Geneval-style offset Horner
2026-09-28 05:59:19 -06:00
/// \complexity One Doerner– Shelat generation of a `Degree + 1` lane comparison (`Degree ≤ 3`), then the same local piece dot as `offset_horner_eval`.
/// The dot is `Θ(P · Degree)` after those calls. `P` is the refined piece count.
/// @note Rounds and bandwidth of each `geneval_cmp` live in the DPF headers, not in this function, so they are not stated here.
/// \preprocessing The randomness object the caller passes (`Rng`). This function also samples one `uint64_t` blind per power.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
2026-09-24 15:16:21 -06:00
template < std : : size_t Degree = offset_horner_max_degree ,
typename InputT ,
typename Rng >
geneval_offset_horner_result < Degree , InputT > geneval_offset_horner (
InputT center0 , InputT center1 , InputT eta ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
Rng rng )
{
const InputT center = geneval_offset_horner_center ( center0 , center1 ) ;
return offset_horner_detail : : geneval_at < Degree > (
center0 , center1 , center , eta , knots , coeff , std : : move ( rng ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity One Doerner– Shelat generation of a `Degree + 1` lane comparison (`Degree ≤ 3`), then the same local piece dot as `offset_horner_eval`.
/// The dot is `Θ(P · Degree)` after those calls. `P` is the refined piece count.
/// @note Rounds and bandwidth of each `geneval_cmp` live in the DPF headers, not in this function, so they are not stated here.
/// \preprocessing The randomness object the caller passes (`Rng`). This function also samples one `uint64_t` blind per power.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
2026-09-24 15:16:21 -06:00
template < std : : size_t Degree = offset_horner_max_degree , typename InputT >
geneval_offset_horner_result < Degree , InputT > geneval_offset_horner (
InputT center0 , InputT center1 , InputT eta ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff )
{
using block = typename dpf : : prg : : aes128 : : block_type ;
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
dpf : : ds_randomness < block ( * ) ( ) , dpf : : detail : : urandom_pad_rng > rng {
dpf : : uniform_sample < block > , { } } ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic pop )
2026-09-24 15:16:21 -06:00
return geneval_offset_horner < Degree > (
center0 , center1 , eta , knots , coeff , std : : move ( rng ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief Additive shares of the center: `center0 + center1` is the comparison point.
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @tparam Rng rng
2026-09-28 05:59:19 -06:00
/// @param center0 party 0 share of the center
/// @param center1 party 1 share of the center
/// @param eta public offset `eta = x - r`
/// @param knots public breakpoints, strictly increasing
2026-09-24 23:18:10 -06:00
/// @param coeff the public coefficient
/// @param rng the Doerner– Shelat randomness tapes
/// @return Additive shares of the center: `center0 + center1` is the comparison point
2026-09-28 05:59:19 -06:00
/// \complexity One Doerner– Shelat generation of a `Degree + 1` lane comparison (`Degree ≤ 3`), then the same local piece dot as `offset_horner_eval`.
/// The dot is `Θ(P · Degree)` after those calls. `P` is the refined piece count.
/// @note Rounds and bandwidth of each `geneval_cmp` live in the DPF headers, not in this function, so they are not stated here.
/// \preprocessing The randomness object the caller passes (`Rng`). This function also samples one `uint64_t` blind per power.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
2026-09-24 20:44:07 -06:00
template < std : : size_t Degree = offset_horner_max_degree ,
typename InputT ,
typename Rng >
geneval_offset_horner_result < Degree , InputT > geneval_offset_horner (
dpf : : arith_input_t , InputT center0 , InputT center1 , InputT eta ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
Rng rng )
{
const InputT center = offset_horner_group_add ( center0 , center1 ) ;
return offset_horner_detail : : geneval_at < Degree > (
true , center0 , center1 , center , eta , knots , coeff , std : : move ( rng ) ) ;
}
2026-09-28 05:59:19 -06:00
/// @brief Additive shares of the input `x` and the mask `r`.
/// @details Opens only `eta = (x0 - r0) + (x1 - r1)`. Passes additive shares
/// of `center = 2r` (`2·r0`, `2·r1`) to arithmetic `geneval_cmp`.
/// The clear center is used only to plant shared `center^m` payloads;
/// it is not returned. F_Horner leaks only `eta`.
2026-09-24 23:18:10 -06:00
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @tparam Rng rng
2026-09-28 05:59:19 -06:00
/// @param x0 party 0 share of the input
/// @param x1 party 1 share of the input
2026-09-24 23:18:10 -06:00
/// @param r0 the party 0's share of the mask
/// @param r1 the party 1's share of the mask
2026-09-28 05:59:19 -06:00
/// @param knots public breakpoints, strictly increasing
2026-09-24 23:18:10 -06:00
/// @param coeff the public coefficient
/// @param rng the Doerner– Shelat randomness tapes
2026-09-28 05:59:19 -06:00
/// @return Both parties' Horner shares and the public `eta`
/// \complexity One Doerner– Shelat generation of a `Degree + 1` lane comparison (`Degree ≤ 3`), then the same local piece dot as `offset_horner_eval`.
/// The dot is `Θ(P · Degree)` after those calls. `P` is the refined piece count.
/// @note Rounds and bandwidth of each `geneval_cmp` live in the DPF headers, not in this function, so they are not stated here.
/// \preprocessing The randomness object the caller passes (`Rng`). This function also samples one `uint64_t` blind per power.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
2026-09-24 15:16:21 -06:00
template < std : : size_t Degree = offset_horner_max_degree ,
typename InputT ,
typename Rng >
geneval_offset_horner_result < Degree , InputT > geneval_offset_horner (
InputT x0 , InputT x1 , InputT r0 , InputT r1 ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
Rng rng )
{
2026-09-28 05:59:19 -06:00
// Open only eta; do not form clear x or r.
const InputT eta = offset_horner_group_add (
offset_horner_group_sub ( x0 , r0 ) ,
offset_horner_group_sub ( x1 , r1 ) ) ;
2026-09-24 20:44:07 -06:00
const InputT center0 = offset_horner_group_add ( r0 , r0 ) ;
const InputT center1 = offset_horner_group_add ( r1 , r1 ) ;
2026-09-28 05:59:19 -06:00
// Payload planting for the joint simulator; not returned to parties.
2026-09-24 20:44:07 -06:00
const InputT center = offset_horner_group_add ( center0 , center1 ) ;
2026-09-24 15:16:21 -06:00
return offset_horner_detail : : geneval_at < Degree > (
2026-09-24 20:44:07 -06:00
true , center0 , center1 , center , eta , knots , coeff , std : : move ( rng ) ) ;
2026-09-24 15:16:21 -06:00
}
2026-09-28 05:59:19 -06:00
/// \complexity One Doerner– Shelat generation of a `Degree + 1` lane comparison (`Degree ≤ 3`), then the same local piece dot as `offset_horner_eval`.
/// The dot is `Θ(P · Degree)` after those calls. `P` is the refined piece count.
/// @note Rounds and bandwidth of each `geneval_cmp` live in the DPF headers, not in this function, so they are not stated here.
/// \preprocessing The randomness object the caller passes (`Rng`). This function also samples one `uint64_t` blind per power.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
2026-09-24 15:16:21 -06:00
template < std : : size_t Degree = offset_horner_max_degree , typename InputT >
geneval_offset_horner_result < Degree , InputT > geneval_offset_horner (
InputT x0 , InputT x1 , InputT r0 , InputT r1 ,
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff )
{
using block = typename dpf : : prg : : aes128 : : block_type ;
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
dpf : : ds_randomness < block ( * ) ( ) , dpf : : detail : : urandom_pad_rng > rng {
dpf : : uniform_sample < block > , { } } ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic pop )
2026-09-24 15:16:21 -06:00
return geneval_offset_horner < Degree > (
x0 , x1 , r0 , r1 , knots , coeff , std : : move ( rng ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief One party's share of the cubic at the wrapped `center + eta`.
/// @details Sum the coefficient shares; they are already scaled by `center^k`.
/// @tparam Party party index, `0` or `1`
/// @tparam Degree degree
/// @tparam InputT input domain type
2026-09-28 05:59:19 -06:00
/// @param mat dealer keys
/// @param knots public breakpoints, strictly increasing
2026-09-24 23:18:10 -06:00
/// @param coeff the public coefficient
2026-09-28 05:59:19 -06:00
/// @param eta public offset `eta = x - r`
/// @param tokens proof token folded by the segment walk, or null
2026-09-24 23:18:10 -06:00
/// @return One party's share of the cubic at the wrapped `center + eta`
2026-09-28 05:59:19 -06:00
/// \complexity `prepare_pieces` sorts the knots and may insert two cuts. Then one segment walk of the `Degree + 1` lane payload.
/// Each walk is `signed_prefix_parities` over those `P` endpoints (a DPF path of `bitlength(InputT)` levels, reusing the common prefix).
/// Refined pieces: the knot vector, plus the cuts `prepare` / `prepare_pieces` inserts (domain minimum, and the carry threshold when the input width is at most 62 bits). Call that count `P`.
/// Time is one segment walk. Extra space is the piece vectors, `Θ(P · Degree)` words, with `Degree ≤ 3`.
/// \rounds None. `eta` is an argument; this function does not open it.
/// \communication None.
/// \preprocessing None created here. It reads the one comparison from `make_offset_horner_keys`.
/// @see grotto::offset_poly_eval
/// @see grotto::offset_jet_eval
template < std : : size_t Party , std : : size_t Degree , typename InputT ,
bool Verifiable = false >
2026-09-24 14:08:32 -06:00
uint64_t offset_horner_eval (
2026-09-28 05:59:19 -06:00
const offset_horner_keys < InputT , Degree , Verifiable > & mat ,
2026-09-24 14:08:32 -06:00
const std : : vector < InputT > & knots ,
const std : : vector < std : : array < uint64_t , Degree + 1 > > & coeff ,
2026-09-28 05:59:19 -06:00
InputT eta , dpf : : proof_token * tokens = nullptr )
2026-09-24 14:08:32 -06:00
{
2026-09-28 05:59:19 -06:00
const auto shares = offset_horner_coefficient_share < Party , Degree > (
mat , knots , coeff , eta , tokens ) ;
2026-09-24 15:16:21 -06:00
uint64_t value = 0 ;
for ( uint64_t term : shares )
value + = term ;
return value ;
2026-09-24 14:08:32 -06:00
}
} // namespace grotto
# endif // LIBDPF_INCLUDE_GROTTO_OFFSET_HORNER_HPP__