libdpf/test/tests/half_tree_test.cpp

356 lines
11 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include "dpf.hpp"
#include <cstdint>
#include <cstring>
#include <vector>
namespace
{
using ht_prg = dpf::prg::aes128_ccr;
using leaf_prg = dpf::prg::aes128;
simde__m128i g_roots[8];
int g_ri = 0;
simde__m128i take_root() { return g_roots[g_ri++]; }
struct Pad
{
uint64_t n = 1;
simde__m128i block()
{
auto v = simde_mm_set_epi64x(static_cast<long long>(n),
static_cast<long long>(n * 9 + 3));
n += 2;
return v;
}
uint8_t bit() { return static_cast<uint8_t>(n++ & 1u); }
};
void reset_roots()
{
g_ri = 0;
for (int i = 0; i < 8; ++i)
g_roots[i] = simde_mm_set_epi64x(0x1111 * (i + 1), 0xA5A50000u + i * 17);
}
template <typename T>
T bare(const T & v)
{
return v;
}
template <typename T, std::size_t Party, dpf::sharing Scheme>
T bare(const dpf::secret_share<T, Party, Scheme> & s)
{
return s.raw();
}
template <typename A, typename B>
auto recon(const A & a, const B & b)
{
using T = decltype(bare(a));
return static_cast<T>(bare(a) - bare(b));
}
template <typename Key, typename In>
auto ev(const Key & key, In x)
{
return bare(*dpf::eval_point(key, x));
}
} // namespace
TEST(HalfTree, TraitsSelectedByCcrPrg)
{
static_assert(dpf::tree_traits<ht_prg>::is_half_tree);
static_assert(!dpf::tree_traits<leaf_prg>::is_half_tree);
static_assert(dpf::tree_traits<ht_prg>::last_level_differs);
static_assert(!dpf::tree_traits<ht_prg>::stores_mid_advice);
}
TEST(HalfTree, DealerPointAndFullDomain)
{
using in_t = std::uint8_t;
using out_t = std::uint32_t;
const in_t alpha = 0x2a;
const out_t beta = 0x01020304;
auto [k0, k1] = dpf::make_dpf<ht_prg, leaf_prg>(alpha, beta);
using key_t = std::decay_t<decltype(k0)>;
static_assert(key_t::tree::is_half_tree);
// Mid-level advice unused; last level may pack BGI-style advice.
for (std::size_t i = 0; i + 1 < key_t::depth; ++i)
EXPECT_EQ(k0.correction_advice(i), 0) << "mid advice " << i;
for (int i = 0; i < 256; ++i)
{
const in_t q = static_cast<in_t>(i);
const out_t got = recon(ev(k0, q), ev(k1, q));
EXPECT_EQ(got, q == alpha ? beta : out_t{}) << i;
}
auto [buf0, it0] = dpf::eval_full(k0);
auto [buf1, it1] = dpf::eval_full(k1);
(void)it0;
(void)it1;
for (int i = 0; i < 256; ++i)
{
const out_t got = recon(buf0[i], buf1[i]);
EXPECT_EQ(got, static_cast<in_t>(i) == alpha ? beta : out_t{}) << i;
}
}
TEST(HalfTree, OutputParityVsBgi)
{
using in_t = std::uint8_t;
using out_t = std::uint16_t;
const in_t alpha = 0x7e;
const out_t beta = 0xabcd;
auto [h0, h1] = dpf::make_dpf<ht_prg, leaf_prg>(alpha, beta);
auto [b0, b1] = dpf::make_dpf<leaf_prg, leaf_prg>(alpha, beta);
// Keys differ (different tree), but reconstructed outputs match.
EXPECT_NE(std::memcmp(&h0.root(), &b0.root(), sizeof(simde__m128i)), 0);
for (int i = 0; i < 256; ++i)
{
const in_t q = static_cast<in_t>(i);
const out_t ht = recon(ev(h0, q), ev(h1, q));
const out_t bgi = recon(ev(b0, q), ev(b1, q));
EXPECT_EQ(ht, bgi) << i;
EXPECT_EQ(ht, q == alpha ? beta : out_t{}) << i;
}
}
TEST(HalfTree, DoernerShelatXorMatchesDealer)
{
using in_t = std::uint8_t;
using out_t = std::uint16_t;
const in_t alpha = 0x11;
const in_t x0 = 0x55;
const in_t x1 = static_cast<in_t>(alpha ^ x0);
const out_t y = 0x42;
reset_roots();
auto dealer = dpf::make_dpf<ht_prg, leaf_prg>(alpha,
dpf::root_sampler_t<ht_prg>{take_root}, y);
reset_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), Pad> ds_rng{take_root, Pad{}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat<ht_prg, leaf_prg>(x0, x1, ds_rng, y);
using key_t = std::decay_t<decltype(dealer.first)>;
EXPECT_EQ(std::memcmp(&dealer.first.root(), &ds.first.root(),
sizeof(simde__m128i)), 0);
EXPECT_EQ(std::memcmp(&dealer.second.root(), &ds.second.root(),
sizeof(simde__m128i)), 0);
for (std::size_t i = 0; i < key_t::depth; ++i)
{
EXPECT_EQ(std::memcmp(&dealer.first.correction_word(i),
&ds.first.correction_word(i), sizeof(simde__m128i)), 0)
<< "cw " << i;
EXPECT_EQ(dealer.first.correction_advice(i),
ds.first.correction_advice(i))
<< "advice " << i;
}
for (int i = 0; i < 256; ++i)
{
const in_t q = static_cast<in_t>(i);
EXPECT_EQ(recon(ev(ds.first, q), ev(ds.second, q)),
q == alpha ? y : out_t{})
<< i;
}
}
TEST(HalfTree, DoernerShelatArithMatchesDealer)
{
using in_t = std::uint8_t;
using out_t = std::uint16_t;
const in_t alpha = 0xc0;
const in_t x0 = 0x40;
const in_t x1 = static_cast<in_t>(alpha - x0);
const out_t y = 9;
reset_roots();
auto dealer = dpf::make_dpf<ht_prg, leaf_prg>(alpha,
dpf::root_sampler_t<ht_prg>{take_root}, y);
reset_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), Pad> ds_rng{take_root, Pad{}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto ds = dpf::make_dpf_doerner_shelat<ht_prg, leaf_prg>(dpf::arith_input,
x0, x1, ds_rng, y);
for (int i = 0; i < 256; ++i)
{
const in_t q = static_cast<in_t>(i);
EXPECT_EQ(recon(ev(ds.first, q), ev(ds.second, q)),
q == alpha ? y : out_t{})
<< i;
EXPECT_EQ(ev(ds.first, q), ev(dealer.first, q)) << "p0 " << i;
EXPECT_EQ(ev(ds.second, q), ev(dealer.second, q)) << "p1 " << i;
}
}
TEST(HalfTree, GenevalPointMatchesDealer)
{
using in_t = std::uint8_t;
using out_t = std::uint16_t;
const in_t alpha = 0x33;
const in_t x0 = 0x0f;
const in_t x1 = static_cast<in_t>(alpha ^ x0);
const out_t y = 0x77;
reset_roots();
auto keys = dpf::make_dpf<ht_prg, leaf_prg>(alpha,
dpf::root_sampler_t<ht_prg>{take_root}, y);
reset_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), Pad> g_rng{take_root, Pad{}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto g = dpf::geneval_point<ht_prg, leaf_prg>(x0, x1, alpha, g_rng, y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_TRUE(g.leaf_live);
EXPECT_EQ(g.live_levels, key_t::depth);
for (std::size_t i = 0; i < g.live_levels; ++i)
{
EXPECT_EQ(std::memcmp(&g.correction_words[i],
&keys.first.correction_word(i), sizeof(simde__m128i)), 0)
<< "cw " << i;
EXPECT_EQ(g.correction_advice[i], keys.first.correction_advice(i))
<< "advice " << i;
}
ASSERT_EQ(g.party0.size(), 1u);
EXPECT_EQ(g.party0[0], ev(keys.first, alpha));
EXPECT_EQ(g.party1[0], ev(keys.second, alpha));
EXPECT_EQ(recon(g.party0[0], g.party1[0]), y);
}
TEST(HalfTree, GenevalArithPointMatchesDealer)
{
using in_t = std::uint8_t;
using out_t = std::uint16_t;
const in_t alpha = 0x90;
const in_t x0 = 0x20;
const in_t x1 = static_cast<in_t>(alpha - x0);
const out_t y = 3;
reset_roots();
auto keys = dpf::make_dpf<ht_prg, leaf_prg>(alpha,
dpf::root_sampler_t<ht_prg>{take_root}, y);
reset_roots();
HEDLEY_PRAGMA(GCC diagnostic push)
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
dpf::ds_randomness<simde__m128i (*)(), Pad> g_rng{take_root, Pad{}};
HEDLEY_PRAGMA(GCC diagnostic pop)
auto g = dpf::geneval_point<ht_prg, leaf_prg>(dpf::arith_input, x0, x1,
alpha, g_rng, y);
EXPECT_TRUE(g.leaf_live);
ASSERT_EQ(g.party0.size(), 1u);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), y);
EXPECT_EQ(g.party0[0], ev(keys.first, alpha));
EXPECT_EQ(g.party1[0], ev(keys.second, alpha));
}
TEST(HalfTree, IncrementalCmpSmoke)
{
using in_t = std::uint8_t;
using ht_prg = dpf::prg::aes128_ccr;
using leaf_prg = dpf::prg::aes128;
const in_t alpha = 0x40;
const uint64_t if_true = 4;
const uint64_t if_false = 1;
auto ht = dpf::make_dpf<ht_prg, leaf_prg>(alpha,
dpf::lt(if_true, if_false));
auto bgi = dpf::make_dpf<leaf_prg, leaf_prg>(alpha,
dpf::lt(if_true, if_false));
using key_t = std::decay_t<decltype(ht.first)>;
static_assert(key_t::tree::is_half_tree);
for (std::size_t i = 0; i + 1 < key_t::depth; ++i)
EXPECT_EQ(ht.first.correction_advice(i), 0) << "mid advice " << i;
const uint64_t mask = ht.first.cmp().mask;
auto recon_cmp = [&](const auto & k0, const auto & k1, in_t q) {
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q),
dpf::eval_point(dpf::cmp, k1, q))
& mask;
};
for (int i = 0; i < 256; ++i)
{
const in_t q = static_cast<in_t>(i);
const auto ht_got = recon_cmp(ht.first, ht.second, q);
const auto bgi_got = recon_cmp(bgi.first, bgi.second, q);
const uint64_t expect = (q < alpha) ? if_true : if_false;
EXPECT_EQ(ht_got, expect) << i;
EXPECT_EQ(ht_got, bgi_got) << "parity " << i;
}
}
TEST(HalfTree, IncrementalPlacementSmoke)
{
using in_t = std::uint8_t;
const in_t alpha = 0x40;
auto keys = dpf::make_dpf<ht_prg, leaf_prg>(alpha, dpf::at<8>(uint8_t{7}));
using key_t = std::decay_t<decltype(keys.first)>;
static_assert(key_t::tree::is_half_tree);
for (std::size_t i = 0; i + 1 < key_t::depth; ++i)
EXPECT_EQ(keys.first.correction_advice(i), 0) << "mid advice " << i;
for (int i = 0; i < 256; ++i)
{
const in_t q = static_cast<in_t>(i);
const auto got = recon(*dpf::eval_point(keys.first, q),
*dpf::eval_point(keys.second, q));
EXPECT_EQ(got, q == alpha ? uint8_t{7} : uint8_t{0}) << i;
}
}
TEST(HalfTree, VerifiableFullDomainDetectsSeedTamper)
{
using in_t = std::uint8_t;
using out_t = std::uint16_t;
const in_t alpha = 0;
const out_t beta = 0x1111;
auto [k0, k1] = dpf::make_dpf<ht_prg, leaf_prg>(alpha, beta, dpf::verifiable{});
EXPECT_TRUE(std::decay_t<decltype(k0)>::tree::is_half_tree);
for (int i = 0; i < 256; ++i)
{
const in_t q = static_cast<in_t>(i);
dpf::proof_token a{}, b{};
const out_t got = recon(*dpf::eval_point(k0, q, dpf::prove(a)),
*dpf::eval_point(k1, q, dpf::prove(b)));
EXPECT_EQ(got, q == alpha ? beta : out_t{}) << i;
EXPECT_TRUE(dpf::verify(a, b)) << i;
}
for (auto & cs : const_cast<typename std::decay_t<decltype(k0)>::correction_seeds_array &>(
k0.correction_seeds()))
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
int rejected = 0;
for (int i = 0; i < 256; ++i)
{
const in_t q = static_cast<in_t>(i);
dpf::proof_token a{}, b{};
(void)*dpf::eval_point(k0, q, dpf::prove(a));
(void)*dpf::eval_point(k1, q, dpf::prove(b));
if (!dpf::verify(a, b))
++rejected;
}
EXPECT_GT(rejected, 0);
}