#include #include #include #include #include #include #include "dpf.hpp" #include "simde/simde/x86/avx2.h" namespace { bool blocks_equal(simde__m128i a, simde__m128i b) { return simde_mm_movemask_epi8(simde_mm_cmpeq_epi8(a, b)) == 0xFFFF; } simde__m128i block_from_lanes(std::uint64_t lo, std::uint64_t hi) { simde__m128i x; std::uint64_t lane[2] = {lo, hi}; std::memcpy(&x, lane, sizeof(x)); return x; } simde__m128i block_from_bytes(const std::uint8_t * p) { simde__m128i x; std::memcpy(&x, p, sizeof(x)); return x; } // RFC 8439 ยง2.3.2. Counter = 1, nonce = 00:00:00:09:00:00:00:4a:00:00:00:00. constexpr std::uint8_t k_rfc_keystream[64] = { 0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, 0xc4, 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, 0x6c, 0x4e, 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2, 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e }; } // namespace TEST(ChachaPrg, Rfc8439Block) { const std::uint32_t key[8] = { 0x03020100u, 0x07060504u, 0x0b0a0908u, 0x0f0e0d0cu, 0x13121110u, 0x17161514u, 0x1b1a1918u, 0x1f1e1d1cu }; const std::uint32_t nonce[3] = {0x09000000u, 0x4a000000u, 0x00000000u}; std::uint8_t out[64]; dpf::prg::chacha_detail::block<20>(key, 1, nonce, out); EXPECT_EQ(0, std::memcmp(out, k_rfc_keystream, sizeof(out))); std::uint8_t fewer[64]; dpf::prg::chacha_detail::block<8>(key, 1, nonce, fewer); EXPECT_NE(0, std::memcmp(fewer, k_rfc_keystream, sizeof(fewer))); } TEST(ChachaPrg, WideBlockMatchesScalar) { std::uint32_t keys[4][8]; std::uint32_t counters[4] = {0u, 1u, 5u, 0x00fffff0u}; for (int lane = 0; lane < 4; ++lane) { for (int w = 0; w < 8; ++w) { keys[lane][w] = 0x9e3779b9u * static_cast(lane + 1) + static_cast(w) * 0x01000193u; } } std::uint8_t wide[4][64]; dpf::prg::chacha_detail::block4<20>(keys, counters, wide); for (int lane = 0; lane < 4; ++lane) { std::uint8_t scalar[64]; dpf::prg::chacha_detail::block<20>(keys[lane], counters[lane], dpf::prg::chacha_detail::zero_nonce, scalar); EXPECT_EQ(0, std::memcmp(wide[lane], scalar, 64)) << "lane=" << lane; } } TEST(ChachaPrg, EvalIsKeystreamChunk) { using prg = dpf::prg::chacha20; simde__m128i seed = block_from_lanes(0x0123456789abcdefull, 0xfedcba9876543210ull); std::uint32_t key[8]; dpf::prg::chacha_detail::seed_key(seed, key); for (psnip_uint32_t pos = 0; pos < 8; ++pos) { std::uint8_t buf[64]; dpf::prg::chacha_detail::block<20>(key, pos >> 2, dpf::prg::chacha_detail::zero_nonce, buf); EXPECT_TRUE(blocks_equal(prg::eval(seed, pos), block_from_bytes(buf + 16 * (pos & 3u)))) << "pos=" << pos; } auto kids = prg::eval01(seed); EXPECT_TRUE(blocks_equal(kids[0], prg::eval(seed, 0))); EXPECT_TRUE(blocks_equal(kids[1], prg::eval(seed, 1))); EXPECT_FALSE(blocks_equal(kids[0], kids[1])); EXPECT_FALSE(blocks_equal(kids[0], seed)); EXPECT_FALSE(blocks_equal(dpf::prg::chacha12::eval(seed, 0), kids[0])); EXPECT_FALSE(blocks_equal(dpf::prg::chacha8::eval(seed, 0), kids[0])); } TEST(ChachaPrg, BulkAndWideAgree) { using prg = dpf::prg::chacha20; simde__m128i seed = block_from_lanes(0x0123456789abcdefull, 0xfedcba9876543210ull); auto check_bulk = [&](psnip_uint32_t pos, psnip_uint32_t count) { alignas(16) simde__m128i bulk[32]; prg::eval(seed, bulk, count, pos); for (psnip_uint32_t i = 0; i < count; ++i) { EXPECT_TRUE(blocks_equal(bulk[i], prg::eval(seed, pos + i))) << "pos=" << pos << " i=" << i; } }; check_bulk(0, 1); check_bulk(0, 2); check_bulk(0, 4); check_bulk(0, 16); check_bulk(1, 20); check_bulk(3, 6); check_bulk(4, 7); check_bulk(0xfffffffeu, 2); alignas(16) simde__m128i seeds[8]; alignas(16) simde__m128i out4[4]; alignas(16) simde__m128i out8[8]; alignas(16) simde__m128i left[4]; alignas(16) simde__m128i right[4]; for (int i = 0; i < 8; ++i) { seeds[i] = block_from_lanes(0x1000u + static_cast(i), 0x2000u); } prg::eval_x4(seeds, out4, 5); prg::eval_x8(seeds, out8, 0); prg::eval01_x4(seeds, left, right); for (int i = 0; i < 4; ++i) { EXPECT_TRUE(blocks_equal(out4[i], prg::eval(seeds[i], 5))); EXPECT_TRUE(blocks_equal(left[i], prg::eval(seeds[i], 0))); EXPECT_TRUE(blocks_equal(right[i], prg::eval(seeds[i], 1))); } for (int i = 0; i < 8; ++i) { EXPECT_TRUE(blocks_equal(out8[i], prg::eval(seeds[i], 0))); } alignas(16) simde__m128i one[1]; EXPECT_NO_THROW(prg::eval(seed, one, 1, 0xffffffffu)); EXPECT_TRUE(blocks_equal(one[0], prg::eval(seed, 0xffffffffu))); EXPECT_THROW(prg::eval(seed, out4, 2, 0xffffffffu), std::invalid_argument); prg::eval(seed, out4, 0, 0xffffffffu); } TEST(ChachaPrg, DpfPointAndFull) { using prg = dpf::prg::chacha20; const std::uint8_t x = 0x2a; const std::uint32_t y = 0x01020304; auto [k0, k1] = dpf::make_dpf(x, y); for (int i = 0; i < 256; ++i) { auto y0 = *dpf::eval_point(k0, static_cast(i)); auto y1 = *dpf::eval_point(k1, static_cast(i)); auto sum = dpf::reconstruct(y0, y1); EXPECT_EQ(sum, static_cast(i) == x ? y : 0u) << "i=" << i; } auto [buf0, iter0] = dpf::eval_full(k0); auto [buf1, iter1] = dpf::eval_full(k1); (void)buf0; (void)buf1; std::size_t i = 0; auto it0 = std::begin(iter0); auto it1 = std::begin(iter1); for (; it0 != std::end(iter0); ++it0, ++it1, ++i) { auto sum = dpf::reconstruct(*it0, *it1); EXPECT_EQ(sum, static_cast(i) == x ? y : 0u) << "i=" << i; } EXPECT_EQ(i, std::size_t{256}); } TEST(ChachaPrg, ReducedRoundsStillCorrect) { using prg = dpf::prg::chacha8; const std::uint8_t x = 0x11; const std::uint32_t y = 0xabcdu; auto [k0, k1] = dpf::make_dpf(x, y); for (int i = 0; i < 256; ++i) { auto sum = dpf::reconstruct( *dpf::eval_point(k0, static_cast(i)), *dpf::eval_point(k1, static_cast(i))); EXPECT_EQ(sum, static_cast(i) == x ? y : 0u) << "i=" << i; } } TEST(ChachaPrg, CounterWrapperCountsEval01) { using prg = dpf::prg::counter_wrapper; const auto before = prg::count(); simde__m128i seed = block_from_lanes(0x1111ull, 0x2222ull); auto kids = prg::eval01(seed); EXPECT_FALSE(blocks_equal(kids[0], kids[1])); EXPECT_EQ(prg::count(), before + 2u); alignas(16) simde__m128i bulk[4]; prg::eval(seed, bulk, 4, 0); EXPECT_EQ(prg::count(), before + 2u + 4u); } TEST(ChachaPrg, ExpandAndBufferedReplay) { using prg = dpf::prg::chacha20; simde__m128i seed = block_from_lanes(0x1111ull, 0x2222ull); auto t0 = prg::expand(seed, 3); auto t1 = prg::expand(seed, 3); EXPECT_EQ(dpf::reconstruct(t0, t1), 0u); auto u0 = prg::expand(seed, 0); auto u1 = prg::expand(seed, 1); EXPECT_NE(u0.raw(), u1.raw()); dpf::randomness::buffered_prg streamed(seed, 8); auto s0 = streamed.get<0>(); auto s1 = streamed.get<1>(); dpf::randomness::buffered_prg replay(seed, 8); EXPECT_EQ(replay.at<0>(0), s0); EXPECT_EQ(replay.at<1>(0), s1); EXPECT_EQ(replay.get<0>(), s0); EXPECT_NE(s0, streamed.get<0>()); } template void expect_chacha_comparison_and_interval() { const std::uint8_t alpha = 0x3C; auto [c0, c1] = dpf::make_dpf(alpha, dpf::lt(std::uint32_t{7}, std::uint32_t{1})); for (int i = 0; i < 256; ++i) { const auto x = static_cast(i); const auto got = dpf::reconstruct( dpf::eval_point(dpf::cmp, c0, x), dpf::eval_point(dpf::cmp, c1, x)); EXPECT_EQ(static_cast(got) & 0xffffffffu, x < alpha ? 7u : 1u) << "x=" << i; } const std::uint8_t r = 9, p = 2, q = 5; auto keys = dpf::make_dpf(r, dpf::ic(p, q, std::uint32_t{4}, std::uint32_t{1})); for (int i = 0; i < 256; ++i) { const auto x = static_cast(i); const auto got = dpf::reconstruct( dpf::eval_point(dpf::ic, keys.first, x), dpf::eval_point(dpf::ic, keys.second, x)); const auto w = static_cast(x - r); EXPECT_EQ(static_cast(got) & 0xffffffffu, (w >= p && w <= q) ? 4u : 1u) << "x=" << i; } } TEST(ChachaPrg, ComparisonAndIntervalKeys) { expect_chacha_comparison_and_interval(); expect_chacha_comparison_and_interval(); expect_chacha_comparison_and_interval(); }