216 lines
7.6 KiB
C++
216 lines
7.6 KiB
C++
|
|
#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{});
|
|||
|
|
}
|
|||
|
|
|