Annotate noexcept and constexpr with HEDLEY, and add interval containment, ChaCha, and the dyadic range tables.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 20:44:07 -06:00
parent 875f09fec1
commit 0d8a5a8131
97 changed files with 9212 additions and 1159 deletions

View file

@ -40,6 +40,10 @@ add_executable(bit_array_test tests/bit_array_test.cpp)
add_executable(parallel_bit_iterable_test tests/parallel_bit_iterable_test.cpp)
add_executable(setbit_index_iterable_test tests/setbit_index_iterable_test.cpp)
add_executable(incremental_test tests/incremental_test.cpp)
add_executable(path_recipe_test tests/path_recipe_test.cpp)
add_executable(blocked_dcf_test tests/blocked_dcf_test.cpp)
target_compile_definitions(blocked_dcf_test PRIVATE LIBDPF_HAS_NLOHMANN_JSON)
target_include_directories(blocked_dcf_test PRIVATE ../thirdparty/json/include)
add_executable(geneval_test tests/geneval_test.cpp)
add_executable(stress_scenarios_test tests/stress_scenarios_test.cpp)
add_executable(incremental_json_test tests/incremental_json_test.cpp)
@ -58,14 +62,19 @@ target_compile_options(random_test PRIVATE -ftrapv)
find_package(Threads REQUIRED)
target_link_libraries(random_test Threads::Threads)
add_executable(prg_lowmc_test tests/prg_lowmc_test.cpp)
add_executable(prg_chacha_test tests/prg_chacha_test.cpp)
add_executable(secret_share_test tests/secret_share_test.cpp)
add_executable(beaver_test tests/beaver_test.cpp)
add_executable(constant_lut_test tests/constant_lut_test.cpp)
add_executable(signed_prefix_test tests/signed_prefix_test.cpp)
add_executable(easy_lut_test tests/easy_lut_test.cpp)
add_executable(dyadic_lut_test tests/dyadic_lut_test.cpp)
add_executable(nmod_test tests/nmod_test.cpp)
add_executable(principal_lut_test tests/principal_lut_test.cpp)
add_executable(range_lut_test tests/range_lut_test.cpp)
add_executable(window_lut_test tests/window_lut_test.cpp)
add_executable(offset_horner_test tests/offset_horner_test.cpp)
add_executable(corner_gaps_test tests/corner_gaps_test.cpp)
include(GoogleTest)
gtest_discover_tests(dpf_key_test)
@ -86,6 +95,8 @@ gtest_discover_tests(bit_array_test)
gtest_discover_tests(parallel_bit_iterable_test)
gtest_discover_tests(setbit_index_iterable_test)
gtest_discover_tests(incremental_test)
gtest_discover_tests(path_recipe_test)
gtest_discover_tests(blocked_dcf_test)
gtest_discover_tests(geneval_test)
gtest_discover_tests(stress_scenarios_test)
gtest_discover_tests(incremental_json_test)
@ -96,11 +107,18 @@ gtest_discover_tests(lane_blast_test)
gtest_discover_tests(context_blast_test)
gtest_discover_tests(random_test)
gtest_discover_tests(prg_lowmc_test)
gtest_discover_tests(prg_chacha_test)
gtest_discover_tests(secret_share_test)
gtest_discover_tests(beaver_test)
gtest_discover_tests(constant_lut_test)
gtest_discover_tests(signed_prefix_test)
gtest_discover_tests(easy_lut_test)
gtest_discover_tests(dyadic_lut_test)
gtest_discover_tests(nmod_test)
gtest_discover_tests(principal_lut_test)
gtest_discover_tests(range_lut_test)
gtest_discover_tests(window_lut_test)
gtest_discover_tests(offset_horner_test)
add_executable(ic_test tests/ic_test.cpp)
gtest_discover_tests(ic_test)
gtest_discover_tests(corner_gaps_test)

View file

@ -1552,3 +1552,26 @@ TEST(Beaver, RejectsBadUse)
EXPECT_THROW((void)[&] { return a.bit_mul(x, x); }(), std::invalid_argument);
(void)z;
}
TEST(Beaver, ProductExtremes)
{
const std::tuple<u64, u64, u64> cases[] = {
{0ull, 5ull, 0ull},
{7ull, 0ull, 0ull},
{~u64{0}, ~u64{0}, 1ull},
{1ull, ~u64{0}, ~u64{0}},
};
for (const auto & [a, b, want] : cases)
{
session64 s;
auto x = s.input();
auto y = s.input();
auto z = s(x * y);
Counter rng;
s.sample(rng);
s.bind(x, a, rng);
s.bind(y, b, rng);
s.evaluate();
EXPECT_EQ(s.open(z), want) << a << " * " << b;
}
}

View file

@ -0,0 +1,404 @@
#include <gtest/gtest.h>
#include "dpf.hpp"
#include "dpf/json.hpp"
#include "grotto/offset_horner.hpp"
#include "grotto/prefix_parity.hpp"
#include <array>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <vector>
namespace
{
simde__m128i g_roots[16];
int g_ri = 0;
simde__m128i take_root() { return g_roots[g_ri++]; }
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;
}
void fill(void * p, std::size_t nbytes)
{
auto * b = static_cast<unsigned char *>(p);
for (std::size_t i = 0; i < nbytes; ++i)
b[i] = static_cast<unsigned char>(n + i * 17);
n += nbytes;
}
uint8_t bit() { return static_cast<uint8_t>(n++ & 1u); }
};
void reset_tape()
{
g_ri = 0;
for (int i = 0; i < 16; ++i)
g_roots[i] = simde_mm_set_epi64x(0x11110000LL + i, 0x22220000LL + i * 3);
}
template <typename A, typename B>
auto recon(const A & a, const B & b)
{
return dpf::reconstruct(a, b);
}
template <typename K0, typename K1, typename X>
uint64_t recon_cmp(const K0 & k0, const K1 & k1, X x)
{
return recon(dpf::eval_point(dpf::cmp, k0, x),
dpf::eval_point(dpf::cmp, k1, x)) & k0.cmp().mask;
}
template <std::size_t B, typename Spec>
void expect_u8_kind(uint8_t alpha, Spec spec, uint64_t below, uint64_t at,
uint64_t above)
{
auto [k0, k1] = dpf::make_dpf(alpha, dpf::block_width<B>(std::move(spec)));
using KT = std::decay_t<decltype(k0)>;
EXPECT_GT(KT::cmp_block, 0u);
EXPECT_EQ(KT::cmp_q, 2u);
EXPECT_EQ(KT::cmp_h, 6u);
EXPECT_EQ(k0.value_cw().size(), KT::cmp_checkpoints);
EXPECT_EQ(k0.tail_cw().size(), KT::cmp_tail);
EXPECT_EQ(KT::depth, KT::cmp_h);
for (int x = 0; x < 256; ++x)
{
const auto q = static_cast<uint8_t>(x);
const uint64_t got = recon_cmp(k0, k1, q);
const uint64_t want = q < alpha ? below : (q == alpha ? at : above);
ASSERT_EQ(got, want) << "B=" << B << " x=" << x << " alpha=" << int(alpha);
}
}
template <std::size_t B>
void exhaustive_lt(uint8_t alpha, uint64_t yt, uint64_t yf)
{
expect_u8_kind<B>(alpha, dpf::lt(yt, yf), yt, yf, yf);
}
} // namespace
TEST(BlockedDcf, ExhaustiveUint8AllWidths)
{
const uint64_t yt = 9, yf = 0;
for (uint8_t alpha : {uint8_t{0}, uint8_t{1}, uint8_t{7}, uint8_t{64},
uint8_t{127}, uint8_t{200}, uint8_t{255}})
{
exhaustive_lt<1>(alpha, yt, yf);
exhaustive_lt<2>(alpha, yt, yf);
exhaustive_lt<3>(alpha, yt, yf);
exhaustive_lt<4>(alpha, yt, yf);
}
}
TEST(BlockedDcf, KindsIfFalseAndPayloadWidths)
{
const uint8_t alpha = 40;
expect_u8_kind<4>(alpha, dpf::lt(uint64_t{9}, uint64_t{2}), 9, 2, 2);
expect_u8_kind<4>(alpha, dpf::leq(uint64_t{9}, uint64_t{2}), 9, 9, 2);
expect_u8_kind<4>(alpha, dpf::gt(uint64_t{9}, uint64_t{2}), 2, 2, 9);
expect_u8_kind<4>(alpha, dpf::geq(uint64_t{9}, uint64_t{2}), 2, 9, 9);
expect_u8_kind<4>(alpha, dpf::lt(dpf::bit::one), 1, 0, 0);
expect_u8_kind<2>(alpha, dpf::lt(uint16_t{7}, uint16_t{1}), 7, 1, 1);
}
TEST(BlockedDcf, Block1MatchesFunctionNotBytes)
{
const uint8_t alpha = 30;
auto bare = dpf::make_dpf(alpha, dpf::lt(uint64_t{4}));
auto blocked = dpf::make_dpf(alpha, dpf::block_width<1>(dpf::lt(uint64_t{4})));
using Bare = std::decay_t<decltype(bare.first)>;
using Blk = std::decay_t<decltype(blocked.first)>;
EXPECT_NE(Bare::depth, Blk::depth);
EXPECT_NE(bare.first.value_cw().size(), blocked.first.value_cw().size());
for (int x = 0; x < 256; ++x)
{
const auto q = static_cast<uint8_t>(x);
EXPECT_EQ(recon_cmp(bare.first, bare.second, q),
recon_cmp(blocked.first, blocked.second, q));
}
}
TEST(BlockedDcf, DeeperOutputForcesFullTree)
{
const uint32_t alpha = 0x01020304u;
auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{11},
dpf::block_width<4>(dpf::lt_at<8>(uint64_t{5}, uint64_t{1})));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::cmp_q, 0u);
EXPECT_EQ(KT::cmp_h, 8u);
EXPECT_GT(KT::depth, KT::cmp_h);
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)), 11u);
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u),
*dpf::eval_point(k1, alpha ^ 1u)), 0u);
auto top = [](uint32_t v) { return static_cast<uint8_t>(v >> 24); };
auto cmp = [&](uint32_t q) { return recon_cmp(k0, k1, q); };
EXPECT_EQ(cmp((uint32_t{top(alpha)} - 1u) << 24), 5u);
EXPECT_EQ(cmp(alpha), 1u);
EXPECT_EQ(cmp((uint32_t{top(alpha)} + 1u) << 24), 1u);
}
TEST(BlockedDcf, ShallowerLeafKeepsTail)
{
const uint16_t alpha = 0x1234;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<4>(uint8_t{3}),
dpf::block_width<4>(dpf::lt(uint64_t{8})));
using KT = std::decay_t<decltype(k0)>;
EXPECT_EQ(KT::cmp_q, 2u);
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 4>, k0, alpha),
*dpf::eval_point(dpf::out<0, 4>, k1, alpha)), 3u);
EXPECT_EQ(recon_cmp(k0, k1, uint16_t{alpha - 1}), 8u);
EXPECT_EQ(recon_cmp(k0, k1, alpha), 0u);
}
TEST(BlockedDcf, PointIntervalFullSequenceInnerProduct)
{
const uint8_t alpha = 100;
auto [k0, k1] = dpf::make_dpf(alpha,
dpf::block_width<4>(dpf::lt(uint64_t{6}, uint64_t{1})));
using KT = std::decay_t<decltype(k0)>;
auto path0 = dpf::make_basic_path_memoizer(k0);
auto path1 = dpf::make_basic_path_memoizer(k1);
EXPECT_EQ(recon_cmp(k0, k1, uint8_t{50}), 6u);
EXPECT_EQ(dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, uint8_t{50}, path0),
dpf::eval_point(dpf::cmp, k1, uint8_t{50}, path1)) & k0.cmp().mask,
6u);
EXPECT_EQ(dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, uint8_t{150}, path0),
dpf::eval_point(dpf::cmp, k1, uint8_t{150}, path1)) & k0.cmp().mask,
1u);
auto narrow0 = dpf::make_output_buffer(dpf::cmp, k0, uint8_t{90}, uint8_t{110});
auto narrow1 = dpf::make_output_buffer(dpf::cmp, k1, uint8_t{90}, uint8_t{110});
dpf::eval_interval(dpf::cmp, k0, uint8_t{90}, uint8_t{110}, narrow0);
dpf::eval_interval(dpf::cmp, k1, uint8_t{90}, uint8_t{110}, narrow1);
for (uint8_t x = 90; x <= 110; ++x)
{
EXPECT_EQ(recon(narrow0[x - 90], narrow1[x - 90]) & k0.cmp().mask,
recon_cmp(k0, k1, x));
}
constexpr std::size_t stop = KT::cmp_depth;
dpf::detail::incr::cmp_full_interval_memo<KT, stop> memo0{21};
dpf::detail::incr::cmp_full_interval_memo<KT, stop> memo1{21};
auto again0 = dpf::make_output_buffer(dpf::cmp, k0, uint8_t{90}, uint8_t{110});
auto again1 = dpf::make_output_buffer(dpf::cmp, k1, uint8_t{90}, uint8_t{110});
dpf::eval_interval(dpf::cmp, k0, uint8_t{90}, uint8_t{110}, again0, memo0);
dpf::eval_interval(dpf::cmp, k1, uint8_t{90}, uint8_t{110}, again1, memo1);
EXPECT_EQ(recon(again0[0], again1[0]) & k0.cmp().mask, 6u);
EXPECT_EQ(recon(again0[10], again1[10]) & k0.cmp().mask, 1u);
auto full0 = dpf::eval_full(dpf::cmp, k0);
auto full1 = dpf::eval_full(dpf::cmp, k1);
ASSERT_EQ(full0.size(), 256u);
for (int x = 0; x < 256; ++x)
{
EXPECT_EQ(recon(full0[x], full1[x]) & k0.cmp().mask,
recon_cmp(k0, k1, static_cast<uint8_t>(x)));
}
std::array<uint8_t, 4> pts{{0, 99, 100, 255}};
auto seq0 = dpf::make_output_buffer(dpf::cmp, k0, pts.size());
auto seq1 = dpf::make_output_buffer(dpf::cmp, k1, pts.size());
dpf::eval_sequence(dpf::cmp, k0, pts.begin(), pts.end(), seq0, path0);
dpf::eval_sequence(dpf::cmp, k1, pts.begin(), pts.end(), seq1, path1);
for (std::size_t i = 0; i < pts.size(); ++i)
{
EXPECT_EQ(recon(seq0[i], seq1[i]) & k0.cmp().mask,
recon_cmp(k0, k1, pts[i]));
}
std::vector<uint64_t> w(21, 1);
const uint64_t dot =
dpf::eval_inner_product(dpf::cmp, k0, uint8_t{90}, uint8_t{110}, w)
+ dpf::eval_inner_product(dpf::cmp, k1, uint8_t{90}, uint8_t{110}, w);
uint64_t want = 0;
for (uint8_t x = 90; x <= 110; ++x)
want = (want + recon_cmp(k0, k1, x)) & k0.cmp().mask;
EXPECT_EQ(dot & k0.cmp().mask, want);
}
TEST(BlockedDcf, DealerMatchesDoernerShelat)
{
const uint8_t alpha = 77;
const uint8_t x0 = 3;
const uint8_t x1 = static_cast<uint8_t>(alpha ^ x0);
reset_tape();
auto dealer = dpf::make_dpf(alpha,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::block_width<4>(dpf::lt(uint64_t{15}, uint64_t{2})));
reset_tape();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
dpf::block_width<4>(dpf::lt(uint64_t{15}, uint64_t{2})));
EXPECT_EQ(std::memcmp(dealer.first.correction_words().data(),
ds.first.correction_words().data(),
dealer.first.correction_words().size()
* sizeof(dealer.first.correction_words()[0])),
0);
EXPECT_EQ(dealer.first.correction_advice(), ds.first.correction_advice());
EXPECT_EQ(dealer.first.value_cw(), ds.first.value_cw());
EXPECT_EQ(dealer.first.tail_cw(), ds.first.tail_cw());
EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last());
EXPECT_EQ(dealer.first.cmp_addend().raw(), ds.first.cmp_addend().raw());
EXPECT_EQ(dealer.second.cmp_addend().raw(), ds.second.cmp_addend().raw());
for (int x = 0; x < 256; ++x)
{
const auto q = static_cast<uint8_t>(x);
EXPECT_EQ(recon_cmp(dealer.first, dealer.second, q),
recon_cmp(ds.first, ds.second, q));
}
}
TEST(BlockedDcf, WildcardAssignMatchesConcrete)
{
const uint8_t alpha = 19;
const uint64_t yt = 42;
reset_tape();
auto wild = dpf::make_dpf(alpha,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::block_width<4>(dpf::lt(dpf::wildcard_value<uint64_t>{})));
EXPECT_FALSE(wild.first.cmp_assigned());
EXPECT_THROW(dpf::eval_point(dpf::cmp, wild.first, alpha), std::exception);
reset_tape();
auto concrete = dpf::make_dpf(alpha,
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::block_width<4>(dpf::lt(yt)));
dpf::assign_cmp(wild.first, wild.second, yt);
EXPECT_TRUE(wild.first.cmp_assigned());
EXPECT_EQ(wild.first.value_cw(), concrete.first.value_cw());
EXPECT_EQ(wild.first.tail_cw(), concrete.first.tail_cw());
EXPECT_EQ(wild.first.cw_last(), concrete.first.cw_last());
for (int x = 0; x < 256; ++x)
{
const auto q = static_cast<uint8_t>(x);
EXPECT_EQ(recon_cmp(wild.first, wild.second, q),
recon_cmp(concrete.first, concrete.second, q));
}
}
TEST(BlockedDcf, TrivialDomainEdges)
{
auto hi_gt = dpf::make_dpf(uint8_t{255},
dpf::block_width<4>(dpf::gt(uint64_t{3})));
EXPECT_EQ(hi_gt.first.cmp().trivial, dpf::cmp_trivial::always_false);
EXPECT_EQ(recon_cmp(hi_gt.first, hi_gt.second, uint8_t{0}), 0u);
EXPECT_EQ(recon_cmp(hi_gt.first, hi_gt.second, uint8_t{255}), 0u);
auto hi_leq = dpf::make_dpf(uint8_t{255},
dpf::block_width<4>(dpf::leq(uint64_t{3})));
EXPECT_EQ(hi_leq.first.cmp().trivial, dpf::cmp_trivial::always_true);
EXPECT_EQ(recon_cmp(hi_leq.first, hi_leq.second, uint8_t{0}), 3u);
EXPECT_EQ(recon_cmp(hi_leq.first, hi_leq.second, uint8_t{255}), 3u);
}
TEST(BlockedDcf, JsonRoundTrip)
{
const uint8_t alpha = 12;
auto [k0, k1] = dpf::make_dpf(alpha,
dpf::block_width<4>(dpf::gt(uint64_t{7}, uint64_t{1})));
using KT = typename std::decay_t<decltype(k0)>::key_type;
const std::string s0 = dpf::json::to_json(k0.key());
const std::string s1 = dpf::json::to_json(k1.key());
EXPECT_NE(s0.find("block_width"), std::string::npos);
EXPECT_NE(s0.find("tail_cw"), std::string::npos);
auto r0 = dpf::json::from_json<KT>(s0);
auto r1 = dpf::json::from_json<KT>(s1);
EXPECT_EQ(r0.value_cw(), k0.value_cw());
EXPECT_EQ(r0.tail_cw(), k0.tail_cw());
EXPECT_EQ(r0.cmp().block_width, 4);
EXPECT_EQ(r0.cmp().tail_bits, static_cast<int>(KT::cmp_q));
const uint64_t mask = k0.cmp().mask;
for (int x = 0; x < 256; ++x)
{
const auto q = static_cast<uint8_t>(x);
const uint64_t got =
(dpf::eval_point(dpf::cmp, r0, q) + dpf::eval_point(dpf::cmp, r1, q))
& mask;
EXPECT_EQ(got, recon_cmp(k0, k1, q));
}
}
TEST(BlockedDcf, GenevalOpensCheckpointWords)
{
const uint8_t alpha = 20;
const uint8_t x0 = 1;
const uint8_t x1 = static_cast<uint8_t>(alpha ^ x0);
reset_tape();
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
std::array<uint8_t, 3> ends{{0, 20, 21}};
auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng,
dpf::block_width<4>(dpf::lt(uint64_t{5})));
using KT = std::decay_t<decltype(dpf::make_dpf(alpha,
dpf::block_width<4>(dpf::lt(uint64_t{5}))).first)>;
EXPECT_EQ(g.value_cw.size(), KT::cmp_checkpoints);
EXPECT_EQ(g.tail_cw.size(), KT::cmp_tail);
EXPECT_EQ(g.correction_words.size(), KT::depth);
EXPECT_EQ((g.party0[0] + g.party1[0]) & g.mask, 5u);
EXPECT_EQ((g.party0[1] + g.party1[1]) & g.mask, 0u);
EXPECT_EQ((g.party0[2] + g.party1[2]) & g.mask, 0u);
}
TEST(BlockedDcf, GrottoPrefixSegmentAndHorner)
{
const uint16_t alpha = 1000;
std::array<uint16_t, 4> ends{{0, 500, 1000, 4000}};
auto bare = dpf::make_dpf(alpha, dpf::gt(uint64_t{1}));
auto blk = dpf::make_dpf(alpha, dpf::block_width<4>(dpf::gt(uint64_t{1})));
const auto b0 = grotto::signed_prefix_parities(bare.first, ends);
const auto b1 = grotto::signed_prefix_parities(bare.second, ends);
const auto k0 = grotto::signed_prefix_parities(blk.first, ends);
const auto k1 = grotto::signed_prefix_parities(blk.second, ends);
const uint64_t maskp = bare.first.cmp().mask;
for (std::size_t i = 0; i < ends.size(); ++i)
EXPECT_EQ((b0[i] + b1[i]) & maskp, (k0[i] + k1[i]) & maskp);
const auto s0 = grotto::signed_segment_parities(bare.first, ends);
const auto s1 = grotto::signed_segment_parities(bare.second, ends);
const auto t0 = grotto::signed_segment_parities(blk.first, ends);
const auto t1 = grotto::signed_segment_parities(blk.second, ends);
for (std::size_t i = 0; i < ends.size(); ++i)
EXPECT_EQ((s0[i] + s1[i]) & maskp, (t0[i] + t1[i]) & maskp);
const uint16_t center = 30;
auto mat = grotto::make_offset_horner_keys<uint16_t, 1>(center);
uint64_t payload[2];
payload[0] = 1;
payload[1] = center;
using bare_pair = decltype(dpf::make_dpf(center, dpf::gt(uint64_t{0})));
using pair = decltype(dpf::make_dpf(center,
dpf::block_width<4>(dpf::gt(uint64_t{0}))));
std::vector<bare_pair> bare_keys{
dpf::make_dpf(center, dpf::gt(payload[0])),
dpf::make_dpf(center, dpf::gt(payload[1]))};
std::vector<pair> keys{
dpf::make_dpf(center, dpf::block_width<4>(dpf::gt(payload[0]))),
dpf::make_dpf(center, dpf::block_width<4>(dpf::gt(payload[1])))};
std::vector<uint16_t> knots{0, 10, 40};
std::vector<std::array<uint64_t, 2>> coeff{
{1, 0},
{2, 3},
{4, 1}};
const uint16_t eta = 0;
const auto b0s = grotto::offset_horner_coefficient_share<0, 1>(
bare_keys, mat.wrap_share, knots, coeff, eta);
const auto b1s = grotto::offset_horner_coefficient_share<1, 1>(
bare_keys, mat.wrap_share, knots, coeff, eta);
const auto c0 = grotto::offset_horner_coefficient_share<0, 1>(
keys, mat.wrap_share, knots, coeff, eta);
const auto c1 = grotto::offset_horner_coefficient_share<1, 1>(
keys, mat.wrap_share, knots, coeff, eta);
for (std::size_t m = 0; m < 2; ++m)
EXPECT_EQ(b0s[m] + b1s[m], c0[m] + c1[m]);
}

