536 lines
20 KiB
C++
536 lines
20 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
|
||
|
|
#include "dpf.hpp"
|
||
|
|
#include "grotto/offset_horner.hpp"
|
||
|
|
#include "grotto/lut_union.hpp"
|
||
|
|
#include "grotto/offset_jet.hpp"
|
||
|
|
#include "grotto/offset_repr.hpp"
|
||
|
|
#include "grotto/offset_twist.hpp"
|
||
|
|
|
||
|
|
#include <array>
|
||
|
|
#include <cstdint>
|
||
|
|
#include <cstring>
|
||
|
|
#include <limits>
|
||
|
|
#include <stdexcept>
|
||
|
|
#include <type_traits>
|
||
|
|
#include <utility>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
namespace
|
||
|
|
{
|
||
|
|
|
||
|
|
template <typename T, typename = void>
|
||
|
|
struct has_member_alpha : std::false_type
|
||
|
|
{ };
|
||
|
|
template <typename T>
|
||
|
|
struct has_member_alpha<T, std::void_t<decltype(std::declval<T>().alpha)>>
|
||
|
|
: std::true_type
|
||
|
|
{ };
|
||
|
|
|
||
|
|
template <typename T, typename = void>
|
||
|
|
struct has_member_center : std::false_type
|
||
|
|
{ };
|
||
|
|
template <typename T>
|
||
|
|
struct has_member_center<T, std::void_t<decltype(std::declval<T>().center)>>
|
||
|
|
: std::true_type
|
||
|
|
{ };
|
||
|
|
|
||
|
|
template <typename T, typename = void>
|
||
|
|
struct has_member_beta : std::false_type
|
||
|
|
{ };
|
||
|
|
template <typename T>
|
||
|
|
struct has_member_beta<T, std::void_t<decltype(std::declval<T>().beta)>>
|
||
|
|
: std::true_type
|
||
|
|
{ };
|
||
|
|
|
||
|
|
template <typename T, typename = void>
|
||
|
|
struct has_both_dcf_halves : std::false_type
|
||
|
|
{ };
|
||
|
|
template <typename T>
|
||
|
|
struct has_both_dcf_halves<T, std::void_t<
|
||
|
|
decltype(std::declval<T>().key_a), decltype(std::declval<T>().key_b)>>
|
||
|
|
: std::true_type
|
||
|
|
{ };
|
||
|
|
|
||
|
|
bool tokens_equal(const dpf::proof_token & a, const dpf::proof_token & b)
|
||
|
|
{
|
||
|
|
return std::memcmp(a.data(), b.data(), sizeof(dpf::proof_token)) == 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void xor_first_byte(void * p)
|
||
|
|
{
|
||
|
|
auto * bytes = static_cast<unsigned char *>(p);
|
||
|
|
bytes[0] = static_cast<unsigned char>(bytes[0] ^ 0x1u);
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Out>
|
||
|
|
Out group_neg_one()
|
||
|
|
{
|
||
|
|
return -Out{1};
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Out>
|
||
|
|
void expect_point_near_modulus()
|
||
|
|
{
|
||
|
|
const auto beta = group_neg_one<Out>();
|
||
|
|
const std::uint8_t alpha = 0x2a;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||
|
|
const Out on = dpf::reconstruct(*dpf::eval_point(k0, alpha),
|
||
|
|
*dpf::eval_point(k1, alpha));
|
||
|
|
EXPECT_EQ(on, beta);
|
||
|
|
EXPECT_NE(on, Out{});
|
||
|
|
const Out off = dpf::reconstruct(*dpf::eval_point(k0, std::uint8_t{0}),
|
||
|
|
*dpf::eval_point(k1, std::uint8_t{0}));
|
||
|
|
EXPECT_EQ(off, Out{});
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Out>
|
||
|
|
void expect_comparison_keeps_negative_delta()
|
||
|
|
{
|
||
|
|
const auto beta = group_neg_one<Out>();
|
||
|
|
const std::uint8_t alpha = 10;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(beta));
|
||
|
|
const Out hot = dpf::reconstruct(
|
||
|
|
dpf::eval_point<Out>(dpf::cmp, k0, std::uint8_t{0}),
|
||
|
|
dpf::eval_point<Out>(dpf::cmp, k1, std::uint8_t{0}));
|
||
|
|
EXPECT_EQ(hot, beta);
|
||
|
|
EXPECT_NE(hot, Out{});
|
||
|
|
const Out cold = dpf::reconstruct(
|
||
|
|
dpf::eval_point<Out>(dpf::cmp, k0, std::uint8_t{11}),
|
||
|
|
dpf::eval_point<Out>(dpf::cmp, k1, std::uint8_t{11}));
|
||
|
|
EXPECT_EQ(cold, Out{});
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Out>
|
||
|
|
void expect_proof_binds_leaf_and_warm_path()
|
||
|
|
{
|
||
|
|
const std::uint8_t alpha = 0x2a;
|
||
|
|
const Out beta = Out{9};
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta, dpf::verifiable{});
|
||
|
|
using key_t = std::decay_t<decltype(k0)>;
|
||
|
|
|
||
|
|
dpf::proof_token cold0{};
|
||
|
|
dpf::proof_token cold1{};
|
||
|
|
(void)*dpf::eval_point(k0, alpha, dpf::prove(cold0));
|
||
|
|
(void)*dpf::eval_point(k1, alpha, dpf::prove(cold1));
|
||
|
|
EXPECT_TRUE(dpf::verify(cold0, cold1));
|
||
|
|
EXPECT_FALSE(dpf::verify(dpf::proof_token{}, dpf::proof_token{}));
|
||
|
|
|
||
|
|
dpf::basic_path_memoizer<key_t> memo;
|
||
|
|
(void)*dpf::eval_point(k0, alpha, memo);
|
||
|
|
dpf::proof_token warm{};
|
||
|
|
(void)*dpf::eval_point(k0, alpha, dpf::prove(warm), memo);
|
||
|
|
EXPECT_TRUE(tokens_equal(warm, cold0));
|
||
|
|
|
||
|
|
auto & leaves = const_cast<std::decay_t<decltype(k0.leaves())> &>(k0.leaves());
|
||
|
|
xor_first_byte(&std::get<0>(leaves).get());
|
||
|
|
dpf::proof_token tampered{};
|
||
|
|
(void)*dpf::eval_point(k0, alpha, dpf::prove(tampered), memo);
|
||
|
|
EXPECT_FALSE(dpf::verify(tampered, cold1));
|
||
|
|
EXPECT_FALSE(tokens_equal(tampered, cold0));
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Seeded>
|
||
|
|
void expect_from_seed_reads_past_first_word()
|
||
|
|
{
|
||
|
|
unsigned char lo[16]{};
|
||
|
|
unsigned char hi[16]{};
|
||
|
|
hi[8] = 1;
|
||
|
|
EXPECT_NE(Seeded::from_seed(lo, sizeof(lo)), Seeded::from_seed(hi, sizeof(hi)));
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Seeded>
|
||
|
|
void expect_from_seed_reads_past_16_bytes()
|
||
|
|
{
|
||
|
|
unsigned char lo[32]{};
|
||
|
|
unsigned char hi[32]{};
|
||
|
|
hi[24] = 1;
|
||
|
|
EXPECT_NE(Seeded::from_seed(lo, sizeof(lo)), Seeded::from_seed(hi, sizeof(hi)));
|
||
|
|
}
|
||
|
|
|
||
|
|
bool scalar_below_order(const dpf::p256_scalar & s)
|
||
|
|
{
|
||
|
|
for (int i = 3; i >= 0; --i)
|
||
|
|
{
|
||
|
|
const auto limb = s.limb(static_cast<std::size_t>(i));
|
||
|
|
const auto bound = dpf::p256_scalar::order[i];
|
||
|
|
if (limb < bound)
|
||
|
|
return true;
|
||
|
|
if (limb > bound)
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
struct IcPad
|
||
|
|
{
|
||
|
|
simde__m128i block() { return dpf::uniform_sample<simde__m128i>(); }
|
||
|
|
std::uint8_t bit() { return 0; }
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
TEST(ClassSweep, ModularLeafAndComparisonKeepTheField)
|
||
|
|
{
|
||
|
|
expect_point_near_modulus<dpf::fp61>();
|
||
|
|
expect_point_near_modulus<dpf::field64>();
|
||
|
|
expect_point_near_modulus<dpf::field128>();
|
||
|
|
expect_point_near_modulus<dpf::p256_scalar>();
|
||
|
|
expect_point_near_modulus<dpf::gf2>();
|
||
|
|
expect_point_near_modulus<dpf::gf22>();
|
||
|
|
expect_point_near_modulus<dpf::gf24>();
|
||
|
|
expect_point_near_modulus<dpf::gf28>();
|
||
|
|
expect_point_near_modulus<dpf::gf216>();
|
||
|
|
expect_point_near_modulus<dpf::gf232>();
|
||
|
|
expect_point_near_modulus<dpf::gf264>();
|
||
|
|
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::fp61>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::field64>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::field128>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::p256_scalar>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::gf2>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::gf22>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::gf24>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::gf28>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::gf216>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::gf232>();
|
||
|
|
expect_comparison_keeps_negative_delta<dpf::gf264>();
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, SimdLeafAddReducesInTheField)
|
||
|
|
{
|
||
|
|
alignas(16) std::uint64_t left[2] = {dpf::fp61_mod - 1, dpf::field64::mod - 3};
|
||
|
|
alignas(16) std::uint64_t right[2] = {4, 5};
|
||
|
|
simde__m128i a{};
|
||
|
|
simde__m128i b{};
|
||
|
|
std::memcpy(&a, left, sizeof(left));
|
||
|
|
std::memcpy(&b, right, sizeof(right));
|
||
|
|
|
||
|
|
alignas(16) std::uint64_t fp_out[2]{};
|
||
|
|
const auto fp_sum = dpf::leaf_arithmetic::add_t<dpf::fp61, simde__m128i>{}(a, b);
|
||
|
|
std::memcpy(fp_out, &fp_sum, sizeof(fp_out));
|
||
|
|
EXPECT_EQ(fp_out[0], dpf::fp61::reduce((dpf::fp61_mod - 1) + 4));
|
||
|
|
EXPECT_EQ(fp_out[1], dpf::fp61::reduce((dpf::field64::mod - 3) + 5));
|
||
|
|
|
||
|
|
alignas(16) std::uint64_t f64_left[2] = {dpf::field64::mod - 1, dpf::field64::mod - 4};
|
||
|
|
alignas(16) std::uint64_t f64_right[2] = {2, 6};
|
||
|
|
std::memcpy(&a, f64_left, sizeof(f64_left));
|
||
|
|
std::memcpy(&b, f64_right, sizeof(f64_right));
|
||
|
|
alignas(16) std::uint64_t f64_out[2]{};
|
||
|
|
const auto f64_sum = dpf::leaf_arithmetic::add_t<dpf::field64, simde__m128i>{}(a, b);
|
||
|
|
std::memcpy(f64_out, &f64_sum, sizeof(f64_out));
|
||
|
|
EXPECT_EQ(f64_out[0], 1u);
|
||
|
|
EXPECT_EQ(f64_out[1], 2u);
|
||
|
|
|
||
|
|
const auto wide = (static_cast<unsigned __int128>(dpf::field128::mod_hi) << 64)
|
||
|
|
| dpf::field128::mod_lo;
|
||
|
|
const dpf::field128 near{wide - 1};
|
||
|
|
const dpf::field128 step{3};
|
||
|
|
simde__m128i na{};
|
||
|
|
simde__m128i nb{};
|
||
|
|
std::memcpy(&na, &near, sizeof(na));
|
||
|
|
std::memcpy(&nb, &step, sizeof(nb));
|
||
|
|
const auto f128_sum = dpf::leaf_arithmetic::add_t<dpf::field128, simde__m128i>{}(na, nb);
|
||
|
|
dpf::field128 got{};
|
||
|
|
std::memcpy(&got, &f128_sum, sizeof(got));
|
||
|
|
EXPECT_EQ(got, near + step);
|
||
|
|
EXPECT_EQ(got, dpf::field128{2});
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, FromSeedConsumesBytesPastTheOldWindow)
|
||
|
|
{
|
||
|
|
expect_from_seed_reads_past_first_word<dpf::fp61>();
|
||
|
|
expect_from_seed_reads_past_first_word<dpf::field64>();
|
||
|
|
expect_from_seed_reads_past_first_word<dpf::field128>();
|
||
|
|
expect_from_seed_reads_past_first_word<dpf::p256_scalar>();
|
||
|
|
expect_from_seed_reads_past_first_word<dpf::p256>();
|
||
|
|
expect_from_seed_reads_past_16_bytes<dpf::p256_scalar>();
|
||
|
|
expect_from_seed_reads_past_16_bytes<dpf::p256>();
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, RejectionSampleStaysInTheScalarField)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < 32; ++i)
|
||
|
|
{
|
||
|
|
EXPECT_TRUE(scalar_below_order(dpf::uniform_sample<dpf::p256_scalar>()));
|
||
|
|
const auto s = -dpf::p256_scalar{1};
|
||
|
|
EXPECT_EQ(-(-s), s);
|
||
|
|
EXPECT_EQ(s, dpf::p256_scalar{1} - dpf::p256_scalar{2});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, MalformedCurveEncodingsAreRejected)
|
||
|
|
{
|
||
|
|
for (unsigned prefix : {0x00u, 0x01u, 0x04u, 0x05u})
|
||
|
|
{
|
||
|
|
unsigned char bad[33]{};
|
||
|
|
bad[0] = static_cast<unsigned char>(prefix);
|
||
|
|
bad[32] = 1;
|
||
|
|
EXPECT_THROW(dpf::p256::from_compressed(bad), std::invalid_argument) << prefix;
|
||
|
|
}
|
||
|
|
unsigned char off_curve[33]{};
|
||
|
|
off_curve[0] = 0x02;
|
||
|
|
off_curve[32] = 1;
|
||
|
|
EXPECT_THROW(dpf::p256::from_compressed(off_curve), std::invalid_argument);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, ExtractableCodomainIsTheSketchGroupOnly)
|
||
|
|
{
|
||
|
|
static_assert(dpf::extractable_codomain_ok_v<dpf::fp61>);
|
||
|
|
static_assert(dpf::extractable_codomain_ok_v<dpf::xor_wrapper<dpf::fp61>>);
|
||
|
|
static_assert(!dpf::extractable_codomain_ok_v<dpf::field64>);
|
||
|
|
static_assert(!dpf::extractable_codomain_ok_v<dpf::field128>);
|
||
|
|
static_assert(!dpf::extractable_codomain_ok_v<dpf::p256>);
|
||
|
|
static_assert(!dpf::extractable_codomain_ok_v<dpf::p256_scalar>);
|
||
|
|
static_assert(!dpf::extractable_codomain_ok_v<std::uint64_t>);
|
||
|
|
static_assert(!dpf::extractable_codomain_ok_v<float>);
|
||
|
|
static_assert(!dpf::extractable_codomain_ok_v<double>);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, FloatAndDoubleLeavesUseTheXorGroup)
|
||
|
|
{
|
||
|
|
const float xf = dpf::leaf_group_add(1.0f, 2.0f);
|
||
|
|
const float ieee_f = 1.0f + 2.0f;
|
||
|
|
EXPECT_NE(xf, ieee_f);
|
||
|
|
std::uint32_t fb = 0;
|
||
|
|
std::memcpy(&fb, &xf, sizeof(fb));
|
||
|
|
EXPECT_EQ(fb, 0x3f800000u ^ 0x40000000u);
|
||
|
|
|
||
|
|
const double xd = dpf::leaf_group_add(1.0, 2.0);
|
||
|
|
EXPECT_NE(xd, 1.0 + 2.0);
|
||
|
|
std::uint64_t db = 0;
|
||
|
|
std::memcpy(&db, &xd, sizeof(db));
|
||
|
|
EXPECT_EQ(db, 0x3ff0000000000000ull ^ 0x4000000000000000ull);
|
||
|
|
|
||
|
|
simde__m128i a = simde_mm_set1_epi32(static_cast<int>(0x3f800000));
|
||
|
|
simde__m128i b = simde_mm_set1_epi32(static_cast<int>(0x40000000));
|
||
|
|
const auto packed = dpf::leaf_arithmetic::add_t<float, simde__m128i>{}(a, b);
|
||
|
|
const auto expect = simde_mm_xor_si128(a, b);
|
||
|
|
EXPECT_EQ(std::memcmp(&packed, &expect, sizeof(packed)), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, ProofsBindEveryEvalEntry)
|
||
|
|
{
|
||
|
|
expect_proof_binds_leaf_and_warm_path<std::uint32_t>();
|
||
|
|
expect_proof_binds_leaf_and_warm_path<std::uint64_t>();
|
||
|
|
expect_proof_binds_leaf_and_warm_path<dpf::fp61>();
|
||
|
|
expect_proof_binds_leaf_and_warm_path<dpf::field64>();
|
||
|
|
|
||
|
|
const std::uint8_t alpha = 12;
|
||
|
|
auto [c0, c1] = dpf::make_dpf(alpha, dpf::lt(std::uint64_t{9}), dpf::verifiable{});
|
||
|
|
dpf::proof_token before{};
|
||
|
|
dpf::proof_token after{};
|
||
|
|
(void)dpf::eval_point(dpf::cmp, c0, std::uint8_t{3}, dpf::prove(before));
|
||
|
|
auto & words = const_cast<std::decay_t<decltype(c0.value_cw())> &>(c0.value_cw());
|
||
|
|
xor_first_byte(&words[0]);
|
||
|
|
(void)dpf::eval_point(dpf::cmp, c0, std::uint8_t{3}, dpf::prove(after));
|
||
|
|
EXPECT_FALSE(tokens_equal(before, after));
|
||
|
|
|
||
|
|
dpf::proof_token other{};
|
||
|
|
(void)dpf::eval_point(dpf::cmp, c1, std::uint8_t{3}, dpf::prove(other));
|
||
|
|
EXPECT_FALSE(dpf::verify(after, other));
|
||
|
|
|
||
|
|
auto [p0, p1] = dpf::make_dpf(std::uint8_t{4}, std::uint32_t{3}, dpf::verifiable{});
|
||
|
|
const std::uint8_t from = 1;
|
||
|
|
const std::uint8_t to = 6;
|
||
|
|
dpf::proof_token i0{};
|
||
|
|
dpf::proof_token i1{};
|
||
|
|
dpf::prove_interval(p0, from, to, dpf::prove(i0));
|
||
|
|
dpf::prove_interval(p1, from, to, dpf::prove(i1));
|
||
|
|
EXPECT_TRUE(dpf::verify(i0, i1));
|
||
|
|
EXPECT_FALSE(dpf::verify(dpf::proof_token{}, dpf::proof_token{}));
|
||
|
|
|
||
|
|
dpf::proof_token full0{};
|
||
|
|
dpf::proof_token full1{};
|
||
|
|
dpf::prove_full(p0, dpf::prove(full0));
|
||
|
|
dpf::prove_full(p1, dpf::prove(full1));
|
||
|
|
EXPECT_TRUE(dpf::verify(full0, full1));
|
||
|
|
|
||
|
|
std::array<std::uint8_t, 4> seq{2, 3, 4, 9};
|
||
|
|
dpf::proof_token s0{};
|
||
|
|
dpf::proof_token s1{};
|
||
|
|
dpf::prove_sequence(p0, seq.begin(), seq.end(), dpf::prove(s0));
|
||
|
|
dpf::prove_sequence(p1, seq.begin(), seq.end(), dpf::prove(s1));
|
||
|
|
EXPECT_TRUE(dpf::verify(s0, s1));
|
||
|
|
dpf::proof_token s0_again{};
|
||
|
|
dpf::prove_sequence(p0, seq.begin(), seq.end(), dpf::prove(s0_again));
|
||
|
|
EXPECT_TRUE(tokens_equal(s0, s0_again));
|
||
|
|
|
||
|
|
// Packed leaves may touch slots next to [from, to]. The token does not use the weights.
|
||
|
|
std::vector<std::uint32_t> weights(256, 1u);
|
||
|
|
dpf::proof_token dot{};
|
||
|
|
(void)dpf::eval_inner_product(p0, from, to, weights, dpf::prove(dot));
|
||
|
|
EXPECT_TRUE(tokens_equal(dot, i0));
|
||
|
|
|
||
|
|
dpf::proof_token a0{};
|
||
|
|
dpf::proof_token a1{};
|
||
|
|
dpf::proof_token b0{};
|
||
|
|
dpf::proof_token b1{};
|
||
|
|
(void)*dpf::eval_point(p0, std::uint8_t{4}, dpf::prove(a0));
|
||
|
|
(void)*dpf::eval_point(p1, std::uint8_t{4}, dpf::prove(b0));
|
||
|
|
(void)*dpf::eval_point(p0, std::uint8_t{5}, dpf::prove(a1));
|
||
|
|
(void)*dpf::eval_point(p1, std::uint8_t{5}, dpf::prove(b1));
|
||
|
|
std::array<dpf::proof_token, 2> left{a0, a1};
|
||
|
|
std::array<dpf::proof_token, 2> right{b0, b1};
|
||
|
|
EXPECT_TRUE(dpf::verify_batch(left, right));
|
||
|
|
// A swap of equal second halves is a no-op. Flip one byte of each half
|
||
|
|
// so a batch that folds only token[0] still accepts the second-half flip.
|
||
|
|
auto flipped_lo = left;
|
||
|
|
xor_first_byte(&flipped_lo[0][0]);
|
||
|
|
EXPECT_FALSE(dpf::verify_batch(flipped_lo, right));
|
||
|
|
auto flipped_hi = left;
|
||
|
|
xor_first_byte(&flipped_hi[1][1]);
|
||
|
|
EXPECT_FALSE(dpf::verify_batch(flipped_hi, right));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, OneComparisonHalfIsNotThePredicate)
|
||
|
|
{
|
||
|
|
const std::uint8_t thresh = 40;
|
||
|
|
const std::uint64_t if_true = 19;
|
||
|
|
auto keys = dpf::make_dpf3_cmp(thresh, if_true, std::uint64_t{0});
|
||
|
|
auto & k1 = std::get<0>(keys);
|
||
|
|
auto & k2 = std::get<1>(keys);
|
||
|
|
auto & k3 = std::get<2>(keys);
|
||
|
|
static_assert(!has_both_dcf_halves<std::decay_t<decltype(k1)>>::value);
|
||
|
|
static_assert(!has_both_dcf_halves<std::decay_t<decltype(k2)>>::value);
|
||
|
|
|
||
|
|
const auto full1 = dpf::eval_full(k1);
|
||
|
|
ASSERT_EQ(full1.size(), 256u);
|
||
|
|
int hidden = 0;
|
||
|
|
for (unsigned x = 0; x < 256; ++x)
|
||
|
|
{
|
||
|
|
const auto q = static_cast<std::uint8_t>(x);
|
||
|
|
dpf::basic_path_memoizer<std::decay_t<decltype(k1.dpf_key)>> memo;
|
||
|
|
const auto s1 = dpf::eval_dpf3_cmp(k1, q, memo);
|
||
|
|
const auto s1_again = dpf::eval_dpf3_cmp(k1, q, memo);
|
||
|
|
const auto s2 = dpf::eval_dpf3_cmp(k2, q);
|
||
|
|
const auto s3 = dpf::eval_dpf3_cmp(k3, q);
|
||
|
|
EXPECT_EQ(s1, s1_again);
|
||
|
|
EXPECT_EQ(s1, s3);
|
||
|
|
EXPECT_EQ(s1, full1[x]);
|
||
|
|
const auto opened = dpf::reconstruct_cmp_halves(s1, s2);
|
||
|
|
const dpf::fp61 want = x < thresh ? dpf::fp61{if_true} : dpf::fp61{};
|
||
|
|
EXPECT_EQ(opened, want) << x;
|
||
|
|
if (dpf::fp61{s1} != want && dpf::fp61{s2} != want)
|
||
|
|
++hidden;
|
||
|
|
}
|
||
|
|
EXPECT_GT(hidden, 200);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, OpenedResultsDoNotCarryTheSecretPoint)
|
||
|
|
{
|
||
|
|
static_assert(!has_member_alpha<dpf::geneval_result<std::uint32_t, simde__m128i>>::value);
|
||
|
|
static_assert(!has_member_beta<dpf::geneval_result<std::uint32_t, simde__m128i>>::value);
|
||
|
|
static_assert(!has_member_alpha<dpf::geneval_cmp_result>::value);
|
||
|
|
static_assert(!has_member_center<grotto::geneval_offset_horner_result<2, std::uint8_t>>::value);
|
||
|
|
static_assert(!has_member_center<grotto::geneval_lut_union_result<std::uint8_t>>::value);
|
||
|
|
static_assert(!has_member_center<grotto::geneval_offset_horner_result<3, std::int8_t>>::value);
|
||
|
|
static_assert(!has_member_center<grotto::offset_horner_keys<std::uint8_t, 2, false>>::value);
|
||
|
|
static_assert(!has_member_center<grotto::offset_poly_keys<std::uint8_t>>::value);
|
||
|
|
static_assert(!has_member_center<grotto::offset_jet_keys<std::uint8_t>>::value);
|
||
|
|
static_assert(!has_member_center<grotto::offset_repr_keys<std::uint8_t>>::value);
|
||
|
|
static_assert(!has_member_center<grotto::offset_twist_keys<std::uint8_t>>::value);
|
||
|
|
|
||
|
|
std::vector<std::uint8_t> none;
|
||
|
|
auto empty = dpf::geneval_ic(std::uint8_t{1}, std::uint8_t{2},
|
||
|
|
none.begin(), none.end(),
|
||
|
|
dpf::ds_randomness<decltype(&dpf::uniform_sample<simde__m128i>), IcPad>{
|
||
|
|
&dpf::uniform_sample<simde__m128i>, {}},
|
||
|
|
dpf::ic(std::uint8_t{0}, std::uint8_t{4}, std::uint32_t{1}, std::uint32_t{0}));
|
||
|
|
EXPECT_FALSE(dpf::verify(empty.proof0, empty.proof1));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, ConstrainedComparisonAbortsUnlessAdjacent)
|
||
|
|
{
|
||
|
|
const std::uint64_t samples[][2] = {
|
||
|
|
{0, 0}, {0, 2}, {5, 8}, {100, 0},
|
||
|
|
{std::numeric_limits<std::uint64_t>::max(), 0},
|
||
|
|
{20, 20},
|
||
|
|
};
|
||
|
|
for (const auto & pair : samples)
|
||
|
|
EXPECT_THROW(dpf::local_ccmp(pair[0], pair[1]), std::invalid_argument)
|
||
|
|
<< pair[0] << "," << pair[1];
|
||
|
|
|
||
|
|
EXPECT_NO_THROW(dpf::local_ccmp(4, 5));
|
||
|
|
EXPECT_NO_THROW(dpf::local_ccmp(5, 4));
|
||
|
|
EXPECT_NO_THROW(dpf::local_ccmp(0, 1));
|
||
|
|
EXPECT_NO_THROW(dpf::local_ccmp(
|
||
|
|
std::numeric_limits<std::uint64_t>::max(),
|
||
|
|
std::numeric_limits<std::uint64_t>::max() - 1));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, SignedJetMatchesClearForEveryDegreeAtLeastTwo)
|
||
|
|
{
|
||
|
|
const std::int8_t centers[] = {-40, -7, -1, 3};
|
||
|
|
const int etas[] = {-12, 0, 5, 18};
|
||
|
|
for (std::size_t degree = 2; degree <= 3; ++degree)
|
||
|
|
{
|
||
|
|
std::vector<std::uint64_t> coeff(degree + 1, 1);
|
||
|
|
coeff[2] = 3;
|
||
|
|
const std::vector<std::int8_t> knots{std::numeric_limits<std::int8_t>::min()};
|
||
|
|
for (std::int8_t center : centers)
|
||
|
|
{
|
||
|
|
const auto mat = grotto::make_offset_jet_keys<std::int8_t>(center, degree);
|
||
|
|
for (int eta : etas)
|
||
|
|
{
|
||
|
|
const auto e = static_cast<std::int8_t>(eta);
|
||
|
|
const auto s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, e);
|
||
|
|
const auto s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, e);
|
||
|
|
const auto clear = grotto::offset_jet_clear<std::int8_t>(
|
||
|
|
center, knots, coeff, e);
|
||
|
|
EXPECT_EQ(s0 + s1, clear)
|
||
|
|
<< "degree=" << degree << " center=" << int(center) << " eta=" << eta;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, DyadicShiftMatchesClearAcrossDegrees)
|
||
|
|
{
|
||
|
|
const std::uint8_t centers[] = {1, 4, 200};
|
||
|
|
const int etas[] = {0, 3, 39, 100};
|
||
|
|
for (std::size_t degree = 0; degree <= 2; ++degree)
|
||
|
|
{
|
||
|
|
std::vector<std::uint64_t> coeff(degree + 1, 1);
|
||
|
|
if (degree >= 1)
|
||
|
|
coeff[1] = 3;
|
||
|
|
const std::vector<std::uint8_t> knots{0};
|
||
|
|
for (std::uint8_t center : centers)
|
||
|
|
{
|
||
|
|
const auto mat = grotto::make_offset_twist_keys<std::uint8_t>(
|
||
|
|
center, degree, grotto::twist_half);
|
||
|
|
for (int eta : etas)
|
||
|
|
{
|
||
|
|
const auto e = static_cast<std::uint8_t>(eta);
|
||
|
|
const auto s0 = grotto::offset_twist_eval<0>(mat, knots, coeff, e);
|
||
|
|
const auto s1 = grotto::offset_twist_eval<1>(mat, knots, coeff, e);
|
||
|
|
const auto clear = grotto::offset_twist_clear(
|
||
|
|
center, grotto::twist_half, knots, coeff, e);
|
||
|
|
EXPECT_EQ(s0 + s1, clear)
|
||
|
|
<< "degree=" << degree << " center=" << int(center) << " eta=" << eta;
|
||
|
|
EXPECT_NE(s0, clear);
|
||
|
|
EXPECT_NE(s1, clear);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(ClassSweep, HornerSharesMatchClearAndHideTheCenter)
|
||
|
|
{
|
||
|
|
constexpr std::size_t D = 2;
|
||
|
|
const std::uint8_t center = 9;
|
||
|
|
const auto mat = grotto::make_offset_horner_keys<std::uint8_t, D>(center);
|
||
|
|
const std::vector<std::uint8_t> knots{0, 40};
|
||
|
|
const std::vector<std::array<std::uint64_t, D + 1>> coeff{
|
||
|
|
{1, 2, 0},
|
||
|
|
{4, 0, 1},
|
||
|
|
};
|
||
|
|
for (std::uint8_t eta : {std::uint8_t{0}, std::uint8_t{3}, std::uint8_t{70}})
|
||
|
|
{
|
||
|
|
const auto s0 = grotto::offset_horner_eval<0, D>(mat, knots, coeff, eta);
|
||
|
|
const auto s1 = grotto::offset_horner_eval<1, D>(mat, knots, coeff, eta);
|
||
|
|
const auto clear = grotto::offset_horner_clear<D>(center, knots, coeff, eta);
|
||
|
|
EXPECT_EQ(s0 + s1, clear) << int(eta);
|
||
|
|
}
|
||
|
|
}
|