808 lines
28 KiB
C++
808 lines
28 KiB
C++
/// @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__
|