View file

@ -0,0 +1,390 @@
#include <gtest/gtest.h>
#include "dpf.hpp"
#include "grotto/fixedpoint.hpp"
#include "grotto/fixedpoint_mul.hpp"
#include "grotto/principal_lut.hpp"
#include <cstdint>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <tuple>
#include <vector>
namespace
{
template <typename T>
void expect_same(const T & got, const T & point)
{
EXPECT_EQ(std::memcmp(&got, &point, sizeof(T)), 0);
}
template <typename In, typename Out>
void expect_wrapping_interval(In from, In to, In alpha, Out y, std::size_t leaves)
{
auto [k0, k1] = dpf::make_dpf(alpha, y);
using key_t = std::decay_t<decltype(k0)>;
EXPECT_EQ((dpf::utils::get_nodes_in_interval<key_t>(from, to)), leaves);
auto [buf0, it0] = dpf::eval_interval(k0, from, to);
auto [buf1, it1] = dpf::eval_interval(k1, from, to);
auto a = std::begin(it0);
auto b = std::begin(it1);
const auto a_end = std::end(it0);
const auto b_end = std::end(it1);
std::size_t n = 0;
In cur = from;
for (;;)
{
ASSERT_NE(a, a_end);
ASSERT_NE(b, b_end);
auto p0 = *dpf::eval_point(k0, cur);
auto p1 = *dpf::eval_point(k1, cur);
expect_same(*a, p0);
expect_same(*b, p1);
++a;
++b;
++n;
if (cur == to)
break;
cur = static_cast<In>(static_cast<std::uint64_t>(cur) + 1u);
ASSERT_LT(n, std::size_t{1} << 20);
}
EXPECT_EQ(a, a_end);
EXPECT_EQ(b, b_end);
const std::uint64_t width = std::uint64_t{1} << dpf::utils::bitlength_of_v<In>;
const std::uint64_t masked = (static_cast<std::uint64_t>(to)
- static_cast<std::uint64_t>(from)) & (width - 1);
EXPECT_EQ(n, masked + 1);
}
} // namespace
TEST(CornerGaps, SameLeafWrapUint8Uint32)
{
expect_wrapping_interval<uint8_t, uint32_t>(10, 9, 40, 0x11111111u, 65);
}
TEST(CornerGaps, AdjacentLeafWrapUint8Uint32)
{
expect_wrapping_interval<uint8_t, uint32_t>(8, 7, 40, 0x22222222u, 64);
}
TEST(CornerGaps, LongWrapStillMatchesPoint)
{
expect_wrapping_interval<uint8_t, uint32_t>(200, 10, 3, 0x33333333u, 17);
auto [k0, k1] = dpf::make_dpf(uint8_t{3}, uint32_t{0x33333333u});
using key_t = std::decay_t<decltype(k0)>;
auto memo0 = dpf::make_full_tree_interval_memoizer<key_t>(uint8_t{200}, uint8_t{10});
auto memo1 = dpf::make_full_tree_interval_memoizer<key_t>(uint8_t{200}, uint8_t{10});
auto [buf0, it0] = dpf::eval_interval(k0, uint8_t{200}, uint8_t{10}, std::move(memo0));
auto [buf1, it1] = dpf::eval_interval(k1, uint8_t{200}, uint8_t{10}, std::move(memo1));
auto p0 = *dpf::eval_point(k0, uint8_t{200});
auto p1 = *dpf::eval_point(k1, uint8_t{200});
expect_same(*std::begin(it0), p0);
expect_same(*std::begin(it1), p1);
EXPECT_EQ(dpf::reconstruct(*std::begin(it0), *std::begin(it1)), 0u);
auto back0 = std::begin(it0);
auto back1 = std::begin(it1);
for (int i = 0; i < 66; ++i, ++back0, ++back1) {}
auto q0 = *dpf::eval_point(k0, uint8_t{10});
auto q1 = *dpf::eval_point(k1, uint8_t{10});
expect_same(*back0, q0);
expect_same(*back1, q1);
}
TEST(CornerGaps, OneOutputPerLeafWrap)
{
expect_wrapping_interval<uint16_t, simde_uint128>(5, 4, 9, simde_uint128{7}, 65536);
}
TEST(CornerGaps, SaturatedUint64LeafCount)
{
using in_t = uint64_t;
using out_t = simde_uint128;
using key_t = dpf::utils::dpf_type_t<dpf::prg::aes128, dpf::prg::aes128, in_t, out_t>;
EXPECT_EQ((dpf::utils::get_nodes_in_interval<key_t>(in_t{1}, ~in_t{0})),
std::numeric_limits<std::size_t>::max());
EXPECT_THROW((dpf::utils::get_nodes_in_interval<key_t>(in_t{0}, ~in_t{0})),
std::length_error);
EXPECT_THROW((dpf::utils::get_nodes_in_interval<key_t>(in_t{5}, in_t{4})),
std::length_error);
}
TEST(CornerGaps, MemoizerRejectsALargerInterval)
{
auto [k0, k1] = dpf::make_dpf(uint8_t{4}, uint32_t{1});
using key_t = std::decay_t<decltype(k0)>;
auto memo = dpf::make_basic_interval_memoizer<key_t>(uint8_t{0}, uint8_t{10});
auto buf = dpf::make_output_buffer_for_interval<key_t>(uint8_t{0}, uint8_t{100});
EXPECT_THROW(dpf::eval_interval(k0, uint8_t{0}, uint8_t{100}, buf, memo),
std::length_error);
(void)k1;
}
TEST(CornerGaps, OddStartInteriorTail)
{
auto [k0, k1] = dpf::make_dpf(uint16_t{3}, simde_uint128{11});
for (uint16_t to : {uint16_t{9}, uint16_t{10}})
{
auto [b0, it0] = dpf::eval_interval(k0, uint16_t{1}, to);
auto [b1, it1] = dpf::eval_interval(k1, uint16_t{1}, to);
auto a = std::begin(it0);
auto b = std::begin(it1);
for (uint16_t q = 1; q <= to; ++q, ++a, ++b)
{
auto p0 = *dpf::eval_point(k0, q);
auto p1 = *dpf::eval_point(k1, q);
expect_same(*a, p0);
expect_same(*b, p1);
}
EXPECT_EQ(a, std::end(it0));
EXPECT_EQ(b, std::end(it1));
}
}
TEST(CornerGaps, PathMemoizerExtremes)
{
auto [k0, k1] = dpf::make_dpf(uint16_t{0x0102}, uint16_t{9});
using key_t = std::decay_t<decltype(k0)>;
dpf::basic_path_memoizer<key_t> m0;
dpf::basic_path_memoizer<key_t> m1;
const uint16_t queries[] = {0, 0x8000, 1, 0};
for (uint16_t q : queries)
{
auto a = *dpf::eval_point(k0, q, m0);
auto b = *dpf::eval_point(k1, q, m1);
auto fa = *dpf::eval_point(k0, q);
auto fb = *dpf::eval_point(k1, q);
EXPECT_EQ(a, fa) << q;
EXPECT_EQ(b, fb) << q;
}
}
TEST(CornerGaps, DepthOneBitInterval)
{
for (uint8_t alpha : {uint8_t{0}, uint8_t{127}, uint8_t{128}, uint8_t{255}})
{
auto [k0, k1] = dpf::make_dpf(alpha, dpf::bit::one);
using key_t = std::decay_t<decltype(k0)>;
EXPECT_EQ(key_t::depth, 1u);
auto check = [&](uint8_t from, uint8_t to) {
auto [b0, it0] = dpf::eval_interval(k0, from, to);
auto [b1, it1] = dpf::eval_interval(k1, from, to);
auto a = std::begin(it0);
auto b = std::begin(it1);
for (uint8_t q = from; ; )
{
const bool on = q == alpha;
const bool bit = static_cast<bool>(*a) != static_cast<bool>(*b);
EXPECT_EQ(bit, on) << int(q);
++a;
++b;
if (q == to)
break;
++q;
}
EXPECT_EQ(a, std::end(it0));
EXPECT_EQ(std::end(it1), b);
};
check(alpha, alpha);
check(127, 128);
}
}
TEST(CornerGaps, EmptyAndDuplicateSequence)
{
auto [k0, k1] = dpf::make_dpf(uint8_t{40}, uint8_t{7});
std::vector<uint8_t> empty;
auto [eb0, eit0] = dpf::eval_sequence(k0, empty.begin(), empty.end());
auto [eb1, eit1] = dpf::eval_sequence(k1, empty.begin(), empty.end());
EXPECT_EQ(std::begin(eit0), std::end(eit0));
EXPECT_EQ(std::begin(eit1), std::end(eit1));
const std::vector<uint8_t> seq{40, 40, 41};
auto [b0, it0] = dpf::eval_sequence(k0, seq.begin(), seq.end());
auto [b1, it1] = dpf::eval_sequence(k1, seq.begin(), seq.end());
auto a = std::begin(it0);
auto b = std::begin(it1);
const uint8_t want[] = {7, 7, 0};
for (int i = 0; i < 3; ++i, ++a, ++b)
EXPECT_EQ(dpf::reconstruct(*a, *b), want[i]) << i;
EXPECT_EQ(a, std::end(it0));
}
TEST(CornerGaps, IncrementalAdjacentLaneWrap)
{
const uint16_t alpha = 0x00ab;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::at<8>(uint32_t{0xabcdu}));
auto [b0, it0] = dpf::eval_interval(dpf::out<0, 8>, k0, uint8_t{8}, uint8_t{7});
auto [b1, it1] = dpf::eval_interval(dpf::out<0, 8>, k1, uint8_t{8}, uint8_t{7});
auto a = std::begin(it0);
auto b = std::begin(it1);
std::size_t n = 0;
for (int lane = 8; ; )
{
const uint16_t query = static_cast<uint16_t>(static_cast<uint16_t>(lane) << 8);
auto p0 = *dpf::eval_point(dpf::out<0, 8>, k0, query);
auto p1 = *dpf::eval_point(dpf::out<0, 8>, k1, query);
EXPECT_EQ(*a, p0) << lane;
EXPECT_EQ(*b, p1) << lane;
++a;
++b;
++n;
if (lane == 7)
break;
lane = (lane + 1) & 255;
}
EXPECT_EQ(n, 256u);
EXPECT_EQ(a, std::end(it0));
}
TEST(CornerGaps, ModintWideLiteralAndLimbShift)
{
using namespace dpf::literals;
EXPECT_EQ(1_u129, dpf::modint<129>{1});
const uint256_t bit128{1, 0};
const auto wide = 340282366920938463463374607431768211456_u129;
EXPECT_EQ(wide, (dpf::modint<129>{bit128}));
const bool shift10 = (dpf::modint<10>{1} << 10) == dpf::modint<10>{0};
const bool shift16 = (dpf::modint<10>{1} << 16) == dpf::modint<10>{0};
const bool shift64 = (dpf::modint<64>{1} << 64) == dpf::modint<64>{0};
const bool shift128 = (dpf::modint<65>{1} << 128) == dpf::modint<65>{0};
const bool rshift10 = (dpf::modint<10>{5} >> 10) == dpf::modint<10>{0};
const bool rshift64 = (dpf::modint<64>{1} >> 64) == dpf::modint<64>{0};
EXPECT_TRUE(shift10 && shift16 && shift64 && shift128 && rshift10 && rshift64);
dpf::modint<10> assigned{1};
assigned <<= 16;
EXPECT_TRUE(assigned == dpf::modint<10>{0});
assigned = dpf::modint<10>{7};
assigned >>= 10;
EXPECT_TRUE(assigned == dpf::modint<10>{0});
}
TEST(CornerGaps, SetbitEmptyAndSingleAndNarrowLeaf)
{
dpf::dynamic_bit_array<> zeros(128);
using iter_t = decltype(zeros.begin());
dpf::subinterval_iterable<iter_t> all(zeros.begin(), zeros.size(),
0, zeros.size() - 1, 0, 0);
auto none = dpf::indices_set_in(all);
EXPECT_EQ(none.begin(), none.end());
zeros[0] = true;
dpf::subinterval_iterable<iter_t> one(zeros.begin(), zeros.size(),
0, zeros.size() - 1, 0, 0);
auto set = dpf::indices_set_in(one);
auto it = set.begin();
ASSERT_NE(it, set.end());
EXPECT_EQ(*it, 0u);
++it;
EXPECT_EQ(it, set.end());
dpf::dynamic_bit_array<> narrow(128);
narrow[0] = true;
dpf::subinterval_iterable<iter_t> clipped(narrow.begin(), narrow.size(),
0, 1, 0, 2);
auto clipped_set = dpf::indices_set_in(clipped);
auto cit = clipped_set.begin();
ASSERT_NE(cit, clipped_set.end());
EXPECT_EQ(*cit, 0u);
++cit;
EXPECT_EQ(cit, clipped_set.end());
}
TEST(CornerGaps, EmptyRotationIsEmptyAndZeroIsIdentity)
{
std::vector<int> empty;
dpf::rotation_iterable<std::vector<int>::iterator> none(
empty.begin(), empty.end(), 1);
EXPECT_EQ(none.begin(), none.end());
std::vector<int> values{1, 2, 3};
dpf::rotation_iterable<std::vector<int>::iterator> id(
values.begin(), values.end(), 0);
std::vector<int> got;
for (auto it = id.begin(); it != id.end(); ++it)
got.push_back(*it);
EXPECT_EQ(got, values);
dpf::rotation_iterable<std::vector<int>::iterator> rot(
values.begin(), values.end(), 1);
got.clear();
for (auto it = rot.begin(); it != rot.end(); ++it)
got.push_back(*it);
EXPECT_EQ(got, (std::vector<int>{2, 3, 1}));
}
TEST(CornerGaps, Party1NegatesSignedMinimum)
{
using sub = dpf::subtractive_share<int32_t, 1>;
using add0 = dpf::additive_share<int32_t, 0>;
const auto raw = std::numeric_limits<int32_t>::min();
const auto party1 = sub::from_raw(raw).as_additive();
EXPECT_EQ(party1.raw(), raw);
EXPECT_EQ(dpf::reconstruct(add0::from_raw(0), party1), raw);
EXPECT_EQ((-sub::from_raw(raw)).raw(), raw);
}
TEST(CornerGaps, FixedMulFloorsAndPrecisionCastDiffersFromLogicalShift)
{
using q4 = grotto::fixedpoint<4, std::int32_t>;
const auto prod = grotto::fixed_mul<8, 4>(q4::from_raw(-3), q4::from_raw(1));
EXPECT_EQ(prod.integral_representation(), -1);
const auto neg_pair = grotto::fixed_mul<8, 4>(q4::from_raw(-3), q4::from_raw(-2));
EXPECT_EQ(neg_pair.integral_representation(), 0);
auto quarter = q4::from_raw(-12);
const auto casted = grotto::precision_cast<0>(quarter);
EXPECT_EQ(casted.integral_representation(), -1);
quarter >>= 4;
EXPECT_EQ(quarter.integral_representation(), 268435455);
}
TEST(CornerGaps, Int64MinFactorIsDefined)
{
using grotto::principal_detail::w_from_i128;
using grotto::principal_detail::w_mul_i64;
using grotto::principal_detail::w_mul_u64;
using grotto::principal_detail::w_neg;
const auto value = w_from_i128(3);
const auto got = w_mul_i64(value, std::numeric_limits<std::int64_t>::min());
const auto want = w_neg(w_mul_u64(value, std::uint64_t{1} << 63));
EXPECT_EQ(got.lo, want.lo);
EXPECT_EQ(got.hi, want.hi);
}
TEST(CornerGaps, PrgRejectsUint32Seam)
{
alignas(64) simde__m128i seed = simde_mm_set_epi64x(1, 2);
alignas(64) simde__m128i out[4];
const auto pos = static_cast<psnip_uint32_t>(UINT32_MAX - 1u);
EXPECT_THROW(dpf::prg::aes128::eval(seed, out, 4, pos), std::invalid_argument);
EXPECT_THROW(dpf::prg::lowmc128::eval(seed, out, 4, pos), std::invalid_argument);
const auto ok = static_cast<psnip_uint32_t>(UINT32_MAX - 3u);
dpf::prg::aes128::eval(seed, out, 4, ok);
for (psnip_uint32_t i = 0; i < 4; ++i)
{
const auto one = dpf::prg::aes128::eval(seed, ok + i);
EXPECT_EQ(std::memcmp(&out[i], &one, sizeof(one)), 0) << i;
}
dpf::prg::lowmc128::eval(seed, out, 4, ok);
for (psnip_uint32_t i = 0; i < 4; ++i)
{
const auto one = dpf::prg::lowmc128::eval(seed, ok + i);
EXPECT_EQ(std::memcmp(&out[i], &one, sizeof(one)), 0) << i;
}
EXPECT_THROW((dpf::randomness::detail::lane_codec<dpf::prg::aes128, simde__m128i>::fill(
seed, static_cast<std::uint64_t>(UINT32_MAX) - 1u, out, 4)),
std::invalid_argument);
}

