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:
Ryan Henry 2026-09-24 15:16:21 -06:00
parent 3f10e05176
commit 875f09fec1
14 changed files with 42668 additions and 184 deletions

View file

@ -2,6 +2,7 @@
#include <cstdint>
#include <stdexcept>
#include <tuple>
#include <vector>
#include "dpf/beaver.hpp"
@ -46,6 +47,50 @@ struct XorSeq
};
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
@ -488,9 +533,18 @@ TEST(Beaver, GrottoAppendixEPreprocessing)
auto a0 = linear.input();
auto a1 = linear.input();
auto lin = linear(sgn * (a1 * x + a0));
(void)lin;
// Four masks plus four fused products (ePrint 2023/108, Table 3).
EXPECT_EQ(linear.preprocessing_count(), 8u);
// Two-round column of Table 3: the sign is a later multiply, and the
// constant coefficient is added from its value share.
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;
auto x2 = quad.input();
@ -499,8 +553,17 @@ TEST(Beaver, GrottoAppendixEPreprocessing)
auto b1 = quad.input();
auto b2 = quad.input();
auto q = quad(s2 * (b2 * pow(x2, 2) + b1 * x2 + b0));
(void)q;
EXPECT_EQ(quad.preprocessing_count(), 13u);
EXPECT_EQ(quad.round_of(q), 2);
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;
auto x3 = cube.input();
@ -510,7 +573,10 @@ TEST(Beaver, GrottoAppendixEPreprocessing)
auto c2 = cube.input();
auto c3 = cube.input();
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;
cube.sample(rng);
@ -537,9 +603,10 @@ TEST(Beaver, MultivariatePolynomialsSharePowers)
// Fused buckets: fewer shares than one subset product per monomial.
EXPECT_EQ(s.monomial_count(), 5u);
auto q = s(sgn * (x * y + pow(x, 2)));
EXPECT_EQ(s.round_of(q), 1);
// Sign crosses are new; λx² and λx λy are not sampled again.
EXPECT_EQ(s.monomial_count(), 10u);
EXPECT_EQ(s.round_of(q), 2);
// The sign is a later multiply, so the new shares are that product
// rather than a second one-round crossing of every power.
EXPECT_EQ(s.monomial_count(), 8u);
Counter rng;
s.sample(rng);
s.bind(x, u64{2}, rng);
@ -552,6 +619,84 @@ TEST(Beaver, MultivariatePolynomialsSharePowers)
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)
{
dpf::beavers::session<u64> s;
@ -607,6 +752,789 @@ TEST(Beaver, LikeTermsCollapse)
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)
{
dpf::beavers::session<u64> a;

View file

@ -1121,3 +1121,105 @@ TEST(Geneval, SignedRegressionsFromTheCornerPass)
expect_prefix_words(wild.first, g.correction_words, g.correction_advice,
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]);
}
}

View file

@ -105,8 +105,9 @@ template <std::size_t Degree, typename T>
uint64_t gold(T center, T eta, const std::vector<T> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff)
{
const auto & a = coeff_of_wrapped<Degree>(center, eta, knots, coeff);
return power_sum<Degree>(a, lift(center) + lift(eta));
const T wrapped = offset_horner_group_add(center, 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>
@ -202,8 +203,12 @@ TEST(OffsetHorner, HandCubicAtCenterPlusEta)
EXPECT_EQ(open_eval<D>(mat, 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 want = binomial_shift<D>(coeff[0], lift(center));
EXPECT_EQ(got, want);
uint64_t summed = 0;
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.
const uint64_t p0 = party_eval<0, 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);
}
TEST(OffsetHorner, UnreducedSumIsNotTheWrappedRepresentative)
TEST(OffsetHorner, CarrySplitEvaluatesTheWrappedRepresentative)
{
constexpr std::size_t D = 1;
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);
const uint64_t got = open_eval<D>(mat, knots, coeff, eta);
EXPECT_EQ(got, gold<D>(center, eta, knots, coeff));
EXPECT_EQ(got, 600u);
const int piece = circular_piece(wrapped, knots);
const uint64_t at_wrapped = power_sum<D>(coeff[static_cast<std::size_t>(piece)], lift(wrapped));
EXPECT_EQ(at_wrapped, 88u);
EXPECT_NE(got, at_wrapped);
EXPECT_EQ(got, 88u);
EXPECT_NE(got, 600u);
const std::vector<int8_t> sknots{-128, 0};
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)
@ -297,8 +313,11 @@ TEST(OffsetHorner, DegreesZeroOneAndTwo)
const auto coeff = take_degree<D>(full);
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_coeffs<D>(mat, knots, coeff, eta),
grotto::offset_horner_clear_coefficients<D>(center, knots, coeff, eta));
const auto parts = open_coeffs<D>(mat, 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;
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_coeffs<D>(mat, knots, coeff, eta),
binomial_shift<D>(coeff_of_wrapped<D>(center, eta, knots, coeff), lift(center)));
const auto parts = open_coeffs<D>(mat, knots, coeff, eta);
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});
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);
EXPECT_EQ(open_eval<D>(mat, knots, coeff, eta), gold<D>(center, eta, knots, coeff))
<< trial;
EXPECT_EQ(open_coeffs<D>(mat, knots, coeff, eta),
binomial_shift<D>(coeff_of_wrapped<D>(center, eta, knots, coeff), lift(center)));
const auto parts = open_coeffs<D>(mat, knots, coeff, eta);
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 uint64_t got = open_eval<D>(mat, knots, coeff, eta);
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
<< " got=" << got << " want=" << want;
<< " got=" << got << " clear=" << cleared
<< " want=" << want;
return;
}
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 uint64_t got = open_eval<D>(mat, knots, coeff, eta);
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
<< " got=" << got << " want=" << want;
<< " got=" << got << " clear=" << cleared
<< " want=" << want;
return;
}
const int8_t wrapped = offset_horner_group_add(center, eta);
@ -698,6 +727,94 @@ TEST(OffsetHorner, ExhaustiveInt8AgreesWithGoldAndCountsOverflows)
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)
{
constexpr std::size_t D = 3;
@ -712,13 +829,574 @@ TEST(OffsetHorner, HornerOfOpenedCoefficientsMatchesValue)
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
const auto c = open_coeffs<D>(mat, knots, coeff, eta);
uint64_t y = 0;
uint64_t p = 1;
const uint64_t e = lift(eta);
for (uint64_t ck : c)
{
y += ck * p;
p *= e;
}
y += ck;
EXPECT_EQ(y, open_eval<D>(mat, knots, coeff, eta));
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>();
}

View file

@ -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::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));
}

File diff suppressed because it is too large Load diff

View 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},