Record Grotto half-ulp tables and comparison geneval, and factor shared beaver terms before the quotient.
Horner and window evaluation need those tables in the tree. Comparison geneval opens the same value words as a Doerner–Shelat key. A factor common to every polynomial term is multiplied first so that preprocessing stays smaller. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3f10e05176
commit
875f09fec1
14 changed files with 42668 additions and 184 deletions
|
|
@ -14,9 +14,12 @@
|
||||||
/// A polynomial is a sum of monomials in several wires.
|
/// A polynomial is a sum of monomials in several wires.
|
||||||
/// `2 + 3*x + 4*y + 5*x*y + 6*pow(x, 2) + pow(x, 2)*y + x*y*z`
|
/// `2 + 3*x + 4*y + 5*x*y + 6*pow(x, 2) + pow(x, 2)*y + x*y*z`
|
||||||
/// is one round. `λ_x²` is stored once whether it appears as `x²`,
|
/// is one round. `λ_x²` is stored once whether it appears as `x²`,
|
||||||
/// inside `x² y`, or in a second polynomial. `sgn * (x*y + pow(x, 2))`
|
/// inside `x² y`, or in a second polynomial. Wires that occur with the
|
||||||
/// is the sign-corrected form and reuses those powers. A product that
|
/// same exponents in every term, as in `a3*(x*z)^3 + a2*(x*z)^2 + a1*(x*z) + a0`,
|
||||||
/// uses an output of an earlier polynomial is a later round.
|
/// are multiplied first and the univariate polynomial is a later round.
|
||||||
|
/// A factor shared by every term, such as a sign or a piecewise scale,
|
||||||
|
/// is applied after the quotient when that uses fewer preprocessing
|
||||||
|
/// values. A lone secret summand is added from its value share.
|
||||||
///
|
///
|
||||||
/// Doerner–Shelat's per-level AND is a `bit_mul` of a fresh bit and
|
/// Doerner–Shelat's per-level AND is a `bit_mul` of a fresh bit and
|
||||||
/// a fresh block. A wildcard leaf is a `scale` of one scalar by each
|
/// a fresh block. A wildcard leaf is a `scale` of one scalar by each
|
||||||
|
|
@ -720,9 +723,23 @@ public:
|
||||||
}
|
}
|
||||||
else if (g.kind == gate_kind::poly)
|
else if (g.kind == gate_kind::poly)
|
||||||
{
|
{
|
||||||
for (const auto & term : g.terms)
|
for (const auto & step : g.steps)
|
||||||
for (auto id : term.factors)
|
{
|
||||||
|
if (step.value_wire >= 0)
|
||||||
|
{
|
||||||
|
const auto id = static_cast<std::uint32_t>(step.value_wire);
|
||||||
|
if (!wires_[id].value_ready)
|
||||||
|
throw std::logic_error("beaver wire is not ready to open");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (auto [id, exp] : step.delta)
|
||||||
|
{
|
||||||
|
(void)exp;
|
||||||
ensure_delta(id);
|
ensure_delta(id);
|
||||||
|
}
|
||||||
|
if (step.mask_wire >= 0)
|
||||||
|
ensure_delta(static_cast<std::uint32_t>(step.mask_wire));
|
||||||
|
}
|
||||||
val = eval_poly(g);
|
val = eval_poly(g);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -858,6 +875,7 @@ private:
|
||||||
Ring scale{};
|
Ring scale{};
|
||||||
int bundle = -1;
|
int bundle = -1;
|
||||||
int mask_wire = -1;
|
int mask_wire = -1;
|
||||||
|
int value_wire = -1;
|
||||||
bool public_only = false;
|
bool public_only = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1176,67 +1194,108 @@ private:
|
||||||
for (std::uint32_t id = 0; id < wires_.size(); ++id)
|
for (std::uint32_t id = 0; id < wires_.size(); ++id)
|
||||||
already[id] = needs_blind(id) ? 1 : 0;
|
already[id] = needs_blind(id) ? 1 : 0;
|
||||||
auto saved = bundles_;
|
auto saved = bundles_;
|
||||||
|
std::vector<std::vector<poly_step>> compiled;
|
||||||
|
compiled.reserve(pieces.size());
|
||||||
for (const auto & piece : pieces)
|
for (const auto & piece : pieces)
|
||||||
(void)compile_poly(piece);
|
compiled.push_back(compile_poly(piece));
|
||||||
std::size_t added = bundles_.size() - saved.size();
|
const auto bundle_base = saved.size();
|
||||||
bundles_ = std::move(saved);
|
std::size_t added = bundles_.size() - bundle_base;
|
||||||
std::map<std::uint32_t, char> blinds;
|
std::map<std::uint32_t, char> blinds;
|
||||||
for (const auto & piece : pieces)
|
auto note = [&](std::uint32_t id) {
|
||||||
{
|
if (id < already.size() && already[id] != 0)
|
||||||
for (const auto & term : piece)
|
return;
|
||||||
{
|
|
||||||
for (auto id : term.factors)
|
|
||||||
{
|
|
||||||
if (id >= already.size() || already[id] == 0)
|
|
||||||
blinds[id] = 1;
|
blinds[id] = 1;
|
||||||
|
};
|
||||||
|
for (std::size_t i = bundle_base; i < bundles_.size(); ++i)
|
||||||
|
{
|
||||||
|
for (const auto & part : bundles_[i].parts)
|
||||||
|
{
|
||||||
|
for (auto [wid, exp] : part.lam)
|
||||||
|
{
|
||||||
|
(void)exp;
|
||||||
|
note(wid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const auto & steps : compiled)
|
||||||
|
{
|
||||||
|
for (const auto & step : steps)
|
||||||
|
{
|
||||||
|
if (step.mask_wire >= 0)
|
||||||
|
note(static_cast<std::uint32_t>(step.mask_wire));
|
||||||
|
for (auto [wid, exp] : step.delta)
|
||||||
|
{
|
||||||
|
(void)exp;
|
||||||
|
note(wid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bundles_ = std::move(saved);
|
||||||
return added + blinds.size();
|
return added + blinds.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
wire schedule_terms(std::vector<poly_term> terms)
|
static std::vector<poly_term> drop_one(std::vector<poly_term> terms, std::uint32_t wire_id)
|
||||||
{
|
{
|
||||||
std::vector<std::vector<poly_term>> best{terms};
|
for (auto & term : terms)
|
||||||
std::size_t best_cost = estimate_pieces(best);
|
|
||||||
|
|
||||||
std::map<std::uint32_t, char> seen;
|
|
||||||
for (const auto & term : terms)
|
|
||||||
for (auto id : term.factors)
|
|
||||||
seen[id] = 1;
|
|
||||||
for (auto [wire_id, _] : seen)
|
|
||||||
{
|
|
||||||
bool common = true;
|
|
||||||
for (const auto & term : terms)
|
|
||||||
{
|
|
||||||
if (factor_exp(term, wire_id) == 0)
|
|
||||||
common = false;
|
|
||||||
}
|
|
||||||
if (!common)
|
|
||||||
continue;
|
|
||||||
std::vector<poly_term> quot = terms;
|
|
||||||
for (auto & term : quot)
|
|
||||||
{
|
{
|
||||||
auto it = std::find(term.factors.begin(), term.factors.end(), wire_id);
|
auto it = std::find(term.factors.begin(), term.factors.end(), wire_id);
|
||||||
if (it != term.factors.end())
|
if (it != term.factors.end())
|
||||||
term.factors.erase(it);
|
term.factors.erase(it);
|
||||||
}
|
}
|
||||||
const std::uint32_t mid = static_cast<std::uint32_t>(wires_.size());
|
return terms;
|
||||||
poly_term mul;
|
}
|
||||||
mul.coeff = traits::one();
|
|
||||||
mul.factors = {mid, wire_id};
|
static bool wire_in_every(const std::vector<poly_term> & terms, std::uint32_t wire_id)
|
||||||
std::vector<std::vector<poly_term>> seq{std::move(quot), {std::move(mul)}};
|
{
|
||||||
std::size_t cost = estimate_pieces(seq);
|
for (const auto & term : terms)
|
||||||
|
{
|
||||||
|
if (factor_exp(term, wire_id) == 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !terms.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
wire schedule_terms(std::vector<poly_term> terms)
|
||||||
|
{
|
||||||
|
if (terms.size() < 2)
|
||||||
|
return emit_terms(std::move(terms));
|
||||||
|
|
||||||
|
std::vector<std::vector<poly_term>> best{terms};
|
||||||
|
std::size_t best_cost = estimate_pieces(best);
|
||||||
|
auto consider = [&](std::vector<std::vector<poly_term>> seq) {
|
||||||
|
const std::size_t cost = estimate_pieces(seq);
|
||||||
if (cost < best_cost)
|
if (cost < best_cost)
|
||||||
{
|
{
|
||||||
best = std::move(seq);
|
best = std::move(seq);
|
||||||
best_cost = cost;
|
best_cost = cost;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<std::uint32_t, char> seen;
|
||||||
|
for (const auto & term : terms)
|
||||||
|
for (auto id : term.factors)
|
||||||
|
seen[id] = 1;
|
||||||
|
|
||||||
|
std::vector<std::uint32_t> common;
|
||||||
|
for (auto [wire_id, present] : seen)
|
||||||
|
{
|
||||||
|
(void)present;
|
||||||
|
if (wire_in_every(terms, wire_id))
|
||||||
|
common.push_back(wire_id);
|
||||||
|
}
|
||||||
|
const auto fresh = static_cast<std::uint32_t>(wires_.size());
|
||||||
|
for (auto wire_id : common)
|
||||||
|
{
|
||||||
|
poly_term mul;
|
||||||
|
mul.coeff = traits::one();
|
||||||
|
mul.factors = {fresh, wire_id};
|
||||||
|
consider({drop_one(terms, wire_id), {std::move(mul)}});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::vector<std::uint8_t>, std::vector<std::uint32_t>> clusters;
|
std::map<std::vector<std::uint8_t>, std::vector<std::uint32_t>> clusters;
|
||||||
for (auto [wire_id, _] : seen)
|
for (auto [wire_id, present] : seen)
|
||||||
{
|
{
|
||||||
|
(void)present;
|
||||||
std::vector<std::uint8_t> shape;
|
std::vector<std::uint8_t> shape;
|
||||||
shape.reserve(terms.size());
|
shape.reserve(terms.size());
|
||||||
bool any = false;
|
bool any = false;
|
||||||
|
|
@ -1247,13 +1306,13 @@ private:
|
||||||
any = any || e != 0;
|
any = any || e != 0;
|
||||||
}
|
}
|
||||||
if (any)
|
if (any)
|
||||||
clusters[shape].push_back(wire_id);
|
clusters[std::move(shape)].push_back(wire_id);
|
||||||
}
|
}
|
||||||
for (auto & [shape, group] : clusters)
|
for (auto & [shape, group] : clusters)
|
||||||
{
|
{
|
||||||
|
(void)shape;
|
||||||
if (group.size() < 2)
|
if (group.size() < 2)
|
||||||
continue;
|
continue;
|
||||||
const std::uint32_t mid = static_cast<std::uint32_t>(wires_.size());
|
|
||||||
poly_term prod;
|
poly_term prod;
|
||||||
prod.coeff = traits::one();
|
prod.coeff = traits::one();
|
||||||
prod.factors = group;
|
prod.factors = group;
|
||||||
|
|
@ -1270,26 +1329,28 @@ private:
|
||||||
next.factors.push_back(f);
|
next.factors.push_back(f);
|
||||||
}
|
}
|
||||||
for (std::uint8_t i = 0; i < e; ++i)
|
for (std::uint8_t i = 0; i < e; ++i)
|
||||||
next.factors.push_back(mid);
|
next.factors.push_back(fresh);
|
||||||
rewritten.push_back(std::move(next));
|
rewritten.push_back(std::move(next));
|
||||||
}
|
}
|
||||||
std::vector<std::vector<poly_term>> seq{{std::move(prod)}, std::move(rewritten)};
|
consider({{prod}, rewritten});
|
||||||
std::size_t cost = estimate_pieces(seq);
|
|
||||||
if (cost < best_cost)
|
std::map<std::uint32_t, char> rewritten_seen;
|
||||||
|
for (const auto & term : rewritten)
|
||||||
|
for (auto id : term.factors)
|
||||||
|
rewritten_seen[id] = 1;
|
||||||
|
const auto later = fresh + 1;
|
||||||
|
for (auto [wire_id, present] : rewritten_seen)
|
||||||
{
|
{
|
||||||
best = std::move(seq);
|
(void)present;
|
||||||
best_cost = cost;
|
if (!wire_in_every(rewritten, wire_id))
|
||||||
|
continue;
|
||||||
|
poly_term mul;
|
||||||
|
mul.coeff = traits::one();
|
||||||
|
mul.factors = {later, wire_id};
|
||||||
|
consider({{prod}, drop_one(rewritten, wire_id), {std::move(mul)}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (best.size() != 1 && terms.size() == 1 && terms[0].factors.size() <= 4)
|
|
||||||
{
|
|
||||||
std::fprintf(stderr, "split factors=%zu pieces=%zu cost=%zu flat_factors=",
|
|
||||||
terms[0].factors.size(), best.size(), best_cost);
|
|
||||||
for (auto f : terms[0].factors)
|
|
||||||
std::fprintf(stderr, "%u ", f);
|
|
||||||
std::fprintf(stderr, "\n");
|
|
||||||
}
|
|
||||||
wire last{};
|
wire last{};
|
||||||
for (auto & piece : best)
|
for (auto & piece : best)
|
||||||
last = emit_terms(std::move(piece));
|
last = emit_terms(std::move(piece));
|
||||||
|
|
@ -1352,10 +1413,7 @@ private:
|
||||||
if (b.parts[0].lam == key)
|
if (b.parts[0].lam == key)
|
||||||
return b.share;
|
return b.share;
|
||||||
}
|
}
|
||||||
throw std::logic_error(
|
throw std::logic_error("beaver monomial was not prepared");
|
||||||
"beaver monomial was not prepared (key " + std::to_string(key.size())
|
|
||||||
+ " bundles " + std::to_string(bundles_.size())
|
|
||||||
+ " gates " + std::to_string(gates_.size()) + ")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned exponent_of(const exp_list & key, std::uint32_t id) const
|
unsigned exponent_of(const exp_list & key, std::uint32_t id) const
|
||||||
|
|
@ -1512,8 +1570,17 @@ private:
|
||||||
std::map<exp_list, Ring> lams;
|
std::map<exp_list, Ring> lams;
|
||||||
};
|
};
|
||||||
std::map<exp_list, bucket> buckets;
|
std::map<exp_list, bucket> buckets;
|
||||||
|
std::vector<poly_step> steps;
|
||||||
for (const auto & term : terms)
|
for (const auto & term : terms)
|
||||||
{
|
{
|
||||||
|
if (term.factors.size() == 1)
|
||||||
|
{
|
||||||
|
poly_step step;
|
||||||
|
step.scale = term.coeff;
|
||||||
|
step.value_wire = static_cast<int>(term.factors[0]);
|
||||||
|
steps.push_back(std::move(step));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
auto groups = group_exponents(term.factors);
|
auto groups = group_exponents(term.factors);
|
||||||
(void)expansion_size(groups);
|
(void)expansion_size(groups);
|
||||||
for_each_term(groups, [&](const exp_list & key) {
|
for_each_term(groups, [&](const exp_list & key) {
|
||||||
|
|
@ -1544,7 +1611,6 @@ private:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<poly_step> steps;
|
|
||||||
for (auto & [delta, slot] : buckets)
|
for (auto & [delta, slot] : buckets)
|
||||||
{
|
{
|
||||||
if (!(slot.pub == traits::zero()))
|
if (!(slot.pub == traits::zero()))
|
||||||
|
|
@ -1627,6 +1693,13 @@ private:
|
||||||
Ring s1 = traits::zero();
|
Ring s1 = traits::zero();
|
||||||
for (const auto & step : g.steps)
|
for (const auto & step : g.steps)
|
||||||
{
|
{
|
||||||
|
if (step.value_wire >= 0)
|
||||||
|
{
|
||||||
|
const auto & val = wires_[static_cast<std::size_t>(step.value_wire)].value;
|
||||||
|
s0 = traits::add(s0, traits::mul(step.scale, val.p0));
|
||||||
|
s1 = traits::add(s1, traits::mul(step.scale, val.p1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Ring pub = pow_delta(step.delta);
|
Ring pub = pow_delta(step.delta);
|
||||||
if (step.public_only)
|
if (step.public_only)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -371,16 +371,19 @@ struct ds_gen_state
|
||||||
/// When `cmp` is non-null and active for `level`, also opens `value_cw` via
|
/// When `cmp` is non-null and active for `level`, also opens `value_cw` via
|
||||||
/// the protocol (no second PRG expand outside).
|
/// the protocol (no second PRG expand outside).
|
||||||
template <typename InteriorPRG, typename CwProtocol, typename NodeT,
|
template <typename InteriorPRG, typename CwProtocol, typename NodeT,
|
||||||
typename InputT, typename AdviceT>
|
typename InputT, typename MaskT, typename AdviceT>
|
||||||
void ds_advance_level(ds_gen_state<NodeT> & st, InputT x0, InputT x1,
|
void ds_advance_level(ds_gen_state<NodeT> & st, InputT x0, InputT x1,
|
||||||
InputT mask, std::size_t level, CwProtocol & proto, NodeT & cw_out,
|
MaskT mask, std::size_t level, CwProtocol & proto, NodeT & cw_out,
|
||||||
AdviceT & advice_out, uint64_t * value_cw_out = nullptr,
|
AdviceT & advice_out, uint64_t * value_cw_out = nullptr,
|
||||||
ds_cmp_gen_state * cmp = nullptr)
|
ds_cmp_gen_state * cmp = nullptr)
|
||||||
{
|
{
|
||||||
// Integral bridge so bit extraction works for `keyword` / `modint` /
|
// Integral bridge so bit extraction works for `keyword` / `modint` /
|
||||||
// signed / bitstring the same way dealer gen does via `mask & x`.
|
// signed / bitstring the same way dealer gen does via `mask & x`.
|
||||||
|
// `msb_mask` is the unsigned bit pattern; a signed input must not be
|
||||||
|
// required to have that same type.
|
||||||
constexpr auto to_int = utils::to_integral_type<InputT>{};
|
constexpr auto to_int = utils::to_integral_type<InputT>{};
|
||||||
const auto mi = to_int(mask);
|
constexpr auto to_mask = utils::to_integral_type<MaskT>{};
|
||||||
|
const auto mi = to_mask(mask);
|
||||||
const uint8_t bit0 = static_cast<uint8_t>(!!(mi & to_int(x0)));
|
const uint8_t bit0 = static_cast<uint8_t>(!!(mi & to_int(x0)));
|
||||||
const uint8_t bit1 = static_cast<uint8_t>(!!(mi & to_int(x1)));
|
const uint8_t bit1 = static_cast<uint8_t>(!!(mi & to_int(x1)));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,13 @@
|
||||||
/// a public query. It samples a random target, runs geneval there,
|
/// a public query. It samples a random target, runs geneval there,
|
||||||
/// and shifts the query by `target - x`, which is what
|
/// and shifts the query by `target - x`, which is what
|
||||||
/// `offset_x` does after a wildcard key is bound to `x`.
|
/// `offset_x` does after a wildcard key is bound to `x`.
|
||||||
|
///
|
||||||
|
/// `geneval_cmp` is the comparison-channel form. The value-correction
|
||||||
|
/// word is a function of the secret path at every level, so the walk
|
||||||
|
/// stays live for the whole depth and the opened words match a
|
||||||
|
/// Doerner–Shelat comparison key. Prefix shares are
|
||||||
|
/// `eval_point(cmp, ...)` at each endpoint. Piecewise-cubic evaluation
|
||||||
|
/// on top of that is `grotto::geneval_offset_horner`.
|
||||||
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
|
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
|
||||||
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
|
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
|
||||||
/// see [LICENSE.md](@ref license) for details.
|
/// see [LICENSE.md](@ref license) for details.
|
||||||
|
|
@ -37,6 +44,7 @@
|
||||||
|
|
||||||
#include "dpf/aligned_allocator.hpp"
|
#include "dpf/aligned_allocator.hpp"
|
||||||
#include "dpf/doerner_shelat.hpp"
|
#include "dpf/doerner_shelat.hpp"
|
||||||
|
#include "dpf/eval_target.hpp"
|
||||||
#include "dpf/leaf_node.hpp"
|
#include "dpf/leaf_node.hpp"
|
||||||
|
|
||||||
namespace dpf
|
namespace dpf
|
||||||
|
|
@ -633,6 +641,83 @@ auto geneval_sequence(wildcard_input_t, InputT x0, InputT x1,
|
||||||
begin, end, std::move(rng), [] { return dpf::uniform_sample<InputT>(); }, y);
|
begin, end, std::move(rng), [] { return dpf::uniform_sample<InputT>(); }, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opened comparison key material and one prefix share per endpoint.
|
||||||
|
/// `live_levels` is the full depth: a comparison value word depends on the
|
||||||
|
/// secret path at every level, so there is no early dummy-word tail.
|
||||||
|
struct geneval_cmp_result
|
||||||
|
{
|
||||||
|
std::vector<uint64_t> party0;
|
||||||
|
std::vector<uint64_t> party1;
|
||||||
|
std::vector<simde__m128i, aligned_allocator<simde__m128i>> correction_words;
|
||||||
|
std::vector<uint8_t> correction_advice;
|
||||||
|
std::vector<uint64_t> value_cw;
|
||||||
|
uint64_t cw_last = 0;
|
||||||
|
uint64_t addend0 = 0;
|
||||||
|
uint64_t addend1 = 0;
|
||||||
|
uint64_t mask = 0;
|
||||||
|
std::size_t live_levels = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Doerner–Shelat comparison geneval. `x0 XOR x1` is the secret point, in the
|
||||||
|
/// same share convention as `geneval_point`. `spec` is an `lt` / `leq` / `gt`
|
||||||
|
/// / `geq` pack. Each endpoint is returned in order as the two parties'
|
||||||
|
/// `eval_point(cmp, ...)` shares. An empty range opens nothing.
|
||||||
|
template <typename InputT,
|
||||||
|
typename ForwardIterator,
|
||||||
|
typename RootSampler,
|
||||||
|
typename PadRng,
|
||||||
|
typename Spec>
|
||||||
|
HEDLEY_WARN_UNUSED_RESULT
|
||||||
|
geneval_cmp_result geneval_cmp(InputT x0, InputT x1,
|
||||||
|
ForwardIterator begin, ForwardIterator end,
|
||||||
|
ds_randomness<RootSampler, PadRng> rng, Spec spec)
|
||||||
|
{
|
||||||
|
geneval_cmp_result out;
|
||||||
|
if (begin == end)
|
||||||
|
return out;
|
||||||
|
|
||||||
|
auto keys = make_dpf_doerner_shelat(std::move(x0), std::move(x1),
|
||||||
|
std::move(rng), std::move(spec));
|
||||||
|
const auto & k0 = keys.first;
|
||||||
|
const auto & k1 = keys.second;
|
||||||
|
using key_type = std::decay_t<decltype(k0)>;
|
||||||
|
constexpr std::size_t depth = key_type::depth;
|
||||||
|
out.live_levels = depth;
|
||||||
|
out.mask = k0.cmp().mask;
|
||||||
|
out.cw_last = k0.cw_last();
|
||||||
|
out.addend0 = k0.cmp_addend().raw();
|
||||||
|
out.addend1 = k1.cmp_addend().raw();
|
||||||
|
out.correction_words.resize(depth);
|
||||||
|
out.correction_advice.resize(depth);
|
||||||
|
out.value_cw.resize(depth);
|
||||||
|
for (std::size_t level = 0; level < depth; ++level)
|
||||||
|
{
|
||||||
|
out.correction_words[level] = k0.correction_word(level);
|
||||||
|
out.correction_advice[level] = static_cast<uint8_t>(k0.correction_advice(level));
|
||||||
|
out.value_cw[level] = k0.value_cw(level);
|
||||||
|
}
|
||||||
|
for (auto it = begin; it != end; ++it)
|
||||||
|
{
|
||||||
|
out.party0.push_back(eval_point(dpf::cmp, k0, *it).raw());
|
||||||
|
out.party1.push_back(eval_point(dpf::cmp, k1, *it).raw());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `gt(beta)` comparison geneval. `if_false` is 0.
|
||||||
|
template <typename InputT,
|
||||||
|
typename ForwardIterator,
|
||||||
|
typename RootSampler,
|
||||||
|
typename PadRng>
|
||||||
|
HEDLEY_WARN_UNUSED_RESULT
|
||||||
|
geneval_cmp_result geneval_cmp(InputT x0, InputT x1,
|
||||||
|
ForwardIterator begin, ForwardIterator end,
|
||||||
|
ds_randomness<RootSampler, PadRng> rng, uint64_t beta)
|
||||||
|
{
|
||||||
|
return geneval_cmp(std::move(x0), std::move(x1), begin, end,
|
||||||
|
std::move(rng), dpf::gt(beta));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dpf
|
} // namespace dpf
|
||||||
|
|
||||||
#endif // LIBDPF_INCLUDE_DPF_GENEVAL_HPP__
|
#endif // LIBDPF_INCLUDE_DPF_GENEVAL_HPP__
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,19 @@
|
||||||
/// @brief Noninteractive cubic evaluation after the public offset is opened.
|
/// @brief Noninteractive cubic evaluation after the public offset is opened.
|
||||||
/// @details The dealer keys one comparison at `center` per power
|
/// @details The dealer keys one comparison at `center` per power
|
||||||
/// `1, center, center^2, center^3` in Z/2^64. After the parties open
|
/// `1, center, center^2, center^3` in Z/2^64. After the parties open
|
||||||
/// `eta`, each party shifts the knots by `eta` and sorts them. The
|
/// `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
|
/// sign-respecting segment walk then returns additive shares of
|
||||||
/// `center^m` on the piece that contains the wrapped sum
|
/// `center^m` on the refined piece that contains `center`. On each
|
||||||
/// `center + eta`, and shares of 0 on the other pieces. A public
|
/// refined piece the wrapped input is `center + kappa` for a public
|
||||||
/// binomial combination of those shares is a share of the coefficients
|
/// `kappa`: `eta` on the side that does not overflow, and
|
||||||
/// of that piece as a polynomial in `eta`. Horner at the public `eta`
|
/// `eta ∓ 2^n` on the side that does. A public binomial shift by that
|
||||||
/// needs no further round.
|
/// `kappa`, dotted with the segment shares, is a share of the
|
||||||
|
/// polynomial at the wrapped group element. No further round.
|
||||||
///
|
///
|
||||||
/// The opened value is that polynomial at `lift(center) + lift(eta)`
|
/// Domains of 63 bits or more are already the ring Z/2^64, so the
|
||||||
/// in Z/2^64. `lift` sign-extends a signed domain element and
|
/// carry adjustment is the identity there. `lift` sign-extends a
|
||||||
/// zero-extends an unsigned one. This equals the polynomial at the
|
/// signed domain element and zero-extends an unsigned one.
|
||||||
/// wrapped group element only when the domain addition does not
|
|
||||||
/// overflow. Piece selection still follows the wrapped element.
|
|
||||||
///
|
///
|
||||||
/// `offset_horner_at_x_plus_r` is the wiring from the reconstruction
|
/// `offset_horner_at_x_plus_r` is the wiring from the reconstruction
|
||||||
/// the parties already do: `eta = x - r` and `center = 2r`.
|
/// the parties already do: `eta = x - r` and `center = 2r`.
|
||||||
|
|
@ -26,6 +26,8 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
@ -159,18 +161,13 @@ std::vector<shifted_piece<Degree, InputT>> shift_and_sort(
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Key, typename InputT>
|
inline std::vector<uint64_t> segments_from_prefixes(
|
||||||
std::vector<uint64_t> segments_of(const Key & key, const std::vector<InputT> & knots,
|
const std::vector<uint64_t> & prefix, uint64_t wrap_share, uint64_t mask)
|
||||||
uint64_t wrap_share)
|
|
||||||
{
|
{
|
||||||
using namespace dpf::detail::dcf_impl;
|
using namespace dpf::detail::dcf_impl;
|
||||||
const std::size_t n = knots.size();
|
const std::size_t n = prefix.size();
|
||||||
const uint64_t mask = key.cmp().mask;
|
|
||||||
if (n == 1)
|
if (n == 1)
|
||||||
return std::vector<uint64_t>{wrap_share & mask};
|
return std::vector<uint64_t>{wrap_share & mask};
|
||||||
|
|
||||||
std::vector<uint64_t> prefix(n);
|
|
||||||
signed_prefix_parities_into(key, knots.data(), n, prefix.data());
|
|
||||||
std::vector<uint64_t> seg(n);
|
std::vector<uint64_t> seg(n);
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
for (std::size_t i = 0; i < n; ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -181,23 +178,87 @@ std::vector<uint64_t> segments_of(const Key & key, const std::vector<InputT> & k
|
||||||
return seg;
|
return seg;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::size_t Degree>
|
template <typename Key, typename InputT>
|
||||||
void accumulate(std::array<uint64_t, Degree + 1> & out,
|
std::vector<uint64_t> segments_of(const Key & key, const std::vector<InputT> & knots,
|
||||||
const std::array<std::vector<uint64_t>, Degree + 1> & seg,
|
uint64_t wrap_share)
|
||||||
const std::vector<std::array<uint64_t, Degree + 1>> & coeff)
|
|
||||||
{
|
{
|
||||||
const std::size_t n = coeff.size();
|
const std::size_t n = knots.size();
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
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>
|
||||||
|
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>
|
||||||
|
T domain_min() noexcept
|
||||||
|
{
|
||||||
|
if constexpr (std::is_signed_v<T>)
|
||||||
|
return std::numeric_limits<T>::min();
|
||||||
|
else
|
||||||
|
return T{0};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public center-space cut where `center + eta` crosses the domain end.
|
||||||
|
/// Empty when that cut is outside the domain, including `eta == 0`.
|
||||||
|
template <typename T>
|
||||||
|
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>)
|
||||||
{
|
{
|
||||||
for (std::size_t m = 0; m <= Degree; ++m)
|
if (ez > 0)
|
||||||
{
|
return static_cast<T>(half - ez);
|
||||||
for (std::size_t k = 0; k <= m; ++k)
|
if (ez < 0)
|
||||||
{
|
return static_cast<T>(-half - ez);
|
||||||
// seg[m-k] opens to center^{m-k} on this piece.
|
return std::nullopt;
|
||||||
const uint64_t weight = seg[m - k][i];
|
|
||||||
out[k] += weight * coeff[i][m] * binom[m][k];
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ez == 0)
|
||||||
|
return std::nullopt;
|
||||||
|
return static_cast<T>(mod - ez);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `center + kappa` is the wrapped representative, as a mathematical integer.
|
||||||
|
template <typename T>
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,6 +293,85 @@ std::array<uint64_t, Degree + 1> binomial_coefficients(
|
||||||
return c;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `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`.
|
||||||
|
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
|
} // namespace offset_horner_detail
|
||||||
|
|
||||||
/// Both parties' comparison keys and wrap-piece shares for one center.
|
/// Both parties' comparison keys and wrap-piece shares for one center.
|
||||||
|
|
@ -272,7 +412,9 @@ offset_horner_keys<InputT, Degree> make_offset_horner_keys(InputT center)
|
||||||
return mat;
|
return mat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cleartext coefficients of the selected piece, shifted to `center`, in Z/2^64.
|
/// 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>
|
template <std::size_t Degree, typename InputT>
|
||||||
std::array<uint64_t, Degree + 1> offset_horner_clear_coefficients(
|
std::array<uint64_t, Degree + 1> offset_horner_clear_coefficients(
|
||||||
InputT center,
|
InputT center,
|
||||||
|
|
@ -282,15 +424,18 @@ std::array<uint64_t, Degree + 1> offset_horner_clear_coefficients(
|
||||||
{
|
{
|
||||||
using namespace offset_horner_detail;
|
using namespace offset_horner_detail;
|
||||||
check_knots(knots, coeff.size());
|
check_knots(knots, coeff.size());
|
||||||
const auto rows = shift_and_sort<Degree>(knots, coeff, eta);
|
const auto pieces = prepare_pieces<Degree>(knots, coeff, eta);
|
||||||
std::vector<InputT> shifted(rows.size());
|
std::vector<InputT> cuts;
|
||||||
for (std::size_t i = 0; i < rows.size(); ++i)
|
cuts.reserve(pieces.size());
|
||||||
shifted[i] = rows[i].knot;
|
for (const auto & piece : pieces)
|
||||||
const int hot = piece_index<Degree>(center, shifted);
|
cuts.push_back(piece.knot);
|
||||||
return binomial_coefficients<Degree>(rows[static_cast<std::size_t>(hot)].coeff, lift(center));
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cleartext value: selected piece at `lift(center) + lift(eta)` in Z/2^64.
|
/// Cleartext value of the selected piece at the wrapped `center + eta`.
|
||||||
template <std::size_t Degree, typename InputT>
|
template <std::size_t Degree, typename InputT>
|
||||||
uint64_t offset_horner_clear(
|
uint64_t offset_horner_clear(
|
||||||
InputT center,
|
InputT center,
|
||||||
|
|
@ -299,7 +444,40 @@ uint64_t offset_horner_clear(
|
||||||
InputT eta)
|
InputT eta)
|
||||||
{
|
{
|
||||||
const auto c = offset_horner_clear_coefficients<Degree>(center, knots, coeff, 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(eta));
|
return offset_horner_detail::horner_at<Degree>(c, offset_horner_detail::lift(center));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Party` selects `.first` or `.second` of each key pair.
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One party's coefficient shares. `Party` is 0 or 1.
|
/// One party's coefficient shares. `Party` is 0 or 1.
|
||||||
|
|
@ -310,30 +488,164 @@ std::array<uint64_t, Degree + 1> offset_horner_coefficient_share(
|
||||||
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
|
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
|
||||||
InputT eta)
|
InputT eta)
|
||||||
{
|
{
|
||||||
static_assert(Party < 2, "offset horner party is 0 or 1");
|
std::vector<typename offset_horner_keys<InputT, Degree>::key_pair> keys(
|
||||||
using namespace offset_horner_detail;
|
mat.keys.begin(), mat.keys.end());
|
||||||
|
return offset_horner_coefficient_share<Party, Degree>(
|
||||||
|
keys, mat.wrap_share, knots, coeff, eta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Both parties' Horner shares from one joint Doerner–Shelat generation.
|
||||||
|
/// `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.
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Logical comparison point for geneval's XOR shares. Matches `make_dpf(P)`
|
||||||
|
/// when `center1 = P XOR center0` or when `center0 = P` and `center1 = 0`.
|
||||||
|
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(
|
||||||
|
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());
|
check_knots(knots, coeff.size());
|
||||||
const auto rows = shift_and_sort<Degree>(knots, coeff, eta);
|
const auto pieces = prepare_pieces<Degree>(knots, coeff, eta);
|
||||||
std::vector<InputT> shifted(rows.size());
|
std::vector<InputT> shifted(pieces.size());
|
||||||
std::vector<std::array<uint64_t, Degree + 1>> ordered(rows.size());
|
std::vector<std::array<uint64_t, Degree + 1>> ordered(pieces.size());
|
||||||
for (std::size_t i = 0; i < rows.size(); ++i)
|
std::vector<int64_t> kappa(pieces.size());
|
||||||
|
for (std::size_t i = 0; i < pieces.size(); ++i)
|
||||||
{
|
{
|
||||||
shifted[i] = rows[i].knot;
|
shifted[i] = pieces[i].knot;
|
||||||
ordered[i] = rows[i].coeff;
|
ordered[i] = pieces[i].coeff;
|
||||||
|
kappa[i] = pieces[i].kappa;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<std::vector<uint64_t>, Degree + 1> seg;
|
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)
|
for (std::size_t m = 0; m <= Degree; ++m)
|
||||||
{
|
{
|
||||||
seg[m] = segments_of(std::get<Party>(mat.keys[m]), shifted,
|
const auto opened = dpf::geneval_cmp(center0, center1,
|
||||||
mat.wrap_share[m][Party]);
|
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);
|
||||||
}
|
}
|
||||||
std::array<uint64_t, Degree + 1> out{};
|
|
||||||
accumulate<Degree>(out, seg, ordered);
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One party's share of the cubic at `lift(center) + lift(eta)`.
|
} // namespace offset_horner_detail
|
||||||
|
|
||||||
|
/// Geneval-style offset Horner. The center is XOR-shared as in `geneval_point`.
|
||||||
|
/// 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.
|
||||||
|
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;
|
||||||
|
dpf::ds_randomness<block (*)(), dpf::detail::urandom_pad_rng> rng{
|
||||||
|
dpf::uniform_sample<block>, {}};
|
||||||
|
return geneval_offset_horner<Degree>(
|
||||||
|
center0, center1, eta, knots, coeff, std::move(rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additive shares of the input `x` and the mask `r`. Reconstructs
|
||||||
|
/// `eta = x - r` and `center = 2r`, XOR-shares that center as `(center, 0)`,
|
||||||
|
/// and returns both parties' Horner shares of the cubic at `x + r`
|
||||||
|
/// (the group element `x + 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 center = offset_horner_group_add(r, r);
|
||||||
|
InputT zero{};
|
||||||
|
return offset_horner_detail::geneval_at<Degree>(
|
||||||
|
center, zero, 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;
|
||||||
|
dpf::ds_randomness<block (*)(), dpf::detail::urandom_pad_rng> rng{
|
||||||
|
dpf::uniform_sample<block>, {}};
|
||||||
|
return geneval_offset_horner<Degree>(
|
||||||
|
x0, x1, r0, r1, knots, coeff, std::move(rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One party's share of the cubic at the wrapped `center + eta`.
|
||||||
|
/// Sum the coefficient shares; they are already scaled by `center^k`.
|
||||||
template <std::size_t Party, std::size_t Degree, typename InputT>
|
template <std::size_t Party, std::size_t Degree, typename InputT>
|
||||||
uint64_t offset_horner_eval(
|
uint64_t offset_horner_eval(
|
||||||
const offset_horner_keys<InputT, Degree> & mat,
|
const offset_horner_keys<InputT, Degree> & mat,
|
||||||
|
|
@ -342,7 +654,10 @@ uint64_t offset_horner_eval(
|
||||||
InputT eta)
|
InputT eta)
|
||||||
{
|
{
|
||||||
const auto shares = offset_horner_coefficient_share<Party, Degree>(mat, knots, coeff, eta);
|
const auto shares = offset_horner_coefficient_share<Party, Degree>(mat, knots, coeff, eta);
|
||||||
return offset_horner_detail::horner_at<Degree>(shares, offset_horner_detail::lift(eta));
|
uint64_t value = 0;
|
||||||
|
for (uint64_t term : shares)
|
||||||
|
value += term;
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace grotto
|
} // namespace grotto
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,12 @@
|
||||||
/// Chebfun). A requested precision only rounds those coefficients
|
/// Chebfun). A requested precision only rounds those coefficients
|
||||||
/// down to `k + 16` fractional bits. `coth` is different: its
|
/// down to `k + 16` fractional bits. `coth` is different: its
|
||||||
/// principal function depends on `k` through
|
/// principal function depends on `k` through
|
||||||
/// `beta = ln(2^{k+1}+1)/2`, so each precision has its own uniform
|
/// `beta = ln(2^{k+1}+1)/2`, so each precision has its own
|
||||||
/// partition. The returned raw value is
|
/// partition. `inv` (1/x), `rsqrt` (1/sqrt(x)), and `invsq`
|
||||||
|
/// (1/x^2) are also per precision: each is a longest-feasible
|
||||||
|
/// cubic march on the closed principal interval [1/2, 1], with
|
||||||
|
/// absolute error at most half an ulp at that precision. They do
|
||||||
|
/// not apply an exponent lift. The returned raw value is
|
||||||
/// `round_half_away(p(x) * 2^k)`. On the closed principal domain,
|
/// `round_half_away(p(x) * 2^k)`. On the closed principal domain,
|
||||||
/// `p` stays within one unit in the last place of that precision.
|
/// `p` stays within one unit in the last place of that precision.
|
||||||
|
|
||||||
|
|
@ -33,6 +37,9 @@ enum class principal : unsigned
|
||||||
sec,
|
sec,
|
||||||
gsec,
|
gsec,
|
||||||
csch,
|
csch,
|
||||||
|
inv,
|
||||||
|
rsqrt,
|
||||||
|
invsq,
|
||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr unsigned principal_precisions[] = {8u, 12u, 16u, 20u, 24u, 28u, 32u};
|
inline constexpr unsigned principal_precisions[] = {8u, 12u, 16u, 20u, 24u, 28u, 32u};
|
||||||
|
|
@ -71,6 +78,7 @@ struct table_ref
|
||||||
};
|
};
|
||||||
|
|
||||||
#include "grotto/principal_tables.inc"
|
#include "grotto/principal_tables.inc"
|
||||||
|
#include "grotto/principal_recip_tables.inc"
|
||||||
|
|
||||||
struct w256
|
struct w256
|
||||||
{
|
{
|
||||||
|
|
@ -288,6 +296,14 @@ inline const table_ref & table_for(principal which, unsigned fractional_bits)
|
||||||
const unsigned slot = fractional_bits / 4u - 2u;
|
const unsigned slot = fractional_bits / 4u - 2u;
|
||||||
return *COTH_BY_K[slot];
|
return *COTH_BY_K[slot];
|
||||||
}
|
}
|
||||||
|
if (which == principal::inv || which == principal::rsqrt || which == principal::invsq)
|
||||||
|
{
|
||||||
|
const unsigned slot = fractional_bits / 4u - 2u;
|
||||||
|
const table_ref * const * bank = which == principal::inv
|
||||||
|
? INV_BY_K
|
||||||
|
: which == principal::rsqrt ? RSQRT_BY_K : INVSQ_BY_K;
|
||||||
|
return *bank[slot];
|
||||||
|
}
|
||||||
return *SHARED_TABLE[index];
|
return *SHARED_TABLE[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
1134
include/grotto/principal_recip_tables.inc
Normal file
1134
include/grotto/principal_recip_tables.inc
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -5,7 +5,10 @@
|
||||||
/// cubic. `erfc`, `softminus`, `logsigmoid`, and `acos` are integer
|
/// cubic. `erfc`, `softminus`, `logsigmoid`, and `acos` are integer
|
||||||
/// rewrites of `erf`, `softplus`, and `asin`. `asin` on `(1/2, 1]`
|
/// rewrites of `erf`, `softplus`, and `asin`. `asin` on `(1/2, 1]`
|
||||||
/// uses `π/2 − 2 asin(sqrt((1−x)/2))` with the principal square-root
|
/// uses `π/2 − 2 asin(sqrt((1−x)/2))` with the principal square-root
|
||||||
/// table. `probit` is stored on `(0, 1/2]` and mirrored.
|
/// table. `probit` is stored on `(0, 1/2]` and mirrored. The tail
|
||||||
|
/// below 1/20 is a cubic in `ln(p)` (knots at scale `2^{k+10}`),
|
||||||
|
/// because a cubic in `p` cannot meet half an ulp on the first
|
||||||
|
/// input step once `k` is large.
|
||||||
|
|
||||||
#ifndef LIBDPF_INCLUDE_GROTTO_WINDOW_LUT_HPP__
|
#ifndef LIBDPF_INCLUDE_GROTTO_WINDOW_LUT_HPP__
|
||||||
#define LIBDPF_INCLUDE_GROTTO_WINDOW_LUT_HPP__
|
#define LIBDPF_INCLUDE_GROTTO_WINDOW_LUT_HPP__
|
||||||
|
|
@ -128,38 +131,153 @@ inline std::int64_t round_half_away_i128(__int128 number, unsigned shift)
|
||||||
return static_cast<std::int64_t>(neg ? -out : out);
|
return static_cast<std::int64_t>(neg ? -out : out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `round(sqrt(v / 2^{k+1}) * 2^k)`, `v > 0`.
|
inline unsigned __int128 isqrt_floor(unsigned __int128 n)
|
||||||
inline std::int64_t sqrt_half_scale(unsigned fractional_bits, std::int64_t magnitude)
|
|
||||||
{
|
{
|
||||||
const int log = 63 - __builtin_clzll(static_cast<unsigned long long>(magnitude));
|
if (n == 0)
|
||||||
const std::int64_t mant = magnitude << (fractional_bits - static_cast<unsigned>(log + 1));
|
return 0;
|
||||||
const std::int64_t root = eval_principal(principal::sqrt, fractional_bits, mant);
|
const unsigned bits = (n >> 64) != 0
|
||||||
const int exp2 = log - static_cast<int>(fractional_bits);
|
? 128u - static_cast<unsigned>(__builtin_clzll(static_cast<unsigned long long>(n >> 64)))
|
||||||
if ((exp2 & 1) == 0)
|
: 64u - static_cast<unsigned>(__builtin_clzll(static_cast<unsigned long long>(n)));
|
||||||
return round_half_away_i128(root, static_cast<unsigned>(-exp2) / 2u);
|
unsigned __int128 x = static_cast<unsigned __int128>(1) << ((bits + 1u) / 2u);
|
||||||
const unsigned t = static_cast<unsigned>(-exp2 - 1) / 2u;
|
for (;;)
|
||||||
// sqrt(2) rounded onto 62 fractional bits.
|
{
|
||||||
constexpr __int128 sqrt2_62 = 6521908912666391106LL;
|
const unsigned __int128 y = (x + n / x) >> 1;
|
||||||
return round_half_away_i128(__int128(root) * sqrt2_62, 62u + t + 1u);
|
if (y >= x)
|
||||||
|
break;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
while (x > 0 && x > n / x)
|
||||||
|
--x;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `round(sqrt(v / 2^{k+1}) * 2^{k+extra})`, `v > 0`. Eight extra bits so the
|
||||||
|
/// half-angle identity can absorb the square root before the final rounding.
|
||||||
|
inline std::int64_t sqrt_half_scale_fine(unsigned fractional_bits, std::int64_t magnitude)
|
||||||
|
{
|
||||||
|
constexpr unsigned extra = 8;
|
||||||
|
const unsigned shift = fractional_bits + 2u * extra;
|
||||||
|
const unsigned __int128 radicand =
|
||||||
|
static_cast<unsigned __int128>(static_cast<std::uint64_t>(magnitude)) << shift;
|
||||||
|
const unsigned __int128 root = isqrt_floor(radicand);
|
||||||
|
// sqrt(gap << (k+2*extra)) / sqrt(2) = sqrt(gap / 2^{k+1}) * 2^{k+extra}
|
||||||
|
static constexpr unsigned __int128 sqrt2_64 =
|
||||||
|
(static_cast<unsigned __int128>(1) << 64) | static_cast<unsigned __int128>(7640891576956012809ULL);
|
||||||
|
const unsigned __int128 scaled = (root * sqrt2_64 + (static_cast<unsigned __int128>(1) << 64)) >> 65;
|
||||||
|
return static_cast<std::int64_t>(scaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int piece_of_scaled(const window_table & table, std::int64_t raw, unsigned extra)
|
||||||
|
{
|
||||||
|
int lo = 0;
|
||||||
|
int hi = static_cast<int>(table.nparts);
|
||||||
|
while (hi - lo > 1)
|
||||||
|
{
|
||||||
|
const int mid = (lo + hi) / 2;
|
||||||
|
if ((table.knots[mid] << extra) <= raw)
|
||||||
|
lo = mid;
|
||||||
|
else
|
||||||
|
hi = mid;
|
||||||
|
}
|
||||||
|
return lo;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::int64_t eval_asin_abs(unsigned fractional_bits, std::int64_t magnitude)
|
inline std::int64_t eval_asin_abs(unsigned fractional_bits, std::int64_t magnitude)
|
||||||
{
|
{
|
||||||
|
constexpr unsigned extra = 8;
|
||||||
const auto half = std::int64_t{1} << (fractional_bits - 1);
|
const auto half = std::int64_t{1} << (fractional_bits - 1);
|
||||||
const window_table & table = at(ASIN, fractional_bits);
|
const window_table & table = at(ASIN, fractional_bits);
|
||||||
if (magnitude <= half)
|
if (magnitude <= half)
|
||||||
return eval_table(table, fractional_bits, magnitude);
|
return eval_table(table, fractional_bits, magnitude);
|
||||||
const std::int64_t one = std::int64_t{1} << fractional_bits;
|
const std::int64_t one = std::int64_t{1} << fractional_bits;
|
||||||
|
if (magnitude >= one)
|
||||||
|
return HALF_PI_RAW[slot_of(fractional_bits)];
|
||||||
const std::int64_t gap = one - magnitude;
|
const std::int64_t gap = one - magnitude;
|
||||||
const auto pi = HALF_PI_RAW[slot_of(fractional_bits)];
|
std::int64_t reduced = sqrt_half_scale_fine(fractional_bits, gap);
|
||||||
if (gap <= 0)
|
const std::int64_t half_fine = half << extra;
|
||||||
return pi;
|
if (reduced > half_fine)
|
||||||
std::int64_t reduced = sqrt_half_scale(fractional_bits, gap);
|
reduced = half_fine;
|
||||||
if (reduced > half)
|
const unsigned scale = fractional_bits + extra;
|
||||||
reduced = half;
|
const std::int64_t inner = horner(
|
||||||
const std::int64_t inner = eval_table(table, fractional_bits, reduced);
|
table.pieces[piece_of_scaled(table, reduced, extra)], table.q, reduced, scale);
|
||||||
const std::int64_t lifted = pi - 2 * inner;
|
// pi/2 at 64 fractional bits, then onto scale k+extra in one rounding.
|
||||||
return lifted < 0 ? 0 : lifted;
|
static constexpr unsigned __int128 half_pi_64 =
|
||||||
|
(static_cast<unsigned __int128>(1) << 64) | static_cast<unsigned __int128>(10529333758598939754ULL);
|
||||||
|
const __int128 pi_fine = round_half_away_i128(
|
||||||
|
static_cast<__int128>(half_pi_64), 64u - scale);
|
||||||
|
const __int128 lifted = pi_fine - 2 * static_cast<__int128>(inner);
|
||||||
|
const auto out = round_half_away_i128(lifted, extra);
|
||||||
|
return out < 0 ? 0 : out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Surplus fractional bits on probit-tail knots. `u = ln(p)` is stored as
|
||||||
|
/// `round(u * 2^{k+probit_tail_extra})`.
|
||||||
|
inline constexpr unsigned probit_tail_extra = 10;
|
||||||
|
|
||||||
|
// ln((32+i)/64) * 2^64, stored as a positive magnitude. Every anchor is in (0, ln 2].
|
||||||
|
static constexpr std::uint64_t probit_ln2_64 = 12786308645202655660ull;
|
||||||
|
static constexpr std::uint64_t probit_ln_anchor_mag[32] = {
|
||||||
|
12786308645202655660ull, 12218671733053503897ull, 11667981761989453435ull, 11133256087961349648ull,
|
||||||
|
10613595130224743362ull, 10108173265494422292ull, 9616230936675340827ull, 9137067786804269247ull,
|
||||||
|
8670036662410753619ull, 8214538357444912273ull, 7770016990662967709ull, 7335955927010031419ull,
|
||||||
|
6911874167941132216ull, 6497323147432841322ull, 6091883880171659064ull, 5695164416463867605ull,
|
||||||
|
5306797565112371681ull, 4926438851101192057ull, 4553764679618851579ull, 4188470681899169456ull,
|
||||||
|
3830270221691897566ull, 3478893044001375095ull, 3134084050134459383ull, 2795602185149230175ull,
|
||||||
|
2463219425550596028ull, 2136719856585056848ull, 1815898829783402670ull, 1500562192519310430ull,
|
||||||
|
1190525582320469641ull, 885613779509420443ull, 585660112482476600ull, 290505910572683730ull,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `round_half_away(ln(probability / 2^k) * 2^{k+10})`.
|
||||||
|
inline std::int64_t probit_ln_argument(unsigned fractional_bits, std::int64_t probability)
|
||||||
|
{
|
||||||
|
const auto bits = static_cast<unsigned long long>(probability);
|
||||||
|
const int e = 63 - __builtin_clzll(bits);
|
||||||
|
const unsigned shift_in = static_cast<unsigned>(e + 1);
|
||||||
|
const auto wide = static_cast<unsigned __int128>(bits) << (64u - shift_in);
|
||||||
|
const auto m64 = static_cast<std::uint64_t>(wide);
|
||||||
|
const unsigned idx = static_cast<unsigned>((m64 - (1ull << 63)) >> 58);
|
||||||
|
const unsigned b_num = 32u + idx;
|
||||||
|
const unsigned __int128 t_scaled = (static_cast<unsigned __int128>(m64) * 64u) / b_num;
|
||||||
|
__int128 t = static_cast<__int128>(t_scaled - (static_cast<unsigned __int128>(1) << 64));
|
||||||
|
__int128 p = t;
|
||||||
|
__int128 acc = 0;
|
||||||
|
for (int n = 1; n <= 14; ++n)
|
||||||
|
{
|
||||||
|
const __int128 term = p / n;
|
||||||
|
acc += (n & 1) ? term : -term;
|
||||||
|
p = (p * t) >> 64;
|
||||||
|
}
|
||||||
|
const __int128 ln_m = -static_cast<__int128>(probit_ln_anchor_mag[idx]) + acc;
|
||||||
|
const int exp_fix = e + 1 - static_cast<int>(fractional_bits);
|
||||||
|
const __int128 ln_x = ln_m + static_cast<__int128>(exp_fix) * static_cast<__int128>(probit_ln2_64);
|
||||||
|
return round_half_away_i128(ln_x, 54u - fractional_bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Horner, then one extra right shift so a tail argument at scale `k+10` rounds onto scale `k`.
|
||||||
|
inline std::int64_t eval_cubic_extra(
|
||||||
|
const cubic_bits & piece, unsigned q, std::int64_t raw,
|
||||||
|
unsigned fractional_bits, unsigned extra_shift)
|
||||||
|
{
|
||||||
|
using namespace principal_detail;
|
||||||
|
__int128 coeff[4];
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
coeff[i] = unpack_coeff(piece.hi[i], piece.lo[i]);
|
||||||
|
const int q_use = static_cast<int>(q) < static_cast<int>(fractional_bits) + 16
|
||||||
|
? static_cast<int>(q)
|
||||||
|
: static_cast<int>(fractional_bits) + 16;
|
||||||
|
const int drop = static_cast<int>(q) - q_use;
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
coeff[i] = rshift_ties_even(coeff[i], drop);
|
||||||
|
|
||||||
|
w256 acc = w_from_i128(coeff[3]);
|
||||||
|
for (int i = 2; i >= 0; --i)
|
||||||
|
{
|
||||||
|
acc = w_mul_i64(acc, raw);
|
||||||
|
w256 term = w_shl(w_from_i128(coeff[i]), fractional_bits * static_cast<unsigned>(3 - i));
|
||||||
|
acc = w_add(acc, term);
|
||||||
|
}
|
||||||
|
const unsigned denom_shift = static_cast<unsigned>(q_use) + 2u * fractional_bits + extra_shift;
|
||||||
|
return round_half_away_pow2(acc, denom_shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::int64_t eval_probit_abs(unsigned fractional_bits, std::int64_t probability)
|
inline std::int64_t eval_probit_abs(unsigned fractional_bits, std::int64_t probability)
|
||||||
|
|
@ -167,7 +285,14 @@ inline std::int64_t eval_probit_abs(unsigned fractional_bits, std::int64_t proba
|
||||||
const window_table & mid = at(PROBIT_MID, fractional_bits);
|
const window_table & mid = at(PROBIT_MID, fractional_bits);
|
||||||
if (probability >= mid.knots[0])
|
if (probability >= mid.knots[0])
|
||||||
return eval_table(mid, fractional_bits, probability);
|
return eval_table(mid, fractional_bits, probability);
|
||||||
return eval_table(at(PROBIT_TAIL, fractional_bits), fractional_bits, probability);
|
const window_table & tail = at(PROBIT_TAIL, fractional_bits);
|
||||||
|
std::int64_t u = probit_ln_argument(fractional_bits, probability);
|
||||||
|
if (u < tail.knots[0])
|
||||||
|
u = tail.knots[0];
|
||||||
|
if (u > tail.knots[tail.nparts])
|
||||||
|
u = tail.knots[tail.nparts];
|
||||||
|
const unsigned scale = fractional_bits + probit_tail_extra;
|
||||||
|
return eval_cubic_extra(tail.pieces[piece_of(tail, u)], tail.q, u, scale, probit_tail_extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::int64_t eval_smoothstep(unsigned fractional_bits, std::int64_t raw)
|
inline std::int64_t eval_smoothstep(unsigned fractional_bits, std::int64_t raw)
|
||||||
|
|
|
||||||
36725
include/grotto/window_tables.inc
Normal file
36725
include/grotto/window_tables.inc
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "dpf/beaver.hpp"
|
#include "dpf/beaver.hpp"
|
||||||
|
|
@ -46,6 +47,50 @@ struct XorSeq
|
||||||
};
|
};
|
||||||
|
|
||||||
using u64 = std::uint64_t;
|
using u64 = std::uint64_t;
|
||||||
|
using session64 = dpf::beavers::session<u64>;
|
||||||
|
using wire64 = session64::wire;
|
||||||
|
|
||||||
|
struct PolyRun
|
||||||
|
{
|
||||||
|
u64 value = 0;
|
||||||
|
int rounds = 0;
|
||||||
|
std::size_t prep = 0;
|
||||||
|
std::size_t monos = 0;
|
||||||
|
std::size_t wires = 0;
|
||||||
|
int draws = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Formula>
|
||||||
|
PolyRun run_poly(std::size_t ninputs, Formula formula, const std::vector<u64> & secrets)
|
||||||
|
{
|
||||||
|
session64 s;
|
||||||
|
std::vector<wire64> in;
|
||||||
|
in.reserve(ninputs);
|
||||||
|
for (std::size_t i = 0; i < ninputs; ++i)
|
||||||
|
in.push_back(s.input());
|
||||||
|
auto y = formula(s, in);
|
||||||
|
PolyRun out;
|
||||||
|
out.rounds = s.round_of(y);
|
||||||
|
out.prep = s.preprocessing_count();
|
||||||
|
out.monos = s.monomial_count();
|
||||||
|
out.wires = s.wire_count();
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
out.draws = rng.draws;
|
||||||
|
for (std::size_t i = 0; i < ninputs; ++i)
|
||||||
|
s.bind(in[i], secrets[i], rng);
|
||||||
|
s.evaluate();
|
||||||
|
out.value = s.open(y);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 mpow(u64 base, unsigned exp)
|
||||||
|
{
|
||||||
|
u64 acc = 1;
|
||||||
|
for (unsigned i = 0; i < exp; ++i)
|
||||||
|
acc *= base;
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
@ -488,9 +533,18 @@ TEST(Beaver, GrottoAppendixEPreprocessing)
|
||||||
auto a0 = linear.input();
|
auto a0 = linear.input();
|
||||||
auto a1 = linear.input();
|
auto a1 = linear.input();
|
||||||
auto lin = linear(sgn * (a1 * x + a0));
|
auto lin = linear(sgn * (a1 * x + a0));
|
||||||
(void)lin;
|
// Two-round column of Table 3: the sign is a later multiply, and the
|
||||||
// Four masks plus four fused products (ePrint 2023/108, Table 3).
|
// constant coefficient is added from its value share.
|
||||||
EXPECT_EQ(linear.preprocessing_count(), 8u);
|
EXPECT_EQ(linear.round_of(lin), 2);
|
||||||
|
EXPECT_EQ(linear.preprocessing_count(), 6u);
|
||||||
|
Counter lin_rng;
|
||||||
|
linear.sample(lin_rng);
|
||||||
|
linear.bind(x, u64{2}, lin_rng);
|
||||||
|
linear.bind(sgn, u64{3}, lin_rng);
|
||||||
|
linear.bind(a0, u64{4}, lin_rng);
|
||||||
|
linear.bind(a1, u64{5}, lin_rng);
|
||||||
|
linear.evaluate();
|
||||||
|
EXPECT_EQ(linear.open(lin), 3u * (5u * 2u + 4u));
|
||||||
|
|
||||||
dpf::beavers::session<u64> quad;
|
dpf::beavers::session<u64> quad;
|
||||||
auto x2 = quad.input();
|
auto x2 = quad.input();
|
||||||
|
|
@ -499,8 +553,17 @@ TEST(Beaver, GrottoAppendixEPreprocessing)
|
||||||
auto b1 = quad.input();
|
auto b1 = quad.input();
|
||||||
auto b2 = quad.input();
|
auto b2 = quad.input();
|
||||||
auto q = quad(s2 * (b2 * pow(x2, 2) + b1 * x2 + b0));
|
auto q = quad(s2 * (b2 * pow(x2, 2) + b1 * x2 + b0));
|
||||||
(void)q;
|
EXPECT_EQ(quad.round_of(q), 2);
|
||||||
EXPECT_EQ(quad.preprocessing_count(), 13u);
|
EXPECT_EQ(quad.preprocessing_count(), 9u);
|
||||||
|
Counter quad_rng;
|
||||||
|
quad.sample(quad_rng);
|
||||||
|
quad.bind(x2, u64{2}, quad_rng);
|
||||||
|
quad.bind(s2, u64{3}, quad_rng);
|
||||||
|
quad.bind(b0, u64{4}, quad_rng);
|
||||||
|
quad.bind(b1, u64{5}, quad_rng);
|
||||||
|
quad.bind(b2, u64{6}, quad_rng);
|
||||||
|
quad.evaluate();
|
||||||
|
EXPECT_EQ(quad.open(q), 3u * (6u * 4u + 5u * 2u + 4u));
|
||||||
|
|
||||||
dpf::beavers::session<u64> cube;
|
dpf::beavers::session<u64> cube;
|
||||||
auto x3 = cube.input();
|
auto x3 = cube.input();
|
||||||
|
|
@ -510,7 +573,10 @@ TEST(Beaver, GrottoAppendixEPreprocessing)
|
||||||
auto c2 = cube.input();
|
auto c2 = cube.input();
|
||||||
auto c3 = cube.input();
|
auto c3 = cube.input();
|
||||||
auto y = cube(s3 * (c3 * pow(x3, 3) + c2 * pow(x3, 2) + c1 * x3 + c0));
|
auto y = cube(s3 * (c3 * pow(x3, 3) + c2 * pow(x3, 2) + c1 * x3 + c0));
|
||||||
EXPECT_EQ(cube.preprocessing_count(), 18u);
|
EXPECT_EQ(cube.round_of(y), 2);
|
||||||
|
// Table 3 lists 13. The constant coefficient is a value share, so its
|
||||||
|
// mask is not part of the preprocessing.
|
||||||
|
EXPECT_EQ(cube.preprocessing_count(), 12u);
|
||||||
|
|
||||||
Counter rng;
|
Counter rng;
|
||||||
cube.sample(rng);
|
cube.sample(rng);
|
||||||
|
|
@ -537,9 +603,10 @@ TEST(Beaver, MultivariatePolynomialsSharePowers)
|
||||||
// Fused buckets: fewer shares than one subset product per monomial.
|
// Fused buckets: fewer shares than one subset product per monomial.
|
||||||
EXPECT_EQ(s.monomial_count(), 5u);
|
EXPECT_EQ(s.monomial_count(), 5u);
|
||||||
auto q = s(sgn * (x * y + pow(x, 2)));
|
auto q = s(sgn * (x * y + pow(x, 2)));
|
||||||
EXPECT_EQ(s.round_of(q), 1);
|
EXPECT_EQ(s.round_of(q), 2);
|
||||||
// Sign crosses are new; λx² and λx λy are not sampled again.
|
// The sign is a later multiply, so the new shares are that product
|
||||||
EXPECT_EQ(s.monomial_count(), 10u);
|
// rather than a second one-round crossing of every power.
|
||||||
|
EXPECT_EQ(s.monomial_count(), 8u);
|
||||||
Counter rng;
|
Counter rng;
|
||||||
s.sample(rng);
|
s.sample(rng);
|
||||||
s.bind(x, u64{2}, rng);
|
s.bind(x, u64{2}, rng);
|
||||||
|
|
@ -552,6 +619,84 @@ TEST(Beaver, MultivariatePolynomialsSharePowers)
|
||||||
EXPECT_EQ(s.open(q), 5u * (2u * 3u + 4u));
|
EXPECT_EQ(s.open(q), 5u * (2u * 3u + 4u));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ElementaryLogFactorsMatchingPowers)
|
||||||
|
{
|
||||||
|
dpf::beavers::session<u64> s;
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto a0 = s.input();
|
||||||
|
auto a1 = s.input();
|
||||||
|
auto a2 = s.input();
|
||||||
|
auto a3 = s.input();
|
||||||
|
auto y = s(a3 * pow(x, 3) * pow(z, 3) + a2 * pow(x, 2) * pow(z, 2)
|
||||||
|
+ a1 * x * z + a0);
|
||||||
|
EXPECT_EQ(s.round_of(y), 2);
|
||||||
|
// m = x*z, then a cubic in m. Same preprocessing as the two-round cubic.
|
||||||
|
EXPECT_EQ(s.preprocessing_count(), 12u);
|
||||||
|
EXPECT_EQ(s.monomial_count(), 6u);
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
s.bind(x, u64{2}, rng);
|
||||||
|
s.bind(z, u64{3}, rng);
|
||||||
|
s.bind(a0, u64{1}, rng);
|
||||||
|
s.bind(a1, u64{1}, rng);
|
||||||
|
s.bind(a2, u64{1}, rng);
|
||||||
|
s.bind(a3, u64{1}, rng);
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(y), 259u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ElementaryExpScalesAfterTheCubic)
|
||||||
|
{
|
||||||
|
dpf::beavers::session<u64> s;
|
||||||
|
auto r = s.input();
|
||||||
|
auto c = s.input();
|
||||||
|
auto a0 = s.input();
|
||||||
|
auto a1 = s.input();
|
||||||
|
auto a2 = s.input();
|
||||||
|
auto a3 = s.input();
|
||||||
|
auto y = s(c * (a3 * pow(r, 3) + a2 * pow(r, 2) + a1 * r + a0));
|
||||||
|
EXPECT_EQ(s.round_of(y), 2);
|
||||||
|
EXPECT_EQ(s.preprocessing_count(), 12u);
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
s.bind(r, u64{2}, rng);
|
||||||
|
s.bind(c, u64{3}, rng);
|
||||||
|
s.bind(a0, u64{4}, rng);
|
||||||
|
s.bind(a1, u64{5}, rng);
|
||||||
|
s.bind(a2, u64{6}, rng);
|
||||||
|
s.bind(a3, u64{7}, rng);
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(y), 282u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ElementaryScaleAfterFactoredPower)
|
||||||
|
{
|
||||||
|
dpf::beavers::session<u64> s;
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto sgn = s.input();
|
||||||
|
auto a0 = s.input();
|
||||||
|
auto a1 = s.input();
|
||||||
|
auto a2 = s.input();
|
||||||
|
auto a3 = s.input();
|
||||||
|
auto y = s(sgn * (a3 * pow(x, 3) * pow(z, 3) + a2 * pow(x, 2) * pow(z, 2)
|
||||||
|
+ a1 * x * z + a0));
|
||||||
|
EXPECT_EQ(s.round_of(y), 3);
|
||||||
|
EXPECT_EQ(s.preprocessing_count(), 15u);
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
s.bind(x, u64{2}, rng);
|
||||||
|
s.bind(z, u64{3}, rng);
|
||||||
|
s.bind(sgn, u64{3}, rng);
|
||||||
|
s.bind(a0, u64{1}, rng);
|
||||||
|
s.bind(a1, u64{1}, rng);
|
||||||
|
s.bind(a2, u64{1}, rng);
|
||||||
|
s.bind(a3, u64{1}, rng);
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(y), 3u * 259u);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Beaver, PolynomialsSharePowers)
|
TEST(Beaver, PolynomialsSharePowers)
|
||||||
{
|
{
|
||||||
dpf::beavers::session<u64> s;
|
dpf::beavers::session<u64> s;
|
||||||
|
|
@ -607,6 +752,789 @@ TEST(Beaver, LikeTermsCollapse)
|
||||||
EXPECT_EQ(s.open(q), 8u * 16u);
|
EXPECT_EQ(s.open(q), 8u * 16u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ElementaryLogAgreesWithAHandSplit)
|
||||||
|
{
|
||||||
|
const u64 x = 4, z = 5, a0 = 6, a1 = 7, a2 = 8, a3 = 9;
|
||||||
|
const u64 m = x * z;
|
||||||
|
const u64 expect = a0 + a1 * m + a2 * m * m + a3 * m * m * m;
|
||||||
|
|
||||||
|
session64 automatic;
|
||||||
|
auto ax = automatic.input();
|
||||||
|
auto az = automatic.input();
|
||||||
|
auto aa0 = automatic.input();
|
||||||
|
auto aa1 = automatic.input();
|
||||||
|
auto aa2 = automatic.input();
|
||||||
|
auto aa3 = automatic.input();
|
||||||
|
auto ay = automatic(aa3 * pow(ax, 3) * pow(az, 3) + aa2 * pow(ax, 2) * pow(az, 2)
|
||||||
|
+ aa1 * ax * az + aa0);
|
||||||
|
|
||||||
|
session64 manual;
|
||||||
|
auto mx = manual.input();
|
||||||
|
auto mz = manual.input();
|
||||||
|
auto ma0 = manual.input();
|
||||||
|
auto ma1 = manual.input();
|
||||||
|
auto ma2 = manual.input();
|
||||||
|
auto ma3 = manual.input();
|
||||||
|
auto mm = manual(mx * mz);
|
||||||
|
auto my = manual(ma3 * pow(mm, 3) + ma2 * pow(mm, 2) + ma1 * mm + ma0);
|
||||||
|
|
||||||
|
EXPECT_EQ(automatic.round_of(ay), manual.round_of(my));
|
||||||
|
EXPECT_EQ(automatic.preprocessing_count(), manual.preprocessing_count());
|
||||||
|
EXPECT_EQ(automatic.monomial_count(), manual.monomial_count());
|
||||||
|
EXPECT_EQ(automatic.wire_count(), manual.wire_count());
|
||||||
|
|
||||||
|
Counter ar, mr;
|
||||||
|
automatic.sample(ar);
|
||||||
|
manual.sample(mr);
|
||||||
|
EXPECT_EQ(ar.draws, mr.draws);
|
||||||
|
automatic.bind(ax, x, ar);
|
||||||
|
automatic.bind(az, z, ar);
|
||||||
|
automatic.bind(aa0, a0, ar);
|
||||||
|
automatic.bind(aa1, a1, ar);
|
||||||
|
automatic.bind(aa2, a2, ar);
|
||||||
|
automatic.bind(aa3, a3, ar);
|
||||||
|
manual.bind(mx, x, mr);
|
||||||
|
manual.bind(mz, z, mr);
|
||||||
|
manual.bind(ma0, a0, mr);
|
||||||
|
manual.bind(ma1, a1, mr);
|
||||||
|
manual.bind(ma2, a2, mr);
|
||||||
|
manual.bind(ma3, a3, mr);
|
||||||
|
automatic.evaluate();
|
||||||
|
manual.evaluate();
|
||||||
|
EXPECT_EQ(automatic.open(ay), expect);
|
||||||
|
EXPECT_EQ(manual.open(my), expect);
|
||||||
|
EXPECT_EQ(automatic.open(ay), manual.open(my));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ElementaryLogManyPoints)
|
||||||
|
{
|
||||||
|
const u64 xs[] = {0, 1, 2, 7};
|
||||||
|
const u64 zs[] = {0, 1, 3, 6};
|
||||||
|
const u64 coeffs[] = {0, 1, 4};
|
||||||
|
for (u64 x : xs)
|
||||||
|
for (u64 z : zs)
|
||||||
|
for (u64 a0 : coeffs)
|
||||||
|
for (u64 a3 : coeffs)
|
||||||
|
{
|
||||||
|
const u64 a1 = a0 + 2;
|
||||||
|
const u64 a2 = a3 + 1;
|
||||||
|
const u64 m = x * z;
|
||||||
|
const u64 expect = a0 + a1 * m + a2 * m * m + a3 * mpow(m, 3);
|
||||||
|
auto got = run_poly(6, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[5] * pow(in[0], 3) * pow(in[1], 3)
|
||||||
|
+ in[4] * pow(in[0], 2) * pow(in[1], 2)
|
||||||
|
+ in[3] * in[0] * in[1]
|
||||||
|
+ in[2]);
|
||||||
|
}, {x, z, a0, a1, a2, a3});
|
||||||
|
EXPECT_EQ(got.value, expect);
|
||||||
|
EXPECT_EQ(got.rounds, 2);
|
||||||
|
EXPECT_EQ(got.prep, 12u);
|
||||||
|
EXPECT_EQ(got.monos, 6u);
|
||||||
|
EXPECT_EQ(got.wires, 8u);
|
||||||
|
EXPECT_EQ(got.draws, 18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ElementaryQuadraticAndQuarticFactor)
|
||||||
|
{
|
||||||
|
auto quadratic = run_poly(5, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
auto x = in[0], z = in[1], a0 = in[2], a1 = in[3], a2 = in[4];
|
||||||
|
return s(a2 * pow(x, 2) * pow(z, 2) + a1 * x * z + a0);
|
||||||
|
}, {3, 4, 5, 6, 7});
|
||||||
|
const u64 mq = 3u * 4u;
|
||||||
|
EXPECT_EQ(quadratic.value, 5u + 6u * mq + 7u * mq * mq);
|
||||||
|
EXPECT_EQ(quadratic.rounds, 2);
|
||||||
|
EXPECT_EQ(quadratic.wires, 7u);
|
||||||
|
|
||||||
|
auto quartic = run_poly(7, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
auto x = in[0], z = in[1];
|
||||||
|
return s(in[6] * pow(x, 4) * pow(z, 4) + in[5] * pow(x, 3) * pow(z, 3)
|
||||||
|
+ in[4] * pow(x, 2) * pow(z, 2) + in[3] * x * z + in[2]);
|
||||||
|
}, {2, 3, 1, 2, 3, 4, 5});
|
||||||
|
const u64 m = 2u * 3u;
|
||||||
|
const u64 expect = 1u + 2u * m + 3u * mpow(m, 2) + 4u * mpow(m, 3) + 5u * mpow(m, 4);
|
||||||
|
EXPECT_EQ(quartic.value, expect);
|
||||||
|
EXPECT_EQ(quartic.rounds, 2);
|
||||||
|
EXPECT_EQ(quartic.wires, 9u);
|
||||||
|
EXPECT_LT(quartic.prep, quadratic.prep + 40u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ElementaryLeadingTermKeepsAnExtraFactor)
|
||||||
|
{
|
||||||
|
// x and y share exponents. z appears only on the leading term.
|
||||||
|
auto got = run_poly(6, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
auto x = in[0], y = in[1], z = in[2], a0 = in[3], a1 = in[4], a2 = in[5];
|
||||||
|
return s(a2 * pow(x, 2) * pow(y, 2) * z + a1 * x * y + a0);
|
||||||
|
}, {2, 3, 4, 5, 6, 7});
|
||||||
|
const u64 m = 2u * 3u;
|
||||||
|
EXPECT_EQ(got.value, 5u + 6u * m + 7u * m * m * 4u);
|
||||||
|
EXPECT_EQ(got.rounds, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ElementaryThreeWireCluster)
|
||||||
|
{
|
||||||
|
auto got = run_poly(5, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
auto x = in[0], y = in[1], z = in[2], a0 = in[3], a1 = in[4];
|
||||||
|
return s(u64{2} * pow(x, 2) * pow(y, 2) * pow(z, 2) + a1 * x * y * z + a0);
|
||||||
|
}, {2, 3, 4, 5, 6});
|
||||||
|
const u64 m = 2u * 3u * 4u;
|
||||||
|
EXPECT_EQ(got.value, 5u + 6u * m + 2u * m * m);
|
||||||
|
EXPECT_EQ(got.rounds, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, MismatchedPowersPeelTheSharedVariable)
|
||||||
|
{
|
||||||
|
// x is in every term and z is in every term, but their exponents do not
|
||||||
|
// match, so the polynomial is not rewritten as a polynomial in x*z.
|
||||||
|
auto got = run_poly(4, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
auto a = in[0], b = in[1], x = in[2], z = in[3];
|
||||||
|
return s(a * pow(x, 2) * z + b * x * pow(z, 2));
|
||||||
|
}, {2, 5, 3, 4});
|
||||||
|
EXPECT_EQ(got.value, 2u * 9u * 4u + 5u * 3u * 16u);
|
||||||
|
EXPECT_EQ(got.rounds, 2);
|
||||||
|
EXPECT_EQ(got.wires, 6u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, SecretScaleOfALowPublicPolynomialStaysOneRound)
|
||||||
|
{
|
||||||
|
auto got = run_poly(2, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[0] * (u64{1} + u64{2} * in[1] + u64{3} * pow(in[1], 2)));
|
||||||
|
}, {4, 5});
|
||||||
|
EXPECT_EQ(got.value, 4u * (1u + 2u * 5u + 3u * 25u));
|
||||||
|
EXPECT_EQ(got.rounds, 1);
|
||||||
|
EXPECT_EQ(got.wires, 3u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, PublicCoefficientsStayOneRound)
|
||||||
|
{
|
||||||
|
auto got = run_poly(1, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s.horner(in[0], {u64{1}, u64{2}, u64{3}, u64{4}, u64{5}});
|
||||||
|
}, {3});
|
||||||
|
const u64 x = 3;
|
||||||
|
EXPECT_EQ(got.value, 1u + 2u * x + 3u * x * x + 4u * x * x * x + 5u * mpow(x, 4));
|
||||||
|
EXPECT_EQ(got.rounds, 1);
|
||||||
|
EXPECT_EQ(got.wires, 2u);
|
||||||
|
|
||||||
|
session64 compared;
|
||||||
|
auto manual_x = compared.input();
|
||||||
|
auto manual = compared(u64{1} + u64{2} * manual_x + u64{3} * pow(manual_x, 2)
|
||||||
|
+ u64{4} * pow(manual_x, 3) + u64{5} * pow(manual_x, 4));
|
||||||
|
EXPECT_EQ(compared.round_of(manual), 1);
|
||||||
|
EXPECT_EQ(compared.preprocessing_count(), got.prep);
|
||||||
|
EXPECT_EQ(compared.monomial_count(), got.monos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, PublicSignSplitsOnceThePowerCrossesGrow)
|
||||||
|
{
|
||||||
|
session64 quadratic;
|
||||||
|
auto qsgn = quadratic.input();
|
||||||
|
auto qx = quadratic.input();
|
||||||
|
auto qy = quadratic(qsgn * (u64{1} + qx + pow(qx, 2)));
|
||||||
|
session64 quadratic_hand;
|
||||||
|
auto qhs = quadratic_hand.input();
|
||||||
|
auto qhx = quadratic_hand.input();
|
||||||
|
auto qinner = quadratic_hand(u64{1} + qhx + pow(qhx, 2));
|
||||||
|
auto qouter = quadratic_hand(qhs * qinner);
|
||||||
|
// Equal preprocessing. The one-round form wins the tie.
|
||||||
|
EXPECT_EQ(quadratic.round_of(qy), 1);
|
||||||
|
EXPECT_EQ(quadratic_hand.round_of(qouter), 2);
|
||||||
|
EXPECT_EQ(quadratic.preprocessing_count(), quadratic_hand.preprocessing_count());
|
||||||
|
|
||||||
|
session64 cubic;
|
||||||
|
auto sgn = cubic.input();
|
||||||
|
auto x = cubic.input();
|
||||||
|
auto y = cubic(sgn * (u64{1} + x + pow(x, 2) + pow(x, 3)));
|
||||||
|
session64 cubic_hand;
|
||||||
|
auto ms = cubic_hand.input();
|
||||||
|
auto mx = cubic_hand.input();
|
||||||
|
auto inner = cubic_hand(u64{1} + mx + pow(mx, 2) + pow(mx, 3));
|
||||||
|
auto outer = cubic_hand(ms * inner);
|
||||||
|
EXPECT_EQ(cubic.round_of(y), 2);
|
||||||
|
EXPECT_EQ(cubic_hand.round_of(outer), 2);
|
||||||
|
EXPECT_EQ(cubic.preprocessing_count(), cubic_hand.preprocessing_count());
|
||||||
|
|
||||||
|
Counter rng;
|
||||||
|
cubic.sample(rng);
|
||||||
|
cubic.bind(sgn, u64{3}, rng);
|
||||||
|
cubic.bind(x, u64{4}, rng);
|
||||||
|
cubic.evaluate();
|
||||||
|
EXPECT_EQ(cubic.open(y), 3u * (1u + 4u + 16u + 64u));
|
||||||
|
(void)qouter;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, UnivariateSecretCubicMatchesTheFactoredBudget)
|
||||||
|
{
|
||||||
|
session64 uni;
|
||||||
|
auto x = uni.input();
|
||||||
|
auto a0 = uni.input();
|
||||||
|
auto a1 = uni.input();
|
||||||
|
auto a2 = uni.input();
|
||||||
|
auto a3 = uni.input();
|
||||||
|
auto cubic = uni(a3 * pow(x, 3) + a2 * pow(x, 2) + a1 * x + a0);
|
||||||
|
EXPECT_EQ(uni.round_of(cubic), 1);
|
||||||
|
EXPECT_EQ(uni.wire_count(), 6u);
|
||||||
|
|
||||||
|
session64 factored;
|
||||||
|
auto fx = factored.input();
|
||||||
|
auto fz = factored.input();
|
||||||
|
auto fa0 = factored.input();
|
||||||
|
auto fa1 = factored.input();
|
||||||
|
auto fa2 = factored.input();
|
||||||
|
auto fa3 = factored.input();
|
||||||
|
auto logp = factored(fa3 * pow(fx, 3) * pow(fz, 3) + fa2 * pow(fx, 2) * pow(fz, 2)
|
||||||
|
+ fa1 * fx * fz + fa0);
|
||||||
|
// Two extra input blinds and one product share, with m standing in for x.
|
||||||
|
EXPECT_EQ(factored.preprocessing_count(), uni.preprocessing_count() + 3u);
|
||||||
|
EXPECT_EQ(factored.round_of(logp), 2);
|
||||||
|
|
||||||
|
session64 scaled;
|
||||||
|
auto sx = scaled.input();
|
||||||
|
auto ss = scaled.input();
|
||||||
|
auto s0 = scaled.input();
|
||||||
|
auto s1 = scaled.input();
|
||||||
|
auto s2 = scaled.input();
|
||||||
|
auto s3 = scaled.input();
|
||||||
|
auto signed_cubic = scaled(ss * (s3 * pow(sx, 3) + s2 * pow(sx, 2) + s1 * sx + s0));
|
||||||
|
EXPECT_EQ(scaled.preprocessing_count(), uni.preprocessing_count() + 3u);
|
||||||
|
EXPECT_EQ(scaled.round_of(signed_cubic), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ConstantCoefficientIsNotMasked)
|
||||||
|
{
|
||||||
|
session64 secret_const;
|
||||||
|
auto x = secret_const.input();
|
||||||
|
auto sgn = secret_const.input();
|
||||||
|
auto a0 = secret_const.input();
|
||||||
|
auto a1 = secret_const.input();
|
||||||
|
auto secret = secret_const(sgn * (a1 * x + a0));
|
||||||
|
|
||||||
|
session64 public_const;
|
||||||
|
auto px = public_const.input();
|
||||||
|
auto ps = public_const.input();
|
||||||
|
auto pa1 = public_const.input();
|
||||||
|
auto pub = public_const(ps * (pa1 * px + u64{4}));
|
||||||
|
|
||||||
|
EXPECT_EQ(secret_const.preprocessing_count(), public_const.preprocessing_count());
|
||||||
|
EXPECT_EQ(secret_const.wire_count(), public_const.wire_count() + 1u);
|
||||||
|
EXPECT_EQ(secret_const.round_of(secret), 2);
|
||||||
|
EXPECT_EQ(public_const.round_of(pub), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, SumsAndCancellationsNeedNoProducts)
|
||||||
|
{
|
||||||
|
auto sum = run_poly(3, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[0] + in[1] + in[2]);
|
||||||
|
}, {4, 5, 6});
|
||||||
|
EXPECT_EQ(sum.value, 15u);
|
||||||
|
EXPECT_EQ(sum.prep, 0u);
|
||||||
|
EXPECT_EQ(sum.monos, 0u);
|
||||||
|
EXPECT_EQ(sum.rounds, 1);
|
||||||
|
|
||||||
|
auto cancelled = run_poly(2, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[0] * in[1] - in[1] * in[0] + u64{4});
|
||||||
|
}, {8, 9});
|
||||||
|
EXPECT_EQ(cancelled.value, 4u);
|
||||||
|
EXPECT_EQ(cancelled.prep, 0u);
|
||||||
|
EXPECT_EQ(cancelled.monos, 0u);
|
||||||
|
|
||||||
|
auto wiped = run_poly(2, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[0] * in[1] - in[0] * in[1]);
|
||||||
|
}, {8, 9});
|
||||||
|
EXPECT_EQ(wiped.value, 0u);
|
||||||
|
EXPECT_EQ(wiped.prep, 0u);
|
||||||
|
|
||||||
|
session64 empty;
|
||||||
|
auto dummy = empty.input();
|
||||||
|
auto zero = empty.horner(dummy, {});
|
||||||
|
EXPECT_EQ(empty.preprocessing_count(), 0u);
|
||||||
|
Counter rng;
|
||||||
|
empty.sample(rng);
|
||||||
|
EXPECT_EQ(rng.draws, 0);
|
||||||
|
empty.bind(dummy, u64{12}, rng);
|
||||||
|
empty.evaluate();
|
||||||
|
EXPECT_EQ(empty.open(zero), 0u);
|
||||||
|
|
||||||
|
auto constant = run_poly(1, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(pow(in[0], 0) + u64{10});
|
||||||
|
}, {99});
|
||||||
|
EXPECT_EQ(constant.value, 11u);
|
||||||
|
EXPECT_EQ(constant.prep, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, SubtractionAndNegativeCoefficients)
|
||||||
|
{
|
||||||
|
auto got = run_poly(4, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
auto sgn = in[0], x = in[1], a1 = in[2], a0 = in[3];
|
||||||
|
return s(sgn * (pow(x, 2) - a1 * x - a0));
|
||||||
|
}, {2, 5, 4, 6});
|
||||||
|
const u64 inner = u64{25} - u64{4} * u64{5} - u64{6};
|
||||||
|
EXPECT_EQ(got.value, u64{2} * inner);
|
||||||
|
EXPECT_EQ(got.rounds, 2);
|
||||||
|
|
||||||
|
auto wrapped = run_poly(3, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[0] - in[1] * in[2]);
|
||||||
|
}, {3, 10, 4});
|
||||||
|
EXPECT_EQ(wrapped.value, u64{3} - u64{10} * u64{4});
|
||||||
|
|
||||||
|
auto neg = run_poly(2, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(-(in[0] * in[1] + in[0]));
|
||||||
|
}, {6, 7});
|
||||||
|
EXPECT_EQ(neg.value, u64{0} - (u64{6} * u64{7} + u64{6}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, PowAndMonomialSpellingsAgree)
|
||||||
|
{
|
||||||
|
auto as_pow = run_poly(4, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[2] * pow(in[0], 2) * pow(in[1], 2) + in[3] * in[0] * in[1]);
|
||||||
|
}, {3, 5, 2, 4});
|
||||||
|
auto as_mono = run_poly(4, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[2] * in[0] * in[0] * in[1] * in[1] + in[3] * in[0] * in[1]);
|
||||||
|
}, {3, 5, 2, 4});
|
||||||
|
const u64 m = 3u * 5u;
|
||||||
|
EXPECT_EQ(as_pow.value, 2u * m * m + 4u * m);
|
||||||
|
EXPECT_EQ(as_mono.value, as_pow.value);
|
||||||
|
EXPECT_EQ(as_mono.prep, as_pow.prep);
|
||||||
|
EXPECT_EQ(as_mono.rounds, as_pow.rounds);
|
||||||
|
EXPECT_EQ(as_mono.monos, as_pow.monos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, FactoredProductIsVisibleAndTheSexticTermIsNot)
|
||||||
|
{
|
||||||
|
session64 s;
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto a0 = s.input();
|
||||||
|
auto a1 = s.input();
|
||||||
|
auto y = s(a1 * pow(x, 2) * pow(z, 2) + a0);
|
||||||
|
s.pin(y);
|
||||||
|
const auto prep = s.preprocessing_count();
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
EXPECT_EQ(s.preprocessing_count(), prep);
|
||||||
|
EXPECT_EQ(s.monomial({{x, 1u}, {z, 1u}}).open(),
|
||||||
|
s.lambda(x).open() * s.lambda(z).open());
|
||||||
|
EXPECT_THROW((void)[&] { return s.monomial({{x, 2u}, {z, 2u}}); }(),
|
||||||
|
std::logic_error);
|
||||||
|
s.bind(x, u64{6}, rng);
|
||||||
|
s.bind(z, u64{7}, rng);
|
||||||
|
s.bind(a0, u64{8}, rng);
|
||||||
|
s.bind(a1, u64{9}, rng);
|
||||||
|
s.evaluate();
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(y), 8u + 9u * 42u * 42u);
|
||||||
|
EXPECT_EQ(s.delta(y), s.open(y) + s.lambda(y).open());
|
||||||
|
EXPECT_EQ(s.delta(x), u64{6} + s.lambda(x).open());
|
||||||
|
EXPECT_EQ(dpf::reconstruct(s.value(y).party0(), s.value(y).party1()), s.open(y));
|
||||||
|
int draws = rng.draws;
|
||||||
|
s.sample(rng);
|
||||||
|
EXPECT_EQ(rng.draws, draws);
|
||||||
|
EXPECT_THROW(s.bind(x, u64{1}, rng), std::logic_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, OutputBlindIsSampledOnlyWhenPinned)
|
||||||
|
{
|
||||||
|
session64 s;
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto y = s(x * z + u64{3});
|
||||||
|
EXPECT_THROW(s.lambda(y), std::logic_error);
|
||||||
|
const auto before = s.preprocessing_count();
|
||||||
|
s.pin(y);
|
||||||
|
EXPECT_EQ(s.preprocessing_count(), before + 1u);
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
auto lam = s.lambda(y);
|
||||||
|
s.bind(x, u64{4}, rng);
|
||||||
|
s.bind(z, u64{5}, rng);
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(y), 23u);
|
||||||
|
EXPECT_EQ(s.delta(y), 23u + lam.open());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, SecondPolynomialReusesTheFactoredPair)
|
||||||
|
{
|
||||||
|
session64 s;
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto a0 = s.input();
|
||||||
|
auto a1 = s.input();
|
||||||
|
auto y0 = s(a1 * pow(x, 2) * pow(z, 2) + a0);
|
||||||
|
const auto monos0 = s.monomial_count();
|
||||||
|
const auto prep0 = s.preprocessing_count();
|
||||||
|
auto b0 = s.input();
|
||||||
|
auto b1 = s.input();
|
||||||
|
auto y1 = s(b1 * x * z + b0);
|
||||||
|
EXPECT_LT(s.monomial_count() - monos0, monos0);
|
||||||
|
EXPECT_LT(s.preprocessing_count() - prep0, prep0);
|
||||||
|
EXPECT_EQ(s.round_of(y0), 2);
|
||||||
|
// b1*x*z + b0 is already one product plus a value share.
|
||||||
|
EXPECT_EQ(s.round_of(y1), 1);
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
s.bind(x, u64{3}, rng);
|
||||||
|
s.bind(z, u64{4}, rng);
|
||||||
|
s.bind(a0, u64{5}, rng);
|
||||||
|
s.bind(a1, u64{6}, rng);
|
||||||
|
s.bind(b0, u64{7}, rng);
|
||||||
|
s.bind(b1, u64{8}, rng);
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(y0), 5u + 6u * 12u * 12u);
|
||||||
|
EXPECT_EQ(s.open(y1), 7u + 8u * 12u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, FactoredOutputFeedsALaterProduct)
|
||||||
|
{
|
||||||
|
session64 s;
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto a0 = s.input();
|
||||||
|
auto a1 = s.input();
|
||||||
|
auto y = s(a1 * x * z + a0);
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
auto blind_x = s.lambda(x);
|
||||||
|
s.bind(x, u64{5}, rng);
|
||||||
|
s.bind(z, u64{6}, rng);
|
||||||
|
s.bind(a0, u64{7}, rng);
|
||||||
|
s.bind(a1, u64{8}, rng);
|
||||||
|
auto w = s.input();
|
||||||
|
auto prod = s(y * w);
|
||||||
|
EXPECT_EQ(s.round_of(y), 1);
|
||||||
|
EXPECT_EQ(s.round_of(prod), 2);
|
||||||
|
s.sample(rng);
|
||||||
|
EXPECT_EQ(s.lambda(x), blind_x);
|
||||||
|
s.bind(w, u64{9}, rng);
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(y), 7u + 8u * 30u);
|
||||||
|
EXPECT_EQ(s.open(prod), s.open(y) * 9u);
|
||||||
|
EXPECT_EQ(s.lambda(x), blind_x);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ScheduledPolynomialReplaysFromASeed)
|
||||||
|
{
|
||||||
|
using block = dpf::prg::aes128::block_type;
|
||||||
|
block seed = simde_mm_set_epi64x(0x1234, 0x5678);
|
||||||
|
dpf::beavers::oracle<u64> left(seed, 4);
|
||||||
|
dpf::beavers::oracle<u64> right(seed, 4);
|
||||||
|
|
||||||
|
auto build = [](session64 & s) {
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto a0 = s.input();
|
||||||
|
auto a1 = s.input();
|
||||||
|
auto a2 = s.input();
|
||||||
|
auto y = s(a2 * pow(x, 2) * pow(z, 2) + a1 * x * z + a0);
|
||||||
|
return std::tuple{x, z, a0, a1, a2, y};
|
||||||
|
};
|
||||||
|
session64 a;
|
||||||
|
session64 b;
|
||||||
|
auto [ax, az, aa0, aa1, aa2, ay] = build(a);
|
||||||
|
auto [bx, bz, ba0, ba1, ba2, by] = build(b);
|
||||||
|
a.sample_from(left, 4);
|
||||||
|
b.sample_from(right, 4);
|
||||||
|
auto am = a.material_at(left, 4);
|
||||||
|
auto bm = b.material_at(right, 4);
|
||||||
|
EXPECT_EQ(am.bundles, bm.bundles);
|
||||||
|
EXPECT_EQ(am.lambda, bm.lambda);
|
||||||
|
auto other = a.material_at(left, 5);
|
||||||
|
EXPECT_NE(other.lambda, am.lambda);
|
||||||
|
|
||||||
|
Counter ra, rb;
|
||||||
|
a.bind(ax, u64{2}, ra);
|
||||||
|
a.bind(az, u64{3}, ra);
|
||||||
|
a.bind(aa0, u64{4}, ra);
|
||||||
|
a.bind(aa1, u64{5}, ra);
|
||||||
|
a.bind(aa2, u64{6}, ra);
|
||||||
|
b.bind(bx, u64{2}, rb);
|
||||||
|
b.bind(bz, u64{3}, rb);
|
||||||
|
b.bind(ba0, u64{4}, rb);
|
||||||
|
b.bind(ba1, u64{5}, rb);
|
||||||
|
b.bind(ba2, u64{6}, rb);
|
||||||
|
a.evaluate();
|
||||||
|
b.evaluate();
|
||||||
|
const u64 m = 6;
|
||||||
|
EXPECT_EQ(a.open(ay), 4u + 5u * m + 6u * m * m);
|
||||||
|
EXPECT_EQ(b.open(by), a.open(ay));
|
||||||
|
EXPECT_EQ(a.lambda(ax), b.lambda(bx));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, BindSharesFeedsAScheduledPolynomial)
|
||||||
|
{
|
||||||
|
session64 s;
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto c = s.input();
|
||||||
|
auto y = s(c * (pow(x, 2) * pow(z, 2) + x * z + u64{1}));
|
||||||
|
s.sample();
|
||||||
|
s.bind_shares(x, u64{2}, u64{5});
|
||||||
|
s.bind_shares(z, u64{1}, u64{2});
|
||||||
|
s.bind_shares(c, u64{10}, u64{4});
|
||||||
|
s.evaluate();
|
||||||
|
const u64 xv = 7, zv = 3, cv = 14, m = xv * zv;
|
||||||
|
EXPECT_EQ(s.open(y), cv * (m * m + m + 1u));
|
||||||
|
EXPECT_EQ(dpf::reconstruct(s.value(x).party0(), s.value(x).party1()), xv);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, GrottoPolynomialsAtSeveralPoints)
|
||||||
|
{
|
||||||
|
const u64 xs[] = {0, 1, 2, 5};
|
||||||
|
const u64 signs[] = {0, 1, 3};
|
||||||
|
const u64 coeffs[] = {0, 4, 7};
|
||||||
|
for (u64 x : xs)
|
||||||
|
for (u64 sgn : signs)
|
||||||
|
for (u64 a0 : coeffs)
|
||||||
|
for (u64 a1 : coeffs)
|
||||||
|
{
|
||||||
|
const u64 a2 = a0 + 1;
|
||||||
|
const u64 a3 = a1 + 2;
|
||||||
|
auto linear = run_poly(4, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[1] * (in[3] * in[0] + in[2]));
|
||||||
|
}, {x, sgn, a0, a1});
|
||||||
|
EXPECT_EQ(linear.value, sgn * (a1 * x + a0));
|
||||||
|
EXPECT_EQ(linear.rounds, 2);
|
||||||
|
EXPECT_EQ(linear.prep, 6u);
|
||||||
|
|
||||||
|
auto quad = run_poly(5, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[1] * (in[4] * pow(in[0], 2) + in[3] * in[0] + in[2]));
|
||||||
|
}, {x, sgn, a0, a1, a2});
|
||||||
|
EXPECT_EQ(quad.value, sgn * (a2 * x * x + a1 * x + a0));
|
||||||
|
EXPECT_EQ(quad.rounds, 2);
|
||||||
|
EXPECT_EQ(quad.prep, 9u);
|
||||||
|
|
||||||
|
auto cube = run_poly(6, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(in[1] * (in[5] * pow(in[0], 3) + in[4] * pow(in[0], 2)
|
||||||
|
+ in[3] * in[0] + in[2]));
|
||||||
|
}, {x, sgn, a0, a1, a2, a3});
|
||||||
|
EXPECT_EQ(cube.value, sgn * (a3 * x * x * x + a2 * x * x + a1 * x + a0));
|
||||||
|
EXPECT_EQ(cube.rounds, 2);
|
||||||
|
EXPECT_EQ(cube.prep, 12u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ExpScaleAgreesWithAHandSplit)
|
||||||
|
{
|
||||||
|
session64 automatic;
|
||||||
|
auto r = automatic.input();
|
||||||
|
auto c = automatic.input();
|
||||||
|
auto a0 = automatic.input();
|
||||||
|
auto a1 = automatic.input();
|
||||||
|
auto a2 = automatic.input();
|
||||||
|
auto a3 = automatic.input();
|
||||||
|
auto y = automatic(c * (a3 * pow(r, 3) + a2 * pow(r, 2) + a1 * r + a0));
|
||||||
|
|
||||||
|
session64 manual;
|
||||||
|
auto mr = manual.input();
|
||||||
|
auto mc = manual.input();
|
||||||
|
auto m0 = manual.input();
|
||||||
|
auto m1 = manual.input();
|
||||||
|
auto m2 = manual.input();
|
||||||
|
auto m3 = manual.input();
|
||||||
|
auto inner = manual(m3 * pow(mr, 3) + m2 * pow(mr, 2) + m1 * mr + m0);
|
||||||
|
auto outer = manual(mc * inner);
|
||||||
|
EXPECT_EQ(automatic.round_of(y), manual.round_of(outer));
|
||||||
|
EXPECT_EQ(automatic.preprocessing_count(), manual.preprocessing_count());
|
||||||
|
EXPECT_EQ(automatic.wire_count(), manual.wire_count());
|
||||||
|
|
||||||
|
const u64 rv = 3, cv = 4, c0 = 5, c1 = 0, c2 = 2, c3 = 1;
|
||||||
|
Counter ra, rm;
|
||||||
|
automatic.sample(ra);
|
||||||
|
manual.sample(rm);
|
||||||
|
automatic.bind(r, rv, ra);
|
||||||
|
automatic.bind(c, cv, ra);
|
||||||
|
automatic.bind(a0, c0, ra);
|
||||||
|
automatic.bind(a1, c1, ra);
|
||||||
|
automatic.bind(a2, c2, ra);
|
||||||
|
automatic.bind(a3, c3, ra);
|
||||||
|
manual.bind(mr, rv, rm);
|
||||||
|
manual.bind(mc, cv, rm);
|
||||||
|
manual.bind(m0, c0, rm);
|
||||||
|
manual.bind(m1, c1, rm);
|
||||||
|
manual.bind(m2, c2, rm);
|
||||||
|
manual.bind(m3, c3, rm);
|
||||||
|
automatic.evaluate();
|
||||||
|
manual.evaluate();
|
||||||
|
const u64 expect = cv * (c3 * 27u + c2 * 9u + c1 * rv + c0);
|
||||||
|
EXPECT_EQ(automatic.open(y), expect);
|
||||||
|
EXPECT_EQ(manual.open(outer), expect);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ScaleAfterAPowerAgreesWithAHandSplit)
|
||||||
|
{
|
||||||
|
session64 automatic;
|
||||||
|
auto x = automatic.input();
|
||||||
|
auto z = automatic.input();
|
||||||
|
auto sgn = automatic.input();
|
||||||
|
auto a0 = automatic.input();
|
||||||
|
auto a1 = automatic.input();
|
||||||
|
auto y = automatic(sgn * (a1 * pow(x, 2) * pow(z, 2) + a0));
|
||||||
|
|
||||||
|
session64 manual;
|
||||||
|
auto mx = manual.input();
|
||||||
|
auto mz = manual.input();
|
||||||
|
auto ms = manual.input();
|
||||||
|
auto m0 = manual.input();
|
||||||
|
auto m1 = manual.input();
|
||||||
|
auto mm = manual(mx * mz);
|
||||||
|
auto inner = manual(m1 * pow(mm, 2) + m0);
|
||||||
|
auto outer = manual(ms * inner);
|
||||||
|
EXPECT_EQ(automatic.round_of(y), 3);
|
||||||
|
EXPECT_EQ(manual.round_of(outer), 3);
|
||||||
|
EXPECT_EQ(automatic.preprocessing_count(), manual.preprocessing_count());
|
||||||
|
EXPECT_EQ(automatic.wire_count(), manual.wire_count());
|
||||||
|
|
||||||
|
Counter ra, rm;
|
||||||
|
automatic.sample(ra);
|
||||||
|
manual.sample(rm);
|
||||||
|
automatic.bind(x, u64{2}, ra);
|
||||||
|
automatic.bind(z, u64{5}, ra);
|
||||||
|
automatic.bind(sgn, u64{3}, ra);
|
||||||
|
automatic.bind(a0, u64{4}, ra);
|
||||||
|
automatic.bind(a1, u64{6}, ra);
|
||||||
|
manual.bind(mx, u64{2}, rm);
|
||||||
|
manual.bind(mz, u64{5}, rm);
|
||||||
|
manual.bind(ms, u64{3}, rm);
|
||||||
|
manual.bind(m0, u64{4}, rm);
|
||||||
|
manual.bind(m1, u64{6}, rm);
|
||||||
|
automatic.evaluate();
|
||||||
|
manual.evaluate();
|
||||||
|
EXPECT_EQ(automatic.open(y), 3u * (6u * 100u + 4u));
|
||||||
|
EXPECT_EQ(manual.open(outer), automatic.open(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ModintScheduledPolynomialsWrap)
|
||||||
|
{
|
||||||
|
using M = dpf::modint<17>;
|
||||||
|
const M points[] = {M{0}, M{1}, M{8}, M{16}};
|
||||||
|
for (M x : points)
|
||||||
|
for (M z : points)
|
||||||
|
for (M a3 : {M{0}, M{5}})
|
||||||
|
{
|
||||||
|
dpf::beavers::session<M> s;
|
||||||
|
auto wx = s.input();
|
||||||
|
auto wz = s.input();
|
||||||
|
auto wa0 = s.input();
|
||||||
|
auto wa1 = s.input();
|
||||||
|
auto wa2 = s.input();
|
||||||
|
auto wa3 = s.input();
|
||||||
|
auto y = s(wa3 * pow(wx, 3) * pow(wz, 3) + wa2 * pow(wx, 2) * pow(wz, 2)
|
||||||
|
+ wa1 * wx * wz + wa0);
|
||||||
|
EXPECT_EQ(s.round_of(y), 2);
|
||||||
|
EXPECT_EQ(s.preprocessing_count(), 12u);
|
||||||
|
Seq<M> rng;
|
||||||
|
s.sample(rng);
|
||||||
|
const M a0{3}, a1{4}, a2{9};
|
||||||
|
s.bind(wx, x, rng);
|
||||||
|
s.bind(wz, z, rng);
|
||||||
|
s.bind(wa0, a0, rng);
|
||||||
|
s.bind(wa1, a1, rng);
|
||||||
|
s.bind(wa2, a2, rng);
|
||||||
|
s.bind(wa3, a3, rng);
|
||||||
|
s.evaluate();
|
||||||
|
const M m = x * z;
|
||||||
|
EXPECT_EQ(s.open(y), a0 + a1 * m + a2 * m * m + a3 * m * m * m);
|
||||||
|
}
|
||||||
|
|
||||||
|
dpf::beavers::session<M> scaled;
|
||||||
|
auto r = scaled.input();
|
||||||
|
auto c = scaled.input();
|
||||||
|
auto y = scaled(c * (M{4} + M{5} * r + M{16} * pow(r, 2)));
|
||||||
|
EXPECT_EQ(scaled.round_of(y), 1);
|
||||||
|
Seq<M> rng;
|
||||||
|
scaled.sample(rng);
|
||||||
|
scaled.bind(r, M{6}, rng);
|
||||||
|
scaled.bind(c, M{3}, rng);
|
||||||
|
scaled.evaluate();
|
||||||
|
EXPECT_EQ(scaled.open(y), M{3} * (M{4} + M{5} * M{6} + M{16} * M{6} * M{6}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, XorWrapperScheduledPolynomials)
|
||||||
|
{
|
||||||
|
using W = dpf::xor_wrapper<std::uint32_t>;
|
||||||
|
dpf::beavers::session<W> s;
|
||||||
|
auto x = s.input();
|
||||||
|
auto z = s.input();
|
||||||
|
auto a = s.input();
|
||||||
|
auto b = s.input();
|
||||||
|
auto y = s(a * x * z + b);
|
||||||
|
auto square = s(x * x);
|
||||||
|
auto binomial = s((x + z) * (x + z));
|
||||||
|
EXPECT_EQ(s.round_of(y), 1);
|
||||||
|
EXPECT_EQ(s.round_of(square), 1);
|
||||||
|
XorSeq<W> rng;
|
||||||
|
s.sample(rng);
|
||||||
|
const W xv{0b11001100u};
|
||||||
|
const W zv{0b10101010u};
|
||||||
|
const W av{0b11110000u};
|
||||||
|
const W bv{0b00001111u};
|
||||||
|
s.bind(x, xv, rng);
|
||||||
|
s.bind(z, zv, rng);
|
||||||
|
s.bind(a, av, rng);
|
||||||
|
s.bind(b, bv, rng);
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(y), av * (xv * zv) + bv);
|
||||||
|
EXPECT_EQ(s.open(square), xv);
|
||||||
|
EXPECT_EQ(s.open(binomial), xv + zv);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, HighPowersAndTheExpansionLimit)
|
||||||
|
{
|
||||||
|
auto sixteenth = run_poly(1, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(pow(in[0], 16));
|
||||||
|
}, {2});
|
||||||
|
EXPECT_EQ(sixteenth.value, 65536u);
|
||||||
|
EXPECT_EQ(sixteenth.rounds, 1);
|
||||||
|
|
||||||
|
auto combined = run_poly(1, [](session64 & s, const std::vector<wire64> & in) {
|
||||||
|
return s(pow(in[0], 10) * pow(in[0], 6));
|
||||||
|
}, {3});
|
||||||
|
EXPECT_EQ(combined.value, mpow(3, 16));
|
||||||
|
|
||||||
|
session64 s;
|
||||||
|
auto x = s.input();
|
||||||
|
EXPECT_THROW((void)[&] { return pow(x, 17u); }(), std::invalid_argument);
|
||||||
|
EXPECT_THROW((void)[&] { return pow(x, 10) * pow(x, 7); }(), std::invalid_argument);
|
||||||
|
|
||||||
|
std::vector<wire64> wide;
|
||||||
|
wide.reserve(12);
|
||||||
|
for (int i = 0; i < 12; ++i)
|
||||||
|
wide.push_back(s.input());
|
||||||
|
auto expr12 = wide[0] * wide[1];
|
||||||
|
for (int i = 2; i < 12; ++i)
|
||||||
|
expr12 = expr12 * wide[i];
|
||||||
|
auto all = s(expr12);
|
||||||
|
Counter rng;
|
||||||
|
s.sample(rng);
|
||||||
|
for (auto w : wide)
|
||||||
|
s.bind(w, u64{1}, rng);
|
||||||
|
s.bind(x, u64{2}, rng);
|
||||||
|
s.evaluate();
|
||||||
|
EXPECT_EQ(s.open(all), 1u);
|
||||||
|
|
||||||
|
auto thirteenth = s.input();
|
||||||
|
EXPECT_THROW((void)[&] { return s(expr12 * thirteenth); }(), std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Beaver, ScheduledPolynomialRejectsEarlyUse)
|
||||||
|
{
|
||||||
|
session64 a;
|
||||||
|
session64 b;
|
||||||
|
auto x = a.input();
|
||||||
|
auto y = b.input();
|
||||||
|
EXPECT_THROW((void)[&] { return x + y; }(), std::invalid_argument);
|
||||||
|
EXPECT_THROW((void)[&] { return x * y; }(), std::invalid_argument);
|
||||||
|
auto z = a(x + pow(x, 2));
|
||||||
|
EXPECT_THROW(a.open(z), std::logic_error);
|
||||||
|
EXPECT_THROW(a.evaluate(), std::logic_error);
|
||||||
|
a.sample();
|
||||||
|
EXPECT_THROW(a.evaluate(), std::logic_error);
|
||||||
|
EXPECT_THROW(a.delta(x), std::logic_error);
|
||||||
|
a.bind(x, u64{3});
|
||||||
|
a.evaluate();
|
||||||
|
EXPECT_EQ(a.open(z), 3u + 9u);
|
||||||
|
EXPECT_THROW(a.bind(x, u64{4}), std::logic_error);
|
||||||
|
EXPECT_THROW(a.bind_shares(z, u64{1}, u64{1}), std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Beaver, RejectsBadUse)
|
TEST(Beaver, RejectsBadUse)
|
||||||
{
|
{
|
||||||
dpf::beavers::session<u64> a;
|
dpf::beavers::session<u64> a;
|
||||||
|
|
|
||||||
|
|
@ -1121,3 +1121,105 @@ TEST(Geneval, SignedRegressionsFromTheCornerPass)
|
||||||
expect_prefix_words(wild.first, g.correction_words, g.correction_advice,
|
expect_prefix_words(wild.first, g.correction_words, g.correction_advice,
|
||||||
g.live_levels, g.leaf_live, &g.leaf, sizeof(g.leaf));
|
g.live_levels, g.leaf_live, &g.leaf, sizeof(g.leaf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Geneval, CmpEmptyRangeOpensNothing)
|
||||||
|
{
|
||||||
|
reset_roots();
|
||||||
|
const uint8_t ends[] = {0};
|
||||||
|
auto g = dpf::geneval_cmp(uint8_t{1}, uint8_t{2}, ends, ends, rng<uint8_t>(),
|
||||||
|
uint64_t{1});
|
||||||
|
EXPECT_TRUE(g.party0.empty());
|
||||||
|
EXPECT_TRUE(g.party1.empty());
|
||||||
|
EXPECT_EQ(g.live_levels, 0u);
|
||||||
|
EXPECT_TRUE(g.value_cw.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Geneval, CmpMatchesDoernerShelatKeyAndGtPredicate)
|
||||||
|
{
|
||||||
|
using in_t = uint8_t;
|
||||||
|
const in_t alpha = 40;
|
||||||
|
const in_t x0 = 0x11;
|
||||||
|
const in_t x1 = static_cast<in_t>(alpha ^ x0);
|
||||||
|
const uint64_t beta = 7;
|
||||||
|
const std::vector<in_t> ends{0, 1, 10, 40, 200, 255, 40};
|
||||||
|
|
||||||
|
reset_roots();
|
||||||
|
auto keys = dpf::make_dpf_doerner_shelat(x0, x1, rng<in_t>(), dpf::gt(beta));
|
||||||
|
reset_roots();
|
||||||
|
auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng<in_t>(), beta);
|
||||||
|
|
||||||
|
using key_t = std::decay_t<decltype(keys.first)>;
|
||||||
|
EXPECT_EQ(g.live_levels, key_t::depth);
|
||||||
|
ASSERT_EQ(g.correction_words.size(), key_t::depth);
|
||||||
|
ASSERT_EQ(g.value_cw.size(), key_t::depth);
|
||||||
|
for (std::size_t level = 0; level < key_t::depth; ++level)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(std::memcmp(&g.correction_words[level],
|
||||||
|
&keys.first.correction_word(level), sizeof(simde__m128i)), 0) << level;
|
||||||
|
EXPECT_EQ(g.correction_advice[level], keys.first.correction_advice(level)) << level;
|
||||||
|
EXPECT_EQ(g.value_cw[level], keys.first.value_cw(level)) << level;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(g.cw_last, keys.first.cw_last());
|
||||||
|
EXPECT_EQ(g.addend0, keys.first.cmp_addend().raw());
|
||||||
|
EXPECT_EQ(g.addend1, keys.second.cmp_addend().raw());
|
||||||
|
EXPECT_EQ((g.addend0 + g.addend1) & g.mask, beta);
|
||||||
|
ASSERT_EQ(g.party0.size(), ends.size());
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < ends.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto e0 = dpf::eval_point(dpf::cmp, keys.first, ends[i]);
|
||||||
|
const auto e1 = dpf::eval_point(dpf::cmp, keys.second, ends[i]);
|
||||||
|
EXPECT_EQ(g.party0[i], e0.raw()) << int(ends[i]);
|
||||||
|
EXPECT_EQ(g.party1[i], e1.raw()) << int(ends[i]);
|
||||||
|
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
|
||||||
|
EXPECT_EQ(opened, ends[i] > alpha ? beta : 0u) << int(ends[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Geneval, CmpSignedPayloadAndDomainMax)
|
||||||
|
{
|
||||||
|
using in_t = int16_t;
|
||||||
|
const in_t alpha = -3;
|
||||||
|
const in_t x0 = 9;
|
||||||
|
const in_t x1 = static_cast<in_t>(alpha ^ x0);
|
||||||
|
const uint64_t beta = 5;
|
||||||
|
const std::vector<in_t> ends{-100, -3, -2, 0, 4, 32767};
|
||||||
|
|
||||||
|
reset_roots();
|
||||||
|
auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng<in_t>(),
|
||||||
|
dpf::gt(beta));
|
||||||
|
EXPECT_EQ(g.live_levels, 16u);
|
||||||
|
EXPECT_EQ(g.value_cw.size(), g.live_levels);
|
||||||
|
for (std::size_t i = 0; i < ends.size(); ++i)
|
||||||
|
{
|
||||||
|
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
|
||||||
|
EXPECT_EQ(opened, ends[i] > alpha ? beta : 0u) << ends[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const in_t top0 = 1;
|
||||||
|
const in_t top = std::numeric_limits<in_t>::max();
|
||||||
|
const in_t top1 = static_cast<in_t>(top ^ top0);
|
||||||
|
const std::vector<in_t> all{std::numeric_limits<in_t>::min(), in_t{0}, top};
|
||||||
|
reset_roots();
|
||||||
|
auto trivial = dpf::geneval_cmp(top0, top1, all.begin(), all.end(), rng<in_t>(),
|
||||||
|
uint64_t{1});
|
||||||
|
for (std::size_t i = 0; i < all.size(); ++i)
|
||||||
|
EXPECT_EQ((trivial.party0[i] + trivial.party1[i]) & trivial.mask, 0u) << all[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Geneval, CmpLtIsTheComplementOfTheStrictUpperSet)
|
||||||
|
{
|
||||||
|
using in_t = uint8_t;
|
||||||
|
const in_t alpha = 10;
|
||||||
|
const in_t x0 = 3;
|
||||||
|
const in_t x1 = static_cast<in_t>(alpha ^ x0);
|
||||||
|
const std::vector<in_t> ends{0, 10, 11, 255};
|
||||||
|
reset_roots();
|
||||||
|
auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng<in_t>(),
|
||||||
|
dpf::lt(uint64_t{4}));
|
||||||
|
for (std::size_t i = 0; i < ends.size(); ++i)
|
||||||
|
{
|
||||||
|
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
|
||||||
|
EXPECT_EQ(opened, ends[i] < alpha ? 4u : 0u) << int(ends[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,9 @@ template <std::size_t Degree, typename T>
|
||||||
uint64_t gold(T center, T eta, const std::vector<T> & knots,
|
uint64_t gold(T center, T eta, const std::vector<T> & knots,
|
||||||
const std::vector<std::array<uint64_t, Degree + 1>> & coeff)
|
const std::vector<std::array<uint64_t, Degree + 1>> & coeff)
|
||||||
{
|
{
|
||||||
const auto & a = coeff_of_wrapped<Degree>(center, eta, knots, coeff);
|
const T wrapped = offset_horner_group_add(center, eta);
|
||||||
return power_sum<Degree>(a, lift(center) + lift(eta));
|
const auto & a = coeff[static_cast<std::size_t>(circular_piece(wrapped, knots))];
|
||||||
|
return power_sum<Degree>(a, lift(wrapped));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::size_t Party, std::size_t Degree, typename T>
|
template <std::size_t Party, std::size_t Degree, typename T>
|
||||||
|
|
@ -202,8 +203,12 @@ TEST(OffsetHorner, HandCubicAtCenterPlusEta)
|
||||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), 740u);
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), 740u);
|
||||||
EXPECT_EQ(grotto::offset_horner_clear<D>(center, knots, coeff, eta), 740u);
|
EXPECT_EQ(grotto::offset_horner_clear<D>(center, knots, coeff, eta), 740u);
|
||||||
const auto got = open_coeffs<D>(mat, knots, coeff, eta);
|
const auto got = open_coeffs<D>(mat, knots, coeff, eta);
|
||||||
const auto want = binomial_shift<D>(coeff[0], lift(center));
|
uint64_t summed = 0;
|
||||||
EXPECT_EQ(got, want);
|
for (uint64_t term : got)
|
||||||
|
summed += term;
|
||||||
|
EXPECT_EQ(summed, 740u);
|
||||||
|
const auto q = grotto::offset_horner_clear_coefficients<D>(center, knots, coeff, eta);
|
||||||
|
EXPECT_EQ(q, binomial_shift<D>(coeff[0], lift(eta)));
|
||||||
// Each party evaluates from its own shares and the public eta.
|
// Each party evaluates from its own shares and the public eta.
|
||||||
const uint64_t p0 = party_eval<0, D>(mat, knots, coeff, eta);
|
const uint64_t p0 = party_eval<0, D>(mat, knots, coeff, eta);
|
||||||
const uint64_t p1 = party_eval<1, D>(mat, knots, coeff, eta);
|
const uint64_t p1 = party_eval<1, D>(mat, knots, coeff, eta);
|
||||||
|
|
@ -228,7 +233,7 @@ TEST(OffsetHorner, MultiPieceSelectsWrappedInput)
|
||||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), want);
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), want);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OffsetHorner, UnreducedSumIsNotTheWrappedRepresentative)
|
TEST(OffsetHorner, CarrySplitEvaluatesTheWrappedRepresentative)
|
||||||
{
|
{
|
||||||
constexpr std::size_t D = 1;
|
constexpr std::size_t D = 1;
|
||||||
const std::vector<uint8_t> knots{0, 30, 80};
|
const std::vector<uint8_t> knots{0, 30, 80};
|
||||||
|
|
@ -245,11 +250,22 @@ TEST(OffsetHorner, UnreducedSumIsNotTheWrappedRepresentative)
|
||||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
||||||
EXPECT_EQ(got, gold<D>(center, eta, knots, coeff));
|
EXPECT_EQ(got, gold<D>(center, eta, knots, coeff));
|
||||||
EXPECT_EQ(got, 600u);
|
EXPECT_EQ(got, 88u);
|
||||||
const int piece = circular_piece(wrapped, knots);
|
EXPECT_NE(got, 600u);
|
||||||
const uint64_t at_wrapped = power_sum<D>(coeff[static_cast<std::size_t>(piece)], lift(wrapped));
|
|
||||||
EXPECT_EQ(at_wrapped, 88u);
|
const std::vector<int8_t> sknots{-128, 0};
|
||||||
EXPECT_NE(got, at_wrapped);
|
const auto scoeff = take_degree<D>(pad3({
|
||||||
|
{0, 3, 0, 0},
|
||||||
|
{5, 0, 0, 0},
|
||||||
|
}));
|
||||||
|
const int8_t sc = 100;
|
||||||
|
const int8_t se = 100;
|
||||||
|
const int8_t sw = offset_horner_group_add(sc, se);
|
||||||
|
EXPECT_EQ(sw, int8_t{-56});
|
||||||
|
auto smat = grotto::make_offset_horner_keys<int8_t, D>(sc);
|
||||||
|
EXPECT_EQ(open_eval<D>(smat, sknots, scoeff, se), gold<D>(sc, se, sknots, scoeff));
|
||||||
|
EXPECT_EQ(open_eval<D>(smat, sknots, scoeff, se),
|
||||||
|
power_sum<D>(scoeff[0], lift(sw)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(OffsetHorner, XPlusRWiring)
|
TEST(OffsetHorner, XPlusRWiring)
|
||||||
|
|
@ -297,8 +313,11 @@ TEST(OffsetHorner, DegreesZeroOneAndTwo)
|
||||||
const auto coeff = take_degree<D>(full);
|
const auto coeff = take_degree<D>(full);
|
||||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff));
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff));
|
||||||
EXPECT_EQ(open_coeffs<D>(mat, knots, coeff, eta),
|
const auto parts = open_coeffs<D>(mat, knots, coeff, eta);
|
||||||
grotto::offset_horner_clear_coefficients<D>(center, knots, coeff, eta));
|
uint64_t summed = 0;
|
||||||
|
for (uint64_t term : parts)
|
||||||
|
summed += term;
|
||||||
|
EXPECT_EQ(summed, open_eval<D>(mat, knots, coeff, eta));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -357,8 +376,11 @@ TEST(OffsetHorner, NegativeCoefficientsAndSignedDomain)
|
||||||
const int8_t eta = -3;
|
const int8_t eta = -3;
|
||||||
auto mat = grotto::make_offset_horner_keys<int8_t, D>(center);
|
auto mat = grotto::make_offset_horner_keys<int8_t, D>(center);
|
||||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff));
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff));
|
||||||
EXPECT_EQ(open_coeffs<D>(mat, knots, coeff, eta),
|
const auto parts = open_coeffs<D>(mat, knots, coeff, eta);
|
||||||
binomial_shift<D>(coeff_of_wrapped<D>(center, eta, knots, coeff), lift(center)));
|
uint64_t summed = 0;
|
||||||
|
for (uint64_t term : parts)
|
||||||
|
summed += term;
|
||||||
|
EXPECT_EQ(summed, open_eval<D>(mat, knots, coeff, eta));
|
||||||
|
|
||||||
auto at_max = grotto::make_offset_horner_keys<int8_t, D>(int8_t{127});
|
auto at_max = grotto::make_offset_horner_keys<int8_t, D>(int8_t{127});
|
||||||
EXPECT_EQ(open_eval<D>(at_max, knots, coeff, int8_t{-4}),
|
EXPECT_EQ(open_eval<D>(at_max, knots, coeff, int8_t{-4}),
|
||||||
|
|
@ -603,8 +625,11 @@ TEST(OffsetHorner, ManyPiecesAndRandomUint16)
|
||||||
auto mat = grotto::make_offset_horner_keys<uint16_t, D>(center);
|
auto mat = grotto::make_offset_horner_keys<uint16_t, D>(center);
|
||||||
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff))
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff))
|
||||||
<< trial;
|
<< trial;
|
||||||
EXPECT_EQ(open_coeffs<D>(mat, knots, coeff, eta),
|
const auto parts = open_coeffs<D>(mat, knots, coeff, eta);
|
||||||
binomial_shift<D>(coeff_of_wrapped<D>(center, eta, knots, coeff), lift(center)));
|
uint64_t summed = 0;
|
||||||
|
for (uint64_t term : parts)
|
||||||
|
summed += term;
|
||||||
|
EXPECT_EQ(summed, gold<D>(center, eta, knots, coeff)) << trial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -631,10 +656,12 @@ TEST(OffsetHorner, ExhaustiveUint8AgreesWithGoldAndCountsWraps)
|
||||||
const auto eta = static_cast<uint8_t>(e);
|
const auto eta = static_cast<uint8_t>(e);
|
||||||
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
||||||
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
||||||
if (got != want)
|
const uint64_t cleared = grotto::offset_horner_clear<D>(center, knots, coeff, eta);
|
||||||
|
if (got != want || cleared != want)
|
||||||
{
|
{
|
||||||
ADD_FAILURE() << "center=" << c << " eta=" << e
|
ADD_FAILURE() << "center=" << c << " eta=" << e
|
||||||
<< " got=" << got << " want=" << want;
|
<< " got=" << got << " clear=" << cleared
|
||||||
|
<< " want=" << want;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
||||||
|
|
@ -678,10 +705,12 @@ TEST(OffsetHorner, ExhaustiveInt8AgreesWithGoldAndCountsOverflows)
|
||||||
const auto eta = static_cast<int8_t>(e);
|
const auto eta = static_cast<int8_t>(e);
|
||||||
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
||||||
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
||||||
if (got != want)
|
const uint64_t cleared = grotto::offset_horner_clear<D>(center, knots, coeff, eta);
|
||||||
|
if (got != want || cleared != want)
|
||||||
{
|
{
|
||||||
ADD_FAILURE() << "center=" << c << " eta=" << e
|
ADD_FAILURE() << "center=" << c << " eta=" << e
|
||||||
<< " got=" << got << " want=" << want;
|
<< " got=" << got << " clear=" << cleared
|
||||||
|
<< " want=" << want;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const int8_t wrapped = offset_horner_group_add(center, eta);
|
const int8_t wrapped = offset_horner_group_add(center, eta);
|
||||||
|
|
@ -698,6 +727,94 @@ TEST(OffsetHorner, ExhaustiveInt8AgreesWithGoldAndCountsOverflows)
|
||||||
EXPECT_EQ(piece_mismatch, 0);
|
EXPECT_EQ(piece_mismatch, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, GenevalXorSharesMatchTheDealerPoint)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
const std::vector<uint8_t> knots{0, 10, 50};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{0, 2, 0, 0},
|
||||||
|
{7, 0, 0, 1},
|
||||||
|
}));
|
||||||
|
const uint8_t center = 12;
|
||||||
|
const uint8_t share = 0x3c;
|
||||||
|
const uint8_t other = static_cast<uint8_t>(center ^ share);
|
||||||
|
const uint8_t eta = 3;
|
||||||
|
EXPECT_EQ(grotto::geneval_offset_horner_center(share, other), center);
|
||||||
|
EXPECT_EQ(grotto::geneval_offset_horner_center(center, uint8_t{0}), center);
|
||||||
|
|
||||||
|
const auto got = grotto::geneval_offset_horner<D>(share, other, eta, knots, coeff);
|
||||||
|
EXPECT_EQ(got.center, center);
|
||||||
|
EXPECT_EQ(got.eta, eta);
|
||||||
|
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||||
|
EXPECT_EQ(got.value0 + got.value1, 30u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, GenevalFromAdditiveSharesOfXAndR)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 2;
|
||||||
|
const std::vector<uint8_t> knots{0, 30, 80};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 2, 0, 0},
|
||||||
|
{9, 0, 0, 0},
|
||||||
|
}));
|
||||||
|
const uint8_t x = 100;
|
||||||
|
const uint8_t r = 200;
|
||||||
|
const uint8_t x0 = 7;
|
||||||
|
const uint8_t r0 = 11;
|
||||||
|
const uint8_t x1 = offset_horner_group_sub(x, x0);
|
||||||
|
const uint8_t r1 = offset_horner_group_sub(r, r0);
|
||||||
|
const auto got = grotto::geneval_offset_horner<D>(x0, x1, r0, r1, knots, coeff);
|
||||||
|
const uint8_t eta = offset_horner_group_sub(x, r);
|
||||||
|
const uint8_t center = offset_horner_group_add(r, r);
|
||||||
|
EXPECT_EQ(got.eta, eta);
|
||||||
|
EXPECT_EQ(got.center, center);
|
||||||
|
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||||
|
EXPECT_EQ(got.value0 + got.value1, 88u);
|
||||||
|
const uint8_t wrapped = offset_horner_group_add(center, eta);
|
||||||
|
EXPECT_EQ(wrapped, 44);
|
||||||
|
EXPECT_EQ(got.value0 + got.value1,
|
||||||
|
power_sum<D>(coeff[static_cast<std::size_t>(circular_piece(wrapped, knots))],
|
||||||
|
lift(wrapped)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, GenevalSignedSharesUseGenevalConvention)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
const std::vector<int8_t> knots{-128, -40, 0, 20, 100};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{uint64_t(-3), 4, 0, 1},
|
||||||
|
{0, uint64_t(-1), 2, 0},
|
||||||
|
{1, 1, 1, 1},
|
||||||
|
{uint64_t(-5), uint64_t(-5), 0, 0},
|
||||||
|
{2, 0, uint64_t(-1), 0},
|
||||||
|
}));
|
||||||
|
const int8_t center = -20;
|
||||||
|
const int8_t share = 3;
|
||||||
|
const int8_t other = static_cast<int8_t>(center ^ share);
|
||||||
|
const int8_t eta = -3;
|
||||||
|
EXPECT_EQ(grotto::geneval_offset_horner_center(share, other), center);
|
||||||
|
const auto got = grotto::geneval_offset_horner<D>(share, other, eta, knots, coeff);
|
||||||
|
EXPECT_EQ(got.center, center);
|
||||||
|
EXPECT_EQ(got.value0 + got.value1, gold<D>(center, eta, knots, coeff));
|
||||||
|
|
||||||
|
const int8_t x = 40;
|
||||||
|
const int8_t r = -15;
|
||||||
|
const int8_t x0 = -100;
|
||||||
|
const int8_t r0 = 50;
|
||||||
|
const auto from_mask = grotto::geneval_offset_horner<D>(
|
||||||
|
x0, offset_horner_group_sub(x, x0),
|
||||||
|
r0, offset_horner_group_sub(r, r0),
|
||||||
|
knots, coeff);
|
||||||
|
const int8_t expect_center = offset_horner_group_add(r, r);
|
||||||
|
const int8_t expect_eta = offset_horner_group_sub(x, r);
|
||||||
|
EXPECT_EQ(from_mask.center, expect_center);
|
||||||
|
EXPECT_EQ(from_mask.eta, expect_eta);
|
||||||
|
EXPECT_EQ(from_mask.value0 + from_mask.value1,
|
||||||
|
gold<D>(expect_center, expect_eta, knots, coeff));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(OffsetHorner, HornerOfOpenedCoefficientsMatchesValue)
|
TEST(OffsetHorner, HornerOfOpenedCoefficientsMatchesValue)
|
||||||
{
|
{
|
||||||
constexpr std::size_t D = 3;
|
constexpr std::size_t D = 3;
|
||||||
|
|
@ -712,13 +829,574 @@ TEST(OffsetHorner, HornerOfOpenedCoefficientsMatchesValue)
|
||||||
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
const auto c = open_coeffs<D>(mat, knots, coeff, eta);
|
const auto c = open_coeffs<D>(mat, knots, coeff, eta);
|
||||||
uint64_t y = 0;
|
uint64_t y = 0;
|
||||||
uint64_t p = 1;
|
|
||||||
const uint64_t e = lift(eta);
|
|
||||||
for (uint64_t ck : c)
|
for (uint64_t ck : c)
|
||||||
{
|
y += ck;
|
||||||
y += ck * p;
|
|
||||||
p *= e;
|
|
||||||
}
|
|
||||||
EXPECT_EQ(y, open_eval<D>(mat, knots, coeff, eta));
|
EXPECT_EQ(y, open_eval<D>(mat, knots, coeff, eta));
|
||||||
EXPECT_EQ(y, gold<D>(center, eta, knots, coeff));
|
EXPECT_EQ(y, gold<D>(center, eta, knots, coeff));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <std::size_t Degree, typename T>
|
||||||
|
void expect_wrapped(const grotto::offset_horner_keys<T, Degree> & mat,
|
||||||
|
const std::vector<T> & knots,
|
||||||
|
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
|
||||||
|
T center, T eta, const char * where)
|
||||||
|
{
|
||||||
|
const uint64_t want = gold<Degree>(center, eta, knots, coeff);
|
||||||
|
const uint64_t got = open_eval<Degree>(mat, knots, coeff, eta);
|
||||||
|
const uint64_t cleared = grotto::offset_horner_clear<Degree>(center, knots, coeff, eta);
|
||||||
|
const auto q = grotto::offset_horner_clear_coefficients<Degree>(center, knots, coeff, eta);
|
||||||
|
uint64_t horner = q[Degree];
|
||||||
|
const uint64_t limb = lift(center);
|
||||||
|
for (std::size_t k = Degree; k-- > 0; )
|
||||||
|
horner = horner * limb + q[k];
|
||||||
|
EXPECT_EQ(got, want) << where;
|
||||||
|
EXPECT_EQ(cleared, want) << where;
|
||||||
|
EXPECT_EQ(horner, want) << where;
|
||||||
|
if (got != want || cleared != want || horner != want)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t Degree, typename T>
|
||||||
|
void expect_geneval(T center, T eta, const std::vector<T> & knots,
|
||||||
|
const std::vector<std::array<uint64_t, Degree + 1>> & coeff, const char * where)
|
||||||
|
{
|
||||||
|
const T share = static_cast<T>(0x3c);
|
||||||
|
const T other = static_cast<T>(center ^ share);
|
||||||
|
const auto g = grotto::geneval_offset_horner<Degree>(share, other, eta, knots, coeff);
|
||||||
|
const uint64_t want = gold<Degree>(center, eta, knots, coeff);
|
||||||
|
EXPECT_EQ(g.center, center) << where;
|
||||||
|
EXPECT_EQ(g.value0 + g.value1, want) << where;
|
||||||
|
uint64_t summed = 0;
|
||||||
|
for (std::size_t k = 0; k <= Degree; ++k)
|
||||||
|
summed += g.coeff0[k] + g.coeff1[k];
|
||||||
|
EXPECT_EQ(summed, want) << where;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, KnotsThatOmitZeroStillSplitTheCarry)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
const std::vector<uint8_t> knots{40, 90, 150, 220};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{1, 0, 0, 1},
|
||||||
|
{0, uint64_t(-3), 1, 0},
|
||||||
|
{4, 2, 0, uint64_t(-1)},
|
||||||
|
{9, 0, 2, 1},
|
||||||
|
}));
|
||||||
|
for (int c = 0; c < 256; ++c)
|
||||||
|
{
|
||||||
|
const auto center = static_cast<uint8_t>(c);
|
||||||
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
|
for (int e = 0; e < 256; e += 1)
|
||||||
|
{
|
||||||
|
const auto eta = static_cast<uint8_t>(e);
|
||||||
|
expect_wrapped<D>(mat, knots, coeff, center, eta, "omit-zero");
|
||||||
|
if (HasFailure())
|
||||||
|
{
|
||||||
|
ADD_FAILURE() << "center=" << c << " eta=" << e;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, SignedKnotsThatOmitTheMinimum)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
const std::vector<int8_t> knots{-40, 10, 70};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{uint64_t(-2), 1, 0, 1},
|
||||||
|
{3, 0, uint64_t(-1), 0},
|
||||||
|
{0, 4, 2, uint64_t(-3)},
|
||||||
|
}));
|
||||||
|
for (int c = -128; c <= 127; ++c)
|
||||||
|
{
|
||||||
|
const auto center = static_cast<int8_t>(c);
|
||||||
|
auto mat = grotto::make_offset_horner_keys<int8_t, D>(center);
|
||||||
|
for (int e = -128; e <= 127; ++e)
|
||||||
|
{
|
||||||
|
const auto eta = static_cast<int8_t>(e);
|
||||||
|
expect_wrapped<D>(mat, knots, coeff, center, eta, "omit-min");
|
||||||
|
if (HasFailure())
|
||||||
|
{
|
||||||
|
ADD_FAILURE() << "center=" << c << " eta=" << e;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, CarryThresholdLandsOnEveryKnotAndOnTheDomainEnds)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 2;
|
||||||
|
const std::vector<uint8_t> knots{1, 16, 64, 128, 200, 255};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{1, 1, 0, 0},
|
||||||
|
{2, 0, 1, 0},
|
||||||
|
{3, uint64_t(-1), 0, 0},
|
||||||
|
{4, 2, 2, 0},
|
||||||
|
{5, 0, 0, 0},
|
||||||
|
{6, 3, 1, 0},
|
||||||
|
}));
|
||||||
|
for (uint8_t knot : knots)
|
||||||
|
{
|
||||||
|
if (knot == 0)
|
||||||
|
continue;
|
||||||
|
const uint8_t eta = static_cast<uint8_t>(256u - knot);
|
||||||
|
for (int c = 0; c < 256; ++c)
|
||||||
|
{
|
||||||
|
const auto center = static_cast<uint8_t>(c);
|
||||||
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
|
expect_wrapped<D>(mat, knots, coeff, center, eta, "threshold-on-knot");
|
||||||
|
if (HasFailure())
|
||||||
|
{
|
||||||
|
ADD_FAILURE() << "knot=" << int(knot) << " center=" << c;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint8_t eta : {uint8_t{0}, uint8_t{1}, uint8_t{255}})
|
||||||
|
{
|
||||||
|
for (uint8_t center : {uint8_t{0}, uint8_t{1}, uint8_t{254}, uint8_t{255}})
|
||||||
|
{
|
||||||
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
|
expect_wrapped<D>(mat, knots, coeff, center, eta, "domain-end");
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, DegreeZeroIsThePieceConstantOnBothSidesOfTheCarry)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 0;
|
||||||
|
const std::vector<uint8_t> knots{10, 80, 200};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{4, 0, 0, 0},
|
||||||
|
{11, 0, 0, 0},
|
||||||
|
{uint64_t(-2), 0, 0, 0},
|
||||||
|
}));
|
||||||
|
for (int c = 0; c < 256; c += 3)
|
||||||
|
{
|
||||||
|
const auto center = static_cast<uint8_t>(c);
|
||||||
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
|
for (int e = 0; e < 256; e += 5)
|
||||||
|
{
|
||||||
|
const auto eta = static_cast<uint8_t>(e);
|
||||||
|
const uint64_t want = gold<D>(center, eta, knots, coeff);
|
||||||
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), want);
|
||||||
|
const auto wrapped = offset_horner_group_add(center, eta);
|
||||||
|
const auto piece = static_cast<std::size_t>(circular_piece(wrapped, knots));
|
||||||
|
EXPECT_EQ(want, coeff[piece][0]);
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, CubicAcrossUnsignedAndSignedCarryHasANegativeKappa)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
const std::vector<uint8_t> uknots{1, 70};
|
||||||
|
const auto ucoeff = take_degree<D>(pad3({
|
||||||
|
{1, 0, 0, 1},
|
||||||
|
{2, 3, uint64_t(-1), 1},
|
||||||
|
}));
|
||||||
|
const uint8_t uc = 200;
|
||||||
|
const uint8_t ue = 100;
|
||||||
|
const uint8_t uw = offset_horner_group_add(uc, ue);
|
||||||
|
EXPECT_EQ(uw, 44);
|
||||||
|
EXPECT_NE(lift(uc) + lift(ue), lift(uw));
|
||||||
|
auto umat = grotto::make_offset_horner_keys<uint8_t, D>(uc);
|
||||||
|
expect_wrapped<D>(umat, uknots, ucoeff, uc, ue, "cubic-unsigned");
|
||||||
|
EXPECT_NE(open_eval<D>(umat, uknots, ucoeff, ue),
|
||||||
|
power_sum<D>(ucoeff[static_cast<std::size_t>(circular_piece(uw, uknots))],
|
||||||
|
lift(uc) + lift(ue)));
|
||||||
|
|
||||||
|
const std::vector<int8_t> sknots{-20, 30};
|
||||||
|
const auto scoeff = take_degree<D>(pad3({
|
||||||
|
{0, 0, 0, 1},
|
||||||
|
{7, 1, 0, 0},
|
||||||
|
}));
|
||||||
|
const int8_t sc = -100;
|
||||||
|
const int8_t se = -80;
|
||||||
|
const int8_t sw = offset_horner_group_add(sc, se);
|
||||||
|
EXPECT_LT(int(sc) + int(se), -128);
|
||||||
|
auto smat = grotto::make_offset_horner_keys<int8_t, D>(sc);
|
||||||
|
expect_wrapped<D>(smat, sknots, scoeff, sc, se, "cubic-signed-low");
|
||||||
|
EXPECT_EQ(open_eval<D>(smat, sknots, scoeff, se),
|
||||||
|
power_sum<D>(scoeff[static_cast<std::size_t>(circular_piece(sw, sknots))], lift(sw)));
|
||||||
|
|
||||||
|
const int8_t hc = 90;
|
||||||
|
const int8_t he = 80;
|
||||||
|
auto hmat = grotto::make_offset_horner_keys<int8_t, D>(hc);
|
||||||
|
expect_wrapped<D>(hmat, sknots, scoeff, hc, he, "cubic-signed-high");
|
||||||
|
const int8_t hw = offset_horner_group_add(hc, he);
|
||||||
|
EXPECT_GT(int(hc) + int(he), 127);
|
||||||
|
EXPECT_EQ(open_eval<D>(hmat, sknots, scoeff, he),
|
||||||
|
power_sum<D>(scoeff[static_cast<std::size_t>(circular_piece(hw, sknots))], lift(hw)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, ZeroPolynomialAndProperShares)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
const std::vector<uint8_t> knots{5, 40, 90};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
}));
|
||||||
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(uint8_t{200});
|
||||||
|
EXPECT_EQ(open_eval<D>(mat, knots, coeff, uint8_t{200}), 0u);
|
||||||
|
EXPECT_EQ(grotto::offset_horner_clear<D>(uint8_t{200}, knots, coeff, uint8_t{200}), 0u);
|
||||||
|
|
||||||
|
const auto live = take_degree<D>(pad3({
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{5, 6, 7, 8},
|
||||||
|
{9, 8, 7, 6},
|
||||||
|
}));
|
||||||
|
const uint64_t p0 = party_eval<0, D>(mat, knots, live, uint8_t{180});
|
||||||
|
const uint64_t p1 = party_eval<1, D>(mat, knots, live, uint8_t{180});
|
||||||
|
const uint64_t want = gold<D>(uint8_t{200}, uint8_t{180}, knots, live);
|
||||||
|
EXPECT_EQ(p0 + p1, want);
|
||||||
|
EXPECT_NE(p0, want);
|
||||||
|
EXPECT_NE(p1, want);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, XPlusRMatchesTheWrappedSumOnAStride)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
const std::vector<uint8_t> knots{7, 60, 130, 210};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{1, 1, 0, 1},
|
||||||
|
{0, uint64_t(-4), 2, 0},
|
||||||
|
{3, 0, 0, uint64_t(-1)},
|
||||||
|
{8, 2, 1, 0},
|
||||||
|
}));
|
||||||
|
for (int rv = 0; rv < 256; rv += 5)
|
||||||
|
{
|
||||||
|
const auto r = static_cast<uint8_t>(rv);
|
||||||
|
const auto center = offset_horner_group_add(r, r);
|
||||||
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
|
for (int xv = 0; xv < 256; xv += 5)
|
||||||
|
{
|
||||||
|
const auto x = static_cast<uint8_t>(xv);
|
||||||
|
const auto eta = offset_horner_group_sub(x, r);
|
||||||
|
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
|
||||||
|
const auto sum = offset_horner_group_add(x, r);
|
||||||
|
EXPECT_EQ(offset_horner_group_add(center, eta), sum);
|
||||||
|
EXPECT_EQ(got, gold<D>(center, eta, knots, coeff));
|
||||||
|
EXPECT_EQ(got, power_sum<D>(
|
||||||
|
coeff[static_cast<std::size_t>(circular_piece(sum, knots))], lift(sum)));
|
||||||
|
if (HasFailure())
|
||||||
|
{
|
||||||
|
ADD_FAILURE() << "x=" << xv << " r=" << rv;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, GenevalAgreesWithDealerAcrossCarryAndEdges)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
const std::vector<uint8_t> knots{25, 80, 140, 200};
|
||||||
|
const auto coeff = take_degree<D>(pad3({
|
||||||
|
{1, 0, 2, 1},
|
||||||
|
{uint64_t(-5), 3, 0, 1},
|
||||||
|
{4, 0, uint64_t(-2), 0},
|
||||||
|
{0, 1, 1, uint64_t(-1)},
|
||||||
|
}));
|
||||||
|
auto check = [&](uint8_t center, uint8_t eta) {
|
||||||
|
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
|
||||||
|
expect_wrapped<D>(mat, knots, coeff, center, eta, "dealer");
|
||||||
|
expect_geneval<D>(center, eta, knots, coeff, "geneval");
|
||||||
|
};
|
||||||
|
for (int c = 0; c < 256; c += 8)
|
||||||
|
{
|
||||||
|
for (int e = 0; e < 256; e += 8)
|
||||||
|
{
|
||||||
|
check(static_cast<uint8_t>(c), static_cast<uint8_t>(e));
|
||||||
|
if (HasFailure())
|
||||||
|
{
|
||||||
|
ADD_FAILURE() << "center=" << c << " eta=" << e;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint8_t end : {uint8_t{0}, uint8_t{1}, uint8_t{127}, uint8_t{128}, uint8_t{254}, uint8_t{255}})
|
||||||
|
{
|
||||||
|
check(end, uint8_t{1});
|
||||||
|
check(end, uint8_t{255});
|
||||||
|
check(uint8_t{200}, end);
|
||||||
|
check(uint8_t{3}, end);
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<int8_t> sknots{-100, -5, 20, 90};
|
||||||
|
const auto scoeff = take_degree<D>(pad3({
|
||||||
|
{1, 0, 0, 1},
|
||||||
|
{0, uint64_t(-1), 2, 0},
|
||||||
|
{4, 3, 0, uint64_t(-2)},
|
||||||
|
{9, 0, 1, 1},
|
||||||
|
}));
|
||||||
|
for (int c = -128; c <= 127; c += 9)
|
||||||
|
{
|
||||||
|
for (int e = -128; e <= 127; e += 9)
|
||||||
|
{
|
||||||
|
const auto center = static_cast<int8_t>(c);
|
||||||
|
const auto eta = static_cast<int8_t>(e);
|
||||||
|
auto mat = grotto::make_offset_horner_keys<int8_t, D>(center);
|
||||||
|
expect_wrapped<D>(mat, sknots, scoeff, center, eta, "signed-dealer");
|
||||||
|
expect_geneval<D>(center, eta, sknots, scoeff, "signed-geneval");
|
||||||
|
if (HasFailure())
|
||||||
|
{
|
||||||
|
ADD_FAILURE() << "center=" << c << " eta=" << e;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, WiderRandomDomainsMatchWrappedGold)
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
std::mt19937 rng(0x0c0ffe);
|
||||||
|
std::uniform_int_distribution<int> u16(0, 65535);
|
||||||
|
std::vector<uint16_t> uknots{0, 1000, 8000, 20000, 40000, 60000};
|
||||||
|
std::vector<std::array<uint64_t, D + 1>> ucoeff(uknots.size());
|
||||||
|
for (auto & row : ucoeff)
|
||||||
|
for (uint64_t & a : row)
|
||||||
|
a = rng();
|
||||||
|
for (int trial = 0; trial < 40; ++trial)
|
||||||
|
{
|
||||||
|
const auto center = static_cast<uint16_t>(u16(rng));
|
||||||
|
const auto eta = static_cast<uint16_t>(u16(rng));
|
||||||
|
auto mat = grotto::make_offset_horner_keys<uint16_t, D>(center);
|
||||||
|
expect_wrapped<D>(mat, uknots, ucoeff, center, eta, "u16");
|
||||||
|
expect_geneval<D>(center, eta, uknots, ucoeff, "u16-geneval");
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uniform_int_distribution<int> s16(-32768, 32767);
|
||||||
|
std::vector<int16_t> sknots{-32768, -20000, -100, 0, 5000, 30000};
|
||||||
|
std::vector<std::array<uint64_t, D + 1>> scoeff(sknots.size());
|
||||||
|
for (auto & row : scoeff)
|
||||||
|
for (uint64_t & a : row)
|
||||||
|
a = rng();
|
||||||
|
for (int trial = 0; trial < 40; ++trial)
|
||||||
|
{
|
||||||
|
const auto center = static_cast<int16_t>(s16(rng));
|
||||||
|
const auto eta = static_cast<int16_t>(s16(rng));
|
||||||
|
auto mat = grotto::make_offset_horner_keys<int16_t, D>(center);
|
||||||
|
expect_wrapped<D>(mat, sknots, scoeff, center, eta, "i16");
|
||||||
|
expect_geneval<D>(center, eta, sknots, scoeff, "i16-geneval");
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
int64_t math_of(T value)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_signed_v<T>)
|
||||||
|
return static_cast<int64_t>(value);
|
||||||
|
else
|
||||||
|
return static_cast<int64_t>(static_cast<std::make_unsigned_t<T>>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool fits_in_domain(int64_t value)
|
||||||
|
{
|
||||||
|
return value >= math_of(std::numeric_limits<T>::min())
|
||||||
|
&& value <= math_of(std::numeric_limits<T>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool addition_leaves_domain(T center, T eta)
|
||||||
|
{
|
||||||
|
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
|
||||||
|
if (bits > 62)
|
||||||
|
return false;
|
||||||
|
const int64_t sum = math_of(center) + math_of(eta);
|
||||||
|
const int64_t mod = int64_t{1} << bits;
|
||||||
|
if constexpr (std::is_signed_v<T>)
|
||||||
|
return sum >= (mod >> 1) || sum < -(mod >> 1);
|
||||||
|
else
|
||||||
|
return sum >= mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void exercise_big_domain()
|
||||||
|
{
|
||||||
|
constexpr std::size_t D = 3;
|
||||||
|
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
|
||||||
|
using lim = std::numeric_limits<T>;
|
||||||
|
const T minv = lim::min();
|
||||||
|
const T maxv = lim::max();
|
||||||
|
|
||||||
|
std::vector<T> knots;
|
||||||
|
if constexpr (std::is_signed_v<T>)
|
||||||
|
{
|
||||||
|
knots.push_back(static_cast<T>(minv / 2));
|
||||||
|
knots.push_back(T{-2});
|
||||||
|
knots.push_back(T{-1});
|
||||||
|
knots.push_back(T{1});
|
||||||
|
knots.push_back(T{2});
|
||||||
|
knots.push_back(static_cast<T>(maxv / 2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using u = std::make_unsigned_t<T>;
|
||||||
|
knots.push_back(T{1});
|
||||||
|
knots.push_back(T{2});
|
||||||
|
knots.push_back(static_cast<T>(u{1} << (bits / 2)));
|
||||||
|
if (bits > 1 && bits <= 63)
|
||||||
|
knots.push_back(static_cast<T>(u{1} << (bits - 1)));
|
||||||
|
knots.push_back(static_cast<T>(maxv - 2));
|
||||||
|
knots.push_back(static_cast<T>(maxv - 1));
|
||||||
|
}
|
||||||
|
std::sort(knots.begin(), knots.end());
|
||||||
|
knots.erase(std::unique(knots.begin(), knots.end()), knots.end());
|
||||||
|
ASSERT_GE(knots.size(), 4u);
|
||||||
|
ASSERT_NE(knots.front(), minv);
|
||||||
|
|
||||||
|
std::vector<std::array<uint64_t, D + 1>> coeff(knots.size());
|
||||||
|
for (std::size_t i = 0; i < knots.size(); ++i)
|
||||||
|
{
|
||||||
|
coeff[i] = {
|
||||||
|
static_cast<uint64_t>(i + 1),
|
||||||
|
static_cast<uint64_t>(-static_cast<int>(i) - 3),
|
||||||
|
static_cast<uint64_t>(i * 5 + 1),
|
||||||
|
uint64_t{1} << (8 + (i % 4)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<T> points;
|
||||||
|
auto add_point = [&](T value) { points.push_back(value); };
|
||||||
|
add_point(minv);
|
||||||
|
add_point(static_cast<T>(minv + T{1}));
|
||||||
|
if constexpr (std::is_signed_v<T>)
|
||||||
|
{
|
||||||
|
add_point(T{-1});
|
||||||
|
add_point(T{0});
|
||||||
|
add_point(T{1});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add_point(T{0});
|
||||||
|
}
|
||||||
|
add_point(static_cast<T>(maxv - T{1}));
|
||||||
|
add_point(maxv);
|
||||||
|
for (T knot : knots)
|
||||||
|
{
|
||||||
|
add_point(knot);
|
||||||
|
if (knot != minv)
|
||||||
|
add_point(static_cast<T>(knot - T{1}));
|
||||||
|
if (knot != maxv)
|
||||||
|
add_point(static_cast<T>(knot + T{1}));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mt19937 rng(0xB16Du ^ bits ^ (std::is_signed_v<T> ? 0x51u : 0u));
|
||||||
|
std::uniform_int_distribution<uint64_t> dist(
|
||||||
|
0, std::numeric_limits<std::make_unsigned_t<T>>::max());
|
||||||
|
for (int n = 0; n < 24; ++n)
|
||||||
|
add_point(static_cast<T>(dist(rng)));
|
||||||
|
|
||||||
|
std::vector<T> etas = points;
|
||||||
|
if (bits <= 62)
|
||||||
|
{
|
||||||
|
const int64_t mod = int64_t{1} << bits;
|
||||||
|
const int64_t half = mod >> 1;
|
||||||
|
for (T knot : knots)
|
||||||
|
{
|
||||||
|
const int64_t k = math_of(knot);
|
||||||
|
if constexpr (std::is_signed_v<T>)
|
||||||
|
{
|
||||||
|
if (fits_in_domain<T>(half - k))
|
||||||
|
etas.push_back(static_cast<T>(half - k));
|
||||||
|
if (fits_in_domain<T>(-half - k))
|
||||||
|
etas.push_back(static_cast<T>(-half - k));
|
||||||
|
}
|
||||||
|
else if (k != 0 && fits_in_domain<T>(mod - k))
|
||||||
|
{
|
||||||
|
etas.push_back(static_cast<T>(mod - k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(etas.begin(), etas.end());
|
||||||
|
etas.erase(std::unique(etas.begin(), etas.end()), etas.end());
|
||||||
|
std::sort(points.begin(), points.end());
|
||||||
|
points.erase(std::unique(points.begin(), points.end()), points.end());
|
||||||
|
|
||||||
|
int wraps = 0;
|
||||||
|
for (T center : points)
|
||||||
|
{
|
||||||
|
auto mat = grotto::make_offset_horner_keys<T, D>(center);
|
||||||
|
for (T eta : etas)
|
||||||
|
{
|
||||||
|
if (addition_leaves_domain(center, eta))
|
||||||
|
++wraps;
|
||||||
|
expect_wrapped<D>(mat, knots, coeff, center, eta, "big-dealer");
|
||||||
|
expect_geneval<D>(center, eta, knots, coeff, "big-geneval");
|
||||||
|
if (::testing::Test::HasFailure())
|
||||||
|
{
|
||||||
|
if constexpr (std::is_signed_v<T>)
|
||||||
|
ADD_FAILURE() << "signed " << bits << " center=" << static_cast<long long>(center)
|
||||||
|
<< " eta=" << static_cast<long long>(eta);
|
||||||
|
else
|
||||||
|
ADD_FAILURE() << "unsigned " << bits << " center=" << static_cast<unsigned long long>(center)
|
||||||
|
<< " eta=" << static_cast<unsigned long long>(eta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bits <= 62)
|
||||||
|
EXPECT_GT(wraps, 0) << (std::is_signed_v<T> ? "signed " : "unsigned ") << bits;
|
||||||
|
|
||||||
|
const std::vector<std::array<uint64_t, 1>> constants(knots.size(), {uint64_t{42}});
|
||||||
|
const T const_center = points.back();
|
||||||
|
const T const_eta = etas.front();
|
||||||
|
auto const_keys = grotto::make_offset_horner_keys<T, 0>(const_center);
|
||||||
|
EXPECT_EQ(open_eval<0>(const_keys, knots, constants, const_eta),
|
||||||
|
gold<0>(const_center, const_eta, knots, constants));
|
||||||
|
|
||||||
|
for (int n = 0; n < 8; ++n)
|
||||||
|
{
|
||||||
|
const T x = static_cast<T>(dist(rng));
|
||||||
|
const T r = static_cast<T>(dist(rng));
|
||||||
|
const T x0 = static_cast<T>(dist(rng));
|
||||||
|
const T r0 = static_cast<T>(dist(rng));
|
||||||
|
const T x1 = offset_horner_group_sub(x, x0);
|
||||||
|
const T r1 = offset_horner_group_sub(r, r0);
|
||||||
|
const auto got = grotto::geneval_offset_horner<D>(x0, x1, r0, r1, knots, coeff);
|
||||||
|
const T sum = offset_horner_group_add(x, r);
|
||||||
|
EXPECT_EQ(got.center, offset_horner_group_add(r, r));
|
||||||
|
EXPECT_EQ(got.eta, offset_horner_group_sub(x, r));
|
||||||
|
EXPECT_EQ(offset_horner_group_add(got.center, got.eta), sum);
|
||||||
|
EXPECT_EQ(got.value0 + got.value1, gold<D>(got.center, got.eta, knots, coeff));
|
||||||
|
if (::testing::Test::HasFailure())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(OffsetHorner, BiggerDomainsExerciseCarrySplitAndGeneval)
|
||||||
|
{
|
||||||
|
exercise_big_domain<uint16_t>();
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
exercise_big_domain<int16_t>();
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
exercise_big_domain<uint32_t>();
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
exercise_big_domain<int32_t>();
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
exercise_big_domain<uint64_t>();
|
||||||
|
if (HasFailure())
|
||||||
|
return;
|
||||||
|
exercise_big_domain<int64_t>();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,3 +169,84 @@ TEST(PrincipalLut, RejectsBadPrecisionAndDomain)
|
||||||
EXPECT_THROW(grotto::eval_principal(grotto::principal::exp, 8, -1), std::out_of_range);
|
EXPECT_THROW(grotto::eval_principal(grotto::principal::exp, 8, -1), std::out_of_range);
|
||||||
EXPECT_THROW(grotto::eval_principal(grotto::principal::sin, 8, 257), std::out_of_range);
|
EXPECT_THROW(grotto::eval_principal(grotto::principal::sin, 8, 257), std::out_of_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long double recip_reference(grotto::principal which, long double x)
|
||||||
|
{
|
||||||
|
switch (which)
|
||||||
|
{
|
||||||
|
case grotto::principal::inv:
|
||||||
|
return 1.0L / x;
|
||||||
|
case grotto::principal::rsqrt:
|
||||||
|
return 1.0L / sqrtl(x);
|
||||||
|
case grotto::principal::invsq:
|
||||||
|
return 1.0L / (x * x);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void expect_recip(grotto::principal which, unsigned k, std::int64_t raw)
|
||||||
|
{
|
||||||
|
const auto y = grotto::eval_principal(which, k, raw);
|
||||||
|
const long double x = ldexpl(static_cast<long double>(raw), -static_cast<int>(k));
|
||||||
|
const long double truth = recip_reference(which, x) * ldexpl(1.0L, static_cast<int>(k));
|
||||||
|
EXPECT_LE(fabsl(static_cast<long double>(y) - truth), 1.5L)
|
||||||
|
<< static_cast<int>(which) << " k=" << k << " raw=" << raw << " y=" << y;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PrincipalLut, ReciprocalPieceCounts)
|
||||||
|
{
|
||||||
|
const unsigned inv[] = {2u, 3u, 5u, 9u, 18u, 35u, 69u};
|
||||||
|
const unsigned rsqrt[] = {1u, 2u, 3u, 6u, 12u, 24u, 48u};
|
||||||
|
const unsigned invsq[] = {2u, 4u, 8u, 15u, 29u, 57u, 113u};
|
||||||
|
unsigned slot = 0;
|
||||||
|
for (unsigned k : grotto::principal_precisions)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(grotto::principal_parts(grotto::principal::inv, k), inv[slot]);
|
||||||
|
EXPECT_EQ(grotto::principal_parts(grotto::principal::rsqrt, k), rsqrt[slot]);
|
||||||
|
EXPECT_EQ(grotto::principal_parts(grotto::principal::invsq, k), invsq[slot]);
|
||||||
|
++slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PrincipalLut, ReciprocalWithinOneAndAHalfUlp)
|
||||||
|
{
|
||||||
|
const grotto::principal maps[] = {
|
||||||
|
grotto::principal::inv,
|
||||||
|
grotto::principal::rsqrt,
|
||||||
|
grotto::principal::invsq,
|
||||||
|
};
|
||||||
|
for (const auto which : maps)
|
||||||
|
{
|
||||||
|
for (unsigned k : {8u, 12u, 16u})
|
||||||
|
{
|
||||||
|
const auto left = std::int64_t{1} << (k - 1);
|
||||||
|
const auto right = std::int64_t{1} << k;
|
||||||
|
for (std::int64_t raw = left; raw <= right; ++raw)
|
||||||
|
expect_recip(which, k, raw);
|
||||||
|
}
|
||||||
|
for (unsigned k : {20u, 24u, 28u, 32u})
|
||||||
|
{
|
||||||
|
const auto left = std::int64_t{1} << (k - 1);
|
||||||
|
const auto right = std::int64_t{1} << k;
|
||||||
|
const std::int64_t step = std::max<std::int64_t>(1, (right - left) / 4096);
|
||||||
|
expect_recip(which, k, left);
|
||||||
|
expect_recip(which, k, right);
|
||||||
|
for (std::int64_t raw = left; raw < right; raw += step)
|
||||||
|
expect_recip(which, k, raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(PrincipalLut, ReciprocalRejectsOutsidePrincipalInterval)
|
||||||
|
{
|
||||||
|
EXPECT_THROW(grotto::eval_principal(grotto::principal::inv, 7, 128), std::invalid_argument);
|
||||||
|
EXPECT_THROW(grotto::eval_principal(grotto::principal::inv, 8, 127), std::out_of_range);
|
||||||
|
EXPECT_THROW(grotto::eval_principal(grotto::principal::inv, 8, 257), std::out_of_range);
|
||||||
|
EXPECT_THROW(grotto::eval_principal(grotto::principal::rsqrt, 12, 2047), std::out_of_range);
|
||||||
|
EXPECT_THROW(grotto::eval_principal(grotto::principal::invsq, 16, (std::int64_t{1} << 16) + 1), std::out_of_range);
|
||||||
|
EXPECT_THROW(grotto::eval_principal(grotto::principal::invsq, 8, -1), std::out_of_range);
|
||||||
|
EXPECT_NO_THROW(grotto::eval_principal(grotto::principal::inv, 8, 128));
|
||||||
|
EXPECT_NO_THROW(grotto::eval_principal(grotto::principal::rsqrt, 8, 256));
|
||||||
|
EXPECT_NO_THROW(grotto::eval_principal(grotto::principal::invsq, 12, 4096));
|
||||||
|
}
|
||||||
|
|
|
||||||
1981
test/tests/window_accuracy.inc
Normal file
1981
test/tests/window_accuracy.inc
Normal file
File diff suppressed because it is too large
Load diff
238
test/tests/window_samples.inc
Normal file
238
test/tests/window_samples.inc
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
{1, 8, -1792, 0},
|
||||||
|
{1, 8, 0, 128},
|
||||||
|
{1, 8, 1792, 256},
|
||||||
|
{1, 12, -40960, 0},
|
||||||
|
{1, 12, 0, 2048},
|
||||||
|
{1, 12, 40960, 4096},
|
||||||
|
{1, 16, -786432, 0},
|
||||||
|
{1, 16, 0, 32768},
|
||||||
|
{1, 16, 786432, 65536},
|
||||||
|
{1, 20, -15728640, 0},
|
||||||
|
{1, 20, 0, 524288},
|
||||||
|
{1, 20, 15728640, 1048576},
|
||||||
|
{1, 24, -301989888, 0},
|
||||||
|
{1, 24, 0, 8388608},
|
||||||
|
{1, 24, 301989888, 16777216},
|
||||||
|
{1, 28, -5637144576, 0},
|
||||||
|
{1, 28, 0, 134217728},
|
||||||
|
{1, 28, 5637144576, 268435456},
|
||||||
|
{1, 32, -98784247808, 0},
|
||||||
|
{1, 32, 0, 2147483648},
|
||||||
|
{1, 32, 98784247808, 4294967296},
|
||||||
|
{2, 8, -1024, -256},
|
||||||
|
{2, 8, 0, 0},
|
||||||
|
{2, 8, 1024, 256},
|
||||||
|
{2, 12, -20480, -4096},
|
||||||
|
{2, 12, 0, 0},
|
||||||
|
{2, 12, 20480, 4096},
|
||||||
|
{2, 16, -458752, -65536},
|
||||||
|
{2, 16, 0, 0},
|
||||||
|
{2, 16, 458752, 65536},
|
||||||
|
{2, 20, -8388608, -1048576},
|
||||||
|
{2, 20, 0, 0},
|
||||||
|
{2, 20, 8388608, 1048576},
|
||||||
|
{2, 24, -167772160, -16777216},
|
||||||
|
{2, 24, 0, 0},
|
||||||
|
{2, 24, 167772160, 16777216},
|
||||||
|
{2, 28, -2952790016, -268435456},
|
||||||
|
{2, 28, 0, 0},
|
||||||
|
{2, 28, 2952790016, 268435456},
|
||||||
|
{2, 32, -51539607552, -4294967296},
|
||||||
|
{2, 32, 0, 0},
|
||||||
|
{2, 32, 51539607552, 4294967296},
|
||||||
|
{3, 8, -768, -256},
|
||||||
|
{3, 8, 0, 0},
|
||||||
|
{3, 8, 768, 256},
|
||||||
|
{3, 12, -12288, -4096},
|
||||||
|
{3, 12, 0, 0},
|
||||||
|
{3, 12, 12288, 4096},
|
||||||
|
{3, 16, -262144, -65536},
|
||||||
|
{3, 16, 0, 0},
|
||||||
|
{3, 16, 262144, 65536},
|
||||||
|
{3, 20, -4194304, -1048576},
|
||||||
|
{3, 20, 0, 0},
|
||||||
|
{3, 20, 4194304, 1048576},
|
||||||
|
{3, 24, -67108864, -16777216},
|
||||||
|
{3, 24, 0, 0},
|
||||||
|
{3, 24, 67108864, 16777216},
|
||||||
|
{3, 28, -1342177280, -268435456},
|
||||||
|
{3, 28, 0, 0},
|
||||||
|
{3, 28, 1342177280, 268435456},
|
||||||
|
{3, 32, -21474836480, -4294967296},
|
||||||
|
{3, 32, 0, 0},
|
||||||
|
{3, 32, 21474836480, 4294967296},
|
||||||
|
{5, 8, -1792, 0},
|
||||||
|
{5, 8, 0, 177},
|
||||||
|
{5, 8, 1792, 1792},
|
||||||
|
{5, 12, -40960, 0},
|
||||||
|
{5, 12, 0, 2839},
|
||||||
|
{5, 12, 40960, 40960},
|
||||||
|
{5, 16, -786432, 0},
|
||||||
|
{5, 16, 0, 45426},
|
||||||
|
{5, 16, 786432, 786432},
|
||||||
|
{5, 20, -15728640, 0},
|
||||||
|
{5, 20, 0, 726818},
|
||||||
|
{5, 20, 15728640, 15728640},
|
||||||
|
{5, 24, -301989888, 0},
|
||||||
|
{5, 24, 0, 11629080},
|
||||||
|
{5, 24, 301989888, 301989888},
|
||||||
|
{5, 28, -5637144576, 0},
|
||||||
|
{5, 28, 0, 186065280},
|
||||||
|
{5, 28, 5637144576, 5637144576},
|
||||||
|
{5, 32, -98784247808, 0},
|
||||||
|
{5, 32, 0, 2977044472},
|
||||||
|
{5, 32, 98784247808, 98784247808},
|
||||||
|
{8, 8, -1024, 0},
|
||||||
|
{8, 8, 0, 0},
|
||||||
|
{8, 8, 1024, 1024},
|
||||||
|
{8, 12, -20480, 0},
|
||||||
|
{8, 12, 0, 0},
|
||||||
|
{8, 12, 20480, 20480},
|
||||||
|
{8, 16, -327680, 0},
|
||||||
|
{8, 16, 0, 0},
|
||||||
|
{8, 16, 327680, 327680},
|
||||||
|
{8, 20, -6291456, 0},
|
||||||
|
{8, 20, 0, 0},
|
||||||
|
{8, 20, 6291456, 6291456},
|
||||||
|
{8, 24, -100663296, 0},
|
||||||
|
{8, 24, 0, 0},
|
||||||
|
{8, 24, 100663296, 100663296},
|
||||||
|
{8, 28, -1879048192, 0},
|
||||||
|
{8, 28, 0, 0},
|
||||||
|
{8, 28, 1879048192, 1879048192},
|
||||||
|
{8, 32, -30064771072, 0},
|
||||||
|
{8, 32, 0, 0},
|
||||||
|
{8, 32, 30064771072, 30064771072},
|
||||||
|
{9, 8, -2304, 0},
|
||||||
|
{9, 8, 0, 0},
|
||||||
|
{9, 8, 2304, 2304},
|
||||||
|
{9, 12, -49152, 0},
|
||||||
|
{9, 12, 0, 0},
|
||||||
|
{9, 12, 49152, 49152},
|
||||||
|
{9, 16, -983040, 0},
|
||||||
|
{9, 16, 0, 0},
|
||||||
|
{9, 16, 983040, 983040},
|
||||||
|
{9, 20, -18874368, 0},
|
||||||
|
{9, 20, 0, 0},
|
||||||
|
{9, 20, 18874368, 18874368},
|
||||||
|
{9, 24, -352321536, 0},
|
||||||
|
{9, 24, 0, 0},
|
||||||
|
{9, 24, 352321536, 352321536},
|
||||||
|
{9, 28, -6442450944, 0},
|
||||||
|
{9, 28, 0, 0},
|
||||||
|
{9, 28, 6442450944, 6442450944},
|
||||||
|
{9, 32, -115964116992, 0},
|
||||||
|
{9, 32, 0, 0},
|
||||||
|
{9, 32, 115964116992, 115964116992},
|
||||||
|
{10, 8, -2304, 0},
|
||||||
|
{10, 8, -512, -65},
|
||||||
|
{10, 8, 1280, 1280},
|
||||||
|
{10, 12, -49152, 0},
|
||||||
|
{10, 12, -12288, -597},
|
||||||
|
{10, 12, 24576, 24576},
|
||||||
|
{10, 16, -983040, 0},
|
||||||
|
{10, 16, -229376, -6822},
|
||||||
|
{10, 16, 524288, 524288},
|
||||||
|
{10, 20, -18874368, 0},
|
||||||
|
{10, 20, -4718592, -52128},
|
||||||
|
{10, 20, 9437184, 9437184},
|
||||||
|
{10, 24, -352321536, 0},
|
||||||
|
{10, 24, -83886080, -563316},
|
||||||
|
{10, 24, 184549376, 184549376},
|
||||||
|
{10, 28, -6442450944, 0},
|
||||||
|
{10, 28, -1610612736, -3987362},
|
||||||
|
{10, 28, 3221225472, 3221225472},
|
||||||
|
{10, 32, -115964116992, 0},
|
||||||
|
{10, 32, -27917287424, -41940393},
|
||||||
|
{10, 32, 60129542144, 60129542144},
|
||||||
|
{12, 8, -2304, 0},
|
||||||
|
{12, 8, -768, -42},
|
||||||
|
{12, 8, 768, 768},
|
||||||
|
{12, 12, -49152, 0},
|
||||||
|
{12, 12, -18432, -230},
|
||||||
|
{12, 12, 12288, 12288},
|
||||||
|
{12, 16, -983040, 0},
|
||||||
|
{12, 16, -360448, -1659},
|
||||||
|
{12, 16, 262144, 262144},
|
||||||
|
{12, 20, -18874368, 0},
|
||||||
|
{12, 20, -7340032, -7549},
|
||||||
|
{12, 20, 4194304, 4194304},
|
||||||
|
{12, 24, -352321536, 0},
|
||||||
|
{12, 24, -134217728, -50797},
|
||||||
|
{12, 24, 83886080, 83886080},
|
||||||
|
{12, 28, -6442450944, 0},
|
||||||
|
{12, 28, -2550136832, -215380},
|
||||||
|
{12, 28, 1342177280, 1342177280},
|
||||||
|
{12, 32, -115964116992, 0},
|
||||||
|
{12, 32, -47244640256, -890358},
|
||||||
|
{12, 32, 21474836480, 21474836480},
|
||||||
|
{13, 8, -2304, 0},
|
||||||
|
{13, 8, -896, -27},
|
||||||
|
{13, 8, 512, 512},
|
||||||
|
{13, 12, -49152, 0},
|
||||||
|
{13, 12, -20480, -138},
|
||||||
|
{13, 12, 8192, 8192},
|
||||||
|
{13, 16, -983040, 0},
|
||||||
|
{13, 16, -425984, -640},
|
||||||
|
{13, 16, 131072, 131072},
|
||||||
|
{13, 20, -18874368, 0},
|
||||||
|
{13, 20, -7864320, -4350},
|
||||||
|
{13, 20, 3145728, 3145728},
|
||||||
|
{13, 24, -352321536, 0},
|
||||||
|
{13, 24, -150994944, -18634},
|
||||||
|
{13, 24, 50331648, 50331648},
|
||||||
|
{13, 28, -6442450944, 0},
|
||||||
|
{13, 28, -2818572288, -77613},
|
||||||
|
{13, 28, 805306368, 805306368},
|
||||||
|
{13, 32, -115964116992, 0},
|
||||||
|
{13, 32, -51539607552, -316670},
|
||||||
|
{13, 32, 12884901888, 12884901888},
|
||||||
|
{11, 8, -1792, 0},
|
||||||
|
{11, 8, -448, -31},
|
||||||
|
{11, 8, 256, 187},
|
||||||
|
{11, 8, 2304, 2304},
|
||||||
|
{11, 12, -40960, 0},
|
||||||
|
{11, 12, 0, 0},
|
||||||
|
{11, 12, 4096, 2994},
|
||||||
|
{11, 12, 49152, 49152},
|
||||||
|
{11, 16, -786432, 0},
|
||||||
|
{11, 16, 0, 0},
|
||||||
|
{11, 16, 98304, 80371},
|
||||||
|
{11, 16, 983040, 983040},
|
||||||
|
{11, 20, -15728640, 0},
|
||||||
|
{11, 20, 0, 0},
|
||||||
|
{11, 20, 1572864, 1285933},
|
||||||
|
{11, 20, 18874368, 18874368},
|
||||||
|
{11, 24, -301989888, 0},
|
||||||
|
{11, 24, 0, 0},
|
||||||
|
{11, 24, 25165824, 20574935},
|
||||||
|
{11, 24, 352321536, 352321536},
|
||||||
|
{11, 28, -5637144576, 0},
|
||||||
|
{11, 28, 0, 0},
|
||||||
|
{11, 28, 402653184, 329198966},
|
||||||
|
{11, 28, 6442450944, 6442450944},
|
||||||
|
{11, 32, -98784247808, 0},
|
||||||
|
{11, 32, 0, 0},
|
||||||
|
{11, 32, 8589934592, 7565989289},
|
||||||
|
{11, 32, 115964116992, 115964116992},
|
||||||
|
{14, 8, 0, 0},
|
||||||
|
{14, 8, 64, 65},
|
||||||
|
{14, 8, 128, 134},
|
||||||
|
{14, 12, 0, 0},
|
||||||
|
{14, 12, 1024, 1035},
|
||||||
|
{14, 12, 2048, 2145},
|
||||||
|
{14, 16, 0, 0},
|
||||||
|
{14, 16, 16384, 16560},
|
||||||
|
{14, 16, 32768, 34315},
|
||||||
|
{14, 20, 0, 0},
|
||||||
|
{14, 20, 262144, 264954},
|
||||||
|
{14, 20, 524288, 549033},
|
||||||
|
{14, 24, 0, 0},
|
||||||
|
{14, 24, 4194304, 4239271},
|
||||||
|
{14, 24, 8388608, 8784530},
|
||||||
|
{14, 28, 0, 0},
|
||||||
|
{14, 28, 67108864, 67828340},
|
||||||
|
{14, 28, 134217728, 140552476},
|
||||||
|
{14, 32, 0, 0},
|
||||||
|
{14, 32, 1073741824, 1085253432},
|
||||||
|
{14, 32, 2147483648, 2248839617},
|
||||||
Loading…
Add table
Add a link
Reference in a new issue