View file

@ -0,0 +1,215 @@
#include <gtest/gtest.h>
#include "grotto/dyadic_lut.hpp"
#include <cstdint>
#include <limits>
namespace
{
int ref_clz(std::int16_t raw)
{
const auto bits = static_cast<std::uint16_t>(raw);
if (bits == 0)
return 16;
return __builtin_clz(static_cast<unsigned>(bits)) - (32 - 16);
}
int ref_clrsb(std::int16_t raw)
{
const auto bits = static_cast<std::uint16_t>(raw);
const bool neg = (bits & 0x8000u) != 0;
int count = 0;
for (int b = 14; b >= 0; --b)
{
const bool bit = ((bits >> b) & 1u) != 0;
if (bit != neg)
break;
++count;
}
return count;
}
int ref_ilogb(std::int16_t raw, unsigned k)
{
if (raw == 0)
return 0;
unsigned mag;
if (raw == std::numeric_limits<std::int16_t>::min())
mag = 1u << 15;
else
mag = static_cast<unsigned>(raw < 0 ? -raw : raw);
int log = 0;
while (mag > 1)
{
mag >>= 1;
++log;
}
return log - static_cast<int>(k);
}
int ref_ilog10(std::int64_t raw, unsigned k)
{
if (raw == 0)
return 0;
using u128 = unsigned __int128;
u128 mag;
if (raw == std::numeric_limits<std::int64_t>::min())
mag = u128{1} << 63;
else
mag = static_cast<u128>(raw < 0 ? -raw : raw);
int e = -static_cast<int>(k) - 2;
for (;;)
{
bool ge = false;
if (e >= 0)
{
u128 decade = 1;
for (int i = 0; i < e; ++i)
decade *= 10;
ge = mag >= (decade << k);
}
else
{
u128 decade = 1;
for (int i = 0; i < -e; ++i)
decade *= 10;
const u128 scale = u128{1} << k;
u128 threshold = scale / decade;
if (scale % decade != 0)
++threshold;
ge = mag >= threshold;
}
if (!ge)
return e - 1;
++e;
if (e > 40)
return 40;
}
}
std::int64_t enc(std::int64_t units, unsigned k)
{
return units << k;
}
} // namespace
TEST(DyadicLut, SignBundleInt16)
{
const auto positive = grotto::make_positive_lut<std::int16_t>();
const auto negative = grotto::make_negative_lut<std::int16_t>();
const auto nonnegative = grotto::make_nonnegative_lut<std::int16_t>();
const auto nonpositive = grotto::make_nonpositive_lut<std::int16_t>();
const auto zero = grotto::make_zero_lut<std::int16_t>();
const auto nonzero = grotto::make_nonzero_lut<std::int16_t>();
const auto signum = grotto::make_signum_lut<std::int16_t>();
EXPECT_EQ(positive.parts(), 2u);
EXPECT_EQ(signum.parts(), 3u);
EXPECT_EQ(zero.parts(), 3u);
for (std::int32_t raw = -32768; raw <= 32767; ++raw)
{
const auto x = static_cast<std::int16_t>(raw);
EXPECT_EQ(positive(x), x > 0 ? 1 : 0);
EXPECT_EQ(negative(x), x < 0 ? 1 : 0);
EXPECT_EQ(nonnegative(x), x >= 0 ? 1 : 0);
EXPECT_EQ(nonpositive(x), x <= 0 ? 1 : 0);
EXPECT_EQ(zero(x), x == 0 ? 1 : 0);
EXPECT_EQ(nonzero(x), x != 0 ? 1 : 0);
const int sgn = x < 0 ? -1 : (x > 0 ? 1 : 0);
EXPECT_EQ(signum(x), sgn);
}
const auto scaled = grotto::make_positive_lut<std::int16_t>(4);
EXPECT_EQ(scaled(std::int16_t{1}), 16);
EXPECT_EQ(scaled(std::int16_t{0}), 0);
EXPECT_EQ(grotto::make_signum_lut<std::int16_t>(4)(std::int16_t{-3}), -16);
}
TEST(DyadicLut, ClzAndClrsbInt16)
{
const auto clz = grotto::make_clz_lut<std::int16_t>();
const auto clrsb = grotto::make_clrsb_lut<std::int16_t>();
EXPECT_EQ(clz.parts(), 17u);
for (std::int32_t raw = -32768; raw <= 32767; ++raw)
{
const auto x = static_cast<std::int16_t>(raw);
EXPECT_EQ(clz(x), ref_clz(x)) << raw;
EXPECT_EQ(clrsb(x), ref_clrsb(x)) << raw;
}
const auto scaled = grotto::make_clz_lut<std::int16_t>(3);
EXPECT_EQ(scaled(std::int16_t{1}), ref_clz(1) << 3);
}
TEST(DyadicLut, IntegerLogsInt16)
{
for (unsigned k : {0u, 4u})
{
const auto lg = grotto::make_ilogb_lut<std::int16_t>(k);
const auto log10 = grotto::make_ilog10_lut<std::int16_t>(k);
EXPECT_EQ(lg(std::int16_t{0}), grotto::ilog_of_zero);
EXPECT_EQ(log10(std::int16_t{0}), grotto::ilog_of_zero);
for (std::int32_t raw = -32768; raw <= 32767; ++raw)
{
if (raw == 0)
continue;
const auto x = static_cast<std::int16_t>(raw);
EXPECT_EQ(lg(x), enc(ref_ilogb(x, k), k)) << raw << " k=" << k;
EXPECT_EQ(log10(x), enc(ref_ilog10(raw, k), k)) << raw << " k=" << k;
}
}
EXPECT_EQ(grotto::make_ilogb_lut<std::int16_t>()(std::int16_t{1}), 0);
EXPECT_EQ(grotto::make_ilogb_lut<std::int16_t>()(std::int16_t{2}), 1);
EXPECT_EQ(grotto::make_ilog10_lut<std::int16_t>()(std::int16_t{9}), 0);
EXPECT_EQ(grotto::make_ilog10_lut<std::int16_t>()(std::int16_t{10}), 1);
EXPECT_EQ(grotto::make_ilog10_lut<std::int16_t>(4)(std::int16_t{1}), enc(-2, 4));
}
TEST(DyadicLut, MostSignificantBits)
{
for (unsigned i = 0; i < grotto::msb_bit_limit && i < 16; ++i)
{
const auto lut = grotto::make_msb_lut<std::int16_t>(i);
EXPECT_EQ(lut.parts(), std::size_t{1} << (i + 1)) << i;
const int shift = 15 - static_cast<int>(i);
for (std::int32_t raw = -32768; raw <= 32767; ++raw)
{
const auto x = static_cast<std::int16_t>(raw);
const int bit = (static_cast<std::uint16_t>(x) >> shift) & 1;
EXPECT_EQ(lut(x), bit) << raw << " i=" << i;
}
}
EXPECT_THROW(grotto::make_msb_lut<std::int16_t>(grotto::msb_bit_limit),
std::invalid_argument);
const auto scaled = grotto::make_msb_lut<std::int16_t>(0, 4);
EXPECT_EQ(scaled(std::int16_t{-1}), 16);
EXPECT_EQ(scaled(std::int16_t{1}), 0);
}
TEST(DyadicLut, Int64Edges)
{
const auto clz = grotto::make_clz_lut<std::int64_t>();
EXPECT_EQ(clz(std::int64_t{0}), 64);
EXPECT_EQ(clz(std::int64_t{-1}), 0);
EXPECT_EQ(clz(std::int64_t{1}), 63);
EXPECT_EQ(clz(std::numeric_limits<std::int64_t>::min()), 0);
EXPECT_EQ(clz(std::int64_t{1} << 62), 1);
const auto clrsb = grotto::make_clrsb_lut<std::int64_t>();
EXPECT_EQ(clrsb(std::int64_t{0}), 63);
EXPECT_EQ(clrsb(std::int64_t{-1}), 63);
EXPECT_EQ(clrsb(std::int64_t{1}), 62);
EXPECT_EQ(clrsb(std::numeric_limits<std::int64_t>::min()), 0);
const auto lg = grotto::make_ilogb_lut<std::int64_t>(16);
EXPECT_EQ(lg(std::int64_t{1} << 16), 0);
EXPECT_EQ(lg(std::int64_t{1} << 17), enc(1, 16));
EXPECT_EQ(lg(std::numeric_limits<std::int64_t>::min()), enc(63 - 16, 16));
const auto bit = grotto::make_msb_lut<std::int64_t>(0);
EXPECT_EQ(bit.parts(), 2u);
EXPECT_EQ(bit(std::int64_t{-5}), 1);
EXPECT_EQ(bit(std::int64_t{5}), 0);
}

