Document the new DPF surfaces in one command set, and test the field, half-tree, and multipoint edges.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 23:18:10 -06:00
parent 0d8a5a8131
commit 0dff6df8ed
250 changed files with 12199 additions and 1981 deletions

View file

@ -25,11 +25,12 @@
#include "dpf/dpf_key.hpp"
#include "dpf/random.hpp"
#include "dpf/dcf.hpp"
#include "dpf/constrained_cmp.hpp"
namespace dpf
{
/// Tag: Doerner–Shelat / geneval takes additive shares of the point
/// @brief Tag: Doerner–Shelat / geneval takes additive shares of the point
/// (`x0 + x1` in the input ring). Default calls take XOR shares.
struct arith_input_t
{
@ -37,9 +38,60 @@ struct arith_input_t
inline constexpr arith_input_t arith_input{};
/// Roots and the Beaver-pad stream for one Doerner–Shelat generation.
/// `root` is called twice, same as `make_dpf`: party 0 clears the low bit of
/// @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
/// the first sample, party 1 sets the low bit of the second.
/// @tparam RootSampler sampler for the Doerner–Shelat root seed
/// @tparam PadRng pad stream for the Doerner–Shelat protocol
template <typename RootSampler, typename PadRng>
struct ds_randomness
{
@ -227,7 +279,7 @@ inline simde__m128i ds_deliver(uint8_t b_exp, simde__m128i base, simde__m128i M,
return ds_xor(ds_xor(local, z.z0), z.z1);
}
/// Per-level messages prepared before the CW protocol runs (blinds + pads).
/// @brief Per-level messages prepared before the CW protocol runs (blinds + pads).
struct ds_level_blinds
{
ds_cw_pads cwp;
@ -240,7 +292,7 @@ struct ds_level_blinds
uint8_t bit1;
};
/// Opened CW, advice, and AND products delivered by a `CwProtocol`.
/// @brief Opened CW, advice, and AND products delivered by a `CwProtocol`.
struct ds_level_open
{
simde__m128i cw;
@ -250,8 +302,8 @@ struct ds_level_open
uint64_t value_cw = 0; // public after open when cmp is active at this level
};
/// Running comparison-gen state shared across DS levels (Va residual).
/// When `track_coeff` is set (wildcard cmp payload), a parallel β = 1
/// @brief Running comparison-gen state shared across DS levels (Va residual).
/// @details When `track_coeff` is set (wildcard cmp payload), a parallel β = 1
/// 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
@ -274,8 +326,9 @@ struct ds_cmp_gen_state
const void * paint_ctx = nullptr;
};
/// Local joint simulation: today's `ds_cw_outs` / `ds_open_advice` / `ds_and_open`.
/// An MPC backend would send `blinds` and return the same `ds_level_open` shape.
/// @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
template <typename PadRng>
struct local_cw_protocol
{
@ -310,7 +363,11 @@ struct local_cw_protocol
return out;
}
/// Open CW + advice only (AND pads stay in `blinds` for a later open).
/// @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")
HEDLEY_NO_THROW
std::pair<simde__m128i, uint8_t> open_cw(const ds_level_blinds & b) noexcept
{
@ -318,9 +375,18 @@ struct local_cw_protocol
b.b0, b.b1),
ds_open_advice(b.L0, b.R0, b.bit0, b.L1, b.R1, b.bit1)};
}
HEDLEY_PRAGMA(GCC diagnostic pop)
/// Open the public value CW for this level (local: clear convert+make_value_cw).
/// MPC backends open additive shares of the same word.
/// @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`
HEDLEY_NO_THROW
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
@ -329,7 +395,15 @@ struct local_cw_protocol
adv1, ai, Va, beta, mask);
}
/// Open a path-paint value CW. `plant` is the scaled lose-subtree constant.
/// @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`
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
@ -345,9 +419,16 @@ struct local_cw_protocol
return ds_and_open(p, M, b_recv);
}
/// Open the final comparison leaf CW. Wraps `make_final_cw` so the
/// @brief Open the final comparison leaf CW. Wraps `make_final_cw` so the
/// Doerner–Shelat gen does not call it directly on reconstructed seeds;
/// an MPC backend would open additive shares of the same word.
/// @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`
HEDLEY_NO_THROW
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
@ -355,9 +436,13 @@ struct local_cw_protocol
return dcf_impl::make_final_cw(s0, s1, t1, Va, mask, on_path);
}
/// Draw the group-width `cmp_addend` blind. Local joint simulation reuses
/// @brief Draw the group-width `cmp_addend` blind. Local joint simulation reuses
/// 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.
/// @tparam BlockSampler block sampler
/// @param mask the bit mask
/// @param sample the `sample`
/// @return the returned `uint64_t`
template <typename BlockSampler>
HEDLEY_NO_THROW
uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept
@ -366,14 +451,23 @@ struct local_cw_protocol
std::forward<BlockSampler>(sample));
}
/// Majority of three bits (next carry of a full adder).
/// @brief Majority of three bits (next carry of a full adder).
/// @param a the `a`
/// @param b the `b`
/// @param c the `c`
/// @return Majority of three bits (next carry of a full adder)
HEDLEY_NO_THROW
static constexpr uint8_t majority(uint8_t a, uint8_t b, uint8_t c) noexcept
{
return static_cast<uint8_t>((a & b) | (a & c) | (b & c));
}
/// One additive digit: sum bit `a XOR b XOR cin`, carry out = majority.
/// @brief One additive digit: sum bit `a XOR b XOR cin`, carry out = majority.
/// @param a the `a`
/// @param b the `b`
/// @param cin the `cin`
/// @param cout the `cout`
/// @return One additive digit: sum bit `a XOR b XOR cin`, carry out = majority
HEDLEY_NO_THROW
static constexpr uint8_t open_sum_bit(uint8_t a, uint8_t b, uint8_t cin,
uint8_t & cout) noexcept
@ -382,9 +476,14 @@ struct local_cw_protocol
return static_cast<uint8_t>(a ^ b ^ cin);
}
/// Reconstruct `a0 + a1` via a LSB→MSB carry chain, then flip the MSB
/// @brief Reconstruct `a0 + a1` via a LSB→MSB carry chain, then flip the MSB
/// when the domain is signed — matching `make_dpf` on the sum. The call
/// site never forms the sum; an MPC backend would open the same bits.
/// @tparam InputT input domain type
/// @param a0 the `a0`
/// @param a1 the `a1`
/// @return Reconstruct `a0 + a1` via a LSB→MSB carry chain, then flip the MSB when the domain
/// is signed — matching `make_dpf` on the sum
template <typename InputT>
InputT open_arith_point(InputT a0, InputT a1) const
{
@ -409,9 +508,13 @@ struct local_cw_protocol
return out;
}
/// Encode shares for the XOR-style CW walk. XOR mode flips party 0's MSB
/// @brief Encode shares for the XOR-style CW walk. XOR mode flips party 0's MSB
/// (linear over XOR). Arithmetic mode opens the sum (carry + signed MSB)
/// and returns `(alpha, 0)` so the walk matches `make_dpf(alpha)`.
/// @tparam InputT input domain type
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param arith the `arith`
template <typename InputT>
void encode_walk_shares(InputT & x0, InputT & x1, bool arith) const
{
@ -427,13 +530,84 @@ struct local_cw_protocol
}
}
/// Open a group of leaf correction words for one prefix group. In this
/// @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.
/// @details Ring: `β = y0 + y1`; `g = CCMP(t0,t1)` selects `β − M` vs `M − β`
/// (matches `make_leaf` with `sign = t0`). Characteristic 2: `β = y0 ⊕ y1`
/// and CW = `β ⊕ M` (sign mux is a no-op under XOR).
/// @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
/// @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`
/// @param lane_x lane of the shared payload
/// @return the opened leaf correction word
template <typename ExteriorPRG, std::size_t I = 0, typename OutputsTuple,
typename InteriorBlock, typename OutputT>
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,
std::size_t lane_x) -> dpf::leaf_node_t<typename ExteriorPRG::block_type,
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)
const auto M = dpf::make_leaf_mask<ExteriorPRG, I, OutputsTuple, InteriorBlock>(
seed0, seed1, pos_base);
output_type beta{};
if constexpr (utils::has_characteristic_two_v<output_type>)
{
(void)t0;
(void)t1;
beta = static_cast<output_type>(y0 ^ y1);
const leaf_type naked = dpf::make_naked_leaf<node_type>(lane_x, beta);
return dpf::subtract_leaf<output_type>(naked, M);
}
else
{
const uint8_t g = open_ccmp(t0, t1);
beta = static_cast<output_type>(y0 + y1);
const leaf_type naked = dpf::make_naked_leaf<node_type>(lane_x, beta);
// CW = (−1)^{t1}(β − M): g=0 → β−M; g=1 → M−β. Matches make_leaf(sign=t0).
if (g & 1u)
return dpf::subtract_leaf<output_type>(M, naked);
return dpf::subtract_leaf<output_type>(naked, M);
}
}
/// @brief Open a group of leaf correction words for one prefix group. In this
/// local joint simulation both XOR shares of the point are present, so the
/// point is reconstructed *inside* the protocol and handed to `leaf_fn`
/// (which runs `make_leaves` for the group). The Doerner–Shelat gen never
/// forms `x = x0 ^ x1` at its own call site; an MPC backend would instead
/// run a per-group leaf CW exchange that never reveals `x`. After
/// `encode_walk_shares`, arithmetic inputs are already `(alpha, 0)`.
/// @tparam InputT input domain type
/// @tparam LeafFn leaf fn
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param leaf_fn the `leaf_fn`
template <typename InputT, typename LeafFn>
void open_leaf_group(InputT x0, InputT x1, LeafFn && leaf_fn)
{
@ -441,7 +615,8 @@ struct local_cw_protocol
}
};
/// Generation-side level state (seeds / home bits). Not an eval path memoizer.
/// @brief Generation-side level state (seeds / home bits). Not an eval path memoizer.
/// @tparam NodeT GGM node type
template <typename NodeT>
struct ds_gen_state
{
@ -471,16 +646,34 @@ struct ds_gen_state
const NodeT & seed1() const noexcept { return inbox[home[1]]; }
};
/// One interior level: expand, protocol open, advance both party seeds.
/// When `cmp` is non-null and active for `level`, also opens `value_cw` via
/// @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
/// the protocol (no second PRG expand outside).
/// @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
template <typename InteriorPRG, typename CwProtocol, typename NodeT,
typename InputT, typename MaskT, typename AdviceT>
void ds_advance_level(ds_gen_state<NodeT> & st, InputT x0, InputT x1,
MaskT mask, std::size_t level, CwProtocol & proto, NodeT & cw_out,
AdviceT & advice_out, uint64_t * value_cw_out = nullptr,
MaskT mask, std::size_t level, std::size_t depth, CwProtocol & proto,
NodeT & cw_out, AdviceT & advice_out, uint64_t * value_cw_out = nullptr,
ds_cmp_gen_state * cmp = nullptr)
{
using tree = dpf::tree_traits<InteriorPRG>;
// Integral bridge so bit extraction works for `keyword` / `modint` /
// signed / bitstring the same way dealer gen does via `mask & x`.
// `msb_mask` is the unsigned bit pattern; a signed input must not be
@ -490,21 +683,28 @@ void ds_advance_level(ds_gen_state<NodeT> & st, InputT x0, InputT x1,
const auto mi = to_mask(mask);
const uint8_t bit0 = static_cast<uint8_t>(!!(mi & to_int(x0)));
const uint8_t bit1 = static_cast<uint8_t>(!!(mi & to_int(x1)));
const bool is_last = tree::is_last_level(level, depth);
NodeT s0 = st.seed0();
NodeT s1 = st.seed1();
const uint8_t adv0 = static_cast<uint8_t>(
dpf::get_lo_bit_and_clear_lo_2bits(s0));
const uint8_t adv1 = static_cast<uint8_t>(
dpf::get_lo_bit_and_clear_lo_2bits(s1));
const auto c0 = InteriorPRG::eval01(s0);
const auto c1 = InteriorPRG::eval01(s1);
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);
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)
{
// 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];
const int ai = static_cast<int>(
(cmp->thresh >> (cmp->nbits - 1 - level)) & 1);
if (cmp->paint)
@ -514,34 +714,47 @@ void ds_advance_level(ds_gen_state<NodeT> & st, InputT x0, InputT x1,
cmp->paint_cb, cmp->paint_ctx);
const uint64_t plant = dcf_impl::scale_plant(unit, cmp->beta,
cmp->mask);
*value_cw_out = proto.open_planted_cw(blinds, adv0, adv1, ai,
*value_cw_out = proto.open_planted_cw(vblinds, adv0, adv1, ai,
cmp->Va, plant, cmp->mask);
if (cmp->track_coeff)
{
const uint64_t plant1 = dcf_impl::scale_plant(unit, 1ULL,
cmp->mask);
const uint64_t v1 = proto.open_planted_cw(blinds, adv0, adv1,
const uint64_t v1w = proto.open_planted_cw(vblinds, adv0, adv1,
ai, cmp->Va1, plant1, cmp->mask);
cmp->last_vcw_coeff =
(v1 + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask;
(v1w + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask;
}
}
else
{
*value_cw_out = proto.open_value_cw(blinds, adv0, adv1, ai, cmp->Va,
*value_cw_out = proto.open_value_cw(vblinds, adv0, adv1, ai, cmp->Va,
cmp->beta, cmp->mask);
if (cmp->track_coeff)
{
// Affine coefficient: same level with β = 1 on a parallel Va.
const uint64_t v1 = proto.open_value_cw(blinds, adv0, adv1, ai,
const uint64_t v1w = proto.open_value_cw(vblinds, adv0, adv1, ai,
cmp->Va1, 1ULL, cmp->mask);
cmp->last_vcw_coeff =
(v1 + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask;
(v1w + dcf_impl::neg_m(*value_cw_out, cmp->mask)) & cmp->mask;
}
}
}
auto [cw, tpack] = proto.open_cw(blinds);
// 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.
const uint8_t exp0 = st.home[0] == 0 ? bit0 : bit1;
const uint8_t rec0 = st.home[0] == 0 ? bit1 : bit0;
@ -549,8 +762,29 @@ void ds_advance_level(ds_gen_state<NodeT> & st, InputT x0, InputT x1,
const uint8_t rec1 = st.home[1] == 0 ? bit1 : bit0;
NodeT M0, base0, M1, base1;
ds_next_terms(c0[0], c0[1], adv0, cw, tpack, M0, base0);
ds_next_terms(c1[0], c1[1], adv1, cw, tpack, M1, base1);
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);
}
const NodeT nxt0 =
ds_deliver(exp0, base0, M0, proto.open_and(blinds.and0, M0, rec0));
const NodeT nxt1 =
@ -598,9 +832,124 @@ template <typename InteriorPRG,
typename ExteriorPRG,
typename InputT,
typename OutputT,
typename ...OutputTs,
typename RootSampler,
typename CwProtocol>
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));
input_type x = utils::xor_input_shares(x0, x1);
leaf_tuple leaves0{};
leaf_tuple leaves1{};
beaver_tuple beavers0{};
beaver_tuple beavers1{};
if (arith_out)
{
constexpr auto to_int = utils::to_integral_type<input_type>{};
const std::size_t lane = static_cast<std::size_t>(to_int(x));
auto cw = proto.template open_arith_leaf<ExteriorPRG, 0, outputs_tuple>(
dpf::unset_lo_2bits(parent0), dpf::unset_lo_2bits(parent1), t0, t1,
y0, y1, std::size_t{0}, lane);
std::get<0>(leaves0) = cw;
std::get<0>(leaves1) = cw;
}
else
{
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);
(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)>>
auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1,
RootSampler & root_sampler, CwProtocol & proto, OutputT && y,
OutputTs && ...ys)
@ -620,10 +969,21 @@ auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1,
proto.encode_walk_shares(x0, x1, arith);
const node root0 = dpf::unset_lo_bit(static_cast<node>(root_sampler()));
const node root1 = dpf::set_lo_bit(static_cast<node>(root_sampler()));
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{};
@ -632,7 +992,7 @@ auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1,
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, proto,
ds_advance_level<InteriorPRG>(st, x0, x1, mask, level, depth, proto,
correction_words[level], correction_advice[level]);
}
@ -654,9 +1014,37 @@ auto make_dpf_doerner_shelat_impl(bool arith, InputT x0, InputT x1,
built.second.first, built.second.second, off1});
}
/// @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{});
}
} // namespace detail
/// Local CW protocol (pads cancel; same keys as dealer when roots match).
/// @brief Local CW protocol (pads cancel; same keys as dealer when roots match).
template <typename PadRng>
using local_cw_protocol = detail::local_cw_protocol<PadRng>;