#include #include #include #include #include #include #include #include #include #include #include "dpf.hpp" namespace { template auto recon(const A & a, const B & b) { if constexpr (dpf::is_secret_share_v> && dpf::is_secret_share_v>) return dpf::reconstruct(a, b); else { using T = std::common_type_t, std::decay_t>; if constexpr (std::is_integral_v && std::is_unsigned_v) return static_cast(a - b); else return a - b; } } template void assign_input_local(Key0 & k0, Key1 & k1, InputT alpha) { using input_type = InputT; const input_type a0 = static_cast(0x12); const input_type a1 = static_cast(alpha - a0); const auto sh0 = k0.offset_x.compute_and_get_share(a0); const auto sh1 = k1.offset_x.compute_and_get_share(a1); k0.offset_x.reconstruct(sh1); k1.offset_x.reconstruct(sh0); } template void expect_recon_equal(Def0 && deferred0, Def1 && deferred1, Eager0 && eager0, Eager1 && eager1) { auto it_a = std::begin(deferred0); auto it_b = std::begin(deferred1); auto it_c = std::begin(eager0); auto it_d = std::begin(eager1); const auto end_a = std::end(deferred0); std::size_t n = 0; while (it_a != end_a) { ASSERT_NE(it_b, std::end(deferred1)) << "at index " << n; ASSERT_NE(it_c, std::end(eager0)) << "at index " << n; ASSERT_NE(it_d, std::end(eager1)) << "at index " << n; EXPECT_EQ(recon(*it_a, *it_b), recon(*it_c, *it_d)) << "at index " << n; ++it_a; ++it_b; ++it_c; ++it_d; ++n; } EXPECT_EQ(it_b, std::end(deferred1)); EXPECT_EQ(it_c, std::end(eager0)); EXPECT_EQ(it_d, std::end(eager1)); EXPECT_GT(n, std::size_t{0}); } template std::size_t inclusive_span(InputT from, InputT to) { constexpr auto bits = dpf::utils::bitlength_of_v; constexpr auto to_int = dpf::utils::to_integral_type{}; auto span = to_int(to) - to_int(from); if constexpr (bits < dpf::utils::bitlength_of_v) span &= (decltype(span){1} << bits) - 1; return static_cast(span) + 1; } template void expect_point_mass(View0 && v0, View1 && v1, InputT from, InputT to, InputT alpha, OutputT beta) { auto it0 = std::begin(v0); auto it1 = std::begin(v1); bool saw = false; InputT x = from; for (std::size_t i = 0; i < inclusive_span(from, to); ++i) { ASSERT_NE(it0, std::end(v0)); ASSERT_NE(it1, std::end(v1)); const auto y = recon(*it0, *it1); if (x == alpha) { EXPECT_EQ(y, beta) << "x=" << +x; saw = true; } else { EXPECT_EQ(y, OutputT{0}) << "x=" << +x; } ++it0; ++it1; ++x; } EXPECT_EQ(it0, std::end(v0)); EXPECT_EQ(it1, std::end(v1)); bool expect_hit = false; if constexpr (std::is_unsigned_v) { if (from <= to) expect_hit = (alpha >= from && alpha <= to); else expect_hit = (alpha >= from) || (alpha <= to); } else { expect_hit = (alpha >= from && alpha <= to); } EXPECT_EQ(saw, expect_hit); } template void compare_deferred_interval(Key0 & d0, Key1 & d1, InputT from, InputT to, InputT alpha, OutputT beta) { auto buf0 = dpf::make_output_buffer_for_full(d0); auto buf1 = dpf::make_output_buffer_for_full(d1); auto deferred0 = dpf::defer_eval_interval(d0, from, to, buf0); auto deferred1 = dpf::defer_eval_interval(d1, from, to, buf1); assign_input_local(d0, d1, alpha); auto eager_buf0 = dpf::make_output_buffer_for_interval(d0, from, to); auto eager_buf1 = dpf::make_output_buffer_for_interval(d1, from, to); auto eager_memo0 = dpf::make_basic_full_memoizer(d0); auto eager_memo1 = dpf::make_basic_full_memoizer(d1); auto eager0 = dpf::eval_interval(d0, from, to, eager_buf0, eager_memo0); auto eager1 = dpf::eval_interval(d1, from, to, eager_buf1, eager_memo1); auto view0 = deferred0.get(); auto view1 = deferred1.get(); expect_recon_equal(view0, view1, eager0, eager1); expect_point_mass(view0, view1, from, to, alpha, beta); } } // namespace // --------------------------------------------------------------------------- // Happy paths already covered lightly; keep regressions + expand corners. // --------------------------------------------------------------------------- TEST(DeferEvalTest, IntervalMatchesEagerAfterAssignUint8) { using input_type = std::uint8_t; using output_type = std::uint32_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{7}); compare_deferred_interval(d0, d1, input_type{0x10}, input_type{0x40}, input_type{0x2A}, output_type{7}); } TEST(DeferEvalTest, FullMatchesEagerAfterAssignUint8) { using input_type = std::uint8_t; using output_type = std::uint64_t; constexpr output_type beta{0x1111}; constexpr input_type alpha{0x33}; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, beta); auto buf0 = dpf::make_output_buffer_for_full(d0); auto buf1 = dpf::make_output_buffer_for_full(d1); auto deferred0 = dpf::defer_eval_full(d0, buf0); auto deferred1 = dpf::defer_eval_full(d1, buf1); assign_input_local(d0, d1, alpha); auto eager_buf0 = dpf::make_output_buffer_for_full(d0); auto eager_buf1 = dpf::make_output_buffer_for_full(d1); auto eager0 = dpf::eval_full(d0, eager_buf0); auto eager1 = dpf::eval_full(d1, eager_buf1); auto view0 = deferred0.get(); auto view1 = deferred1.get(); expect_recon_equal(view0, view1, eager0, eager1); expect_point_mass(view0, view1, std::numeric_limits::min(), std::numeric_limits::max(), alpha, beta); } TEST(DeferEvalTest, IntervalMatchesEagerAfterAssignSigned) { using input_type = std::int8_t; using output_type = std::uint32_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{9}); compare_deferred_interval(d0, d1, input_type{-40}, input_type{10}, input_type{-20}, output_type{9}); } TEST(DeferEvalTest, MultiOutputLeafMatchesEager) { using input_type = std::uint8_t; using output_type = std::uint64_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{0xABCDEF0123456789ull}); ASSERT_GT(decltype(d0)::outputs_per_leaf, std::size_t{1}); compare_deferred_interval(d0, d1, input_type{0x70}, input_type{0x8F}, input_type{0x7E}, output_type{0xABCDEF0123456789ull}); } // --------------------------------------------------------------------------- // Assert / misuse corners // --------------------------------------------------------------------------- TEST(DeferEvalTest, GetBeforeAssignThrows) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{1}); (void)d1; auto buf = dpf::make_output_buffer_for_full(d0); auto deferred = dpf::defer_eval_interval(d0, input_type{0}, input_type{3}, buf); EXPECT_THROW(static_cast(deferred.get()), std::runtime_error); EXPECT_THROW(static_cast(deferred.begin()), std::runtime_error); } TEST(DeferEvalTest, DeferAfterAssignThrows) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{1}); assign_input_local(d0, d1, input_type{9}); auto buf = dpf::make_output_buffer_for_full(d0); EXPECT_THROW( static_cast(dpf::defer_eval_interval(d0, input_type{0}, input_type{3}, buf)), std::runtime_error); EXPECT_THROW(static_cast(dpf::defer_eval_full(d0, buf)), std::runtime_error); } TEST(DeferEvalTest, EagerEvalBeforeAssignThrows) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{1}); (void)d1; EXPECT_THROW(static_cast(dpf::eval_point(d0, input_type{0})), std::runtime_error); EXPECT_THROW(static_cast(dpf::eval_interval(d0, input_type{0}, input_type{1})), std::runtime_error); EXPECT_THROW(static_cast(dpf::eval_full(d0)), std::runtime_error); } TEST(DeferEvalTest, DeferTraverseIntervalRequiresAssignedInput) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{1}); auto memo = dpf::make_basic_full_memoizer(d0); EXPECT_THROW( dpf::defer_traverse_interval(d0, input_type{0}, input_type{10}, memo), std::runtime_error); assign_input_local(d0, d1, input_type{4}); EXPECT_NO_THROW( dpf::defer_traverse_interval(d0, input_type{0}, input_type{10}, memo)); } TEST(DeferEvalTest, DeferTraverseFullAllowsUnassignedInput) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{3}); auto memo0 = dpf::make_basic_full_memoizer(d0); auto memo1 = dpf::make_basic_full_memoizer(d1); EXPECT_NO_THROW(dpf::defer_traverse_full(d0, memo0)); EXPECT_NO_THROW(dpf::defer_traverse_full(d1, memo1)); assign_input_local(d0, d1, input_type{0x55}); EXPECT_EQ(recon(*dpf::eval_point(d0, input_type{0x55}), *dpf::eval_point(d1, input_type{0x55})), std::uint32_t{3}); } // --------------------------------------------------------------------------- // Boundary / length / caching // --------------------------------------------------------------------------- TEST(DeferEvalTest, SinglePointInterval) { using input_type = std::uint8_t; using output_type = std::uint32_t; constexpr input_type alpha{0x77}; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{42}); compare_deferred_interval(d0, d1, alpha, alpha, alpha, output_type{42}); } TEST(DeferEvalTest, SpikeAtFromAndToBoundaries) { using input_type = std::uint8_t; using output_type = std::uint32_t; { auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{5}); compare_deferred_interval(d0, d1, input_type{0x20}, input_type{0x30}, input_type{0x20}, output_type{5}); } { auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{6}); compare_deferred_interval(d0, d1, input_type{0x20}, input_type{0x30}, input_type{0x30}, output_type{6}); } } TEST(DeferEvalTest, SpikeOutsideIntervalIsZero) { using input_type = std::uint8_t; using output_type = std::uint32_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{99}); compare_deferred_interval(d0, d1, input_type{0x10}, input_type{0x20}, input_type{0x80}, output_type{99}); } TEST(DeferEvalTest, ViewLengthMatchesInclusiveSpan) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{1}); constexpr input_type from{5}; constexpr input_type to{12}; auto buf0 = dpf::make_output_buffer_for_full(d0); auto buf1 = dpf::make_output_buffer_for_full(d1); auto deferred0 = dpf::defer_eval_interval(d0, from, to, buf0); auto deferred1 = dpf::defer_eval_interval(d1, from, to, buf1); assign_input_local(d0, d1, input_type{7}); auto view0 = deferred0.get(); auto view1 = deferred1.get(); EXPECT_EQ(static_cast(std::distance(std::begin(view0), std::end(view0))), inclusive_span(from, to)); EXPECT_EQ(static_cast(std::distance(std::begin(view1), std::end(view1))), inclusive_span(from, to)); } TEST(DeferEvalTest, GetCachesRotationSecondCallMatches) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{11}); auto buf0 = dpf::make_output_buffer_for_full(d0); auto buf1 = dpf::make_output_buffer_for_full(d1); auto deferred0 = dpf::defer_eval_interval(d0, input_type{1}, input_type{20}, buf0); auto deferred1 = dpf::defer_eval_interval(d1, input_type{1}, input_type{20}, buf1); assign_input_local(d0, d1, input_type{9}); auto a0 = deferred0.get(); auto a1 = deferred1.get(); auto b0 = deferred0.get(); auto b1 = deferred1.get(); expect_recon_equal(a0, a1, b0, b1); } TEST(DeferEvalTest, BeginEndOnDeferredObject) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{2}); auto buf0 = dpf::make_output_buffer_for_full(d0); auto buf1 = dpf::make_output_buffer_for_full(d1); auto deferred0 = dpf::defer_eval_interval(d0, input_type{0}, input_type{4}, buf0); auto deferred1 = dpf::defer_eval_interval(d1, input_type{0}, input_type{4}, buf1); assign_input_local(d0, d1, input_type{2}); std::size_t n = 0; auto it0 = deferred0.begin(); auto it1 = deferred1.begin(); for (; it0 != deferred0.end(); ++it0, ++it1, ++n) (void)recon(*it0, *it1); EXPECT_EQ(it1, deferred1.end()); EXPECT_EQ(n, inclusive_span(input_type{0}, input_type{4})); } TEST(DeferEvalTest, DeferEvalIntervalMinMaxMatchesDeferFull) { using input_type = std::uint8_t; using output_type = std::uint32_t; constexpr output_type beta{13}; constexpr input_type alpha{0x01}; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, beta); auto buf_i0 = dpf::make_output_buffer_for_full(d0); auto buf_i1 = dpf::make_output_buffer_for_full(d1); auto buf_f0 = dpf::make_output_buffer_for_full(d0); auto buf_f1 = dpf::make_output_buffer_for_full(d1); auto as_interval0 = dpf::defer_eval_interval(d0, std::numeric_limits::min(), std::numeric_limits::max(), buf_i0); auto as_interval1 = dpf::defer_eval_interval(d1, std::numeric_limits::min(), std::numeric_limits::max(), buf_i1); auto as_full0 = dpf::defer_eval_full(d0, buf_f0); auto as_full1 = dpf::defer_eval_full(d1, buf_f1); assign_input_local(d0, d1, alpha); expect_recon_equal(as_interval0.get(), as_interval1.get(), as_full0.get(), as_full1.get()); } // --------------------------------------------------------------------------- // Wrapping intervals and offset stress // --------------------------------------------------------------------------- TEST(DeferEvalTest, WrappingLogicalIntervalUint8) { using input_type = std::uint8_t; using output_type = std::uint32_t; // [200, 10] wraps across 0. constexpr input_type from{200}; constexpr input_type to{10}; ASSERT_GT(from, to); auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{8}); compare_deferred_interval(d0, d1, from, to, input_type{250}, output_type{8}); } TEST(DeferEvalTest, WrappingIntervalSpikeInLowHalf) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{4}); compare_deferred_interval(d0, d1, input_type{200}, input_type{10}, input_type{5}, std::uint32_t{4}); } TEST(DeferEvalTest, ManyRandomOffsetsMatchEager) { using input_type = std::uint8_t; using output_type = std::uint32_t; constexpr input_type from{30}; constexpr input_type to{90}; constexpr output_type beta{77}; // Sweep alphas; each keygen draws a fresh mask so offsets differ. for (unsigned a = 0; a < 256; a += 17) { auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, beta); compare_deferred_interval(d0, d1, from, to, static_cast(a), beta); } } TEST(DeferEvalTest, SignedFullDomain) { using input_type = std::int8_t; using output_type = std::uint32_t; constexpr output_type beta{21}; constexpr input_type alpha{-128}; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, beta); auto buf0 = dpf::make_output_buffer_for_full(d0); auto buf1 = dpf::make_output_buffer_for_full(d1); auto deferred0 = dpf::defer_eval_full(d0, buf0); auto deferred1 = dpf::defer_eval_full(d1, buf1); assign_input_local(d0, d1, alpha); auto eager_buf0 = dpf::make_output_buffer_for_full(d0); auto eager_buf1 = dpf::make_output_buffer_for_full(d1); auto eager0 = dpf::eval_full(d0, eager_buf0); auto eager1 = dpf::eval_full(d1, eager_buf1); expect_recon_equal(deferred0.get(), deferred1.get(), eager0, eager1); } TEST(DeferEvalTest, SignedSpanCrossingZero) { using input_type = std::int8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{3}); compare_deferred_interval(d0, d1, input_type{-5}, input_type{5}, input_type{0}, std::uint32_t{3}); } TEST(DeferEvalTest, UnalignedMultiOutputLeafInterval) { using input_type = std::uint8_t; using output_type = std::uint64_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, output_type{0x55}); ASSERT_GT(decltype(d0)::outputs_per_leaf, std::size_t{1}); // Odd endpoints: not leaf-aligned when opl == 2. compare_deferred_interval(d0, d1, input_type{0x11}, input_type{0x2A}, input_type{0x1F}, output_type{0x55}); } // --------------------------------------------------------------------------- // Multi-output keys // --------------------------------------------------------------------------- TEST(DeferEvalTest, TwoOutputSlotsIndependent) { using input_type = std::uint8_t; using out0 = std::uint32_t; using out1 = std::uint32_t; constexpr input_type alpha{0x44}; constexpr out0 beta0{100}; constexpr out1 beta1{200}; constexpr input_type from{0x40}; constexpr input_type to{0x50}; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, beta0, beta1); auto bufs0 = dpf::make_output_buffer_for_full<0, 1>(d0); auto bufs1 = dpf::make_output_buffer_for_full<0, 1>(d1); auto def0 = dpf::defer_eval_interval<0, 1>(d0, from, to, bufs0); auto def1 = dpf::defer_eval_interval<0, 1>(d1, from, to, bufs1); assign_input_local(d0, d1, alpha); auto eager_bufs0 = dpf::make_output_buffer_for_interval<0, 1>(d0, from, to); auto eager_bufs1 = dpf::make_output_buffer_for_interval<0, 1>(d1, from, to); auto memo0 = dpf::make_basic_full_memoizer(d0); auto memo1 = dpf::make_basic_full_memoizer(d1); auto eager0 = dpf::eval_interval<0, 1>(d0, from, to, eager_bufs0, memo0); auto eager1 = dpf::eval_interval<0, 1>(d1, from, to, eager_bufs1, memo1); expect_recon_equal(std::get<0>(def0).get(), std::get<0>(def1).get(), std::get<0>(eager0), std::get<0>(eager1)); expect_recon_equal(std::get<1>(def0).get(), std::get<1>(def1).get(), std::get<1>(eager0), std::get<1>(eager1)); expect_point_mass(std::get<0>(def0).get(), std::get<0>(def1).get(), from, to, alpha, beta0); expect_point_mass(std::get<1>(def0).get(), std::get<1>(def1).get(), from, to, alpha, beta1); } TEST(DeferEvalTest, SelectSecondOutputOnly) { using input_type = std::uint8_t; constexpr input_type alpha{0x08}; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{1}, std::uint32_t{99}); auto buf0 = dpf::make_output_buffer_for_full<1>(d0); auto buf1 = dpf::make_output_buffer_for_full<1>(d1); auto def0 = dpf::defer_eval_interval<1>(d0, input_type{0}, input_type{15}, buf0); auto def1 = dpf::defer_eval_interval<1>(d1, input_type{0}, input_type{15}, buf1); assign_input_local(d0, d1, alpha); auto eager_buf0 = dpf::make_output_buffer_for_interval<1>(d0, input_type{0}, input_type{15}); auto eager_buf1 = dpf::make_output_buffer_for_interval<1>(d1, input_type{0}, input_type{15}); auto memo0 = dpf::make_basic_full_memoizer(d0); auto memo1 = dpf::make_basic_full_memoizer(d1); auto eager0 = dpf::eval_interval<1>(d0, input_type{0}, input_type{15}, eager_buf0, memo0); auto eager1 = dpf::eval_interval<1>(d1, input_type{0}, input_type{15}, eager_buf1, memo1); expect_recon_equal(def0.get(), def1.get(), eager0, eager1); expect_point_mass(def0.get(), def1.get(), input_type{0}, input_type{15}, alpha, std::uint32_t{99}); } // --------------------------------------------------------------------------- // Metadata on the deferred object // --------------------------------------------------------------------------- TEST(DeferEvalTest, DeferredStoresFromToAndKey) { using input_type = std::uint8_t; auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{1}); (void)d1; auto buf = dpf::make_output_buffer_for_full(d0); auto deferred = dpf::defer_eval_interval(d0, input_type{3}, input_type{9}, buf); EXPECT_EQ(deferred.from(), input_type{3}); EXPECT_EQ(deferred.to(), input_type{9}); EXPECT_EQ(&deferred.dpf(), &d0); } TEST(DeferEvalTest, DomainMinMaxSpike) { using input_type = std::uint8_t; { auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{1}); compare_deferred_interval(d0, d1, input_type{0}, input_type{255}, input_type{0}, std::uint32_t{1}); } { auto [d0, d1] = dpf::make_dpf(dpf::wildcard_value{}, std::uint32_t{2}); compare_deferred_interval(d0, d1, input_type{0}, input_type{255}, input_type{255}, std::uint32_t{2}); } }