View file

@ -362,110 +362,87 @@ TEST(Geneval, EmptySequence)
EXPECT_TRUE(g.correction_words.empty());
}
TEST(Geneval, WildcardPointMatchesShiftedEval)
TEST(Geneval, ArithPointMatchesDealerAtQuery)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t x = 0x1357;
in_t x0 = 0x0100;
in_t x1 = static_cast<in_t>(x - x0);
in_t alpha = 0xabcd;
in_t query = 0x2000;
out_t y = 99;
const in_t delta = static_cast<in_t>(alpha - x);
const in_t shifted = static_cast<in_t>(query + delta);
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto keys = dpf::make_dpf(x, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(dpf::wildcard_input, x0, x1, query, rng<in_t>(),
[&] { return alpha; }, y);
auto g = dpf::geneval_point(dpf::arith_input, x0, x1, query, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(shifted), key_t::depth),
lcp_bits(leaf_of<key_t>(x), leaf_of<key_t>(query), key_t::depth),
key_t::depth);
EXPECT_EQ(g.live_levels, live);
EXPECT_LT(live, key_t::depth);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, g.leaf_live, &g.leaf, sizeof(g.leaf));
auto e0 = ev(keys.first, shifted);
auto e1 = ev(keys.second, shifted);
auto e0 = ev(keys.first, query);
auto e1 = ev(keys.second, query);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), recon(e0, e1));
dpf::wildcard_value<in_t> slot{alpha};
reset_roots();
auto wild = dpf::make_dpf(slot, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto s0 = wild.first.offset_x.compute_and_get_share(x0);
auto s1 = wild.second.offset_x.compute_and_get_share(x1);
wild.first.offset_x.reconstruct(s1);
wild.second.offset_x.reconstruct(s0);
auto w0 = ev(wild.first, query);
auto w1 = ev(wild.second, query);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), recon(w0, w1));
expect_prefix_words(wild.first, g.correction_words, g.correction_advice,
g.live_levels, false, nullptr, 0);
}
TEST(Geneval, WildcardPointOnSecretIsFullKey)
TEST(Geneval, ArithPointOnSecretIsFullKey)
{
using in_t = uint16_t;
using out_t = uint16_t;
in_t x = 0x42;
in_t x0 = 0x10;
in_t x1 = static_cast<in_t>(x - x0);
in_t alpha = 0x1111;
out_t y = 8;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto keys = dpf::make_dpf(x, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(dpf::wildcard_input, x0, x1, x, rng<in_t>(),
[&] { return alpha; }, y);
auto g = dpf::geneval_point(dpf::arith_input, x0, x1, x, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
auto e0 = ev(keys.first, alpha);
auto e1 = ev(keys.second, alpha);
auto e0 = ev(keys.first, x);
auto e1 = ev(keys.second, x);
EXPECT_EQ(g.party0[0], e0);
EXPECT_EQ(g.party1[0], e1);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), y);
}
TEST(Geneval, WildcardIntervalAndSequence)
TEST(Geneval, ArithIntervalAndSequence)
{
using in_t = uint8_t;
using out_t = uint8_t;
in_t x = 40;
in_t x0 = 7;
in_t x1 = static_cast<in_t>(x - x0);
in_t alpha = 200;
out_t y = 3;
const in_t delta = static_cast<in_t>(alpha - x);
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto keys = dpf::make_dpf(x, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto iv = dpf::geneval_interval(dpf::wildcard_input, x0, x1, in_t{10}, in_t{20},
rng<in_t>(), [&] { return alpha; }, y);
auto iv = dpf::geneval_interval(dpf::arith_input, x0, x1, in_t{10}, in_t{20},
rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
ASSERT_EQ(iv.party0.size(), 11u);
for (in_t q = 10; q <= 20; ++q)
{
const in_t shifted = static_cast<in_t>(q + delta);
const std::size_t i = static_cast<std::size_t>(q - 10);
auto e0 = ev(keys.first, shifted);
auto e1 = ev(keys.second, shifted);
auto e0 = ev(keys.first, q);
auto e1 = ev(keys.second, q);
EXPECT_EQ(recon(iv.party0[i], iv.party1[i]), recon(e0, e1)) << int(q);
}
const in_t shifted_from = static_cast<in_t>(10 + delta);
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(shifted_from), key_t::depth),
lcp_bits(leaf_of<key_t>(x), leaf_of<key_t>(in_t{10}), key_t::depth),
key_t::depth);
EXPECT_EQ(iv.live_levels, live);
expect_prefix_words(keys.first, iv.correction_words, iv.correction_advice,
@ -473,8 +450,8 @@ TEST(Geneval, WildcardIntervalAndSequence)
const in_t seq[] = {1, x, 255, 2};
reset_roots();
auto sq = dpf::geneval_sequence(dpf::wildcard_input, x0, x1,
std::begin(seq), std::end(seq), rng<in_t>(), [&] { return alpha; }, y);
auto sq = dpf::geneval_sequence(dpf::arith_input, x0, x1,
std::begin(seq), std::end(seq), rng<in_t>(), y);
ASSERT_EQ(sq.party0.size(), 4u);
EXPECT_TRUE(sq.leaf_live);
EXPECT_EQ(sq.live_levels, key_t::depth);
@ -482,31 +459,27 @@ TEST(Geneval, WildcardIntervalAndSequence)
sq.live_levels, true, &sq.leaf, sizeof(sq.leaf));
for (std::size_t i = 0; i < 4; ++i)
{
const in_t shifted = static_cast<in_t>(seq[i] + delta);
auto e0 = ev(keys.first, shifted);
auto e1 = ev(keys.second, shifted);
auto e0 = ev(keys.first, seq[i]);
auto e1 = ev(keys.second, seq[i]);
EXPECT_EQ(sq.party0[i], e0);
EXPECT_EQ(sq.party1[i], e1);
EXPECT_EQ(recon(sq.party0[i], sq.party1[i]), seq[i] == x ? y : out_t{0});
}
}
TEST(Geneval, WildcardFullRotates)
TEST(Geneval, ArithFullMatchesDealer)
{
using in_t = uint8_t;
using out_t = uint8_t;
in_t x = 40;
in_t x0 = 7;
in_t x1 = static_cast<in_t>(x - x0);
in_t alpha = 200;
out_t y = 3;
const in_t delta = static_cast<in_t>(alpha - x);
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto keys = dpf::make_dpf(x, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_full(dpf::wildcard_input, x0, x1, rng<in_t>(),
[&] { return alpha; }, y);
auto g = dpf::geneval_full(dpf::arith_input, x0, x1, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.party0.size(), 256u);
@ -517,9 +490,8 @@ TEST(Geneval, WildcardFullRotates)
EXPECT_EQ(recon(g.party0[x], g.party1[x]), y);
for (int q = 0; q < 256; ++q)
{
const in_t shifted = static_cast<in_t>(static_cast<in_t>(q) + delta);
auto e0 = ev(keys.first, shifted);
auto e1 = ev(keys.second, shifted);
auto e0 = ev(keys.first, static_cast<in_t>(q));
auto e1 = ev(keys.second, static_cast<in_t>(q));
EXPECT_EQ(g.party0[q], e0);
EXPECT_EQ(g.party1[q], e1);
}
@ -919,7 +891,7 @@ TEST(Geneval, SignedPointIntervalAndCrossZero)
(void)near;
}
TEST(Geneval, SignedFullAndWildcardFull)
TEST(Geneval, SignedFullAndArithFull)
{
using in_t = int8_t;
using out_t = int8_t;
@ -945,13 +917,11 @@ TEST(Geneval, SignedFullAndWildcardFull)
in_t secret = -20;
in_t a0 = 100;
in_t a1 = static_cast<in_t>(secret - a0);
in_t target = 40;
const in_t delta = static_cast<in_t>(target - secret);
ASSERT_EQ(static_cast<in_t>(a0 + a1), secret);
reset_roots();
auto wkeys = dpf::make_dpf(target, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto wkeys = dpf::make_dpf(secret, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto w = dpf::geneval_full(dpf::wildcard_input, a0, a1, rng<in_t>(),
[&] { return target; }, y);
auto w = dpf::geneval_full(dpf::arith_input, a0, a1, rng<in_t>(), y);
ASSERT_EQ(w.party0.size(), 256u);
EXPECT_EQ(w.live_levels, std::decay_t<decltype(wkeys.first)>::depth);
expect_prefix_words(wkeys.first, w.correction_words, w.correction_advice,
@ -959,16 +929,15 @@ TEST(Geneval, SignedFullAndWildcardFull)
for (int q = -128; q <= 127; ++q)
{
in_t v = static_cast<in_t>(q);
in_t shifted = static_cast<in_t>(v + delta);
const std::size_t i = static_cast<std::size_t>(to_int(v));
EXPECT_EQ(w.party0[i], ev(wkeys.first, shifted)) << q;
EXPECT_EQ(w.party1[i], ev(wkeys.second, shifted)) << q;
EXPECT_EQ(w.party0[i], ev(wkeys.first, v)) << q;
EXPECT_EQ(w.party1[i], ev(wkeys.second, v)) << q;
}
EXPECT_EQ(recon(w.party0[static_cast<std::size_t>(to_int(secret))],
w.party1[static_cast<std::size_t>(to_int(secret))]), y);
}
TEST(Geneval, WildcardShareOverflowAndWrappingInterval)
TEST(Geneval, ArithShareOverflowAndWrappingInterval)
{
using in_t = uint8_t;
using out_t = uint8_t;
@ -976,15 +945,12 @@ TEST(Geneval, WildcardShareOverflowAndWrappingInterval)
in_t x0 = 200;
in_t x1 = 66;
ASSERT_EQ(static_cast<in_t>(x0 + x1), secret);
in_t alpha = 5;
out_t y = 17;
const in_t delta = static_cast<in_t>(alpha - secret);
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto keys = dpf::make_dpf(secret, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto on = dpf::geneval_point(dpf::wildcard_input, x0, x1, secret, rng<in_t>(),
[&] { return alpha; }, y);
auto on = dpf::geneval_point(dpf::arith_input, x0, x1, secret, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(on.live_levels, key_t::depth);
EXPECT_TRUE(on.leaf_live);
@ -993,36 +959,33 @@ TEST(Geneval, WildcardShareOverflowAndWrappingInterval)
EXPECT_EQ(recon(on.party0[0], on.party1[0]), y);
in_t query = 250;
const in_t shifted = static_cast<in_t>(query + delta);
reset_roots();
auto off = dpf::geneval_point(dpf::wildcard_input, x0, x1, query, rng<in_t>(),
[&] { return alpha; }, y);
auto off = dpf::geneval_point(dpf::arith_input, x0, x1, query, rng<in_t>(), y);
const auto live = live_through_lcp(
lcp_bits(leaf_of<key_t>(alpha), leaf_of<key_t>(shifted), key_t::depth),
lcp_bits(leaf_of<key_t>(secret), leaf_of<key_t>(query), key_t::depth),
key_t::depth);
EXPECT_EQ(off.live_levels, live);
expect_prefix_words(keys.first, off.correction_words, off.correction_advice,
off.live_levels, off.leaf_live, &off.leaf, sizeof(off.leaf));
EXPECT_EQ(recon(off.party0[0], off.party1[0]),
recon(ev(keys.first, shifted), ev(keys.second, shifted)));
recon(ev(keys.first, query), ev(keys.second, query)));
in_t from = 250;
in_t to = 10;
EXPECT_THROW((dpf::geneval_interval(dpf::wildcard_input, x0, x1, from, to,
rng<in_t>(), [&] { return alpha; }, y)), std::invalid_argument);
EXPECT_THROW((dpf::geneval_interval(dpf::arith_input, x0, x1, from, to,
rng<in_t>(), y)), std::invalid_argument);
from = 250;
to = 255;
reset_roots();
auto iv = dpf::geneval_interval(dpf::wildcard_input, x0, x1, from, to,
rng<in_t>(), [&] { return alpha; }, y);
auto iv = dpf::geneval_interval(dpf::arith_input, x0, x1, from, to,
rng<in_t>(), y);
ASSERT_EQ(iv.party0.size(), 6u);
for (in_t q = from; ; ++q)
{
const std::size_t i = static_cast<std::size_t>(static_cast<in_t>(q - from));
const in_t s = static_cast<in_t>(q + delta);
EXPECT_EQ(recon(iv.party0[i], iv.party1[i]),
recon(ev(keys.first, s), ev(keys.second, s))) << int(q);
recon(ev(keys.first, q), ev(keys.second, q))) << int(q);
if (q == to)
break;
}
@ -1099,26 +1062,19 @@ TEST(Geneval, SignedRegressionsFromTheCornerPass)
EXPECT_EQ(full.party0[bit], ev(keys.first, v)) << q;
}
// Negative target, additive shares that wrap, bound through a real
// wildcard key so the raw offset bits are what geneval subtracts.
// Negative secret, additive shares that wrap through the signed MSB.
in_t secret = -20;
in_t a0 = 100;
in_t a1 = static_cast<in_t>(secret - a0);
ASSERT_EQ(static_cast<in_t>(a0 + a1), secret);
in_t target = -90;
in_t query = 60;
reset_roots();
dpf::wildcard_value<in_t> slot{target};
auto wild = dpf::make_dpf(slot, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
auto s0 = wild.first.offset_x.compute_and_get_share(a0);
auto s1 = wild.second.offset_x.compute_and_get_share(a1);
wild.first.offset_x.reconstruct(s1);
wild.second.offset_x.reconstruct(s0);
auto akeys = dpf::make_dpf(secret, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(dpf::wildcard_input, a0, a1, query, rng<in_t>(),
[&] { return target; }, y);
EXPECT_EQ(recon(g.party0[0], g.party1[0]), recon(ev(wild.first, query), ev(wild.second, query)));
expect_prefix_words(wild.first, g.correction_words, g.correction_advice,
auto g = dpf::geneval_point(dpf::arith_input, a0, a1, query, rng<in_t>(), y);
EXPECT_EQ(recon(g.party0[0], g.party1[0]),
recon(ev(akeys.first, query), ev(akeys.second, query)));
expect_prefix_words(akeys.first, g.correction_words, g.correction_advice,
g.live_levels, g.leaf_live, &g.leaf, sizeof(g.leaf));
}
@ -1223,3 +1179,211 @@ TEST(Geneval, CmpLtIsTheComplementOfTheStrictUpperSet)
EXPECT_EQ(opened, ends[i] < alpha ? 4u : 0u) << int(ends[i]);
}
}
TEST(Geneval, DoernerShelatOnTargetSharesMatch)
{
using in_t = uint16_t;
using out_t = uint16_t;
const in_t alpha = 0x55aa;
const in_t x0 = 0x1234;
const in_t x1 = static_cast<in_t>(alpha ^ x0);
const out_t y = 0x9f3c;
reset_roots();
dpf::ds_randomness<simde__m128i (*)(), Pad> ds_rng{take_root, Pad{}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, ds_rng, y);
reset_roots();
auto g = dpf::geneval_point(x0, x1, alpha, rng<in_t>(), y);
using key_t = std::decay_t<decltype(ds.first)>;
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(ds.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
EXPECT_EQ(g.party0[0], ev(ds.first, alpha));
EXPECT_EQ(g.party1[0], ev(ds.second, alpha));
EXPECT_EQ(recon(g.party0[0], g.party1[0]), y);
}
TEST(Geneval, WideLiveFrontierMatchesDealer)
{
using in_t = uint16_t;
using out_t = uint16_t;
const in_t alpha = 0x00ff;
const in_t x0 = 0x0f0f;
const in_t x1 = static_cast<in_t>(alpha ^ x0);
const out_t y = 0xabcd;
const in_t from = 0;
const in_t to = 0x00ff;
reset_roots();
auto keys = dpf::make_dpf(alpha, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_interval(x0, x1, from, to, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
ASSERT_EQ(g.party0.size(), static_cast<std::size_t>(to - from) + 1);
for (in_t q = from; ; ++q)
{
const std::size_t i = static_cast<std::size_t>(q - from);
EXPECT_EQ(g.party0[i], ev(keys.first, q)) << q;
EXPECT_EQ(g.party1[i], ev(keys.second, q)) << q;
const out_t opened = recon(g.party0[i], g.party1[i]);
EXPECT_EQ(opened, q == alpha ? y : out_t{0}) << q;
if (q == to)
break;
}
}
TEST(Geneval, CmpLeqGeqNonzeroElseAndDomainMin)
{
using in_t = uint8_t;
const in_t alpha = 10;
const in_t x0 = 3;
const in_t x1 = static_cast<in_t>(alpha ^ x0);
const std::vector<in_t> ends{0, 9, 10, 11, 255};
auto check = [&](auto spec, auto pred) {
auto spec_ds = spec;
auto spec_g = spec;
reset_roots();
dpf::ds_randomness<simde__m128i (*)(), Pad> ds_rng{take_root, Pad{}};
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, ds_rng, spec_ds);
reset_roots();
auto g = dpf::geneval_cmp(x0, x1, ends.begin(), ends.end(), rng<in_t>(), spec_g);
using key_t = std::decay_t<decltype(ds.first)>;
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_EQ(g.correction_words.size(), key_t::depth);
for (std::size_t level = 0; level < key_t::depth; ++level)
{
EXPECT_EQ(std::memcmp(&g.correction_words[level],
&ds.first.correction_word(level), sizeof(simde__m128i)), 0) << level;
EXPECT_EQ(g.correction_advice[level], ds.first.correction_advice(level));
}
for (std::size_t i = 0; i < ends.size(); ++i)
{
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
const uint64_t from_key =
(dpf::eval_point(dpf::cmp, ds.first, ends[i]).raw()
+ dpf::eval_point(dpf::cmp, ds.second, ends[i]).raw())
& ds.first.cmp().mask;
EXPECT_EQ(opened, from_key) << int(ends[i]);
EXPECT_EQ(opened, pred(ends[i])) << int(ends[i]);
}
};
check(dpf::leq(uint64_t{5}, uint64_t{2}), [&](in_t e) {
return e <= alpha ? uint64_t{5} : uint64_t{2};
});
check(dpf::geq(uint64_t{5}, uint64_t{2}), [&](in_t e) {
return e >= alpha ? uint64_t{5} : uint64_t{2};
});
using wide = int16_t;
const wide amin = std::numeric_limits<wide>::min();
const wide w0 = 1;
const wide w1 = static_cast<wide>(amin ^ w0);
const std::vector<wide> wends{amin, static_cast<wide>(amin + 1), wide{-1}, wide{0},
std::numeric_limits<wide>::max()};
reset_roots();
auto g = dpf::geneval_cmp(w0, w1, wends.begin(), wends.end(), rng<wide>(),
dpf::gt(uint64_t{3}));
for (std::size_t i = 0; i < wends.size(); ++i)
{
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
EXPECT_EQ(opened, wends[i] > amin ? 3u : 0u) << wends[i];
}
}
TEST(Geneval, ArithSignedMsbAndCarryAcrossPowerOfTwo)
{
using in_t = int8_t;
using out_t = int8_t;
const in_t secret = -20;
const in_t a0 = 100;
const in_t a1 = static_cast<in_t>(secret - a0);
ASSERT_EQ(static_cast<in_t>(a0 + a1), secret);
const out_t y = -7;
reset_roots();
auto keys = dpf::make_dpf(secret, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(dpf::arith_input, a0, a1, secret, rng<in_t>(), y);
using key_t = std::decay_t<decltype(keys.first)>;
EXPECT_EQ(g.live_levels, key_t::depth);
EXPECT_TRUE(g.leaf_live);
expect_prefix_words(keys.first, g.correction_words, g.correction_advice,
g.live_levels, true, &g.leaf, sizeof(g.leaf));
EXPECT_EQ(g.party0[0], ev(keys.first, secret));
EXPECT_EQ(g.party1[0], ev(keys.second, secret));
EXPECT_EQ(recon(g.party0[0], g.party1[0]), y);
// Carry across 2^k: shares that wrap the unsigned modulus.
using u8 = uint8_t;
const u8 usecret = 7;
const u8 x0 = 200;
const u8 x1 = static_cast<u8>(usecret - x0);
ASSERT_EQ(static_cast<u8>(x0 + x1), usecret);
const u8 uy = 9;
reset_roots();
auto ukeys = dpf::make_dpf(usecret, dpf::root_sampler_t<dpf::prg::aes128>{take_root}, uy);
reset_roots();
auto iv = dpf::geneval_interval(dpf::arith_input, x0, x1, u8{0}, u8{3},
rng<u8>(), uy);
ASSERT_EQ(iv.party0.size(), 4u);
for (u8 q = 0; q <= 3; ++q)
{
const std::size_t i = static_cast<std::size_t>(q);
EXPECT_EQ(iv.party0[i], ev(ukeys.first, q)) << int(q);
EXPECT_EQ(iv.party1[i], ev(ukeys.second, q)) << int(q);
EXPECT_EQ(recon(iv.party0[i], iv.party1[i]),
recon(ev(ukeys.first, q), ev(ukeys.second, q))) << int(q);
}
using ukey_t = std::decay_t<decltype(ukeys.first)>;
EXPECT_EQ(iv.live_levels,
live_through_lcp(
lcp_bits(leaf_of<ukey_t>(usecret), leaf_of<ukey_t>(u8{0}), ukey_t::depth),
ukey_t::depth));
}
TEST(Geneval, ArithDoernerShelatAndCmpMatchDealer)
{
using in_t = uint8_t;
const in_t secret = 40;
const in_t a0 = 250;
const in_t a1 = static_cast<in_t>(secret - a0);
ASSERT_EQ(static_cast<in_t>(a0 + a1), secret);
const uint64_t beta = 7;
const std::vector<in_t> ends{0, 1, 10, 40, 200, 255};
reset_roots();
auto dealer = dpf::make_dpf(secret, dpf::root_sampler_t<dpf::prg::aes128>{take_root},
dpf::gt(beta));
reset_roots();
auto ds = dpf::make_dpf_doerner_shelat(dpf::arith_input, a0, a1, rng<in_t>(),
dpf::gt(beta));
using key_t = std::decay_t<decltype(dealer.first)>;
for (std::size_t level = 0; level < key_t::depth; ++level)
{
EXPECT_EQ(std::memcmp(&ds.first.correction_word(level),
&dealer.first.correction_word(level), sizeof(simde__m128i)), 0) << level;
EXPECT_EQ(ds.first.correction_advice(level),
dealer.first.correction_advice(level)) << level;
EXPECT_EQ(ds.first.value_cw(level), dealer.first.value_cw(level)) << level;
}
reset_roots();
auto g = dpf::geneval_cmp(dpf::arith_input, a0, a1, ends.begin(), ends.end(),
rng<in_t>(), beta);
EXPECT_EQ(g.live_levels, key_t::depth);
for (std::size_t i = 0; i < ends.size(); ++i)
{
const uint64_t opened = (g.party0[i] + g.party1[i]) & g.mask;
EXPECT_EQ(opened, ends[i] > secret ? beta : 0u) << int(ends[i]);
}
}

224
test/tests/ic_test.cpp Normal file
View file

@ -0,0 +1,224 @@
#include <gtest/gtest.h>
#include "dpf.hpp"
#include <cstdint>
#include <random>
#include <vector>
namespace
{
uint64_t oracle(uint64_t x, uint64_t r, uint64_t p, uint64_t q,
uint64_t nmask, uint64_t if_true, uint64_t if_false, uint64_t gmask)
{
const uint64_t w = (x - r) & nmask;
const bool inside = w >= p && w <= q;
return (inside ? if_true : if_false) & gmask;
}
template <typename Input, typename Beta>
void expect_domain(Input r, Input p, Input q, Beta if_true, Beta if_false,
uint64_t gmask)
{
auto keys = dpf::make_dpf(r, dpf::ic(p, q, if_true, if_false));
const uint64_t nmask = keys.first.input_mask;
const uint64_t rb = static_cast<uint64_t>(r);
const uint64_t pb = static_cast<uint64_t>(p);
const uint64_t qb = static_cast<uint64_t>(q);
for (uint64_t x = 0; x <= nmask; ++x)
{
const auto y0 = dpf::eval_point(dpf::ic, keys.first,
static_cast<Input>(x));
const auto y1 = dpf::eval_point(dpf::ic, keys.second,
static_cast<Input>(x));
const uint64_t got = static_cast<uint64_t>(dpf::reconstruct(y0, y1)) & gmask;
const uint64_t want = oracle(x, rb, pb, qb, nmask,
static_cast<uint64_t>(if_true), static_cast<uint64_t>(if_false), gmask);
ASSERT_EQ(got, want) << "r=" << rb << " x=" << x
<< " p=" << pb << " q=" << qb;
}
}
} // namespace
TEST(Ic, Uint8FullDomainCorners)
{
const uint32_t betas[] = {1u, 7u, 255u};
const uint32_t falses[] = {0u, 9u};
const uint8_t intervals[][2] = {
{0, 0}, {0, 255}, {5, 5}, {1, 20}, {200, 250}, {0, 1}, {254, 255}, {10, 40}
};
for (uint32_t beta : betas)
{
for (uint32_t f : falses)
{
for (const auto & iv : intervals)
{
for (int r = 0; r < 256; r += 17)
{
expect_domain<uint8_t>(static_cast<uint8_t>(r), iv[0], iv[1],
beta, f, 0xffffffffu);
}
}
}
}
}
TEST(Ic, Uint8AllMasksOneInterval)
{
expect_domain<uint8_t>(uint8_t{0}, uint8_t{10}, uint8_t{20},
uint32_t{3}, uint32_t{0}, 0xffffffffu);
expect_domain<uint8_t>(uint8_t{200}, uint8_t{10}, uint8_t{100},
uint32_t{3}, uint32_t{1}, 0xffffffffu);
expect_domain<uint8_t>(uint8_t{255}, uint8_t{0}, uint8_t{255},
uint32_t{1}, uint32_t{0}, 0xffffffffu);
}
TEST(Ic, MemoizerAgrees)
{
const uint8_t r = 40, p = 7, q = 90;
auto keys = dpf::make_dpf(r, dpf::ic(p, q, uint32_t{11}, uint32_t{2}));
dpf::basic_path_memoizer<decltype(keys.first.key)> memo0;
dpf::basic_path_memoizer<decltype(keys.second.key)> memo1;
for (int x = 0; x < 256; ++x)
{
const auto a = dpf::eval_point(dpf::ic, keys.first, static_cast<uint8_t>(x), memo0);
const auto b = dpf::eval_point(dpf::ic, keys.second, static_cast<uint8_t>(x), memo1);
const auto c = dpf::eval_point(dpf::ic, keys.first, static_cast<uint8_t>(x));
const auto d = dpf::eval_point(dpf::ic, keys.second, static_cast<uint8_t>(x));
EXPECT_EQ(dpf::reconstruct(a, b), dpf::reconstruct(c, d));
}
}
TEST(Ic, IntervalAndSequenceBuffers)
{
const uint8_t r = 15, p = 4, q = 12;
auto keys = dpf::make_dpf(r, dpf::ic(p, q, uint16_t{9}));
auto buf0 = dpf::make_output_buffer(dpf::ic, keys.first, uint8_t{3}, uint8_t{18});
auto buf1 = dpf::make_output_buffer(dpf::ic, keys.second, uint8_t{3}, uint8_t{18});
dpf::basic_path_memoizer<decltype(keys.first.key)> memo;
dpf::eval_interval(dpf::ic, keys.first, uint8_t{3}, uint8_t{18}, buf0, memo);
dpf::eval_interval(dpf::ic, keys.second, uint8_t{3}, uint8_t{18}, buf1);
for (std::size_t i = 0; i < buf0.size(); ++i)
{
const auto point = dpf::reconstruct(
dpf::eval_point(dpf::ic, keys.first, static_cast<uint8_t>(3 + i)),
dpf::eval_point(dpf::ic, keys.second, static_cast<uint8_t>(3 + i)));
EXPECT_EQ(dpf::reconstruct(buf0[i], buf1[i]), point);
}
const uint8_t pts[] = {0, 9, 15, 255, 4};
auto s0 = dpf::make_output_buffer(dpf::ic, keys.first, 5);
auto s1 = dpf::make_output_buffer(dpf::ic, keys.second, 5);
dpf::eval_sequence(dpf::ic, keys.first, std::begin(pts), std::end(pts), s0);
dpf::eval_sequence(dpf::ic, keys.second, std::begin(pts), std::end(pts), s1);
for (std::size_t i = 0; i < 5; ++i)
{
const auto point = dpf::reconstruct(
dpf::eval_point(dpf::ic, keys.first, pts[i]),
dpf::eval_point(dpf::ic, keys.second, pts[i]));
EXPECT_EQ(dpf::reconstruct(s0[i], s1[i]), point);
}
}
TEST(Ic, WildcardAssign)
{
auto keys = dpf::make_dpf(uint8_t{33},
dpf::ic(uint8_t{2}, uint8_t{8}, dpf::wildcard<uint32_t>));
EXPECT_THROW(dpf::eval_point(dpf::ic, keys.first, uint8_t{0}), std::invalid_argument);
dpf::assign_cmp(keys.first, keys.second, uint32_t{6}, uint32_t{1});
expect_domain<uint8_t>(uint8_t{33}, uint8_t{2}, uint8_t{8},
uint32_t{6}, uint32_t{1}, 0xffffffffu);
// The keys just assigned are a different generation; check those directly.
for (int x = 0; x < 256; ++x)
{
const uint64_t got = static_cast<uint64_t>(dpf::reconstruct(
dpf::eval_point(dpf::ic, keys.first, static_cast<uint8_t>(x)),
dpf::eval_point(dpf::ic, keys.second, static_cast<uint8_t>(x))));
const uint64_t w = static_cast<uint64_t>(static_cast<uint8_t>(x - 33));
const uint64_t want = (w >= 2 && w <= 8) ? 6u : 1u;
EXPECT_EQ(got, want) << x;
}
}
TEST(Ic, DoernerShelatMatchesDealer)
{
std::mt19937 rng{7};
std::uniform_int_distribution<int> d(0, 255);
for (int n = 0; n < 30; ++n)
{
const uint8_t r0 = static_cast<uint8_t>(d(rng));
const uint8_t r1 = static_cast<uint8_t>(d(rng));
const uint8_t p = static_cast<uint8_t>(d(rng));
const uint8_t q = static_cast<uint8_t>(p + static_cast<uint8_t>(d(rng) % (256 - p)));
const uint32_t beta = 1u + static_cast<uint32_t>(d(rng));
const uint8_t r = static_cast<uint8_t>(r0 ^ r1);
auto dealer = dpf::make_dpf(r, dpf::ic(p, q, beta));
struct Pad
{
simde__m128i block() { return dpf::uniform_sample<simde__m128i>(); }
uint8_t bit() { return static_cast<uint8_t>(dpf::uniform_sample<uint8_t>() & 1u); }
};
dpf::ds_randomness<decltype(&dpf::uniform_sample<simde__m128i>), Pad> rngs{
&dpf::uniform_sample<simde__m128i>, {}};
auto ds = dpf::make_dpf_doerner_shelat(r0, r1, rngs, dpf::ic(p, q, beta));
for (int x = 0; x < 256; x += 5)
{
const auto dealer_y = dpf::reconstruct(
dpf::eval_point(dpf::ic, dealer.first, static_cast<uint8_t>(x)),
dpf::eval_point(dpf::ic, dealer.second, static_cast<uint8_t>(x)));
const auto ds_y = dpf::reconstruct(
dpf::eval_point(dpf::ic, ds.first, static_cast<uint8_t>(x)),
dpf::eval_point(dpf::ic, ds.second, static_cast<uint8_t>(x)));
EXPECT_EQ(dealer_y, ds_y) << "x=" << x;
}
}
}
TEST(Ic, Geneval)
{
struct Pad
{
simde__m128i block() { return dpf::uniform_sample<simde__m128i>(); }
uint8_t bit() { return static_cast<uint8_t>(dpf::uniform_sample<uint8_t>() & 1u); }
};
const uint8_t r0 = 9, r1 = 100, p = 3, q = 50;
const uint32_t beta = 4;
const uint8_t queries[] = {0, 3, 12, 49, 50, 51, 255};
dpf::ds_randomness<decltype(&dpf::uniform_sample<simde__m128i>), Pad> rngs{
&dpf::uniform_sample<simde__m128i>, {}};
auto opened = dpf::geneval_ic(r0, r1, std::begin(queries), std::end(queries),
rngs, dpf::ic(p, q, beta));
ASSERT_EQ(opened.party0.size(), 7u);
ASSERT_EQ(opened.live_levels, 8u);
const uint8_t r = static_cast<uint8_t>(r0 ^ r1);
for (std::size_t i = 0; i < 7; ++i)
{
const uint64_t got = (opened.party0[i] + opened.party1[i]) & 0xffffffffu;
const uint64_t w = static_cast<uint64_t>(
static_cast<uint8_t>(queries[i] - r));
const uint64_t want = (w >= p && w <= q) ? beta : 0u;
EXPECT_EQ(got, want) << i;
}
}
TEST(Ic, RejectsWrappedBounds)
{
EXPECT_THROW(dpf::make_dpf(uint8_t{1}, dpf::ic(uint8_t{9}, uint8_t{2}, uint32_t{1})),
std::invalid_argument);
}
TEST(Ic, BitPayload)
{
auto keys = dpf::make_dpf(uint8_t{4},
dpf::ic(uint8_t{1}, uint8_t{3}, dpf::bit::one, dpf::bit::zero));
for (int x = 0; x < 256; ++x)
{
const auto y = dpf::reconstruct(
dpf::eval_point(dpf::ic, keys.first, static_cast<uint8_t>(x)),
dpf::eval_point(dpf::ic, keys.second, static_cast<uint8_t>(x)));
const uint64_t w = static_cast<uint64_t>(static_cast<uint8_t>(x - 4));
EXPECT_EQ(static_cast<bool>(y), w >= 1 && w <= 3) << x;
}
}

View file

@ -448,7 +448,7 @@ TEST(LaneBlast, GenevalMatchesKeyOnPackedLanes)
EXPECT_EQ(opened(sq.party0[i], sq.party1[i]), seq[i] == alpha ? y : out_t{});
}
TEST(LaneBlast, GenevalNybleWildcardAndSigned)
TEST(LaneBlast, GenevalNybleArithAndSigned)
{
using out_t = dpf::nyble;
const out_t y{0x0c};
@ -459,20 +459,19 @@ TEST(LaneBlast, GenevalNybleWildcardAndSigned)
const in_t a0 = 200;
const in_t a1 = 66;
ASSERT_EQ(static_cast<in_t>(a0 + a1), secret);
const in_t target = 5;
reset_roots();
auto keys = dpf::make_dpf(target,
auto keys = dpf::make_dpf(secret,
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
reset_roots();
auto g = dpf::geneval_point(dpf::wildcard_input, a0, a1, secret,
dpf::ds_randomness<simde__m128i (*)(), Pad>{take_root, Pad{}}, [&] { return target; }, y);
auto g = dpf::geneval_point(dpf::arith_input, a0, a1, secret,
dpf::ds_randomness<simde__m128i (*)(), Pad>{take_root, Pad{}}, y);
EXPECT_TRUE(g.leaf_live);
expect_live_words(keys.first, g);
EXPECT_EQ(opened(g.party0[0], g.party1[0]), y);
reset_roots();
auto miss = dpf::geneval_point(dpf::wildcard_input, a0, a1, in_t{250},
dpf::ds_randomness<simde__m128i (*)(), Pad>{take_root, Pad{}}, [&] { return target; }, y);
auto miss = dpf::geneval_point(dpf::arith_input, a0, a1, in_t{250},
dpf::ds_randomness<simde__m128i (*)(), Pad>{take_root, Pad{}}, y);
EXPECT_EQ(opened(miss.party0[0], miss.party1[0]), out_t{});
expect_live_words(keys.first, miss);
}

176
test/tests/nmod_test.cpp Normal file
View file

@ -0,0 +1,176 @@
#include <gtest/gtest.h>
#include "grotto/nmod.hpp"
#include <cstdint>
#include <random>
using u128 = unsigned __int128;
namespace
{
grotto::nmod_result oracle(std::int64_t x_raw, unsigned x_bits, std::uint64_t recip,
unsigned recip_bits, unsigned residue_bits)
{
const unsigned scale = x_bits + recip_bits;
const __int128 prod = static_cast<__int128>(x_raw) * static_cast<__int128>(recip);
const bool neg = prod < 0;
const auto mag = static_cast<u128>(neg ? -prod : prod);
const u128 mask = scale >= 128 ? ~u128{0} : (u128{1} << scale) - 1;
const u128 quot_mag = scale >= 128 ? 0 : mag >> scale;
const u128 rem = scale >= 128 ? mag : mag & mask;
grotto::nmod_result out;
if (!neg)
out.quotient = static_cast<std::int64_t>(quot_mag);
else if (rem == 0)
out.quotient = -static_cast<std::int64_t>(quot_mag);
else
out.quotient = -static_cast<std::int64_t>(quot_mag) - 1;
if (residue_bits == 0 || rem == 0)
return out;
u128 field = rem;
if (neg)
field = (u128{1} << scale) - rem;
if (scale >= residue_bits)
field >>= scale - residue_bits;
else
field <<= residue_bits - scale;
const u128 unit = u128{1} << residue_bits;
out.residue = static_cast<std::int64_t>(field & (unit - 1));
return out;
}
} // namespace
TEST(Nmod, SplitsAnIntegerModulusOnTheFractionalBoundary)
{
// 3.25 = 13/4, modulo 1.
const auto split = grotto::nmod(13, 2, 1, 0, 2);
EXPECT_EQ(split.quotient, 3);
EXPECT_EQ(split.residue, 1);
// -1.25 = -5/4. floor is -2 and the residue is 0.75.
const auto neg = grotto::nmod(-5, 2, 1, 0, 2);
EXPECT_EQ(neg.quotient, -2);
EXPECT_EQ(neg.residue, 3);
// Coarser residue truncates toward -infinity: floor(0.75 * 2) = 1.
EXPECT_EQ(grotto::nmod(-5, 2, 1, 0, 1).residue, 1);
}
TEST(Nmod, ExactNegativeIntegersHaveAZeroResidue)
{
const auto split = grotto::nmod(-8, 2, 1, 0, 2);
EXPECT_EQ(split.quotient, -2);
EXPECT_EQ(split.residue, 0);
EXPECT_EQ(grotto::nmod(0, 8, 1, 0, 8).quotient, 0);
EXPECT_EQ(grotto::nmod(0, 8, 1, 0, 8).residue, 0);
}
TEST(Nmod, FloorDoesNotRoundUpToTheNextQuotient)
{
// 1 - 2^{-16}. A round-to-nearest reciprocal product would become 1.
const auto split = grotto::nmod(1, 0, (std::uint64_t{1} << 16) - 1, 16, 8);
EXPECT_EQ(split.quotient, 0);
EXPECT_EQ(split.residue, 255);
}
TEST(Nmod, PowerOfTwoModulusIsAnExactShift)
{
// 1.5 / 2^{-1} = 3 exactly.
const auto half = grotto::nmod_pow2(6, 2, 1, 2);
EXPECT_EQ(half.quotient, 3);
EXPECT_EQ(half.residue, 0);
// 1.5 / 2 = 0.75.
const auto two = grotto::nmod_pow2(6, 2, -1, 2);
EXPECT_EQ(two.quotient, 0);
EXPECT_EQ(two.residue, 3);
// 2^x splits at the integer.
const auto unit = grotto::nmod_pow2(6, 2, 0, 2);
EXPECT_EQ(unit.quotient, 1);
EXPECT_EQ(unit.residue, 2);
const auto via_recip = grotto::nmod(6, 2, 2, 0, 2);
EXPECT_EQ(half.quotient, via_recip.quotient);
EXPECT_EQ(half.residue, via_recip.residue);
}
TEST(Nmod, IntegerReciprocalAgreesWithFloorDivision)
{
// round(2^100 / 3) == floor(2^100 / 3) for this width.
const u128 recip = (u128{1} << 100) / 3;
const auto split = grotto::nmod(10, 0, recip, 100, 16);
EXPECT_EQ(split.quotient, 3);
EXPECT_EQ(split.residue, 21845);
}
TEST(Nmod, WideQuarterTurnReciprocal)
{
// RN(4/π · 2^80). x = 2.5 at 8 fractional bits, residue at 16 bits.
const u128 recip = (u128{83443} << 64) | 494442167743545356ULL;
const auto split = grotto::nmod(640, 8, recip, 80, 16);
EXPECT_EQ(split.quotient, 3);
EXPECT_EQ(split.residue, 11999);
}
TEST(Nmod, MatchesFloorOnRandomReciprocals)
{
std::mt19937 rng(0x6d6f64u);
std::uniform_int_distribution<int> values(-4000, 4000);
std::uniform_int_distribution<int> bits(0, 20);
std::uniform_int_distribution<unsigned> recip_dist(1, 100000);
for (int i = 0; i < 4000; ++i)
{
const unsigned x_bits = static_cast<unsigned>(bits(rng));
const unsigned recip_bits = static_cast<unsigned>(bits(rng));
const unsigned residue_bits = static_cast<unsigned>(bits(rng) % 16);
const std::int64_t x_raw = values(rng);
const std::uint64_t recip = recip_dist(rng);
const auto got = grotto::nmod(x_raw, x_bits, recip, recip_bits, residue_bits);
const auto want = oracle(x_raw, x_bits, recip, recip_bits, residue_bits);
EXPECT_EQ(got.quotient, want.quotient) << i;
EXPECT_EQ(got.residue, want.residue) << i;
if (got.quotient != want.quotient || got.residue != want.residue)
break;
}
}
TEST(Nmod, PowerOfTwoAgreesWithTheGeneralSplit)
{
std::mt19937 rng(13);
std::uniform_int_distribution<int> values(-2000, 2000);
std::uniform_int_distribution<int> exps(-12, 12);
for (int i = 0; i < 500; ++i)
{
const int exp = exps(rng);
const unsigned residue_bits = static_cast<unsigned>(i % 10);
const std::int64_t x_raw = values(rng);
const auto got = grotto::nmod_pow2(x_raw, 8, exp, residue_bits);
grotto::nmod_result want;
if (exp >= 0)
want = grotto::nmod(x_raw, 8, std::uint64_t{1} << exp, 0, residue_bits);
else
want = grotto::nmod(x_raw, 8, 1, static_cast<unsigned>(-exp), residue_bits);
EXPECT_EQ(got.quotient, want.quotient);
EXPECT_EQ(got.residue, want.residue);
}
}
TEST(Nmod, RejectsAZeroReciprocalAndAHugeQuotient)
{
EXPECT_THROW(grotto::nmod(1, 0, 0, 0, 4), std::invalid_argument);
EXPECT_THROW(grotto::nmod(1, 0, 1, 0, 64), std::invalid_argument);
EXPECT_THROW(grotto::nmod_pow2(1, 0, 128, 4), std::overflow_error);
EXPECT_THROW(grotto::nmod(INT64_MAX, 0, u128{1} << 80, 0, 4),
std::overflow_error);
}
TEST(Nmod, MostNegativeInputModuloOne)
{
const auto split = grotto::nmod(INT64_MIN, 0, 1, 0, 4);
EXPECT_EQ(split.quotient, INT64_MIN);
EXPECT_EQ(split.residue, 0);
}

View file

@ -835,6 +835,33 @@ TEST(OffsetHorner, HornerOfOpenedCoefficientsMatchesValue)
EXPECT_EQ(y, gold<D>(center, eta, knots, coeff));
}
TEST(OffsetHorner, OpenedSharesAreNotHornerInputs)
{
constexpr std::size_t D = 2;
const std::vector<uint8_t> knots{0, 50, 150};
const auto coeff = take_degree<D>(pad3({
{1, 0, 0, 0},
{0, 4, 1, 0},
{8, 0, 0, 0},
}));
const uint8_t center = 10;
const uint8_t eta = 60;
auto mat = grotto::make_offset_horner_keys<uint8_t, D>(center);
const auto c = open_coeffs<D>(mat, knots, coeff, eta);
uint64_t sum = 0;
for (uint64_t ck : c)
sum += ck;
const uint64_t value = gold<D>(center, eta, knots, coeff);
EXPECT_EQ(sum, value);
EXPECT_EQ(value, 5180u);
uint64_t horner = c[D];
const uint64_t limb = lift(center);
for (std::size_t k = D; k-- > 0; )
horner = horner * limb + c[k];
EXPECT_NE(horner, value);
}
template <std::size_t Degree, typename T>
void expect_wrapped(const grotto::offset_horner_keys<T, Degree> & mat,
const std::vector<T> & knots,

View file

@ -0,0 +1,223 @@
#include <gtest/gtest.h>
#include "dpf.hpp"
#include <cstdint>
#include <utility>
namespace
{
template <typename A, typename B, typename Target>
uint64_t recon_cmp(const A & a, const B & b, Target target, uint8_t x)
{
return dpf::reconstruct(dpf::eval_point(target, a, x),
dpf::eval_point(target, b, x));
}
uint64_t lcp_len(uint8_t x, uint8_t alpha, std::size_t n = 8, std::size_t width = 8)
{
for (std::size_t i = 0; i < n; ++i)
{
const uint8_t shift = static_cast<uint8_t>(width - 1 - i);
if (((x >> shift) & 1) != ((alpha >> shift) & 1))
return i;
}
return n;
}
uint64_t high_prefix(uint8_t alpha, uint64_t d, std::size_t n = 8)
{
if (d == 0)
return 0;
if (d >= n)
return alpha & ((1u << n) - 1u);
const unsigned drop = static_cast<unsigned>(n - d);
return (static_cast<unsigned>(alpha) >> drop) << drop;
}
uint64_t low_prefix(uint8_t alpha, uint64_t d, std::size_t n = 8)
{
if (d == 0)
return 0;
if (d >= n)
return alpha;
return static_cast<unsigned>(alpha) >> (n - d);
}
template <typename Make, typename Unit>
void expect_domain(Make make, Unit unit_of)
{
constexpr uint8_t alpha = 0xB4;
auto [k0, k1] = make(alpha);
for (int x = 0; x < 256; ++x)
{
const auto got = recon_cmp(k0, k1, dpf::cmp, static_cast<uint8_t>(x));
EXPECT_EQ(got, unit_of(static_cast<uint8_t>(x), alpha))
<< "x=" << x;
}
}
} // namespace
TEST(PathRecipe, LengthMaskPrefixBreakAndPacked)
{
constexpr uint8_t alpha = 0xB4;
expect_domain(
[](uint8_t a) { return dpf::make_dpf(a, dpf::lcp(uint64_t{1})); },
[](uint8_t x, uint8_t a) { return lcp_len(x, a); });
expect_domain(
[](uint8_t a) { return dpf::make_dpf(a, dpf::common_prefix(uint64_t{1})); },
[](uint8_t x, uint8_t a) { return high_prefix(a, lcp_len(x, a)); });
expect_domain(
[](uint8_t a) { return dpf::make_dpf(a, dpf::prefix_mask(uint64_t{1})); },
[](uint8_t x, uint8_t a) { return high_prefix(0xFF, lcp_len(x, a)); });
expect_domain(
[](uint8_t a) { return dpf::make_dpf(a, dpf::diverge_one_hot(uint64_t{1})); },
[](uint8_t x, uint8_t a) { return 1ULL << lcp_len(x, a); });
expect_domain(
[](uint8_t a) { return dpf::make_dpf(a, dpf::break_bit(uint64_t{3})); },
[](uint8_t x, uint8_t a) {
const auto d = lcp_len(x, a);
if (d >= 8)
return 0ULL;
return 3ULL * ((a >> (7 - d)) & 1);
});
expect_domain(
[](uint8_t a) {
return dpf::make_dpf(a, dpf::prefix_with_length<4>(uint64_t{1}));
},
[](uint8_t x, uint8_t a) {
const auto d = lcp_len(x, a);
return (low_prefix(a, d) << 4) | d;
});
expect_domain(
[](uint8_t a) {
return dpf::make_dpf(a, dpf::lcp(uint64_t{5}, uint64_t{2}));
},
[](uint8_t x, uint8_t a) { return 2ULL + 3ULL * lcp_len(x, a); });
expect_domain(
[](uint8_t a) {
return dpf::make_dpf(a, dpf::path_paint(
[](std::size_t matched, uint64_t, bool) {
return static_cast<uint64_t>(matched);
}));
},
[](uint8_t x, uint8_t a) { return lcp_len(x, a); });
auto [p0, p1] = dpf::make_dpf(alpha, dpf::lcp_at<4>(uint64_t{1}));
for (int x = 0; x < 256; ++x)
{
const auto got = recon_cmp(p0, p1, dpf::cmp, static_cast<uint8_t>(x));
EXPECT_EQ(got, lcp_len(static_cast<uint8_t>(x), alpha, 4, 8)) << x;
}
}
TEST(PathRecipe, WildcardScaleAssignsLength)
{
constexpr uint8_t alpha = 0x3C;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lcp(dpf::wildcard<uint64_t>));
dpf::assign_cmp(k0, k1, uint64_t{4});
for (int x = 0; x < 256; ++x)
{
EXPECT_EQ(recon_cmp(k0, k1, dpf::cmp, static_cast<uint8_t>(x)),
4ULL * lcp_len(static_cast<uint8_t>(x), alpha))
<< x;
}
}
TEST(PathRecipe, IdpfSlotsArePrefixPointFunctions)
{
constexpr uint8_t alpha = 0xA6;
auto [k0, k1] = dpf::make_dpf(alpha,
dpf::idpf(uint64_t{11}, uint64_t{22}, uint64_t{33}));
auto slot = [&](auto target, uint8_t x) {
return dpf::reconstruct(*dpf::eval_point(target, k0, x),
*dpf::eval_point(target, k1, x));
};
for (int x = 0; x < 256; ++x)
{
const auto d = lcp_len(static_cast<uint8_t>(x), alpha);
EXPECT_EQ(slot(dpf::out<0>, static_cast<uint8_t>(x)), d >= 1 ? 11u : 0u);
EXPECT_EQ(slot(dpf::out<1>, static_cast<uint8_t>(x)), d >= 2 ? 22u : 0u);
EXPECT_EQ(slot(dpf::out<2>, static_cast<uint8_t>(x)), d >= 3 ? 33u : 0u);
}
auto [s0, s1] = dpf::make_dpf(alpha, dpf::idpf_at<4, 7>(uint8_t{9}, uint8_t{8}));
for (int x = 0; x < 256; ++x)
{
const auto d = lcp_len(static_cast<uint8_t>(x), alpha);
auto at = [&](auto target) {
return dpf::reconstruct(*dpf::eval_point(target, s0, static_cast<uint8_t>(x)),
*dpf::eval_point(target, s1, static_cast<uint8_t>(x)));
};
EXPECT_EQ(at(dpf::out<0>), d >= 4 ? 9u : 0u);
EXPECT_EQ(at(dpf::out<1>), d >= 7 ? 8u : 0u);
}
}
TEST(PathRecipe, IdcfMatchesComparisonAtEveryPrefix)
{
constexpr uint8_t alpha = 0x6E;
auto check = [&](auto idcf_spec, auto at_spec, auto full_spec, std::size_t L,
auto target) {
auto [i0, i1] = dpf::make_dpf(alpha, idcf_spec);
auto [n0, n1] = dpf::make_dpf(alpha, at_spec);
auto [f0, f1] = dpf::make_dpf(alpha, full_spec);
for (int x = 0; x < 256; ++x)
{
const auto q = static_cast<uint8_t>(x);
EXPECT_EQ(recon_cmp(i0, i1, target, q), recon_cmp(n0, n1, dpf::cmp, q))
<< "L=" << L << " x=" << x;
EXPECT_EQ(recon_cmp(i0, i1, dpf::cmp, q), recon_cmp(f0, f1, dpf::cmp, q))
<< "full x=" << x;
}
EXPECT_EQ(i0.prefix_cw(8), i0.cw_last());
};
check(dpf::idcf(dpf::lt(uint64_t{1})), dpf::lt_at<4>(uint64_t{1}),
dpf::lt(uint64_t{1}), 4, dpf::cmp_prefix<4>);
check(dpf::idcf(dpf::leq(uint64_t{1})), dpf::leq_at<3>(uint64_t{1}),
dpf::leq(uint64_t{1}), 3, dpf::cmp_prefix<3>);
check(dpf::idcf(dpf::gt(uint64_t{1})), dpf::gt_at<5>(uint64_t{1}),
dpf::gt(uint64_t{1}), 5, dpf::cmp_prefix<5>);
check(dpf::idcf(dpf::geq(uint64_t{1})), dpf::geq_at<1>(uint64_t{1}),
dpf::geq(uint64_t{1}), 1, dpf::cmp_prefix<1>);
auto [z0, z1] = dpf::make_dpf(alpha, dpf::idcf(dpf::lt(uint64_t{1})));
auto [e0, e1] = dpf::make_dpf(alpha, dpf::idcf(dpf::leq(uint64_t{1})));
auto [g0, g1] = dpf::make_dpf(alpha, dpf::idcf(dpf::gt(uint64_t{1})));
auto [q0, q1] = dpf::make_dpf(alpha, dpf::idcf(dpf::geq(uint64_t{1})));
for (int x = 0; x < 256; ++x)
{
const auto q = static_cast<uint8_t>(x);
EXPECT_EQ(recon_cmp(z0, z1, dpf::cmp_prefix<0>, q), 0u);
EXPECT_EQ(recon_cmp(e0, e1, dpf::cmp_prefix<0>, q), 1u);
EXPECT_EQ(recon_cmp(g0, g1, dpf::cmp_prefix<0>, q), 0u);
EXPECT_EQ(recon_cmp(q0, q1, dpf::cmp_prefix<0>, q), 1u);
}
}
TEST(PathRecipe, DoernerShelatAndGenevalMatchLength)
{
constexpr uint8_t alpha = 0x91;
const uint8_t x0 = 0x10;
const uint8_t x1 = static_cast<uint8_t>(alpha ^ x0);
auto [k0, k1] = dpf::make_dpf_doerner_shelat(x0, x1, dpf::lcp(uint64_t{1}));
for (int x = 0; x < 256; ++x)
{
EXPECT_EQ(recon_cmp(k0, k1, dpf::cmp, static_cast<uint8_t>(x)),
lcp_len(static_cast<uint8_t>(x), alpha));
}
dpf::ds_randomness<simde__m128i (*)(), dpf::detail::urandom_pad_rng> rng{
dpf::uniform_sample<simde__m128i>, {}};
const uint8_t ends[] = {0x00, 0x91, 0xFF};
auto g = dpf::geneval_cmp(x0, x1, std::begin(ends), std::end(ends), rng,
dpf::lcp(uint64_t{1}));
ASSERT_EQ(g.party0.size(), 3u);
for (std::size_t i = 0; i < 3; ++i)
{
EXPECT_EQ((g.party0[i] + g.party1[i]) & g.mask,
lcp_len(ends[i], alpha));
}
}

View file

@ -0,0 +1,245 @@
#include <gtest/gtest.h>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <stdexcept>
#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<std::uint32_t>(lane + 1)
+ static_cast<std::uint32_t>(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<unsigned>(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<prg>(x, y);
for (int i = 0; i < 256; ++i)
{
auto y0 = *dpf::eval_point(k0, static_cast<std::uint8_t>(i));
auto y1 = *dpf::eval_point(k1, static_cast<std::uint8_t>(i));
auto sum = dpf::reconstruct(y0, y1);
EXPECT_EQ(sum, static_cast<std::uint8_t>(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<std::uint8_t>(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<prg, dpf::prg::chacha12>(x, y);
for (int i = 0; i < 256; ++i)
{
auto sum = dpf::reconstruct(
*dpf::eval_point(k0, static_cast<std::uint8_t>(i)),
*dpf::eval_point(k1, static_cast<std::uint8_t>(i)));
EXPECT_EQ(sum, static_cast<std::uint8_t>(i) == x ? y : 0u) << "i=" << i;
}
}
TEST(ChachaPrg, CounterWrapperCountsEval01)
{
using prg = dpf::prg::counter_wrapper<dpf::prg::chacha20>;
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<std::uint32_t, 0>(seed, 3);
auto t1 = prg::expand<std::uint32_t, 1>(seed, 3);
EXPECT_EQ(dpf::reconstruct(t0, t1), 0u);
auto u0 = prg::expand<std::uint64_t, 0>(seed, 0);
auto u1 = prg::expand<std::uint64_t, 1>(seed, 1);
EXPECT_NE(u0.raw(), u1.raw());
dpf::randomness::buffered_prg<prg, std::uint64_t, std::uint64_t> streamed(seed, 8);
auto s0 = streamed.get<0>();
auto s1 = streamed.get<1>();
dpf::randomness::buffered_prg<prg, std::uint64_t, std::uint64_t> 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>());
}

View file

@ -0,0 +1,246 @@
#include <gtest/gtest.h>
#include "grotto/range_lut.hpp"
#include <cmath>
#include <cstdint>
namespace
{
long double truth_of(grotto::reduced which, long double x)
{
switch (which)
{
case grotto::reduced::ln: return std::log(x);
case grotto::reduced::lg: return std::log2(x);
case grotto::reduced::log10: return std::log10(x);
case grotto::reduced::exp: return std::exp(x);
case grotto::reduced::exp2: return std::exp2(x);
case grotto::reduced::exp10: return std::exp(x * std::log(10.0L));
case grotto::reduced::sin: return std::sin(x);
case grotto::reduced::cos: return std::cos(x);
case grotto::reduced::tan: return std::tan(x);
case grotto::reduced::cot: return 1.0L / std::tan(x);
case grotto::reduced::sec: return 1.0L / std::cos(x);
case grotto::reduced::csc: return 1.0L / std::sin(x);
case grotto::reduced::sinh: return std::sinh(x);
case grotto::reduced::cosh: return std::cosh(x);
case grotto::reduced::tanh: return std::tanh(x);
case grotto::reduced::coth: return 1.0L / std::tanh(x);
case grotto::reduced::sech: return 1.0L / std::cosh(x);
case grotto::reduced::csch: return 1.0L / std::sinh(x);
case grotto::reduced::sqrt: return std::sqrt(x);
case grotto::reduced::inv: return 1.0L / x;
case grotto::reduced::rsqrt: return 1.0L / std::sqrt(x);
case grotto::reduced::invsq: return 1.0L / (x * x);
}
return 0;
}
const char * name_of(grotto::reduced which)
{
switch (which)
{
case grotto::reduced::ln: return "ln";
case grotto::reduced::lg: return "lg";
case grotto::reduced::log10: return "log10";
case grotto::reduced::exp: return "exp";
case grotto::reduced::exp2: return "exp2";
case grotto::reduced::exp10: return "exp10";
case grotto::reduced::sin: return "sin";
case grotto::reduced::cos: return "cos";
case grotto::reduced::tan: return "tan";
case grotto::reduced::cot: return "cot";
case grotto::reduced::sec: return "sec";
case grotto::reduced::csc: return "csc";
case grotto::reduced::sinh: return "sinh";
case grotto::reduced::cosh: return "cosh";
case grotto::reduced::tanh: return "tanh";
case grotto::reduced::coth: return "coth";
case grotto::reduced::sech: return "sech";
case grotto::reduced::csch: return "csch";
case grotto::reduced::sqrt: return "sqrt";
case grotto::reduced::inv: return "inv";
case grotto::reduced::rsqrt: return "rsqrt";
case grotto::reduced::invsq: return "invsq";
}
return "?";
}
std::int64_t raw_of(long double x, unsigned k)
{
const long double scaled = std::ldexp(x, static_cast<int>(k));
return std::llround(scaled);
}
void expect_ulps(grotto::reduced which, unsigned k, long double x, long double ulps)
{
const std::int64_t raw = raw_of(x, k);
std::int64_t got = 0;
ASSERT_NO_THROW(got = grotto::eval_reduced(which, k, raw))
<< name_of(which) << " k=" << k << " x=" << static_cast<double>(x);
const long double truth = truth_of(which, std::ldexp(static_cast<long double>(raw), -static_cast<int>(k)));
const long double want = truth * std::ldexp(1.0L, static_cast<int>(k));
EXPECT_LE(std::fabsl(static_cast<long double>(got) - want), ulps)
<< name_of(which) << " k=" << k << " x=" << static_cast<double>(x)
<< " got=" << got << " want=" << static_cast<double>(want);
}
bool near_odd_multiple_of_half_pi(long double x)
{
const long double turn = std::fmod(std::fabsl(x), 3.14159265358979323846L);
const long double dist = std::fmod(turn + 1.5707963267948966L, 3.14159265358979323846L);
const long double folded = dist > 1.5707963267948966L ? 3.14159265358979323846L - dist : dist;
return folded < 0.15L;
}
} // namespace
TEST(RangeLut, LogarithmsTrackLibmOnEveryPrecision)
{
const grotto::reduced maps[] = {
grotto::reduced::ln, grotto::reduced::lg, grotto::reduced::log10,
};
const long double samples[] = {
0.125L, 0.3L, 0.5L, 0.75L, 1.0L, 1.5L, 2.0L, 3.0L, 7.5L, 16.0L, 24.0L, 100.0L,
};
for (auto which : maps)
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
expect_ulps(which, k, x, 6.0L);
}
TEST(RangeLut, ExponentialsTrackLibmOnEveryPrecision)
{
const long double samples[] = {
-2.0L, -1.5L, -0.5L, -0.1L, 0.0L, 0.1L, 0.5L, 1.0L, 1.5L, 2.0L,
};
for (auto which : {grotto::reduced::exp, grotto::reduced::exp2})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
expect_ulps(which, k, x, 16.0L);
for (unsigned k : grotto::principal_precisions)
{
for (long double x : {-0.9L, -0.25L, 0.0L, 0.25L, 0.9L})
expect_ulps(grotto::reduced::exp10, k, x, 24.0L);
// 10^q scales the absolute error of exp(f ln 10) by the integer power.
for (long double x : {-1.5L, 1.5L})
expect_ulps(grotto::reduced::exp10, k, x, 80.0L);
}
}
TEST(RangeLut, QuarterTurnTrigTracksLibm)
{
const long double samples[] = {
-3.5L, -2.2L, -1.2L, -0.7L, -0.3L, 0.0L, 0.2L, 0.4L, 0.7L, 1.0L, 1.2L, 2.5L, 3.5L,
};
for (auto which : {grotto::reduced::sin, grotto::reduced::cos})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
expect_ulps(which, k, x, 3.0L);
for (auto which : {grotto::reduced::tan, grotto::reduced::sec})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
{
if (near_odd_multiple_of_half_pi(x))
continue;
expect_ulps(which, k, x, 8.0L);
}
for (auto which : {grotto::reduced::cot, grotto::reduced::csc})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
{
if (x == 0.0L || near_odd_multiple_of_half_pi(x))
continue;
expect_ulps(which, k, x, 8.0L);
}
}
TEST(RangeLut, HyperbolicReductionsTrackLibm)
{
const long double samples[] = {
-2.0L, -1.2L, -0.4L, -0.05L, 0.05L, 0.4L, 0.7L, 1.2L, 2.0L,
};
for (auto which : {grotto::reduced::sinh, grotto::reduced::cosh})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
expect_ulps(which, k, x, 12.0L);
for (auto which : {
grotto::reduced::tanh, grotto::reduced::sech, grotto::reduced::coth, grotto::reduced::csch,
})
{
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
{
if ((which == grotto::reduced::coth || which == grotto::reduced::csch) && std::fabsl(x) < 0.2L)
continue;
expect_ulps(which, k, x, 6.0L);
}
}
}
TEST(RangeLut, DyadicLiftsCoverOddAndEvenExponents)
{
const grotto::reduced maps[] = {
grotto::reduced::sqrt, grotto::reduced::inv, grotto::reduced::rsqrt, grotto::reduced::invsq,
};
const long double samples[] = {
0.125L, 0.3L, 0.5L, 0.75L, 1.0L, 1.5L, 2.0L, 3.0L, 6.0L, 7.5L, 16.0L, 24.0L, 48.0L, 100.0L,
};
for (auto which : maps)
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
{
const long double budget = which == grotto::reduced::sqrt ? 12.0L : 4.0L;
expect_ulps(which, k, x, budget);
}
}
TEST(RangeLut, TinyHyperbolicArgumentsUseThePrincipalTables)
{
for (unsigned k : grotto::principal_precisions)
{
if (k < 13)
continue;
const long double tiny = std::ldexp(1.0L, -16);
expect_ulps(grotto::reduced::sinh, k, tiny, 2.0L);
expect_ulps(grotto::reduced::cosh, k, tiny, 2.0L);
expect_ulps(grotto::reduced::sinh, k, -tiny, 2.0L);
expect_ulps(grotto::reduced::cosh, k, -tiny, 2.0L);
}
}
TEST(RangeLut, TanhAndCothSaturatePastBeta)
{
for (unsigned k : grotto::principal_precisions)
{
const std::int64_t one = std::int64_t{1} << k;
const std::int64_t raw = raw_of(20.0L, k);
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::tanh, k, raw), one);
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::tanh, k, -raw), -one);
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::coth, k, raw), one);
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::coth, k, -raw), -one);
}
}
TEST(RangeLut, SquareRootOfZeroIsZero)
{
for (unsigned k : grotto::principal_precisions)
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::sqrt, k, 0), 0);
}
TEST(RangeLut, RejectsPolesAndNonPositiveLogarithms)
{
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::ln, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::ln, 16, -4), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::lg, 16, -1), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::sqrt, 16, -4), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::inv, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::rsqrt, 16, -8), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::cot, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::csc, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::coth, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::csch, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::ln, 7, 32), std::invalid_argument);
}

