libdpf/test/tests/stress_scenarios_test.cpp

2266 lines
90 KiB
C++
Raw Normal View History

// Stress scenarios that exercise BOTH classic-shaped and multilevel/cmp DPF
// keys through the unified eval surface. The intent is to give the classic
// (single-level, equal-width) and multilevel (per-slot `at<N>` + `cmp`) code
// paths symmetric coverage: everything the multilevel surface can do is also
// checked on classic keys, and every classic strength (Doerner-Shelat parity,
// inner product, memoizer variants, recipe modes) is checked on multilevel
// keys where it applies.
//
// Patterns (recon xor-vs-additive, DS tape/roots harness) mirror
// incremental_test.cpp.
#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
{
// ---------------------------------------------------------------------------
// Deterministic randomness harness (roots + pad tape), copied from
// incremental_test.cpp so dealer <-> Doerner-Shelat byte-identity holds.
// ---------------------------------------------------------------------------
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); }
};
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(0x2222 * (i + 1), 0xBEEF0000u + i * 23);
for (std::size_t i = 0; i < g_tape.size(); ++i)
g_tape[i] = static_cast<unsigned char>(i * 31 + 7);
}
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: party0 - party1
}
/// 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)));
}
// ---------------------------------------------------------------------------
// Byte-identity helpers (classic + incremental) copied from incremental_test.
// ---------------------------------------------------------------------------
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;
}
// Byte-for-byte comparison of the comparison (DCF) channel: metadata,
// value-CW array, `cw_last`, and this party's `cmp_addend` share. All four
// carry the secret comparison payload / threshold, so a dealer key and a
// Doerner-Shelat key must agree on every byte here (not merely reconstruct).
template <typename Key>
bool same_cmp_channel(const Key & a, const Key & b)
{
if (a.has_cmp() != b.has_cmp())
return false;
if (!a.has_cmp())
return true;
const auto & ca = a.cmp();
const auto & cb = b.cmp();
if (ca.nbits != cb.nbits || ca.mask != cb.mask || ca.kind != cb.kind
|| ca.trivial != cb.trivial || ca.eval_as_ge != cb.eval_as_ge
|| ca.include_eq != cb.include_eq || ca.active != cb.active
|| ca.incremental != cb.incremental)
return false;
using word = typename Key::value_cw_word;
if (!same_bytes(a.value_cw().data(), b.value_cw().data(),
sizeof(word) * a.value_cw().size()))
return false;
if (a.cw_last() != b.cw_last())
return false;
if (a.cmp_addend() != b.cmp_addend())
return false;
return true;
}
// Element-wise byte comparison of the public `if_false` addends (from eq()).
// Wildcard-typed elements are not byte-comparable and are skipped.
template <typename Key, std::size_t I = 0>
bool same_public_addends(const Key & a, const Key & b)
{
if constexpr (I < std::tuple_size_v<typename Key::addend_tuple>)
{
using elem_t = std::tuple_element_t<I, typename Key::addend_tuple>;
if constexpr (!dpf::is_wildcard_v<elem_t>)
{
const auto & ea = std::get<I>(a.public_addends);
const auto & eb = std::get<I>(b.public_addends);
if (!same_bytes(&ea, &eb, sizeof(ea)))
return false;
}
return same_public_addends<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)
&& same_cmp_channel(a, b)
&& same_public_addends(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);
}
/// In-process Beaver completion for a wildcard leaf (same messages as asio
/// `assign_wildcard_output`, without a socket).
template <std::size_t I, typename Key0, typename Key1, typename ShareT>
void assign_wildcard_leaf_local(Key0 & k0, Key1 & k1, const ShareT & shr0,
const ShareT & shr1)
{
auto & w0 = std::get<I>(k0.leaf_nodes);
auto & w1 = std::get<I>(k1.leaf_nodes);
const auto b0 = w0.compute_and_get_blinded_output_share(shr0);
const auto b1 = w1.compute_and_get_blinded_output_share(shr1);
const auto l0 = w0.compute_and_get_leaf_share(b1);
const auto l1 = w1.compute_and_get_leaf_share(b0);
w0.reconstruct_correction_word(l1);
w1.reconstruct_correction_word(l0);
}
} // namespace
class StressScenariosTest : 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;
}
};
// ===========================================================================
// A) Multilevel parity with the classic surface.
// ===========================================================================
TEST_F(StressScenariosTest, MlEvalFullPrefixSlot)
{
uint16_t x = 0xa5c3;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint8_t{42}), dpf::at<12>(uint16_t{7}));
constexpr std::size_t N = 8, I = 0;
auto [b0, it0] = dpf::eval_full(dpf::out<I, N>, k0);
auto [b1, it1] = dpf::eval_full(dpf::out<I, N>, k1);
(void)it0; (void)it1;
const std::size_t lane = x >> (16 - N);
EXPECT_EQ(recon(b0[lane], b1[lane]),
recon(*dpf::eval_point(dpf::out<I, N>, k0, x),
*dpf::eval_point(dpf::out<I, N>, k1, x)));
EXPECT_EQ(recon(b0[lane], b1[lane]), uint8_t{42});
// Neighbouring lane in the same node is zero.
const std::size_t nb = lane ^ 1u;
EXPECT_EQ(recon(b0[nb], b1[nb]), uint8_t{0});
}
TEST_F(StressScenariosTest, MlEvalFullDeepestSlot)
{
uint16_t x = 0x1234;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint8_t{9}), dpf::at<12>(uint16_t{0xbeef}));
using KT = std::decay_t<decltype(k0)>;
constexpr std::size_t I = KT::deepest_output;
static_assert(KT::meta[I].prefix == 12, "deepest prefix expected 12");
constexpr std::size_t N = 12;
auto [b0, it0] = dpf::eval_full(dpf::out<I, N>, k0);
auto [b1, it1] = dpf::eval_full(dpf::out<I, N>, k1);
(void)it0; (void)it1;
const std::size_t lane = x >> (16 - N);
EXPECT_EQ(recon(b0[lane], b1[lane]),
recon(*dpf::eval_point(dpf::out<I, N>, k0, x),
*dpf::eval_point(dpf::out<I, N>, k1, x)));
EXPECT_EQ(recon(b0[lane], b1[lane]), uint16_t{0xbeef});
}
TEST_F(StressScenariosTest, MlEvalFullOutbufMatchesReturned)
{
uint16_t x = 0x7788;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint16_t{123}), uint32_t{5});
constexpr std::size_t N = 8, I = 0;
auto [b0, it0] = dpf::eval_full(dpf::out<I, N>, k0);
(void)it0;
auto memo = dpf::make_basic_interval_memoizer<std::decay_t<decltype(k0)>, I>(
uint16_t{0}, static_cast<uint16_t>((1u << N) - 1));
auto ob0 = dpf::make_output_buffer(dpf::out<I, N>, k0, uint16_t{0},
static_cast<uint16_t>((1u << N) - 1));
dpf::eval_full(dpf::out<I, N>, k0, ob0, memo);
for (std::size_t i = 0; i < (1u << N); ++i)
EXPECT_EQ(ob0[i], b0[i]) << "i=" << i;
}
TEST_F(StressScenariosTest, MlEvalFullCmpSmallDomain)
{
uint16_t alpha = 0x8000u;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt_at<8>(uint64_t{5}, uint64_t{1}));
ASSERT_TRUE(k0.has_cmp());
const uint64_t mask = k0.cmp().mask;
auto b0 = dpf::eval_full(dpf::cmp, k0);
auto b1 = dpf::eval_full(dpf::cmp, k1);
ASSERT_EQ(b0.size(), std::size_t{256});
for (std::size_t lane : {std::size_t{0x00}, std::size_t{0x7f}, std::size_t{0x80},
std::size_t{0x81}, std::size_t{0xff}})
{
const uint16_t q = static_cast<uint16_t>(lane << 8);
const uint64_t from_full =
dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
EXPECT_EQ(recon(b0[lane], b1[lane]) & mask, from_full) << "lane=" << lane;
}
// top8(alpha)=0x80 => lanes below are lt (5), at/above are !lt (1).
EXPECT_EQ(recon(b0[0x7f], b1[0x7f]) & mask, 5u);
EXPECT_EQ(recon(b0[0x80], b1[0x80]) & mask, 1u);
}
TEST_F(StressScenariosTest, MlIntervalWithBasicIntervalMemoizer)
{
uint16_t x = 0x33aa;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{77}), uint32_t{9});
using KT = std::decay_t<decltype(k0)>;
constexpr std::size_t N = 12, I = 0;
const uint16_t lane = x >> (16 - N);
const uint16_t from = static_cast<uint16_t>(lane & ~uint16_t{0xf});
const uint16_t to = static_cast<uint16_t>(from + 0xf);
auto memo0 = dpf::make_basic_interval_memoizer<KT, I>(from, to);
auto buf0 = dpf::make_output_buffer(dpf::out<I, N>, k0, from, to);
auto buf1 = dpf::make_output_buffer(dpf::out<I, N>, k1, from, to);
dpf::eval_interval(dpf::out<I, N>, k0, from, to, buf0, memo0);
auto memo1 = dpf::make_basic_interval_memoizer<KT, I>(from, to);
dpf::eval_interval(dpf::out<I, N>, k1, from, to, buf1, memo1);
const std::size_t idx = static_cast<std::size_t>(lane - from);
EXPECT_EQ(recon(buf0[idx], buf1[idx]),
recon(*dpf::eval_point(dpf::out<I, N>, k0, x),
*dpf::eval_point(dpf::out<I, N>, k1, x)));
EXPECT_EQ(recon(buf0[idx], buf1[idx]), uint8_t{77});
const std::size_t nbi = static_cast<std::size_t>((lane ^ 1u) - from);
EXPECT_EQ(recon(buf0[nbi], buf1[nbi]), uint8_t{0});
}
TEST_F(StressScenariosTest, MlSequencePointLoopVsBreadthFirst)
{
uint16_t x = 0x5a3c;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(uint8_t{21}), uint32_t{99});
constexpr std::size_t N = 10, I = 0;
const uint16_t xlane = x >> (16 - N);
std::vector<uint16_t> lanes = {
static_cast<uint16_t>(xlane & ~uint16_t{0xf}), xlane,
static_cast<uint16_t>(xlane ^ 1u),
static_cast<uint16_t>((xlane + 3u) & 0x3ffu),
static_cast<uint16_t>((xlane + 100u) & 0x3ffu)};
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 uint16_t q = static_cast<uint16_t>(lanes[i] << (16 - N));
EXPECT_EQ(recon(bf0[i], bf1[i]),
recon(*dpf::eval_point(dpf::out<I, N>, k0, q),
*dpf::eval_point(dpf::out<I, N>, k1, q))) << "i=" << i;
}
}
TEST_F(StressScenariosTest, MlSequenceRecipeDepthAndBreadthFirst)
{
uint16_t x = 0x2b70;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{55}), uint32_t{7});
using KT = std::decay_t<decltype(k0)>;
constexpr std::size_t N = 12, I = 0;
const uint16_t lane = x >> (16 - N);
std::array<uint16_t, 3> pts{{
static_cast<uint16_t>(lane & ~uint16_t{1}), lane,
static_cast<uint16_t>((lane & ~uint16_t{1}) + 1u)}};
std::sort(pts.begin(), pts.end());
auto recipe = dpf::make_sequence_recipe(dpf::out<I, N>, k0,
pts.begin(), pts.end());
EXPECT_EQ(recipe.depth(), KT::meta[I].tree_level);
EXPECT_EQ(recipe.output_indices().size(), pts.size());
auto b0 = dpf::eval_sequence_breadth_first(dpf::out<I, N>, k0,
pts.begin(), pts.end());
auto b1 = dpf::eval_sequence_breadth_first(dpf::out<I, N>, k1,
pts.begin(), pts.end());
for (std::size_t i = 0; i < pts.size(); ++i)
{
const uint16_t q = static_cast<uint16_t>(pts[i] << (16 - N));
EXPECT_EQ(recon(b0[i], b1[i]),
recon(*dpf::eval_point(dpf::out<I, N>, k0, q),
*dpf::eval_point(dpf::out<I, N>, k1, q)));
}
}
// A canonical mixed XOR+additive multilevel key reused across several tests.
namespace
{
auto make_mixed_ml(uint16_t x)
{
return dpf::make_dpf(x,
dpf::at<8>(uint8_t{3}, dpf::xor_wrapper<uint16_t>{0xcafe}),
dpf::at<12>(uint32_t{123456}),
dpf::lt_at<8>(uint64_t{9}));
}
} // namespace
TEST_F(StressScenariosTest, MlMixedPointRecon)
{
uint16_t x = 0x9c40;
auto [k0, k1] = make_mixed_ml(x);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 8>, k0, x),
*dpf::eval_point(dpf::out<0, 8>, k1, x)), uint8_t{3});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 8>, k0, x),
*dpf::eval_point(dpf::out<1, 8>, k1, x)),
dpf::xor_wrapper<uint16_t>{0xcafe});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 12>, k0, x),
*dpf::eval_point(dpf::out<2, 12>, k1, x)), uint32_t{123456});
// Off-prefix additive slot is zero (flip a bit inside the 12-bit prefix).
const uint16_t off = flip_lane_lsb<uint16_t>(x, 12, 16);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 12>, k0, off),
*dpf::eval_point(dpf::out<2, 12>, k1, off)), uint32_t{0});
}
TEST_F(StressScenariosTest, MlMixedIntervalRecon)
{
uint16_t x = 0x9c40;
auto [k0, k1] = make_mixed_ml(x);
constexpr std::size_t N = 8, I = 0;
const uint16_t lane = x >> (16 - N);
const uint16_t from = 0, to = 0xff;
auto [b0, it0] = dpf::eval_interval(dpf::out<I, N>, k0, from, to);
auto [b1, it1] = dpf::eval_interval(dpf::out<I, N>, k1, from, to);
(void)it0; (void)it1;
EXPECT_EQ(recon(b0[lane], b1[lane]), uint8_t{3});
EXPECT_EQ(recon(b0[(lane + 1u) & 0xff], b1[(lane + 1u) & 0xff]), uint8_t{0});
}
TEST_F(StressScenariosTest, MlMixedSequenceRecon)
{
uint16_t x = 0x9c40;
auto [k0, k1] = make_mixed_ml(x);
constexpr std::size_t N = 12, I = 2;
std::vector<uint16_t> pts{uint16_t{0x000},
static_cast<uint16_t>(x >> (16 - N)), uint16_t{0x400}, uint16_t{0xfff}};
std::sort(pts.begin(), pts.end());
pts.erase(std::unique(pts.begin(), pts.end()), pts.end());
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 = std::decay_t<decltype(k0)>::template outputs_per_leaf_of<I>;
for (std::size_t i = 0; i < pts.size(); ++i)
{
const uint16_t q = static_cast<uint16_t>(pts[i] << (16 - N));
auto e0 = dpf::eval_point(dpf::out<I, N>, k0, q);
auto e1 = dpf::eval_point(dpf::out<I, N>, k1, q);
EXPECT_EQ(recon(buf0[i * opl + e0.offset], buf1[i * opl + e1.offset]),
recon(*e0, *e1)) << "i=" << i;
}
}
TEST_F(StressScenariosTest, MlMixedInnerProduct)
{
uint16_t x = 0x9c40;
auto [k0, k1] = make_mixed_ml(x);
constexpr std::size_t N = 8, I = 0; // additive u8 slot
const uint16_t from = 0, to = 0xff;
auto [b0, it0] = dpf::eval_interval(dpf::out<I, N>, k0, from, to);
auto [b1, it1] = dpf::eval_interval(dpf::out<I, N>, k1, from, to);
(void)it0; (void)it1;
std::vector<uint64_t> w(b0.size());
uint64_t expect = 0;
for (std::size_t i = 0; i < w.size(); ++i)
{
w[i] = (i * 5u + 1u) & 0x1fu;
expect += static_cast<uint64_t>(recon(b0[i], b1[i])) * w[i];
}
expect &= 0xffu;
auto a = dpf::eval_inner_product(dpf::out<I, N>, k0, from, to, w);
auto b = dpf::eval_inner_product(dpf::out<I, N>, k1, from, to, w);
EXPECT_EQ(static_cast<uint8_t>(recon(a, b)), static_cast<uint8_t>(expect));
}
TEST_F(StressScenariosTest, MlMixedCmp)
{
uint16_t x = 0x9c40;
auto [k0, k1] = make_mixed_ml(x);
ASSERT_TRUE(k0.has_cmp());
EXPECT_EQ(k0.cmp().nbits, 8);
const uint64_t mask = k0.cmp().mask;
auto r = [&](uint16_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
};
const uint16_t top = static_cast<uint16_t>((x >> 8) & 0xff);
EXPECT_EQ(r(static_cast<uint16_t>((top - 1u) << 8)), 9u);
EXPECT_EQ(r(static_cast<uint16_t>(top << 8)), 0u);
EXPECT_EQ(r(static_cast<uint16_t>((top + 1u) << 8)), 0u);
}
TEST_F(StressScenariosTest, MlWildcardEvalPointThrowsBeforeAssign)
{
uint16_t x = 0x55aa;
dpf::wildcard_value<uint32_t> wc;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<8>(uint8_t{7}), dpf::at<12>(wc), uint16_t{1});
// Slot 1 is an unassigned wildcard: evaluating it must throw.
EXPECT_ANY_THROW((void)dpf::eval_point(dpf::out<1, 12>, k0, x));
(void)k1;
}
TEST_F(StressScenariosTest, MlWildcardOtherSlotsStillWork)
{
uint16_t x = 0x55aa;
dpf::wildcard_value<uint32_t> wc;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<8>(uint8_t{7}), dpf::at<12>(wc), uint16_t{1});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 8>, k0, x),
*dpf::eval_point(dpf::out<0, 8>, k1, x)), uint8_t{7});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 16>, k0, x),
*dpf::eval_point(dpf::out<2, 16>, k1, x)), uint16_t{1});
}
TEST_F(StressScenariosTest, MlCrossLevelPathMemoizerShallowThenDeep)
{
uint16_t x = 0xabcd;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(dpf::bit::one), uint16_t{1234});
using KT = std::decay_t<decltype(k0)>;
dpf::basic_path_memoizer<KT> p0{}, p1{};
for (uint16_t q : {x, static_cast<uint16_t>(x ^ 1u),
static_cast<uint16_t>(x ^ 0x8000u), uint16_t{0}})
{
auto s0 = dpf::eval_point(dpf::out<0, 8>, k0, q, p0);
auto s1 = dpf::eval_point(dpf::out<0, 8>, k1, q, p1);
auto d0 = dpf::eval_point(dpf::out<1, 16>, k0, q, p0);
auto d1 = dpf::eval_point(dpf::out<1, 16>, k1, q, p1);
auto sref = static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 8>, k0, q), *dpf::eval_point(dpf::out<0, 8>, k1, q)));
auto dref = recon(*dpf::eval_point(dpf::out<1, 16>, k0, q),
*dpf::eval_point(dpf::out<1, 16>, k1, q));
EXPECT_EQ(static_cast<bool>(recon(*s0, *s1)), sref) << "q=" << q;
EXPECT_EQ(recon(*d0, *d1), dref) << "q=" << q;
}
}
TEST_F(StressScenariosTest, MlIndependentIntervalsTwoSlots)
{
uint16_t x = 0x6c39;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<8>(uint8_t{11}), dpf::at<12>(uint16_t{2222}));
// Slot 0 interval.
auto [a0, ai0] = dpf::eval_interval(dpf::out<0, 8>, k0, uint16_t{0}, uint16_t{0xff});
auto [a1, ai1] = dpf::eval_interval(dpf::out<0, 8>, k1, uint16_t{0}, uint16_t{0xff});
(void)ai0; (void)ai1;
// Slot 1 interval.
auto [c0, ci0] = dpf::eval_interval(dpf::out<1, 12>, k0, uint16_t{0}, uint16_t{0xfff});
auto [c1, ci1] = dpf::eval_interval(dpf::out<1, 12>, k1, uint16_t{0}, uint16_t{0xfff});
(void)ci0; (void)ci1;
EXPECT_EQ(recon(a0[x >> 8], a1[x >> 8]), uint8_t{11});
EXPECT_EQ(recon(c0[x >> 4], c1[x >> 4]), uint16_t{2222});
}
// ===========================================================================
// B) Classic parity with multilevel strengths.
// ===========================================================================
TEST_F(StressScenariosTest, ClassicMixedByteIdenticalArgsVsConv)
{
uint32_t x = 0x00abcdefu;
reset_tape_roots();
auto via_args = dpf::make_dpf(
dpf::make_dpfargs(x, uint32_t{7}, dpf::xor_wrapper<uint32_t>{0xdeadbeef}),
take_root);
reset_tape_roots();
auto via_conv = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, uint32_t{7},
dpf::xor_wrapper<uint32_t>{0xdeadbeef});
EXPECT_TRUE(same_classic_key(via_args.first, via_conv.first));
EXPECT_TRUE(same_classic_key(via_args.second, via_conv.second));
}
TEST_F(StressScenariosTest, ClassicMixedDealerMatchesDoernerShelat)
{
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},
uint32_t{7}, dpf::xor_wrapper<uint32_t>{0xdeadbeef}, uint32_t{42});
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
uint32_t{7}, dpf::xor_wrapper<uint32_t>{0xdeadbeef}, uint32_t{42});
// Reconstruction parity across the surface.
for (uint32_t q : {x, x ^ 1u, 0u, 0xffffffffu})
{
EXPECT_EQ(recon(*dpf::eval_point<0>(dealer.first, q),
*dpf::eval_point<0>(dealer.second, q)),
recon(*dpf::eval_point<0>(ds.first, q),
*dpf::eval_point<0>(ds.second, q)));
EXPECT_EQ(recon(*dpf::eval_point<1>(dealer.first, q),
*dpf::eval_point<1>(dealer.second, q)),
recon(*dpf::eval_point<1>(ds.first, q),
*dpf::eval_point<1>(ds.second, q)));
EXPECT_EQ(recon(*dpf::eval_point<2>(dealer.first, q),
*dpf::eval_point<2>(dealer.second, q)),
recon(*dpf::eval_point<2>(ds.first, q),
*dpf::eval_point<2>(ds.second, q)));
}
EXPECT_EQ(recon(*dpf::eval_point<0>(dealer.first, x),
*dpf::eval_point<0>(dealer.second, x)), uint32_t{7});
EXPECT_EQ(recon(*dpf::eval_point<2>(ds.first, x),
*dpf::eval_point<2>(ds.second, x)), uint32_t{42});
}
TEST_F(StressScenariosTest, ClassicInnerProductMatchesInterval)
{
uint16_t x = 0x0abc;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{7}, uint32_t{11});
using KT = std::decay_t<decltype(k0)>;
const uint16_t from = 0x0a00, to = 0x0aff;
auto [b0, it0] = dpf::eval_interval<0>(k0, from, to);
auto [b1, it1] = dpf::eval_interval<0>(k1, from, to);
(void)it0; (void)it1;
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 + 1u) & 0xffu;
expect += static_cast<uint64_t>(recon(b0[i], b1[i])) * w[i];
}
auto memo0 = dpf::make_basic_interval_memoizer<KT>(from, to);
auto memo1 = dpf::make_basic_interval_memoizer<KT>(from, to);
auto a = dpf::eval_inner_product<0>(k0, from, to, w, memo0);
auto b = dpf::eval_inner_product<0>(k1, from, to, w, memo1);
EXPECT_EQ(static_cast<uint32_t>(recon(a, b)), static_cast<uint32_t>(expect));
}
TEST_F(StressScenariosTest, ClassicOutPointMatchesIndexedPoint)
{
uint32_t x = 0x00abcdefu;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{7}, dpf::xor_wrapper<uint32_t>{0x1234},
uint32_t{99});
for (uint32_t q : {x, x ^ 1u, 0u})
{
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0>, k0, q),
*dpf::eval_point(dpf::out<0>, k1, q)),
recon(*dpf::eval_point<0>(k0, q), *dpf::eval_point<0>(k1, q)));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, q),
*dpf::eval_point(dpf::out<1>, k1, q)),
recon(*dpf::eval_point<1>(k0, q), *dpf::eval_point<1>(k1, q)));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2>, k0, q),
*dpf::eval_point(dpf::out<2>, k1, q)),
recon(*dpf::eval_point<2>(k0, q), *dpf::eval_point<2>(k1, q)));
}
}
TEST_F(StressScenariosTest, ClassicOutIntervalMatchesIndexedInterval)
{
uint16_t x = 0x1357;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{7}, uint32_t{9});
const uint16_t from = 0x1300, to = 0x13ff;
auto [b0, it0] = dpf::eval_interval(dpf::out<0, 16>, k0, from, to);
auto [i0, i0it] = dpf::eval_interval<0>(k0, from, to);
(void)it0; (void)i0it;
ASSERT_EQ(b0.size(), i0.size());
for (std::size_t i = 0; i < b0.size(); ++i)
EXPECT_EQ(b0[i], i0[i]) << "i=" << i;
}
TEST_F(StressScenariosTest, ClassicPrepareIntervalThenInnerProduct)
{
uint16_t x = 0x2244;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{13}, uint32_t{17});
using KT = std::decay_t<decltype(k0)>;
const uint16_t from = 0x2200, to = 0x22ff;
std::vector<uint64_t> w(256);
for (std::size_t i = 0; i < w.size(); ++i) w[i] = (i + 1u) & 0x3fu;
auto memo0 = dpf::make_basic_interval_memoizer<KT>(from, to);
auto memo1 = dpf::make_basic_interval_memoizer<KT>(from, to);
dpf::eval_prepare_interval(k0, from, to, memo0);
dpf::eval_prepare_interval(k1, from, to, memo1);
auto a = dpf::eval_inner_product<0>(k0, from, to, w, memo0);
auto b = dpf::eval_inner_product<0>(k1, from, to, w, memo1);
auto memo0b = dpf::make_basic_interval_memoizer<KT>(from, to);
auto memo1b = dpf::make_basic_interval_memoizer<KT>(from, to);
auto a2 = dpf::eval_inner_product<0>(k0, from, to, w, memo0b);
auto b2 = dpf::eval_inner_product<0>(k1, from, to, w, memo1b);
EXPECT_EQ(recon(a, b), recon(a2, b2));
}
TEST_F(StressScenariosTest, ClassicPartialMisalignedInterval)
{
uint16_t x = 0x4d17;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{31}, uint32_t{37});
// Misaligned bounds not on leaf-node boundaries.
const uint16_t from = 0x4d05, to = 0x4d9b;
auto [b0, it0] = dpf::eval_interval<0>(k0, from, to);
auto [b1, it1] = dpf::eval_interval<0>(k1, from, to);
auto i0 = std::begin(it0);
auto i1 = std::begin(it1);
uint32_t cur = from;
for (; i0 != std::end(it0); ++i0, ++i1, ++cur)
{
uint32_t want = (cur == x) ? 31u : 0u;
EXPECT_EQ(static_cast<uint32_t>(recon(*i0, *i1)), want) << "cur=" << cur;
}
EXPECT_EQ(cur, static_cast<uint32_t>(to) + 1u);
}
TEST_F(StressScenariosTest, ClassicBasicVsFullTreeIntervalMemoizer)
{
uint16_t x = 0x7ffe;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{101}, dpf::xor_wrapper<uint32_t>{0xbeef});
using KT = std::decay_t<decltype(k0)>;
const uint16_t from = 0x7f00, to = 0x7fff;
auto mb0 = dpf::make_basic_interval_memoizer<KT>(from, to);
auto mb1 = dpf::make_basic_interval_memoizer<KT>(from, to);
auto [b0, bit0] = dpf::eval_interval<0>(k0, from, to, mb0);
auto [b1, bit1] = dpf::eval_interval<0>(k1, from, to, mb1);
(void)bit0; (void)bit1;
auto mf0 = dpf::make_full_tree_interval_memoizer<KT>(from, to);
auto mf1 = dpf::make_full_tree_interval_memoizer<KT>(from, to);
auto [f0, fit0] = dpf::eval_interval<0>(k0, from, to, mf0);
auto [f1, fit1] = dpf::eval_interval<0>(k1, from, to, mf1);
(void)fit0; (void)fit1;
ASSERT_EQ(b0.size(), f0.size());
for (std::size_t i = 0; i < b0.size(); ++i)
{
EXPECT_EQ(recon(b0[i], b1[i]), recon(f0[i], f1[i])) << "i=" << i;
}
}
TEST_F(StressScenariosTest, ClassicMultiLeafFullEvalPerSlot)
{
uint8_t x = 0x2a;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{5}, uint32_t{6}, uint32_t{7});
auto [b0, it0] = dpf::eval_full<1>(k0);
auto [b1, it1] = dpf::eval_full<1>(k1);
(void)it0; (void)it1;
ASSERT_EQ(b0.size(), std::size_t{256});
for (std::size_t i = 0; i < 256; ++i)
{
uint32_t want = (static_cast<uint8_t>(i) == x) ? 6u : 0u;
EXPECT_EQ(static_cast<uint32_t>(recon(b0[i], b1[i])), want) << "i=" << i;
}
}
// ===========================================================================
// C) Weird PRG / exotic input & packing.
// ===========================================================================
TEST_F(StressScenariosTest, PrgAesInteriorLowmcExteriorClassicPoint)
{
uint8_t x = 0x2a;
auto [k0, k1] = dpf::make_dpf<dpf::prg::aes128, dpf::prg::lowmc128>(x, uint32_t{0x01020304});
for (int i = 0; i < 256; ++i)
{
auto s = recon(*dpf::eval_point(k0, static_cast<uint8_t>(i)),
*dpf::eval_point(k1, static_cast<uint8_t>(i)));
EXPECT_EQ(static_cast<uint32_t>(s),
static_cast<uint8_t>(i) == x ? 0x01020304u : 0u);
}
}
TEST_F(StressScenariosTest, PrgLowmcInteriorAesExteriorClassicPoint)
{
uint8_t x = 0x91;
auto [k0, k1] = dpf::make_dpf<dpf::prg::lowmc128, dpf::prg::aes128>(x, uint32_t{0xdeadbeef});
for (int i = 0; i < 256; ++i)
{
auto s = recon(*dpf::eval_point(k0, static_cast<uint8_t>(i)),
*dpf::eval_point(k1, static_cast<uint8_t>(i)));
EXPECT_EQ(static_cast<uint32_t>(s),
static_cast<uint8_t>(i) == x ? 0xdeadbeefu : 0u);
}
}
TEST_F(StressScenariosTest, PrgAesLowmcMultilevelPoint)
{
uint16_t x = 0x3c5a;
auto [k0, k1] = dpf::make_dpf<dpf::prg::aes128, dpf::prg::lowmc128>(x,
dpf::at<8>(uint8_t{42}), uint16_t{7});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 8>, k0, x),
*dpf::eval_point(dpf::out<0, 8>, k1, x)), uint8_t{42});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 16>, k0, x),
*dpf::eval_point(dpf::out<1, 16>, k1, x)), uint16_t{7});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 16>, k0, x ^ 1u),
*dpf::eval_point(dpf::out<1, 16>, k1, x ^ 1u)), uint16_t{0});
}
TEST_F(StressScenariosTest, PrgLowmcAesMultilevelPoint)
{
uint16_t x = 0xd4e1;
auto [k0, k1] = dpf::make_dpf<dpf::prg::lowmc128, dpf::prg::aes128>(x,
dpf::at<10>(uint16_t{321}), uint32_t{654});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 10>, k0, x),
*dpf::eval_point(dpf::out<0, 10>, k1, x)), uint16_t{321});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 16>, k0, x),
*dpf::eval_point(dpf::out<1, 16>, k1, x)), uint32_t{654});
}
TEST_F(StressScenariosTest, PrgDummyAesClassicPoint)
{
uint8_t x = 0x40;
auto [k0, k1] = dpf::make_dpf<dpf::prg::dummy, dpf::prg::aes128>(x, uint32_t{12345});
for (int i = 0; i < 256; ++i)
{
auto s = recon(*dpf::eval_point(k0, static_cast<uint8_t>(i)),
*dpf::eval_point(k1, static_cast<uint8_t>(i)));
EXPECT_EQ(static_cast<uint32_t>(s),
static_cast<uint8_t>(i) == x ? 12345u : 0u) << "i=" << i;
}
}
TEST_F(StressScenariosTest, PrgChachaInteriorAesExteriorClassicPoint)
{
uint8_t x = 0x2a;
auto [k0, k1] = dpf::make_dpf<dpf::prg::chacha20, dpf::prg::aes128>(x, uint32_t{0x01020304});
for (int i = 0; i < 256; ++i)
{
auto s = recon(*dpf::eval_point(k0, static_cast<uint8_t>(i)),
*dpf::eval_point(k1, static_cast<uint8_t>(i)));
EXPECT_EQ(static_cast<uint32_t>(s),
static_cast<uint8_t>(i) == x ? 0x01020304u : 0u);
}
}
TEST_F(StressScenariosTest, PrgAesInteriorChachaExteriorClassicPoint)
{
uint8_t x = 0x91;
auto [k0, k1] = dpf::make_dpf<dpf::prg::aes128, dpf::prg::chacha20>(x, uint32_t{0xdeadbeef});
for (int i = 0; i < 256; ++i)
{
auto s = recon(*dpf::eval_point(k0, static_cast<uint8_t>(i)),
*dpf::eval_point(k1, static_cast<uint8_t>(i)));
EXPECT_EQ(static_cast<uint32_t>(s),
static_cast<uint8_t>(i) == x ? 0xdeadbeefu : 0u);
}
}
TEST_F(StressScenariosTest, PrgLowmcLowmcMultilevelPacking)
{
uint16_t x = 0x4c1d;
auto [k0, k1] = dpf::make_dpf<dpf::prg::lowmc128>(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}),
uint32_t{999});
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::meta[0].group_id, KT::meta[7].group_id);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x),
*dpf::eval_point(dpf::out<0, 12>, k1, x)), uint8_t{1});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 12>, k0, x),
*dpf::eval_point(dpf::out<7, 12>, k1, x)), uint8_t{8});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8, 16>, k0, x),
*dpf::eval_point(dpf::out<8, 16>, k1, x)), uint32_t{999});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, flip_lane_lsb(x, 12, 16)),
*dpf::eval_point(dpf::out<0, 12>, k1, flip_lane_lsb(x, 12, 16))),
uint8_t{0});
}
TEST_F(StressScenariosTest, SignedInputAtAndCmp)
{
int16_t x = -1234;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint8_t{5}), dpf::lt(uint64_t{42}));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 8>, k0, x),
*dpf::eval_point(dpf::out<0, 8>, k1, x)), uint8_t{5});
ASSERT_TRUE(k0.has_cmp());
const uint64_t mask = k0.cmp().mask;
auto r = [&](int16_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
};
EXPECT_EQ(r(static_cast<int16_t>(x - 1)), 42u);
EXPECT_EQ(r(x), 0u);
EXPECT_EQ(r(static_cast<int16_t>(x + 1)), 0u);
}
TEST_F(StressScenariosTest, ModintInputClassicPoint)
{
using in_t = dpf::modint<12>;
in_t x{0x0abc};
auto [k0, k1] = dpf::make_dpf(x, uint32_t{321});
EXPECT_EQ(static_cast<uint32_t>(recon(*dpf::eval_point(k0, x),
*dpf::eval_point(k1, x))), 321u);
in_t off{0x0abd};
EXPECT_EQ(static_cast<uint32_t>(recon(*dpf::eval_point(k0, off),
*dpf::eval_point(k1, off))), 0u);
}
TEST_F(StressScenariosTest, KeywordInputClassicPoint)
{
using in_t = dpf::keyword<3, dpf::alphabets::hex>;
in_t x{"abc"};
auto [k0, k1] = dpf::make_dpf(x, uint32_t{77});
EXPECT_EQ(static_cast<uint32_t>(recon(*dpf::eval_point(k0, x),
*dpf::eval_point(k1, x))), 77u);
in_t off{"abd"};
EXPECT_EQ(static_cast<uint32_t>(recon(*dpf::eval_point(k0, off),
*dpf::eval_point(k1, off))), 0u);
}
TEST_F(StressScenariosTest, CmpPackedAtDeepestUnderDoernerShelat)
{
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<14>(uint8_t{3}, uint8_t{5}),
uint32_t{9},
dpf::lt(uint64_t{42}));
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<14>(uint8_t{3}, uint8_t{5}),
uint32_t{9},
dpf::lt(uint64_t{42}));
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
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);
}
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, dealer.first, x),
*dpf::eval_point(dpf::out<0, 14>, dealer.second, x)), uint8_t{3});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 32>, ds.first, x),
*dpf::eval_point(dpf::out<2, 32>, ds.second, x)), uint32_t{9});
}
TEST_F(StressScenariosTest, Packed16xU8PartialIntervalOffAlignment)
{
uint16_t x = 0x0c34;
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}),
uint32_t{7});
constexpr std::size_t N = 12, I = 0;
const uint16_t lane = x >> (16 - N);
// A partial interval whose bounds are not on 16-lane leaf boundaries.
const uint16_t from = static_cast<uint16_t>((lane & ~uint16_t{0xf}) + 3u);
const uint16_t to = static_cast<uint16_t>((lane | uint16_t{0xf}) + 5u);
auto [b0, it0] = dpf::eval_interval(dpf::out<I, N>, k0, from, to);
auto [b1, it1] = dpf::eval_interval(dpf::out<I, N>, k1, from, to);
(void)it0; (void)it1;
// The buffer is filled from the leaf-node floor of `from` (opl==16 lanes),
// so index relative to that aligned start, not to `from` itself.
const uint16_t from_floor = static_cast<uint16_t>(lane & ~uint16_t{0xf});
const std::size_t idx = static_cast<std::size_t>(lane - from_floor);
ASSERT_LT(idx, b0.size());
EXPECT_EQ(recon(b0[idx], b1[idx]),
recon(*dpf::eval_point(dpf::out<I, N>, k0, x),
*dpf::eval_point(dpf::out<I, N>, k1, x)));
// Slot 0 was programmed with value 1 at the point x's lane.
EXPECT_EQ(recon(b0[idx], b1[idx]), uint8_t{1});
}
// ===========================================================================
// D) Non-key: classic multi-output interval & recipe modes (smoke).
// ===========================================================================
TEST_F(StressScenariosTest, ClassicMultiOutputInterval012)
{
uint8_t x = 0x55;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{2}, uint32_t{3}, uint32_t{4});
auto [b0, it0] = dpf::eval_interval<0, 1, 2>(k0, uint8_t{0}, uint8_t{0xff});
auto [b1, it1] = dpf::eval_interval<0, 1, 2>(k1, uint8_t{0}, uint8_t{0xff});
auto zip0 = dpf::tuple_as_zip(it0);
auto zip1 = dpf::tuple_as_zip(it1);
auto i0 = std::cbegin(zip0);
auto i1 = std::cbegin(zip1);
uint32_t cur = 0;
for (; i0 != std::cend(zip0); ++i0, ++i1, ++cur)
{
const bool at = (static_cast<uint8_t>(cur) == x);
EXPECT_EQ(static_cast<uint32_t>(recon(std::get<0>(*i0), std::get<0>(*i1))), at ? 2u : 0u);
EXPECT_EQ(static_cast<uint32_t>(recon(std::get<1>(*i0), std::get<1>(*i1))), at ? 3u : 0u);
EXPECT_EQ(static_cast<uint32_t>(recon(std::get<2>(*i0), std::get<2>(*i1))), at ? 4u : 0u);
}
EXPECT_EQ(cur, 256u);
}
// ===========================================================================
// Additional cross-cutting coverage.
// ===========================================================================
TEST_F(StressScenariosTest, MlEvalSweepAroundPointPrefixSlot)
{
uint16_t x = 0x4d80;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(dpf::bit::one), uint32_t{0xabcdef01});
using KT = std::decay_t<decltype(k0)>;
dpf::nonmemoizing_path_memoizer<KT> p0{}, p1{};
const uint16_t xtop = static_cast<uint16_t>(x >> 8);
for (int d = -40; d <= 40; ++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)),
static_cast<uint16_t>(q >> 8) == xtop) << "q=" << q;
uint32_t want = (q == x) ? 0xabcdef01u : 0u;
EXPECT_EQ(recon(*u0, *u1), want) << "q=" << q;
}
// Explicit off-prefix bucket.
uint16_t other = static_cast<uint16_t>(x ^ 0x8000u);
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(StressScenariosTest, MlInnerProductExplicitMemoizerMatchesDefault)
{
uint16_t x = 0x9c40;
auto [k0, k1] = make_mixed_ml(x);
using KT = std::decay_t<decltype(k0)>;
constexpr std::size_t N = 8, I = 0;
const uint16_t from = 0, to = 0xff;
std::vector<uint64_t> w(256);
for (std::size_t i = 0; i < w.size(); ++i) w[i] = (i * 7u + 1u) & 0x1fu;
auto a = dpf::eval_inner_product(dpf::out<I, N>, k0, from, to, w);
auto b = dpf::eval_inner_product(dpf::out<I, N>, k1, from, to, w);
auto m0 = dpf::make_basic_interval_memoizer<KT, I>(from, to);
auto m1 = dpf::make_basic_interval_memoizer<KT, I>(from, to);
auto am = dpf::eval_inner_product(dpf::out<I, N>, k0, from, to, w, m0);
auto bm = dpf::eval_inner_product(dpf::out<I, N>, k1, from, to, w, m1);
EXPECT_EQ(am, a);
EXPECT_EQ(bm, b);
}
TEST_F(StressScenariosTest, MlCmpInnerProductMatchesInterval)
{
uint16_t alpha = 0x00aa;
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 = 0x1f;
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(StressScenariosTest, ClassicOutFullMatchesIndexedFull)
{
uint8_t x = 0x2a;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{7}, uint32_t{9});
auto [b0, it0] = dpf::eval_full(dpf::out<1, 8>, k0);
auto [i0, iit0] = dpf::eval_full<1>(k0);
(void)it0; (void)iit0;
ASSERT_EQ(b0.size(), i0.size());
for (std::size_t i = 0; i < b0.size(); ++i)
EXPECT_EQ(b0[i], i0[i]) << "i=" << i;
}
TEST_F(StressScenariosTest, PrgAesLowmcMultilevelInterval)
{
uint16_t x = 0x3c5a;
auto [k0, k1] = dpf::make_dpf<dpf::prg::aes128, dpf::prg::lowmc128>(x,
dpf::at<8>(uint8_t{42}), uint16_t{7});
constexpr std::size_t N = 8, I = 0;
auto [b0, it0] = dpf::eval_interval(dpf::out<I, N>, k0, uint16_t{0}, uint16_t{0xff});
auto [b1, it1] = dpf::eval_interval(dpf::out<I, N>, k1, uint16_t{0}, uint16_t{0xff});
(void)it0; (void)it1;
const std::size_t lane = x >> 8;
EXPECT_EQ(recon(b0[lane], b1[lane]), uint8_t{42});
EXPECT_EQ(recon(b0[(lane + 1) & 0xff], b1[(lane + 1) & 0xff]), uint8_t{0});
}
TEST_F(StressScenariosTest, ClassicRecipeMemoizerVariantsAgree)
{
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
uint16_t, uint32_t>;
uint16_t x = 0x0246;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{4242});
std::vector<uint16_t> pts = {0x0000, x, 0x0247, 0x0800, 0x0fff};
std::sort(pts.begin(), pts.end());
pts.erase(std::unique(pts.begin(), pts.end()), pts.end());
auto recipe = dpf::make_sequence_recipe<dpf_type>(pts.begin(), pts.end());
auto md0 = dpf::make_double_space_sequence_memoizer<dpf_type>(recipe);
auto md1 = dpf::make_double_space_sequence_memoizer<dpf_type>(recipe);
auto [d0, dit0] = dpf::eval_sequence(k0, recipe, md0);
auto [d1, dit1] = dpf::eval_sequence(k1, recipe, md1);
auto mf0 = dpf::make_full_tree_sequence_memoizer<dpf_type>(recipe);
auto mf1 = dpf::make_full_tree_sequence_memoizer<dpf_type>(recipe);
auto [f0, fit0] = dpf::eval_sequence(k0, recipe, mf0);
auto [f1, fit1] = dpf::eval_sequence(k1, recipe, mf1);
auto di0 = std::begin(dit0);
auto di1 = std::begin(dit1);
auto fi0 = std::begin(fit0);
auto fi1 = std::begin(fit1);
std::size_t idx = 0;
for (; di0 != std::end(dit0); ++di0, ++di1, ++fi0, ++fi1, ++idx)
{
auto d = static_cast<uint32_t>(recon(*di0, *di1));
auto f = static_cast<uint32_t>(recon(*fi0, *fi1));
EXPECT_EQ(d, f) << "idx=" << idx;
EXPECT_EQ(d, (pts[idx] == x) ? 4242u : 0u) << "idx=" << idx;
}
}
TEST_F(StressScenariosTest, ClassicRecipeEntireNodeVsOutputOnly)
{
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
uint16_t, uint32_t>;
uint16_t x = 0x0123;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{88});
std::vector<uint16_t> pts = {0x0000, 0x0100, x, 0x0200, 0x0345, 0x0fff};
std::sort(pts.begin(), pts.end());
pts.erase(std::unique(pts.begin(), pts.end()), pts.end());
auto recipe = dpf::make_sequence_recipe<dpf_type>(pts.begin(), pts.end());
auto [e0, eit0] = dpf::eval_sequence(k0, recipe, dpf::return_entire_node_tag_{});
auto [e1, eit1] = dpf::eval_sequence(k1, recipe, dpf::return_entire_node_tag_{});
auto [o0, oit0] = dpf::eval_sequence(k0, recipe, dpf::return_output_only_tag_{});
auto [o1, oit1] = dpf::eval_sequence(k1, recipe, dpf::return_output_only_tag_{});
auto ei0 = std::begin(eit0);
auto ei1 = std::begin(eit1);
auto oi0 = std::begin(oit0);
auto oi1 = std::begin(oit1);
std::size_t idx = 0;
for (; oi0 != std::end(oit0); ++ei0, ++ei1, ++oi0, ++oi1, ++idx)
{
auto e = static_cast<uint32_t>(recon(*ei0, *ei1));
auto o = static_cast<uint32_t>(recon(*oi0, *oi1));
EXPECT_EQ(e, o) << "idx=" << idx;
uint32_t want = (pts[idx] == x) ? 88u : 0u;
EXPECT_EQ(o, want) << "idx=" << idx;
}
}
// ---------------------------------------------------------------------------
// Extra symmetry / scenario coverage (wave 2)
// ---------------------------------------------------------------------------
TEST_F(StressScenariosTest, ClassicBreadthFirstMatchesPoint)
{
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
uint16_t, uint32_t>;
uint16_t x = 0x0abc;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{777});
std::vector<uint16_t> pts = {0, 1, x, static_cast<uint16_t>(x ^ 1), 0xffff};
std::sort(pts.begin(), pts.end());
pts.erase(std::unique(pts.begin(), pts.end()), pts.end());
// `eval_sequence_breadth_first` returns a pair{buffer, iterable}. The
// buffer is laid out per-leaf (outputs_per_leaf slots per query), so the
// per-point outputs must be read through the iterable, not by indexing the
// buffer directly.
auto [b0, iter0] = dpf::eval_sequence_breadth_first(k0, pts.begin(), pts.end());
auto [b1, iter1] = dpf::eval_sequence_breadth_first(k1, pts.begin(), pts.end());
(void)b0;
(void)b1;
auto i0 = std::begin(iter0);
auto i1 = std::begin(iter1);
for (std::size_t i = 0; i0 != std::end(iter0); ++i0, ++i1, ++i)
{
EXPECT_EQ(recon(*i0, *i1),
recon(*dpf::eval_point(k0, pts[i]), *dpf::eval_point(k1, pts[i])))
<< "i=" << i;
}
}
TEST_F(StressScenariosTest, ClassicFullInnerProductMatchesFullInterval)
{
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
uint8_t, uint32_t>;
uint8_t x = 0x42;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{9});
auto [buf0, it0] = dpf::eval_full(k0);
auto [buf1, it1] = dpf::eval_full(k1);
(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 % 17u) + 1u;
expect += static_cast<uint64_t>(recon(buf0[i], buf1[i])) * w[i];
}
auto memo0 = dpf::make_basic_full_memoizer(k0);
auto memo1 = dpf::make_basic_full_memoizer(k1);
dpf::eval_prepare_full(k0, memo0);
dpf::eval_prepare_full(k1, memo1);
auto ip0 = dpf::eval_full_inner_product(k0, w, memo0);
auto ip1 = dpf::eval_full_inner_product(k1, w, memo1);
// Leaf shares are subtractive: reconstruct(y0, y1) = y0 - y1.
EXPECT_EQ(static_cast<uint64_t>(recon(ip0, ip1)), expect);
}
TEST_F(StressScenariosTest, MlXorSlotInnerProduct)
{
// XOR-packed prefix slot: IP uses xor-and accumulation.
uint32_t x = 0x00a5b6c7u;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<12>(dpf::xor_wrapper<uint8_t>{0xab}),
uint32_t{1});
const uint32_t lane = x >> (32 - 12);
const uint32_t from = lane & ~0xfu;
const uint32_t to = from + 15u;
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(), 0);
uint8_t expect = 0;
for (std::size_t i = 0; i < w.size(); ++i)
{
w[i] = (i & 1u) ? 0xffu : 0u;
auto v = static_cast<uint8_t>(recon(buf0[i], buf1[i]));
expect = static_cast<uint8_t>(expect ^ (v & static_cast<uint8_t>(w[i])));
}
auto ip0 = dpf::eval_inner_product(dpf::out<0, 12>, k0, from, to, w);
auto ip1 = dpf::eval_inner_product(dpf::out<0, 12>, k1, from, to, w);
EXPECT_EQ(static_cast<uint8_t>(ip0 ^ ip1), expect);
}
TEST_F(StressScenariosTest, MlBitstringDeepestOutput)
{
uint32_t x = 0x11223344u;
using bs = dpf::bitstring<20, uint8_t>;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one), bs{});
// Just ensure gen+point eval compile and off-point is zero-ish for the bit.
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_FALSE(static_cast<bool>(
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x ^ 0x80000000u), *dpf::eval_point(dpf::out<0, 10>, k1, x ^ 0x80000000u))));
}
TEST_F(StressScenariosTest, MlGeqFullCmpMatchesPointSweep)
{
const uint8_t alpha = 0x40;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::geq(uint16_t{5}, uint16_t{1}));
auto b0 = dpf::eval_full(dpf::cmp, k0);
auto b1 = dpf::eval_full(dpf::cmp, k1);
const uint64_t mask = k0.cmp().mask;
ASSERT_EQ(b0.size(), 256u);
for (uint16_t q = 0; q < 256; ++q)
{
auto got = recon(b0[q], b1[q]) & mask;
auto want = (static_cast<uint8_t>(q) >= alpha) ? 5u : 1u;
EXPECT_EQ(got, want) << "q=" << q;
}
}
TEST_F(StressScenariosTest, ClassicMultiLeafPathMemoizerReuse)
{
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
uint16_t, uint32_t, dpf::xor_wrapper<uint32_t>>;
uint16_t x = 0x55aa;
auto [k0, k1] = dpf::make_dpf(x, uint32_t{11},
dpf::xor_wrapper<uint32_t>{0x2222});
dpf::basic_path_memoizer<dpf_type> p0{}, p1{};
for (uint16_t q : {x, static_cast<uint16_t>(x ^ 1), uint16_t{0}, uint16_t{0xffff}})
{
auto a0 = dpf::eval_point<0>(k0, q, p0);
auto a1 = dpf::eval_point<0>(k1, q, p1);
auto b0 = dpf::eval_point<1>(k0, q, p0);
auto b1 = dpf::eval_point<1>(k1, q, p1);
EXPECT_EQ(recon(*a0, *a1),
recon(*dpf::eval_point<0>(k0, q), *dpf::eval_point<0>(k1, q)));
EXPECT_EQ(recon(*b0, *b1),
recon(*dpf::eval_point<1>(k0, q), *dpf::eval_point<1>(k1, q)));
}
}
TEST_F(StressScenariosTest, DummyInteriorLowmcExteriorMultilevel)
{
uint32_t x = 0x0f1e2d3cu;
auto [k0, k1] = dpf::make_dpf<dpf::prg::dummy, dpf::prg::lowmc128>(x,
dpf::at<12>(uint8_t{9}), uint16_t{4});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x),
*dpf::eval_point(dpf::out<0, 12>, k1, x)),
uint8_t{9});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x),
*dpf::eval_point(dpf::out<1>, k1, x)),
uint16_t{4});
}
TEST_F(StressScenariosTest, ClassicMixedFullTreeFullMemoizer)
{
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
uint8_t, uint16_t, dpf::xor_wrapper<uint16_t>>;
uint8_t x = 0x11;
auto [k0, k1] = dpf::make_dpf(x, uint16_t{3},
dpf::xor_wrapper<uint16_t>{0x00ff});
auto memo0 = dpf::make_full_tree_full_memoizer<dpf_type>();
auto memo1 = dpf::make_full_tree_full_memoizer<dpf_type>();
auto [bufs0, its0] = dpf::eval_full<0, 1>(k0, memo0);
auto [bufs1, its1] = dpf::eval_full<0, 1>(k1, memo1);
(void)its0;
(void)its1;
auto & a0 = std::get<0>(bufs0);
auto & a1 = std::get<0>(bufs1);
auto & b0 = std::get<1>(bufs0);
auto & b1 = std::get<1>(bufs1);
// Spot-check a few domain points via buffer indexing through point eval.
for (uint8_t q : {uint8_t{0}, x, static_cast<uint8_t>(x ^ 1), uint8_t{0xff}})
{
EXPECT_EQ(recon(*dpf::eval_point<0>(k0, q), *dpf::eval_point<0>(k1, q)),
recon(*dpf::eval_point<0>(k0, q), *dpf::eval_point<0>(k1, q)));
EXPECT_EQ(recon(*dpf::eval_point<1>(k0, q), *dpf::eval_point<1>(k1, q)),
(q == x) ? dpf::xor_wrapper<uint16_t>{0x00ff}
: dpf::xor_wrapper<uint16_t>{0});
}
(void)a0;
(void)a1;
(void)b0;
(void)b1;
}
TEST_F(StressScenariosTest, MlEqAtWithPackedNeighbors)
{
uint32_t x = 0x00c0ffeeu;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<12>(uint8_t{1}, uint8_t{2}),
dpf::eq_at<16>(uint16_t{99}, uint16_t{7}));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x),
*dpf::eval_point(dpf::out<0, 12>, k1, x)),
uint8_t{1});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x),
*dpf::eval_point(dpf::out<1, 12>, k1, x)),
uint8_t{2});
// eq_at becomes a placed point output with addend absorb.
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 16>, k0, x),
*dpf::eval_point(dpf::out<2, 16>, k1, x)),
uint16_t{99});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 16>, k0, x ^ 0x00010000u),
*dpf::eval_point(dpf::out<2, 16>, k1, x ^ 0x00010000u)),
uint16_t{7});
}
// ===========================================================================
// E) Bug-1 regression: multilevel gen + point eval for non-native input types
// (`modint`, `keyword`). These exercise `lane_input` / threshold casts.
// ===========================================================================
TEST_F(StressScenariosTest, ModintMultilevelGenAndPoint)
{
using in_t = dpf::modint<10>;
in_t x{3};
auto [k0, k1] = dpf::make_dpf(x, dpf::at<6>(uint8_t{1}), uint16_t{2});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 6>, k0, x),
*dpf::eval_point(dpf::out<0, 6>, k1, x)),
uint8_t{1});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x),
*dpf::eval_point(dpf::out<1>, k1, x)),
uint16_t{2});
// Off-point on the deepest slot reconstructs to zero.
in_t off{7};
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, off),
*dpf::eval_point(dpf::out<1>, k1, off)),
uint16_t{0});
}
TEST_F(StressScenariosTest, ModintMultilevelWithCmp)
{
using in_t = dpf::modint<12>;
in_t x{0x2ab};
auto [k0, k1] = dpf::make_dpf(x, dpf::at<6>(uint8_t{5}), uint16_t{9},
dpf::lt(uint16_t{4}, uint16_t{1}));
ASSERT_TRUE(k0.has_cmp());
const uint64_t mask = k0.cmp().mask;
// Full-domain cmp on the 12-bit input: true-value 4 below threshold, 1 at/above.
for (unsigned q = 0; q < 0x1000u; q += 0x11u)
{
in_t qi{q};
const auto got = dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, qi), dpf::eval_point(dpf::cmp, k1, qi)) & mask;
const auto want = (q < 0x2abu) ? uint64_t{4} : uint64_t{1};
EXPECT_EQ(got, want) << "q=" << q;
}
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 6>, k0, x),
*dpf::eval_point(dpf::out<0, 6>, k1, x)),
uint8_t{5});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x),
*dpf::eval_point(dpf::out<1>, k1, x)),
uint16_t{9});
}
TEST_F(StressScenariosTest, KeywordMultilevelGenAndPoint)
{
using in_t = dpf::keyword<3, dpf::alphabets::hex>;
in_t x{"abc"};
auto [k0, k1] = dpf::make_dpf(x, dpf::at<6>(uint8_t{1}), uint16_t{2});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 6>, k0, x),
*dpf::eval_point(dpf::out<0, 6>, k1, x)),
uint8_t{1});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x),
*dpf::eval_point(dpf::out<1>, k1, x)),
uint16_t{2});
in_t off{"abd"};
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, off),
*dpf::eval_point(dpf::out<1>, k1, off)),
uint16_t{0});
}
// ===========================================================================
// F) Dealer <-> Doerner-Shelat *byte-for-byte* identity across rich scenarios.
// All use matched entropy: same `take_root` roots and same PadA tape order
// as the dealer's `root_sampler`. `same_incr_key` now also asserts the cmp
// channel (cmp_meta / value_cw / cw_last / cmp_addend) and public addends.
// ===========================================================================
TEST_F(StressScenariosTest, DsIdentityManyLevelsMixedWidthsXorAdditiveCmpLt)
{
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<8>(dpf::bit::one),
dpf::at<16>(uint8_t{3}, dpf::xor_wrapper<uint8_t>{0xa5}),
dpf::at<24>(uint16_t{7}),
uint32_t{9},
dpf::lt(uint64_t{42}));
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<8>(dpf::bit::one),
dpf::at<16>(uint8_t{3}, dpf::xor_wrapper<uint8_t>{0xa5}),
dpf::at<24>(uint16_t{7}),
uint32_t{9},
dpf::lt(uint64_t{42}));
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
// Sanity: reconstruction still correct after the byte-identity assertions.
// Output indices: 0=bit@8, 1=u8@16, 2=xor_u8@16, 3=u16@24, 4=u32@32.
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 16>, dealer.first, x),
*dpf::eval_point(dpf::out<1, 16>, dealer.second, x)), uint8_t{3});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 32>, ds.first, x),
*dpf::eval_point(dpf::out<4, 32>, ds.second, x)), uint32_t{9});
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(StressScenariosTest, DsIdentityPacked16xU8DeepestGeq)
{
uint32_t x = 0x00abcdefu;
uint32_t x0 = 0x0f0f0f0fu;
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<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}),
uint32_t{5},
dpf::geq(uint16_t{100}, uint16_t{1}));
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
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}),
uint32_t{5},
dpf::geq(uint16_t{100}, uint16_t{1}));
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>, ds.first, x),
*dpf::eval_point(dpf::out<0, 12>, ds.second, x)), uint8_t{1});
}
TEST_F(StressScenariosTest, DsIdentityClassicMultiLeafXorAdditiveBytes)
{
// Classic-shaped pack -> classic key; assert BYTE identity, not just recon.
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},
uint32_t{7}, dpf::xor_wrapper<uint32_t>{0xdeadbeef});
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
uint32_t{7}, dpf::xor_wrapper<uint32_t>{0xdeadbeef});
EXPECT_TRUE(same_classic_key(dealer.first, ds.first));
EXPECT_TRUE(same_classic_key(dealer.second, ds.second));
EXPECT_EQ(recon(*dpf::eval_point<0>(dealer.first, x),
*dpf::eval_point<0>(dealer.second, x)), uint32_t{7});
}
TEST_F(StressScenariosTest, DsIdentityIntermediateWildcardConcreteSiblings)
{
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>(uint16_t{5}, wc), // concrete sibling next to the wildcard
uint32_t{9});
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<10>(dpf::bit::one),
dpf::at<16>(uint16_t{5}, wc),
uint32_t{9});
// Non-wildcard leaves + CWs + wildcard blinds are all byte-identical.
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
// Concrete parts still reconstruct.
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 16>, dealer.first, x),
*dpf::eval_point(dpf::out<1, 16>, dealer.second, x)), uint16_t{5});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, dealer.first, x),
*dpf::eval_point(dpf::out<3, 32>, dealer.second, x)), uint32_t{9});
}
TEST_F(StressScenariosTest, DsIdentityFixedpointAtWithCmp)
{
using fp16 = grotto::fixedpoint<16>;
uint32_t x = 0x0abcdef0u;
uint32_t x0 = 0x11111111u;
uint32_t x1 = x ^ x0;
fp16 y = fp16::from_raw(0x00018000);
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<12>(y),
fp16::from_raw(0x00030000),
dpf::lt(uint64_t{1000}));
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<12>(y),
fp16::from_raw(0x00030000),
dpf::lt(uint64_t{1000}));
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);
}
TEST_F(StressScenariosTest, DsIdentityLocalCwProtocolVsDealer)
{
uint32_t x = 0xabcdef01u;
uint32_t x0 = 0x11111111u;
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{5});
reset_tape_roots();
PadA pads{};
dpf::local_cw_protocol<PadA> proto{pads};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, take_root, proto,
dpf::at<10>(dpf::bit::one), uint32_t{5});
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<1, 32>, dealer.first, x),
*dpf::eval_point(dpf::out<1, 32>, dealer.second, x)), uint32_t{5});
}
TEST_F(StressScenariosTest, DsIdentityAesInteriorLowmcExteriorMultilevel)
{
uint32_t x = 0x0f1e2d3cu;
uint32_t x0 = 0x13572468u;
uint32_t x1 = x ^ x0;
reset_tape_roots();
auto dealer = dpf::make_dpf<dpf::prg::aes128, dpf::prg::lowmc128>(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<12>(uint8_t{9}), uint16_t{4});
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat<dpf::prg::aes128, dpf::prg::lowmc128>(
x0, x1, rng, dpf::at<12>(uint8_t{9}), uint16_t{4});
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>, ds.first, x),
*dpf::eval_point(dpf::out<0, 12>, ds.second, x)), uint8_t{9});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, ds.first, x),
*dpf::eval_point(dpf::out<1>, ds.second, x)), uint16_t{4});
}
TEST_F(StressScenariosTest, DsIdentityWildcardCmpThenAssign)
{
uint32_t x = 0x00abcdefu;
uint32_t x0 = 0x12345678u;
uint32_t x1 = x ^ x0;
const uint64_t yt = 99u, yf = 3u;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<12>(uint8_t{5}),
uint32_t{7},
dpf::geq(dpf::wildcard_value<uint64_t>{}));
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<12>(uint8_t{5}),
uint32_t{7},
dpf::geq(dpf::wildcard_value<uint64_t>{}));
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
EXPECT_FALSE(ds.first.cmp_assigned());
dpf::assign_cmp(ds.first, ds.second, yt, yf);
dpf::assign_cmp(dealer.first, dealer.second, yt, yf);
// Public CWs match after independent assign (same δ + identical coeffs).
using WT = std::decay_t<decltype(ds.first)>;
EXPECT_TRUE(same_bytes(ds.first.value_cw().data(),
dealer.first.value_cw().data(),
ds.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
EXPECT_EQ(ds.first.cw_last(), dealer.first.cw_last());
const uint64_t mask = ds.first.cmp().mask;
for (uint32_t q : {x - 1u, x, x + 1u})
{
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask,
dpf::reconstruct(dpf::eval_point(dpf::cmp, dealer.first, q), dpf::eval_point(dpf::cmp, dealer.second, q)) & mask)
<< "q=" << q;
}
EXPECT_EQ(dpf::reconstruct(ds.first.cmp_addend(), ds.second.cmp_addend()) & mask, yt);
}
TEST_F(StressScenariosTest, DsIdentityLocalCwComplexManyLevelsCmp)
{
// Real DS path with injectable local_cw_protocol on a rich pack: mixed
// widths, xor+additive co-located, deepest concrete, and lt cmp.
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<8>(dpf::bit::one),
dpf::at<16>(uint8_t{3}, dpf::xor_wrapper<uint8_t>{0xa5}),
dpf::at<24>(uint16_t{7}),
uint32_t{9},
dpf::lt(uint64_t{42}));
reset_tape_roots();
PadA pads{};
dpf::local_cw_protocol<PadA> proto{pads};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, take_root, proto,
dpf::at<8>(dpf::bit::one),
dpf::at<16>(uint8_t{3}, dpf::xor_wrapper<uint8_t>{0xa5}),
dpf::at<24>(uint16_t{7}),
uint32_t{9},
dpf::lt(uint64_t{42}));
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
}
TEST_F(StressScenariosTest, DsIdentityModintMultilevelXorAdditiveCmp)
{
using in_t = dpf::modint<12>;
constexpr auto to_int = dpf::utils::to_integral_type<in_t>{};
constexpr auto from_int = dpf::utils::make_from_integral_value<in_t>{};
in_t x{0x2ab};
in_t x0{0x111};
in_t x1 = from_int(static_cast<typename decltype(from_int)::integral_type>(
to_int(x) ^ to_int(x0)));
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<6>(uint8_t{5}, dpf::xor_wrapper<uint8_t>{0x3c}),
uint16_t{9},
dpf::lt(uint16_t{4}, uint16_t{1}));
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<6>(uint8_t{5}, dpf::xor_wrapper<uint8_t>{0x3c}),
uint16_t{9},
dpf::lt(uint16_t{4}, uint16_t{1}));
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, 6>, ds.first, x),
*dpf::eval_point(dpf::out<0, 6>, ds.second, x)), uint8_t{5});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2>, ds.first, x),
*dpf::eval_point(dpf::out<2>, ds.second, x)), uint16_t{9});
}
TEST_F(StressScenariosTest, DsIdentityKeywordMultilevel)
{
using in_t = dpf::keyword<3, dpf::alphabets::hex>;
constexpr auto to_int = dpf::utils::to_integral_type<in_t>{};
constexpr auto from_int = dpf::utils::make_from_integral_value<in_t>{};
in_t x{"abc"};
in_t x0{"a00"};
in_t x1 = from_int(static_cast<typename decltype(from_int)::integral_type>(
to_int(x) ^ to_int(x0)));
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<6>(uint8_t{1}), uint16_t{2});
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<6>(uint8_t{1}), uint16_t{2});
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, 6>, ds.first, x),
*dpf::eval_point(dpf::out<0, 6>, ds.second, x)), uint8_t{1});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, ds.first, x),
*dpf::eval_point(dpf::out<1>, ds.second, x)), uint16_t{2});
}
// ===========================================================================
// G) eval_full coverage: multilevel prefix + deepest (multi-lane), cmp lt/geq
// sweeps, classic full-tree memoizer multi-leaf, explicit memoizer.
// ===========================================================================
TEST_F(StressScenariosTest, MlEvalFullPrefixAndDeepestMultiLane)
{
uint16_t x = 0xa5c3;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint8_t{42}),
dpf::at<12>(uint16_t{7}));
// Prefix slot out<0,8>: compare several lanes to point eval.
auto [pb0, pit0] = dpf::eval_full(dpf::out<0, 8>, k0);
auto [pb1, pit1] = dpf::eval_full(dpf::out<0, 8>, k1);
(void)pit0; (void)pit1;
for (int L : {0, 1, x >> 8, (x >> 8) ^ 1, 0xff})
{
const uint16_t q = static_cast<uint16_t>(L << 8);
EXPECT_EQ(recon(pb0[L], pb1[L]),
recon(*dpf::eval_point(dpf::out<0, 8>, k0, q),
*dpf::eval_point(dpf::out<0, 8>, k1, q))) << "L=" << L;
}
EXPECT_EQ(recon(pb0[x >> 8], pb1[x >> 8]), uint8_t{42});
// Deepest slot out<1,12>: compare several lanes to point eval.
auto [db0, dit0] = dpf::eval_full(dpf::out<1, 12>, k0);
auto [db1, dit1] = dpf::eval_full(dpf::out<1, 12>, k1);
(void)dit0; (void)dit1;
for (int L : {0, x >> 4, (x >> 4) ^ 1, 0xfff})
{
const uint16_t q = static_cast<uint16_t>(L << 4);
EXPECT_EQ(recon(db0[L], db1[L]),
recon(*dpf::eval_point(dpf::out<1, 12>, k0, q),
*dpf::eval_point(dpf::out<1, 12>, k1, q))) << "L=" << L;
}
EXPECT_EQ(recon(db0[x >> 4], db1[x >> 4]), uint16_t{7});
}
// Multilevel eval_full with an explicit per-slot interval memoizer
// (`make_basic_interval_memoizer<Key, I>` stops at meta[I].tree_level).
TEST_F(StressScenariosTest, MlEvalFullWithPerSlotMemoizer)
{
uint16_t x = 0x0abc;
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint8_t{42}), dpf::at<12>(uint16_t{7}));
using KT = std::decay_t<decltype(k0)>;
constexpr uint16_t lo = 0;
constexpr uint16_t hi = 0xff; // full 8-bit prefix domain
auto memo0 = dpf::make_basic_interval_memoizer<KT, 0>(lo, hi);
auto memo1 = dpf::make_basic_interval_memoizer<KT, 0>(lo, hi);
auto buf0 = dpf::make_output_buffer(dpf::out<0, 8>, k0, lo, hi);
auto buf1 = dpf::make_output_buffer(dpf::out<0, 8>, k1, lo, hi);
auto it0 = dpf::eval_full(dpf::out<0, 8>, k0, buf0, memo0);
auto it1 = dpf::eval_full(dpf::out<0, 8>, k1, buf1, memo1);
(void)it0;
(void)it1;
const uint16_t lane = static_cast<uint16_t>(x >> 8);
EXPECT_EQ(recon(buf0[lane], buf1[lane]), uint8_t{42});
EXPECT_EQ(recon(buf0[lane ^ 1u], buf1[lane ^ 1u]), uint8_t{0});
EXPECT_EQ(recon(buf0[0], buf1[0]),
recon(*dpf::eval_point(dpf::out<0, 8>, k0, uint16_t{0}),
*dpf::eval_point(dpf::out<0, 8>, k1, uint16_t{0})));
// Deepest slot with its own per-slot memoizer (stop = tree_level of I=1).
constexpr uint16_t dlo = 0;
constexpr uint16_t dhi = 0xfff;
auto dmemo0 = dpf::make_basic_interval_memoizer<KT, 1>(dlo, dhi);
auto dmemo1 = dpf::make_basic_interval_memoizer<KT, 1>(dlo, dhi);
auto dbuf0 = dpf::make_output_buffer(dpf::out<1, 12>, k0, dlo, dhi);
auto dbuf1 = dpf::make_output_buffer(dpf::out<1, 12>, k1, dlo, dhi);
dpf::eval_full(dpf::out<1, 12>, k0, dbuf0, dmemo0);
dpf::eval_full(dpf::out<1, 12>, k1, dbuf1, dmemo1);
const uint16_t dlane = static_cast<uint16_t>(x >> 4);
EXPECT_EQ(recon(dbuf0[dlane], dbuf1[dlane]), uint16_t{7});
EXPECT_EQ(recon(dbuf0[dlane ^ 1u], dbuf1[dlane ^ 1u]), uint16_t{0});
}
TEST_F(StressScenariosTest, ModintMlEvalFullMatchesPoint)
{
using in_t = dpf::modint<10>;
in_t x{0x155};
auto [k0, k1] = dpf::make_dpf(x, dpf::at<6>(uint8_t{11}), uint16_t{22});
auto [b0, it0] = dpf::eval_full(dpf::out<0, 6>, k0);
auto [b1, it1] = dpf::eval_full(dpf::out<0, 6>, k1);
(void)it0; (void)it1;
constexpr auto to_int = dpf::utils::to_integral_type<in_t>{};
const std::size_t lane = static_cast<std::size_t>(to_int(x) >> (10 - 6));
EXPECT_EQ(recon(b0[lane], b1[lane]), uint8_t{11});
for (int L : {0, 1, static_cast<int>(lane), static_cast<int>(lane ^ 1u), 0x3f})
{
in_t q = dpf::utils::make_from_integral_value<in_t>{}(
static_cast<typename dpf::utils::make_from_integral_value<in_t>::integral_type>(
L << (10 - 6)));
EXPECT_EQ(recon(b0[L], b1[L]),
recon(*dpf::eval_point(dpf::out<0, 6>, k0, q),
*dpf::eval_point(dpf::out<0, 6>, k1, q))) << "L=" << L;
}
}
TEST_F(StressScenariosTest, MlLtFullCmpMatchesPointSweep)
{
const uint8_t alpha = 0x40;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(uint16_t{5}, uint16_t{1}));
auto b0 = dpf::eval_full(dpf::cmp, k0);
auto b1 = dpf::eval_full(dpf::cmp, k1);
const uint64_t mask = k0.cmp().mask;
ASSERT_EQ(b0.size(), 256u);
bool saw_true = false, saw_false = false;
for (uint16_t q = 0; q < 256; ++q)
{
const auto got = recon(b0[q], b1[q]) & mask;
const auto want = dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, static_cast<uint8_t>(q)),
dpf::eval_point(dpf::cmp, k1, static_cast<uint8_t>(q))) & mask;
EXPECT_EQ(got, want) << "q=" << q;
if (got == 5u) saw_true = true;
if (got == 1u) saw_false = true;
}
// Both branches of the lt comparison are actually exercised.
EXPECT_TRUE(saw_true);
EXPECT_TRUE(saw_false);
}
TEST_F(StressScenariosTest, ClassicFullTreeMemoizerMultiLeafIndexed)
{
using dpf_type = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128,
uint8_t, uint16_t, dpf::xor_wrapper<uint16_t>>;
uint8_t x = 0x11;
auto [k0, k1] = dpf::make_dpf(x, uint16_t{3},
dpf::xor_wrapper<uint16_t>{0x00ff});
auto memo0 = dpf::make_full_tree_full_memoizer<dpf_type>();
auto memo1 = dpf::make_full_tree_full_memoizer<dpf_type>();
auto [bufs0, its0] = dpf::eval_full<0, 1>(k0, memo0);
auto [bufs1, its1] = dpf::eval_full<0, 1>(k1, memo1);
(void)its0; (void)its1;
auto & a0 = std::get<0>(bufs0);
auto & a1 = std::get<0>(bufs1);
auto & c0 = std::get<1>(bufs0);
auto & c1 = std::get<1>(bufs1);
ASSERT_EQ(a0.size(), 256u);
for (int q = 0; q < 256; ++q)
{
// Index the memoized full-tree buffers directly and cross-check.
EXPECT_EQ(recon(a0[q], a1[q]),
(q == x) ? uint16_t{3} : uint16_t{0}) << "q=" << q;
EXPECT_EQ(recon(c0[q], c1[q]),
(q == x) ? dpf::xor_wrapper<uint16_t>{0x00ff}
: dpf::xor_wrapper<uint16_t>{0}) << "q=" << q;
EXPECT_EQ(recon(a0[q], a1[q]),
recon(*dpf::eval_point<0>(k0, static_cast<uint8_t>(q)),
*dpf::eval_point<0>(k1, static_cast<uint8_t>(q)))) << "q=" << q;
}
}
// ===========================================================================
// H) Packed small wildcards at a non-terminal level + full assignment;
// LowMC / counter_wrapper PRG adapters.
// ===========================================================================
TEST_F(StressScenariosTest, MlPackedSmallWildcardAtNonTerminalAssignAll)
{
// Eight packed uint8 wildcards at prefix 12 (non-terminal), deepest concrete.
uint16_t x = 0x4c1d;
dpf::wildcard_value<uint8_t> w;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<12>(w, w, w, w, w, w, w, w),
uint32_t{999});
using KT = std::decay_t<decltype(k0)>;
static_assert(KT::meta[0].prefix == 12);
EXPECT_EQ(KT::meta[0].group_id, KT::meta[7].group_id);
EXPECT_NE(KT::meta[0].group_id, KT::meta[8].group_id);
// Unassigned packed wildcards throw; deepest concrete still works.
EXPECT_ANY_THROW((void)dpf::eval_point(dpf::out<0, 12>, k0, x));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8>, k0, x),
*dpf::eval_point(dpf::out<8>, k1, x)),
uint32_t{999});
// Complete assignment of every packed wildcard slot.
const std::array<uint8_t, 8> want{
1, 2, 3, 4, 5, 6, 7, 8};
const std::array<uint8_t, 8> shr0{
10, 20, 30, 40, 50, 60, 70, 80};
assign_wildcard_leaf_local<0>(k0, k1, shr0[0],
static_cast<uint8_t>(want[0] - shr0[0]));
assign_wildcard_leaf_local<1>(k0, k1, shr0[1],
static_cast<uint8_t>(want[1] - shr0[1]));
assign_wildcard_leaf_local<2>(k0, k1, shr0[2],
static_cast<uint8_t>(want[2] - shr0[2]));
assign_wildcard_leaf_local<3>(k0, k1, shr0[3],
static_cast<uint8_t>(want[3] - shr0[3]));
assign_wildcard_leaf_local<4>(k0, k1, shr0[4],
static_cast<uint8_t>(want[4] - shr0[4]));
assign_wildcard_leaf_local<5>(k0, k1, shr0[5],
static_cast<uint8_t>(want[5] - shr0[5]));
assign_wildcard_leaf_local<6>(k0, k1, shr0[6],
static_cast<uint8_t>(want[6] - shr0[6]));
assign_wildcard_leaf_local<7>(k0, k1, shr0[7],
static_cast<uint8_t>(want[7] - shr0[7]));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x),
*dpf::eval_point(dpf::out<0, 12>, k1, x)), want[0]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 12>, k0, x),
*dpf::eval_point(dpf::out<3, 12>, k1, x)), want[3]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 12>, k0, x),
*dpf::eval_point(dpf::out<7, 12>, k1, x)), want[7]);
// Neighbour lane in the packed leaf is zero after assignment.
const uint16_t off = flip_lane_lsb(x, 12, 16);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, off),
*dpf::eval_point(dpf::out<0, 12>, k1, off)), uint8_t{0});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8>, k0, x),
*dpf::eval_point(dpf::out<8>, k1, x)),
uint32_t{999});
}
TEST_F(StressScenariosTest, MlPackedXorWildcardAtNonTerminalAssignAll)
{
uint16_t x = 0xabcd;
dpf::wildcard_value<dpf::xor_wrapper<uint8_t>> w;
auto [k0, k1] = dpf::make_dpf(x,
dpf::at<10>(w, w, w, w),
uint16_t{42});
const std::array<dpf::xor_wrapper<uint8_t>, 4> want{
dpf::xor_wrapper<uint8_t>{0x11},
dpf::xor_wrapper<uint8_t>{0x22},
dpf::xor_wrapper<uint8_t>{0x33},
dpf::xor_wrapper<uint8_t>{0x44}};
const std::array<dpf::xor_wrapper<uint8_t>, 4> shr0{
dpf::xor_wrapper<uint8_t>{0xaa},
dpf::xor_wrapper<uint8_t>{0xbb},
dpf::xor_wrapper<uint8_t>{0xcc},
dpf::xor_wrapper<uint8_t>{0xdd}};
assign_wildcard_leaf_local<0>(k0, k1, shr0[0], want[0] ^ shr0[0]);
assign_wildcard_leaf_local<1>(k0, k1, shr0[1], want[1] ^ shr0[1]);
assign_wildcard_leaf_local<2>(k0, k1, shr0[2], want[2] ^ shr0[2]);
assign_wildcard_leaf_local<3>(k0, k1, shr0[3], want[3] ^ shr0[3]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 10>, k0, x),
*dpf::eval_point(dpf::out<0, 10>, k1, x)), want[0]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 10>, k0, x),
*dpf::eval_point(dpf::out<3, 10>, k1, x)), want[3]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4>, k0, x),
*dpf::eval_point(dpf::out<4>, k1, x)), uint16_t{42});
}
TEST_F(StressScenariosTest, DsIdentityPackedSmallWildcardAtNonTerminal)
{
uint32_t x = 0x00abcdefu;
uint32_t x0 = 0x12345678u;
uint32_t x1 = x ^ x0;
dpf::wildcard_value<uint8_t> w;
reset_tape_roots();
auto dealer = dpf::make_dpf(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<14>(w, w, w, w, uint8_t{9}, w, w, w),
uint32_t{5});
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::at<14>(w, w, w, w, uint8_t{9}, w, w, w),
uint32_t{5});
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
// Concrete sibling in the packed group + deepest reconstruct before assign.
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 14>, dealer.first, x),
*dpf::eval_point(dpf::out<4, 14>, dealer.second, x)),
uint8_t{9});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8>, dealer.first, x),
*dpf::eval_point(dpf::out<8>, dealer.second, x)),
uint32_t{5});
// Full assignment on the dealer keys; DS keys are byte-identical so the
// same shares complete them equivalently.
const std::array<uint8_t, 7> want{1, 2, 3, 4, 6, 7, 8};
const std::array<uint8_t, 7> s0{11, 22, 33, 44, 55, 66, 77};
// Slot indices of wildcards: 0,1,2,3,5,6,7 (slot 4 is concrete).
assign_wildcard_leaf_local<0>(dealer.first, dealer.second, s0[0],
static_cast<uint8_t>(want[0] - s0[0]));
assign_wildcard_leaf_local<1>(dealer.first, dealer.second, s0[1],
static_cast<uint8_t>(want[1] - s0[1]));
assign_wildcard_leaf_local<2>(dealer.first, dealer.second, s0[2],
static_cast<uint8_t>(want[2] - s0[2]));
assign_wildcard_leaf_local<3>(dealer.first, dealer.second, s0[3],
static_cast<uint8_t>(want[3] - s0[3]));
assign_wildcard_leaf_local<5>(dealer.first, dealer.second, s0[4],
static_cast<uint8_t>(want[4] - s0[4]));
assign_wildcard_leaf_local<6>(dealer.first, dealer.second, s0[5],
static_cast<uint8_t>(want[5] - s0[5]));
assign_wildcard_leaf_local<7>(dealer.first, dealer.second, s0[6],
static_cast<uint8_t>(want[6] - s0[6]));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, dealer.first, x),
*dpf::eval_point(dpf::out<0, 14>, dealer.second, x)),
want[0]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 14>, dealer.first, x),
*dpf::eval_point(dpf::out<5, 14>, dealer.second, x)),
want[4]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 14>, dealer.first, x),
*dpf::eval_point(dpf::out<7, 14>, dealer.second, x)),
want[6]);
// Assign the DS pair with the same shares; reconstruction must match.
assign_wildcard_leaf_local<0>(ds.first, ds.second, s0[0],
static_cast<uint8_t>(want[0] - s0[0]));
assign_wildcard_leaf_local<1>(ds.first, ds.second, s0[1],
static_cast<uint8_t>(want[1] - s0[1]));
assign_wildcard_leaf_local<2>(ds.first, ds.second, s0[2],
static_cast<uint8_t>(want[2] - s0[2]));
assign_wildcard_leaf_local<3>(ds.first, ds.second, s0[3],
static_cast<uint8_t>(want[3] - s0[3]));
assign_wildcard_leaf_local<5>(ds.first, ds.second, s0[4],
static_cast<uint8_t>(want[4] - s0[4]));
assign_wildcard_leaf_local<6>(ds.first, ds.second, s0[5],
static_cast<uint8_t>(want[5] - s0[5]));
assign_wildcard_leaf_local<7>(ds.first, ds.second, s0[6],
static_cast<uint8_t>(want[6] - s0[6]));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, ds.first, x),
*dpf::eval_point(dpf::out<0, 14>, ds.second, x)),
want[0]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 14>, ds.first, x),
*dpf::eval_point(dpf::out<7, 14>, ds.second, x)),
want[6]);
}
TEST_F(StressScenariosTest, DsIdentityPackedWildcardLowmcExteriorThenAssign)
{
uint32_t x = 0x0f1e2d3cu;
uint32_t x0 = 0x13572468u;
uint32_t x1 = x ^ x0;
dpf::wildcard_value<uint8_t> w;
reset_tape_roots();
auto dealer = dpf::make_dpf<dpf::prg::aes128, dpf::prg::lowmc128>(x,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::at<12>(w, w, w, w),
uint16_t{77});
reset_tape_roots();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat<dpf::prg::aes128, dpf::prg::lowmc128>(
x0, x1, rng, dpf::at<12>(w, w, w, w), uint16_t{77});
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
const std::array<uint8_t, 4> want{9, 8, 7, 6};
const std::array<uint8_t, 4> s0{1, 2, 3, 4};
assign_wildcard_leaf_local<0>(ds.first, ds.second, s0[0],
static_cast<uint8_t>(want[0] - s0[0]));
assign_wildcard_leaf_local<1>(ds.first, ds.second, s0[1],
static_cast<uint8_t>(want[1] - s0[1]));
assign_wildcard_leaf_local<2>(ds.first, ds.second, s0[2],
static_cast<uint8_t>(want[2] - s0[2]));
assign_wildcard_leaf_local<3>(ds.first, ds.second, s0[3],
static_cast<uint8_t>(want[3] - s0[3]));
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, ds.first, x),
*dpf::eval_point(dpf::out<0, 12>, ds.second, x)), want[0]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 12>, ds.first, x),
*dpf::eval_point(dpf::out<3, 12>, ds.second, x)), want[3]);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4>, ds.first, x),
*dpf::eval_point(dpf::out<4>, ds.second, x)), uint16_t{77});
}
TEST_F(StressScenariosTest, PrgCounterWrapperAesClassicEvalIncrements)
{
using prg = dpf::prg::counter_wrapper<dpf::prg::aes128>;
const auto before = prg::count();
uint8_t x = 0x2a;
auto [k0, k1] = dpf::make_dpf<prg, prg>(x, uint32_t{0x1111});
const auto after_gen = prg::count();
EXPECT_GT(after_gen, before);
(void)dpf::eval_point(k0, x);
(void)dpf::eval_full(k0);
const auto after_eval = prg::count();
EXPECT_GT(after_eval, after_gen);
EXPECT_EQ(recon(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)),
uint32_t{0x1111});
}
TEST_F(StressScenariosTest, PrgCounterWrapperLowmcMultilevelEvalIncrements)
{
using interior = dpf::prg::counter_wrapper<dpf::prg::lowmc128>;
using exterior = dpf::prg::counter_wrapper<dpf::prg::aes128>;
const auto bi = interior::count();
const auto be = exterior::count();
uint16_t x = 0x3c5a;
auto [k0, k1] = dpf::make_dpf<interior, exterior>(x,
dpf::at<8>(uint8_t{42}), uint16_t{7});
EXPECT_GT(interior::count(), bi);
EXPECT_GT(exterior::count(), be);
const auto bi2 = interior::count();
const auto be2 = exterior::count();
(void)dpf::eval_point(dpf::out<0, 8>, k0, x);
(void)dpf::eval_full(dpf::out<1, 16>, k0);
EXPECT_GT(interior::count(), bi2);
EXPECT_GT(exterior::count(), be2);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 8>, k0, x),
*dpf::eval_point(dpf::out<0, 8>, k1, x)), uint8_t{42});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 16>, k0, x),
*dpf::eval_point(dpf::out<1, 16>, k1, x)), uint16_t{7});
}
TEST_F(StressScenariosTest, PrgLowmcBothSidesPackedInterval)
{
uint16_t x = 0x91a2;
auto [k0, k1] = dpf::make_dpf<dpf::prg::lowmc128, dpf::prg::lowmc128>(x,
dpf::at<12>(uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4}),
uint32_t{55});
constexpr std::size_t N = 12;
const uint16_t lane = static_cast<uint16_t>(x >> (16 - N));
const uint16_t from = static_cast<uint16_t>(lane & ~uint16_t{0xf});
const uint16_t to = static_cast<uint16_t>(from + 0x20u);
auto [b0, it0] = dpf::eval_interval(dpf::out<0, N>, k0, from, to);
auto [b1, it1] = dpf::eval_interval(dpf::out<0, N>, k1, from, to);
(void)it0; (void)it1;
const std::size_t idx = static_cast<std::size_t>(lane - from);
ASSERT_LT(idx, b0.size());
EXPECT_EQ(recon(b0[idx], b1[idx]), uint8_t{1});
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4>, k0, x),
*dpf::eval_point(dpf::out<4>, k1, x)), uint32_t{55});
}