libdpf/test/tests/field_output_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

215 lines
7.6 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 "dpf.hpp"
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <type_traits>
namespace
{
template <typename Key0, typename Key1, typename In>
auto open_at(const Key0 &k0, const Key1 &k1, In x)
{
const auto y0 = *dpf::eval_point(k0, x);
const auto y1 = *dpf::eval_point(k1, x);
return dpf::reconstruct(y0, y1);
}
template <typename Out, typename In>
void expect_point_payload(In alpha, Out beta)
{
auto [k0, k1] = dpf::make_dpf(alpha, beta);
const In last = static_cast<In>(16);
for (In x = 0; x < last; ++x)
{
const Out got = open_at(k0, k1, x);
EXPECT_EQ(got, x == alpha ? beta : Out{}) << static_cast<unsigned>(x);
}
const Out on = open_at(k0, k1, alpha);
EXPECT_EQ(on, beta);
}
} // namespace
TEST(Field64, ArithmeticMatchesThePrime)
{
constexpr auto p = dpf::field64::mod;
EXPECT_EQ((dpf::field64{p - 1} + dpf::field64{p - 1}).raw(), p - 2);
EXPECT_EQ((dpf::field64{p - 1} + dpf::field64{1}).raw(), 0u);
EXPECT_EQ((dpf::field64{1} - dpf::field64{2}).raw(), p - 1);
EXPECT_EQ((-dpf::field64{0}).raw(), 0u);
EXPECT_EQ((-dpf::field64{1}).raw(), p - 1);
EXPECT_EQ(dpf::field64{-1}.raw(), p - 1);
EXPECT_EQ((dpf::field64{p - 1} * dpf::field64{p - 1}).raw(), 1u);
EXPECT_EQ((dpf::field64{0x0123456789abcdefull} * dpf::field64{0xfedcba9876543210ull}).raw(),
0xcfaeafd136c7bbaeull);
EXPECT_EQ((dpf::field64{std::uint64_t{1} << 32} * dpf::field64{std::uint64_t{1} << 32}).raw(),
0xffffffffull);
const dpf::field64 a{1000};
const dpf::field64 b{p - 5};
const dpf::field64 c{17};
EXPECT_EQ((a + b) * c, a * c + b * c);
EXPECT_TRUE(std::is_trivially_copyable_v<dpf::field64>);
EXPECT_TRUE(std::is_standard_layout_v<dpf::field64>);
}
TEST(Field128, ArithmeticMatchesThePrime)
{
const dpf::field128 almost{static_cast<unsigned __int128>(
(static_cast<unsigned __int128>(dpf::field128::mod_hi) << 64)
| dpf::field128::mod_lo) - 1};
EXPECT_EQ(almost + dpf::field128{1}, dpf::field128{0});
EXPECT_EQ(almost * almost, dpf::field128{1});
EXPECT_EQ(-almost, dpf::field128{1});
EXPECT_EQ(dpf::field128{-1}, almost);
EXPECT_EQ(dpf::field128{1} - dpf::field128{2}, almost);
EXPECT_TRUE(std::is_trivially_copyable_v<dpf::field128>);
EXPECT_TRUE(std::is_standard_layout_v<dpf::field128>);
}
TEST(Field128, WideProduct)
{
const unsigned __int128 a =
(static_cast<unsigned __int128>(0x123456789abcdef0ull) << 64)
| 0x123456789abcdefull;
const unsigned __int128 b =
(static_cast<unsigned __int128>(0xfedcba9876543210ull) << 64)
| 0xfedcba9876543210ull;
const auto got = dpf::field128{a} * dpf::field128{b};
EXPECT_EQ(got.hi(), 0xb0a8b1cbf73350c9ull);
EXPECT_EQ(got.lo(), 0xf0123456789abe97ull);
}
TEST(P256, GeneratorAndDoubling)
{
const auto g = dpf::p256::generator();
EXPECT_EQ(dpf::p256{1}, g);
EXPECT_EQ(dpf::p256{0}, dpf::p256{});
EXPECT_TRUE(dpf::p256{}.is_identity());
EXPECT_EQ(g + dpf::p256{}, g);
EXPECT_EQ(g - g, dpf::p256{});
EXPECT_EQ(-dpf::p256{1}, dpf::p256{-1});
const auto two = g + g;
EXPECT_EQ(two, dpf::p256{2});
EXPECT_EQ(two - g, g);
const unsigned char expect[] = {
0x03,
0x7c, 0xf2, 0x7b, 0x18, 0x8d, 0x03, 0x4f, 0x7e,
0x8a, 0x52, 0x38, 0x03, 0x04, 0xb5, 0x1a, 0xc3,
0xc0, 0x89, 0x69, 0xe2, 0x77, 0xf2, 0x1b, 0x35,
0xa6, 0x0b, 0x48, 0xfc, 0x47, 0x66, 0x99, 0x78,
};
EXPECT_EQ(std::memcmp(two.bytes(), expect, 33), 0);
EXPECT_EQ(dpf::p256::from_compressed(expect), two);
const unsigned char bad[33] = {0x04};
EXPECT_THROW(dpf::p256::from_compressed(bad), std::invalid_argument);
EXPECT_TRUE(std::is_trivially_copyable_v<dpf::p256>);
EXPECT_TRUE(std::is_standard_layout_v<dpf::p256>);
}
template <typename Out, typename Spec>
void expect_cmp(std::uint8_t alpha, Out beta, Spec spec, bool leq)
{
auto [k0, k1] = dpf::make_dpf(alpha, spec);
for (int x = 0; x < 24; ++x)
{
const auto q = static_cast<std::uint8_t>(x);
const auto y0 = dpf::eval_point<Out>(dpf::cmp, k0, q);
const auto y1 = dpf::eval_point<Out>(dpf::cmp, k1, q);
const bool hot = leq ? x <= static_cast<int>(alpha)
: x < static_cast<int>(alpha);
EXPECT_EQ(dpf::reconstruct(y0, y1), hot ? beta : Out{}) << x;
}
}
TEST(FieldOutput, ComparisonPayload)
{
const auto f = dpf::field64{5};
expect_cmp(std::uint8_t{10}, f, dpf::lt(f), false);
expect_cmp(std::uint8_t{10}, f, dpf::leq(f), true);
const auto w = dpf::field128{9};
expect_cmp(std::uint8_t{10}, w, dpf::lt(w), false);
expect_cmp(std::uint8_t{4}, dpf::p256{1}, dpf::lt(dpf::p256{1}), false);
}
TEST(FieldOutput, IdcfPrefixUsesTheSameGroup)
{
const auto beta = dpf::field64{3};
const std::uint8_t alpha = 0x2a;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::idcf(dpf::lt(beta)));
auto [a0, a1] = dpf::make_dpf(alpha, dpf::lt_at<4>(beta));
for (int x = 0; x < 32; ++x)
{
const auto q = static_cast<std::uint8_t>(x);
const auto full0 = dpf::eval_point<dpf::field64>(dpf::cmp, k0, q);
const auto full1 = dpf::eval_point<dpf::field64>(dpf::cmp, k1, q);
EXPECT_EQ(dpf::reconstruct(full0, full1),
x < static_cast<int>(alpha) ? beta : dpf::field64{});
const auto p0 = dpf::eval_point<4, dpf::field64>(dpf::cmp_prefix<4>, k0, q);
const auto p1 = dpf::eval_point<4, dpf::field64>(dpf::cmp_prefix<4>, k1, q);
const auto n0 = dpf::eval_point<dpf::field64>(dpf::cmp, a0, q);
const auto n1 = dpf::eval_point<dpf::field64>(dpf::cmp, a1, q);
EXPECT_EQ(dpf::reconstruct(p0, p1), dpf::reconstruct(n0, n1)) << x;
}
}
TEST(FieldOutput, PointPayloadRoundTrip)
{
expect_point_payload<dpf::field64>(std::uint8_t{0x2a}, dpf::field64{99});
expect_point_payload<dpf::field128>(std::uint8_t{0x11}, dpf::field128{7});
expect_point_payload<dpf::p256>(std::uint8_t{0x2a}, dpf::p256{1});
}
TEST(FieldOutput, Fp61PayloadNearModulus)
{
const dpf::fp61 beta{dpf::fp61_mod - 1};
expect_point_payload<dpf::fp61>(std::uint8_t{0x2a}, beta);
expect_point_payload<dpf::fp61>(std::uint8_t{0x05}, dpf::fp61{dpf::fp61_mod - 7});
}
TEST(FieldOutput, Fp61NegativeComparisonDelta)
{
// δ = −1 ≡ p−1 must survive comparison (not collapse to 0 via a 61-bit mask).
expect_cmp(std::uint8_t{10}, dpf::fp61{dpf::fp61_mod - 1},
dpf::lt(dpf::fp61{dpf::fp61_mod - 1}), false);
expect_cmp(std::uint8_t{10}, dpf::fp61{dpf::fp61_mod - 1},
dpf::leq(dpf::fp61{dpf::fp61_mod - 1}), true);
}
TEST(FieldOutput, P256MalformedCompressedRejected)
{
unsigned char bad[33] = {0x02};
bad[32] = 1; // x = 1 is not on P-256
EXPECT_THROW(dpf::p256::from_compressed(bad), std::invalid_argument);
dpf::p256 bogus{};
unsigned char raw[sizeof(dpf::p256)]{};
std::memcpy(raw, &bogus, sizeof(raw));
std::memcpy(raw, bad, 33);
std::memcpy(&bogus, raw, sizeof(raw));
EXPECT_THROW(bogus + dpf::p256{}, std::invalid_argument);
EXPECT_THROW(-bogus, std::invalid_argument);
}
TEST(FieldOutput, P256ScalarWideComparisonPayload)
{
const dpf::p256_scalar beta{17};
expect_cmp(std::uint8_t{10}, beta, dpf::lt(beta), false);
expect_cmp(std::uint8_t{10}, beta, dpf::leq(beta), true);
}
TEST(FieldOutput, Field128NoncanonicalNegationAndEquality)
{
dpf::field128 a{};
const std::uint64_t w[2] = {
dpf::field128::mod_lo, dpf::field128::mod_hi};
std::memcpy(&a, w, sizeof(w));
EXPECT_EQ(dpf::field128::canonicalize(a), dpf::field128{});
EXPECT_EQ(-a, dpf::field128{});
EXPECT_EQ(a, dpf::field128{});
}