View file

@ -6,6 +6,7 @@
#include <array>
#include <cstdint>
#include <limits>
#include <vector>
namespace
@ -141,3 +142,54 @@ TEST(SignedPrefix, RejectsAKeyWithoutAComparison)
EXPECT_THROW(grotto::signed_prefix_parities(k0, ends), std::invalid_argument);
EXPECT_THROW(grotto::signed_segment_parities(k1, ends), std::invalid_argument);
}
template <std::size_t N>
void expect_ilogb_segments(const grotto::constant_lut<int8_t> & lut, int8_t alpha)
{
if (lut.bounds.size() != N)
return;
std::array<int8_t, N> ends{};
for (std::size_t i = 0; i < N; ++i)
ends[i] = lut.bounds[i];
auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(uint64_t{1}));
const uint64_t mask = k0.cmp().mask;
const auto s0 = grotto::signed_segment_parities(k0, ends);
const auto s1 = grotto::signed_segment_parities(k1, ends);
uint64_t acc = 0;
for (std::size_t i = 0; i < N; ++i)
{
const uint64_t bit = opened(s0[i], s1[i], mask);
acc += bit * static_cast<uint64_t>(lut.values[i]);
}
EXPECT_EQ(acc, static_cast<uint64_t>(lut(alpha))) << int(alpha);
}
template <std::size_t N>
void dispatch_ilogb(const grotto::constant_lut<int8_t> & lut, int8_t alpha, bool & matched)
{
if (lut.bounds.size() == N)
{
expect_ilogb_segments<N>(lut, alpha);
matched = true;
return;
}
if constexpr (N > 1)
dispatch_ilogb<N - 1>(lut, alpha, matched);
}
TEST(SignedPrefix, SegmentsRecoverIlogbInt8)
{
for (unsigned frac : {0u, 4u})
{
const auto lut = grotto::make_exact_constant_lut<int8_t>(
grotto::exact_constant::ilogb, frac);
ASSERT_GE(lut.bounds.size(), 3u);
ASSERT_LE(lut.bounds.size(), 40u);
for (int v = -128; v <= 127; ++v)
{
bool matched = false;
dispatch_ilogb<40>(lut, static_cast<int8_t>(v), matched);
ASSERT_TRUE(matched) << lut.bounds.size();
}
}
}

View file

@ -145,7 +145,8 @@ bool same_cmp_channel(const Key & a, const Key & b)
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.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(),
@ -874,6 +875,32 @@ TEST_F(StressScenariosTest, PrgDummyAesClassicPoint)
}
}
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;