#include #include #include #include #include #include #include #include #include #include "grotto.hpp" namespace { struct bus { std::mutex mu; std::condition_variable cv; std::uint64_t word[2]{}; std::uint64_t deliver[2]{}; int epoch = 0; int arrived = 0; int sent0 = 0; std::uint64_t exchange(int me, std::uint64_t mine) { std::unique_lock lock(mu); const int seen = epoch; word[me] = mine; if (me == 0) ++sent0; if (++arrived == 2) { deliver[0] = word[1]; deliver[1] = word[0]; arrived = 0; ++epoch; cv.notify_all(); } else cv.wait(lock, [&] { return epoch != seen; }); return deliver[me]; } }; template std::pair split_word(T secret) { using U = std::make_unsigned_t; const U mask = static_cast(~U{0}); const U bits = static_cast(secret); const U share = static_cast(dpf::uniform_sample() & mask); const U other = static_cast(bits - share); return {static_cast(share), static_cast(other)}; } template simde_uint128 raw_bits(Fixed value) { std::uint64_t limb[4] = {}; grotto::detail::store_raw_limbs(value.integral_representation(), limb); return simde_uint128{limb[0]} | (simde_uint128{limb[1]} << 64); } template void expect_product(std::pair lhs, std::pair rhs) { using shape = grotto::fixed_mul_beaver_shape; ASSERT_TRUE(shape::fits); static const auto prep = grotto::make_fixed_mul_beaver_prep(); const auto triple = grotto::sample_fixed_mul_beaver_triple(shape::plan::multiply_bits); bus link; using result = typename shape::plan::result_type; result y0{}; result y1{}; std::exception_ptr e0; std::exception_ptr e1; std::thread t0([&] { try { y0 = grotto::eval_fixed_mul_beaver( prep, triple, 0, lhs.first, rhs.first, [&](std::uint64_t mine) { return link.exchange(0, mine); }); } catch (...) { e0 = std::current_exception(); } }); std::thread t1([&] { try { y1 = grotto::eval_fixed_mul_beaver( prep, triple, 1, lhs.second, rhs.second, [&](std::uint64_t mine) { return link.exchange(1, mine); }); } catch (...) { e1 = std::current_exception(); } }); t0.join(); t1.join(); if (e0) std::rethrow_exception(e0); if (e1) std::rethrow_exception(e1); EXPECT_EQ(link.sent0, static_cast(shape::messages)); const auto clear = grotto::fixed_mul( grotto::make_fixed_from_integral_type( static_cast(static_cast>(lhs.first) + static_cast>(lhs.second))), grotto::make_fixed_from_integral_type( static_cast(static_cast>(rhs.first) + static_cast>(rhs.second)))); const unsigned storage = shape::storage_bits; const simde_uint128 mask = storage >= 128u ? ~simde_uint128{0} : (simde_uint128{1} << storage) - 1; const auto got = (raw_bits(y0) + raw_bits(y1)) & mask; const auto want = raw_bits(clear) & mask; EXPECT_EQ(static_cast(got), static_cast(want)); EXPECT_EQ(static_cast(got >> 64), static_cast(want >> 64)); } template T reconstruct(std::pair shares) { using U = std::make_unsigned_t; return static_cast(static_cast(shares.first) + static_cast(shares.second)); } } // namespace TEST(FixedMulBeaver, EmptyWindowIsZeroAndSilent) { using shape = grotto::fixed_mul_beaver_shape<0, 4, 0, std::int32_t, 0, std::int32_t>; EXPECT_TRUE(shape::fits); EXPECT_FALSE(shape::active); EXPECT_EQ(shape::messages, 0u); auto prep = grotto::make_fixed_mul_beaver_prep<0, 4, 0, std::int32_t, 0, std::int32_t>(); grotto::fixed_mul_beaver_triple triple; int calls = 0; auto y = grotto::eval_fixed_mul_beaver<0, 4, 0, std::int32_t, 0, std::int32_t>( prep, triple, 0, 7, 9, [&](std::uint64_t) { ++calls; return std::uint64_t{0}; }); EXPECT_EQ(calls, 0); EXPECT_EQ(y.integral_representation(), 0); } TEST(FixedMulBeaver, WideSignedWindowDoesNotFit) { using shape = grotto::fixed_mul_beaver_shape<16, 64, 16, std::int32_t, 16, std::int32_t>; EXPECT_FALSE(shape::fits); EXPECT_TRUE(shape::result_lift); EXPECT_GT(shape::plan::out_bits, 64u); } TEST(FixedMulBeaver, TruncationIsOnlyTheBeaverOpening) { using L = std::uint32_t; using shape = grotto::fixed_mul_beaver_shape<16, 0, 0, L, 0, L>; EXPECT_TRUE(shape::fits); EXPECT_FALSE(shape::lhs_lift); EXPECT_FALSE(shape::shift_right); EXPECT_EQ(shape::messages, 2u); auto prep = grotto::make_fixed_mul_beaver_prep<16, 0, 0, L, 0, L>(); for (int i = 0; i < 8; ++i) { const auto lhs = split_word(i == 0 ? L{7} : dpf::uniform_sample()); const auto rhs = split_word(i == 0 ? L{9} : dpf::uniform_sample()); const auto triple = grotto::sample_fixed_mul_beaver_triple(16u); bus link; auto y0 = grotto::fixedpoint<0, std::uint16_t>{}; auto y1 = y0; std::thread t0([&] { y0 = grotto::eval_fixed_mul_beaver<16, 0, 0, L, 0, L>( prep, triple, 0, lhs.first, rhs.first, [&](std::uint64_t mine) { return link.exchange(0, mine); }); }); std::thread t1([&] { y1 = grotto::eval_fixed_mul_beaver<16, 0, 0, L, 0, L>( prep, triple, 1, lhs.second, rhs.second, [&](std::uint64_t mine) { return link.exchange(1, mine); }); }); t0.join(); t1.join(); EXPECT_EQ(link.sent0, 2); const auto clear = grotto::fixed_mul<16, 0>( grotto::make_fixed_from_integral_type<0, L>(reconstruct(lhs)), grotto::make_fixed_from_integral_type<0, L>(reconstruct(rhs))); const auto got = static_cast( static_cast(y0.integral_representation()) + static_cast(y1.integral_representation())); EXPECT_EQ(got, clear.integral_representation()); } } TEST(FixedMulBeaver, NegativeProductFloorsIntoTheWindow) { using Q = std::int32_t; const auto lhs = split_word(Q{-3}); const auto rhs = split_word(Q{1}); expect_product<8, 4, 4, Q, 4, Q>(lhs, rhs); const auto neg = split_word(Q{-2}); expect_product<8, 4, 4, Q, 4, Q>(lhs, neg); } TEST(FixedMulBeaver, SignedLiftAndRightShift) { using Q = std::int32_t; using shape = grotto::fixed_mul_beaver_shape<16, 16, 16, Q, 16, Q>; EXPECT_TRUE(shape::lhs_lift); EXPECT_TRUE(shape::shift_right); EXPECT_FALSE(shape::product_lift); EXPECT_EQ(shape::plan::multiply_bits, 48u); EXPECT_EQ(shape::messages, 5u); auto prep = grotto::make_fixed_mul_beaver_prep<16, 16, 16, Q, 16, Q>(); const Q samples[][2] = { {0, 0}, {1 << 16, 2 << 16}, {-(3 << 16), 1 << 16}, {-(3 << 16), -(2 << 16)}, {std::numeric_limits::max(), -1}, {std::numeric_limits::min(), 1}, }; for (const auto & sample : samples) { const auto triple = grotto::sample_fixed_mul_beaver_triple(48u); const auto lhs = split_word(sample[0]); const auto rhs = split_word(sample[1]); bus link; using result = typename shape::plan::result_type; result y0{}; result y1{}; std::thread t0([&] { y0 = grotto::eval_fixed_mul_beaver<16, 16, 16, Q, 16, Q>( prep, triple, 0, lhs.first, rhs.first, [&](std::uint64_t mine) { return link.exchange(0, mine); }); }); std::thread t1([&] { y1 = grotto::eval_fixed_mul_beaver<16, 16, 16, Q, 16, Q>( prep, triple, 1, lhs.second, rhs.second, [&](std::uint64_t mine) { return link.exchange(1, mine); }); }); t0.join(); t1.join(); EXPECT_EQ(link.sent0, 5); const auto clear = grotto::fixed_mul<16, 16>( grotto::make_fixed_from_integral_type<16, Q>(reconstruct(lhs)), grotto::make_fixed_from_integral_type<16, Q>(reconstruct(rhs))); const auto got = static_cast(y0.integral_representation()) + static_cast(y1.integral_representation()); EXPECT_EQ(got, static_cast(clear.integral_representation())) << "lhs=" << sample[0] << " rhs=" << sample[1]; } } TEST(FixedMulBeaver, LeftShiftAndUnsignedLift) { using L = std::uint16_t; using shape = grotto::fixed_mul_beaver_shape<4, 8, 0, L, 0, L>; EXPECT_TRUE(shape::shift_left); EXPECT_TRUE(shape::fits); const auto lhs = split_word(L{3}); const auto rhs = split_word(L{5}); expect_product<4, 8, 0, L, 0, L>(lhs, rhs); } TEST(FixedMulBeaver, NarrowProductIsSignExtendedBeforeTheWindow) { using Q = std::int8_t; using shape = grotto::fixed_mul_beaver_shape<16, 8, 4, Q, 4, Q>; EXPECT_TRUE(shape::fits); EXPECT_TRUE(shape::lhs_lift); EXPECT_TRUE(shape::product_lift); EXPECT_TRUE(shape::result_lift); EXPECT_FALSE(shape::shift_right); EXPECT_EQ(shape::messages, 6u); const Q samples[] = {0, 1, -1, -3, 7, -8, 12, std::numeric_limits::max(), std::numeric_limits::min()}; for (Q a : samples) for (Q b : samples) expect_product<16, 8, 4, Q, 4, Q>(split_word(a), split_word(b)); } TEST(FixedMulBeaver, Int64WindowUsesA96BitProduct) { using Q = std::int64_t; using shape = grotto::fixed_mul_beaver_shape<32, 32, 32, Q, 32, Q>; ASSERT_TRUE(shape::fits); EXPECT_EQ(shape::plan::multiply_bits, 96u); EXPECT_EQ(shape::plan::modulus_bits, 96u); EXPECT_TRUE(shape::lhs_lift); EXPECT_EQ(shape::messages, 8u); const Q samples[][2] = { {0, 0}, {Q{3} << 32, Q{5} << 32}, {-(Q{3} << 32), Q{1} << 32}, {-(Q{4} << 32), -(Q{2} << 32)}, }; for (const auto & sample : samples) expect_product<32, 32, 32, Q, 32, Q>(split_word(sample[0]), split_word(sample[1])); }