#include #include "dpf.hpp" #include #include #include 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(n), static_cast(n * 9 + 3)); n += 2; return v; } uint8_t bit() { return static_cast(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 T bare(const T & v) { return v; } template T bare(const dpf::secret_share & s) { return s.raw(); } template auto recon(const A & a, const B & b) { using T = decltype(bare(a)); return static_cast(bare(a) - bare(b)); } template auto ev(const Key & key, In x) { return bare(*dpf::eval_point(key, x)); } } // namespace TEST(HalfTree, TraitsSelectedByCcrPrg) { static_assert(dpf::tree_traits::is_half_tree); static_assert(!dpf::tree_traits::is_half_tree); static_assert(dpf::tree_traits::last_level_differs); static_assert(!dpf::tree_traits::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(alpha, beta); using key_t = std::decay_t; 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(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(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(alpha, beta); auto [b0, b1] = dpf::make_dpf(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(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(alpha ^ x0); const out_t y = 0x42; reset_roots(); auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, y); reset_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness ds_rng{take_root, Pad{}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(x0, x1, ds_rng, y); using key_t = std::decay_t; 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(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(alpha - x0); const out_t y = 9; reset_roots(); auto dealer = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, y); reset_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness ds_rng{take_root, Pad{}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_input, x0, x1, ds_rng, y); for (int i = 0; i < 256; ++i) { const in_t q = static_cast(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(alpha ^ x0); const out_t y = 0x77; reset_roots(); auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, y); reset_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness g_rng{take_root, Pad{}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto g = dpf::geneval_point(x0, x1, alpha, g_rng, y); using key_t = std::decay_t; 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(alpha - x0); const out_t y = 3; reset_roots(); auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t{take_root}, y); reset_roots(); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") dpf::ds_randomness g_rng{take_root, Pad{}}; HEDLEY_PRAGMA(GCC diagnostic pop) auto g = dpf::geneval_point(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(alpha, dpf::lt(if_true, if_false)); auto bgi = dpf::make_dpf(alpha, dpf::lt(if_true, if_false)); using key_t = std::decay_t; 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(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(alpha, dpf::at<8>(uint8_t{7})); using key_t = std::decay_t; 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(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; } }