libdpf/test/tests/field_output_test.cpp

216 lines
7.6 KiB
C++
Raw Normal View History

#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{});
}