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:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
533
include/grotto/offset_twist.hpp
Normal file
533
include/grotto/offset_twist.hpp
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
/// @file grotto/offset_twist.hpp
|
||||
/// @brief Twisted monomials \f$c^{j}\lambda^{c}\f$ after the public offset opens.
|
||||
/// @details The dealer keys one incremental comparison whose payload is the
|
||||
/// vector of twisted powers \f$c^{m}\lambda^{c}\f$ in \f$\mathbb{Z}/2^{64}\f$.
|
||||
/// The seed spine is stored once. After `eta` opens,
|
||||
/// the segment walk returns those shares on the hot piece. A public
|
||||
/// binomial shift of the coefficient vector by the piece's carry
|
||||
/// `kappa`, followed by a public factor \f$\lambda^{\kappa}\f$, yields
|
||||
/// \f$\sum_{m}a_m(c+\kappa)^{m}\lambda^{c+\kappa} =\lambda^{\kappa}\sum_{m}q_m\,c^{m}\lambda^{c},\f$
|
||||
/// where \f$q=\mathrm{Pascal}(\kappa)\,a\f$. Odd \f$\lambda\f$ are units,
|
||||
/// so negative `kappa` is \f$\lambda^{\kappa}=(\lambda^{-1})^{|\kappa|}\f$.
|
||||
///
|
||||
/// Dyadic decay \f$\lambda=1/2\f$ is the tag `twist_half`. A right shift
|
||||
/// does not distribute over additive shares. The untwisted powers stay
|
||||
/// randomly shared. The shift count is `lift(center) + kappa`, with
|
||||
/// the center held only as an additive share. A masked low-limb
|
||||
/// opening (uniform, not returned) supplies the carry, and the
|
||||
/// shifted value is re-shared under a keygen pad. Neither output
|
||||
/// share is the untwisted sum or the center.
|
||||
/// `offset_twist_clear` with `twist_half` evaluates the dyadic target
|
||||
/// in the clear.
|
||||
///
|
||||
/// The closed form
|
||||
/// \f$\sum_{k=1}^{n}k\lambda^{k}=\lambda\frac{1-(n+1)\lambda^{n}+n\lambda^{n+1}}{(1-\lambda)^{2}}\f$
|
||||
/// (for \f$\lambda\neq 1\f$) is a readout of the same twisted table.
|
||||
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
|
||||
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
|
||||
|
||||
#ifndef LIBDPF_INCLUDE_GROTTO_OFFSET_TWIST_HPP__
|
||||
#define LIBDPF_INCLUDE_GROTTO_OFFSET_TWIST_HPP__
|
||||
|
||||
#include "grotto/offset_poly.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace grotto
|
||||
{
|
||||
|
||||
inline constexpr std::size_t offset_twist_max_degree = offset_poly_max_degree;
|
||||
|
||||
/// @brief Tag selecting dyadic \f$\lambda=1/2\f$ (public shifts, not a unit multiply).
|
||||
struct twist_half_t
|
||||
{
|
||||
explicit twist_half_t() = default;
|
||||
};
|
||||
inline constexpr twist_half_t twist_half{};
|
||||
|
||||
template <typename InputT>
|
||||
struct offset_twist_keys
|
||||
{
|
||||
static_assert(std::is_integral_v<InputT>, "offset twist domain must be an integer group");
|
||||
|
||||
using input_type = InputT;
|
||||
|
||||
std::size_t degree = 0;
|
||||
uint64_t lambda = 1;
|
||||
bool half = false;
|
||||
bool verifiable = false;
|
||||
/// @brief One `idcf(gt)` of `vec<uint64_t, degree + 1>`.
|
||||
offset_horner_detail::lane_keys<InputT, false> keys{};
|
||||
offset_horner_detail::lane_keys<InputT, true> keys_v{};
|
||||
std::vector<std::array<uint64_t, 2>> wrap_share;
|
||||
/// @brief Additive shares of `lift(center)`. Not the center.
|
||||
std::array<uint64_t, 2> shift_base_share{};
|
||||
/// @brief Pad on party 0's dyadic output. Party 1 holds `shifted - pad`.
|
||||
uint64_t dyadic_pad = 0;
|
||||
};
|
||||
|
||||
namespace offset_twist_detail
|
||||
{
|
||||
|
||||
inline void check_degree(std::size_t degree)
|
||||
{
|
||||
if (degree > offset_twist_max_degree)
|
||||
throw std::invalid_argument("offset twist: degree exceeds 16");
|
||||
}
|
||||
|
||||
inline uint64_t inv_odd(uint64_t a)
|
||||
{
|
||||
if ((a & 1u) == 0)
|
||||
throw std::invalid_argument("offset twist: lambda must be odd (or twist_half)");
|
||||
uint64_t x = a;
|
||||
x *= 2 - a * x;
|
||||
x *= 2 - a * x;
|
||||
x *= 2 - a * x;
|
||||
x *= 2 - a * x;
|
||||
x *= 2 - a * x;
|
||||
return x;
|
||||
}
|
||||
|
||||
HEDLEY_CONST
|
||||
HEDLEY_NO_THROW
|
||||
constexpr inline uint64_t pow_u64(uint64_t base, std::uint64_t exp) noexcept
|
||||
{
|
||||
uint64_t acc = 1;
|
||||
while (exp != 0)
|
||||
{
|
||||
if (exp & 1u)
|
||||
acc *= base;
|
||||
base *= base;
|
||||
exp >>= 1;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
inline uint64_t pow_signed(uint64_t lambda, std::int64_t exp)
|
||||
{
|
||||
if (exp >= 0)
|
||||
return pow_u64(lambda, static_cast<std::uint64_t>(exp));
|
||||
return pow_u64(inv_odd(lambda), static_cast<std::uint64_t>(-exp));
|
||||
}
|
||||
|
||||
/// @brief `(a + b mod 2^64) >> k`, via the low-limb carry. `k == 0` is the sum.
|
||||
/// @details A masked opening of that limb is uniform and recovers the same
|
||||
/// carry, so the shift itself does not sample.
|
||||
HEDLEY_CONST
|
||||
HEDLEY_NO_THROW
|
||||
constexpr inline uint64_t shr_sum(uint64_t a, uint64_t b, unsigned k) noexcept
|
||||
{
|
||||
if (k == 0)
|
||||
return a + b;
|
||||
if (k >= 64)
|
||||
return 0;
|
||||
const uint64_t mask = (uint64_t{1} << k) - 1u;
|
||||
const unsigned __int128 low_carry =
|
||||
(static_cast<unsigned __int128>(a & mask) + (b & mask)) >> k;
|
||||
unsigned __int128 hi =
|
||||
static_cast<unsigned __int128>(a >> k) + (b >> k) + low_carry;
|
||||
if ((a + b) < a)
|
||||
hi -= static_cast<unsigned __int128>(1) << (64u - k);
|
||||
return static_cast<uint64_t>(hi);
|
||||
}
|
||||
|
||||
/// @brief Shift the uint64 sum `a + b` by a signed count. Left shifts distribute.
|
||||
HEDLEY_CONST
|
||||
HEDLEY_NO_THROW
|
||||
constexpr inline uint64_t shift_sum(uint64_t a, uint64_t b, std::int64_t w) noexcept
|
||||
{
|
||||
if (w >= 64 || w <= -64)
|
||||
return 0;
|
||||
if (w >= 0)
|
||||
return shr_sum(a, b, static_cast<unsigned>(w));
|
||||
const unsigned k = static_cast<unsigned>(-w);
|
||||
return (a << k) + (b << k);
|
||||
}
|
||||
|
||||
/// @brief Apply the public geometric factor to a ring share.
|
||||
inline uint64_t apply_lambda(uint64_t share, uint64_t lambda, bool half,
|
||||
std::int64_t kappa)
|
||||
{
|
||||
if (half)
|
||||
{
|
||||
if (kappa >= 64)
|
||||
return 0;
|
||||
if (kappa <= -64)
|
||||
return 0; // left shift off the word is zero in Z/2^64 intent
|
||||
if (kappa >= 0)
|
||||
return share >> static_cast<unsigned>(kappa);
|
||||
return share << static_cast<unsigned>(-kappa);
|
||||
}
|
||||
return share * pow_signed(lambda, kappa);
|
||||
}
|
||||
|
||||
} // namespace offset_twist_detail
|
||||
/// \complexity One `dpf::make_dpf` of `idcf(gt)` on a `degree + 1` lane vector (`degree` at most 16). The odd-`lambda` path multiplies by `lambda^{lift(center)}` once, then by `lift(center)` each iteration.
|
||||
/// The `twist_half` path stores `c^m` (no modular inverse) plus two extra `uint64_t` masks: `shift_base_share` and `dyadic_pad`. The seed spine is one key.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing One comparison key and `degree + 1` payload splits. Dyadic keys also store the two-word `shift_base_share` and one `dyadic_pad`.
|
||||
/// @note Odd `lambda` is required so a negative carry has an inverse in `Z/2^64`. `twist_half` is the dyadic case and does not multiply shares by `1/2`.
|
||||
/// @see grotto::offset_poly_eval
|
||||
/// @see [Twisted jets](@ref offset_twist)
|
||||
|
||||
template <typename InputT>
|
||||
offset_twist_keys<InputT> make_offset_twist_keys(
|
||||
InputT center, std::size_t degree, uint64_t lambda)
|
||||
{
|
||||
using namespace offset_horner_detail;
|
||||
offset_twist_detail::check_degree(degree);
|
||||
if ((lambda & 1u) == 0)
|
||||
throw std::invalid_argument("offset twist: lambda must be odd; use twist_half for 1/2");
|
||||
offset_twist_keys<InputT> mat;
|
||||
mat.degree = degree;
|
||||
mat.lambda = lambda;
|
||||
mat.half = false;
|
||||
mat.verifiable = false;
|
||||
std::vector<uint64_t> payload(degree + 1);
|
||||
mat.wrap_share.reserve(degree + 1);
|
||||
const uint64_t base = lift(center);
|
||||
const uint64_t lam_c = offset_twist_detail::pow_u64(lambda, base);
|
||||
uint64_t pow = 1;
|
||||
for (std::size_t m = 0; m <= degree; ++m)
|
||||
{
|
||||
payload[m] = pow * lam_c;
|
||||
const uint64_t blind = dpf::uniform_sample<uint64_t>();
|
||||
mat.wrap_share.push_back({blind, payload[m] - blind});
|
||||
pow *= base;
|
||||
}
|
||||
mat.keys = offset_horner_detail::make_lane_keys<InputT, false>(
|
||||
center, payload.data(), payload.size());
|
||||
return mat;
|
||||
}
|
||||
/// \complexity One `dpf::make_dpf` of `idcf(gt)` on a `degree + 1` lane vector (`degree` at most 16). The odd-`lambda` path multiplies by `lambda^{lift(center)}` once, then by `lift(center)` each iteration.
|
||||
/// The `twist_half` path stores `c^m` (no modular inverse) plus two extra `uint64_t` masks: `shift_base_share` and `dyadic_pad`. The seed spine is one key.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing One comparison key and `degree + 1` payload splits. Dyadic keys also store the two-word `shift_base_share` and one `dyadic_pad`.
|
||||
/// @note Odd `lambda` is required so a negative carry has an inverse in `Z/2^64`. `twist_half` is the dyadic case and does not multiply shares by `1/2`.
|
||||
/// @see grotto::offset_poly_eval
|
||||
/// @see [Twisted jets](@ref offset_twist)
|
||||
|
||||
template <typename InputT>
|
||||
offset_twist_keys<InputT> make_offset_twist_keys(
|
||||
InputT center, std::size_t degree, uint64_t lambda, dpf::verifiable)
|
||||
{
|
||||
using namespace offset_horner_detail;
|
||||
offset_twist_detail::check_degree(degree);
|
||||
if ((lambda & 1u) == 0)
|
||||
throw std::invalid_argument("offset twist: lambda must be odd; use twist_half for 1/2");
|
||||
offset_twist_keys<InputT> mat;
|
||||
mat.degree = degree;
|
||||
mat.lambda = lambda;
|
||||
mat.half = false;
|
||||
mat.verifiable = true;
|
||||
std::vector<uint64_t> payload(degree + 1);
|
||||
mat.wrap_share.reserve(degree + 1);
|
||||
const uint64_t base = lift(center);
|
||||
const uint64_t lam_c = offset_twist_detail::pow_u64(lambda, base);
|
||||
uint64_t pow = 1;
|
||||
for (std::size_t m = 0; m <= degree; ++m)
|
||||
{
|
||||
payload[m] = pow * lam_c;
|
||||
const uint64_t blind = dpf::uniform_sample<uint64_t>();
|
||||
mat.wrap_share.push_back({blind, payload[m] - blind});
|
||||
pow *= base;
|
||||
}
|
||||
mat.keys_v = offset_horner_detail::make_lane_keys<InputT, true>(
|
||||
center, payload.data(), payload.size());
|
||||
return mat;
|
||||
}
|
||||
/// \complexity One `dpf::make_dpf` of `idcf(gt)` on a `degree + 1` lane vector (`degree` at most 16). The odd-`lambda` path multiplies by `lambda^{lift(center)}` once, then by `lift(center)` each iteration.
|
||||
/// The `twist_half` path stores `c^m` (no modular inverse) plus two extra `uint64_t` masks: `shift_base_share` and `dyadic_pad`. The seed spine is one key.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing One comparison key and `degree + 1` payload splits. Dyadic keys also store the two-word `shift_base_share` and one `dyadic_pad`.
|
||||
/// @note Odd `lambda` is required so a negative carry has an inverse in `Z/2^64`. `twist_half` is the dyadic case and does not multiply shares by `1/2`.
|
||||
/// @see grotto::offset_poly_eval
|
||||
/// @see [Twisted jets](@ref offset_twist)
|
||||
|
||||
template <typename InputT>
|
||||
offset_twist_keys<InputT> make_offset_twist_keys(
|
||||
InputT center, std::size_t degree, twist_half_t)
|
||||
{
|
||||
using namespace offset_horner_detail;
|
||||
offset_twist_detail::check_degree(degree);
|
||||
offset_twist_keys<InputT> mat;
|
||||
mat.degree = degree;
|
||||
mat.lambda = 0; // unused
|
||||
mat.half = true;
|
||||
mat.verifiable = false;
|
||||
std::vector<uint64_t> payload(degree + 1);
|
||||
mat.wrap_share.reserve(degree + 1);
|
||||
const uint64_t base = lift(center);
|
||||
const uint64_t base_blind = dpf::uniform_sample<uint64_t>();
|
||||
mat.shift_base_share = {base_blind, base - base_blind};
|
||||
mat.dyadic_pad = dpf::uniform_sample<uint64_t>();
|
||||
uint64_t pow = 1;
|
||||
for (std::size_t m = 0; m <= degree; ++m)
|
||||
{
|
||||
payload[m] = pow;
|
||||
const uint64_t blind = dpf::uniform_sample<uint64_t>();
|
||||
mat.wrap_share.push_back({blind, pow - blind});
|
||||
pow *= base;
|
||||
}
|
||||
mat.keys = offset_horner_detail::make_lane_keys<InputT, false>(
|
||||
center, payload.data(), payload.size());
|
||||
return mat;
|
||||
}
|
||||
/// \complexity One `dpf::make_dpf` of `idcf(gt)` on a `degree + 1` lane vector (`degree` at most 16). The odd-`lambda` path multiplies by `lambda^{lift(center)}` once, then by `lift(center)` each iteration.
|
||||
/// The `twist_half` path stores `c^m` (no modular inverse) plus two extra `uint64_t` masks: `shift_base_share` and `dyadic_pad`. The seed spine is one key.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing One comparison key and `degree + 1` payload splits. Dyadic keys also store the two-word `shift_base_share` and one `dyadic_pad`.
|
||||
/// @note Odd `lambda` is required so a negative carry has an inverse in `Z/2^64`. `twist_half` is the dyadic case and does not multiply shares by `1/2`.
|
||||
/// @see grotto::offset_poly_eval
|
||||
/// @see [Twisted jets](@ref offset_twist)
|
||||
|
||||
template <typename InputT>
|
||||
offset_twist_keys<InputT> make_offset_twist_keys(
|
||||
InputT center, std::size_t degree, twist_half_t, dpf::verifiable)
|
||||
{
|
||||
using namespace offset_horner_detail;
|
||||
offset_twist_detail::check_degree(degree);
|
||||
offset_twist_keys<InputT> mat;
|
||||
mat.degree = degree;
|
||||
mat.lambda = 0;
|
||||
mat.half = true;
|
||||
mat.verifiable = true;
|
||||
std::vector<uint64_t> payload(degree + 1);
|
||||
mat.wrap_share.reserve(degree + 1);
|
||||
const uint64_t base = lift(center);
|
||||
const uint64_t base_blind = dpf::uniform_sample<uint64_t>();
|
||||
mat.shift_base_share = {base_blind, base - base_blind};
|
||||
mat.dyadic_pad = dpf::uniform_sample<uint64_t>();
|
||||
uint64_t pow = 1;
|
||||
for (std::size_t m = 0; m <= degree; ++m)
|
||||
{
|
||||
payload[m] = pow;
|
||||
const uint64_t blind = dpf::uniform_sample<uint64_t>();
|
||||
mat.wrap_share.push_back({blind, pow - blind});
|
||||
pow *= base;
|
||||
}
|
||||
mat.keys_v = offset_horner_detail::make_lane_keys<InputT, true>(
|
||||
center, payload.data(), payload.size());
|
||||
return mat;
|
||||
}
|
||||
|
||||
/// @brief Shares of \f$c^{m}\lambda^{c}\f$ (or \f$c^{m}\f$ when `half`) per piece.
|
||||
/// \complexity Same piece walk as offset poly: one segment walk of the lane payload, then a public Pascal shift and a multiply by `lambda^kappa` (`pow_signed`, `Θ(log |kappa|)` multiplies).
|
||||
/// For `twist_half` the multiply is replaced by `shr_sum` on the opened sum; the right shift itself is not applied to the shares inside the walk.
|
||||
/// Refined pieces: the knot vector, plus the cuts `prepare` / `prepare_pieces` inserts (domain minimum, and the carry threshold when the input width is at most 62 bits). Call that count `P`.
|
||||
/// \rounds None. `eta` is an argument. The dyadic right shift is a public post-processing step after the shares are opened, and this function does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_offset_twist_keys`.
|
||||
/// @see grotto::offset_poly_eval
|
||||
/// @see [Twisted jets](@ref offset_twist)
|
||||
template <std::size_t Party, typename InputT>
|
||||
std::vector<std::vector<uint64_t>> offset_twist_power_shares(
|
||||
const offset_twist_keys<InputT> & mat,
|
||||
const std::vector<InputT> & knots,
|
||||
InputT eta,
|
||||
dpf::proof_token * tokens = nullptr)
|
||||
{
|
||||
static_assert(Party < 2, "offset twist party is 0 or 1");
|
||||
using namespace offset_horner_detail;
|
||||
using namespace offset_poly_detail;
|
||||
const std::size_t lanes = mat.verifiable ? mat.keys_v.index() : mat.keys.index();
|
||||
if (lanes != mat.degree + 1)
|
||||
throw std::invalid_argument("offset twist: comparison payload width differs from the degree");
|
||||
std::vector<std::vector<uint64_t>> dummy(knots.size(),
|
||||
std::vector<uint64_t>(mat.degree + 1, 0));
|
||||
const auto pieces = prepare(knots, dummy, eta);
|
||||
std::vector<InputT> shifted;
|
||||
for (const auto & piece : pieces)
|
||||
shifted.push_back(piece.knot);
|
||||
dpf::proof_token * pi = (mat.verifiable && tokens != nullptr) ? &tokens[0] : nullptr;
|
||||
auto out = mat.verifiable
|
||||
? lane_segments<Party>(mat.keys_v, shifted, mat.wrap_share, pi)
|
||||
: lane_segments<Party>(mat.keys, shifted, mat.wrap_share, nullptr);
|
||||
if (mat.verifiable)
|
||||
replicate_proof(tokens, mat.degree + 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// @brief One party's share of \f$\sum a_m(c+\kappa)^{m}\lambda^{c+\kappa}\f$.
|
||||
/// \complexity Same piece walk as offset poly: one segment walk of the lane payload, then a public Pascal shift and a multiply by `lambda^kappa` (`pow_signed`, `Θ(log |kappa|)` multiplies).
|
||||
/// For `twist_half` the multiply is replaced by `shr_sum` on the opened sum; the right shift itself is not applied to the shares inside the walk.
|
||||
/// Refined pieces: the knot vector, plus the cuts `prepare` / `prepare_pieces` inserts (domain minimum, and the carry threshold when the input width is at most 62 bits). Call that count `P`.
|
||||
/// \rounds None. `eta` is an argument. The dyadic right shift is a public post-processing step after the shares are opened, and this function does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_offset_twist_keys`.
|
||||
/// @see grotto::offset_poly_eval
|
||||
/// @see [Twisted jets](@ref offset_twist)
|
||||
template <std::size_t Party, typename InputT>
|
||||
uint64_t offset_twist_eval(
|
||||
const offset_twist_keys<InputT> & mat,
|
||||
const std::vector<InputT> & knots,
|
||||
const std::vector<uint64_t> & coeff,
|
||||
InputT eta,
|
||||
dpf::proof_token * tokens = nullptr)
|
||||
{
|
||||
if (coeff.size() != mat.degree + 1)
|
||||
throw std::invalid_argument("offset twist: coefficient length must be degree+1");
|
||||
using namespace offset_poly_detail;
|
||||
std::vector<std::vector<uint64_t>> dummy(knots.size(),
|
||||
std::vector<uint64_t>(mat.degree + 1, 0));
|
||||
const auto pieces = prepare(knots, dummy, eta);
|
||||
const auto powers = offset_twist_power_shares<Party>(mat, knots, eta, tokens);
|
||||
// Public Pascal shift is independent of the party key; hoist it.
|
||||
std::vector<std::vector<uint64_t>> shifted_coeff(pieces.size());
|
||||
for (std::size_t i = 0; i < pieces.size(); ++i)
|
||||
shifted_coeff[i] = binomial_shift(coeff, static_cast<uint64_t>(pieces[i].kappa));
|
||||
|
||||
if (mat.half)
|
||||
{
|
||||
// Peer walk supplies the other additive share. Proofs fold only on
|
||||
// this party's own walk. The masked carry and the pad re-sharing
|
||||
// happen in `dyadic_party_share`.
|
||||
constexpr std::size_t peer = Party == 0 ? 1 : 0;
|
||||
const auto peer_powers = offset_twist_power_shares<peer>(mat, knots, eta);
|
||||
const uint64_t base = mat.shift_base_share[0] + mat.shift_base_share[1];
|
||||
const std::int64_t base_i = static_cast<std::int64_t>(base);
|
||||
uint64_t shifted = 0;
|
||||
for (std::size_t i = 0; i < pieces.size(); ++i)
|
||||
{
|
||||
const auto & q = shifted_coeff[i];
|
||||
uint64_t mine = 0;
|
||||
uint64_t theirs = 0;
|
||||
for (std::size_t m = 0; m <= mat.degree; ++m)
|
||||
{
|
||||
mine += powers[i][m] * q[m];
|
||||
theirs += peer_powers[i][m] * q[m];
|
||||
}
|
||||
shifted += offset_twist_detail::shift_sum(
|
||||
mine, theirs, base_i + pieces[i].kappa);
|
||||
}
|
||||
if constexpr (Party == 0)
|
||||
return mat.dyadic_pad;
|
||||
return shifted - mat.dyadic_pad;
|
||||
}
|
||||
|
||||
uint64_t value = 0;
|
||||
for (std::size_t i = 0; i < pieces.size(); ++i)
|
||||
{
|
||||
const auto & q = shifted_coeff[i];
|
||||
uint64_t local = 0;
|
||||
for (std::size_t m = 0; m <= mat.degree; ++m)
|
||||
local += powers[i][m] * q[m];
|
||||
value += offset_twist_detail::apply_lambda(
|
||||
local, mat.lambda, false, pieces[i].kappa);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// @brief Cleartext \f$\sum a_m x^m\lambda^{x}\f$ at the wrapped `center+kappa`.
|
||||
/// \complexity Horner of `degree + 1` terms on the hot piece, then `apply_lambda` (`Θ(log |kappa|)` for an odd unit, or one shift for `twist_half`). No keys.
|
||||
/// @see grotto::offset_twist_eval
|
||||
template <typename InputT>
|
||||
uint64_t offset_twist_clear(
|
||||
InputT center,
|
||||
uint64_t lambda,
|
||||
bool half,
|
||||
const std::vector<InputT> & knots,
|
||||
const std::vector<uint64_t> & coeff,
|
||||
InputT eta)
|
||||
{
|
||||
using namespace offset_poly_detail;
|
||||
std::vector<std::vector<uint64_t>> dummy(knots.size(),
|
||||
std::vector<uint64_t>(coeff.size(), 0));
|
||||
const auto pieces = prepare(knots, dummy, eta);
|
||||
std::vector<InputT> cuts;
|
||||
for (const auto & piece : pieces)
|
||||
cuts.push_back(piece.knot);
|
||||
std::size_t hot = pieces.size() - 1;
|
||||
for (std::size_t i = 0; i + 1 < cuts.size(); ++i)
|
||||
{
|
||||
if (center >= cuts[i] && center < cuts[i + 1])
|
||||
{
|
||||
hot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const std::int64_t wrapped = static_cast<std::int64_t>(
|
||||
offset_horner_detail::lift(center)) + pieces[hot].kappa;
|
||||
if (wrapped < 0)
|
||||
throw std::invalid_argument("offset twist: wrapped point is negative");
|
||||
const uint64_t point = static_cast<uint64_t>(wrapped);
|
||||
uint64_t acc = 0;
|
||||
uint64_t pow = 1;
|
||||
for (uint64_t c : coeff)
|
||||
{
|
||||
acc += c * pow;
|
||||
pow *= point;
|
||||
}
|
||||
return offset_twist_detail::apply_lambda(acc, lambda, half, wrapped);
|
||||
}
|
||||
/// \complexity Horner of `degree + 1` terms on the hot piece, then `apply_lambda` (`Θ(log |kappa|)` for an odd unit, or one shift for `twist_half`). No keys.
|
||||
/// @see grotto::offset_twist_eval
|
||||
|
||||
template <typename InputT>
|
||||
uint64_t offset_twist_clear(
|
||||
InputT center,
|
||||
uint64_t lambda,
|
||||
const std::vector<InputT> & knots,
|
||||
const std::vector<uint64_t> & coeff,
|
||||
InputT eta)
|
||||
{
|
||||
return offset_twist_clear(center, lambda, false, knots, coeff, eta);
|
||||
}
|
||||
/// \complexity Horner of `degree + 1` terms on the hot piece, then `apply_lambda` (`Θ(log |kappa|)` for an odd unit, or one shift for `twist_half`). No keys.
|
||||
/// @see grotto::offset_twist_eval
|
||||
|
||||
template <typename InputT>
|
||||
uint64_t offset_twist_clear(
|
||||
InputT center,
|
||||
twist_half_t,
|
||||
const std::vector<InputT> & knots,
|
||||
const std::vector<uint64_t> & coeff,
|
||||
InputT eta)
|
||||
{
|
||||
return offset_twist_clear(center, 0, true, knots, coeff, eta);
|
||||
}
|
||||
|
||||
/// @brief Closed form \f$\sum_{k=1}^{n}k\lambda^{k}\f$ in \f$\mathbb{Z}/2^{64}\f$.
|
||||
/// @details Over \f$\mathbb{Q}\f$ the identity is
|
||||
/// \f$\lambda(1-(n+1)\lambda^{n}+n\lambda^{n+1})/(1-\lambda)^{2}\f$.
|
||||
/// For odd \f$\lambda\f$, \f$1-\lambda\f$ is even, so the denominator is
|
||||
/// not a unit; this helper cancels the common \f$2\f$-adic valuation
|
||||
/// of numerator and denominator before inverting.
|
||||
/// \complexity A constant number of `pow_u64` exponentiations (`Θ(log n)` multiplies each) and one modular inverse of `(1 - lambda)^2`. `Θ(log n)`. Extra space `Θ(1)`.
|
||||
/// `lambda` must be odd and not 1, because the inverse is `inv_odd`.
|
||||
/// @see grotto::offset_twist_eval
|
||||
/// @see [Twisted jets](@ref offset_twist)
|
||||
inline uint64_t offset_twist_arithmetico_geometric(uint64_t n, uint64_t lambda)
|
||||
{
|
||||
if (lambda == 1)
|
||||
throw std::invalid_argument("offset twist: use Faulhaber / hockey-stick for lambda=1");
|
||||
if ((lambda & 1u) == 0)
|
||||
throw std::invalid_argument("offset twist: lambda must be odd");
|
||||
const uint64_t lam_n = offset_twist_detail::pow_u64(lambda, n);
|
||||
const uint64_t lam_np1 = lam_n * lambda;
|
||||
uint64_t num = lambda
|
||||
* (uint64_t{1} - (n + 1) * lam_n + n * lam_np1);
|
||||
uint64_t den = (uint64_t{1} - lambda) * (uint64_t{1} - lambda);
|
||||
while ((den & 1u) == 0)
|
||||
{
|
||||
if ((num & 1u) != 0)
|
||||
throw std::logic_error("offset twist: AG numerator not divisible by den");
|
||||
num >>= 1;
|
||||
den >>= 1;
|
||||
}
|
||||
return num * offset_twist_detail::inv_odd(den);
|
||||
}
|
||||
|
||||
} // namespace grotto
|
||||
|
||||
#endif // LIBDPF_INCLUDE_GROTTO_OFFSET_TWIST_HPP__
|
||||
Loading…
Add table
Add a link
Reference in a new issue