2026-09-24 14:08:32 -06:00
/// @file dpf/doerner_shelat.hpp
/// @brief Doerner– Shelat generation of a dealer DPF key.
2026-09-24 20:44:07 -06:00
/// @details Two shares of the point are walked level by level — XOR shares by
2026-09-28 05:59:19 -06:00
/// default. `arith_input` holds additive shares of the point. A beaver
/// ripple-carry converts them to XOR shares of the sum bits, and that
/// sharing is what the walk consumes. The sum is not opened.
2026-09-24 20:44:07 -06:00
/// Correction words, advice bits, seeds, and leaves are the ones
2026-09-28 05:59:19 -06:00
/// `make_dpf` would emit for that point, the same roots, and the same
/// beaver coins. Beaver pads used to hide a path bit cancel and are
/// not part of the key. Pad randomness must not come from
/// `uniform_fill` if the beaver tape is being matched.
/// @note Following Jack Doerner and abhi shelat, CCS 2017 (ePrint 2017/827): one correction word opened per level from shares of the point.
/// @note Guo, Yang, Wang, Zhang, Xie, Zhang, and Liu (ePrint 2022/1431, §5.2) generate a DPF in the COT/OLE hybrid in n+3 rounds, with no beaver-pad dealer. This header uses that dealer tape and one opening round per level.
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_DOERNER_SHELAT_HPP__
# define LIBDPF_INCLUDE_DPF_DOERNER_SHELAT_HPP__
# include <cstddef>
# include <cstdint>
2026-09-28 05:59:19 -06:00
# include <cstring>
2026-09-24 14:08:32 -06:00
# include <type_traits>
# include <utility>
2026-09-28 05:59:19 -06:00
# include <vector>
2026-09-24 14:08:32 -06:00
# include "hedley/hedley.h"
# include "simde/simde/x86/avx2.h"
# include "dpf/dpf_key.hpp"
2026-09-28 05:59:19 -06:00
# include "dpf/experiment_note.hpp"
2026-09-24 14:08:32 -06:00
# include "dpf/random.hpp"
# include "dpf/dcf.hpp"
2026-09-24 23:18:10 -06:00
# include "dpf/constrained_cmp.hpp"
2026-09-28 05:59:19 -06:00
# include "dpf/beaver.hpp"
# include "dpf/xor_wrapper.hpp"
# include "dpf/leaf_node.hpp"
# include "dpf/leaf_arithmetic.hpp"
2026-09-24 14:08:32 -06:00
namespace dpf
{
2026-09-24 23:18:10 -06:00
/// @brief Tag: Doerner– Shelat / geneval takes additive shares of the point
2026-09-24 20:44:07 -06:00
/// (`x0 + x1` in the input ring). Default calls take XOR shares.
struct arith_input_t
{
} ;
inline constexpr arith_input_t arith_input { } ;
2026-09-24 23:18:10 -06:00
/// @brief Tag: payload β is additively shared (`y0 + y1`). Leaf CW is opened via
/// Π_CCMP on the on-path control bits (see `open_arith_leaf`).
/// @see `open_arith_leaf`
struct arith_output_t
{
} ;
inline constexpr arith_output_t arith_output { } ;
/// @brief Additive (or XOR) shares of one concrete payload for dealerless leaf open.
/// @details Use as a placed value / `at<>` element when several outputs are shared.
/// @tparam T value type
template < typename T >
struct arith_beta
{
using payload_type = T ;
T y0 { } ;
T y1 { } ;
} ;
template < typename T >
struct is_arith_beta : std : : false_type
{
} ;
template < typename T >
struct is_arith_beta < arith_beta < T > > : std : : true_type
{
} ;
template < typename T >
inline constexpr bool is_arith_beta_v = is_arith_beta < std : : decay_t < T > > : : value ;
namespace detail
{
namespace incr
{
/// @brief `placed<N, arith_beta<T>>::output_type` is `T` (see placement.hpp).
/// @tparam T value type
template < typename T >
struct unwrap_placed_output < arith_beta < T > >
{
using type = T ;
} ;
} // namespace incr
} // namespace detail
/// @brief Roots and the Beaver-pad stream for one Doerner– Shelat generation.
/// @details `root` is called twice, same as `make_dpf`: party 0 clears the low bit of
2026-09-24 14:08:32 -06:00
/// the first sample, party 1 sets the low bit of the second.
2026-09-24 23:18:10 -06:00
/// @tparam RootSampler sampler for the Doerner– Shelat root seed
/// @tparam PadRng pad stream for the Doerner– Shelat protocol
2026-09-24 14:08:32 -06:00
template < typename RootSampler , typename PadRng >
struct ds_randomness
{
RootSampler root ;
PadRng pad ;
} ;
2026-09-28 05:59:19 -06:00
/// @brief Pad stream whose `block()` and `bit()` come from one PRG seed.
/// @details Drop-in for `detail::urandom_pad_rng` on Doerner– Shelat dealers.
/// `pseudorandom_root_sampler<PRG>` is the matching root source.
template < typename PRG = dpf : : prg : : aes128 >
struct prg_pad_rng
{
using block_type = typename PRG : : block_type ;
explicit prg_pad_rng ( block_type seed = dpf : : uniform_sample < block_type > ( ) )
: seed_ ( seed )
{
note_experiment_seed ( " prg_pad_rng " , seed_ ) ;
}
block_type block ( )
{
return PRG : : eval ( seed_ , n_ + + ) ;
}
uint8_t bit ( )
{
const block_type drawn = block ( ) ;
unsigned char low = 0 ;
std : : memcpy ( & low , & drawn , 1 ) ;
return static_cast < uint8_t > ( low & 1u ) ;
}
const block_type & seed ( ) const noexcept { return seed_ ; }
private :
block_type seed_ { } ;
std : : uint32_t n_ = 0 ;
} ;
2026-09-24 14:08:32 -06:00
namespace detail
{
struct urandom_pad_rng
{
simde__m128i block ( )
{
return dpf : : uniform_sample < simde__m128i > ( ) ;
}
uint8_t bit ( )
{
return static_cast < uint8_t > ( dpf : : uniform_sample < unsigned char > ( ) & 1u ) ;
}
} ;
struct ds_cw_party
{
simde__m128i rand ;
simde__m128i gamma ;
uint8_t bit ;
} ;
struct ds_cw_pads
{
ds_cw_party p0 ;
ds_cw_party p1 ;
} ;
struct ds_blind
{
simde__m128i msg ;
uint8_t bit ;
} ;
struct ds_and_pads
{
uint8_t a0 ;
uint8_t a1 ;
simde__m128i b0_share , b1_share , c0_share , c1_share ;
} ;
struct ds_and_shares
{
simde__m128i z0 ;
simde__m128i z1 ;
} ;
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
HEDLEY_ALWAYS_INLINE
simde__m128i ds_xor ( simde__m128i a , simde__m128i b ) noexcept
{
return simde_mm_xor_si128 ( a , b ) ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
HEDLEY_ALWAYS_INLINE
simde__m128i ds_gate ( uint8_t bit , simde__m128i block ) noexcept
{
return dpf : : get_if ( block , bit & 1u ) ;
}
template < typename PadRng >
ds_cw_pads ds_sample_cw ( PadRng & pad )
{
2026-09-28 05:59:19 -06:00
// Ideal (semi-honest): each party holds (rand, bit, gamma) with
// gamma0 ⊕ gamma1 = (bit1 · rand0) ⊕ (bit0 · rand1).
// The full product (bit1 · rand0) is NOT given to party 0: that would
// leak bit1, and with the opened blind bit = path1 ⊕ bit1 it would
// open the peer path bit (and thus α under an oblivious walk). Shares
// of the XOR of both products hide both pad bits from each party.
// The product share is a beaver session over the XOR ring; the clear
// bit and block stay with the party that owns them.
using Ring = dpf : : xor_wrapper < simde_uint128 > ;
using traits = dpf : : beavers : : ring_traits < Ring > ;
auto to_ring = [ ] ( simde__m128i block ) {
simde_uint128 raw { } ;
std : : memcpy ( & raw , & block , sizeof ( block ) ) ;
return Ring { raw } ;
} ;
auto to_block = [ ] ( const Ring & ring ) {
simde__m128i block { } ;
const auto raw = static_cast < typename Ring : : value_type > ( ring ) ;
std : : memcpy ( & block , & raw , sizeof ( block ) ) ;
return block ;
} ;
const Ring rand0 = to_ring ( pad . block ( ) ) ;
const Ring rand1 = to_ring ( pad . block ( ) ) ;
const bool bit0 = ( pad . bit ( ) & 1u ) ! = 0 ;
const bool bit1 = ( pad . bit ( ) & 1u ) ! = 0 ;
auto sampler = [ & pad , & to_ring ] ( ) - > Ring {
return to_ring ( pad . block ( ) ) ;
} ;
dpf : : beavers : : session < Ring > s ;
auto b0 = s . bit ( ) ;
auto b1 = s . bit ( ) ;
auto r0 = s . input ( ) ;
auto r1 = s . input ( ) ;
auto prod = s ( b1 * r0 + b0 * r1 ) ;
s . pin ( prod ) ;
s . sample ( sampler ) ;
s . bind ( b0 , bit0 ? traits : : one ( ) : traits : : zero ( ) , sampler ) ;
s . bind ( b1 , bit1 ? traits : : one ( ) : traits : : zero ( ) , sampler ) ;
s . bind ( r0 , rand0 , sampler ) ;
s . bind ( r1 , rand1 , sampler ) ;
s . evaluate ( ) ;
const auto gamma = s . value ( prod ) ;
2026-09-24 14:08:32 -06:00
ds_cw_pads p { } ;
2026-09-28 05:59:19 -06:00
p . p0 . rand = to_block ( rand0 ) ;
p . p1 . rand = to_block ( rand1 ) ;
p . p0 . bit = static_cast < uint8_t > ( bit0 ) ;
p . p1 . bit = static_cast < uint8_t > ( bit1 ) ;
p . p0 . gamma = to_block ( gamma . p0 ) ;
p . p1 . gamma = to_block ( gamma . p1 ) ;
2026-09-24 14:08:32 -06:00
return p ;
}
2026-09-28 05:59:19 -06:00
/// @brief Pack a XOR-ring `bit_mul` into the classical DS AND pad shape.
/// @tparam Ring XOR ring whose unit is the all-ones word
/// @param bm the sampled bit-mul material
/// @param a0_share party 0's XOR share of the opened bit
/// @return pads ready for `ds_and_open`
template < typename Ring >
ds_and_pads ds_and_from_bit_mul ( const dpf : : beavers : : bit_mul_beaver < Ring > & bm ,
uint8_t a0_share )
2026-09-24 14:08:32 -06:00
{
2026-09-28 05:59:19 -06:00
using traits = dpf : : beavers : : ring_traits < Ring > ;
const Ring opened = bm . bit . open ( ) ;
const uint8_t a = ( opened = = traits : : one ( ) ) ? uint8_t { 1 } : uint8_t { 0 } ;
2026-09-24 14:08:32 -06:00
ds_and_pads p { } ;
2026-09-28 05:59:19 -06:00
p . a0 = static_cast < uint8_t > ( a0_share & 1u ) ;
2026-09-24 14:08:32 -06:00
p . a1 = static_cast < uint8_t > ( a ^ p . a0 ) ;
2026-09-28 05:59:19 -06:00
auto to_block = [ ] ( const Ring & r ) {
simde__m128i b { } ;
const auto v = static_cast < typename Ring : : value_type > ( r ) ;
static_assert ( sizeof ( v ) = = sizeof ( simde__m128i ) ,
" ds AND packs a 128-bit XOR ring into an AES block " ) ;
std : : memcpy ( & b , & v , sizeof ( b ) ) ;
return b ;
} ;
p . b0_share = to_block ( bm . scalar . p0 ) ;
p . b1_share = to_block ( bm . scalar . p1 ) ;
p . c0_share = to_block ( bm . product . p0 ) ;
p . c1_share = to_block ( bm . product . p1 ) ;
2026-09-24 14:08:32 -06:00
return p ;
}
2026-09-28 05:59:19 -06:00
/// @brief Sample one DS AND from `sample_bit_mul`, driven by `pad` or `rng`.
/// @tparam PadRng pad stream with `block()` / `bit()`
/// @tparam Sample callable returning a 128-bit XOR ring element
/// @param pad the Doerner– Shelat pad stream (bit for the clear Beaver bit)
/// @param rng ring sampler for `sample_bit_mul` (defaults to `pad.block()`)
/// @return classical AND pads for `ds_and_open`
template < typename PadRng , typename Sample >
ds_and_pads ds_sample_and ( PadRng & pad , Sample & & rng )
{
using Ring = dpf : : xor_wrapper < simde_uint128 > ;
using traits = dpf : : beavers : : ring_traits < Ring > ;
auto & ring_rng = rng ;
std : : size_t phase = 0 ;
auto sampler = [ & ] ( ) - > Ring {
if ( phase = = 0 )
{
+ + phase ;
return ( pad . bit ( ) & 1u ) ? traits : : one ( ) : traits : : zero ( ) ;
}
+ + phase ;
return ring_rng ( ) ;
} ;
auto bm = dpf : : beavers : : sample_bit_mul < Ring > ( sampler ) ;
const uint8_t a0 = static_cast < uint8_t > ( pad . bit ( ) & 1u ) ;
return ds_and_from_bit_mul ( bm , a0 ) ;
}
template < typename PadRng >
ds_and_pads ds_sample_and ( PadRng & pad )
{
using Ring = dpf : : xor_wrapper < simde_uint128 > ;
auto from_pad = [ & pad ] ( ) - > Ring {
const simde__m128i b = pad . block ( ) ;
simde_uint128 v { } ;
std : : memcpy ( & v , & b , sizeof ( b ) ) ;
return Ring { v } ;
} ;
return ds_sample_and ( pad , from_pad ) ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
HEDLEY_ALWAYS_INLINE
simde__m128i ds_cw_share ( simde__m128i L , simde__m128i R , uint8_t my_bit ,
const ds_cw_party & mine , const ds_blind & their ) noexcept
{
simde__m128i out = ds_xor ( R , mine . gamma ) ;
if ( my_bit & 1u )
{
out = ds_xor ( out , ds_xor ( ds_xor ( L , R ) , their . msg ) ) ;
}
if ( their . bit & 1u )
{
out = ds_xor ( out , mine . rand ) ;
}
return out ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
inline void ds_cw_blinds ( const ds_cw_pads & p ,
simde__m128i L0 , simde__m128i R0 , uint8_t bit0 ,
simde__m128i L1 , simde__m128i R1 , uint8_t bit1 ,
ds_blind & b0 , ds_blind & b1 ) noexcept
{
b0 . bit = static_cast < uint8_t > ( bit0 ^ p . p0 . bit ) ;
b1 . bit = static_cast < uint8_t > ( bit1 ^ p . p1 . bit ) ;
b0 . msg = ds_xor ( ds_xor ( L0 , R0 ) , p . p0 . rand ) ;
b1 . msg = ds_xor ( ds_xor ( L1 , R1 ) , p . p1 . rand ) ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
inline simde__m128i ds_cw_outs ( const ds_cw_pads & p ,
simde__m128i L0 , simde__m128i R0 , uint8_t bit0 ,
simde__m128i L1 , simde__m128i R1 , uint8_t bit1 ,
const ds_blind & b0 , const ds_blind & b1 ) noexcept
{
return ds_xor (
ds_cw_share ( L0 , R0 , bit0 , p . p0 , b1 ) ,
ds_cw_share ( L1 , R1 , bit1 , p . p1 , b0 ) ) ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
inline uint8_t ds_open_advice ( simde__m128i L0 , simde__m128i R0 , uint8_t bit0 ,
simde__m128i L1 , simde__m128i R1 , uint8_t bit1 ) noexcept
{
const uint8_t a00 = static_cast < uint8_t > ( dpf : : get_lo_bit ( L0 ) ^ bit0 ) ;
const uint8_t a01 = static_cast < uint8_t > ( dpf : : get_lo_bit ( R0 ) ^ bit0 ) ;
const uint8_t a10 = static_cast < uint8_t > ( dpf : : get_lo_bit ( L1 ) ^ bit1 ) ;
const uint8_t a11 = static_cast < uint8_t > ( dpf : : get_lo_bit ( R1 ) ^ bit1 ) ;
const uint8_t t0 = static_cast < uint8_t > ( a00 ^ a10 ^ 1u ) ;
const uint8_t t1 = static_cast < uint8_t > ( a01 ^ a11 ) ;
return static_cast < uint8_t > ( ( t1 < < 1 ) | ( t0 & 1u ) ) ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
inline void ds_next_terms ( simde__m128i L , simde__m128i R , uint8_t advice ,
simde__m128i cw , uint8_t tpack , simde__m128i & M , simde__m128i & base ) noexcept
{
const simde__m128i D = ds_xor ( L , R ) ;
const uint8_t t0 = static_cast < uint8_t > ( tpack & 1u ) ;
const uint8_t t1 = static_cast < uint8_t > ( ( tpack > > 1 ) & 1u ) ;
const simde__m128i lo = dpf : : set_lo_bit ( simde_mm_setzero_si128 ( ) , 1 ) ;
const simde__m128i DT = ds_gate ( static_cast < uint8_t > ( t0 ^ t1 ) , lo ) ;
const simde__m128i cw_base = ds_xor ( dpf : : unset_lo_bit ( cw ) , ds_gate ( t0 , lo ) ) ;
M = ( advice & 1u ) ? ds_xor ( D , DT ) : D ;
base = ( advice & 1u ) ? ds_xor ( L , cw_base ) : L ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
inline ds_and_shares ds_and_open ( const ds_and_pads & p , simde__m128i M ,
uint8_t b_recv ) noexcept
{
const simde__m128i e = ds_xor ( ds_xor ( M , p . b0_share ) , p . b1_share ) ;
const uint8_t d = static_cast < uint8_t > ( ( b_recv ^ p . a1 ) ^ p . a0 ) ;
ds_and_shares z ;
z . z0 = ds_xor ( ds_xor ( ds_xor ( ds_gate ( d , e ) , ds_gate ( d , p . b0_share ) ) ,
ds_gate ( p . a0 , e ) ) , p . c0_share ) ;
z . z1 = ds_xor ( ds_xor ( ds_gate ( d , p . b1_share ) , ds_gate ( p . a1 , e ) ) , p . c1_share ) ;
return z ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
inline simde__m128i ds_deliver ( uint8_t b_exp , simde__m128i base , simde__m128i M ,
const ds_and_shares & z ) noexcept
{
const simde__m128i local = ds_xor ( base , ds_gate ( b_exp , M ) ) ;
return ds_xor ( ds_xor ( local , z . z0 ) , z . z1 ) ;
}
2026-09-28 05:59:19 -06:00
/// @brief XOR shares of a bit-Beaver triple `(α , β, α∧β)`.
struct ds_bit_triple
{
uint8_t a0 ;
uint8_t a1 ;
uint8_t b0 ;
uint8_t b1 ;
uint8_t c0 ;
uint8_t c1 ;
} ;
/// @brief Sample one bit-AND triple from `pad`, via `sample_beaver2` on the XOR ring.
/// @tparam PadRng pad stream with `bit()`
/// @param pad the pad stream
/// @return shares of `(α , β, α∧β)`
template < typename PadRng >
ds_bit_triple ds_sample_bit_and ( PadRng & pad )
{
using Bit = dpf : : xor_wrapper < std : : uint8_t > ;
auto sampler = [ & pad ] ( ) - > Bit {
std : : uint8_t packed = 0 ;
for ( int i = 0 ; i < 8 ; + + i )
{
packed = static_cast < std : : uint8_t > (
packed | ( static_cast < std : : uint8_t > ( pad . bit ( ) & 1u ) < < i ) ) ;
}
return Bit { packed } ;
} ;
const auto triple = dpf : : beavers : : sample_beaver2 < Bit > ( sampler ) ;
auto low = [ ] ( Bit x ) {
return static_cast < uint8_t > ( static_cast < std : : uint8_t > ( x ) & 1u ) ;
} ;
return ds_bit_triple {
low ( triple . a . p0 ) , low ( triple . a . p1 ) ,
low ( triple . b . p0 ) , low ( triple . b . p1 ) ,
low ( triple . ab . p0 ) , low ( triple . ab . p1 ) } ;
}
/// @brief One party's share of `x ∧ y` after `d = x⊕α ` and `e = y⊕β` are open.
/// @param d the opened mask of `x`
/// @param e the opened mask of `y`
/// @param a this party's share of `α `
/// @param b this party's share of `β`
/// @param c this party's share of `α∧β`
/// @param hold_de party 0 adds the public `d∧e` term
/// @return this party's XOR share of the product
HEDLEY_NO_THROW
inline uint8_t ds_bit_and_party ( uint8_t d , uint8_t e , uint8_t a , uint8_t b ,
uint8_t c , bool hold_de ) noexcept
{
uint8_t z = static_cast < uint8_t > ( ( d & b ) ^ ( e & a ) ^ c ) ;
if ( hold_de )
z = static_cast < uint8_t > ( z ^ ( d & e ) ) ;
return static_cast < uint8_t > ( z & 1u ) ;
}
/// @brief Joint evaluation of one bit-AND. `d` and `e` are the opened masks.
/// @param t the triple
/// @param x0 party 0's share of `x`
/// @param x1 party 1's share of `x`
/// @param y0 party 0's share of `y`
/// @param y1 party 1's share of `y`
/// @return XOR shares of `x ∧ y`
HEDLEY_NO_THROW
inline std : : pair < uint8_t , uint8_t > ds_bit_and_shares ( const ds_bit_triple & t ,
uint8_t x0 , uint8_t x1 , uint8_t y0 , uint8_t y1 ) noexcept
{
const uint8_t d = static_cast < uint8_t > ( x0 ^ x1 ^ t . a0 ^ t . a1 ) ;
const uint8_t e = static_cast < uint8_t > ( y0 ^ y1 ^ t . b0 ^ t . b1 ) ;
return { ds_bit_and_party ( d , e , t . a0 , t . b0 , t . c0 , true ) ,
ds_bit_and_party ( d , e , t . a1 , t . b1 , t . c1 , false ) } ;
}
/// @brief Replace additive shares with XOR shares of their sum.
/// @details One beaver bit-AND per bit except the last. Party 0's sum-bit
/// share is `a ⊕ c0`; party 1's is `b ⊕ c1`. The carry share is
/// `((a⊕c) ∧ (b⊕c)) ⊕ c`, which is the majority. Neither share is
/// the sum, and the sum is not written down.
/// @tparam PadRng pad stream with `bit()`
/// @tparam InputT input domain type
/// @param pads the pad stream
/// @param x0 party 0's additive share, replaced by its XOR share of the sum
/// @param x1 party 1's additive share, replaced by its XOR share of the sum
template < typename PadRng , typename InputT >
void split_additive_to_xor ( PadRng & pads , InputT & x0 , InputT & x1 )
{
constexpr auto to_int = utils : : to_integral_type < InputT > { } ;
using FromI = typename utils : : make_from_integral_value < InputT > : : integral_type ;
using U = std : : make_unsigned_t < FromI > ;
const U u0 = static_cast < U > ( to_int ( x0 ) ) ;
const U u1 = static_cast < U > ( to_int ( x1 ) ) ;
U s0 = 0 ;
U s1 = 0 ;
uint8_t c0 = 0 ;
uint8_t c1 = 0 ;
constexpr std : : size_t nbits = utils : : bitlength_of_v < InputT > ;
for ( std : : size_t i = 0 ; i < nbits ; + + i )
{
const uint8_t a = static_cast < uint8_t > ( ( u0 > > i ) & U { 1 } ) ;
const uint8_t b = static_cast < uint8_t > ( ( u1 > > i ) & U { 1 } ) ;
const uint8_t sum0 = static_cast < uint8_t > ( a ^ c0 ) ;
const uint8_t sum1 = static_cast < uint8_t > ( b ^ c1 ) ;
s0 = static_cast < U > ( s0 | ( static_cast < U > ( sum0 ) < < i ) ) ;
s1 = static_cast < U > ( s1 | ( static_cast < U > ( sum1 ) < < i ) ) ;
if ( i + 1 = = nbits )
break ;
const ds_bit_triple triple = ds_sample_bit_and ( pads ) ;
const auto prod = ds_bit_and_shares ( triple ,
static_cast < uint8_t > ( a ^ c0 ) , c1 ,
c0 , static_cast < uint8_t > ( b ^ c1 ) ) ;
c0 = static_cast < uint8_t > ( prod . first ^ c0 ) ;
c1 = static_cast < uint8_t > ( prod . second ^ c1 ) ;
}
x0 = utils : : make_from_integral_value < InputT > { } ( static_cast < FromI > ( s0 ) ) ;
x1 = utils : : make_from_integral_value < InputT > { } ( static_cast < FromI > ( s1 ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief Per-level messages prepared before the CW protocol runs (blinds + pads).
2026-09-24 14:08:32 -06:00
struct ds_level_blinds
{
ds_cw_pads cwp ;
ds_and_pads and0 ;
ds_and_pads and1 ;
ds_blind b0 ;
ds_blind b1 ;
simde__m128i L0 , R0 , L1 , R1 ;
uint8_t bit0 ;
uint8_t bit1 ;
} ;
2026-09-24 23:18:10 -06:00
/// @brief Opened CW, advice, and AND products delivered by a `CwProtocol`.
2026-09-24 14:08:32 -06:00
struct ds_level_open
{
simde__m128i cw ;
uint8_t advice ;
ds_and_shares z0 ;
ds_and_shares z1 ;
uint64_t value_cw = 0 ; // public after open when cmp is active at this level
} ;
2026-09-24 23:18:10 -06:00
/// @brief Running comparison-gen state shared across DS levels (Va residual).
/// @details When `track_coeff` is set (wildcard cmp payload), a parallel β = 1
2026-09-24 14:08:32 -06:00
/// accumulator `Va1` is advanced alongside `Va` so the gen can stash
/// `value_cw(1) − value_cw(β)` coefficients for a later `assign_cmp`.
struct ds_cmp_gen_state
{
bool active = false ;
std : : size_t nbits = 0 ;
uint64_t mask = 0 ;
uint64_t beta = 0 ;
bool include_eq = false ;
cmp_trivial trivial = cmp_trivial : : none ;
uint64_t Va = 0 ;
unsigned __int128 thresh = 0 ;
bool track_coeff = false ;
uint64_t Va1 = 0 ;
uint64_t last_vcw_coeff = 0 ;
2026-09-24 20:44:07 -06:00
cmp_kind kind = cmp_kind : : lt ;
bool paint = false ;
std : : size_t length_bits = 0 ;
paint_callback paint_cb = nullptr ;
const void * paint_ctx = nullptr ;
2026-09-24 14:08:32 -06:00
} ;
2026-09-28 05:59:19 -06:00
/// @brief Local joint-sim mux of a packed naked leaf from XOR bit shares.
/// @details Mirrors `party/oblivious_select.hpp` `mux_leaf_share`: each level
/// selects with `bit0 ^ bit1` so the call site never forms a clear
/// point for `make_leaves`. The joint simulator holds both shares.
/// @tparam Leaf packed leaf type
/// @tparam Make candidate builder `Leaf(unsigned lane)`
/// @tparam Bit0 party-0 bit accessor
/// @tparam Bit1 party-1 bit accessor
template < typename Leaf , typename Make , typename Bit0 , typename Bit1 >
Leaf mux_naked_leaf_local ( std : : size_t lg , Make & & make , Bit0 & & bit0_at ,
Bit1 & & bit1_at )
{
if ( lg = = 0 )
return make ( 0u ) ;
std : : vector < Leaf > cand ( std : : size_t { 1 } < < lg ) ;
for ( std : : size_t i = 0 ; i < cand . size ( ) ; + + i )
cand [ i ] = make ( static_cast < unsigned > ( i ) ) ;
for ( std : : size_t b = 0 ; b < lg ; + + b )
{
const uint8_t bit = static_cast < uint8_t > (
( bit0_at ( b ) ^ bit1_at ( b ) ) & 1u ) ;
std : : vector < Leaf > next ( cand . size ( ) / 2 ) ;
for ( std : : size_t k = 0 ; k < next . size ( ) ; + + k )
next [ k ] = bit ? cand [ 2 * k + 1 ] : cand [ 2 * k ] ;
cand . swap ( next ) ;
}
return cand [ 0 ] ;
}
2026-09-24 23:18:10 -06:00
/// @brief Local joint simulation: today's `ds_cw_outs` / `ds_open_advice` / `ds_and_open`.
/// @details An MPC backend would send `blinds` and return the same `ds_level_open` shape.
/// @tparam PadRng pad stream for the Doerner– Shelat protocol
2026-09-24 14:08:32 -06:00
template < typename PadRng >
struct local_cw_protocol
{
PadRng & pads ;
ds_level_blinds prepare_level ( simde__m128i L0 , simde__m128i R0 , uint8_t bit0 ,
simde__m128i L1 , simde__m128i R1 , uint8_t bit1 )
{
ds_level_blinds b ;
b . cwp = ds_sample_cw ( pads ) ;
b . and0 = ds_sample_and ( pads ) ;
b . and1 = ds_sample_and ( pads ) ;
b . L0 = L0 ;
b . R0 = R0 ;
b . L1 = L1 ;
b . R1 = R1 ;
b . bit0 = bit0 ;
b . bit1 = bit1 ;
ds_cw_blinds ( b . cwp , L0 , R0 , bit0 , L1 , R1 , bit1 , b . b0 , b . b1 ) ;
return b ;
}
ds_level_open complete_level ( std : : size_t /*level*/ , const ds_level_blinds & b ,
simde__m128i M0 , simde__m128i M1 , uint8_t rec0 , uint8_t rec1 )
{
ds_level_open out ;
out . advice = ds_open_advice ( b . L0 , b . R0 , b . bit0 , b . L1 , b . R1 , b . bit1 ) ;
out . cw = ds_cw_outs ( b . cwp , b . L0 , b . R0 , b . bit0 , b . L1 , b . R1 , b . bit1 ,
b . b0 , b . b1 ) ;
out . z0 = ds_and_open ( b . and0 , M0 , rec0 ) ;
out . z1 = ds_and_open ( b . and1 , M1 , rec1 ) ;
return out ;
}
2026-09-24 23:18:10 -06:00
/// @brief Open CW + advice only (AND pads stay in `blinds` for a later open).
/// @param b the `b`
/// @return the returned `std::pair<simde__m128i, uint8_t>`
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
std : : pair < simde__m128i , uint8_t > open_cw ( const ds_level_blinds & b ) noexcept
{
return { ds_cw_outs ( b . cwp , b . L0 , b . R0 , b . bit0 , b . L1 , b . R1 , b . bit1 ,
b . b0 , b . b1 ) ,
ds_open_advice ( b . L0 , b . R0 , b . bit0 , b . L1 , b . R1 , b . bit1 ) } ;
}
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic pop )
/// @brief Open the public value CW for this level (local: clear convert+make_value_cw).
/// @details MPC backends open additive shares of the same word.
/// @param b the `b`
/// @param adv0 the `adv0`
/// @param adv1 the `adv1`
/// @param ai the `ai`
/// @param Va the `Va`
/// @param beta the payload
/// @param mask the bit mask
/// @return the returned `uint64_t`
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
uint64_t open_value_cw ( const ds_level_blinds & b , uint8_t adv0 , uint8_t adv1 ,
int ai , uint64_t & Va , uint64_t beta , uint64_t mask ) noexcept
{
return dcf_impl : : make_value_cw ( b . L0 , b . R0 , b . L1 , b . R1 , adv0 ,
adv1 , ai , Va , beta , mask ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief Open a path-paint value CW. `plant` is the scaled lose-subtree constant.
/// @param b the `b`
/// @param adv0 the `adv0`
/// @param adv1 the `adv1`
/// @param ai the `ai`
/// @param Va the `Va`
/// @param plant the unit plant
/// @param mask the bit mask
/// @return the returned `uint64_t`
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
uint64_t open_planted_cw ( const ds_level_blinds & b , uint8_t adv0 , uint8_t adv1 ,
int ai , uint64_t & Va , uint64_t plant , uint64_t mask ) noexcept
{
return dcf_impl : : make_value_cw_planted ( b . L0 , b . R0 , b . L1 , b . R1 , adv0 ,
adv1 , ai , Va , plant , mask ) ;
}
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
ds_and_shares open_and ( const ds_and_pads & p , simde__m128i M ,
uint8_t b_recv ) noexcept
{
return ds_and_open ( p , M , b_recv ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief Open the final comparison leaf CW. Wraps `make_final_cw` so the
2026-09-24 14:08:32 -06:00
/// Doerner– Shelat gen does not call it directly on reconstructed seeds;
/// an MPC backend would open additive shares of the same word.
2026-09-24 23:18:10 -06:00
/// @param s0 the `s0`
/// @param s1 the `s1`
/// @param t1 the `t1`
/// @param Va the `Va`
/// @param mask the bit mask
/// @param on_path the value reconstructed on the secret path
/// @return the returned `uint64_t`
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
uint64_t open_final_cw ( simde__m128i s0 , simde__m128i s1 , uint8_t t1 ,
uint64_t Va , uint64_t mask , uint64_t on_path ) noexcept
{
return dcf_impl : : make_final_cw ( s0 , s1 , t1 , Va , mask , on_path ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief Draw the group-width `cmp_addend` blind. Local joint simulation reuses
2026-09-24 14:08:32 -06:00
/// the shared root sampler so the blind matches the dealer's; an MPC
/// backend would instead pull a group-width element from the pad stream.
2026-09-24 23:18:10 -06:00
/// @tparam BlockSampler block sampler
/// @param mask the bit mask
/// @param sample the `sample`
/// @return the returned `uint64_t`
2026-09-24 14:08:32 -06:00
template < typename BlockSampler >
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
uint64_t sample_addend_blind ( uint64_t mask , BlockSampler & & sample ) noexcept
{
return dcf_impl : : sample_addend_blind ( mask ,
std : : forward < BlockSampler > ( sample ) ) ;
}
2026-09-28 05:59:19 -06:00
/// @brief Encode shares for the XOR-style CW walk.
/// @details XOR inputs flip party 0's MSB, which is linear over XOR.
/// Additive inputs are converted first: `split_additive_to_xor`
/// draws one bit-Beaver per carry and leaves XOR shares of the
/// sum. The MSB flip is then the same one XOR inputs take, so
/// the walk matches `make_dpf` on the sum. The sum is not opened
/// and party 1's share is not cleared.
2026-09-24 23:18:10 -06:00
/// @tparam InputT input domain type
2026-09-28 05:59:19 -06:00
/// @param x0 party 0's share
/// @param x1 party 1's share
/// @param arith `true` when `x0`, `x1` are additive
2026-09-24 20:44:07 -06:00
template < typename InputT >
2026-09-28 05:59:19 -06:00
void encode_walk_shares ( InputT & x0 , InputT & x1 , bool arith )
2026-09-24 20:44:07 -06:00
{
if ( arith )
2026-09-28 05:59:19 -06:00
split_additive_to_xor ( pads , x0 , x1 ) ;
utils : : flip_msb_if_signed_integral ( x0 ) ;
2026-09-24 20:44:07 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief Constrained comparison Π_CCMP: open `1{x0 < x1}` when `|x0− x1|=1`.
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @return Constrained comparison Π_CCMP: open `1{x0 < x1}` when `|x0− x1|=1`
HEDLEY_NO_THROW
uint8_t open_ccmp ( std : : uint64_t x0 , std : : uint64_t x1 ) noexcept
{
( void ) pads ; // MPC backend would consume an AND pad here
return dpf : : local_ccmp ( x0 , x1 ) ;
}
/// @brief Open a public leaf CW for a shared payload.
2026-09-28 05:59:19 -06:00
/// @details Splits the payload into leaf words (`naked(y0) ± naked(y1)`) so
/// scalar `β = y0 + y1` (or `y0 ⊕ y1`) is never formed. The packing
/// lane is muxed from XOR bit shares of the point, matching
/// `mux_leaf_share` on the socket. Ring: `g = CCMP(t0,t1)` selects
/// `N − M` vs `M − N` (matches `make_leaf` with `sign = t0`).
/// Characteristic 2: CW = `N ⊕ M` (sign mux is a no-op under XOR).
2026-09-24 23:18:10 -06:00
/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG`
/// @tparam I output index
/// @tparam OutputsTuple outputs tuple
/// @tparam InteriorBlock interior block
/// @tparam OutputT output type
2026-09-28 05:59:19 -06:00
/// @tparam InputT input domain type
2026-09-24 23:18:10 -06:00
/// @param seed0 the `seed0`
/// @param seed1 the `seed1`
/// @param t0 the `t0`
/// @param t1 the `t1`
/// @param y0 the `y0`
/// @param y1 the `y1`
/// @param pos_base the `pos_base`
2026-09-28 05:59:19 -06:00
/// @param x0 party 0's XOR share of the (lane) point
/// @param x1 party 1's XOR share of the (lane) point
/// @return the opened leaf correction word
2026-09-24 23:18:10 -06:00
template < typename ExteriorPRG , std : : size_t I = 0 , typename OutputsTuple ,
2026-09-28 05:59:19 -06:00
typename InteriorBlock , typename OutputT , typename InputT >
2026-09-24 23:18:10 -06:00
auto open_arith_leaf ( const InteriorBlock & seed0 , const InteriorBlock & seed1 ,
uint8_t t0 , uint8_t t1 , OutputT y0 , OutputT y1 , std : : size_t pos_base ,
2026-09-28 05:59:19 -06:00
InputT x0 , InputT x1 ) - > dpf : : leaf_node_t < typename ExteriorPRG : : block_type ,
2026-09-24 23:18:10 -06:00
OutputT >
{
using output_type = OutputT ;
using node_type = typename ExteriorPRG : : block_type ;
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
using leaf_type = dpf : : leaf_node_t < node_type , output_type > ;
HEDLEY_PRAGMA ( GCC diagnostic pop )
2026-09-28 05:59:19 -06:00
constexpr std : : size_t lg =
dpf : : lg_outputs_per_leaf_v < output_type , node_type > ;
constexpr auto to_int = utils : : to_integral_type < InputT > { } ;
auto bit0_at = [ & ] ( std : : size_t b ) {
return static_cast < uint8_t > ( ( to_int ( x0 ) > > b ) & 1u ) ;
} ;
auto bit1_at = [ & ] ( std : : size_t b ) {
return static_cast < uint8_t > ( ( to_int ( x1 ) > > b ) & 1u ) ;
} ;
auto naked_of = [ & ] ( output_type y ) {
return mux_naked_leaf_local < leaf_type > ( lg ,
[ & ] ( unsigned i ) {
return dpf : : make_naked_leaf < node_type > (
static_cast < InputT > ( i ) , y ) ;
} ,
bit0_at , bit1_at ) ;
} ;
// Split payload across leaf words; never form scalar β.
const leaf_type naked =
dpf : : add_leaf < output_type > ( naked_of ( y0 ) , naked_of ( y1 ) ) ;
2026-09-24 23:18:10 -06:00
const auto M = dpf : : make_leaf_mask < ExteriorPRG , I , OutputsTuple , InteriorBlock > (
seed0 , seed1 , pos_base ) ;
if constexpr ( utils : : has_characteristic_two_v < output_type > )
{
( void ) t0 ;
( void ) t1 ;
return dpf : : subtract_leaf < output_type > ( naked , M ) ;
}
else
{
const uint8_t g = open_ccmp ( t0 , t1 ) ;
2026-09-28 05:59:19 -06:00
// CW = (− 1)^{t1}(N − M): g=0 → N− M; g=1 → M− N. Matches make_leaf(sign=t0).
2026-09-24 23:18:10 -06:00
if ( g & 1u )
return dpf : : subtract_leaf < output_type > ( M , naked ) ;
return dpf : : subtract_leaf < output_type > ( naked , M ) ;
}
}
2026-09-28 05:59:19 -06:00
/// @brief Open the comparison threshold lane from XOR shares of the point.
/// @details Reconstructs only inside this protocol hook for paint units,
/// domain-edge triviality, and blocked suffixes. Per-level value
/// words use share bits (`bit0 ^ bit1`) instead of this value.
/// @tparam InputT input domain type
/// @param x0 party 0's XOR share
/// @param x1 party 1's XOR share
/// @param nbits width of the comparison lane
/// @return the comparison threshold as an integer lane
template < typename InputT >
unsigned __int128 open_cmp_threshold ( InputT x0 , InputT x1 ,
std : : size_t nbits ) noexcept
{
constexpr auto to_int = utils : : to_integral_type < InputT > { } ;
constexpr std : : size_t bl = utils : : bitlength_of_v < InputT > ;
const InputT x = utils : : xor_input_shares ( x0 , x1 ) ;
if ( nbits > = bl )
return static_cast < unsigned __int128 > ( to_int ( x ) ) ;
return static_cast < unsigned __int128 > ( to_int ( x ) > > ( bl - nbits ) ) ;
}
/// @brief Public correction seed from XOR shares of the path prefix.
/// @details Reconstructs the prefix only inside this protocol hook and
/// returns `make_cs` (same digest as `oblivious_cs` when both
/// seeds are in-process). The clear prefix is not returned.
/// @tparam InputT input domain type
/// @param level fold level (or blocked tag | level)
/// @param x0 party 0's XOR share of the encoded point
/// @param x1 party 1's XOR share of the encoded point
/// @param bits number of high path bits in the prefix
/// @param s0 party 0's on-path seed
/// @param s1 party 1's on-path seed
/// @return the public correction seed
template < typename InputT >
cs_block open_correction_seed ( std : : size_t level , InputT x0 , InputT x1 ,
std : : size_t bits , simde__m128i s0 , simde__m128i s1 ) noexcept
{
constexpr auto to_int = utils : : to_integral_type < InputT > { } ;
constexpr std : : size_t bl = utils : : bitlength_of_v < InputT > ;
const InputT x = utils : : xor_input_shares ( x0 , x1 ) ;
// Prefer `uint64_t` over `psnip_uint64_t{...}`: that macro expands to
// `long unsigned int`, which is not a valid braced/cast type-id alone.
const uint64_t prefix = ( bits = = 0 | | bits > bl )
? uint64_t { 0 }
: static_cast < uint64_t > ( to_int ( x ) > > ( bl - bits ) ) ;
return detail : : vdpf : : make_cs ( level , prefix , s0 , s1 ) ;
}
/// @brief Open a group of leaf correction words for one prefix group.
/// @details Hands both XOR shares to `leaf_fn`. The builder may reconstruct
/// the point only to emit public CWs (never return α to the DS
/// call site). An MPC backend would run a per-group leaf CW
/// exchange that never reveals `x`. Additive inputs have already
/// been replaced by XOR shares of the sum.
2026-09-24 23:18:10 -06:00
/// @tparam InputT input domain type
/// @tparam LeafFn leaf fn
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param leaf_fn the `leaf_fn`
2026-09-24 14:08:32 -06:00
template < typename InputT , typename LeafFn >
void open_leaf_group ( InputT x0 , InputT x1 , LeafFn & & leaf_fn )
{
2026-09-28 05:59:19 -06:00
std : : forward < LeafFn > ( leaf_fn ) ( x0 , x1 ) ;
2026-09-24 14:08:32 -06:00
}
} ;
2026-09-24 23:18:10 -06:00
/// @brief Generation-side level state (seeds / home bits). Not an eval path memoizer.
/// @tparam NodeT GGM node type
2026-09-24 14:08:32 -06:00
template < typename NodeT >
struct ds_gen_state
{
NodeT inbox [ 2 ] ;
int home [ 2 ] ;
NodeT root0 ;
NodeT root1 ;
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
void init ( NodeT r0 , NodeT r1 ) noexcept
{
root0 = r0 ;
root1 = r1 ;
inbox [ 0 ] = r0 ;
inbox [ 1 ] = r1 ;
home [ 0 ] = 0 ;
home [ 1 ] = 1 ;
}
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
NodeT & seed0 ( ) noexcept { return inbox [ home [ 0 ] ] ; }
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
NodeT & seed1 ( ) noexcept { return inbox [ home [ 1 ] ] ; }
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
const NodeT & seed0 ( ) const noexcept { return inbox [ home [ 0 ] ] ; }
2026-09-24 20:44:07 -06:00
HEDLEY_NO_THROW
2026-09-24 14:08:32 -06:00
const NodeT & seed1 ( ) const noexcept { return inbox [ home [ 1 ] ] ; }
} ;
2026-09-24 23:18:10 -06:00
/// @brief One interior level: expand, protocol open, advance both party seeds.
/// @details When `cmp` is non-null and active for `level`, also opens `value_cw` via
2026-09-24 14:08:32 -06:00
/// the protocol (no second PRG expand outside).
2026-09-24 23:18:10 -06:00
/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128`
/// @tparam CwProtocol correction-word protocol
/// @tparam NodeT GGM node type
/// @tparam InputT input domain type
/// @tparam MaskT mask type
/// @tparam AdviceT advice type
/// @param st the `st`
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param mask the bit mask
/// @param level the tree level
/// @param depth the tree depth
/// @param proto the `proto`
/// @param cw_out the `cw_out`
/// @param advice_out the `advice_out`
/// @param value_cw_out the `value_cw_out`
/// @param cmp the comparison specification
2026-09-24 14:08:32 -06:00
template < typename InteriorPRG , typename CwProtocol , typename NodeT ,
2026-09-24 15:16:21 -06:00
typename InputT , typename MaskT , typename AdviceT >
2026-09-24 14:08:32 -06:00
void ds_advance_level ( ds_gen_state < NodeT > & st , InputT x0 , InputT x1 ,
2026-09-24 23:18:10 -06:00
MaskT mask , std : : size_t level , std : : size_t depth , CwProtocol & proto ,
NodeT & cw_out , AdviceT & advice_out , uint64_t * value_cw_out = nullptr ,
2026-09-24 14:08:32 -06:00
ds_cmp_gen_state * cmp = nullptr )
{
2026-09-24 23:18:10 -06:00
using tree = dpf : : tree_traits < InteriorPRG > ;
2026-09-24 14:08:32 -06:00
// Integral bridge so bit extraction works for `keyword` / `modint` /
// signed / bitstring the same way dealer gen does via `mask & x`.
2026-09-24 15:16:21 -06:00
// `msb_mask` is the unsigned bit pattern; a signed input must not be
// required to have that same type.
2026-09-24 14:08:32 -06:00
constexpr auto to_int = utils : : to_integral_type < InputT > { } ;
2026-09-24 15:16:21 -06:00
constexpr auto to_mask = utils : : to_integral_type < MaskT > { } ;
const auto mi = to_mask ( mask ) ;
2026-09-24 14:08:32 -06:00
const uint8_t bit0 = static_cast < uint8_t > ( ! ! ( mi & to_int ( x0 ) ) ) ;
const uint8_t bit1 = static_cast < uint8_t > ( ! ! ( mi & to_int ( x1 ) ) ) ;
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
NodeT s0 = st . seed0 ( ) ;
NodeT s1 = st . seed1 ( ) ;
2026-09-24 23:18:10 -06:00
const uint8_t adv0 = static_cast < uint8_t > ( dpf : : get_lo_bit ( s0 ) ) ;
const uint8_t adv1 = static_cast < uint8_t > ( dpf : : get_lo_bit ( s1 ) ) ;
const auto c0 = tree : : expand ( s0 , is_last ) ;
const auto c1 = tree : : expand ( s1 , is_last ) ;
2026-09-24 14:08:32 -06:00
auto blinds = proto . prepare_level ( c0 [ 0 ] , c0 [ 1 ] , bit0 , c1 [ 0 ] , c1 [ 1 ] , bit1 ) ;
if ( value_cw_out ! = nullptr & & cmp ! = nullptr & & cmp - > active
& & cmp - > trivial = = cmp_trivial : : none & & level < cmp - > nbits )
{
2026-09-24 23:18:10 -06:00
// Convert uses expand_value (HT: always two-tweak); seed walk used expand.
const auto v0 = tree : : expand_value ( s0 ) ;
const auto v1 = tree : : expand_value ( s1 ) ;
auto vblinds = blinds ;
vblinds . L0 = v0 [ 0 ] ;
vblinds . R0 = v0 [ 1 ] ;
vblinds . L1 = v1 [ 0 ] ;
vblinds . R1 = v1 [ 1 ] ;
2026-09-28 05:59:19 -06:00
// Path bit from share bits (threshold bit equals the walk bit).
const int ai = static_cast < int > ( ( bit0 ^ bit1 ) & 1u ) ;
2026-09-24 20:44:07 -06:00
if ( cmp - > paint )
{
const uint64_t unit = dcf_impl : : paint_unit ( cmp - > kind , level ,
cmp - > thresh , cmp - > nbits , cmp - > length_bits , false ,
cmp - > paint_cb , cmp - > paint_ctx ) ;
const uint64_t plant = dcf_impl : : scale_plant ( unit , cmp - > beta ,
cmp - > mask ) ;
2026-09-24 23:18:10 -06:00
* value_cw_out = proto . open_planted_cw ( vblinds , adv0 , adv1 , ai ,
2026-09-24 20:44:07 -06:00
cmp - > Va , plant , cmp - > mask ) ;
if ( cmp - > track_coeff )
{
const uint64_t plant1 = dcf_impl : : scale_plant ( unit , 1ULL ,
cmp - > mask ) ;
2026-09-24 23:18:10 -06:00
const uint64_t v1w = proto . open_planted_cw ( vblinds , adv0 , adv1 ,
2026-09-24 20:44:07 -06:00
ai , cmp - > Va1 , plant1 , cmp - > mask ) ;
cmp - > last_vcw_coeff =
2026-09-24 23:18:10 -06:00
( v1w + dcf_impl : : neg_m ( * value_cw_out , cmp - > mask ) ) & cmp - > mask ;
2026-09-24 20:44:07 -06:00
}
}
else
2026-09-24 14:08:32 -06:00
{
2026-09-24 23:18:10 -06:00
* value_cw_out = proto . open_value_cw ( vblinds , adv0 , adv1 , ai , cmp - > Va ,
2026-09-24 20:44:07 -06:00
cmp - > beta , cmp - > mask ) ;
if ( cmp - > track_coeff )
{
// Affine coefficient: same level with β = 1 on a parallel Va.
2026-09-24 23:18:10 -06:00
const uint64_t v1w = proto . open_value_cw ( vblinds , adv0 , adv1 , ai ,
2026-09-24 20:44:07 -06:00
cmp - > Va1 , 1ULL , cmp - > mask ) ;
cmp - > last_vcw_coeff =
2026-09-24 23:18:10 -06:00
( v1w + dcf_impl : : neg_m ( * value_cw_out , cmp - > mask ) ) & cmp - > mask ;
2026-09-24 20:44:07 -06:00
}
2026-09-24 14:08:32 -06:00
}
}
auto [ cw , tpack ] = proto . open_cw ( blinds ) ;
2026-09-24 23:18:10 -06:00
// Half-Tree mid levels store no advice; last level keeps BGI packing.
if constexpr ( tree : : is_half_tree )
{
if ( ! is_last )
tpack = 0 ;
}
else
{
// BGI: opened advice stands.
}
// Dealer-equivalent CW for Half-Tree mid: off-path children XOR already
// matches H(s0)⊕H(s1)⊕ᾱΔ via the open. For last/BGI, open matches Gen.
2026-09-24 14:08:32 -06:00
const uint8_t exp0 = st . home [ 0 ] = = 0 ? bit0 : bit1 ;
const uint8_t rec0 = st . home [ 0 ] = = 0 ? bit1 : bit0 ;
const uint8_t exp1 = st . home [ 1 ] = = 0 ? bit0 : bit1 ;
const uint8_t rec1 = st . home [ 1 ] = = 0 ? bit1 : bit0 ;
NodeT M0 , base0 , M1 , base1 ;
2026-09-24 23:18:10 -06:00
if constexpr ( tree : : is_half_tree )
{
if ( ! is_last )
{
// Mid: next = child[bit] ⊕ (t ? full_cw : 0).
const NodeT D0 = ds_xor ( c0 [ 0 ] , c0 [ 1 ] ) ;
const NodeT D1 = ds_xor ( c1 [ 0 ] , c1 [ 1 ] ) ;
M0 = D0 ;
base0 = ( adv0 & 1u ) ? ds_xor ( c0 [ 0 ] , cw ) : c0 [ 0 ] ;
M1 = D1 ;
base1 = ( adv1 & 1u ) ? ds_xor ( c1 [ 0 ] , cw ) : c1 [ 0 ] ;
}
else
{
ds_next_terms ( c0 [ 0 ] , c0 [ 1 ] , adv0 , cw , tpack , M0 , base0 ) ;
ds_next_terms ( c1 [ 0 ] , c1 [ 1 ] , adv1 , cw , tpack , M1 , base1 ) ;
}
}
else
{
ds_next_terms ( c0 [ 0 ] , c0 [ 1 ] , adv0 , cw , tpack , M0 , base0 ) ;
ds_next_terms ( c1 [ 0 ] , c1 [ 1 ] , adv1 , cw , tpack , M1 , base1 ) ;
}
2026-09-24 14:08:32 -06:00
const NodeT nxt0 =
ds_deliver ( exp0 , base0 , M0 , proto . open_and ( blinds . and0 , M0 , rec0 ) ) ;
const NodeT nxt1 =
ds_deliver ( exp1 , base1 , M1 , proto . open_and ( blinds . and1 , M1 , rec1 ) ) ;
st . home [ 0 ] ^ = 1 ;
st . home [ 1 ] ^ = 1 ;
st . inbox [ st . home [ 0 ] ] = nxt0 ;
st . inbox [ st . home [ 1 ] ] = nxt1 ;
cw_out = cw ;
advice_out = tpack ;
}
template < typename T >
struct is_ds_randomness : std : : false_type { } ;
template < typename RootSampler , typename PadRng >
struct is_ds_randomness < ds_randomness < RootSampler , PadRng > > : std : : true_type { } ;
template < typename T , typename = void >
struct is_cw_protocol : std : : false_type { } ;
template < typename PadRng >
struct is_cw_protocol < local_cw_protocol < PadRng > , void > : std : : true_type { } ;
template < typename T >
struct is_cw_protocol < T ,
std : : void_t < decltype ( std : : declval < T & > ( ) . prepare_level (
simde_mm_setzero_si128 ( ) , simde_mm_setzero_si128 ( ) ,
uint8_t { } , simde_mm_setzero_si128 ( ) ,
simde_mm_setzero_si128 ( ) , uint8_t { } ) ) ,
decltype ( std : : declval < T & > ( ) . open_cw (
std : : declval < const ds_level_blinds & > ( ) ) ) > >
: std : : true_type { } ;
template < typename . . . Ts >
struct first_is_cw_protocol : std : : false_type { } ;
template < typename T , typename . . . Rest >
struct first_is_cw_protocol < T , Rest . . . >
: is_cw_protocol < std : : decay_t < T > > { } ;
template < typename InteriorPRG ,
typename ExteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename CwProtocol >
2026-09-24 23:18:10 -06:00
auto make_dpf_doerner_shelat_impl ( bool arith , bool arith_out , InputT x0 ,
InputT x1 , RootSampler & root_sampler , CwProtocol & proto , OutputT y0 ,
OutputT y1 = OutputT { } )
{
static_assert ( ! dpf : : is_wildcard_v < InputT > ,
" Doerner– Shelat gen takes shares of a concrete point " ) ;
static_assert ( ! dpf : : is_secret_share_v < InputT > ,
" Doerner– Shelat: pass additive_share of xor_wrapper, or raw shares " ) ;
static_assert ( ! dpf : : is_wildcard_v < OutputT > ,
" arith_output / classic DS leaf expects a concrete payload " ) ;
static_assert ( sizeof ( typename InteriorPRG : : block_type ) = = sizeof ( simde__m128i ) ,
" Doerner– Shelat gen uses the AES-block interior node " ) ;
using dpf_type = utils : : dpf_type_t < InteriorPRG , ExteriorPRG , InputT , OutputT > ;
using node = typename dpf_type : : interior_node ;
using input_type = typename dpf_type : : input_type ;
using leaf_tuple = typename dpf_type : : leaf_tuple ;
using beaver_tuple = typename dpf_type : : beaver_tuple ;
using outputs_tuple = std : : tuple < OutputT > ;
constexpr auto depth = dpf_type : : depth ;
proto . encode_walk_shares ( x0 , x1 , arith ) ;
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 ] ;
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
ds_gen_state < node > st ;
HEDLEY_PRAGMA ( GCC diagnostic pop )
st . init ( root0 , root1 ) ;
typename dpf_type : : correction_words_array correction_words { } ;
typename dpf_type : : correction_advice_array correction_advice { } ;
auto mask = dpf_type : : msb_mask ;
for ( std : : size_t level = 0 ; level < depth ; + + level , mask > > = 1 )
{
ds_advance_level < InteriorPRG > ( st , x0 , x1 , mask , level , depth , proto ,
correction_words [ level ] , correction_advice [ level ] ) ;
}
const node parent0 = st . seed0 ( ) ;
const node parent1 = st . seed1 ( ) ;
const bool sign0 = dpf : : get_lo_bit ( parent0 ) ;
const uint8_t t0 = static_cast < uint8_t > ( sign0 ) ;
const uint8_t t1 = static_cast < uint8_t > ( dpf : : get_lo_bit ( parent1 ) ) ;
leaf_tuple leaves0 { } ;
leaf_tuple leaves1 { } ;
beaver_tuple beavers0 { } ;
beaver_tuple beavers1 { } ;
if ( arith_out )
{
auto cw = proto . template open_arith_leaf < ExteriorPRG , 0 , outputs_tuple > (
dpf : : unset_lo_2bits ( parent0 ) , dpf : : unset_lo_2bits ( parent1 ) , t0 , t1 ,
2026-09-28 05:59:19 -06:00
y0 , y1 , std : : size_t { 0 } , x0 , x1 ) ;
2026-09-24 23:18:10 -06:00
std : : get < 0 > ( leaves0 ) = cw ;
std : : get < 0 > ( leaves1 ) = cw ;
}
else
{
2026-09-28 05:59:19 -06:00
// Reconstruct the point only inside the leaf protocol hook.
proto . open_leaf_group ( x0 , x1 , [ & ] ( input_type sx0 , input_type sx1 ) {
const input_type x = utils : : xor_input_shares ( sx0 , sx1 ) ;
auto built = dpf : : make_leaves < ExteriorPRG > ( x ,
dpf : : unset_lo_2bits ( parent0 ) , dpf : : unset_lo_2bits ( parent1 ) ,
sign0 , std : : size_t { 0 } , y0 ) ;
leaves0 = std : : move ( built . first . first ) ;
beavers0 = std : : move ( built . first . second ) ;
leaves1 = std : : move ( built . second . first ) ;
beavers1 = std : : move ( built . second . second ) ;
} ) ;
2026-09-24 23:18:10 -06:00
( void ) y1 ;
}
input_type off0 { } ;
input_type off1 { } ;
return dpf : : make_party_key_pair (
dpf_type { root0 , correction_words , correction_advice ,
leaves0 , beavers0 , off0 } ,
dpf_type { root1 , correction_words , correction_advice ,
leaves1 , beavers1 , off1 } ) ;
}
/// @brief Plaintext-β multi-output classic path (unchanged).
/// @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 OutputTs output ts
/// @tparam RootSampler sampler for the Doerner– Shelat root seed
/// @tparam CwProtocol correction-word protocol
/// @param arith the `arith`
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param root_sampler the `root_sampler`
/// @param proto the `proto`
/// @param y the `y`
/// @param ys the `ys`
/// @return Plaintext-β multi-output classic path (unchanged)
template < typename InteriorPRG ,
typename ExteriorPRG ,
typename InputT ,
typename OutputT ,
typename . . . OutputTs ,
typename RootSampler ,
typename CwProtocol ,
typename = std : : enable_if_t < ( sizeof . . . ( OutputTs ) > 0 ) > >
2026-09-24 20:44:07 -06:00
auto make_dpf_doerner_shelat_impl ( bool arith , InputT x0 , InputT x1 ,
2026-09-24 14:08:32 -06:00
RootSampler & root_sampler , CwProtocol & proto , OutputT & & y ,
OutputTs & & . . . ys )
{
static_assert ( ! dpf : : is_wildcard_v < InputT > ,
2026-09-24 20:44:07 -06:00
" Doerner– Shelat gen takes shares of a concrete point " ) ;
2026-09-24 14:08:32 -06:00
static_assert ( ! dpf : : is_secret_share_v < InputT > ,
2026-09-24 20:44:07 -06:00
" Doerner– Shelat: pass additive_share of xor_wrapper, or raw shares " ) ;
2026-09-24 14:08:32 -06:00
static_assert ( sizeof ( typename InteriorPRG : : block_type ) = = sizeof ( simde__m128i ) ,
" Doerner– Shelat gen uses the AES-block interior node " ) ;
using dpf_type = utils : : dpf_type_t < InteriorPRG , ExteriorPRG , InputT ,
OutputT , OutputTs . . . > ;
using node = typename dpf_type : : interior_node ;
using input_type = typename dpf_type : : input_type ;
constexpr auto depth = dpf_type : : depth ;
2026-09-24 20:44:07 -06:00
proto . encode_walk_shares ( x0 , x1 , arith ) ;
2026-09-24 14:08:32 -06:00
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 ] ;
HEDLEY_PRAGMA ( GCC diagnostic push )
HEDLEY_PRAGMA ( GCC diagnostic ignored " -Wignored-attributes " )
2026-09-24 14:08:32 -06:00
ds_gen_state < node > st ;
2026-09-24 23:18:10 -06:00
HEDLEY_PRAGMA ( GCC diagnostic pop )
2026-09-24 14:08:32 -06:00
st . init ( root0 , root1 ) ;
typename dpf_type : : correction_words_array correction_words { } ;
typename dpf_type : : correction_advice_array correction_advice { } ;
auto mask = dpf_type : : msb_mask ;
for ( std : : size_t level = 0 ; level < depth ; + + level , mask > > = 1 )
{
2026-09-24 23:18:10 -06:00
ds_advance_level < InteriorPRG > ( st , x0 , x1 , mask , level , depth , proto ,
2026-09-24 14:08:32 -06:00
correction_words [ level ] , correction_advice [ level ] ) ;
}
const node parent0 = st . seed0 ( ) ;
const node parent1 = st . seed1 ( ) ;
const bool sign0 = dpf : : get_lo_bit ( parent0 ) ;
2026-09-28 05:59:19 -06:00
typename dpf_type : : leaf_tuple leaves0 { } ;
typename dpf_type : : beaver_tuple beavers0 { } ;
typename dpf_type : : leaf_tuple leaves1 { } ;
typename dpf_type : : beaver_tuple beavers1 { } ;
proto . open_leaf_group ( x0 , x1 , [ & ] ( input_type sx0 , input_type sx1 ) {
const input_type x = utils : : xor_input_shares ( sx0 , sx1 ) ;
auto built = dpf : : make_leaves < ExteriorPRG > ( x ,
dpf : : unset_lo_2bits ( parent0 ) , dpf : : unset_lo_2bits ( parent1 ) , sign0 ,
std : : size_t { 0 } , std : : forward < OutputT > ( y ) ,
std : : forward < OutputTs > ( ys ) . . . ) ;
leaves0 = std : : move ( built . first . first ) ;
beavers0 = std : : move ( built . first . second ) ;
leaves1 = std : : move ( built . second . first ) ;
beavers1 = std : : move ( built . second . second ) ;
} ) ;
2026-09-24 14:08:32 -06:00
input_type off0 { } ;
input_type off1 { } ;
return dpf : : make_party_key_pair (
dpf_type { root0 , correction_words , correction_advice ,
2026-09-28 05:59:19 -06:00
leaves0 , beavers0 , off0 } ,
2026-09-24 14:08:32 -06:00
dpf_type { root1 , correction_words , correction_advice ,
2026-09-28 05:59:19 -06:00
leaves1 , beavers1 , off1 } ) ;
2026-09-24 14:08:32 -06:00
}
2026-09-24 23:18:10 -06:00
/// @brief Single-output plaintext β (disambiguates from arith_out overload).
/// @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 CwProtocol correction-word protocol
/// @param arith the `arith`
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param root_sampler the `root_sampler`
/// @param proto the `proto`
/// @param y the `y`
/// @return Single-output plaintext β (disambiguates from arith_out overload)
template < typename InteriorPRG ,
typename ExteriorPRG ,
typename InputT ,
typename OutputT ,
typename RootSampler ,
typename CwProtocol >
auto make_dpf_doerner_shelat_impl ( bool arith , InputT x0 , InputT x1 ,
RootSampler & root_sampler , CwProtocol & proto , OutputT & & y )
{
return make_dpf_doerner_shelat_impl < InteriorPRG , ExteriorPRG > ( arith , false ,
std : : move ( x0 ) , std : : move ( x1 ) , root_sampler , proto ,
std : : forward < OutputT > ( y ) , OutputT { } ) ;
}
2026-09-24 14:08:32 -06:00
} // namespace detail
2026-09-24 23:18:10 -06:00
/// @brief Local CW protocol (pads cancel; same keys as dealer when roots match).
2026-09-24 14:08:32 -06:00
template < typename PadRng >
using local_cw_protocol = detail : : local_cw_protocol < PadRng > ;
template < typename NodeT >
using ds_gen_state = detail : : ds_gen_state < NodeT > ;
// Public `make_dpf_doerner_shelat(x0, x1, ...)` lives in incremental.hpp so
// classic and `at<>` / mixed-width packs share one entry point.
} // namespace dpf
# endif // LIBDPF_INCLUDE_DPF_DOERNER_SHELAT_HPP__