libdpf/test/tests/incremental_test.cpp

1898 lines
77 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include "dpf.hpp"
#include "grotto/fixedpoint.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <type_traits>
#include <vector>
namespace
{
simde__m128i g_roots[16];
int g_ri = 0;
simde__m128i take_root() { return g_roots[g_ri++]; }
std::vector<unsigned char> g_tape(1 << 18);
std::size_t g_ti = 0;
void tape_fill(void * p, std::size_t n)
{
if (g_ti + n > g_tape.size())
std::abort();
std::memcpy(p, g_tape.data() + g_ti, n);
g_ti += n;
}
struct PadA
{
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); }
};
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); }
};
void reset_tape_roots()
{
g_ri = 0;
g_ti = 0;
}
void seed_fixed_rng()
{
for (int i = 0; i < 16; ++i)
g_roots[i] = simde_mm_set_epi64x(0x1111 * (i + 1), 0xABCD0000u + i * 17);
for (std::size_t i = 0; i < g_tape.size(); ++i)
g_tape[i] = static_cast<unsigned char>(i * 17 + 3);
}
bool same_bytes(const void * a, const void * b, std::size_t n)
{
return std::memcmp(a, b, n) == 0;
}
template <typename T>
constexpr bool is_xor_out_v =
std::is_same_v<T, dpf::bit> || dpf::utils::is_xor_wrapper_v<T>;
template <typename A, typename B>
auto recon(const A & a, const B & b)
{
if constexpr (dpf::is_secret_share_v<A> && dpf::is_secret_share_v<B>)
return dpf::reconstruct(a, b);
else if constexpr (is_xor_out_v<A>)
return static_cast<A>(a ^ b);
else
return static_cast<A>(a - b); // subtractive leaf: party0 - party1
}
template <typename Key, std::size_t I = 0>
bool same_leaf_beaver(const Key & a, const Key & b)
{
if constexpr (I < Key::num_outputs)
{
const auto & la = a.template leaf<I>();
const auto & lb = b.template leaf<I>();
if (!same_bytes(&la, &lb, sizeof(la)))
return false;
const auto & ba = a.template beaver<I>();
const auto & bb = b.template beaver<I>();
using out_t = typename Key::template output_type_t<I>;
if constexpr (dpf::is_wildcard_v<out_t>)
{
return same_bytes(&ba.output_blind, &bb.output_blind,
sizeof(ba.output_blind))
&& same_bytes(&ba.vector_blind, &bb.vector_blind,
sizeof(ba.vector_blind))
&& same_bytes(&ba.blinded_vector, &bb.blinded_vector,
sizeof(ba.blinded_vector))
&& same_leaf_beaver<Key, I + 1>(a, b);
}
return same_bytes(&ba, &bb, sizeof(ba))
&& same_leaf_beaver<Key, I + 1>(a, b);
}
return true;
}
template <typename Key>
bool same_incr_key(const Key & a, const Key & b)
{
return same_bytes(&a.root(), &b.root(), sizeof(a.root()))
&& same_bytes(a.correction_words().data(), b.correction_words().data(),
sizeof(a.correction_words()))
&& same_bytes(a.correction_advice().data(), b.correction_advice().data(),
sizeof(a.correction_advice()))
&& same_leaf_beaver(a, b);
}
template <typename Key, std::size_t I = 0>
bool same_leaf_beaver_classic(const Key & a, const Key & b)
{
if constexpr (I < std::tuple_size_v<typename Key::outputs_tuple>)
{
const auto & la = a.template leaf<I>();
const auto & lb = b.template leaf<I>();
if (!same_bytes(&la, &lb, sizeof(la)))
return false;
const auto & ba = a.template beaver<I>();
const auto & bb = b.template beaver<I>();
using out_t = typename Key::template output_type_t<I>;
if constexpr (dpf::is_wildcard_v<out_t>)
{
return same_bytes(&ba.output_blind, &bb.output_blind,
sizeof(ba.output_blind))
&& same_bytes(&ba.vector_blind, &bb.vector_blind,
sizeof(ba.vector_blind))
&& same_bytes(&ba.blinded_vector, &bb.blinded_vector,
sizeof(ba.blinded_vector))
&& same_leaf_beaver_classic<Key, I + 1>(a, b);
}
return same_bytes(&ba, &bb, sizeof(ba))
&& same_leaf_beaver_classic<Key, I + 1>(a, b);
}
return true;
}
template <typename Key>
bool same_classic_key(const Key & a, const Key & b)
{
if (!same_bytes(&a.root(), &b.root(), sizeof(a.root())))
return false;
if (!same_bytes(a.correction_words().data(), b.correction_words().data(),
sizeof(a.correction_words())))
return false;
if (!same_bytes(a.correction_advice().data(), b.correction_advice().data(),
sizeof(a.correction_advice())))
return false;
return same_leaf_beaver_classic(a, b);
}
/// Flip the low bit of the N-bit MSB prefix (neighbor lane for packed leaves).
template <typename InputT>
InputT flip_lane_lsb(InputT x, std::size_t prefix, std::size_t bitlen)
{
return static_cast<InputT>(x ^ (InputT{1} << (bitlen - prefix)));
}
} // namespace
class IncrementalDpfTest : public ::testing::Test
{
protected:
void SetUp() override
{
seed_fixed_rng();
dpf::detail::uniform_bytes_hook = tape_fill;
reset_tape_roots();
}
void TearDown() override
{
dpf::detail::uniform_bytes_hook = nullptr;
}
};
TEST_F(IncrementalDpfTest, ClassicPathByteIdentical)
{
uint32_t x = 0x00abcdefu;
uint32_t y = 0x55555555u;
reset_tape_roots();
auto via_args = dpf::make_dpf(dpf::make_dpfargs(x, y), take_root);
reset_tape_roots();
auto via_conv = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
using KT = std::decay_t<decltype(via_args.first)>;
static_assert(std::is_same_v<KT, std::decay_t<decltype(via_conv.first)>>);
EXPECT_TRUE(same_classic_key(via_args.first, via_conv.first));
EXPECT_TRUE(same_classic_key(via_args.second, via_conv.second));
auto a0 = dpf::eval_point(via_conv.first, x);
auto a1 = dpf::eval_point(via_conv.second, x);
EXPECT_EQ(static_cast<uint32_t>(recon(*a0, *a1)), y);
}
TEST_F(IncrementalDpfTest, At10BitDepthAndLanes)
{
uint32_t x = 0x00abcdefu;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::depth, 3u); // 10 - lg(128)=7
EXPECT_EQ(KT::meta[0].prefix, 10u);
EXPECT_EQ(KT::meta[0].tree_level, 3u);
EXPECT_EQ(KT::meta[0].pos_base, 0u); // only / deepest group
auto y0 = dpf::eval_point(dpf::out<0, 10>, k0, x);
auto y1 = dpf::eval_point(dpf::out<0, 10>, k1, x);
EXPECT_TRUE(static_cast<bool>(recon(*y0, *y1)));
uint32_t nb = flip_lane_lsb(x, 10, 32);
auto z0 = dpf::eval_point(dpf::out<0, 10>, k0, nb);
auto z1 = dpf::eval_point(dpf::out<0, 10>, k1, nb);
EXPECT_FALSE(static_cast<bool>(recon(*z0, *z1)));
}
TEST_F(IncrementalDpfTest, SameWidthPackedInOneGroup)
{
uint32_t x = 0x12345678u;
auto [k0, k1] =
dpf::make_dpf(x, dpf::at<12>(uint8_t{3}, uint8_t{5}, uint8_t{7}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::num_outputs, 3u);
EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id);
EXPECT_EQ(KT::meta[1].group_id, KT::meta[2].group_id);
EXPECT_EQ(KT::meta[0].index_in_group, 0u);
EXPECT_EQ(KT::meta[1].index_in_group, 1u);
EXPECT_EQ(KT::meta[2].index_in_group, 2u);
// 12 - lg(16)=4 => level 8
EXPECT_EQ(KT::depth, 8u);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)),
uint8_t{3});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)),
uint8_t{5});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 12>, k0, x), *dpf::eval_point(dpf::out<2, 12>, k1, x)),
uint8_t{7});
uint32_t nb = flip_lane_lsb(x, 12, 32);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, nb), *dpf::eval_point(dpf::out<0, 12>, k1, nb)),
uint8_t{0});
}
TEST_F(IncrementalDpfTest, MixedWidthsSamePrefixSeparateGroups)
{
uint32_t x = 0x0f0f0f0fu;
auto [k0, k1] =
dpf::make_dpf(x, dpf::at<12>(dpf::bit::one, uint8_t{9}, uint16_t{0xabcd}));
using KT = std::decay_t<decltype(k0)>;
// bit level 12-7=5, u8 level 12-4=8, u16 level 12-3=9 — wait, same prefix
// different lg => different tree levels!
EXPECT_EQ(KT::meta[0].tree_level, 5u);
EXPECT_EQ(KT::meta[1].tree_level, 8u);
EXPECT_EQ(KT::meta[2].tree_level, 9u);
EXPECT_NE(KT::meta[0].group_id, KT::meta[1].group_id);
EXPECT_NE(KT::meta[1].group_id, KT::meta[2].group_id);
EXPECT_EQ(KT::depth, 9u);
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x))));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)),
uint8_t{9});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 12>, k0, x), *dpf::eval_point(dpf::out<2, 12>, k1, x)),
uint16_t{0xabcd});
}
TEST_F(IncrementalDpfTest, ManyLevelsManyTypes)
{
uint32_t x = 0xa5a5a5a5u;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(dpf::bit::one),
dpf::at<14>(uint8_t{2}, uint8_t{4}),
dpf::at<18>(uint16_t{1000}),
dpf::at<22>(uint32_t{0x11111111u}),
uint64_t{0x2222222233333333ull},
dpf::xor_wrapper<uint32_t>{0xdeadbeefu},
dpf::bit{true});
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::num_outputs, 8u);
// deepest is full-domain u64/xor/bit at level 32-lg
EXPECT_EQ(KT::deepest_prefix, 32u);
EXPECT_EQ(KT::meta[0].tree_level, 3u); // at<10>(bit): 10-7
EXPECT_EQ(KT::meta[1].tree_level, 10u); // at<14>(u8): 14-4
EXPECT_EQ(KT::meta[3].tree_level, 15u); // at<18>(u16): 18-3
EXPECT_EQ(KT::meta[4].tree_level, 20u); // at<22>(u32): 22-2
EXPECT_EQ(KT::depth, 31u); // u64: 32-1
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 14>, k0, x), *dpf::eval_point(dpf::out<1, 14>, k1, x)),
uint8_t{2});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 14>, k0, x), *dpf::eval_point(dpf::out<2, 14>, k1, x)),
uint8_t{4});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 18>, k0, x), *dpf::eval_point(dpf::out<3, 18>, k1, x)),
uint16_t{1000});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 22>, k0, x), *dpf::eval_point(dpf::out<4, 22>, k1, x)),
uint32_t{0x11111111u});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, x), *dpf::eval_point(dpf::out<5, 32>, k1, x)),
uint64_t{0x2222222233333333ull});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6, 32>, k0, x), *dpf::eval_point(dpf::out<6, 32>, k1, x)),
dpf::xor_wrapper<uint32_t>{0xdeadbeefu});
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<7, 32>, k0, x), *dpf::eval_point(dpf::out<7, 32>, k1, x))));
// Leave the 10-bit MSB prefix entirely.
uint32_t off_pref = x ^ (1u << 31);
EXPECT_FALSE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, off_pref), *dpf::eval_point(dpf::out<0, 10>, k1, off_pref))));
// Neighbor lane within the same prefix node.
uint32_t off_lane = flip_lane_lsb(x, 10, 32);
EXPECT_FALSE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, off_lane), *dpf::eval_point(dpf::out<0, 10>, k1, off_lane))));
// Full-domain off-point.
uint32_t off = x ^ 1u;
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, off), *dpf::eval_point(dpf::out<5, 32>, k1, off)),
uint64_t{0});
// eval_point() defaults to deepest output (first at prefix 32 = slot 5)
EXPECT_EQ(KT::deepest_output, 5u);
EXPECT_EQ(recon(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)),
uint64_t{0x2222222233333333ull});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6>, k0, x), *dpf::eval_point(dpf::out<6>, k1, x)),
dpf::xor_wrapper<uint32_t>{0xdeadbeefu});
}
TEST_F(IncrementalDpfTest, ManySameLevelGroupsHighPosBase)
{
// Same tree level for several widths by choosing N = level + lg(opl).
// level 10: bit needs N=17, u8 needs N=14, u16 needs N=13, u32 needs N=12
// Different levels. To share a level use matching N-lg.
// Force many groups at the *deepest* level with distinct widths — pos starts 0.
// For non-final: put many groups on a shallow shared level.
// bit@10 (lvl3), and also use at<10> with only bits in multiple at<>? same group.
// Use distinct prefixes that collide on level via different types:
// at<10>(bit) lvl 3, at<7>(u8) lvl 3, at<6>(u16) lvl 3, at<5>(u32) lvl 3
uint32_t x = 0x7f3a9c1bu;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(dpf::bit::one),
dpf::at<7>(uint8_t{11}),
dpf::at<6>(uint16_t{22}),
dpf::at<5>(uint32_t{33}),
// another batch at level 8: u8@12, u16@11, u32@10 — wait u32@10 is lvl 8
dpf::at<12>(uint8_t{44}),
dpf::at<11>(uint16_t{55}),
dpf::at<10>(uint32_t{66}),
// deepest full domain
uint64_t{77});
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::meta[0].tree_level, 3u);
EXPECT_EQ(KT::meta[1].tree_level, 3u);
EXPECT_EQ(KT::meta[2].tree_level, 3u);
EXPECT_EQ(KT::meta[3].tree_level, 3u);
// four groups at level 3: pos bases 2,3,4,5 (each 1 block)
EXPECT_EQ(KT::meta[0].pos_base, 2u);
EXPECT_EQ(KT::meta[1].pos_base, 3u);
EXPECT_EQ(KT::meta[2].pos_base, 4u);
EXPECT_EQ(KT::meta[3].pos_base, 5u);
EXPECT_EQ(KT::meta[4].tree_level, 8u);
EXPECT_EQ(KT::meta[5].tree_level, 8u);
EXPECT_EQ(KT::meta[6].tree_level, 8u);
EXPECT_EQ(KT::meta[4].pos_base, 2u);
EXPECT_EQ(KT::meta[5].pos_base, 3u);
EXPECT_EQ(KT::meta[6].pos_base, 4u);
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 7>, k0, x), *dpf::eval_point(dpf::out<1, 7>, k1, x)),
uint8_t{11});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 6>, k0, x), *dpf::eval_point(dpf::out<2, 6>, k1, x)),
uint16_t{22});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 5>, k0, x), *dpf::eval_point(dpf::out<3, 5>, k1, x)),
uint32_t{33});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 12>, k0, x), *dpf::eval_point(dpf::out<4, 12>, k1, x)),
uint8_t{44});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 11>, k0, x), *dpf::eval_point(dpf::out<5, 11>, k1, x)),
uint16_t{55});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6, 10>, k0, x), *dpf::eval_point(dpf::out<6, 10>, k1, x)),
uint32_t{66});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 32>, k0, x), *dpf::eval_point(dpf::out<7, 32>, k1, x)),
uint64_t{77});
}
TEST_F(IncrementalDpfTest, NinePlusGroupsScales)
{
// 9 distinct (prefix,width) groups — previously the hard 8-group cap.
uint32_t x = 0x10203040u;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(dpf::bit::one),
dpf::at<11>(dpf::bit::one),
dpf::at<12>(dpf::bit::one),
dpf::at<13>(dpf::bit::one),
dpf::at<14>(dpf::bit::one),
dpf::at<15>(dpf::bit::one),
dpf::at<16>(dpf::bit::one),
dpf::at<17>(dpf::bit::one),
dpf::at<18>(dpf::bit::one));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::num_outputs, 9u);
std::size_t ng = 0;
for (std::size_t i = 0; i < 9; ++i)
ng = std::max(ng, KT::meta[i].group_id + 1);
EXPECT_EQ(ng, 9u);
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<8, 18>, k0, x), *dpf::eval_point(dpf::out<8, 18>, k1, x))));
uint32_t off = x ^ (1u << 20);
EXPECT_FALSE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<8, 18>, k0, off), *dpf::eval_point(dpf::out<8, 18>, k1, off))));
}
TEST_F(IncrementalDpfTest, DealerMatchesDoernerShelat)
{
uint32_t x = 0x00abcdefu;
uint32_t x0 = 0x12345678u;
uint32_t x1 = x ^ x0;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<10>(dpf::bit::one),
dpf::at<14>(uint8_t{3}, uint8_t{5}),
uint32_t{9},
dpf::xor_wrapper<uint16_t>{0xcafe});
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<10>(dpf::bit::one),
dpf::at<14>(uint8_t{3}, uint8_t{5}),
uint32_t{9},
dpf::xor_wrapper<uint16_t>{0xcafe});
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadB> rngb{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds2 = dpf::make_dpf_doerner_shelat(x0, x1, rngb,
dpf::at<10>(dpf::bit::one),
dpf::at<14>(uint8_t{3}, uint8_t{5}),
uint32_t{9},
dpf::xor_wrapper<uint16_t>{0xcafe});
EXPECT_TRUE(same_incr_key(ds.first, ds2.first));
EXPECT_TRUE(same_incr_key(ds.second, ds2.second));
}
TEST_F(IncrementalDpfTest, IntermediateWildcardSameKey)
{
uint32_t x = 0x55aa55aau;
uint32_t x0 = 0x0f0f0f0fu;
uint32_t x1 = x ^ x0;
dpf::wildcard_value<uint32_t> wc;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<10>(dpf::bit::one),
dpf::at<16>(wc),
uint64_t{42});
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<10>(dpf::bit::one), dpf::at<16>(wc), uint64_t{42});
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
// concrete outputs still reconstruct
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, dealer.first, x), *dpf::eval_point(dpf::out<0, 10>, dealer.second, x))));
EXPECT_EQ(
recon(*dpf::eval_point(dpf::out<2, 32>, dealer.first, x),
*dpf::eval_point(dpf::out<2, 32>, dealer.second, x)),
uint64_t{42});
}
TEST_F(IncrementalDpfTest, EvalSweepAroundPoint)
{
uint16_t x = 0x1234;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<8>(dpf::bit::one),
uint32_t{0xabcdef01u});
using path_t = dpf::nonmemoizing_path_memoizer<std::decay_t<decltype(k0)>>;
path_t p0{}, p1{};
auto prefix8 = [](uint16_t q) {
return static_cast<uint16_t>(q >> (16 - 8));
};
const auto xp = prefix8(x);
for (int d = -64; d <= 64; ++d)
{
uint16_t q = static_cast<uint16_t>(x + d);
auto b0 = dpf::eval_point(dpf::out<0, 8>, k0, q, p0);
auto b1 = dpf::eval_point(dpf::out<0, 8>, k1, q, p1);
auto u0 = dpf::eval_point(dpf::out<1, 16>, k0, q, p0);
auto u1 = dpf::eval_point(dpf::out<1, 16>, k1, q, p1);
EXPECT_EQ(static_cast<bool>(recon(*b0, *b1)), prefix8(q) == xp) << "q=" << q;
uint32_t expect = (q == x) ? 0xabcdef01u : 0u;
EXPECT_EQ(recon(*u0, *u1), expect) << "q=" << q;
}
// Explicitly leave the 8-bit MSB bucket.
uint16_t other = static_cast<uint16_t>(x ^ 0x8000);
EXPECT_NE(prefix8(other), xp);
EXPECT_FALSE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 8>, k0, other), *dpf::eval_point(dpf::out<0, 8>, k1, other))));
}
TEST_F(IncrementalDpfTest, SignedInput)
{
int32_t x = -1000;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<12>(uint16_t{7}),
int32_t{42});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)),
uint16_t{7});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, k0, x), *dpf::eval_point(dpf::out<1, 32>, k1, x)),
int32_t{42});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, k0, x + 1), *dpf::eval_point(dpf::out<1, 32>, k1, x + 1)),
int32_t{0});
}
TEST_F(IncrementalDpfTest, FullDomainAtEqualsBare)
{
uint32_t x = 0x9999u;
uint32_t y = 12345u;
reset_tape_roots();
auto bare = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_tape_roots();
// at<32> same width alone should still be classic path
auto placed = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, dpf::at<32>(y));
using BareT = std::decay_t<decltype(bare.first)>;
using PlacedT = std::decay_t<decltype(placed.first)>;
static_assert(std::is_same_v<BareT, PlacedT>);
EXPECT_TRUE(same_classic_key(bare.first, placed.first));
EXPECT_TRUE(same_classic_key(bare.second, placed.second));
}
TEST_F(IncrementalDpfTest, DepthZeroLeaf)
{
// at<7>(bit): tree level 0, no correction words.
uint32_t x = 0x00ffffffu;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<7>(dpf::bit::one));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::depth, 0u);
EXPECT_EQ(KT::meta[0].pos_base, 0u);
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 7>, k0, x), *dpf::eval_point(dpf::out<0, 7>, k1, x))));
EXPECT_FALSE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 7>, k0, flip_lane_lsb(x, 7, 32)), *dpf::eval_point(dpf::out<0, 7>, k1, flip_lane_lsb(x, 7, 32)))));
}
TEST_F(IncrementalDpfTest, MemoizedPathEval)
{
uint32_t x = 0xabcdef01u;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(dpf::bit::one), uint32_t{1234});
using KT = std::decay_t<decltype(k0)>;
dpf::basic_path_memoizer<KT> p0{}, p1{};
for (uint32_t q : {x, x ^ 1u, x ^ 0x80000000u, 0u})
{
auto e0 = recon(*dpf::eval_point(dpf::out<0, 10>, k0, q, p0),
*dpf::eval_point(dpf::out<0, 10>, k1, q, p1));
auto e1 = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q, p0),
*dpf::eval_point(dpf::out<1, 32>, k1, q, p1));
auto r0 = recon(*dpf::eval_point(dpf::out<0, 10>, k0, q),
*dpf::eval_point(dpf::out<0, 10>, k1, q));
auto r1 = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q),
*dpf::eval_point(dpf::out<1, 32>, k1, q));
EXPECT_EQ(static_cast<bool>(e0), static_cast<bool>(r0));
EXPECT_EQ(e1, r1);
}
}
TEST_F(IncrementalDpfTest, DsEvalAgreesWithDealer)
{
uint32_t x = 0x31415926u;
uint32_t x0 = 0x27182818u;
uint32_t x1 = x ^ x0;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<10>(dpf::bit::one), uint32_t{99});
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<10>(dpf::bit::one), uint32_t{99});
uint32_t pts[] = {x, x ^ 1u, 0u, 0xffffffffu, 0x80000000u};
for (uint32_t q : pts)
{
EXPECT_EQ(static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, dealer.first, q), *dpf::eval_point(dpf::out<0, 10>, dealer.second, q))),
static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, ds.first, q), *dpf::eval_point(dpf::out<0, 10>, ds.second, q))));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, dealer.first, q),
*dpf::eval_point(dpf::out<1, 32>, dealer.second, q)),
recon(*dpf::eval_point(dpf::out<1, 32>, ds.first, q),
*dpf::eval_point(dpf::out<1, 32>, ds.second, q)));
}
}
TEST_F(IncrementalDpfTest, SixteenUint8PackedOneLevel)
{
// u8 has opl=16: fill an entire packed node at at<12> (level 8).
uint32_t x = 0x4c1d2e3fu;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<12>(
uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4},
uint8_t{5}, uint8_t{6}, uint8_t{7}, uint8_t{8},
uint8_t{9}, uint8_t{10}, uint8_t{11}, uint8_t{12},
uint8_t{13}, uint8_t{14}, uint8_t{15}, uint8_t{16}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::num_outputs, 16u);
EXPECT_EQ(KT::meta[0].group_id, KT::meta[15].group_id);
EXPECT_EQ(KT::meta[15].index_in_group, 15u);
EXPECT_EQ(KT::depth, 8u);
EXPECT_EQ(KT::meta[0].pos_base, 0u);
auto check = [&](auto idx, uint8_t expect) {
constexpr std::size_t I = decltype(idx)::value;
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<I, 12>, k0, x), *dpf::eval_point(dpf::out<I, 12>, k1, x)),
expect);
};
check(std::integral_constant<std::size_t, 0>{}, uint8_t{1});
check(std::integral_constant<std::size_t, 1>{}, uint8_t{2});
check(std::integral_constant<std::size_t, 7>{}, uint8_t{8});
check(std::integral_constant<std::size_t, 15>{}, uint8_t{16});
uint32_t nb = flip_lane_lsb(x, 12, 32);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, nb), *dpf::eval_point(dpf::out<0, 12>, k1, nb)),
uint8_t{0});
uint32_t off_pref = x ^ (1u << 31);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<15, 12>, k0, off_pref),
*dpf::eval_point(dpf::out<15, 12>, k1, off_pref)),
uint8_t{0});
}
TEST_F(IncrementalDpfTest, EightUint16AndFourUint32Packed)
{
uint32_t x = 0x11121314u;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<15>(
uint16_t{100}, uint16_t{200}, uint16_t{300}, uint16_t{400},
uint16_t{500}, uint16_t{600}, uint16_t{700}, uint16_t{800}),
dpf::at<20>(
uint32_t{1000}, uint32_t{2000}, uint32_t{3000}, uint32_t{4000}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::num_outputs, 12u);
// u16@15 => level 12; u32@20 => level 18
EXPECT_EQ(KT::meta[0].tree_level, 12u);
EXPECT_EQ(KT::meta[8].tree_level, 18u);
EXPECT_EQ(KT::meta[0].group_id, KT::meta[7].group_id);
EXPECT_EQ(KT::meta[8].group_id, KT::meta[11].group_id);
EXPECT_NE(KT::meta[0].group_id, KT::meta[8].group_id);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 15>, k0, x), *dpf::eval_point(dpf::out<0, 15>, k1, x)),
uint16_t{100});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 15>, k0, x), *dpf::eval_point(dpf::out<7, 15>, k1, x)),
uint16_t{800});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8, 20>, k0, x), *dpf::eval_point(dpf::out<8, 20>, k1, x)),
uint32_t{1000});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<11, 20>, k0, x), *dpf::eval_point(dpf::out<11, 20>, k1, x)),
uint32_t{4000});
}
TEST_F(IncrementalDpfTest, ManyBitsOnePrefix)
{
uint32_t x = 0x00c0ffeeu;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(
dpf::bit::one, dpf::bit::one, dpf::bit{false}, dpf::bit::one,
dpf::bit{false}, dpf::bit::one, dpf::bit::one, dpf::bit{false}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::meta[0].group_id, KT::meta[7].group_id);
EXPECT_EQ(KT::depth, 3u);
bool expect[] = {true, true, false, true, false, true, true, false};
auto check_bit = [&](auto idx, bool e) {
constexpr std::size_t I = decltype(idx)::value;
EXPECT_EQ(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<I, 10>, k0, x), *dpf::eval_point(dpf::out<I, 10>, k1, x))),
e);
};
check_bit(std::integral_constant<std::size_t, 0>{}, expect[0]);
check_bit(std::integral_constant<std::size_t, 2>{}, expect[2]);
check_bit(std::integral_constant<std::size_t, 7>{}, expect[7]);
}
TEST_F(IncrementalDpfTest, FixedpointIntermediateAndFullDomain)
{
using fp16 = grotto::fixedpoint<16>;
using fp8 = grotto::fixedpoint<8, int32_t>;
uint32_t x = 0x2a2b2c2du;
fp16 a = fp16::from_raw(0x00010000); // 1.0
fp16 b = fp16::from_raw(0x00008000); // 0.5
fp8 c = fp8::from_raw(0x00000100); // 1.0 in Q8.8? frac=8 on int32
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<12>(a, b),
dpf::at<18>(c),
fp16::from_raw(0x00020000)); // 2.0 full-domain
using KT = std::decay_t<decltype(k0)>;
// fp16 is 64-bit packable: lg=1 => at<12> level 11; fp8 32-bit lg=2 => at<18> level 16
EXPECT_EQ(KT::meta[0].tree_level, 11u);
EXPECT_EQ(KT::meta[1].tree_level, 11u);
EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id);
EXPECT_EQ(KT::meta[2].tree_level, 16u);
EXPECT_EQ(KT::depth, 31u); // full-domain fp16: 32-1
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)), a);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)), b);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 18>, k0, x), *dpf::eval_point(dpf::out<2, 18>, k1, x)), c);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, k0, x), *dpf::eval_point(dpf::out<3, 32>, k1, x)),
fp16::from_raw(0x00020000));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, flip_lane_lsb(x, 12, 32)),
*dpf::eval_point(dpf::out<0, 12>, k1, flip_lane_lsb(x, 12, 32))),
fp16{});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, k0, x ^ 1u), *dpf::eval_point(dpf::out<3, 32>, k1, x ^ 1u)),
fp16{});
}
TEST_F(IncrementalDpfTest, FixedpointMixedWithIntegralAndBit)
{
using fp16 = grotto::fixedpoint<16>;
uint32_t x = 0x55aa00ffu;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(dpf::bit::one),
dpf::at<14>(fp16::from_raw(0x00004000), uint8_t{9}),
dpf::at<20>(uint32_t{42}, fp16::from_raw(0xffff0000)),
int64_t{-7});
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 14>, k0, x), *dpf::eval_point(dpf::out<1, 14>, k1, x)),
fp16::from_raw(0x00004000));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 14>, k0, x), *dpf::eval_point(dpf::out<2, 14>, k1, x)),
uint8_t{9});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 20>, k0, x), *dpf::eval_point(dpf::out<3, 20>, k1, x)),
uint32_t{42});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 20>, k0, x), *dpf::eval_point(dpf::out<4, 20>, k1, x)),
fp16::from_raw(0xffff0000));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, x), *dpf::eval_point(dpf::out<5, 32>, k1, x)),
int64_t{-7});
}
TEST_F(IncrementalDpfTest, FixedpointDealerMatchesDoernerShelat)
{
using fp16 = grotto::fixedpoint<16>;
using fp8 = grotto::fixedpoint<8, int32_t>;
uint32_t x = 0x0abcdef0u;
uint32_t x0 = 0x11111111u;
uint32_t x1 = x ^ x0;
fp16 y = fp16::from_raw(0x00018000);
fp8 z = fp8::from_raw(0x00000200);
dpf::wildcard_value<fp16> wc;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<12>(y, z),
dpf::at<16>(wc),
fp16::from_raw(0x00030000));
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<12>(y, z), dpf::at<16>(wc), fp16::from_raw(0x00030000));
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, dealer.first, x),
*dpf::eval_point(dpf::out<0, 12>, dealer.second, x)),
y);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, dealer.first, x),
*dpf::eval_point(dpf::out<1, 12>, dealer.second, x)),
z);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, dealer.first, x),
*dpf::eval_point(dpf::out<3, 32>, dealer.second, x)),
fp16::from_raw(0x00030000));
}
TEST_F(IncrementalDpfTest, DenseSameLevelManyGroups)
{
// 12 groups on tree level 5 via matching N - lg(opl):
// bit@12, u8@9, u16@8, u32@7, u64@6, xu32@7 — only a few unique.
// Use distinct bit prefixes all at level 5: N = 5+7 = 12 for bits only,
// and pad with other widths at level 5.
// bit N=12, u8 N=9, u16 N=8, u32 N=7, fp8(i32) N=7, xor u32 N=7 — still few.
// Stack many *bit* prefixes that share level by using N=12 only once...
// Instead: many groups of different widths all non-final at level 10,
// each with several packed leaves to push pos_base up.
uint32_t x = 0x6f5e4d3cu;
using fp8 = grotto::fixedpoint<8, int32_t>;
auto [k0, k1] = dpf::make_dpf(x,
// level 3: four bit prefixes (10,11,12,13) — wait different levels
// Force level 10 for: u8@14, u16@13, u32@12, u64@11, fp8@12, xu32@12
dpf::at<14>(uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4}),
dpf::at<13>(uint16_t{10}, uint16_t{20}, uint16_t{30}, uint16_t{40}),
dpf::at<12>(uint32_t{100}, uint32_t{200}),
dpf::at<11>(uint64_t{1000}, uint64_t{2000}),
dpf::at<12>(fp8::from_raw(0x10), fp8::from_raw(0x20)),
dpf::at<12>(dpf::xor_wrapper<uint32_t>{0xaa},
dpf::xor_wrapper<uint32_t>{0xbb}),
// deepest
uint32_t{999});
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::meta[0].tree_level, 10u); // 14-4
EXPECT_EQ(KT::meta[4].tree_level, 10u); // 13-3
EXPECT_EQ(KT::meta[8].tree_level, 10u); // 12-2
EXPECT_EQ(KT::meta[10].tree_level, 10u); // 11-1
// groups at level 10 take consecutive pos bases starting at 2
EXPECT_EQ(KT::meta[0].pos_base, 2u);
EXPECT_GE(KT::meta[10].pos_base, 2u);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, k0, x), *dpf::eval_point(dpf::out<0, 14>, k1, x)),
uint8_t{1});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 14>, k0, x), *dpf::eval_point(dpf::out<3, 14>, k1, x)),
uint8_t{4});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 13>, k0, x), *dpf::eval_point(dpf::out<4, 13>, k1, x)),
uint16_t{10});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 13>, k0, x), *dpf::eval_point(dpf::out<7, 13>, k1, x)),
uint16_t{40});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8, 12>, k0, x), *dpf::eval_point(dpf::out<8, 12>, k1, x)),
uint32_t{100});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<10, 11>, k0, x), *dpf::eval_point(dpf::out<10, 11>, k1, x)),
uint64_t{1000});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<12, 12>, k0, x), *dpf::eval_point(dpf::out<12, 12>, k1, x)),
fp8::from_raw(0x10));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<14, 12>, k0, x), *dpf::eval_point(dpf::out<14, 12>, k1, x)),
dpf::xor_wrapper<uint32_t>{0xaa});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<16, 32>, k0, x), *dpf::eval_point(dpf::out<16, 32>, k1, x)),
uint32_t{999});
}
TEST_F(IncrementalDpfTest, TwelveBitPrefixesPlusFullDomain)
{
// 12 distinct bit prefixes (12 groups) plus a full-domain payload.
uint32_t x = 0x13579bdFu;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(dpf::bit::one),
dpf::at<11>(dpf::bit::one),
dpf::at<12>(dpf::bit::one),
dpf::at<13>(dpf::bit::one),
dpf::at<14>(dpf::bit::one),
dpf::at<15>(dpf::bit::one),
dpf::at<16>(dpf::bit::one),
dpf::at<17>(dpf::bit::one),
dpf::at<18>(dpf::bit::one),
dpf::at<19>(dpf::bit::one),
dpf::at<20>(dpf::bit::one),
dpf::at<21>(dpf::bit::one),
uint64_t{0x1122334455667788ull});
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::num_outputs, 13u);
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
EXPECT_TRUE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<11, 21>, k0, x), *dpf::eval_point(dpf::out<11, 21>, k1, x))));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<12, 32>, k0, x), *dpf::eval_point(dpf::out<12, 32>, k1, x)),
uint64_t{0x1122334455667788ull});
// Off-prefix for the deepest bit leaf.
uint32_t off = x ^ (1u << (32 - 21));
EXPECT_FALSE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<11, 21>, k0, off), *dpf::eval_point(dpf::out<11, 21>, k1, off))));
}
TEST_F(IncrementalDpfTest, TwoFullPackedNodesSameLevel)
{
// Two separate at<14>(16 x u8) would be same prefix+width => one group.
// Use at<14> and at<15> both u8: levels 10 and 11.
// Same level two full nodes: at<14>(16 u8) is one group of 16 (= 16 blocks
// if each u8 is 1 block — yes blen=1, so 16 blocks in one make_leaves).
// Add at<13>(8 u16) at level 10 as second group on same level.
uint32_t x = 0x01020304u;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<14>(
uint8_t{0}, uint8_t{1}, uint8_t{2}, uint8_t{3},
uint8_t{4}, uint8_t{5}, uint8_t{6}, uint8_t{7},
uint8_t{8}, uint8_t{9}, uint8_t{10}, uint8_t{11},
uint8_t{12}, uint8_t{13}, uint8_t{14}, uint8_t{15}),
dpf::at<13>(
uint16_t{0x10}, uint16_t{0x20}, uint16_t{0x30}, uint16_t{0x40},
uint16_t{0x50}, uint16_t{0x60}, uint16_t{0x70}, uint16_t{0x80}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::meta[0].tree_level, 10u);
EXPECT_EQ(KT::meta[16].tree_level, 10u);
EXPECT_EQ(KT::meta[0].pos_base, 0u); // deepest (only) level
EXPECT_EQ(KT::meta[16].pos_base, 16u); // after 16 u8 blocks
EXPECT_EQ(KT::num_outputs, 24u);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, k0, x), *dpf::eval_point(dpf::out<0, 14>, k1, x)),
uint8_t{0});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<15, 14>, k0, x), *dpf::eval_point(dpf::out<15, 14>, k1, x)),
uint8_t{15});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<16, 13>, k0, x), *dpf::eval_point(dpf::out<16, 13>, k1, x)),
uint16_t{0x10});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<23, 13>, k0, x), *dpf::eval_point(dpf::out<23, 13>, k1, x)),
uint16_t{0x80});
}
TEST_F(IncrementalDpfTest, IntervalAtMatchesPointEval)
{
// at<12>(uint8) => lg_opl=4, tree_level=8, lane domain 2^12.
uint32_t x = 0x00a5b6c7u;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::meta[0].tree_level, 8u);
const uint32_t lane = x >> (32 - 12);
const uint32_t from = (lane & ~0xffu);
const uint32_t to = from + 255u;
auto [buf0, it0] = dpf::eval_interval(dpf::out<0, 12>, k0, from, to);
auto [buf1, it1] = dpf::eval_interval(dpf::out<0, 12>, k1, from, to);
(void)it0;
(void)it1;
auto y0 = dpf::eval_point(dpf::out<0, 12>, k0, x);
auto y1 = dpf::eval_point(dpf::out<0, 12>, k1, x);
const auto expect = recon(*y0, *y1);
const std::size_t opl = KT::template outputs_per_leaf_of<0>;
const std::size_t idx = static_cast<std::size_t>(lane - from);
const std::size_t leaf = idx / opl;
const std::size_t off = idx % opl;
EXPECT_EQ(recon(buf0[leaf * opl + off], buf1[leaf * opl + off]), expect);
const uint32_t other_lane = (lane ^ 1u);
if (other_lane >= from && other_lane <= to)
{
const std::size_t oidx = static_cast<std::size_t>(other_lane - from);
EXPECT_EQ(recon(buf0[(oidx / opl) * opl + (oidx % opl)],
buf1[(oidx / opl) * opl + (oidx % opl)]),
uint8_t{0});
}
}
TEST_F(IncrementalDpfTest, FullAtDeepestAndSequenceAt)
{
uint32_t x = 0x11121314u;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint8_t{7}), uint32_t{99});
using KT = std::decay_t<decltype(k0)>;
constexpr auto I = KT::deepest_output;
constexpr auto N = KT::meta[I].prefix;
EXPECT_EQ(N, 32u);
std::array<uint32_t, 3> pts{{x, x ^ 1u, x ^ 0x100u}};
auto buf0 = dpf::make_output_buffer_for<I>(k0, pts.size());
auto buf1 = dpf::make_output_buffer_for<I>(k1, pts.size());
dpf::eval_sequence(dpf::out<I, N>, k0, pts.begin(), pts.end(), buf0);
dpf::eval_sequence(dpf::out<I, N>, k1, pts.begin(), pts.end(), buf1);
constexpr auto opl = KT::template outputs_per_leaf_of<I>;
auto e0 = dpf::eval_point(dpf::out<I, N>, k0, pts[0]);
auto e1 = dpf::eval_point(dpf::out<I, N>, k1, pts[0]);
EXPECT_EQ(recon(buf0[e0.offset], buf1[e1.offset]), recon(*e0, *e1));
auto f0 = dpf::eval_point(dpf::out<I, N>, k0, pts[1]);
auto f1 = dpf::eval_point(dpf::out<I, N>, k1, pts[1]);
EXPECT_EQ(recon(buf0[opl + f0.offset], buf1[opl + f1.offset]),
recon(*f0, *f1));
auto buf0b = dpf::make_output_buffer_for<I>(k0, pts.size());
dpf::eval_sequence(k0, pts.begin(), pts.end(), buf0b);
EXPECT_EQ(buf0b[e0.offset], buf0[e0.offset]);
}
TEST_F(IncrementalDpfTest, LocalCwProtocolMatchesDsRandomness)
{
uint32_t x = 0xabcdef01u;
uint32_t x0 = 0x11111111u;
uint32_t x1 = x ^ x0;
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto via_rng = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<10>(dpf::bit::one), uint32_t{5});
reset_tape_roots();
PadA pads{};
dpf::local_cw_protocol<PadA> proto{pads};
auto via_proto = dpf::make_dpf_doerner_shelat(x0, x1, take_root, proto,
dpf::at<10>(dpf::bit::one), uint32_t{5});
EXPECT_EQ(static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, via_rng.first, x), *dpf::eval_point(dpf::out<0, 10>, via_rng.second, x))),
static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, via_proto.first, x), *dpf::eval_point(dpf::out<0, 10>, via_proto.second, x))));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, via_rng.first, x),
*dpf::eval_point(dpf::out<1, 32>, via_rng.second, x)),
recon(*dpf::eval_point(dpf::out<1, 32>, via_proto.first, x),
*dpf::eval_point(dpf::out<1, 32>, via_proto.second, x)));
}
TEST_F(IncrementalDpfTest, DpfAndCmpSameKey)
{
const uint32_t alpha = 0x00abcdefu;
const uint64_t yt = 42u;
auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(yt));
EXPECT_TRUE(k0.has_cmp());
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)),
7u);
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u),
*dpf::eval_point(k1, alpha ^ 1u)),
0u);
const uint64_t mask = k0.cmp().mask;
auto recon_cmp = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
};
EXPECT_EQ(recon_cmp(alpha - 1u), yt);
EXPECT_EQ(recon_cmp(0u), yt);
EXPECT_EQ(recon_cmp(alpha), 0u);
EXPECT_EQ(recon_cmp(alpha + 1u), 0u);
auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(yt));
auto recon_ge = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, q), dpf::eval_point(dpf::cmp, g1, q)) & mask;
};
EXPECT_EQ(recon_ge(alpha - 1u), 0u);
EXPECT_EQ(recon_ge(alpha), yt);
EXPECT_EQ(recon_ge(alpha + 1u), yt);
}
TEST_F(IncrementalDpfTest, CmpPairAndRelations)
{
const uint32_t alpha = 100u;
const uint64_t yt = 5u, yf = 9u;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf));
const uint64_t mask = k0.cmp().mask;
auto r = [&](uint32_t q) {
return dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, q),
dpf::eval_point(dpf::cmp, k1, q)) & mask;
};
EXPECT_EQ(r(50u), yt);
EXPECT_EQ(r(100u), yf);
EXPECT_EQ(r(200u), yf);
auto [a0, a1] = dpf::make_dpf(alpha, dpf::leq(yt, yf));
auto rq = [&](auto &x0, auto &x1, uint32_t q) {
return dpf::reconstruct(
dpf::eval_point(dpf::cmp, x0, q),
dpf::eval_point(dpf::cmp, x1, q)) & mask;
};
EXPECT_EQ(rq(a0, a1, 100u), yt);
EXPECT_EQ(rq(a0, a1, 101u), yf);
auto [b0, b1] = dpf::make_dpf(alpha, dpf::gt(yt, yf));
EXPECT_EQ(rq(b0, b1, 100u), yf);
EXPECT_EQ(rq(b0, b1, 101u), yt);
auto [c0, c1] = dpf::make_dpf(alpha, dpf::geq(yt, yf));
EXPECT_EQ(rq(c0, c1, 99u), yf);
EXPECT_EQ(rq(c0, c1, 100u), yt);
}
TEST_F(IncrementalDpfTest, EqSynonymAndEqAt)
{
const uint32_t alpha = 0x12345678u;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(uint32_t{7}));
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)),
7u);
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u),
*dpf::eval_point(k1, alpha ^ 1u)),
0u);
auto [p0, p1] = dpf::make_dpf(alpha, dpf::eq(uint32_t{7}, uint32_t{3}));
EXPECT_EQ(recon(*dpf::eval_point(p0, alpha), *dpf::eval_point(p1, alpha)),
7u);
EXPECT_EQ(recon(*dpf::eval_point(p0, alpha ^ 1u),
*dpf::eval_point(p1, alpha ^ 1u)),
3u);
auto [q0, q1] = dpf::make_dpf(alpha, dpf::eq_at<16>(uint16_t{9}),
dpf::lt_at<8>(uint64_t{5}));
EXPECT_TRUE(q0.has_cmp());
EXPECT_EQ(q0.cmp().nbits, 8);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, q0, alpha),
*dpf::eval_point(dpf::out<0, 16>, q1, alpha)),
9u);
const uint64_t m8 = q0.cmp().mask;
auto top8 = [](uint32_t v) { return v >> 24; };
auto recon8 = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, q0, q), dpf::eval_point(dpf::cmp, q1, q)) & m8;
};
EXPECT_EQ(recon8(alpha), 0u);
EXPECT_EQ(recon8((top8(alpha) - 1u) << 24), 5u);
EXPECT_EQ(recon8((top8(alpha) + 1u) << 24), 0u);
}
TEST_F(IncrementalDpfTest, CmpWithDoernerShelat)
{
const uint32_t alpha = 0x01020304u;
const uint32_t x0 = 0x11111111u;
const uint32_t x1 = alpha ^ x0;
auto [k0, k1] = dpf::make_dpf_doerner_shelat(x0, x1, uint32_t{3},
dpf::lt(uint64_t{11}));
EXPECT_TRUE(k0.has_cmp());
const uint64_t mask = k0.cmp().mask;
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1), dpf::eval_point(dpf::cmp, k1, alpha - 1)) & mask,
11u);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, 0u);
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)),
3u);
}
TEST_F(IncrementalDpfTest, CmpDealerMatchesDoernerShelat)
{
uint32_t x = 0x00abcdefu;
uint32_t x0 = 0x12345678u;
uint32_t x1 = x ^ x0;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<10>(dpf::bit::one),
dpf::lt(uint64_t{42}));
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<10>(dpf::bit::one),
dpf::lt(uint64_t{42}));
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
// Comparison channel must match (same tree value CWs; δ not clear on keys).
EXPECT_EQ(dealer.first.cmp().nbits, ds.first.cmp().nbits);
EXPECT_EQ(dealer.first.cmp().kind, ds.first.cmp().kind);
EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last());
EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(),
ds.first.value_cw().data(),
dealer.first.value_cw().size() * sizeof(uint64_t)));
EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend());
EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend());
// Shares reconstruct if_false (=0 here).
EXPECT_EQ(dpf::reconstruct(dealer.first.cmp_addend(), dealer.second.cmp_addend())
& dealer.first.cmp().mask,
0u);
const uint64_t mask = dealer.first.cmp().mask;
for (uint32_t q : {x - 1u, x, x + 1u, 0u})
{
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, dealer.first, q), dpf::eval_point(dpf::cmp, dealer.second, q))
& mask,
dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask);
}
}
TEST_F(IncrementalDpfTest, CmpIntervalAndSequenceBuffers)
{
const uint16_t alpha = 0x00aau;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt_at<8>(uint64_t{5}, uint64_t{1}));
EXPECT_TRUE(k0.has_cmp());
const uint64_t mask = k0.cmp().mask;
auto buf0 = dpf::make_output_buffer(dpf::cmp, k0, uint8_t{0x00},
uint8_t{0xff});
auto buf1 = dpf::make_output_buffer(dpf::cmp, k1, uint8_t{0x00},
uint8_t{0xff});
dpf::eval_interval(dpf::cmp, k0, uint8_t{0x00}, uint8_t{0xff}, buf0);
dpf::eval_interval(dpf::cmp, k1, uint8_t{0x00}, uint8_t{0xff}, buf1);
// alpha top-8 is 0x00; wait alpha=0x00aa so top 8 of 16-bit is 0x00.
// lt_at<8>: compare on top 8 bits of 16-bit domain = 0x00.
// Lane 0x00 == alpha prefix => not < => if_false=1
// Lane < 0x00: none for uint8
EXPECT_EQ(recon(buf0[0], buf1[0]) & mask, 1u);
EXPECT_EQ(recon(buf0[1], buf1[1]) & mask, 1u); // 0x01 > 0x00
// Use a mid alpha so both sides of the cut appear.
const uint16_t a2 = 0x8000u;
auto [p0, p1] = dpf::make_dpf(a2, dpf::lt_at<8>(uint64_t{5}, uint64_t{1}));
auto b0 = dpf::eval_interval(dpf::cmp, p0, uint8_t{0x7f}, uint8_t{0x81});
auto b1 = dpf::eval_interval(dpf::cmp, p1, uint8_t{0x7f}, uint8_t{0x81});
// top8(a2)=0x80; lanes 0x7f,0x80,0x81 -> 5, 1, 1
EXPECT_EQ(recon(b0[0], b1[0]) & mask, 5u);
EXPECT_EQ(recon(b0[1], b1[1]) & mask, 1u);
EXPECT_EQ(recon(b0[2], b1[2]) & mask, 1u);
std::array<uint16_t, 3> pts{{static_cast<uint16_t>(a2 - 1), a2,
static_cast<uint16_t>(a2 + 1)}};
auto s0 = dpf::make_output_buffer(dpf::cmp, p0, pts.size());
auto s1 = dpf::make_output_buffer(dpf::cmp, p1, pts.size());
dpf::eval_sequence(dpf::cmp, p0, pts.begin(), pts.end(), s0);
dpf::eval_sequence(dpf::cmp, p1, pts.begin(), pts.end(), s1);
EXPECT_EQ(recon(s0[0], s1[0]) & mask, 5u);
EXPECT_EQ(recon(s0[1], s1[1]) & mask, 1u);
EXPECT_EQ(recon(s0[2], s1[2]) & mask, 1u);
}
TEST_F(IncrementalDpfTest, CmpSharesPathMemoizerWithEvalAt)
{
const uint32_t alpha = 0x00abcdefu;
auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(uint64_t{3}));
using key_t = std::decay_t<decltype(k0)>;
auto path0 = dpf::make_basic_path_memoizer(k0);
auto path1 = dpf::make_basic_path_memoizer(k1);
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0),
*dpf::eval_point(k1, alpha, path1)),
7u);
const uint64_t mask = k0.cmp().mask;
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u, path0), dpf::eval_point(dpf::cmp, k1, alpha - 1u, path1))
& mask,
3u);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, path0), dpf::eval_point(dpf::cmp, k1, alpha, path1))
& mask,
0u);
// Re-eval point with the same memoizers (resume / reuse).
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0),
*dpf::eval_point(k1, alpha, path1)),
7u);
}
TEST_F(IncrementalDpfTest, CmpOnlyZeroOutputs)
{
const uint32_t alpha = 0x42u;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(uint64_t{9}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::num_outputs, 0u);
EXPECT_TRUE(k0.has_cmp());
EXPECT_EQ(k0.cmp().nbits, 32);
const uint64_t mask = k0.cmp().mask;
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u)) & mask,
9u);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, 0u);
}
TEST_F(IncrementalDpfTest, CmpDomainEdgeTrivial)
{
// leq at α = 2^n-1 is always-true; gt there is always-false.
const uint8_t alpha = 0xffu;
auto [l0, l1] = dpf::make_dpf(alpha, dpf::leq(uint64_t{7}, uint64_t{1}));
EXPECT_EQ(l0.cmp().trivial, dpf::cmp_trivial::always_true);
const uint64_t mask = l0.cmp().mask;
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, l0, uint8_t{0}), dpf::eval_point(dpf::cmp, l1, uint8_t{0})) & mask,
7u);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, l0, alpha), dpf::eval_point(dpf::cmp, l1, alpha)) & mask, 7u);
// if_true = 7 is split across addends, not stored clear.
EXPECT_EQ(dpf::reconstruct(l0.cmp_addend(), l1.cmp_addend()) & mask, 7u);
auto [g0, g1] = dpf::make_dpf(alpha, dpf::gt(uint64_t{7}, uint64_t{1}));
EXPECT_EQ(g0.cmp().trivial, dpf::cmp_trivial::always_false);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, uint8_t{0}), dpf::eval_point(dpf::cmp, g1, uint8_t{0})) & mask,
1u);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha), dpf::eval_point(dpf::cmp, g1, alpha)) & mask, 1u);
EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, 1u);
}
TEST_F(IncrementalDpfTest, CmpValueCwGroupWidth)
{
// uint64 payload → 8-byte value_cw words (the classic full width).
const uint32_t alpha = 0x00abcdefu;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(uint64_t{42}));
using K64 = std::decay_t<decltype(k0)>;
EXPECT_EQ(sizeof(typename K64::value_cw_word), 8u);
EXPECT_EQ(K64::cmp_out_bits, 64u);
// uint16 payload → 2-byte value_cw words (no padded uint64 on the wire).
auto [u0, u1] = dpf::make_dpf(alpha, dpf::lt(uint16_t{42}));
using K16 = std::decay_t<decltype(u0)>;
EXPECT_EQ(sizeof(typename K16::value_cw_word), 2u);
EXPECT_EQ(K16::cmp_out_bits, 16u);
// bit payload → 1-byte value_cw words (no 8-byte-per-level waste).
auto [b0, b1] = dpf::make_dpf(alpha, dpf::lt(dpf::bit::one));
using KB = std::decay_t<decltype(b0)>;
EXPECT_EQ(sizeof(typename KB::value_cw_word), 1u);
EXPECT_EQ(KB::cmp_out_bits, 1u);
// The narrow-width value CWs still reconstruct to the same (public) bytes.
EXPECT_TRUE(same_bytes(b0.value_cw().data(), b1.value_cw().data(),
b0.value_cw().size() * sizeof(typename KB::value_cw_word)));
// uint16 comparison reconstructs correctly through the narrow words.
const uint64_t m16 = u0.cmp().mask;
auto r16 = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, u0, q), dpf::eval_point(dpf::cmp, u1, q)) & m16;
};
EXPECT_EQ(r16(alpha - 1u), 42u);
EXPECT_EQ(r16(alpha), 0u);
EXPECT_EQ(r16(alpha + 1u), 0u);
}
TEST_F(IncrementalDpfTest, CmpValueCwGroupWidthDsParity)
{
// Narrow-width value CWs must stay byte-identical dealer↔Doerner–Shelat.
uint32_t x = 0x00abcdefu;
uint32_t x0 = 0x12345678u;
uint32_t x1 = x ^ x0;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::lt(uint16_t{42}));
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::lt(uint16_t{42}));
using KT = std::decay_t<decltype(dealer.first)>;
EXPECT_EQ(sizeof(typename KT::value_cw_word), 2u);
EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(),
ds.first.value_cw().data(),
dealer.first.value_cw().size() * sizeof(typename KT::value_cw_word)));
EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last());
EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend());
EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend());
const uint64_t mask = dealer.first.cmp().mask;
for (uint32_t q : {x - 1u, x, x + 1u, 0u})
{
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, dealer.first, q), dpf::eval_point(dpf::cmp, dealer.second, q)) & mask,
dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask);
}
}
TEST_F(IncrementalDpfTest, CmpPayloadHiddenOnKeys)
{
const uint32_t alpha = 0x55u;
const uint64_t yt = 42u, yf = 7u;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf));
const uint64_t mask = k0.cmp().mask;
const uint64_t delta = (yt - yf) & mask;
// δ is not a clear field; if_false is only as additive shares.
EXPECT_EQ(dpf::reconstruct(k0.cmp_addend(), k1.cmp_addend()) & mask, yf);
// value CWs are identical (public) and encode δ, not a readable beta field.
EXPECT_TRUE(same_bytes(k0.value_cw().data(), k1.value_cw().data(),
k0.value_cw().size() * sizeof(uint64_t)));
EXPECT_EQ(k0.cw_last(), k1.cw_last());
(void)delta;
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u))
& mask,
yt);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, yf);
auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(yt, yf));
// geq absorb target is δ + if_false = if_true.
EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, yt);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha), dpf::eval_point(dpf::cmp, g1, alpha)) & mask, yt);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha - 1u), dpf::eval_point(dpf::cmp, g1, alpha - 1u))
& mask,
yf);
}
TEST_F(IncrementalDpfTest, UnifiedEvalTargetSurface)
{
const uint32_t alpha = 0x00abcdefu;
auto [k0, k1] = dpf::make_dpf(alpha,
dpf::at<16>(uint16_t{9}),
uint32_t{7},
dpf::lt(uint64_t{3}, uint64_t{1}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_TRUE(dpf::is_incremental_dpf_key_v<KT>);
EXPECT_TRUE(dpf::is_out_v<decltype(dpf::out<0>)>);
EXPECT_TRUE(dpf::is_cmp_target_v<decltype(dpf::cmp)>);
// Point via out<I> / out<I,N> matches out<> point eval.
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha),
*dpf::eval_point(dpf::out<0, 16>, k1, alpha)),
9u);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, alpha),
*dpf::eval_point(dpf::out<1>, k1, alpha)),
7u);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha),
*dpf::eval_point(dpf::out<0, 16>, k1, alpha)),
9u);
// Cmp via eval_point(cmp).
const uint64_t mask = k0.cmp().mask;
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u))
& mask,
3u);
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha))
& mask,
1u);
// Interval / sequence target-first for cmp.
auto b0 = dpf::eval_interval(dpf::cmp, k0,
static_cast<uint32_t>(alpha - 1u), static_cast<uint32_t>(alpha + 1u));
auto b1 = dpf::eval_interval(dpf::cmp, k1,
static_cast<uint32_t>(alpha - 1u), static_cast<uint32_t>(alpha + 1u));
EXPECT_EQ(recon(b0[0], b1[0]) & mask, 3u);
EXPECT_EQ(recon(b0[1], b1[1]) & mask, 1u);
EXPECT_EQ(recon(b0[2], b1[2]) & mask, 1u);
std::array<uint32_t, 2> pts{{alpha - 1u, alpha}};
auto s0 = dpf::make_output_buffer(dpf::cmp, k0, pts.size());
auto s1 = dpf::make_output_buffer(dpf::cmp, k1, pts.size());
dpf::eval_sequence(dpf::cmp, k0, pts.begin(), pts.end(), s0);
dpf::eval_sequence(dpf::cmp, k1, pts.begin(), pts.end(), s1);
EXPECT_EQ(recon(s0[0], s1[0]) & mask, 3u);
EXPECT_EQ(recon(s0[1], s1[1]) & mask, 1u);
// Point interval via out<> matches out<> interval eval.
constexpr std::size_t N = 16;
constexpr std::size_t I = 0;
auto lane = static_cast<uint16_t>(alpha >> (32 - N));
auto [buf0, it0] = dpf::eval_interval(dpf::out<I, N>, k0, lane, lane);
auto [buf1, it1] = dpf::eval_interval(dpf::out<I, N>, k1, lane, lane);
(void)it0;
(void)it1;
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<I, N>, k0, alpha),
*dpf::eval_point(dpf::out<I, N>, k1, alpha)),
9u);
}
TEST_F(IncrementalDpfTest, ClassicKeyHasSlotMetaAndOutEval)
{
// Every key now carries a constexpr `slot_meta` table. A classic
// (single-level, equal-width, no-cmp) key keeps `is_multilevel == false`
// and still routes through the classic eval fast path, but the unified
// `out<I>` surface works on it too.
uint32_t x = 0x00abcdefu;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{7}, uint32_t{9});
using KT = std::decay_t<decltype(k0)>;
// Foundation traits: has slot meta (so "incremental" trait is true) but is
// not multi-level.
EXPECT_TRUE(dpf::is_incremental_dpf_key_v<KT>);
EXPECT_FALSE(dpf::is_multilevel_key_v<KT>);
EXPECT_EQ(KT::num_outputs, 2u);
EXPECT_EQ(KT::cmp_depth, 0u);
EXPECT_EQ(KT::deepest_output, 0u);
EXPECT_EQ(KT::meta[0].prefix, 32u);
EXPECT_EQ(KT::meta[1].prefix, 32u);
EXPECT_EQ(KT::meta[0].tree_level, KT::depth);
EXPECT_EQ(KT::meta[1].tree_level, KT::depth);
// Equal-width classic packing: one group, consecutive block positions.
// Effective leaf position = pos_base + index_in_group * block_len, which
// matches the classic `block_offset_of_leaf` layout.
EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id);
EXPECT_EQ(KT::meta[0].pos_base, 0u);
EXPECT_EQ(KT::meta[0].index_in_group, 0u);
EXPECT_EQ(KT::meta[1].index_in_group, 1u);
const std::size_t eff0 = KT::meta[0].pos_base
+ KT::meta[0].index_in_group * KT::meta[0].block_len;
const std::size_t eff1 = KT::meta[1].pos_base
+ KT::meta[1].index_in_group * KT::meta[1].block_len;
EXPECT_EQ(eff0, 0u);
EXPECT_EQ(eff1, KT::meta[1].block_len);
// Unified out<I> eval matches the classic per-slot eval_point.
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0>, k0, x),
*dpf::eval_point(dpf::out<0>, k1, x)),
recon(*dpf::eval_point<0>(k0, x), *dpf::eval_point<0>(k1, x)));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0>, k0, x),
*dpf::eval_point(dpf::out<0>, k1, x)),
7u);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x),
*dpf::eval_point(dpf::out<1>, k1, x)),
9u);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x ^ 1u),
*dpf::eval_point(dpf::out<1>, k1, x ^ 1u)),
0u);
}
TEST_F(IncrementalDpfTest, CmpWildcardAssignMatchesConcrete)
{
// Gen the comparison with a wildcard payload (δ opened at 0), then
// `assign_cmp` the concrete `lt(42)` and check both the (public) value CWs
// match a byte-for-byte concrete gen on the same tape and that the channel
// reconstructs the comparison.
const uint32_t alpha = 0x00abcdefu;
const uint64_t yt = 42u;
reset_tape_roots();
auto wc = dpf::make_dpf(alpha,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::lt(dpf::wildcard_value<uint64_t>{}));
using WT = std::decay_t<decltype(wc.first)>;
static_assert(WT::cmp_is_wildcard, "expected a wildcard cmp key");
EXPECT_TRUE(wc.first.has_cmp());
EXPECT_FALSE(wc.first.cmp_assigned());
// Evaluating before assignment must throw.
EXPECT_THROW(dpf::eval_point(dpf::cmp, wc.first, alpha), std::exception);
reset_tape_roots();
auto cc = dpf::make_dpf(alpha,
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, dpf::lt(yt));
using CT = std::decay_t<decltype(cc.first)>;
static_assert(!CT::cmp_is_wildcard, "concrete key must not be wildcard");
dpf::assign_cmp(wc.first, wc.second, yt);
EXPECT_TRUE(wc.first.cmp_assigned());
EXPECT_TRUE(wc.second.cmp_assigned());
// Value CWs / cw_last are public and now identical to the concrete gen.
EXPECT_TRUE(same_bytes(wc.first.value_cw().data(), cc.first.value_cw().data(),
wc.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
EXPECT_TRUE(same_bytes(wc.first.value_cw().data(), wc.second.value_cw().data(),
wc.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
EXPECT_EQ(wc.first.cw_last(), cc.first.cw_last());
const uint64_t mask = wc.first.cmp().mask;
EXPECT_EQ(dpf::reconstruct(wc.first.cmp_addend(), wc.second.cmp_addend()) & mask, 0u);
auto r = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, wc.first, q), dpf::eval_point(dpf::cmp, wc.second, q)) & mask;
};
EXPECT_EQ(r(alpha - 1u), yt);
EXPECT_EQ(r(0u), yt);
EXPECT_EQ(r(alpha), 0u);
EXPECT_EQ(r(alpha + 1u), 0u);
}
TEST_F(IncrementalDpfTest, CmpWildcardPairAndGeqAndInterval)
{
// Wildcard with both if_true and if_false, and a geq relation.
const uint32_t alpha = 100u;
const uint64_t yt = 5u, yf = 9u;
auto [k0, k1] = dpf::make_dpf(alpha,
dpf::lt(dpf::wildcard_value<uint64_t>{}));
dpf::assign_cmp(k0, k1, yt, yf);
const uint64_t mask = k0.cmp().mask;
auto r = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
};
EXPECT_EQ(r(50u), yt);
EXPECT_EQ(r(100u), yf);
EXPECT_EQ(r(200u), yf);
// if_false reconstructs from the split addend shares.
EXPECT_EQ(dpf::reconstruct(k0.cmp_addend(), k1.cmp_addend()) & mask, yf);
auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(dpf::wildcard_value<uint64_t>{}));
dpf::assign_cmp(g0, g1, yt, yf);
auto rg = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, q), dpf::eval_point(dpf::cmp, g1, q)) & mask;
};
EXPECT_EQ(rg(99u), yf);
EXPECT_EQ(rg(100u), yt);
EXPECT_EQ(rg(101u), yt);
// geq absorb target is δ + if_false = if_true.
EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, yt);
// Interval eval on an assigned wildcard cmp still works.
auto b0 = dpf::eval_interval(dpf::cmp, k0,
static_cast<uint32_t>(99u), static_cast<uint32_t>(101u));
auto b1 = dpf::eval_interval(dpf::cmp, k1,
static_cast<uint32_t>(99u), static_cast<uint32_t>(101u));
EXPECT_EQ(recon(b0[0], b1[0]) & mask, yt); // 99 < 100
EXPECT_EQ(recon(b0[1], b1[1]) & mask, yf); // 100 !< 100
EXPECT_EQ(recon(b0[2], b1[2]) & mask, yf); // 101 !< 100
}
TEST_F(IncrementalDpfTest, CmpWildcardNarrowPayloadAndAt)
{
// Narrow (uint16) wildcard payload keeps the group-width value CW words,
// and `lt_at<N>` prefixes work through the wildcard path.
const uint32_t alpha = 0x00abcdefu;
auto [k0, k1] = dpf::make_dpf(alpha,
dpf::at<16>(uint16_t{9}),
dpf::lt(dpf::wildcard_value<uint16_t>{}));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(sizeof(typename KT::value_cw_word), 2u);
EXPECT_TRUE(KT::cmp_is_wildcard);
// The concrete output slot is unaffected by the wildcard cmp.
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha),
*dpf::eval_point(dpf::out<0, 16>, k1, alpha)),
9u);
dpf::assign_cmp(k0, k1, uint16_t{42});
const uint64_t mask = k0.cmp().mask;
auto r = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
};
EXPECT_EQ(r(alpha - 1u), 42u);
EXPECT_EQ(r(alpha), 0u);
EXPECT_EQ(r(alpha + 1u), 0u);
}
TEST_F(IncrementalDpfTest, DsWildcardCmpMatchesDealerThenAssign)
{
// Local Doerner–Shelat gen of a wildcard comparison payload must be
// byte-identical to the dealer (δ = 0 CWs + addend blinds), and after
// `assign_cmp` the patched public CWs / reconstruction must match a
// concrete `lt(β)` keygen on the same tape.
const uint32_t alpha = 0x00abcdefu;
const uint32_t a0 = 0x12345678u;
const uint32_t a1 = alpha ^ a0;
const uint64_t yt = 42u;
reset_tape_roots();
auto dealer = dpf::make_dpf(alpha,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::lt(dpf::wildcard_value<uint64_t>{}));
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng,
dpf::lt(dpf::wildcard_value<uint64_t>{}));
using WT = std::decay_t<decltype(dealer.first)>;
static_assert(WT::cmp_is_wildcard);
EXPECT_FALSE(dealer.first.cmp_assigned());
EXPECT_FALSE(ds.first.cmp_assigned());
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(),
ds.first.value_cw().data(),
dealer.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last());
EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend());
EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend());
reset_tape_roots();
auto concrete = dpf::make_dpf(alpha,
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, dpf::lt(yt));
dpf::assign_cmp(dealer.first, dealer.second, yt);
dpf::assign_cmp(ds.first, ds.second, yt);
EXPECT_TRUE(dealer.first.cmp_assigned());
EXPECT_TRUE(ds.first.cmp_assigned());
EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(),
concrete.first.value_cw().data(),
dealer.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
EXPECT_TRUE(same_bytes(ds.first.value_cw().data(),
concrete.first.value_cw().data(),
ds.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
EXPECT_EQ(dealer.first.cw_last(), concrete.first.cw_last());
EXPECT_EQ(ds.first.cw_last(), concrete.first.cw_last());
const uint64_t mask = dealer.first.cmp().mask;
auto r_ds = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask;
};
auto r_cc = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, concrete.first, q), dpf::eval_point(dpf::cmp, concrete.second, q)) & mask;
};
for (uint32_t q : {alpha - 1u, alpha, alpha + 1u, 0u})
EXPECT_EQ(r_ds(q), r_cc(q)) << "q=" << q;
}
TEST_F(IncrementalDpfTest, DsWildcardCmpWithAtAndNarrowPayload)
{
const uint32_t alpha = 0x00abcdefu;
const uint32_t a0 = 0x0f0f0f0fu;
const uint32_t a1 = alpha ^ a0;
reset_tape_roots();
auto dealer = dpf::make_dpf(alpha,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<16>(uint16_t{9}),
dpf::lt(dpf::wildcard_value<uint16_t>{}));
reset_tape_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng,
dpf::at<16>(uint16_t{9}),
dpf::lt(dpf::wildcard_value<uint16_t>{}));
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, ds.first, alpha),
*dpf::eval_point(dpf::out<0, 16>, ds.second, alpha)),
uint16_t{9});
dpf::assign_cmp(ds.first, ds.second, uint16_t{7}, uint16_t{1});
const uint64_t mask = ds.first.cmp().mask;
auto r = [&](uint32_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask;
};
EXPECT_EQ(r(alpha - 1u), 7u);
EXPECT_EQ(r(alpha), 1u);
EXPECT_EQ(dpf::reconstruct(ds.first.cmp_addend(), ds.second.cmp_addend()) & mask, 1u);
}
TEST_F(IncrementalDpfTest, MultilevelInnerProductMatchesInterval)
{
// Inner product of a prefix-slot (`at<12>(u8)`) against a public weight
// vector must reconstruct to the dot of the interval outputs with the
// weights. Additive output => recon(ip) == Σ recon(interval)[j] * w[j].
uint32_t x = 0x00a5b6c7u;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42}), uint32_t{99});
const uint32_t lane = x >> (32 - 12);
const uint32_t from = (lane & ~0xffu);
const uint32_t to = from + 255u;
auto [buf0, it0] = dpf::eval_interval(dpf::out<0, 12>, k0, from, to);
auto [buf1, it1] = dpf::eval_interval(dpf::out<0, 12>, k1, from, to);
(void)it0;
(void)it1;
std::vector<uint64_t> w(buf0.size());
uint64_t expect = 0;
for (std::size_t i = 0; i < w.size(); ++i)
{
w[i] = (i * 7u + 1u) & 0x3fu;
expect += static_cast<uint64_t>(recon(buf0[i], buf1[i])) * w[i];
}
expect &= 0xffu; // u8 output wraps at 8 bits
auto a = dpf::eval_inner_product(dpf::out<0, 12>, k0, from, to, w);
auto b = dpf::eval_inner_product(dpf::out<0, 12>, k1, from, to, w);
EXPECT_EQ(static_cast<uint8_t>(recon(a, b)), static_cast<uint8_t>(expect));
// Same result when driving a caller-supplied stop-level memoizer.
using KT = std::decay_t<decltype(k0)>;
auto m0 = dpf::make_basic_interval_memoizer<KT, 0>(from, to);
auto m1 = dpf::make_basic_interval_memoizer<KT, 0>(from, to);
auto am = dpf::eval_inner_product(dpf::out<0, 12>, k0, from, to, w, m0);
auto bm = dpf::eval_inner_product(dpf::out<0, 12>, k1, from, to, w, m1);
EXPECT_EQ(am, a);
EXPECT_EQ(bm, b);
}
TEST_F(IncrementalDpfTest, CmpInnerProductMatchesInterval)
{
// Cmp inner product: Σ (path-sum share) * w over the interval; the two
// parties' results sum to Σ value[i] * w[i].
const uint16_t alpha = 0x00aau;
auto [k0, k1] = dpf::make_dpf(alpha,
dpf::lt_at<8>(uint64_t{5}, uint64_t{1}));
const uint64_t mask = k0.cmp().mask;
const uint8_t from = 0x00, to = 0x0f;
auto b0 = dpf::eval_interval(dpf::cmp, k0, from, to);
auto b1 = dpf::eval_interval(dpf::cmp, k1, from, to);
std::vector<uint64_t> w(b0.size());
uint64_t expect = 0;
for (std::size_t i = 0; i < w.size(); ++i)
{
w[i] = (i * 3u + 2u) & 0xffu;
const uint64_t value = recon(b0[i], b1[i]) & mask;
expect = (expect + value * (w[i] & mask)) & mask;
}
auto a = dpf::eval_inner_product(dpf::cmp, k0, from, to, w);
auto b = dpf::eval_inner_product(dpf::cmp, k1, from, to, w);
EXPECT_EQ((a + b) & mask, expect);
}
TEST_F(IncrementalDpfTest, BreadthFirstAtNonFinalSlot)
{
// Breadth-first sequence eval stopping at a prefix slot's tree level.
uint32_t x = 0x00a5b6c7u;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{77}), uint32_t{99});
constexpr std::size_t N = 12, I = 0;
const uint32_t xlane = x >> (32 - N);
std::vector<uint32_t> lanes = {
(xlane & ~0xfu), xlane,
static_cast<uint32_t>(xlane ^ 1u),
static_cast<uint32_t>((xlane + 5u) & 0xfffu),
static_cast<uint32_t>((xlane + 300u) & 0xfffu)};
std::sort(lanes.begin(), lanes.end());
lanes.erase(std::unique(lanes.begin(), lanes.end()), lanes.end());
auto bf0 = dpf::eval_sequence_breadth_first(dpf::out<I, N>, k0,
lanes.begin(), lanes.end());
auto bf1 = dpf::eval_sequence_breadth_first(dpf::out<I, N>, k1,
lanes.begin(), lanes.end());
for (std::size_t i = 0; i < lanes.size(); ++i)
{
const uint32_t q = lanes[i] << (32 - N);
const auto expect = recon(*dpf::eval_point(dpf::out<I, N>, k0, q),
*dpf::eval_point(dpf::out<I, N>, k1, q));
EXPECT_EQ(recon(bf0[i], bf1[i]), expect) << "i=" << i;
}
}
TEST_F(IncrementalDpfTest, CrossLevelPathMemoizerShallowThenDeep)
{
// A single path memoizer reused shallow (prefix slot) then deep (full
// domain) must agree with independent non-memoized evals at both levels.
uint32_t x = 0xabcdef01u;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(dpf::bit::one), uint32_t{1234});
using KT = std::decay_t<decltype(k0)>;
dpf::basic_path_memoizer<KT> p0{}, p1{};
for (uint32_t q : {x, static_cast<uint32_t>(x ^ 1u),
static_cast<uint32_t>(x ^ 0x80000000u), 0u})
{
// Shallow first, then deep, sharing the same memoizer.
auto s0 = dpf::eval_point(dpf::out<0, 10>, k0, q, p0);
auto s1 = dpf::eval_point(dpf::out<0, 10>, k1, q, p1);
auto d0 = dpf::eval_point(dpf::out<1, 32>, k0, q, p0);
auto d1 = dpf::eval_point(dpf::out<1, 32>, k1, q, p1);
auto sref = static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, q), *dpf::eval_point(dpf::out<0, 10>, k1, q)));
auto dref = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q),
*dpf::eval_point(dpf::out<1, 32>, k1, q));
EXPECT_EQ(static_cast<bool>(recon(*s0, *s1)), sref) << "q=" << q;
EXPECT_EQ(recon(*d0, *d1), dref) << "q=" << q;
}
}
TEST_F(IncrementalDpfTest, SequenceRecipeAtPrefixSlot)
{
// Recipe depth follows the slot's tree_level, not the full key depth.
uint32_t x = 0x00a5b6c7u;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42}), uint32_t{7});
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::meta[0].tree_level, 8u);
const uint32_t lane = x >> (32 - 12);
std::array<uint32_t, 3> pts{{
static_cast<uint32_t>(lane & ~1u), lane,
static_cast<uint32_t>((lane & ~1u) + 1u)}};
std::sort(pts.begin(), pts.end());
auto recipe = dpf::make_sequence_recipe(dpf::out<0, 12>, k0,
pts.begin(), pts.end());
EXPECT_EQ(recipe.depth(), KT::meta[0].tree_level);
EXPECT_EQ(recipe.output_indices().size(), pts.size());
// Breadth-first at the same slot reconstructs the point payloads.
auto b0 = dpf::eval_sequence_breadth_first(dpf::out<0, 12>, k0,
pts.begin(), pts.end());
auto b1 = dpf::eval_sequence_breadth_first(dpf::out<0, 12>, k1,
pts.begin(), pts.end());
for (std::size_t i = 0; i < pts.size(); ++i)
{
const uint32_t q = static_cast<uint32_t>(pts[i] << (32 - 12));
EXPECT_EQ(recon(b0[i], b1[i]),
recon(*dpf::eval_point(dpf::out<0, 12>, k0, q),
*dpf::eval_point(dpf::out<0, 12>, k1, q)));
}
}