libdpf/include/dpf/multipoint.hpp

798 lines
26 KiB
C++
Raw Permalink Normal View History

/// @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, 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.
#ifndef LIBDPF_INCLUDE_DPF_MULTIPOINT_HPP__
#define LIBDPF_INCLUDE_DPF_MULTIPOINT_HPP__
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <limits>
#include <random>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
#include "hedley/hedley.h"
#include "simde/simde/x86/avx2.h"
#include "dpf/eval_point.hpp"
#include "dpf/incremental.hpp"
#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
{
/// @brief Knobs for cuckoo packing. `lambda` is the Remark 1 failure target.
struct multipoint_params
{
std::uint32_t lambda = 40;
std::uint32_t max_evictions = 4096;
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
{
};
template <std::size_t Party,
typename InputT,
typename OutputT,
typename BucketKey>
struct multipoint_key
{
static constexpr std::size_t party = Party;
static constexpr bool is_multipoint = true;
static constexpr bool is_verifiable = BucketKey::is_verifiable;
static constexpr std::size_t kappa = 3;
using input_type = InputT;
using output_type = OutputT;
using bucket_key = BucketKey;
using bucket_input = typename BucketKey::input_type;
using share_type = subtractive_share<OutputT, Party>;
simde__m128i sigma{};
std::uint64_t bucket_count = 0;
mpf_word bucket_domain{};
std::vector<party_key<Party, BucketKey>> buckets{};
};
template <std::size_t Party, typename InputT, typename OutputT, typename BucketKey>
struct is_multipoint_key<multipoint_key<Party, InputT, OutputT, BucketKey>>
: std::true_type
{
};
template <typename T>
inline constexpr bool is_multipoint_key_v =
is_multipoint_key<std::decay_t<T>>::value;
namespace detail
{
namespace mpf
{
struct prp_walk_error : std::runtime_error
{
prp_walk_error()
: std::runtime_error("multipoint PRP cycle walk exceeded its bound")
{
}
};
struct located
{
std::uint64_t bucket = 0;
mpf_word index{};
};
inline mpf_word word_add(mpf_word a, mpf_word b)
{
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
/// into `[0, domain)`. AES-MMO is the round function.
/// @param seed the PRP seed
/// @param x the input, in `[0, domain)`
/// @param domain the domain size
/// @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 mpf_word permute(simde__m128i seed, mpf_word x, mpf_word 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");
const int bits = word_bit_length(word_sub(domain, one));
const int half = (bits + 1) / 2;
const mpf_word mask = low_mask(half);
mpf_word val = x;
for (int guard = 0; guard < 128; ++guard)
{
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)
{
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 = 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, mpf_word x, int hash,
mpf_word n, mpf_word bucket_domain)
{
constexpr int kappa = 3;
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::uint64_t>(quot.lo);
out.index = rem;
return out;
}
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::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;
}
inline std::uint32_t rng_seed(simde__m128i sigma)
{
const auto block = prg::aes128::eval(sigma, 0xC000u);
alignas(16) std::uint32_t words[4];
simde_mm_store_si128(reinterpret_cast<simde__m128i *>(words), block);
return words[0] ^ (words[1] * 0x9E3779B9u) ^ words[2] ^ words[3];
}
struct slot
{
std::int64_t item = -1;
int hash = -1;
};
template <typename InputT>
bool insert_cuckoo(simde__m128i sigma, const std::vector<InputT> & alphas,
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 auto t = static_cast<std::int64_t>(alphas.size());
for (std::int64_t omega = 0; omega < t; ++omega)
{
std::int64_t cur = omega;
int hash = pick(rng);
std::uint32_t evictions = 0;
for (;;)
{
const auto loc = locate(sigma,
to_word(alphas[static_cast<std::size_t>(cur)]),
hash, n, bucket_domain);
if (loc.bucket >= m)
return false;
if (table[loc.bucket].item < 0)
{
table[loc.bucket] = slot{cur, hash};
break;
}
const std::int64_t evicted = table[loc.bucket].item;
table[loc.bucket] = slot{cur, hash};
cur = evicted;
hash = pick(rng);
if (++evictions > max_evictions)
return false;
}
}
return true;
}
template <bool Verifiable,
typename InteriorPRG,
typename ExteriorPRG,
typename BucketInput,
typename OutputT>
auto make_bucket(BucketInput index, const OutputT & beta)
{
if constexpr (Verifiable)
{
return dpf::make_dpf<InteriorPRG, ExteriorPRG>(index, beta,
dpf::verifiable{});
}
else
{
return dpf::make_dpf<InteriorPRG, ExteriorPRG>(index, beta);
}
}
template <bool Verifiable, typename InteriorPRG, typename ExteriorPRG,
typename BucketInput, typename OutputT>
struct bucket_bare
{
using type = typename decltype(make_bucket<Verifiable, InteriorPRG,
ExteriorPRG>(std::declval<BucketInput>(),
std::declval<const OutputT &>()).first)::key_type;
};
template <bool Verifiable,
typename InteriorPRG,
typename ExteriorPRG,
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,
InputT, OutputT>::type;
using key0 = multipoint_key<0, InputT, OutputT, bare>;
using key1 = multipoint_key<1, InputT, OutputT, bare>;
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> <= 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");
{
auto sorted = alphas;
std::sort(sorted.begin(), sorted.end());
if (std::adjacent_find(sorted.begin(), sorted.end()) != sorted.end())
throw std::invalid_argument("make_multipoint: duplicate points");
}
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 mpf_word n = domain_size(input_bits);
constexpr int kappa = 3;
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)
{
try
{
const simde__m128i sigma = dpf::uniform_sample<simde__m128i>();
std::vector<slot> table;
if (!insert_cuckoo(sigma, alphas, m, n, b, params.max_evictions, table))
continue;
key0 left;
key1 right;
left.sigma = sigma;
right.sigma = sigma;
left.bucket_count = m;
right.bucket_count = 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::uint64_t i = 0; i < m; ++i)
{
InputT gamma{};
OutputT beta{};
if (table[static_cast<std::size_t>(i)].item >= 0)
{
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 = 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);
left.buckets.push_back(std::move(made.first));
right.buckets.push_back(std::move(made.second));
}
return std::make_pair(std::move(left), std::move(right));
}
catch (const prp_walk_error &)
{
continue;
}
}
throw std::runtime_error("make_multipoint: cuckoo hashing failed");
}
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>
typename Key::share_type eval_at(const Key & key, typename Key::input_type x,
proof_token * acc)
{
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 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, 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 = from_word<bucket_input>(loc.index);
const auto & bucket = key.buckets[loc.bucket];
if constexpr (Key::is_verifiable)
{
if (acc != nullptr)
{
proof_token inner{};
sum += *dpf::eval_point(bucket, gamma, dpf::prove(inner));
absorb_proof(*acc, inner);
continue;
}
}
sum += *dpf::eval_point(bucket, gamma);
}
return sum;
}
} // namespace mpf
} // namespace detail
/// @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 AlphaRange range of distinct domain points
/// @tparam BetaRange range of payloads, one per point
/// @param alphas the secret points
/// @param betas the payloads
/// @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,
/// 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 AlphaRange,
typename BetaRange>
HEDLEY_WARN_UNUSED_RESULT
auto make_multipoint(const AlphaRange & alphas, const BetaRange & betas,
multipoint_params params = {})
{
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>(
std::vector<input_type>(std::begin(alphas), std::end(alphas)),
std::vector<output_type>(std::begin(betas), std::end(betas)),
params);
}
/// @brief Same packing as `make_multipoint`, with a verifiable bucket key.
/// @see `make_multipoint`
/// @param alphas the secret points
/// @param betas the payloads
/// @param params packing knobs
/// @return the two verifiable party keys
/// @throws std::invalid_argument if the lists differ in length, are empty,
/// 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 AlphaRange,
typename BetaRange>
HEDLEY_WARN_UNUSED_RESULT
auto make_multipoint(const AlphaRange & alphas, const BetaRange & betas,
verifiable, multipoint_params params = {})
{
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>(
std::vector<input_type>(std::begin(alphas), std::end(alphas)),
std::vector<output_type>(std::begin(betas), std::end(betas)),
params);
}
/// @brief Sum the three bucket shares at `x`.
/// @tparam Key a `multipoint_key`
/// @param key the party key
/// @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)
{
return detail::mpf::eval_at(key, x, nullptr);
}
/// @brief Evaluate `x` and fold that query into `pr`.
/// @tparam Key a verifiable `multipoint_key`
/// @param key the party key
/// @param x the query point
/// @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)
{
static_assert(Key::is_verifiable,
"eval_multipoint(..., prove(π)): key must be a verifiable multipoint key");
pr.token = detail::vdpf::zero_proof();
return detail::mpf::eval_at(key, x, &pr.token);
}
/// @brief Evaluate each point of `xs`, writing one share per point.
/// @tparam Key a `multipoint_key`
/// @tparam Range range of query points
/// @tparam OutIt output iterator of shares
/// @param key the party key
/// @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)
{
for (const auto & x : xs)
*out++ = eval_multipoint(key, static_cast<typename Key::input_type>(x));
}
/// @brief Evaluate `xs` and fold every query into one proof.
/// @tparam Key a verifiable `multipoint_key`
/// @tparam Range range of query points
/// @tparam OutIt output iterator of shares
/// @param key the party key
/// @param xs the query points
/// @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)
{
static_assert(Key::is_verifiable,
"eval_multipoint(..., prove(π)): key must be a verifiable multipoint key");
pr.token = detail::vdpf::zero_proof();
for (const auto & x : xs)
{
*out++ = detail::mpf::eval_at(key,
static_cast<typename Key::input_type>(x), &pr.token);
}
}
/// @brief Fold a canonical evaluation of every bucket into one proof.
/// @tparam Key a verifiable `multipoint_key`
/// @param key the party key
/// @param pr proof token replaced with the audit proof
template <typename Key,
std::enable_if_t<is_multipoint_key_v<Key>, int> = 0>
void audit_multipoint(const Key & key, prove_ref pr)
{
static_assert(Key::is_verifiable,
"audit_multipoint: key must be a verifiable multipoint key");
pr.token = detail::vdpf::zero_proof();
for (const auto & bucket : key.buckets)
{
proof_token inner{};
(void)*dpf::eval_point(bucket, typename Key::bucket_input{},
dpf::prove(inner));
detail::mpf::absorb_proof(pr.token, inner);
}
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_MULTIPOINT_HPP__