libdpf/test/tests/offset_jet_test.cpp
Ryan Henry 0d22946a0e Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-28 05:59:19 -06:00

192 lines
7.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include <tuple>
#include "grotto/offset_jet.hpp"
#include "dpf/verifiable.hpp"
#include <cstdint>
#include <stdexcept>
#include <vector>
namespace
{
uint64_t binom_ref(std::int64_t n, unsigned k)
{
if (k == 0)
return 1;
if (n >= 0)
{
unsigned __int128 acc = 1;
for (unsigned i = 1; i <= k; ++i)
acc = acc * static_cast<unsigned __int128>(n - static_cast<std::int64_t>(k - i)) / i;
return static_cast<uint64_t>(acc);
}
const uint64_t mag = binom_ref(-n + static_cast<std::int64_t>(k) - 1, k);
return (k & 1u) ? static_cast<uint64_t>(0) - mag : mag;
}
} // namespace
TEST(OffsetJet, BinomMatchesReference)
{
for (uint64_t n = 0; n < 40; ++n)
for (unsigned k = 0; k <= 16; ++k)
EXPECT_EQ(grotto::offset_jet_binom(n, k), binom_ref(static_cast<std::int64_t>(n), k))
<< "n=" << n << " k=" << k;
for (std::int64_t n = -20; n < 0; ++n)
for (unsigned k = 0; k <= 10; ++k)
EXPECT_EQ(grotto::offset_jet_binom(n, k), binom_ref(n, k))
<< "n=" << n << " k=" << k;
// Chu–Vandermonde sanity for a large center and a public kappa.
const uint64_t c = (uint64_t{1} << 40) + 17;
const std::int64_t kappa = -3;
for (unsigned k = 0; k <= 8; ++k)
{
uint64_t lhs = grotto::offset_jet_binom(
static_cast<std::int64_t>(c) + kappa, k);
uint64_t rhs = 0;
for (unsigned j = 0; j <= k; ++j)
rhs += grotto::offset_jet_binom(c, j)
* grotto::offset_jet_binom(kappa, k - j);
EXPECT_EQ(lhs, rhs) << "k=" << k;
}
}
TEST(OffsetJet, PublicCoefficientsMatchTheWrappedPoint)
{
const std::size_t degree = 3;
const uint8_t center = 9;
const auto mat = grotto::make_offset_jet_keys<uint8_t>(center, degree);
// f(x) = 2 + 3 C(x,1) + C(x,3)
const std::vector<uint64_t> coeff{2, 3, 0, 1};
const std::vector<uint8_t> knots{0};
for (int eta = 0; eta < 256; eta += 19)
{
const auto e = static_cast<uint8_t>(eta);
const uint64_t s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, e);
const uint64_t s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, e);
const uint64_t clear = grotto::offset_jet_clear<uint8_t>(center, knots, coeff, e);
EXPECT_EQ(s0 + s1, clear);
const uint8_t wrapped = static_cast<uint8_t>(center + e);
uint64_t expect = 0;
for (std::size_t k = 0; k <= degree; ++k)
expect += coeff[k] * grotto::offset_jet_binom(static_cast<uint64_t>(wrapped),
static_cast<unsigned>(k));
EXPECT_EQ(clear, expect);
}
}
TEST(OffsetJet, CarrySplitStillEvaluates)
{
const uint8_t center = 200;
const uint8_t eta = 100;
const auto mat = grotto::make_offset_jet_keys<uint8_t>(center, 1);
const std::vector<uint64_t> coeff{5, 1};
const std::vector<uint8_t> knots{0};
const uint64_t got = grotto::offset_jet_eval<0>(mat, knots, coeff, eta)
+ grotto::offset_jet_eval<1>(mat, knots, coeff, eta);
// wrapped = 200+100 = 44; f = 5 + C(44,1) = 5+44
EXPECT_EQ(got, uint64_t{5} + 44);
}
TEST(OffsetJet, DifferenceAndPrefixFromOneJet)
{
const std::size_t degree = 3;
const uint8_t center = 12;
const uint8_t eta = 3;
// Need degree+1 for prefix of a degree-2 polynomial; use degree 3 key.
const auto mat = grotto::make_offset_jet_keys<uint8_t>(center, degree);
const std::vector<uint64_t> poly{4, 2, 1}; // 4 + 2 C(x,1) + C(x,2)
std::vector<uint64_t> coeff = poly;
coeff.push_back(0); // pad to degree 3
const std::vector<uint8_t> knots{0};
const auto j0 = grotto::offset_jet_shares<0>(mat, knots, eta);
const auto j1 = grotto::offset_jet_shares<1>(mat, knots, eta);
std::vector<uint64_t> jet(degree + 1);
for (std::size_t k = 0; k <= degree; ++k)
jet[k] = j0[k] + j1[k];
const uint8_t x = static_cast<uint8_t>(center + eta);
const uint64_t value = grotto::offset_jet_dot(coeff, jet);
uint64_t expect_v = 0;
for (std::size_t k = 0; k < poly.size(); ++k)
expect_v += poly[k] * grotto::offset_jet_binom(static_cast<uint64_t>(x),
static_cast<unsigned>(k));
EXPECT_EQ(value, expect_v);
const auto dcoeff = grotto::offset_jet_difference_coeff(coeff);
const uint64_t diff = grotto::offset_jet_dot(dcoeff, jet);
const uint64_t fx1 = expect_v
- poly[0] * 0 // recompute f(x+1) - f(x)
+ 0;
uint64_t expect_fx1 = 0;
for (std::size_t k = 0; k < poly.size(); ++k)
expect_fx1 += poly[k] * grotto::offset_jet_binom(
static_cast<uint64_t>(static_cast<uint8_t>(x + 1)),
static_cast<unsigned>(k));
EXPECT_EQ(diff, static_cast<uint64_t>(expect_fx1 - expect_v));
(void)fx1;
// Prefix needs degree+1: key degree 3, poly degree <= 2.
const auto pcoeff = grotto::offset_jet_prefix_coeff(poly);
ASSERT_EQ(pcoeff.size(), degree + 1u);
const uint64_t prefix = grotto::offset_jet_dot(pcoeff, jet);
uint64_t expect_p = 0;
for (uint8_t i = 0; i < x; ++i)
{
for (std::size_t k = 0; k < poly.size(); ++k)
expect_p += poly[k] * grotto::offset_jet_binom(static_cast<uint64_t>(i),
static_cast<unsigned>(k));
}
EXPECT_EQ(prefix, expect_p);
}
TEST(OffsetJet, VerifiableProofs)
{
const std::size_t degree = 2;
const auto mat = grotto::make_offset_jet_keys<uint8_t>(2, degree, dpf::verifiable{});
const std::vector<uint64_t> coeff{1, 0, 3};
const std::vector<uint8_t> knots{0};
const uint8_t eta = 5;
std::vector<dpf::proof_token> a(degree + 1), b(degree + 1);
const uint64_t s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, eta, a.data());
const uint64_t s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, eta, b.data());
EXPECT_EQ(s0 + s1, grotto::offset_jet_clear<uint8_t>(2, knots, coeff, eta));
for (std::size_t m = 0; m <= degree; ++m)
EXPECT_TRUE(dpf::verify(a[m], b[m]));
}
TEST(OffsetJet, RejectsOversizeDegree)
{
EXPECT_THROW(grotto::make_offset_jet_keys<uint8_t>(1, grotto::offset_jet_max_degree + 1), std::invalid_argument);
}
TEST(OffsetJet, NegativeCenterDegreeTwoPlus)
{
const std::size_t degree = 3;
const int8_t center = -7;
const auto mat = grotto::make_offset_jet_keys<int8_t>(center, degree);
// f(x) = 1 + C(x,1) + 3 C(x,2) + C(x,3)
const std::vector<uint64_t> coeff{1, 1, 3, 1};
const std::vector<int8_t> knots{static_cast<int8_t>(-128)};
for (int eta = -20; eta <= 20; eta += 5)
{
const auto e = static_cast<int8_t>(eta);
const uint64_t s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, e);
const uint64_t s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, e);
const uint64_t clear = grotto::offset_jet_clear<int8_t>(center, knots, coeff, e);
EXPECT_EQ(s0 + s1, clear) << "eta=" << eta;
const int8_t wrapped = grotto::offset_horner_group_add(center, e);
uint64_t expect = 0;
for (std::size_t k = 0; k <= degree; ++k)
expect += coeff[k] * grotto::offset_jet_binom(
static_cast<std::int64_t>(wrapped), static_cast<unsigned>(k));
EXPECT_EQ(clear, expect) << "eta=" << eta;
}
}