2304 lines
92 KiB
C++
2304 lines
92 KiB
C++
// 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();
|
|
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,
|
|
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();
|
|
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<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{static_cast<in_t::integral_type>(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();
|
|
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<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();
|
|
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>(
|
|
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();
|
|
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,
|
|
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();
|
|
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>(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();
|
|
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),
|
|
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();
|
|
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<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();
|
|
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>(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();
|
|
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<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();
|
|
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<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();
|
|
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<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();
|
|
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<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});
|
|
}
|
|
|