Document the new DPF surfaces in one command set, and test the field, half-tree, and multipoint edges.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 23:18:10 -06:00
parent 0d8a5a8131
commit 0dff6df8ed
250 changed files with 12199 additions and 1981 deletions

View file

@ -103,6 +103,7 @@ TEST(Beaver, ProductTwo)
Counter rng;
s.sample(rng);
EXPECT_EQ(rng.draws, 5); // two input blinds * 2 draws + one product share
EXPECT_EQ(s.preprocessing_count(), 3u);
EXPECT_EQ(s.monomial_count(), 1u);
EXPECT_EQ(s.round_of(z), 1);
EXPECT_EQ(s.monomial({{x, 1u}, {y, 1u}}).open(),
@ -150,7 +151,8 @@ TEST(Beaver, MulSquareIsOneRound)
s.sample(rng);
EXPECT_EQ(s.round_of(z), 1);
EXPECT_EQ(s.wire_count(), 3u);
// λx², λa λx, λa λx². One blind for both x factors.
// λx², λa λx, λa λx². One blind for both x factors. The output is unpinned.
EXPECT_EQ(s.preprocessing_count(), 5u);
EXPECT_EQ(s.monomial_count(), 3u);
EXPECT_EQ(rng.draws, 7); // two input blinds * 2 + three product shares
EXPECT_EQ(s.monomial({{x, 2u}}).open(),
@ -194,9 +196,10 @@ TEST(Beaver, DotAggregatesCrossTerm)
auto z = s.dot({x0, x1, x2}, {y0, y1, y2});
Counter rng;
s.sample(rng);
EXPECT_EQ(s.monomial_count(), 0u);
EXPECT_EQ(s.monomial_count(), 1u);
EXPECT_EQ(s.round_of(z), 1);
// 6 input blinds, two draws each, plus one aggregated cross draw
// Six input blinds and one fused cross. Three separate products would be three crosses.
EXPECT_EQ(s.preprocessing_count(), 7u);
EXPECT_EQ(rng.draws, 13);
auto cross = s.lambda(x0).open() * s.lambda(y0).open()
+ s.lambda(x1).open() * s.lambda(y1).open()
@ -220,13 +223,427 @@ TEST(Beaver, DotReusesAPair)
auto z = s.dot({a, a}, {b, b});
Counter rng;
s.sample(rng);
EXPECT_EQ(s.monomial_count(), 0u);
EXPECT_EQ(s.monomial_count(), 1u);
EXPECT_EQ(s.preprocessing_count(), 3u);
EXPECT_EQ(rng.draws, 5);
EXPECT_EQ(s.dot_cross(z).open(),
u64{2} * s.lambda(a).open() * s.lambda(b).open());
s.bind(a, u64{3}, rng);
s.bind(b, u64{4}, rng);
s.evaluate();
EXPECT_EQ(s.open(z), 24u);
}
TEST(Beaver, InnerProductMatchesTheSumAndIsOneCross)
{
const u64 x[] = {0, 2, 5};
const u64 y[] = {4, 0, 7};
u64 expect = 0;
for (int i = 0; i < 3; ++i)
expect += x[i] * y[i];
session64 fused;
std::vector<wire64> fx;
std::vector<wire64> fy;
for (int i = 0; i < 3; ++i)
{
fx.push_back(fused.input());
fy.push_back(fused.input());
}
auto dot = fused.dot(fx, fy);
auto as_sum = fused(fx[0] * fy[0] + fx[1] * fy[1] + fx[2] * fy[2]);
EXPECT_EQ(fused.round_of(dot), 1);
EXPECT_EQ(fused.round_of(as_sum), 1);
EXPECT_EQ(fused.preprocessing_count(), 7u);
EXPECT_EQ(fused.monomial_count(), 1u);
session64 separate;
std::vector<wire64> sx;
std::vector<wire64> sy;
std::vector<wire64> prods;
for (int i = 0; i < 3; ++i)
{
sx.push_back(separate.input());
sy.push_back(separate.input());
}
for (int i = 0; i < 3; ++i)
prods.push_back(separate(sx[i] * sy[i]));
auto summed = separate(prods[0] + prods[1] + prods[2]);
EXPECT_EQ(separate.preprocessing_count(), 9u);
EXPECT_LT(fused.preprocessing_count(), separate.preprocessing_count());
Counter rf;
Counter rs;
fused.sample(rf);
separate.sample(rs);
for (int i = 0; i < 3; ++i)
{
fused.bind(fx[i], x[i], rf);
fused.bind(fy[i], y[i], rf);
separate.bind(sx[i], x[i], rs);
separate.bind(sy[i], y[i], rs);
}
fused.evaluate();
separate.evaluate();
EXPECT_EQ(fused.open(dot), expect);
EXPECT_EQ(fused.open(as_sum), expect);
EXPECT_EQ(separate.open(summed), expect);
u64 cross = 0;
for (int i = 0; i < 3; ++i)
cross += fused.lambda(fx[i]).open() * fused.lambda(fy[i]).open();
EXPECT_EQ(fused.dot_cross(dot).open(), cross);
const auto monos = fused.monomial_count();
const auto prep = fused.preprocessing_count();
auto again = fused.dot(fx, fy);
EXPECT_EQ(fused.monomial_count(), monos);
EXPECT_EQ(fused.preprocessing_count(), prep);
fused.sample(rf);
fused.evaluate();
EXPECT_EQ(fused.open(again), expect);
}
TEST(Beaver, InnerProductPeelsASharedFactor)
{
session64 s;
auto a = s.input();
auto x = s.input();
auto y = s.input();
auto z = s.input();
auto dotted = s.dot({a, a, a}, {x, y, z});
EXPECT_EQ(s.round_of(dotted), 2);
EXPECT_EQ(s.preprocessing_count(), 3u);
session64 hand;
auto ha = hand.input();
auto hx = hand.input();
auto hy = hand.input();
auto hz = hand.input();
auto written = hand(ha * (hx + hy + hz));
EXPECT_EQ(hand.round_of(written), 2);
EXPECT_EQ(hand.preprocessing_count(), s.preprocessing_count());
EXPECT_LT(s.preprocessing_count(), 7u);
Counter rng;
s.sample(rng);
s.bind(a, u64{3}, rng);
s.bind(x, u64{4}, rng);
s.bind(y, u64{5}, rng);
s.bind(z, u64{6}, rng);
s.evaluate();
EXPECT_EQ(s.open(dotted), 3u * (4u + 5u + 6u));
}
TEST(Beaver, InnerProductOfSquaresIsOneShare)
{
auto got = run_poly(2, [](session64 & s, const std::vector<wire64> & in) {
return s.dot({in[0], in[1]}, {in[0], in[1]});
}, {6, 7});
EXPECT_EQ(got.value, 36u + 49u);
EXPECT_EQ(got.rounds, 1);
EXPECT_EQ(got.prep, 3u);
EXPECT_EQ(got.monos, 1u);
}
TEST(Beaver, ScaledInnerProductUsesThePolynomialSchedule)
{
session64 automatic;
auto sgn = automatic.input();
auto x0 = automatic.input();
auto x1 = automatic.input();
auto y0 = automatic.input();
auto y1 = automatic.input();
auto y = automatic(sgn * dpf::beavers::dot({x0, x1}, {y0, y1}));
session64 hand;
auto hs = hand.input();
auto hx0 = hand.input();
auto hx1 = hand.input();
auto hy0 = hand.input();
auto hy1 = hand.input();
auto inner = hand.dot({hx0, hx1}, {hy0, hy1});
auto outer = hand(hs * inner);
EXPECT_EQ(automatic.preprocessing_count(), hand.preprocessing_count());
EXPECT_EQ(automatic.round_of(y), hand.round_of(outer));
EXPECT_LT(automatic.preprocessing_count(), 9u);
const u64 sv = 3, a = 2, b = 5, c = 4, d = 6;
Counter ra;
Counter rh;
automatic.sample(ra);
hand.sample(rh);
automatic.bind(sgn, sv, ra);
automatic.bind(x0, a, ra);
automatic.bind(x1, b, ra);
automatic.bind(y0, c, ra);
automatic.bind(y1, d, ra);
hand.bind(hs, sv, rh);
hand.bind(hx0, a, rh);
hand.bind(hx1, b, rh);
hand.bind(hy0, c, rh);
hand.bind(hy1, d, rh);
automatic.evaluate();
hand.evaluate();
const u64 expect = sv * (a * c + b * d);
EXPECT_EQ(automatic.open(y), expect);
EXPECT_EQ(hand.open(outer), expect);
session64 scaled;
auto sx0 = scaled.input();
auto sx1 = scaled.input();
auto sy0 = scaled.input();
auto sy1 = scaled.input();
auto plain = scaled.dot({sx0, sx1}, {sy0, sy1});
auto triple = scaled(u64{3} * dpf::beavers::dot({sx0, sx1}, {sy0, sy1}));
EXPECT_EQ(scaled.preprocessing_count(), 5u);
EXPECT_EQ(scaled.monomial_count(), 1u);
(void)plain;
Counter rs;
scaled.sample(rs);
scaled.bind(sx0, a, rs);
scaled.bind(sx1, b, rs);
scaled.bind(sy0, c, rs);
scaled.bind(sy1, d, rs);
scaled.evaluate();
EXPECT_EQ(scaled.open(triple), 3u * (a * c + b * d));
EXPECT_EQ(scaled.open(plain), a * c + b * d);
}
TEST(Beaver, InnerProductsOnModintAndXor)
{
using M = dpf::modint<17>;
dpf::beavers::session<M> ms;
auto mx0 = ms.input();
auto mx1 = ms.input();
auto my0 = ms.input();
auto my1 = ms.input();
auto my = ms.dot({mx0, mx1}, {my0, my1});
EXPECT_EQ(ms.preprocessing_count(), 5u);
EXPECT_EQ(ms.monomial_count(), 1u);
Seq<M> mr;
ms.sample(mr);
ms.bind(mx0, M{10}, mr);
ms.bind(mx1, M{9}, mr);
ms.bind(my0, M{8}, mr);
ms.bind(my1, M{7}, mr);
ms.evaluate();
EXPECT_EQ(ms.open(my), M{10} * M{8} + M{9} * M{7});
using W = dpf::xor_wrapper<std::uint32_t>;
dpf::beavers::session<W> xs;
auto xx = xs.input();
auto xy = xs.input();
auto yx = xs.input();
auto yy = xs.input();
auto dot = xs.dot({xx, xy}, {yx, yy});
EXPECT_EQ(xs.preprocessing_count(), 5u);
XorSeq<W> xr;
xs.sample(xr);
const W a{0b11110000u};
const W b{0b11001100u};
const W c{0b10101010u};
const W d{0b11111111u};
xs.bind(xx, a, xr);
xs.bind(xy, b, xr);
xs.bind(yx, c, xr);
xs.bind(yy, d, xr);
xs.evaluate();
EXPECT_EQ(xs.open(dot), a * c + b * d);
}
TEST(Beaver, InnerProductOfEarlierProductsIsALaterRound)
{
session64 s;
auto a = s.input();
auto b = s.input();
auto c = s.input();
auto d = s.input();
auto ab = s(a * b);
auto cd = s(c * d);
s.pin(ab);
s.pin(cd);
Counter rng;
s.sample(rng);
auto lab = s.lambda(ab);
s.bind(a, u64{2}, rng);
s.bind(b, u64{3}, rng);
s.bind(c, u64{4}, rng);
s.bind(d, u64{5}, rng);
auto both = s.dot({ab, cd}, {ab, cd});
EXPECT_EQ(s.round_of(both), 2);
const auto draws = rng.draws;
s.sample(rng);
EXPECT_EQ(s.lambda(ab), lab);
EXPECT_EQ(rng.draws, draws + 1);
s.evaluate();
EXPECT_EQ(s.open(ab), 6u);
EXPECT_EQ(s.open(cd), 20u);
EXPECT_EQ(s.open(both), 6u * 6u + 20u * 20u);
}
TEST(Beaver, TriplesUseNoExtraShares)
{
{
session64 s;
auto a = s.input();
auto b = s.input();
auto ab = s(a * b);
s.pin(ab);
EXPECT_EQ(s.preprocessing_count(), 4u);
Counter rng;
s.sample(rng);
s.bind(a, u64{6}, rng);
s.bind(b, u64{7}, rng);
s.evaluate();
EXPECT_EQ(s.open(ab), 42u);
EXPECT_EQ(s.monomial({{a, 1u}, {b, 1u}}).open(),
s.lambda(a).open() * s.lambda(b).open());
}
{
session64 s;
auto a = s.input();
auto b = s.input();
auto c = s.input();
auto abc = s(a * b * c);
s.pin(abc);
EXPECT_EQ(s.preprocessing_count(), 8u);
Counter rng;
s.sample(rng);
s.bind(a, u64{2}, rng);
s.bind(b, u64{3}, rng);
s.bind(c, u64{5}, rng);
s.evaluate();
EXPECT_EQ(s.open(abc), 30u);
EXPECT_EQ(s.monomial({{a, 1u}, {b, 1u}}).open(),
s.lambda(a).open() * s.lambda(b).open());
EXPECT_EQ(s.monomial({{a, 1u}, {b, 1u}, {c, 1u}}).open(),
s.lambda(a).open() * s.lambda(b).open() * s.lambda(c).open());
}
{
session64 s;
auto x = s.input();
auto x2 = s(x * x);
s.pin(x2);
EXPECT_EQ(s.preprocessing_count(), 3u);
Counter rng;
s.sample(rng);
s.bind(x, u64{9}, rng);
s.evaluate();
EXPECT_EQ(s.open(x2), 81u);
}
{
session64 s;
auto a = s.input();
auto x = s.input();
auto ax2 = s(a * x * x);
s.pin(ax2);
EXPECT_EQ(s.preprocessing_count(), 6u);
Counter rng;
s.sample(rng);
s.bind(a, u64{4}, rng);
s.bind(x, u64{3}, rng);
s.evaluate();
EXPECT_EQ(s.open(ax2), 36u);
EXPECT_EQ(s.monomial({{a, 1u}, {x, 2u}}).open(),
s.lambda(a).open() * s.lambda(x).open() * s.lambda(x).open());
}
{
session64 s;
std::vector<wire64> x;
std::vector<wire64> y;
for (int i = 0; i < 3; ++i)
{
x.push_back(s.input());
y.push_back(s.input());
}
auto unpinned = s.dot(x, y);
EXPECT_EQ(s.preprocessing_count(), 7u);
s.pin(unpinned);
EXPECT_EQ(s.preprocessing_count(), 8u);
Counter rng;
s.sample(rng);
const u64 xv[] = {1, 2, 3};
const u64 yv[] = {4, 5, 6};
for (int i = 0; i < 3; ++i)
{
s.bind(x[i], xv[i], rng);
s.bind(y[i], yv[i], rng);
}
s.evaluate();
EXPECT_EQ(s.open(unpinned), 32u);
}
{
session64 s;
auto scalar = s.input();
auto v0 = s.input();
auto v1 = s.input();
auto v2 = s.input();
auto z = s.scale(scalar, {v0, v1, v2});
EXPECT_EQ(s.preprocessing_count(), 7u);
for (auto out : z)
s.pin(out);
EXPECT_EQ(s.preprocessing_count(), 10u);
Counter rng;
s.sample(rng);
s.bind(scalar, u64{3}, rng);
s.bind(v0, u64{4}, rng);
s.bind(v1, u64{5}, rng);
s.bind(v2, u64{0}, rng);
s.evaluate();
EXPECT_EQ(s.open(z[0]), 12u);
EXPECT_EQ(s.open(z[1]), 15u);
EXPECT_EQ(s.open(z[2]), 0u);
}
{
session64 s;
auto bit = s.bit();
auto scalar = s.input();
auto out = s.bit_mul(bit, scalar);
s.pin(out);
EXPECT_EQ(s.preprocessing_count(), 4u);
Counter rng;
s.sample(rng);
s.bind(bit, u64{1}, rng);
s.bind(scalar, u64{19}, rng);
s.evaluate();
EXPECT_EQ(s.open(out), 19u);
}
{
session64 s;
auto bit = s.bit();
auto when1 = s.input();
auto when0 = s.input();
auto out = s.mux(bit, when1, when0);
s.pin(out);
EXPECT_EQ(s.preprocessing_count(), 6u);
Counter rng;
s.sample(rng);
s.bind(bit, u64{0}, rng);
s.bind(when1, u64{8}, rng);
s.bind(when0, u64{9}, rng);
s.evaluate();
EXPECT_EQ(s.open(out), 9u);
}
}
TEST(Beaver, InnerProductRejectsABadShape)
{
session64 s;
auto x = s.input();
auto y = s.input();
auto z = s.input();
EXPECT_THROW((void)[&] {
return s.dot(std::initializer_list<wire64>{}, std::initializer_list<wire64>{});
}(), std::invalid_argument);
EXPECT_THROW((void)[&] { return s.dot({x}, {y, z}); }(), std::invalid_argument);
session64 other;
auto w = other.input();
EXPECT_THROW((void)[&] { return s.dot({x}, {w}); }(), std::invalid_argument);
auto dotted = s.dot({x}, {y});
EXPECT_THROW((void)[&] { return s.dot_cross(dotted); }(), std::logic_error);
EXPECT_THROW((void)[&] { return s.dot_cross(x); }(), std::invalid_argument);
}
TEST(Beaver, ScaleSharesScalarBlind)
{
dpf::beavers::session<u64> s;
@ -240,6 +657,7 @@ TEST(Beaver, ScaleSharesScalarBlind)
s.sample(rng);
EXPECT_EQ(z.size(), 4u);
EXPECT_EQ(s.monomial_count(), 4u);
EXPECT_EQ(s.preprocessing_count(), 9u);
EXPECT_EQ(s.round_of(z[0]), 1);
// 1 scalar + 4 lanes + 4 outputs = 9 wires * 2, plus 4 cross terms
EXPECT_EQ(rng.draws, 14);