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