libdpf/test/tests/geneval_test.cpp
Ryan Henry 875f09fec1 Record Grotto half-ulp tables and comparison geneval, and factor shared beaver terms before the quotient.
Horner and window evaluation need those tables in the tree. Comparison geneval opens the same value words as a Doerner–Shelat key. A factor common to every polynomial term is multiplied first so that preprocessing stays smaller.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 15:16:21 -06:00

1225 lines
42 KiB
C++

#include <gtest/gtest.h>
#include "dpf.hpp"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <cstring>
#include <vector>
namespace
{
simde__m128i g_roots[8];
int g_ri = 0;
simde__m128i take_root() { return g_roots[g_ri++]; }
struct Pad
{
uint64_t n = 1;
simde__m128i block()
{
auto v = simde_mm_set_epi64x(static_cast<long long>(n),
static_cast<long long>(n * 9 + 3));
n += 2;
return v;
}
uint8_t bit() { return static_cast<uint8_t>(n++ & 1u); }
};
void reset_roots()
{
g_ri = 0;
for (int i = 0; i < 8; ++i)
g_roots[i] = simde_mm_set_epi64x(0x1111 * (i + 1), 0xA5A50000u + i * 17);
}
template <typename T>
T bare(const T & v)
{
return v;
}
template <typename T, std::size_t Party, dpf::sharing Scheme>
T bare(const dpf::secret_share<T, Party, Scheme> & s)
{
return s.raw();
}
template <typename A, typename B>
auto recon(const A & a, const B & b)
{
using T = decltype(bare(a));
// Subtractive reconstruction: party 0 minus party 1.
return static_cast<T>(bare(a) - bare(b));
}
template <typename Key, typename In>
auto ev(const Key & key, In x)
{
return bare(*dpf::eval_point(key, x));
}
template <typename Key>
uint64_t leaf_of(typename Key::input_type x)
{
dpf::utils::flip_msb_if_signed_integral(x);
return static_cast<uint64_t>(dpf::utils::get_from_node<Key>(x));
}
std::size_t lcp_bits(uint64_t a, uint64_t b, std::size_t depth)
{
std::size_t n = 0;
for (std::size_t i = 0; i < depth; ++i)
{
const std::size_t sh = depth - 1 - i;
if (((a >> sh) & 1ull) != ((b >> sh) & 1ull))
break;
++n;
}
return n;
}
std::size_t live_through_lcp(std::size_t lcp, std::size_t depth)
{
return std::min(depth, lcp + 1);
}
template <typename Key>
void expect_prefix_words(const Key & key, const std::vector<simde__m128i,
dpf::aligned_allocator<simde__m128i>> & cws,
const std::vector<uint8_t> & advice, std::size_t live, bool leaf_live,
const void * leaf, std::size_t leaf_bytes)
{
ASSERT_LE(live, cws.size());
ASSERT_EQ(advice.size(), cws.size());
for (std::size_t i = 0; i < live; ++i)
{
EXPECT_EQ(std::memcmp(&cws[i], &key.correction_word(i), sizeof(simde__m128i)), 0)
<< "cw " << i;
EXPECT_EQ(advice[i], key.correction_advice(i)) << "advice " << i;
}
if (leaf_live)
{
EXPECT_EQ(live, Key::depth);
EXPECT_EQ(std::memcmp(leaf, &key.template leaf<0>(), leaf_bytes), 0);
}
}
template <typename T>
dpf::ds_randomness<simde__m128i (*)(), Pad> rng()
{
return {take_root, Pad{}};
}
} // namespace
TEST(Geneval, PointOnTargetMatchesKeyAndEval)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 0x0abc;
in_t x0 = 0x1111;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 0x4242;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(x0, x1, alpha, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
const auto id = leaf_of<key_t>(alpha);
const std::size_t live = live_through_lcp(lcp_bits(id, id, key_t::depth), key_t::depth);
EXPECT_EQ(g.live_levels, live);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, g.leaf_live, &g.leaf, sizeof(g.leaf));
auto e0 = ev(keys.first, alpha);
auto e1 = ev(keys.second, alpha);
ASSERT_EQ(g.party0.size(), 1u);
EXPECT_EQ(g.party0[0], e0);
EXPECT_EQ(g.party1[0], e1);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), y);
}
TEST(Geneval, PointDivergesAfterSharedPrefix)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 0x0abc;
in_t query = 0x0a7e;
in_t x0 = 0x00ff;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 7;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(x0, x1, query, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(query), key_t::depth),
key_t::depth);
EXPECT_LT(live, key_t::depth);
EXPECT_EQ(g.live_levels, live);
EXPECT_FALSE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, false, nullptr, 0);
auto e0 = ev(keys.first, query);
auto e1 = ev(keys.second, query);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), recon(e0, e1));
EXPECT_EQ(recon(g.party0[0], g.party1[0]), out_t{0});
}
TEST(Geneval, SameLeafDifferentLane)
{
using in_t = uint16_t;
using out_t = uint8_t;
in_t alpha = 0x1234;
in_t query = static_cast<in_t>(alpha ^ 1u);
in_t x0 = 0x0101;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 9;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
using key_t = std::decay_t<decltype(keys.first)>;
ASSERT_GT(key_t::lg_outputs_per_leaf, 0u);
ASSERT_EQ(leaf_of<key_t>(alpha), leaf_of<key_t>(query));
reset_roots();
auto g = dpf::geneval_point(x0, x1, query, rng<in_t>(), y);
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
auto e0 = ev(keys.first, query);
auto e1 = ev(keys.second, query);
EXPECT_EQ(g.party0[0], e0);
EXPECT_EQ(g.party1[0], e1);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), out_t{0});
}
TEST(Geneval, IntervalContainsTarget)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 1000;
in_t from = 990;
in_t to = 1010;
in_t x0 = 0x0f0f;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 33;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_interval(x0, x1, from, to, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
ASSERT_EQ(g.party0.size(), static_cast<std::size_t>(to - from) + 1);
for (in_t q = from; ; ++q)
{
const std::size_t i = static_cast<std::size_t>(q - from);
auto e0 = ev(keys.first, q);
auto e1 = ev(keys.second, q);
EXPECT_EQ(g.party0[i], e0) << q;
EXPECT_EQ(g.party1[i], e1) << q;
const out_t want = (q == alpha) ? y : out_t{0};
EXPECT_EQ(recon(g.party0[i], g.party1[i]), want) << q;
if (q == to)
break;
}
}
TEST(Geneval, IntervalMissesAfterSharedPrefix)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 0x8000;
in_t from = 0x8100;
in_t to = 0x8108;
in_t x0 = 1;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 5;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_interval(x0, x1, from, to, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
const auto a = leaf_of<key_t>(alpha);
const auto q = leaf_of<key_t>(from);
const auto live = live_through_lcp(lcp_bits(a, q, key_t::depth), key_t::depth);
EXPECT_LT(g.live_levels, key_t::depth);
EXPECT_EQ(g.live_levels, live);
EXPECT_FALSE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, false, nullptr, 0);
for (std::size_t i = 0; i < g.party0.size(); ++i)
EXPECT_EQ(recon(g.party0[i], g.party1[i]), out_t{0}) << i;
}
TEST(Geneval, SequenceOrderAndSharedTrie)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 0x2222;
in_t x0 = 0x00aa;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 11;
const in_t xs[] = {0x2200, alpha, 0x00ff, alpha, 0x2223};
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_sequence(x0, x1, std::begin(xs), std::end(xs), rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_TRUE(g.leaf_live);
EXPECT_EQ(g.live_levels, key_t::depth);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
ASSERT_EQ(g.party0.size(), 5u);
for (std::size_t i = 0; i < 5; ++i)
{
auto e0 = ev(keys.first, xs[i]);
auto e1 = ev(keys.second, xs[i]);
EXPECT_EQ(g.party0[i], e0);
EXPECT_EQ(g.party1[i], e1);
EXPECT_EQ(recon(g.party0[i], g.party1[i]), xs[i] == alpha ? y : out_t{0});
}
reset_roots();
auto miss = dpf::geneval_sequence(x0, x1, xs, xs + 1, rng<in_t>(), y);
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(xs[0]), key_t::depth),
key_t::depth);
EXPECT_EQ(miss.live_levels, live);
EXPECT_LT(miss.live_levels, key_t::depth);
expect_prefix_words(keys.first, miss.correction_words, miss.correction_advice,
miss.live_levels, false, nullptr, 0);
EXPECT_EQ(recon(miss.party0[0], miss.party1[0]), out_t{0});
}
TEST(Geneval, FullDomainUint8)
{
using in_t = uint8_t;
using out_t = uint8_t;
in_t alpha = 0x3c;
in_t x0 = 0x10;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 0x7e;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_full(x0, x1, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.party0.size(), 256u);
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
for (int q = 0; q < 256; ++q)
{
auto e0 = ev(keys.first, static_cast<in_t>(q));
auto e1 = ev(keys.second, static_cast<in_t>(q));
EXPECT_EQ(g.party0[q], e0);
EXPECT_EQ(g.party1[q], e1);
}
EXPECT_EQ(recon(g.party0[alpha], g.party1[alpha]), y);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), out_t{0});
}
TEST(Geneval, EmptySequence)
{
using in_t = uint16_t;
in_t alpha = 1;
in_t x0 = 2;
in_t x1 = static_cast<in_t>(alpha ^ x0);
const in_t * p = nullptr;
reset_roots();
auto g = dpf::geneval_sequence(x0, x1, p, p, rng<in_t>(), uint16_t{1});
EXPECT_TRUE(g.party0.empty());
EXPECT_EQ(g.live_levels, 0u);
EXPECT_TRUE(g.correction_words.empty());
}
TEST(Geneval, WildcardPointMatchesShiftedEval)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t x = 0x1357;
in_t x0 = 0x0100;
in_t x1 = static_cast<in_t>(x - x0);
in_t alpha = 0xabcd;
in_t query = 0x2000;
out_t y = 99;
const in_t delta = static_cast<in_t>(alpha - x);
const in_t shifted = static_cast<in_t>(query + delta);
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(dpf::wildcard_input, x0, x1, query, rng<in_t>(),
[&] { return alpha; }, y);
using key_t = std::decay_t<decltype(keys.first)>;
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(shifted), key_t::depth),
key_t::depth);
EXPECT_EQ(g.live_levels, live);
EXPECT_LT(live, key_t::depth);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, g.leaf_live, &g.leaf, sizeof(g.leaf));
auto e0 = ev(keys.first, shifted);
auto e1 = ev(keys.second, shifted);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), recon(e0, e1));
dpf::wildcard_value<in_t> slot{alpha};
reset_roots();
auto wild = dpf::make_dpf(slot, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto s0 = wild.first.offset_x.compute_and_get_share(x0);
auto s1 = wild.second.offset_x.compute_and_get_share(x1);
wild.first.offset_x.reconstruct(s1);
wild.second.offset_x.reconstruct(s0);
auto w0 = ev(wild.first, query);
auto w1 = ev(wild.second, query);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), recon(w0, w1));
expect_prefix_words(wild.first, g.correction_words, g.correction_advice,
g.live_levels, false, nullptr, 0);
}
TEST(Geneval, WildcardPointOnSecretIsFullKey)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t x = 0x42;
in_t x0 = 0x10;
in_t x1 = static_cast<in_t>(x - x0);
in_t alpha = 0x1111;
out_t y = 8;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(dpf::wildcard_input, x0, x1, x, rng<in_t>(),
[&] { return alpha; }, y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
auto e0 = ev(keys.first, alpha);
auto e1 = ev(keys.second, alpha);
EXPECT_EQ(g.party0[0], e0);
EXPECT_EQ(g.party1[0], e1);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), y);
}
TEST(Geneval, WildcardIntervalAndSequence)
{
using in_t = uint8_t;
using out_t = uint8_t;
in_t x = 40;
in_t x0 = 7;
in_t x1 = static_cast<in_t>(x - x0);
in_t alpha = 200;
out_t y = 3;
const in_t delta = static_cast<in_t>(alpha - x);
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto iv = dpf::geneval_interval(dpf::wildcard_input, x0, x1, in_t{10}, in_t{20},
rng<in_t>(), [&] { return alpha; }, y);
using key_t = std::decay_t<decltype(keys.first)>;
ASSERT_EQ(iv.party0.size(), 11u);
for (in_t q = 10; q <= 20; ++q)
{
const in_t shifted = static_cast<in_t>(q + delta);
const std::size_t i = static_cast<std::size_t>(q - 10);
auto e0 = ev(keys.first, shifted);
auto e1 = ev(keys.second, shifted);
EXPECT_EQ(recon(iv.party0[i], iv.party1[i]), recon(e0, e1)) << int(q);
}
const in_t shifted_from = static_cast<in_t>(10 + delta);
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(shifted_from), key_t::depth),
key_t::depth);
EXPECT_EQ(iv.live_levels, live);
expect_prefix_words(keys.first, iv.correction_words, iv.correction_advice,
iv.live_levels, iv.leaf_live, &iv.leaf, sizeof(iv.leaf));
const in_t seq[] = {1, x, 255, 2};
reset_roots();
auto sq = dpf::geneval_sequence(dpf::wildcard_input, x0, x1,
std::begin(seq), std::end(seq), rng<in_t>(), [&] { return alpha; }, y);
ASSERT_EQ(sq.party0.size(), 4u);
EXPECT_TRUE(sq.leaf_live);
EXPECT_EQ(sq.live_levels, key_t::depth);
expect_prefix_words(keys.first, sq.correction_words, sq.correction_advice,
sq.live_levels, true, &sq.leaf, sizeof(sq.leaf));
for (std::size_t i = 0; i < 4; ++i)
{
const in_t shifted = static_cast<in_t>(seq[i] + delta);
auto e0 = ev(keys.first, shifted);
auto e1 = ev(keys.second, shifted);
EXPECT_EQ(sq.party0[i], e0);
EXPECT_EQ(sq.party1[i], e1);
EXPECT_EQ(recon(sq.party0[i], sq.party1[i]), seq[i] == x ? y : out_t{0});
}
}
TEST(Geneval, WildcardFullRotates)
{
using in_t = uint8_t;
using out_t = uint8_t;
in_t x = 40;
in_t x0 = 7;
in_t x1 = static_cast<in_t>(x - x0);
in_t alpha = 200;
out_t y = 3;
const in_t delta = static_cast<in_t>(alpha - x);
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_full(dpf::wildcard_input, x0, x1, rng<in_t>(),
[&] { return alpha; }, y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.party0.size(), 256u);
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
EXPECT_EQ(recon(g.party0[x], g.party1[x]), y);
for (int q = 0; q < 256; ++q)
{
const in_t shifted = static_cast<in_t>(static_cast<in_t>(q) + delta);
auto e0 = ev(keys.first, shifted);
auto e1 = ev(keys.second, shifted);
EXPECT_EQ(g.party0[q], e0);
EXPECT_EQ(g.party1[q], e1);
}
}
TEST(Geneval, DoernerShelatKeyAgreesOnLivePrefix)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 0x55aa;
in_t query = 0x5500;
in_t x0 = 0x1234;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 1;
reset_roots();
dpf::ds_randomness<simde__m128i (*)(), Pad> ds_rng{take_root, Pad{}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, ds_rng, y);
reset_roots();
auto g = dpf::geneval_point(x0, x1, query, rng<in_t>(), y);
using key_t = std::decay_t<decltype(ds.first)>;
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(query), key_t::depth),
key_t::depth);
EXPECT_EQ(g.live_levels, live);
EXPECT_GT(live, 0u);
EXPECT_LT(live, key_t::depth);
expect_prefix_words(ds.first, g.correction_words, g.correction_advice,
g.live_levels, false, nullptr, 0);
}
struct PadB
{
uint64_t n = 99;
simde__m128i block()
{
auto v = simde_mm_set_epi64x(static_cast<long long>(n * 7),
static_cast<long long>(n ^ 0x5a5a));
n += 3;
return v;
}
uint8_t bit() { return static_cast<uint8_t>((n++ >> 2) & 1u); }
};
template <typename Key, typename In>
std::size_t best_live(const Key &, In alpha, const std::vector<In> & qs)
{
using key_t = Key;
const auto a = leaf_of<key_t>(alpha);
std::size_t best = 0;
for (In q : qs)
best = std::max(best, lcp_bits(a, leaf_of<key_t>(q), key_t::depth));
return live_through_lcp(best, key_t::depth);
}
template <typename T>
std::vector<T> span_inclusive(T from, T to)
{
std::vector<T> qs;
for (T q = from; ; ++q)
{
qs.push_back(q);
if (q == to)
break;
}
return qs;
}
TEST(Geneval, EdgesZeroMaxAndSinglePointShapesAgree)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 0;
in_t x0 = 0xffff;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 0x1111;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
using key_t = std::decay_t<decltype(keys.first)>;
for (in_t q : {in_t{0}, in_t{1}, in_t{0x8000}, in_t{0xffff}})
{
reset_roots();
auto pt = dpf::geneval_point(x0, x1, q, rng<in_t>(), y);
reset_roots();
auto iv = dpf::geneval_interval(x0, x1, q, q, rng<in_t>(), y);
const in_t seq[] = {q};
reset_roots();
auto sq = dpf::geneval_sequence(x0, x1, std::begin(seq), std::end(seq),
rng<in_t>(), y);
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(q), key_t::depth),
key_t::depth);
EXPECT_EQ(pt.live_levels, live) << q;
EXPECT_EQ(iv.live_levels, live) << q;
EXPECT_EQ(sq.live_levels, live) << q;
EXPECT_EQ(pt.correction_words.size(), key_t::depth);
EXPECT_EQ(std::memcmp(pt.correction_words.data(), iv.correction_words.data(),
key_t::depth * sizeof(simde__m128i)), 0) << q;
EXPECT_EQ(std::memcmp(pt.correction_words.data(), sq.correction_words.data(),
key_t::depth * sizeof(simde__m128i)), 0) << q;
EXPECT_EQ(pt.correction_advice, iv.correction_advice);
EXPECT_EQ(pt.correction_advice, sq.correction_advice);
EXPECT_EQ(recon(pt.party0[0], pt.party1[0]), q == alpha ? y : out_t{0});
EXPECT_EQ(recon(iv.party0[0], iv.party1[0]), recon(pt.party0[0], pt.party1[0]));
EXPECT_EQ(recon(sq.party0[0], sq.party1[0]), recon(pt.party0[0], pt.party1[0]));
if (live < key_t::depth)
{
EXPECT_NE(std::memcmp(&pt.correction_words[live],
&keys.first.correction_word(live), sizeof(simde__m128i)), 0) << q;
auto e0 = ev(keys.first, q);
EXPECT_NE(pt.party0[0], e0) << q;
}
}
}
TEST(Geneval, XorSplitAndPadStreamDoNotChangeLiveWords)
{
using in_t = uint16_t;
using out_t = uint32_t;
in_t alpha = 0x0f0f;
in_t query = 0x0e00;
out_t y = 0xabcdef01u;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
using key_t = std::decay_t<decltype(keys.first)>;
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(query), key_t::depth),
key_t::depth);
ASSERT_GT(live, 0u);
ASSERT_LT(live, key_t::depth);
auto check = [&](in_t x0, auto pad, const char * name) {
in_t x1 = static_cast<in_t>(alpha ^ x0);
reset_roots();
dpf::ds_randomness<simde__m128i (*)(), decltype(pad)> r{take_root, pad};
auto g = dpf::geneval_point(x0, x1, query, r, y);
EXPECT_EQ(g.live_levels, live) << name;
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
live, false, nullptr, 0);
EXPECT_NE(std::memcmp(&g.correction_words[live],
&keys.first.correction_word(live), sizeof(simde__m128i)), 0) << name;
};
check(0, Pad{}, "share 0");
check(alpha, Pad{}, "share alpha");
check(0x1234, Pad{}, "mixed share");
check(0x1234, PadB{}, "other pads");
}
TEST(Geneval, IntervalLiveFollowsLongestPrefixNotTheFirstPoint)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 0x0f08;
in_t from = 0x0000;
in_t to = 0x0f00;
in_t x0 = 0x00ff;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 4;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_interval(x0, x1, from, to, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
const auto only_from = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(from), key_t::depth),
key_t::depth);
const auto want = best_live(keys.first, alpha, span_inclusive(from, to));
EXPECT_GT(want, only_from);
EXPECT_FALSE(g.leaf_live);
EXPECT_EQ(g.live_levels, want);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, false, nullptr, 0);
if (g.live_levels < key_t::depth)
{
EXPECT_NE(std::memcmp(&g.correction_words[g.live_levels],
&keys.first.correction_word(g.live_levels), sizeof(simde__m128i)), 0);
}
else
{
EXPECT_NE(std::memcmp(&g.leaf, &keys.first.template leaf<0>(), sizeof(g.leaf)), 0);
}
ASSERT_EQ(g.party0.size(), static_cast<std::size_t>(to - from) + 1);
for (std::size_t i = 0; i < g.party0.size(); ++i)
EXPECT_EQ(recon(g.party0[i], g.party1[i]), out_t{0}) << i;
}
TEST(Geneval, PartialLeafExcludesTargetButKeepsItsWord)
{
using in_t = uint16_t;
using out_t = uint8_t;
in_t alpha = 0x1000;
in_t from = 0x1001;
in_t to = 0x1005;
in_t x0 = 7;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 0x5a;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
using key_t = std::decay_t<decltype(keys.first)>;
ASSERT_GT(key_t::lg_outputs_per_leaf, 0u);
ASSERT_EQ(leaf_of<key_t>(alpha), leaf_of<key_t>(from));
ASSERT_NE(alpha, from);
reset_roots();
auto g = dpf::geneval_interval(x0, x1, from, to, rng<in_t>(), y);
EXPECT_TRUE(g.leaf_live);
EXPECT_EQ(g.live_levels, key_t::depth);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
for (in_t q = from; q <= to; ++q)
{
const std::size_t i = static_cast<std::size_t>(q - from);
EXPECT_EQ(g.party0[i], ev(keys.first, q));
EXPECT_EQ(g.party1[i], ev(keys.second, q));
EXPECT_EQ(recon(g.party0[i], g.party1[i]), out_t{0});
}
}
TEST(Geneval, DepthOneBitOutput)
{
using in_t = uint8_t;
using out_t = dpf::bit;
in_t alpha = 0x80;
in_t other = 0x7f;
in_t same_leaf = 0x81;
in_t x0 = 0x3c;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = dpf::bit::one;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
using key_t = std::decay_t<decltype(keys.first)>;
ASSERT_EQ(key_t::depth, 1u);
reset_roots();
auto on = dpf::geneval_point(x0, x1, alpha, rng<in_t>(), y);
EXPECT_EQ(on.live_levels, 1u);
EXPECT_TRUE(on.leaf_live);
expect_prefix_words(keys.first, on.correction_words, on.correction_advice,
1, true, &on.leaf, sizeof(on.leaf));
EXPECT_EQ(recon(on.party0[0], on.party1[0]), y);
EXPECT_EQ(on.party0[0], ev(keys.first, alpha));
reset_roots();
auto lane = dpf::geneval_point(x0, x1, same_leaf, rng<in_t>(), y);
EXPECT_TRUE(lane.leaf_live);
EXPECT_EQ(recon(lane.party0[0], lane.party1[0]), out_t{false});
EXPECT_EQ(lane.party0[0], ev(keys.first, same_leaf));
reset_roots();
auto off = dpf::geneval_point(x0, x1, other, rng<in_t>(), y);
EXPECT_EQ(off.live_levels, 1u);
EXPECT_FALSE(off.leaf_live);
expect_prefix_words(keys.first, off.correction_words, off.correction_advice,
1, false, nullptr, 0);
EXPECT_EQ(recon(off.party0[0], off.party1[0]), out_t{false});
EXPECT_NE(std::memcmp(&off.leaf, &keys.first.template leaf<0>(), sizeof(off.leaf)), 0);
}
TEST(Geneval, FullMatchesWholeIntervalAndRejectsHugeDomain)
{
using in_t = uint8_t;
using out_t = uint8_t;
in_t alpha = 255;
in_t x0 = 0;
in_t x1 = alpha;
out_t y = 9;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto full = dpf::geneval_full(x0, x1, rng<in_t>(), y);
reset_roots();
auto iv = dpf::geneval_interval(x0, x1, in_t{0}, in_t{255}, rng<in_t>(), y);
ASSERT_EQ(full.party0.size(), iv.party0.size());
EXPECT_EQ(full.correction_advice, iv.correction_advice);
EXPECT_EQ(std::memcmp(full.correction_words.data(), iv.correction_words.data(),
full.correction_words.size() * sizeof(simde__m128i)), 0);
for (std::size_t i = 0; i < full.party0.size(); ++i)
{
EXPECT_EQ(full.party0[i], iv.party0[i]);
EXPECT_EQ(full.party1[i], iv.party1[i]);
}
EXPECT_EQ(recon(full.party0[255], full.party1[255]), y);
EXPECT_EQ(recon(full.party0[0], full.party1[0]), out_t{0});
EXPECT_THROW((dpf::geneval_full(uint32_t{1}, uint32_t{2}, rng<uint32_t>(),
uint32_t{1})), std::length_error);
EXPECT_THROW((dpf::geneval_interval(in_t{5}, in_t{1}, in_t{4}, in_t{3},
rng<in_t>(), y)), std::invalid_argument);
}
TEST(Geneval, SequencePermutationKeepsWordsAndDuplicates)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t alpha = 0x4444;
in_t x0 = 0x0001;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = 6;
const in_t fwd[] = {0x1000, 0x0100, alpha, 0x1000};
const in_t rev[] = {0x1000, alpha, 0x0100, 0x1000};
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto a = dpf::geneval_sequence(x0, x1, std::begin(fwd), std::end(fwd), rng<in_t>(), y);
reset_roots();
auto b = dpf::geneval_sequence(x0, x1, std::begin(rev), std::end(rev), rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(a.live_levels, key_t::depth);
EXPECT_EQ(b.live_levels, key_t::depth);
EXPECT_EQ(std::memcmp(a.correction_words.data(), b.correction_words.data(),
key_t::depth * sizeof(simde__m128i)), 0);
EXPECT_EQ(a.correction_advice, b.correction_advice);
EXPECT_EQ(recon(a.party0[2], a.party1[2]), y);
EXPECT_EQ(recon(a.party0[0], a.party1[0]), recon(a.party0[3], a.party1[3]));
EXPECT_EQ(recon(b.party0[1], b.party1[1]), y);
EXPECT_EQ(a.party0[0], ev(keys.first, fwd[0]));
EXPECT_EQ(b.party0[2], ev(keys.first, rev[2]));
}
TEST(Geneval, SignedPointIntervalAndCrossZero)
{
using in_t = int16_t;
using out_t = int16_t;
in_t alpha = -100;
in_t x0 = 1;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = -25;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
using key_t = std::decay_t<decltype(keys.first)>;
reset_roots();
auto on = dpf::geneval_point(x0, x1, alpha, rng<in_t>(), y);
EXPECT_EQ(on.live_levels, key_t::depth);
EXPECT_TRUE(on.leaf_live);
expect_prefix_words(keys.first, on.correction_words, on.correction_advice,
on.live_levels, true, &on.leaf, sizeof(on.leaf));
EXPECT_EQ(on.party0[0], ev(keys.first, alpha));
EXPECT_EQ(on.party1[0], ev(keys.second, alpha));
EXPECT_EQ(recon(on.party0[0], on.party1[0]), y);
in_t far = 100;
reset_roots();
auto off = dpf::geneval_point(x0, x1, far, rng<in_t>(), y);
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(far), key_t::depth),
key_t::depth);
EXPECT_EQ(off.live_levels, live);
EXPECT_LT(live, key_t::depth);
expect_prefix_words(keys.first, off.correction_words, off.correction_advice,
live, false, nullptr, 0);
EXPECT_EQ(recon(off.party0[0], off.party1[0]), out_t{0});
in_t from = -3;
in_t to = 3;
reset_roots();
auto iv = dpf::geneval_interval(x0, x1, from, to, rng<in_t>(), y);
ASSERT_EQ(iv.party0.size(), 7u);
EXPECT_FALSE(iv.leaf_live);
for (in_t q = from; ; ++q)
{
const std::size_t i = static_cast<std::size_t>(q - from);
EXPECT_EQ(recon(iv.party0[i], iv.party1[i]), out_t{0}) << q;
EXPECT_EQ(recon(iv.party0[i], iv.party1[i]),
recon(ev(keys.first, q), ev(keys.second, q))) << q;
if (q == to)
break;
}
in_t near = -101;
reset_roots();
auto around = dpf::geneval_interval(x0, x1, in_t{-102}, in_t{-98}, rng<in_t>(), y);
EXPECT_TRUE(around.leaf_live);
EXPECT_EQ(around.live_levels, key_t::depth);
expect_prefix_words(keys.first, around.correction_words, around.correction_advice,
around.live_levels, true, &around.leaf, sizeof(around.leaf));
for (in_t q = -102; q <= -98; ++q)
{
const std::size_t i = static_cast<std::size_t>(q - in_t{-102});
EXPECT_EQ(around.party0[i], ev(keys.first, q)) << q;
EXPECT_EQ(recon(around.party0[i], around.party1[i]), q == alpha ? y : out_t{0});
}
(void)near;
}
TEST(Geneval, SignedFullAndWildcardFull)
{
using in_t = int8_t;
using out_t = int8_t;
in_t alpha = -5;
in_t x_xor0 = 3;
in_t x_xor1 = static_cast<in_t>(alpha ^ x_xor0);
out_t y = -9;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_full(x_xor0, x_xor1, rng<in_t>(), y);
ASSERT_EQ(g.party0.size(), 256u);
constexpr auto to_int = dpf::utils::to_integral_type<in_t>{};
for (int q = -128; q <= 127; ++q)
{
in_t v = static_cast<in_t>(q);
const std::size_t i = static_cast<std::size_t>(to_int(v));
EXPECT_EQ(g.party0[i], ev(keys.first, v)) << q;
EXPECT_EQ(g.party1[i], ev(keys.second, v)) << q;
}
in_t secret = -20;
in_t a0 = 100;
in_t a1 = static_cast<in_t>(secret - a0);
in_t target = 40;
const in_t delta = static_cast<in_t>(target - secret);
reset_roots();
auto wkeys = dpf::make_dpf(target, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto w = dpf::geneval_full(dpf::wildcard_input, a0, a1, rng<in_t>(),
[&] { return target; }, y);
ASSERT_EQ(w.party0.size(), 256u);
EXPECT_EQ(w.live_levels, std::decay_t<decltype(wkeys.first)>::depth);
expect_prefix_words(wkeys.first, w.correction_words, w.correction_advice,
w.live_levels, true, &w.leaf, sizeof(w.leaf));
for (int q = -128; q <= 127; ++q)
{
in_t v = static_cast<in_t>(q);
in_t shifted = static_cast<in_t>(v + delta);
const std::size_t i = static_cast<std::size_t>(to_int(v));
EXPECT_EQ(w.party0[i], ev(wkeys.first, shifted)) << q;
EXPECT_EQ(w.party1[i], ev(wkeys.second, shifted)) << q;
}
EXPECT_EQ(recon(w.party0[static_cast<std::size_t>(to_int(secret))],
w.party1[static_cast<std::size_t>(to_int(secret))]), y);
}
TEST(Geneval, WildcardShareOverflowAndWrappingInterval)
{
using in_t = uint8_t;
using out_t = uint8_t;
in_t secret = 10;
in_t x0 = 200;
in_t x1 = 66;
ASSERT_EQ(static_cast<in_t>(x0 + x1), secret);
in_t alpha = 5;
out_t y = 17;
const in_t delta = static_cast<in_t>(alpha - secret);
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto on = dpf::geneval_point(dpf::wildcard_input, x0, x1, secret, rng<in_t>(),
[&] { return alpha; }, y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(on.live_levels, key_t::depth);
EXPECT_TRUE(on.leaf_live);
expect_prefix_words(keys.first, on.correction_words, on.correction_advice,
on.live_levels, true, &on.leaf, sizeof(on.leaf));
EXPECT_EQ(recon(on.party0[0], on.party1[0]), y);
in_t query = 250;
const in_t shifted = static_cast<in_t>(query + delta);
reset_roots();
auto off = dpf::geneval_point(dpf::wildcard_input, x0, x1, query, rng<in_t>(),
[&] { return alpha; }, y);
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(shifted), key_t::depth),
key_t::depth);
EXPECT_EQ(off.live_levels, live);
expect_prefix_words(keys.first, off.correction_words, off.correction_advice,
off.live_levels, off.leaf_live, &off.leaf, sizeof(off.leaf));
EXPECT_EQ(recon(off.party0[0], off.party1[0]),
recon(ev(keys.first, shifted), ev(keys.second, shifted)));
in_t from = 250;
in_t to = 10;
EXPECT_THROW((dpf::geneval_interval(dpf::wildcard_input, x0, x1, from, to,
rng<in_t>(), [&] { return alpha; }, y)), std::invalid_argument);
from = 250;
to = 255;
reset_roots();
auto iv = dpf::geneval_interval(dpf::wildcard_input, x0, x1, from, to,
rng<in_t>(), [&] { return alpha; }, y);
ASSERT_EQ(iv.party0.size(), 6u);
for (in_t q = from; ; ++q)
{
const std::size_t i = static_cast<std::size_t>(static_cast<in_t>(q - from));
const in_t s = static_cast<in_t>(q + delta);
EXPECT_EQ(recon(iv.party0[i], iv.party1[i]),
recon(ev(keys.first, s), ev(keys.second, s))) << int(q);
if (q == to)
break;
}
}
TEST(Geneval, XorWrapperOutput)
{
using in_t = uint16_t;
using out_t = dpf::xor_wrapper<uint32_t>;
in_t alpha = 0x0102;
in_t query = 0x0180;
in_t x0 = 0x00f0;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y{0x01020304u};
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto on = dpf::geneval_point(x0, x1, alpha, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_TRUE(on.leaf_live);
expect_prefix_words(keys.first, on.correction_words, on.correction_advice,
on.live_levels, true, &on.leaf, sizeof(on.leaf));
EXPECT_EQ(recon(on.party0[0], on.party1[0]), y);
EXPECT_EQ(on.party0[0], ev(keys.first, alpha));
reset_roots();
auto off = dpf::geneval_point(x0, x1, query, rng<in_t>(), y);
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(query), key_t::depth),
key_t::depth);
EXPECT_EQ(off.live_levels, live);
EXPECT_LT(live, key_t::depth);
expect_prefix_words(keys.first, off.correction_words, off.correction_advice,
live, false, nullptr, 0);
EXPECT_EQ(recon(off.party0[0], off.party1[0]), out_t{0});
}
TEST(Geneval, SignedRegressionsFromTheCornerPass)
{
using in_t = int8_t;
using out_t = int8_t;
in_t alpha = -40;
in_t x0 = 3;
in_t x1 = static_cast<in_t>(alpha ^ x0);
out_t y = -7;
// An inverted signed range must not wrap the long way around.
EXPECT_THROW((dpf::geneval_interval(in_t{2}, in_t{1}, in_t{4}, in_t{-3},
rng<in_t>(), y)), std::invalid_argument);
// [INT_MIN, INT_MAX] is numeric order; full is bit-pattern order.
// The words are the same trie. Each input's share matches either way.
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto full = dpf::geneval_full(x0, x1, rng<in_t>(), y);
reset_roots();
auto iv = dpf::geneval_interval(x0, x1, std::numeric_limits<in_t>::min(),
std::numeric_limits<in_t>::max(), rng<in_t>(), y);
ASSERT_EQ(full.party0.size(), 256u);
ASSERT_EQ(iv.party0.size(), 256u);
EXPECT_EQ(std::memcmp(full.correction_words.data(), iv.correction_words.data(),
full.correction_words.size() * sizeof(simde__m128i)), 0);
EXPECT_EQ(full.correction_advice, iv.correction_advice);
constexpr auto to_int = dpf::utils::to_integral_type<in_t>{};
for (int q = -128; q <= 127; ++q)
{
in_t v = static_cast<in_t>(q);
const std::size_t bit = static_cast<std::size_t>(to_int(v));
const std::size_t num = static_cast<std::size_t>(q - (-128));
EXPECT_EQ(full.party0[bit], iv.party0[num]) << q;
EXPECT_EQ(full.party1[bit], iv.party1[num]) << q;
EXPECT_EQ(full.party0[bit], ev(keys.first, v)) << q;
}
// Negative target, additive shares that wrap, bound through a real
// wildcard key so the raw offset bits are what geneval subtracts.
in_t secret = -20;
in_t a0 = 100;
in_t a1 = static_cast<in_t>(secret - a0);
ASSERT_EQ(static_cast<in_t>(a0 + a1), secret);
in_t target = -90;
in_t query = 60;
reset_roots();
dpf::wildcard_value<in_t> slot{target};
auto wild = dpf::make_dpf(slot, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto s0 = wild.first.offset_x.compute_and_get_share(a0);
auto s1 = wild.second.offset_x.compute_and_get_share(a1);
wild.first.offset_x.reconstruct(s1);
wild.second.offset_x.reconstruct(s0);
reset_roots();
auto g = dpf::geneval_point(dpf::wildcard_input, a0, a1, query, rng<in_t>(),
[&] { return target; }, y);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), recon(ev(wild.first, query), ev(wild.second, query)));
expect_prefix_words(wild.first, g.correction_words, g.correction_advice,
g.live_levels, g.leaf_live, &g.leaf, sizeof(g.leaf));
}
TEST(Geneval, CmpEmptyRangeOpensNothing)
{
reset_roots();
const uint8_t ends[] = {0};
auto g = dpf::geneval_cmp(uint8_t{1}, uint8_t{2}, ends, ends, rng<uint8_t>(),
uint64_t{1});
EXPECT_TRUE(g.party0.empty());
EXPECT_TRUE(g.party1.empty());
EXPECT_EQ(g.live_levels, 0u);
EXPECT_TRUE(g.value_cw.empty());
}
TEST(Geneval, CmpMatchesDoernerShelatKeyAndGtPredicate)
{
using in_t = uint8_t;
const in_t alpha = 40;
const in_t x0 = 0x11;
const in_t x1 = static_cast<in_t>(alpha ^ x0);
const uint64_t beta = 7;
const std::vector<in_t> ends{0, 1, 10, 40, 200, 255, 40};
reset_roots();
auto keys = dpf::make_dpf_doerner_shelat(x0, x1, rng<in_t>(), dpf::gt(beta));
reset_roots();
auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng<in_t>(), beta);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.live_levels, key_t::depth);
ASSERT_EQ(g.correction_words.size(), key_t::depth);
ASSERT_EQ(g.value_cw.size(), key_t::depth);
for (std::size_t level = 0; level < key_t::depth; ++level)
{
EXPECT_EQ(std::memcmp(&g.correction_words[level],
&keys.first.correction_word(level), sizeof(simde__m128i)), 0) << level;
EXPECT_EQ(g.correction_advice[level], keys.first.correction_advice(level)) << level;
EXPECT_EQ(g.value_cw[level], keys.first.value_cw(level)) << level;
}
EXPECT_EQ(g.cw_last, keys.first.cw_last());
EXPECT_EQ(g.addend0, keys.first.cmp_addend().raw());
EXPECT_EQ(g.addend1, keys.second.cmp_addend().raw());
EXPECT_EQ((g.addend0 + g.addend1) & g.mask, beta);
ASSERT_EQ(g.party0.size(), ends.size());
for (std::size_t i = 0; i < ends.size(); ++i)
{
const auto e0 = dpf::eval_point(dpf::cmp, keys.first, ends[i]);
const auto e1 = dpf::eval_point(dpf::cmp, keys.second, ends[i]);
EXPECT_EQ(g.party0[i], e0.raw()) << int(ends[i]);
EXPECT_EQ(g.party1[i], e1.raw()) << int(ends[i]);
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
EXPECT_EQ(opened, ends[i] > alpha ? beta : 0u) << int(ends[i]);
}
}
TEST(Geneval, CmpSignedPayloadAndDomainMax)
{
using in_t = int16_t;
const in_t alpha = -3;
const in_t x0 = 9;
const in_t x1 = static_cast<in_t>(alpha ^ x0);
const uint64_t beta = 5;
const std::vector<in_t> ends{-100, -3, -2, 0, 4, 32767};
reset_roots();
auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng<in_t>(),
dpf::gt(beta));
EXPECT_EQ(g.live_levels, 16u);
EXPECT_EQ(g.value_cw.size(), g.live_levels);
for (std::size_t i = 0; i < ends.size(); ++i)
{
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
EXPECT_EQ(opened, ends[i] > alpha ? beta : 0u) << ends[i];
}
const in_t top0 = 1;
const in_t top = std::numeric_limits<in_t>::max();
const in_t top1 = static_cast<in_t>(top ^ top0);
const std::vector<in_t> all{std::numeric_limits<in_t>::min(), in_t{0}, top};
reset_roots();
auto trivial = dpf::geneval_cmp(top0, top1, all.begin(), all.end(), rng<in_t>(),
uint64_t{1});
for (std::size_t i = 0; i < all.size(); ++i)
EXPECT_EQ((trivial.party0[i] + trivial.party1[i]) & trivial.mask, 0u) << all[i];
}
TEST(Geneval, CmpLtIsTheComplementOfTheStrictUpperSet)
{
using in_t = uint8_t;
const in_t alpha = 10;
const in_t x0 = 3;
const in_t x1 = static_cast<in_t>(alpha ^ x0);
const std::vector<in_t> ends{0, 10, 11, 255};
reset_roots();
auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng<in_t>(),
dpf::lt(uint64_t{4}));
for (std::size_t i = 0; i < ends.size(); ++i)
{
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
EXPECT_EQ(opened, ends[i] < alpha ? 4u : 0u) << int(ends[i]);
}
}