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:
parent
875f09fec1
commit
0d8a5a8131
97 changed files with 9212 additions and 1159 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
404
test/tests/blocked_dcf_test.cpp
Normal file
404
test/tests/blocked_dcf_test.cpp
Normal 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]);
|
||||
}
|
||||
390
test/tests/corner_gaps_test.cpp
Normal file
390
test/tests/corner_gaps_test.cpp
Normal 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);
|
||||
}
|
||||
215
test/tests/dyadic_lut_test.cpp
Normal file
215
test/tests/dyadic_lut_test.cpp
Normal 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);
|
||||
}
|
||||
|
|
@ -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
224
test/tests/ic_test.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
176
test/tests/nmod_test.cpp
Normal 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);
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
223
test/tests/path_recipe_test.cpp
Normal file
223
test/tests/path_recipe_test.cpp
Normal 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));
|
||||
}
|
||||
}
|
||||
245
test/tests/prg_chacha_test.cpp
Normal file
245
test/tests/prg_chacha_test.cpp
Normal 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>());
|
||||
}
|
||||
246
test/tests/range_lut_test.cpp
Normal file
246
test/tests/range_lut_test.cpp
Normal 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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue