#include #include #include #include #include #include #include "dpf.hpp" namespace { template bool eq_block(const Block & a, const Block & b) { return std::memcmp(&a, &b, sizeof(Block)) == 0; } template void check_pprf_spread(InputT alpha) { auto master = dpf::make_pprf_master(); auto punctured = dpf::puncture(master, alpha, /*program_alpha=*/true); const auto master_at_alpha = dpf::pprf_eval(master, alpha); EXPECT_TRUE(eq_block(master_at_alpha, *punctured.programmed)); EXPECT_TRUE(eq_block(dpf::pprf_eval(punctured, alpha), master_at_alpha)); std::vector points = { InputT{0}, InputT{1}, static_cast(alpha + 1), static_cast(alpha ^ InputT{1}), }; if constexpr (dpf::utils::bitlength_of_v > 8) { points.push_back(static_cast( InputT{1} << (dpf::utils::bitlength_of_v / 2))); points.push_back(static_cast(alpha ^ (InputT{1} << 7))); } for (InputT x : points) { if (x == alpha) continue; EXPECT_TRUE(eq_block(dpf::pprf_eval(master, x), dpf::pprf_eval(punctured, x))); } auto bare = dpf::puncture(master, alpha, /*program_alpha=*/false); EXPECT_FALSE(bare.programmed.has_value()); EXPECT_THROW((void)dpf::pprf_eval(bare, alpha), std::invalid_argument); for (InputT x : points) { if (x == alpha) continue; EXPECT_TRUE(eq_block(dpf::pprf_eval(master, x), dpf::pprf_eval(bare, x))); } EXPECT_EQ(punctured.siblings.size(), dpf::utils::bitlength_of_v); } bool node_has_seed(const dpf::pprf_copath & copath, const dpf::prg::aes128::block_type & seed) { for (const auto & node : copath.nodes) { if (eq_block(node.seed, seed)) return true; } return false; } } // namespace TEST(Pprf, PathBitHelpersAreConstexpr) { using input_t = std::uint8_t; static_assert(dpf::detail::pprf_impl::path_bit(0b1010'0000u, 0)); static_assert(!dpf::detail::pprf_impl::path_bit(0b0010'0000u, 0)); static_assert(dpf::detail::pprf_impl::path_prefix(0b1010'1100u, 4) == input_t{0b1010u}); static_assert(dpf::detail::pprf_impl::prefix_matches( 0b1010'1100u, 4, input_t{0b1010u})); static_assert(!dpf::detail::pprf_impl::prefix_matches( 0b1010'1100u, 4, input_t{0b1011u})); SUCCEED(); } TEST(Pprf, Uint32) { check_pprf_spread(0x00c0ffeeu); } TEST(Pprf, Uint128) { using input_t = simde_uint128; const input_t alpha = (input_t{1} << 120) | input_t{0xdeadbeefull}; check_pprf_spread(alpha); } TEST(PprfCopath, OneHiddenAgreesWithOnePoint) { using input_t = std::uint8_t; constexpr std::size_t n = dpf::utils::bitlength_of_v; auto master = dpf::make_pprf_master(); const input_t alpha = 0x2au; auto one = dpf::puncture(master, alpha, /*program_alpha=*/true); const input_t hidden[] = {alpha}; auto copath = dpf::puncture(master, std::begin(hidden), std::end(hidden), /*program_hidden=*/true); ASSERT_EQ(copath.nodes.size(), n); ASSERT_EQ(copath.programmed.size(), 1u); EXPECT_TRUE(eq_block(copath.programmed[0], *one.programmed)); for (std::size_t i = 0; i < n; ++i) { EXPECT_EQ(copath.nodes[i].level, i + 1); EXPECT_TRUE(eq_block(copath.nodes[i].seed, one.siblings[i])); EXPECT_EQ(copath.nodes[i].prefix, dpf::detail::pprf_impl::path_prefix( static_cast( alpha ^ static_cast(input_t{1} << (n - 1 - i))), i + 1)); } for (unsigned x = 0; x < 256; ++x) { const auto xi = static_cast(x); EXPECT_TRUE(eq_block(dpf::pprf_eval(copath, xi), dpf::pprf_eval(one, xi))); if (xi != alpha) EXPECT_TRUE( eq_block(dpf::pprf_eval(copath, xi), dpf::pprf_eval(master, xi))); } auto bare = dpf::puncture(master, std::begin(hidden), std::end(hidden), /*program_hidden=*/false); EXPECT_TRUE(bare.programmed.empty()); EXPECT_THROW((void)dpf::pprf_eval(bare, alpha), std::invalid_argument); for (unsigned x = 0; x < 256; ++x) { const auto xi = static_cast(x); if (xi == alpha) continue; EXPECT_TRUE( eq_block(dpf::pprf_eval(bare, xi), dpf::pprf_eval(master, xi))); } } TEST(PprfCopath, AdjacentAndSeparatedSets) { using input_t = std::uint8_t; constexpr std::size_t n = dpf::utils::bitlength_of_v; auto master = dpf::make_pprf_master(); const input_t adjacent[] = {2, 3}; auto adj = dpf::puncture(master, std::begin(adjacent), std::end(adjacent)); // Share a 7-bit prefix: one sibling per shared level, none at the leaves. EXPECT_EQ(adj.nodes.size(), n - 1); EXPECT_LE(adj.nodes.size(), n * adj.hidden.size()); EXPECT_LT(adj.nodes.size(), 2 * n); const input_t separated[] = {2, 5}; auto sep = dpf::puncture(master, std::begin(separated), std::end(separated)); EXPECT_LE(sep.nodes.size(), n * sep.hidden.size()); EXPECT_GT(sep.nodes.size(), adj.nodes.size()); for (unsigned x = 0; x < 256; ++x) { const auto xi = static_cast(x); const bool in_adj = (xi == 2 || xi == 3); const bool in_sep = (xi == 2 || xi == 5); if (in_adj) EXPECT_THROW((void)dpf::pprf_eval(adj, xi), std::invalid_argument); else EXPECT_TRUE( eq_block(dpf::pprf_eval(adj, xi), dpf::pprf_eval(master, xi))); if (in_sep) EXPECT_THROW((void)dpf::pprf_eval(sep, xi), std::invalid_argument); else EXPECT_TRUE( eq_block(dpf::pprf_eval(sep, xi), dpf::pprf_eval(master, xi))); } } TEST(PprfCopath, HiddenLeafNotPublishedWhenSiblingHidden) { using input_t = std::uint8_t; auto master = dpf::make_pprf_master(); const input_t leaf = 2; const auto leaf_seed = dpf::pprf_eval(master, leaf); const input_t alone[] = {leaf}; auto solo = dpf::puncture(master, std::begin(alone), std::end(alone), /*program_hidden=*/true); EXPECT_TRUE(node_has_seed(solo, dpf::pprf_eval(master, static_cast(3)))); const input_t both[] = {2, 3}; auto pair = dpf::puncture(master, std::begin(both), std::end(both), /*program_hidden=*/true); EXPECT_FALSE(node_has_seed(pair, leaf_seed)); EXPECT_FALSE(node_has_seed(pair, dpf::pprf_eval(master, static_cast(3)))); ASSERT_EQ(pair.programmed.size(), 2u); EXPECT_TRUE(eq_block(pair.programmed[0], leaf_seed)); EXPECT_TRUE(eq_block(pair.programmed[1], dpf::pprf_eval(master, static_cast(3)))); EXPECT_TRUE(eq_block(dpf::pprf_eval(pair, leaf), leaf_seed)); EXPECT_TRUE(eq_block(dpf::pprf_eval(pair, static_cast(3)), dpf::pprf_eval(master, static_cast(3)))); } TEST(PprfCopath, EmptyAndDuplicates) { using input_t = std::uint8_t; auto master = dpf::make_pprf_master(); const std::vector none{}; auto empty = dpf::puncture(master, none.begin(), none.end()); ASSERT_EQ(empty.nodes.size(), 1u); EXPECT_EQ(empty.nodes[0].level, 0u); EXPECT_EQ(empty.nodes[0].prefix, input_t{}); EXPECT_TRUE(eq_block(empty.nodes[0].seed, master.root)); EXPECT_TRUE(empty.hidden.empty()); for (unsigned x = 0; x < 256; ++x) { const auto xi = static_cast(x); EXPECT_TRUE( eq_block(dpf::pprf_eval(empty, xi), dpf::pprf_eval(master, xi))); } const input_t dups[] = {5, 2, 5, 2, 5}; auto once = dpf::puncture(master, std::begin(dups), std::end(dups)); const input_t unique[] = {2, 5}; auto clean = dpf::puncture(master, std::begin(unique), std::end(unique)); ASSERT_EQ(once.hidden.size(), 2u); EXPECT_EQ(once.hidden, clean.hidden); ASSERT_EQ(once.nodes.size(), clean.nodes.size()); for (std::size_t i = 0; i < once.nodes.size(); ++i) { EXPECT_EQ(once.nodes[i].level, clean.nodes[i].level); EXPECT_EQ(once.nodes[i].prefix, clean.nodes[i].prefix); EXPECT_TRUE(eq_block(once.nodes[i].seed, clean.nodes[i].seed)); } } TEST(PprfCopath, FullDomainPublishesNothing) { using input_t = std::uint8_t; auto master = dpf::make_pprf_master(); std::vector all(256); for (unsigned x = 0; x < 256; ++x) all[x] = static_cast(x); auto bare = dpf::puncture(master, all.begin(), all.end(), /*program_hidden=*/false); EXPECT_TRUE(bare.nodes.empty()); ASSERT_EQ(bare.hidden.size(), 256u); EXPECT_TRUE(bare.programmed.empty()); for (unsigned x = 0; x < 256; ++x) EXPECT_THROW((void)dpf::pprf_eval(bare, static_cast(x)), std::invalid_argument); auto programmed = dpf::puncture(master, all.begin(), all.end(), /*program_hidden=*/true); EXPECT_TRUE(programmed.nodes.empty()); ASSERT_EQ(programmed.programmed.size(), 256u); for (unsigned x = 0; x < 256; ++x) { const auto xi = static_cast(x); EXPECT_TRUE(eq_block(dpf::pprf_eval(programmed, xi), dpf::pprf_eval(master, xi))); } } TEST(PprfCopath, NodePrefixesAreConsistent) { using input_t = std::uint8_t; auto master = dpf::make_pprf_master(); const input_t hidden[] = {2, 5, 200}; auto copath = dpf::puncture(master, std::begin(hidden), std::end(hidden)); EXPECT_LE(copath.nodes.size(), dpf::utils::bitlength_of_v * copath.hidden.size()); for (const auto & node : copath.nodes) { ASSERT_GE(node.level, 1u); ASSERT_LE(node.level, dpf::utils::bitlength_of_v); // Right-aligned prefix fits in `level` bits. if (node.level < dpf::utils::bitlength_of_v) { EXPECT_LT(static_cast(node.prefix), 1u << node.level); } } for (unsigned x = 0; x < 256; ++x) { const auto xi = static_cast(x); if (std::binary_search(std::begin(hidden), std::end(hidden), xi)) continue; EXPECT_TRUE( eq_block(dpf::pprf_eval(copath, xi), dpf::pprf_eval(master, xi))); } } TEST(PprfCopath, Uint32SetMatchesMaster) { using input_t = std::uint32_t; auto master = dpf::make_pprf_master(); const input_t hidden[] = {0u, 0x00c0ffeeu, 0xffffffffu}; auto copath = dpf::puncture(master, std::begin(hidden), std::end(hidden), /*program_hidden=*/true); ASSERT_EQ(copath.programmed.size(), 3u); for (input_t h : hidden) EXPECT_TRUE(eq_block(dpf::pprf_eval(copath, h), dpf::pprf_eval(master, h))); const input_t samples[] = {1u, 2u, 0x00c0ffefu, 0x80000000u, 0xfffffffeu}; for (input_t x : samples) { if (std::find(std::begin(hidden), std::end(hidden), x) != std::end(hidden)) continue; EXPECT_TRUE( eq_block(dpf::pprf_eval(copath, x), dpf::pprf_eval(master, x))); } }