Checkpoint the party/runtime stack before share-program and malicious-mode work.

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>
This commit is contained in:
Ryan Henry 2026-09-28 05:59:19 -06:00
parent 695f8e84f7
commit 0d22946a0e
1835 changed files with 170291 additions and 2849 deletions

View file

@ -1,10 +1,11 @@
/// @file dpf/multipoint.hpp
/// @brief Cuckoo-packed multi-point DPF and verifiable multi-point DPF.
/// @details Packs t distinct points into m ≈ O(t) buckets (de Castro–
/// Polychroniadou, EUROCRYPT 2022, §4). Each bucket is an ordinary
/// Polychroniadou, EUROCRYPT 2022, §4, ePrint 2021/580). Each bucket is an ordinary
/// point key on a smaller domain — `dpf::verifiable` selects VDPF
/// buckets. Evaluation probes κ = 3 buckets and sums the shares.
/// A batched proof is one 2λ token.
/// @note Following that section: κ = 3 cuckoo hashes, one point key per bucket.
/// @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.
@ -32,6 +33,7 @@
#include "dpf/prg_aes.hpp"
#include "dpf/random.hpp"
#include "dpf/secret_share.hpp"
#include "dpf/uint256_t.hpp"
#include "dpf/verifiable.hpp"
namespace dpf
@ -45,6 +47,26 @@ struct multipoint_params
int retries = 8;
};
/// @brief 512-bit word for the cuckoo PRP. Holds `3·2^b` for every input
/// width this library can form a point key on (up to 256 bits).
struct mpf_word
{
uint256_t lo{};
uint256_t hi{};
friend bool operator==(mpf_word a, mpf_word b) noexcept
{
return a.lo == b.lo && a.hi == b.hi;
}
friend bool operator<(mpf_word a, mpf_word b) noexcept
{
if (a.hi != b.hi)
return a.hi < b.hi;
return a.lo < b.lo;
}
};
template <typename T>
struct is_multipoint_key : std::false_type
{
@ -68,8 +90,8 @@ struct multipoint_key
using share_type = subtractive_share<OutputT, Party>;
simde__m128i sigma{};
std::uint32_t bucket_count = 0;
std::uint64_t bucket_domain = 0;
std::uint64_t bucket_count = 0;
mpf_word bucket_domain{};
std::vector<party_key<Party, BucketKey>> buckets{};
};
@ -98,15 +120,237 @@ struct prp_walk_error : std::runtime_error
struct located
{
std::uint32_t bucket = 0;
std::uint64_t index = 0;
std::uint64_t bucket = 0;
mpf_word index{};
};
using wide = unsigned __int128;
inline wide domain_size(std::size_t bits)
inline mpf_word word_add(mpf_word a, mpf_word b)
{
return wide{1} << bits;
mpf_word r;
r.lo = a.lo + b.lo;
r.hi = a.hi + b.hi;
if (r.lo < a.lo)
r.hi = r.hi + uint256_t{1};
return r;
}
inline mpf_word word_sub(mpf_word a, mpf_word b)
{
mpf_word r;
r.lo = a.lo - b.lo;
r.hi = a.hi - b.hi;
if (a.lo < b.lo)
r.hi = r.hi - uint256_t{1};
return r;
}
inline mpf_word word_shl(mpf_word a, unsigned shift)
{
if (shift == 0)
return a;
if (shift >= 512)
return {};
if (shift >= 256)
{
mpf_word r;
r.hi = a.lo << (shift - 256);
return r;
}
mpf_word r;
r.lo = a.lo << shift;
r.hi = (a.hi << shift) | (a.lo >> (256 - shift));
return r;
}
inline mpf_word word_shr(mpf_word a, unsigned shift)
{
if (shift == 0)
return a;
if (shift >= 512)
return {};
if (shift >= 256)
{
mpf_word r;
r.lo = a.hi >> (shift - 256);
return r;
}
mpf_word r;
r.hi = a.hi >> shift;
r.lo = (a.lo >> shift) | (a.hi << (256 - shift));
return r;
}
inline mpf_word word_or(mpf_word a, mpf_word b)
{
a.lo = a.lo | b.lo;
a.hi = a.hi | b.hi;
return a;
}
inline mpf_word word_and(mpf_word a, mpf_word b)
{
a.lo = a.lo & b.lo;
a.hi = a.hi & b.hi;
return a;
}
inline bool word_bit(mpf_word a, unsigned bit)
{
if (bit >= 512)
return false;
if (bit >= 256)
return static_cast<bool>((a.hi >> (bit - 256)) & uint256_t{1});
return static_cast<bool>((a.lo >> bit) & uint256_t{1});
}
inline int word_bit_length(mpf_word a)
{
for (int i = 255; i >= 0; --i)
{
if (static_cast<bool>((a.hi >> i) & uint256_t{1}))
return i + 1 + 256;
}
for (int i = 255; i >= 0; --i)
{
if (static_cast<bool>((a.lo >> i) & uint256_t{1}))
return i + 1;
}
return 0;
}
inline mpf_word word_mul_small(mpf_word a, std::uint64_t k)
{
mpf_word r{};
while (k != 0)
{
if ((k & 1u) != 0)
r = word_add(r, a);
a = word_shl(a, 1);
k >>= 1;
}
return r;
}
inline std::pair<mpf_word, mpf_word> word_divmod(mpf_word num, mpf_word den)
{
if (den == mpf_word{})
throw std::invalid_argument("multipoint division by zero");
mpf_word q{};
mpf_word r{};
const int top = word_bit_length(num);
for (int i = top - 1; i >= 0; --i)
{
r = word_shl(r, 1);
if (word_bit(num, static_cast<unsigned>(i)))
r = word_add(r, mpf_word{uint256_t{1}, uint256_t{0}});
if (!(r < den))
{
r = word_sub(r, den);
mpf_word bit{};
if (i >= 256)
bit.hi = uint256_t{1} << static_cast<unsigned>(i - 256);
else
bit.lo = uint256_t{1} << static_cast<unsigned>(i);
q = word_or(q, bit);
}
}
return {q, r};
}
inline mpf_word domain_size(std::size_t bits)
{
mpf_word r{};
if (bits >= 512)
throw std::invalid_argument("multipoint domain shift is out of range");
if (bits >= 256)
r.hi = uint256_t{1} << (bits - 256);
else if (bits > 0)
r.lo = uint256_t{1} << bits;
return r;
}
template <typename T>
mpf_word to_word(T x)
{
constexpr std::size_t bits = utils::bitlength_of_v<T>;
mpf_word w{};
if constexpr (bits > 128)
{
w.lo = static_cast<uint256_t>(x);
}
else if constexpr (bits > 64)
{
uint128_t low{};
std::memcpy(&low, &x, sizeof(T));
w.lo = uint256_t{low};
}
else
{
w.lo = uint256_t{static_cast<std::uint64_t>(x)};
}
return w;
}
template <typename T>
T from_word(mpf_word w)
{
constexpr std::size_t bits = utils::bitlength_of_v<T>;
if constexpr (bits > 128)
{
return static_cast<T>(w.lo);
}
else if constexpr (bits > 64)
{
const uint128_t low = static_cast<uint128_t>(w.lo);
T out{};
std::memcpy(&out, &low, sizeof(T));
return out;
}
else
{
return static_cast<T>(static_cast<std::uint64_t>(w.lo));
}
}
/// @brief Low `half` bits set, as a 512-bit mask. `half <= 0` is zero.
inline mpf_word low_mask(int half)
{
if (half <= 0)
return {};
if (half >= 512)
{
mpf_word all;
all.lo = ~uint256_t{0};
all.hi = ~uint256_t{0};
return all;
}
return word_sub(domain_size(static_cast<std::size_t>(half)),
mpf_word{uint256_t{1}, uint256_t{0}});
}
inline mpf_word aes_prf(simde__m128i seed, mpf_word right, int round)
{
alignas(16) unsigned char raw[32]{};
std::memcpy(raw, &right.lo, sizeof(right.lo));
alignas(16) simde__m128i block0;
alignas(16) simde__m128i block1;
std::memcpy(&block0, raw, 16);
std::memcpy(&block1, raw + 16, 16);
block0 = simde_mm_xor_si128(block0, seed);
block0 = simde_mm_xor_si128(block0, simde_mm_set_epi32(0, 0, 0, round + 1));
const auto out0 = prg::aes128::eval(block0,
static_cast<psnip_uint32_t>(round + 1));
block1 = simde_mm_xor_si128(block1, seed);
block1 = simde_mm_xor_si128(block1,
simde_mm_set_epi32(0, 0, 0, round + 0x11));
const auto out1 = prg::aes128::eval(block1,
static_cast<psnip_uint32_t>(round + 0x21));
alignas(16) unsigned char packed[32];
std::memcpy(packed, &out0, 16);
std::memcpy(packed + 16, &out1, 16);
mpf_word f{};
std::memcpy(&f.lo, packed, sizeof(f.lo));
return f;
}
/// @brief 4-round Feistel on the next power-of-two square, then cycle-walk
@ -117,75 +361,68 @@ inline wide domain_size(std::size_t bits)
/// @return the permuted value in `[0, domain)`
/// @throws std::invalid_argument if `x` is outside the domain
/// @throws prp_walk_error if the cycle walk exceeds its bound
inline wide permute(simde__m128i seed, wide x, wide domain)
inline mpf_word permute(simde__m128i seed, mpf_word x, mpf_word domain)
{
if (domain <= 1)
return 0;
if (x >= domain)
const mpf_word one{uint256_t{1}, uint256_t{0}};
if (!(one < domain))
return {};
if (!(x < domain))
throw std::invalid_argument("multipoint PRP input is outside the domain");
int bits = 0;
for (wide v = domain - 1; v > 0; v >>= 1)
++bits;
const int bits = word_bit_length(word_sub(domain, one));
const int half = (bits + 1) / 2;
const wide mask = (half >= 128)
? ~wide{0}
: (wide{1} << half) - 1;
const mpf_word mask = low_mask(half);
wide val = x;
mpf_word val = x;
for (int guard = 0; guard < 128; ++guard)
{
unsigned __int128 left = (val >> half) & mask;
unsigned __int128 right = val & mask;
mpf_word left = word_and(word_shr(val, static_cast<unsigned>(half)), mask);
mpf_word right = word_and(val, mask);
for (int round = 0; round < 4; ++round)
{
alignas(16) std::uint64_t lanes[2] = {
static_cast<std::uint64_t>(right),
static_cast<std::uint64_t>(right >> 64)};
auto msg = simde_mm_load_si128(
reinterpret_cast<const simde__m128i *>(lanes));
msg = simde_mm_xor_si128(msg, seed);
msg = simde_mm_xor_si128(msg,
simde_mm_set_epi32(0, 0, 0, round + 1));
const auto out = prg::aes128::eval(msg,
static_cast<psnip_uint32_t>(round + 1));
simde_mm_store_si128(reinterpret_cast<simde__m128i *>(lanes), out);
wide f = lanes[0] | (wide{lanes[1]} << 64);
f &= mask;
left ^= f;
const wide tmp = left;
const mpf_word f = word_and(aes_prf(seed, right, round), mask);
left.lo = left.lo ^ f.lo;
left.hi = left.hi ^ f.hi;
const mpf_word tmp = left;
left = right;
right = tmp;
}
val = (left << half) | right;
val = word_or(word_shl(left, static_cast<unsigned>(half)), right);
if (val < domain)
return val;
}
throw prp_walk_error{};
}
inline located locate(simde__m128i sigma, wide x, int hash,
wide n, wide bucket_domain)
inline located locate(simde__m128i sigma, mpf_word x, int hash,
mpf_word n, mpf_word bucket_domain)
{
constexpr int kappa = 3;
const wide y = permute(sigma,
x + n * static_cast<unsigned>(hash), n * kappa);
const mpf_word y = permute(sigma,
word_add(x, word_mul_small(n, static_cast<std::uint64_t>(hash))),
word_mul_small(n, kappa));
const auto [quot, rem] = word_divmod(y, bucket_domain);
if (quot.hi != uint256_t{0})
throw std::runtime_error("multipoint bucket index does not fit");
located out;
out.bucket = static_cast<std::uint32_t>(y / bucket_domain);
out.index = static_cast<std::uint64_t>(y % bucket_domain);
out.bucket = static_cast<std::uint64_t>(quot.lo);
out.index = rem;
return out;
}
inline std::uint32_t bucket_count_for(std::uint32_t t, std::uint32_t lambda)
inline std::uint64_t bucket_count_for(std::uint64_t t, std::uint32_t lambda)
{
const double log2t = (t <= 1) ? 0.0 : std::log2(static_cast<double>(t));
const double e = (static_cast<double>(lambda) + 130.0 + log2t) / 123.5;
auto m = static_cast<std::uint32_t>(std::ceil(e * static_cast<double>(t)));
auto m = static_cast<std::uint64_t>(std::ceil(e * static_cast<double>(t)));
if (m < t + 1)
m = t + 1;
// Remark 1's simplification wants t ≥ 30. Below that, keep a 2t table.
if (t < 30 && m < t * 2)
m = t * 2;
// At least κ buckets so each within-bucket index fits in the input type.
if (m < 3)
m = 3;
return m;
}
@ -199,28 +436,28 @@ inline std::uint32_t rng_seed(simde__m128i sigma)
struct slot
{
int item = -1;
std::int64_t item = -1;
int hash = -1;
};
template <typename InputT>
bool insert_cuckoo(simde__m128i sigma, const std::vector<InputT> & alphas,
std::uint32_t m, wide n, wide bucket_domain,
std::uint64_t m, mpf_word n, mpf_word bucket_domain,
std::uint32_t max_evictions, std::vector<slot> & table)
{
table.assign(m, slot{});
std::mt19937 rng(rng_seed(sigma));
std::uniform_int_distribution<int> pick(0, 2);
const int t = static_cast<int>(alphas.size());
for (int omega = 0; omega < t; ++omega)
const auto t = static_cast<std::int64_t>(alphas.size());
for (std::int64_t omega = 0; omega < t; ++omega)
{
int cur = omega;
std::int64_t cur = omega;
int hash = pick(rng);
std::uint32_t evictions = 0;
for (;;)
{
const auto loc = locate(sigma,
static_cast<wide>(alphas[static_cast<std::size_t>(cur)]),
to_word(alphas[static_cast<std::size_t>(cur)]),
hash, n, bucket_domain);
if (loc.bucket >= m)
return false;
@ -229,7 +466,7 @@ bool insert_cuckoo(simde__m128i sigma, const std::vector<InputT> & alphas,
table[loc.bucket] = slot{cur, hash};
break;
}
const int evicted = table[loc.bucket].item;
const std::int64_t evicted = table[loc.bucket].item;
table[loc.bucket] = slot{cur, hash};
cur = evicted;
hash = pick(rng);
@ -270,31 +507,26 @@ struct bucket_bare
template <bool Verifiable,
typename InteriorPRG,
typename ExteriorPRG,
typename BucketInput,
typename InputT,
typename OutputT>
auto make_impl(std::vector<InputT> alphas, std::vector<OutputT> betas,
multipoint_params params)
{
using bare = typename bucket_bare<Verifiable, InteriorPRG, ExteriorPRG,
BucketInput, OutputT>::type;
InputT, OutputT>::type;
using key0 = multipoint_key<0, InputT, OutputT, bare>;
using key1 = multipoint_key<1, InputT, OutputT, bare>;
static_assert(std::is_unsigned_v<InputT> && !std::is_same_v<InputT, bool>,
static_assert(!std::is_same_v<InputT, bool>
&& (std::is_unsigned_v<InputT> || std::is_same_v<InputT, uint256_t>),
"make_multipoint: input domain must be an unsigned integer");
static_assert(utils::bitlength_of_v<InputT> <= 32,
"make_multipoint: input domain wider than 32 bits is not supported");
static_assert(std::is_unsigned_v<BucketInput>
&& !std::is_same_v<BucketInput, bool>,
"make_multipoint: BucketInput must be an unsigned integer");
static_assert(utils::bitlength_of_v<InputT> <= 256,
"make_multipoint: input type is wider than a point key in this library");
if (alphas.size() != betas.size())
throw std::invalid_argument("make_multipoint: point and payload counts differ");
if (alphas.empty())
throw std::invalid_argument("make_multipoint: no points");
if (alphas.size() > static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max()))
throw std::invalid_argument("make_multipoint: too many points");
{
auto sorted = alphas;
@ -303,19 +535,16 @@ auto make_impl(std::vector<InputT> alphas, std::vector<OutputT> betas,
throw std::invalid_argument("make_multipoint: duplicate points");
}
const auto t = static_cast<std::uint32_t>(alphas.size());
const auto t = static_cast<std::uint64_t>(alphas.size());
const auto m = bucket_count_for(t, params.lambda);
constexpr std::size_t input_bits = utils::bitlength_of_v<InputT>;
const wide n = domain_size(input_bits);
const mpf_word n = domain_size(input_bits);
constexpr int kappa = 3;
const wide b = (n * kappa + m - 1) / m;
constexpr std::size_t bucket_bits = utils::bitlength_of_v<BucketInput>;
const wide bucket_cap = domain_size(bucket_bits);
if (b > bucket_cap)
{
throw std::invalid_argument(
"make_multipoint: bucket domain does not fit in BucketInput");
}
const mpf_word span = word_mul_small(n, kappa);
const mpf_word den{uint256_t{m}, uint256_t{0}};
const mpf_word numer = word_add(span,
word_sub(den, mpf_word{uint256_t{1}, uint256_t{0}}));
const mpf_word b = word_divmod(numer, den).first;
const int attempts = params.retries < 1 ? 1 : params.retries;
for (int attempt = 0; attempt < attempts; ++attempt)
@ -333,24 +562,26 @@ auto make_impl(std::vector<InputT> alphas, std::vector<OutputT> betas,
right.sigma = sigma;
left.bucket_count = m;
right.bucket_count = m;
left.bucket_domain = static_cast<std::uint64_t>(b);
right.bucket_domain = static_cast<std::uint64_t>(b);
left.buckets.reserve(m);
right.buckets.reserve(m);
left.bucket_domain = b;
right.bucket_domain = b;
left.buckets.reserve(static_cast<std::size_t>(m));
right.buckets.reserve(static_cast<std::size_t>(m));
for (std::uint32_t i = 0; i < m; ++i)
for (std::uint64_t i = 0; i < m; ++i)
{
BucketInput gamma{};
InputT gamma{};
OutputT beta{};
if (table[i].item >= 0)
if (table[static_cast<std::size_t>(i)].item >= 0)
{
const auto & alpha = alphas[static_cast<std::size_t>(table[i].item)];
const auto loc = locate(sigma,
static_cast<wide>(alpha), table[i].hash, n, b);
const auto & alpha = alphas[static_cast<std::size_t>(
table[static_cast<std::size_t>(i)].item)];
const auto loc = locate(sigma, to_word(alpha),
table[static_cast<std::size_t>(i)].hash, n, b);
if (loc.bucket != i)
throw prp_walk_error{};
gamma = static_cast<BucketInput>(loc.index);
beta = betas[static_cast<std::size_t>(table[i].item)];
gamma = from_word<InputT>(loc.index);
beta = betas[static_cast<std::size_t>(
table[static_cast<std::size_t>(i)].item)];
}
auto made = make_bucket<Verifiable, InteriorPRG, ExteriorPRG>(
gamma, beta);
@ -371,6 +602,7 @@ inline void absorb_proof(proof_token & acc, const proof_token & inner)
{
acc = detail::vdpf::xor_proof(acc, inner);
acc[0] = detail::vdpf::mmo(acc[0], 1);
acc[1] = detail::vdpf::mmo(acc[1], 2);
}
template <typename Key>
@ -380,17 +612,17 @@ typename Key::share_type eval_at(const Key & key, typename Key::input_type x,
using input_type = typename Key::input_type;
using bucket_input = typename Key::bucket_input;
constexpr std::size_t input_bits = utils::bitlength_of_v<input_type>;
const wide n = domain_size(input_bits);
const wide b = key.bucket_domain;
const mpf_word n = domain_size(input_bits);
const mpf_word b = key.bucket_domain;
typename Key::share_type sum =
Key::share_type::from_raw(typename Key::output_type{});
for (int hash = 0; hash < static_cast<int>(Key::kappa); ++hash)
{
const auto loc = locate(key.sigma, static_cast<wide>(x), hash, n, b);
const auto loc = locate(key.sigma, to_word(x), hash, n, b);
if (loc.bucket >= key.bucket_count)
throw std::runtime_error("multipoint eval: bucket out of range");
const auto gamma = static_cast<bucket_input>(loc.index);
const auto gamma = from_word<bucket_input>(loc.index);
const auto & bucket = key.buckets[loc.bucket];
if constexpr (Key::is_verifiable)
{
@ -413,7 +645,6 @@ typename Key::share_type eval_at(const Key & key, typename Key::input_type x,
/// @brief Cuckoo-pack distinct points into ordinary point-key buckets.
/// @tparam InteriorPRG PRG that expands interior nodes. Defaults to `dpf::prg::aes128`
/// @tparam ExteriorPRG PRG that expands the root. Defaults to `InteriorPRG`
/// @tparam BucketInput unsigned type of a bucket index. Defaults to `uint32_t`
/// @tparam AlphaRange range of distinct domain points
/// @tparam BetaRange range of payloads, one per point
/// @param alphas the secret points
@ -421,11 +652,12 @@ typename Key::share_type eval_at(const Key & key, typename Key::input_type x,
/// @param params packing knobs. `lambda` is the Remark 1 failure target
/// @return the two party keys
/// @throws std::invalid_argument if the lists differ in length, are empty,
/// contain a duplicate, or a bucket index does not fit `BucketInput`
/// or contain a duplicate
/// @throws std::runtime_error if cuckoo hashing does not succeed
/// @note Following de Castro and Polychroniadou, EUROCRYPT 2022, §4 (ePrint 2021/580): κ = 3 cuckoo buckets, one point key each.
/// \complexity For t points, `bucket_count_for` sets m = ceil((lambda + 130 + log2(t)) / 123.5 * t) (at least 2t when t < 30, and at least 3). Each attempt inserts t cuckoo items (up to `max_evictions` swaps each) and then one `make_dpf` per bucket. The within-bucket domain `b` is ceil(3 * 2^{input bits} / m). Counted `insert_cuckoo` and the bucket loop. Retries are `params.retries`.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename BucketInput = std::uint32_t,
typename AlphaRange,
typename BetaRange>
HEDLEY_WARN_UNUSED_RESULT
@ -434,7 +666,7 @@ auto make_multipoint(const AlphaRange & alphas, const BetaRange & betas,
{
using input_type = std::decay_t<decltype(*std::begin(alphas))>;
using output_type = std::decay_t<decltype(*std::begin(betas))>;
return detail::mpf::make_impl<false, InteriorPRG, ExteriorPRG, BucketInput>(
return detail::mpf::make_impl<false, InteriorPRG, ExteriorPRG>(
std::vector<input_type>(std::begin(alphas), std::end(alphas)),
std::vector<output_type>(std::begin(betas), std::end(betas)),
params);
@ -447,11 +679,12 @@ auto make_multipoint(const AlphaRange & alphas, const BetaRange & betas,
/// @param params packing knobs
/// @return the two verifiable party keys
/// @throws std::invalid_argument if the lists differ in length, are empty,
/// contain a duplicate, or a bucket index does not fit `BucketInput`
/// or contain a duplicate
/// @throws std::runtime_error if cuckoo hashing does not succeed
/// @note Following de Castro and Polychroniadou, EUROCRYPT 2022, §4 (ePrint 2021/580): κ = 3 cuckoo buckets, one point key each.
/// \complexity For t points, `bucket_count_for` sets m = ceil((lambda + 130 + log2(t)) / 123.5 * t) (at least 2t when t < 30, and at least 3). Each attempt inserts t cuckoo items (up to `max_evictions` swaps each) and then one `make_dpf` per bucket. The within-bucket domain `b` is ceil(3 * 2^{input bits} / m). Counted `insert_cuckoo` and the bucket loop. Retries are `params.retries`.
template <typename InteriorPRG = dpf::prg::aes128,
typename ExteriorPRG = InteriorPRG,
typename BucketInput = std::uint32_t,
typename AlphaRange,
typename BetaRange>
HEDLEY_WARN_UNUSED_RESULT
@ -460,7 +693,7 @@ auto make_multipoint(const AlphaRange & alphas, const BetaRange & betas,
{
using input_type = std::decay_t<decltype(*std::begin(alphas))>;
using output_type = std::decay_t<decltype(*std::begin(betas))>;
return detail::mpf::make_impl<true, InteriorPRG, ExteriorPRG, BucketInput>(
return detail::mpf::make_impl<true, InteriorPRG, ExteriorPRG>(
std::vector<input_type>(std::begin(alphas), std::end(alphas)),
std::vector<output_type>(std::begin(betas), std::end(betas)),
params);
@ -472,6 +705,7 @@ auto make_multipoint(const AlphaRange & alphas, const BetaRange & betas,
/// @param x the query point
/// @return the party's share of the payload, or of zero off the packed points
/// @throws std::runtime_error if a located bucket is outside the key
/// \complexity Three `eval_point` calls (`Key::kappa` is 3), each O(n_b) where n_b is the bucket key depth.
template <typename Key,
std::enable_if_t<is_multipoint_key_v<Key>, int> = 0>
auto eval_multipoint(const Key & key, typename Key::input_type x)
@ -486,6 +720,7 @@ auto eval_multipoint(const Key & key, typename Key::input_type x)
/// @param pr proof token replaced with this query's folded proof
/// @return the party's share of the payload
/// @throws std::runtime_error if a located bucket is outside the key
/// \complexity Three `eval_point` calls (`Key::kappa` is 3), each O(n_b) where n_b is the bucket key depth.
template <typename Key,
std::enable_if_t<is_multipoint_key_v<Key>, int> = 0>
auto eval_multipoint(const Key & key, typename Key::input_type x, prove_ref pr)
@ -504,6 +739,7 @@ auto eval_multipoint(const Key & key, typename Key::input_type x, prove_ref pr)
/// @param xs the query points
/// @param out where each share is written
/// @throws std::runtime_error if a located bucket is outside the key
/// \complexity Three `eval_point` calls (`Key::kappa` is 3), each O(n_b) where n_b is the bucket key depth.
template <typename Key, typename Range, typename OutIt,
std::enable_if_t<is_multipoint_key_v<Key>, int> = 0>
void eval_multipoint(const Key & key, const Range & xs, OutIt out)
@ -521,6 +757,7 @@ void eval_multipoint(const Key & key, const Range & xs, OutIt out)
/// @param out where each share is written
/// @param pr proof token replaced with the folded proof of `xs`
/// @throws std::runtime_error if a located bucket is outside the key
/// \complexity Three `eval_point` calls (`Key::kappa` is 3), each O(n_b) where n_b is the bucket key depth.
template <typename Key, typename Range, typename OutIt,
std::enable_if_t<is_multipoint_key_v<Key>, int> = 0>
void eval_multipoint(const Key & key, const Range & xs, OutIt out, prove_ref pr)