libdpf/include/grotto/offset_horner.hpp

809 lines
28 KiB
C++
Raw Normal View History

/// @file grotto/offset_horner.hpp
/// @brief Noninteractive cubic evaluation after the public offset is opened.
/// @details The dealer keys one comparison at `center` per power
/// `1, center, center^2, center^3` in Z/2^64. After the parties open
/// `eta`, each party shifts the knots by `eta`, inserts the domain
/// minimum and the public carry threshold, and sorts. The
/// sign-respecting segment walk then returns additive shares of
/// `center^m` on the refined piece that contains `center`. On each
/// refined piece the wrapped input is `center + kappa` for a public
/// `kappa`: `eta` on the side that does not overflow, and
/// `eta ∓ 2^n` on the side that does. A public binomial shift by that
/// `kappa`, dotted with the segment shares, is a share of the
/// polynomial at the wrapped group element. No further round.
///
/// Domains of 63 bits or more are already the ring Z/2^64, so the
/// carry adjustment is the identity there. `lift` sign-extends a
/// signed domain element and zero-extends an unsigned one.
///
/// `offset_horner_at_x_plus_r` is the wiring from the reconstruction
/// the parties already do: `eta = x - r` and `center = 2r`.
#ifndef LIBDPF_INCLUDE_GROTTO_OFFSET_HORNER_HPP__
#define LIBDPF_INCLUDE_GROTTO_OFFSET_HORNER_HPP__
#include "hedley/hedley.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <optional>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "dpf.hpp"
#include "grotto/prefix_parity.hpp"
namespace grotto
{
inline constexpr std::size_t offset_horner_max_degree = 3;
template <typename T>
HEDLEY_NO_THROW
T offset_horner_group_add(T a, T b) noexcept
{
using u = std::make_unsigned_t<T>;
return static_cast<T>(static_cast<u>(static_cast<u>(a) + static_cast<u>(b)));
}
template <typename T>
HEDLEY_NO_THROW
T offset_horner_group_sub(T a, T b) noexcept
{
using u = std::make_unsigned_t<T>;
return static_cast<T>(static_cast<u>(static_cast<u>(a) - static_cast<u>(b)));
}
/// @brief `eta = x - r` and `center = 2r`, both in the input group.
/// @tparam T value type
template <typename T>
struct offset_horner_x_plus_r
{
T eta{};
T center{};
};
template <typename T>
HEDLEY_NO_THROW
offset_horner_x_plus_r<T> offset_horner_at_x_plus_r(T x, T r) noexcept
{
return offset_horner_x_plus_r<T>{
offset_horner_group_sub(x, r),
offset_horner_group_add(r, r)};
}
template <typename InputT, std::size_t Degree>
struct offset_horner_keys;
namespace offset_horner_detail
{
inline constexpr uint64_t binom[4][4] = {
{1, 0, 0, 0},
{1, 1, 0, 0},
{1, 2, 1, 0},
{1, 3, 3, 1},
};
template <typename T>
HEDLEY_NO_THROW
uint64_t lift(T v) noexcept
{
if constexpr (std::is_signed_v<T>)
return static_cast<uint64_t>(static_cast<std::int64_t>(v));
else
return static_cast<uint64_t>(v);
}
template <std::size_t Degree>
HEDLEY_NO_THROW
uint64_t horner_at(const std::array<uint64_t, Degree + 1> & coeff, uint64_t point) noexcept
{
uint64_t acc = coeff[Degree];
for (std::size_t k = Degree; k-- > 0; )
acc = acc * point + coeff[k];
return acc;
}
template <std::size_t Degree>
HEDLEY_NO_THROW
void fill_payloads(uint64_t base, uint64_t (&payload)[Degree + 1]) noexcept
{
uint64_t pow = 1;
for (std::size_t m = 0; m <= Degree; ++m)
{
payload[m] = pow;
pow *= base;
}
}
template <typename InputT, std::size_t Degree, std::size_t... M>
auto make_key_array(InputT center, const uint64_t (&payload)[Degree + 1],
std::index_sequence<M...>)
{
using pair = typename offset_horner_keys<InputT, Degree>::key_pair;
return std::array<pair, Degree + 1>{
dpf::make_dpf(center, dpf::gt(payload[M]))...};
}
template <typename InputT>
void check_knots(const std::vector<InputT> & knots, std::size_t coeff_rows)
{
if (knots.empty() || knots.size() != coeff_rows)
throw std::invalid_argument("offset horner: knots and coefficient rows differ");
for (std::size_t i = 1; i < knots.size(); ++i)
{
if (!(knots[i - 1] < knots[i]))
throw std::invalid_argument("offset horner: knots must be strictly increasing");
}
}
template <std::size_t Degree, typename InputT>
struct shifted_piece
{
InputT knot{};
std::array<uint64_t, Degree + 1> coeff{};
};
template <std::size_t Degree, typename InputT>
std::vector<shifted_piece<Degree, InputT>> shift_and_sort(
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
InputT eta)
{
std::vector<shifted_piece<Degree, InputT>> rows(knots.size());
for (std::size_t i = 0; i < knots.size(); ++i)
{
rows[i].knot = offset_horner_group_sub(knots[i], eta);
rows[i].coeff = coeff[i];
}
std::sort(rows.begin(), rows.end(),
[](const shifted_piece<Degree, InputT> & a, const shifted_piece<Degree, InputT> & b) {
return a.knot < b.knot;
});
return rows;
}
inline std::vector<uint64_t> segments_from_prefixes(
const std::vector<uint64_t> & prefix, uint64_t wrap_share, uint64_t mask)
{
using namespace dpf::detail::dcf_impl;
const std::size_t n = prefix.size();
if (n == 1)
return std::vector<uint64_t>{wrap_share & mask};
std::vector<uint64_t> seg(n);
for (std::size_t i = 0; i < n; ++i)
{
const uint64_t nxt = prefix[(i + 1) % n];
seg[i] = (nxt + neg_m(prefix[i], mask)) & mask;
}
seg[n - 1] = (seg[n - 1] + wrap_share) & mask;
return seg;
}
template <typename Key, typename InputT>
std::vector<uint64_t> segments_of(const Key & key, const std::vector<InputT> & knots,
uint64_t wrap_share)
{
const std::size_t n = knots.size();
if (n == 1)
return std::vector<uint64_t>{wrap_share & key.cmp().mask};
std::vector<uint64_t> prefix(n);
signed_prefix_parities_into(key, knots.data(), n, prefix.data());
return segments_from_prefixes(prefix, wrap_share, key.cmp().mask);
}
template <typename T>
HEDLEY_NO_THROW
int64_t math_lift(T value) noexcept
{
if constexpr (std::is_signed_v<T>)
return static_cast<int64_t>(value);
else
return static_cast<int64_t>(lift(value));
}
template <typename T>
HEDLEY_NO_THROW
T domain_min() noexcept
{
if constexpr (std::is_signed_v<T>)
return std::numeric_limits<T>::min();
else
return T{0};
}
/// @brief Public center-space cut where `center + eta` crosses the domain end.
/// @details Empty when that cut is outside the domain, including `eta == 0`.
/// @tparam T value type
/// @param eta the `eta`
/// @return Public center-space cut where `center + eta` crosses the domain end
template <typename T>
HEDLEY_NO_THROW
std::optional<T> carry_threshold(T eta) noexcept
{
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
if (bits > 62)
return std::nullopt;
const int64_t mod = int64_t{1} << bits;
const int64_t half = mod >> 1;
const int64_t ez = math_lift(eta);
if constexpr (std::is_signed_v<T>)
{
if (ez > 0)
return static_cast<T>(half - ez);
if (ez < 0)
return static_cast<T>(-half - ez);
return std::nullopt;
}
else
{
if (ez == 0)
return std::nullopt;
return static_cast<T>(mod - ez);
}
}
/// @brief `center + kappa` is the wrapped representative, as a mathematical integer.
/// @tparam T value type
/// @param left the `left`
/// @param eta the `eta`
/// @return `center + kappa` is the wrapped representative, as a mathematical integer
template <typename T>
HEDLEY_NO_THROW
int64_t kappa_for(T left, T eta) noexcept
{
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
const int64_t ez = math_lift(eta);
if (bits > 62)
return ez;
const int64_t mod = int64_t{1} << bits;
const int64_t left_i = math_lift(left);
if constexpr (std::is_signed_v<T>)
{
const int64_t half = mod >> 1;
if (ez > 0 && left_i >= half - ez)
return ez - mod;
if (ez < 0 && left_i < -half - ez)
return ez + mod;
return ez;
}
else
{
if (ez != 0 && left_i >= mod - ez)
return ez - mod;
return ez;
}
}
template <std::size_t Degree, typename InputT>
int piece_index(InputT point, const std::vector<InputT> & sorted_knots)
{
const std::size_t n = sorted_knots.size();
if (n <= 1)
return 0;
for (std::size_t i = 0; i + 1 < n; ++i)
{
if (point >= sorted_knots[i] && point < sorted_knots[i + 1])
return static_cast<int>(i);
}
return static_cast<int>(n - 1);
}
template <std::size_t Degree>
std::array<uint64_t, Degree + 1> binomial_coefficients(
const std::array<uint64_t, Degree + 1> & a, uint64_t center_limb)
{
std::array<uint64_t, Degree + 1> c{};
uint64_t center_pow[Degree + 1];
center_pow[0] = 1;
for (std::size_t m = 1; m <= Degree; ++m)
center_pow[m] = center_pow[m - 1] * center_limb;
for (std::size_t m = 0; m <= Degree; ++m)
{
for (std::size_t k = 0; k <= m; ++k)
c[k] += a[m] * binom[m][k] * center_pow[m - k];
}
return c;
}
template <std::size_t Degree, typename InputT>
struct prepared_piece
{
InputT knot{};
std::array<uint64_t, Degree + 1> coeff{};
int64_t kappa = 0;
};
template <std::size_t Degree, typename InputT>
void insert_cut(std::vector<shifted_piece<Degree, InputT>> & rows, InputT point)
{
for (const auto & row : rows)
{
if (row.knot == point)
return;
}
std::vector<InputT> knots;
knots.reserve(rows.size());
for (const auto & row : rows)
knots.push_back(row.knot);
const int hot = piece_index<Degree>(point, knots);
shifted_piece<Degree, InputT> extra;
extra.knot = point;
extra.coeff = rows[static_cast<std::size_t>(hot)].coeff;
rows.push_back(std::move(extra));
std::sort(rows.begin(), rows.end(),
[](const shifted_piece<Degree, InputT> & a, const shifted_piece<Degree, InputT> & b) {
return a.knot < b.knot;
});
}
template <std::size_t Degree, typename InputT>
std::vector<prepared_piece<Degree, InputT>> prepare_pieces(
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
InputT eta)
{
auto rows = shift_and_sort<Degree>(knots, coeff, eta);
constexpr unsigned bits = dpf::utils::bitlength_of_v<InputT>;
if (bits <= 62)
{
insert_cut<Degree>(rows, domain_min<InputT>());
if (const auto cut = carry_threshold(eta))
insert_cut<Degree>(rows, *cut);
}
std::vector<prepared_piece<Degree, InputT>> out;
out.reserve(rows.size());
for (const auto & row : rows)
{
prepared_piece<Degree, InputT> piece;
piece.knot = row.knot;
piece.coeff = row.coeff;
piece.kappa = kappa_for(row.knot, eta);
out.push_back(std::move(piece));
}
return out;
}
/// @brief `out[k]` sums to the polynomial at the wrapped input. It is
/// `center^k` times the public binomial coefficient of `kappa`, not a
/// coefficient you Horner-evaluate at `eta`.
/// @tparam Degree degree
/// @param seg the `seg`
/// @param coeff the public coefficient
/// @param kappa the `kappa`
/// @return `out[k]` sums to the polynomial at the wrapped input
template <std::size_t Degree>
std::array<uint64_t, Degree + 1> contributions(
const std::array<std::vector<uint64_t>, Degree + 1> & seg,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
const std::vector<int64_t> & kappa)
{
std::array<uint64_t, Degree + 1> out{};
const std::size_t n = coeff.size();
for (std::size_t i = 0; i < n; ++i)
{
const auto q = binomial_coefficients<Degree>(
coeff[i], static_cast<uint64_t>(kappa[i]));
for (std::size_t k = 0; k <= Degree; ++k)
out[k] += seg[k][i] * q[k];
}
return out;
}
} // namespace offset_horner_detail
/// @brief Both parties' comparison keys and wrap-piece shares for one center.
/// @tparam InputT input domain type
/// @tparam Degree degree
template <typename InputT, std::size_t Degree = offset_horner_max_degree>
struct offset_horner_keys
{
static_assert(Degree <= offset_horner_max_degree, "offset horner degree is at most 3");
static_assert(std::is_integral_v<InputT>, "offset horner domain must be an integer group");
static constexpr std::size_t degree = Degree;
using input_type = InputT;
using key_pair = decltype(dpf::make_dpf(std::declval<InputT>(), dpf::gt(uint64_t{0})));
InputT center{};
/// @brief `keys[m]` is `gt(center^m)` keyed at `center`. `.first` is party 0.
std::array<key_pair, Degree + 1> keys;
/// @brief Random additive split of `center^m`, indexed `[power][party]`.
std::array<std::array<uint64_t, 2>, Degree + 1> wrap_share{};
};
template <typename InputT, std::size_t Degree = offset_horner_max_degree>
offset_horner_keys<InputT, Degree> make_offset_horner_keys(InputT center)
{
using namespace offset_horner_detail;
uint64_t payload[Degree + 1];
fill_payloads<Degree>(lift(center), payload);
offset_horner_keys<InputT, Degree> mat{
center,
make_key_array<InputT, Degree>(center, payload, std::make_index_sequence<Degree + 1>{}),
{}};
for (std::size_t m = 0; m <= Degree; ++m)
{
const uint64_t blind = dpf::uniform_sample<uint64_t>();
mat.wrap_share[m][0] = blind;
mat.wrap_share[m][1] = payload[m] - blind;
}
return mat;
}
/// @brief Cleartext binomial coefficients of the selected refined piece in the
/// variable `center`: Horner at `lift(center)` is the polynomial at the
/// wrapped input.
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @param center the `center`
/// @param knots the `knots`
/// @param coeff the public coefficient
/// @param eta the `eta`
/// @return Cleartext binomial coefficients of the selected refined piece in the variable `center`:
/// Horner at `lift(center)` is the polynomial at the wrapped input
template <std::size_t Degree, typename InputT>
std::array<uint64_t, Degree + 1> offset_horner_clear_coefficients(
InputT center,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
InputT eta)
{
using namespace offset_horner_detail;
check_knots(knots, coeff.size());
const auto pieces = prepare_pieces<Degree>(knots, coeff, eta);
std::vector<InputT> cuts;
cuts.reserve(pieces.size());
for (const auto & piece : pieces)
cuts.push_back(piece.knot);
const int hot = piece_index<Degree>(center, cuts);
const auto & piece = pieces[static_cast<std::size_t>(hot)];
return binomial_coefficients<Degree>(
piece.coeff, static_cast<uint64_t>(piece.kappa));
}
/// @brief Cleartext value of the selected piece at the wrapped `center + eta`.
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @param center the `center`
/// @param knots the `knots`
/// @param coeff the public coefficient
/// @param eta the `eta`
/// @return Cleartext value of the selected piece at the wrapped `center + eta`
template <std::size_t Degree, typename InputT>
uint64_t offset_horner_clear(
InputT center,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
InputT eta)
{
const auto c = offset_horner_clear_coefficients<Degree>(center, knots, coeff, eta);
return offset_horner_detail::horner_at<Degree>(c, offset_horner_detail::lift(center));
}
/// @brief `Party` selects `.first` or `.second` of each key pair.
/// @tparam Party party index, `0` or `1`
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @tparam KeyPair key pair
/// @param keys the party keys
/// @param wrap_share the `wrap_share`
/// @param knots the `knots`
/// @param coeff the public coefficient
/// @param eta the `eta`
/// @return `Party` selects `.first` or `.second` of each key pair
/// @throws std::invalid_argument if `one comparison key per power`
template <std::size_t Party, std::size_t Degree, typename InputT, typename KeyPair>
std::array<uint64_t, Degree + 1> offset_horner_coefficient_share(
const std::vector<KeyPair> & keys,
const std::array<std::array<uint64_t, 2>, Degree + 1> & wrap_share,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
InputT eta)
{
static_assert(Party < 2, "offset horner party is 0 or 1");
using namespace offset_horner_detail;
check_knots(knots, coeff.size());
if (keys.size() != Degree + 1)
throw std::invalid_argument("offset horner: one comparison key per power");
const auto pieces = prepare_pieces<Degree>(knots, coeff, eta);
std::vector<InputT> shifted(pieces.size());
std::vector<std::array<uint64_t, Degree + 1>> ordered(pieces.size());
std::vector<int64_t> kappa(pieces.size());
for (std::size_t i = 0; i < pieces.size(); ++i)
{
shifted[i] = pieces[i].knot;
ordered[i] = pieces[i].coeff;
kappa[i] = pieces[i].kappa;
}
std::array<std::vector<uint64_t>, Degree + 1> seg;
for (std::size_t m = 0; m <= Degree; ++m)
{
seg[m] = segments_of(std::get<Party>(keys[m]), shifted, wrap_share[m][Party]);
}
return contributions<Degree>(seg, ordered, kappa);
}
/// @brief One party's coefficient shares. `Party` is 0 or 1.
/// @tparam Party party index, `0` or `1`
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @param mat the `mat`
/// @param knots the `knots`
/// @param coeff the public coefficient
/// @param eta the `eta`
/// @return One party's coefficient shares
template <std::size_t Party, std::size_t Degree, typename InputT>
std::array<uint64_t, Degree + 1> offset_horner_coefficient_share(
const offset_horner_keys<InputT, Degree> & mat,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
InputT eta)
{
std::vector<typename offset_horner_keys<InputT, Degree>::key_pair> keys(
mat.keys.begin(), mat.keys.end());
return offset_horner_coefficient_share<Party, Degree>(
keys, mat.wrap_share, knots, coeff, eta);
}
/// @brief Both parties' Horner shares from one joint Doerner–Shelat generation.
/// @details `center0 XOR center1` is the comparison point, in geneval's share
/// convention (the signed MSB of `center0` is flipped before the XOR, and
/// flipped back here). `eta` is already public.
/// @tparam Degree degree
/// @tparam InputT input domain type
template <std::size_t Degree, typename InputT>
struct geneval_offset_horner_result
{
static_assert(Degree <= offset_horner_max_degree, "offset horner degree is at most 3");
InputT center{};
InputT eta{};
std::array<uint64_t, Degree + 1> coeff0{};
std::array<uint64_t, Degree + 1> coeff1{};
uint64_t value0 = 0;
uint64_t value1 = 0;
};
/// @brief Logical comparison point for geneval's XOR shares. Matches `make_dpf(P)`
/// when `center1 = P XOR center0` or when `center0 = P` and `center1 = 0`.
/// @tparam InputT input domain type
/// @param center0 the `center0`
/// @param center1 the `center1`
/// @return Logical comparison point for geneval's XOR shares
template <typename InputT>
InputT geneval_offset_horner_center(InputT center0, InputT center1)
{
InputT flipped0 = center0;
dpf::utils::flip_msb_if_signed_integral(flipped0);
InputT mixed = dpf::utils::xor_input_shares(flipped0, center1);
dpf::utils::flip_msb_if_signed_integral(mixed);
return mixed;
}
namespace offset_horner_detail
{
template <std::size_t Degree, typename InputT, typename Rng>
geneval_offset_horner_result<Degree, InputT> geneval_at(
bool arith, InputT center0, InputT center1, InputT center, InputT eta,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
Rng rng)
{
check_knots(knots, coeff.size());
const auto pieces = prepare_pieces<Degree>(knots, coeff, eta);
std::vector<InputT> shifted(pieces.size());
std::vector<std::array<uint64_t, Degree + 1>> ordered(pieces.size());
std::vector<int64_t> kappa(pieces.size());
for (std::size_t i = 0; i < pieces.size(); ++i)
{
shifted[i] = pieces[i].knot;
ordered[i] = pieces[i].coeff;
kappa[i] = pieces[i].kappa;
}
uint64_t payload[Degree + 1];
fill_payloads<Degree>(lift(center), payload);
std::array<std::array<uint64_t, 2>, Degree + 1> wrap{};
std::array<std::vector<uint64_t>, Degree + 1> seg0;
std::array<std::vector<uint64_t>, Degree + 1> seg1;
for (std::size_t m = 0; m <= Degree; ++m)
{
const auto opened = arith
? dpf::geneval_cmp(dpf::arith_input, center0, center1,
shifted.begin(), shifted.end(), rng, payload[m])
: dpf::geneval_cmp(center0, center1,
shifted.begin(), shifted.end(), rng, payload[m]);
const uint64_t blind = dpf::uniform_sample<uint64_t>();
wrap[m][0] = blind;
wrap[m][1] = payload[m] - blind;
seg0[m] = segments_from_prefixes(opened.party0, wrap[m][0], opened.mask);
seg1[m] = segments_from_prefixes(opened.party1, wrap[m][1], opened.mask);
}
geneval_offset_horner_result<Degree, InputT> out;
out.center = center;
out.eta = eta;
out.coeff0 = contributions<Degree>(seg0, ordered, kappa);
out.coeff1 = contributions<Degree>(seg1, ordered, kappa);
for (uint64_t term : out.coeff0)
out.value0 += term;
for (uint64_t term : out.coeff1)
out.value1 += term;
return out;
}
template <std::size_t Degree, typename InputT, typename Rng>
geneval_offset_horner_result<Degree, InputT> geneval_at(
InputT center0, InputT center1, InputT center, InputT eta,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
Rng rng)
{
return geneval_at<Degree>(false, center0, center1, center, eta, knots, coeff,
std::move(rng));
}
} // namespace offset_horner_detail
/// @brief Geneval-style offset Horner. The center is XOR-shared as in `geneval_point`.
/// @details Comparison keys are opened with the same local Doerner–Shelat protocol
/// geneval uses for its correction words. The value dot uses the per-piece
/// carry shift and is local.
/// A value-correction word is required on every level of the secret path, so
/// this does not stop early the way a leaf trie does.
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @tparam Rng rng
/// @param center0 the `center0`
/// @param center1 the `center1`
/// @param eta the `eta`
/// @param knots the `knots`
/// @param coeff the public coefficient
/// @param rng the Doerner–Shelat randomness tapes
/// @return Geneval-style offset Horner
template <std::size_t Degree = offset_horner_max_degree,
typename InputT,
typename Rng>
geneval_offset_horner_result<Degree, InputT> geneval_offset_horner(
InputT center0, InputT center1, InputT eta,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
Rng rng)
{
const InputT center = geneval_offset_horner_center(center0, center1);
return offset_horner_detail::geneval_at<Degree>(
center0, center1, center, eta, knots, coeff, std::move(rng));
}
template <std::size_t Degree = offset_horner_max_degree, typename InputT>
geneval_offset_horner_result<Degree, InputT> geneval_offset_horner(
InputT center0, InputT center1, InputT eta,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff)
{
using block = typename dpf::prg::aes128::block_type;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<block (*)(), dpf::detail::urandom_pad_rng> rng{
dpf::uniform_sample<block>, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
return geneval_offset_horner<Degree>(
center0, center1, eta, knots, coeff, std::move(rng));
}
/// @brief Additive shares of the center: `center0 + center1` is the comparison point.
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @tparam Rng rng
/// @param arith_input_t the `arith_input_t`
/// @param center0 the `center0`
/// @param center1 the `center1`
/// @param eta the `eta`
/// @param knots the `knots`
/// @param coeff the public coefficient
/// @param rng the Doerner–Shelat randomness tapes
/// @return Additive shares of the center: `center0 + center1` is the comparison point
template <std::size_t Degree = offset_horner_max_degree,
typename InputT,
typename Rng>
geneval_offset_horner_result<Degree, InputT> geneval_offset_horner(
dpf::arith_input_t, InputT center0, InputT center1, InputT eta,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
Rng rng)
{
const InputT center = offset_horner_group_add(center0, center1);
return offset_horner_detail::geneval_at<Degree>(
true, center0, center1, center, eta, knots, coeff, std::move(rng));
}
/// @brief Additive shares of the input `x` and the mask `r`. Reconstructs
/// `eta = x - r` and passes additive shares of `center = 2r` (`2·r0`, `2·r1`)
/// to arithmetic `geneval_cmp`. Returns both parties' Horner shares of the
/// cubic at `x + r` (the group element `x + r`).
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @tparam Rng rng
/// @param x0 the `x0`
/// @param x1 the `x1`
/// @param r0 the party 0's share of the mask
/// @param r1 the party 1's share of the mask
/// @param knots the `knots`
/// @param coeff the public coefficient
/// @param rng the Doerner–Shelat randomness tapes
/// @return Additive shares of the input `x` and the mask `r`
template <std::size_t Degree = offset_horner_max_degree,
typename InputT,
typename Rng>
geneval_offset_horner_result<Degree, InputT> geneval_offset_horner(
InputT x0, InputT x1, InputT r0, InputT r1,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
Rng rng)
{
const InputT x = offset_horner_group_add(x0, x1);
const InputT r = offset_horner_group_add(r0, r1);
const InputT eta = offset_horner_group_sub(x, r);
const InputT center0 = offset_horner_group_add(r0, r0);
const InputT center1 = offset_horner_group_add(r1, r1);
const InputT center = offset_horner_group_add(center0, center1);
return offset_horner_detail::geneval_at<Degree>(
true, center0, center1, center, eta, knots, coeff, std::move(rng));
}
template <std::size_t Degree = offset_horner_max_degree, typename InputT>
geneval_offset_horner_result<Degree, InputT> geneval_offset_horner(
InputT x0, InputT x1, InputT r0, InputT r1,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff)
{
using block = typename dpf::prg::aes128::block_type;
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<block (*)(), dpf::detail::urandom_pad_rng> rng{
dpf::uniform_sample<block>, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
return geneval_offset_horner<Degree>(
x0, x1, r0, r1, knots, coeff, std::move(rng));
}
/// @brief One party's share of the cubic at the wrapped `center + eta`.
/// @details Sum the coefficient shares; they are already scaled by `center^k`.
/// @tparam Party party index, `0` or `1`
/// @tparam Degree degree
/// @tparam InputT input domain type
/// @param mat the `mat`
/// @param knots the `knots`
/// @param coeff the public coefficient
/// @param eta the `eta`
/// @return One party's share of the cubic at the wrapped `center + eta`
template <std::size_t Party, std::size_t Degree, typename InputT>
uint64_t offset_horner_eval(
const offset_horner_keys<InputT, Degree> & mat,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
InputT eta)
{
const auto shares = offset_horner_coefficient_share<Party, Degree>(mat, knots, coeff, eta);
uint64_t value = 0;
for (uint64_t term : shares)
value += term;
return value;
}
} // namespace grotto
#endif // LIBDPF_INCLUDE_GROTTO_OFFSET_HORNER_HPP__