#include #include #include #include #include #include #include #include #include "dpf.hpp" namespace { struct dpf3_party_tag { static constexpr bool is_dpf3 = true; static constexpr int party = 3; }; TEST(SecretShare, LayoutMatchesValueType) { using T = std::uint64_t; EXPECT_EQ(sizeof(dpf::additive_share), sizeof(T)); EXPECT_EQ(sizeof(dpf::subtractive_share), sizeof(T)); EXPECT_TRUE((std::is_trivially_copyable_v>)); EXPECT_TRUE((std::is_standard_layout_v>)); } TEST(SecretShare, PlaintextSplitOpens) { const std::uint32_t secret = 0xdeadbeefu; auto [a0, a1] = dpf::make_additive_shares(secret); EXPECT_EQ(dpf::reconstruct(a0, a1), secret); EXPECT_EQ(a1.raw(), 0u); auto [s0, s1] = dpf::make_subtractive_shares(secret); EXPECT_EQ(dpf::reconstruct(s0, s1), secret); EXPECT_EQ(s1.raw(), 0u); } TEST(SecretShare, SameSchemeLinearCombo) { auto [a0, a1] = dpf::make_additive_shares(std::uint32_t{10}); auto [b0, b1] = dpf::make_additive_shares(std::uint32_t{3}); auto c0 = a0 * 2 + b0; auto c1 = a1 * 2 + b1; EXPECT_EQ(dpf::reconstruct(c0, c1), 23u); auto [s0, s1] = dpf::make_subtractive_shares(std::uint32_t{10}); auto [t0, t1] = dpf::make_subtractive_shares(std::uint32_t{3}); auto u0 = s0 * 2 - t0; auto u1 = s1 * 2 - t1; EXPECT_EQ(dpf::reconstruct(u0, u1), 17u); } TEST(SecretShare, CrossSchemeSigns) { auto [a0, a1] = dpf::make_additive_shares(std::int32_t{20}); auto [s0, s1] = dpf::make_subtractive_shares(std::int32_t{7}); // party 0: raw add; party 1: flip the differing-scheme operand auto r0 = a0 + s0; auto r1 = a1 + s1; EXPECT_EQ(dpf::reconstruct(r0, r1), 27); auto q0 = s0 + a0; auto q1 = s1 + a1; EXPECT_EQ(dpf::reconstruct(q0, q1), 27); } TEST(SecretShare, PlaintextAbsorbOnParty0) { auto [s0, s1] = dpf::make_subtractive_shares(std::uint32_t{5}); s0 += std::uint32_t{10}; s1 += std::uint32_t{10}; // no-op for party 1 EXPECT_EQ(dpf::reconstruct(s0, s1), 15u); } TEST(SecretShare, AsAdditivePreservesSecret) { auto [s0, s1] = dpf::make_subtractive_shares(std::int32_t{42}); auto a0 = s0.as_additive(); auto a1 = s1.as_additive(); EXPECT_EQ(dpf::reconstruct(a0, a1), 42); } TEST(SecretShare, DpfLeafRoundTrip) { const std::uint8_t alpha = 0x2a; const std::uint32_t beta = 0x01020304; auto [k0, k1] = dpf::make_dpf(alpha, beta); EXPECT_TRUE(dpf::is_party_key_v); EXPECT_EQ(decltype(k0)::party, 0u); EXPECT_EQ(decltype(k1)::party, 1u); auto y0 = *dpf::eval_point(k0, alpha); auto y1 = *dpf::eval_point(k1, alpha); EXPECT_TRUE((std::is_same_v>)); EXPECT_EQ(dpf::reconstruct(y0, y1), beta); auto z0 = *dpf::eval_point(k0, static_cast(alpha ^ 1)); auto z1 = *dpf::eval_point(k1, static_cast(alpha ^ 1)); EXPECT_EQ(dpf::reconstruct(z0, z1), 0u); } TEST(SecretShare, DpfXorLeafRoundTrip) { using X = dpf::xor_wrapper; const std::uint8_t alpha = 7; const X beta{0xA5A5A5A5u}; auto [k0, k1] = dpf::make_dpf(alpha, beta); auto y0 = *dpf::eval_point(k0, alpha); auto y1 = *dpf::eval_point(k1, alpha); EXPECT_EQ(dpf::reconstruct(y0, y1), beta); } TEST(SecretShare, CmpAdditiveRoundTrip) { const std::uint32_t alpha = 100u; const std::uint64_t yt = 5u, yf = 9u; auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf)); const std::uint64_t mask = k0.cmp().mask; auto below = dpf::reconstruct( dpf::eval_point(dpf::cmp, k0, 50u), dpf::eval_point(dpf::cmp, k1, 50u)) & mask; auto at = dpf::reconstruct( dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask; EXPECT_EQ(below, yt); EXPECT_EQ(at, yf); } TEST(SecretShare, PrgExpandSubtractive) { using prg = dpf::prg::aes128; const auto seed0 = dpf::uniform_sample(); const auto seed1 = dpf::uniform_sample(); auto s0 = prg::expand(seed0, 0); auto s1 = prg::expand(seed1, 0); // Same pos, different seeds: reconstruct is raw0 - raw1 (bit pattern). EXPECT_EQ(dpf::reconstruct(s0, s1), static_cast(s0.raw() - s1.raw())); // Same seed → same bits → reconstruct 0 auto t0 = prg::expand(seed0, 3); auto t1 = prg::expand(seed0, 3); EXPECT_EQ(dpf::reconstruct(t0, t1), 0u); } TEST(SecretShare, Additive3LayoutAndPlaintextSplit) { using T = std::uint64_t; static_assert(dpf::sharing_parties_v == 3); static_assert(dpf::sharing_threshold_v == 3); static_assert(dpf::is_secret_share_v>); static_assert(dpf::share_party_v> == 2u); static_assert(dpf::share_scheme_v> == dpf::sharing::additive3); EXPECT_EQ(sizeof(dpf::additive3_share), sizeof(T)); EXPECT_TRUE((std::is_trivially_copyable_v>)); EXPECT_TRUE((std::is_standard_layout_v>)); constexpr auto split = dpf::make_additive3_shares(std::uint32_t{9}); static_assert(dpf::reconstruct( std::get<0>(split), std::get<1>(split), std::get<2>(split)) == 9u); const std::uint32_t secret = 0xdeadbeefu; auto [a0, a1, a2] = dpf::make_additive3_shares(secret); EXPECT_EQ(a1.raw(), 0u); EXPECT_EQ(a2.raw(), 0u); EXPECT_EQ(dpf::reconstruct(a2, a0, a1), secret); } TEST(SecretShare, Additive3LinearComboAndAbsorb) { auto [a0, a1, a2] = dpf::make_additive3_shares(std::uint32_t{10}); auto [b0, b1, b2] = dpf::make_additive3_shares(std::uint32_t{3}); auto c0 = a0 * 2 + b0; auto c1 = a1 * 2 + b1; auto c2 = a2 * 2 + b2; EXPECT_EQ(dpf::reconstruct(c0, c1, c2), 23u); a0 += std::uint32_t{10}; a1 += std::uint32_t{10}; a2 += std::uint32_t{10}; EXPECT_EQ(dpf::reconstruct(a0, a1, a2), 20u); const auto raw = std::numeric_limits::min(); auto s0 = dpf::additive3_share::from_raw(raw); auto s1 = dpf::additive3_share::from_raw(-1); auto s2 = dpf::additive3_share::from_raw(1); EXPECT_EQ(dpf::reconstruct(s0, s1, s2), raw); } TEST(SecretShare, Additive3RandomAndXor) { using X = dpf::xor_wrapper; const X secret{0xA5A5A5A5u}; auto [x0, x1, x2] = dpf::additively_share3(secret); EXPECT_EQ(dpf::reconstruct(x0, x1, x2), secret); const std::int32_t n = -42; auto [a, b, c] = dpf::additively_share3(n); EXPECT_EQ(dpf::reconstruct(c, b, a), n); const float f = 1.5f; auto [f0, f1, f2] = dpf::additively_share3(f); EXPECT_EQ(dpf::reconstruct(f0, f1, f2), f); } TEST(SecretShare, ReplicatedLayoutPlaintextAndPairs) { using T = std::uint32_t; static_assert(dpf::sharing_parties_v == 3); static_assert(dpf::sharing_threshold_v == 2); static_assert(dpf::share_scheme_v> == dpf::sharing::replicated); EXPECT_EQ(sizeof(dpf::replicated_share), 2 * sizeof(T)); EXPECT_TRUE((std::is_trivially_copyable_v>)); EXPECT_TRUE((std::is_standard_layout_v>)); constexpr auto split = dpf::make_replicated_shares(T{11}); static_assert(dpf::reconstruct(std::get<0>(split), std::get<1>(split)) == 11u); static_assert(dpf::reconstruct(std::get<1>(split), std::get<2>(split)) == 11u); static_assert(dpf::reconstruct(std::get<2>(split), std::get<0>(split)) == 11u); auto [r0, r1, r2] = dpf::make_replicated_shares(T{11}); EXPECT_EQ(r0.own, 11u); EXPECT_EQ(r0.next, 0u); EXPECT_EQ(r1.own, 0u); EXPECT_EQ(r1.next, 0u); EXPECT_EQ(r2.own, 0u); EXPECT_EQ(r2.next, 11u); EXPECT_EQ(dpf::reconstruct(r0, r1, r2), 11u); EXPECT_EQ(r0.as_additive3().raw(), 11u); EXPECT_EQ(r0.next_additive3().raw(), r1.as_additive3().raw()); } TEST(SecretShare, ReplicatedLinearComboAbsorbAndAdditive3) { auto [a0, a1, a2] = dpf::make_replicated_shares(std::uint32_t{10}); auto [b0, b1, b2] = dpf::make_replicated_shares(std::uint32_t{3}); auto c0 = a0 * 2 - b0; auto c1 = a1 * 2 - b1; auto c2 = a2 * 2 - b2; EXPECT_EQ(dpf::reconstruct(c0, c1), 17u); EXPECT_EQ(dpf::reconstruct(c1, c2), 17u); EXPECT_EQ(dpf::reconstruct(c0, c2, c1), 17u); c0 += std::uint32_t{4}; c1 += std::uint32_t{4}; c2 += std::uint32_t{4}; EXPECT_EQ(c1.own, (a1 * 2 - b1).own); EXPECT_EQ(c1.next, (a1 * 2 - b1).next); EXPECT_EQ(dpf::reconstruct(c2, c0), 21u); auto [d0, d1, d2] = dpf::make_additive3_shares(std::uint32_t{1}); d1 = dpf::additive3_share::from_raw(2u); d2 = dpf::additive3_share::from_raw(4u); auto [e0, e1, e2] = dpf::add_replicated(c0, c1, c2, d0, d1, d2); EXPECT_EQ(e0.next, e1.own); EXPECT_EQ(e1.next, e2.own); EXPECT_EQ(e2.next, e0.own); EXPECT_EQ(dpf::reconstruct(e0, e1), 28u); EXPECT_EQ(dpf::reconstruct(e1, e2, e0), dpf::reconstruct(e0, e2)); } TEST(SecretShare, ReplicatedFromComponentsAndRandom) { using X = dpf::xor_wrapper; const X secret{0xBEEFu}; auto [a0, a1, a2] = dpf::additively_share3(secret); auto [r0, r1, r2] = dpf::make_replicated_shares(a0, a1, a2); EXPECT_EQ(r0.own, a0.raw()); EXPECT_EQ(r0.next, a1.raw()); EXPECT_EQ(r2.next, a0.raw()); EXPECT_EQ(dpf::reconstruct(r2, r1), secret); EXPECT_EQ(dpf::reconstruct(r0.as_additive3(), r1.as_additive3(), r2.as_additive3()), secret); auto built = dpf::replicated_share::from_additive3( a0, a1); EXPECT_EQ(built, r0); const std::int64_t n = std::numeric_limits::min() + 7; auto [s0, s1, s2] = dpf::share_replicated(n); EXPECT_EQ(s0.next, s1.own); EXPECT_EQ(s1.next, s2.own); EXPECT_EQ(s2.next, s0.own); EXPECT_EQ(dpf::reconstruct(s0, s1), n); EXPECT_EQ(dpf::reconstruct(s1, s2), n); EXPECT_EQ(dpf::reconstruct(s2, s0), n); EXPECT_EQ(dpf::reconstruct(s2, s1, s0), n); auto [p0, p1, p2] = dpf::make_replicated_shares(std::uint32_t{8}); dpf::replicated_share slot{}; dpf::assign_share_slot(slot, p0); EXPECT_EQ(slot, p0); std::uint32_t word = 0; dpf::assign_share_slot(word, p0.as_additive3()); EXPECT_EQ(word, 8u); (void)p1; (void)p2; } TEST(SecretShare, TwoPartyConversionsPreserveSecret) { auto a0 = dpf::additive_share::from_raw(10); auto a1 = dpf::additive_share::from_raw(5); auto b0 = dpf::a2b(a0); auto b1 = dpf::a2b(a1); EXPECT_EQ(b0.raw(), 10); EXPECT_EQ(b1.raw(), -5); EXPECT_EQ(dpf::reconstruct(b0, b1), 15); EXPECT_EQ(dpf::reconstruct(dpf::b2a(b0), dpf::b2a(b1)), 15); auto f0 = dpf::a2fss(a0); auto f1 = dpf::a2fss(a1); EXPECT_EQ(f1.raw(), -5); EXPECT_EQ(dpf::reconstruct(dpf::fss2a(f0), dpf::fss2a(f1)), 15); EXPECT_EQ(dpf::fss2b(f1).raw(), f1.raw()); EXPECT_EQ(dpf::b2fss(b1).raw(), b1.raw()); // Subtractive and FSS share a sign, so party 1 adds the raw words. auto mixed = b1 + f1; EXPECT_EQ(mixed.raw(), -10); // Additive and FSS disagree on party 1, so the FSS word is flipped. auto flipped = a1 + dpf::fss_share::from_raw(4); EXPECT_EQ(flipped.raw(), 1); } TEST(SecretShare, ShamirRoundTripAndReplicatedProduct) { const dpf::fp61 secret{20}; const dpf::fp61 slope{3}; auto [s0, s1, s2] = dpf::make_shamir_shares(secret, slope); EXPECT_EQ((dpf::shamir_share::point), 1u); EXPECT_EQ(s0.raw(), secret + slope * dpf::fp61{1}); EXPECT_EQ(s1.raw(), secret + slope * dpf::fp61{2}); EXPECT_EQ(s2.raw(), secret + slope * dpf::fp61{3}); EXPECT_EQ(dpf::reconstruct(s0, s1), secret); EXPECT_EQ(dpf::reconstruct(s1, s2), secret); EXPECT_EQ(dpf::reconstruct(s2, s0, s1), secret); s0 += dpf::fp61{4}; s1 += dpf::fp61{4}; s2 += dpf::fp61{4}; EXPECT_EQ(dpf::reconstruct(s2, s0), secret + dpf::fp61{4}); auto [y0, y1, y2] = dpf::s2y(s0, s1); EXPECT_EQ(dpf::reconstruct(y0, y1, y2), secret + dpf::fp61{4}); auto back = dpf::y2s(y0, y1, y2, slope); EXPECT_EQ(dpf::reconstruct(std::get<0>(back), std::get<2>(back)), secret + dpf::fp61{4}); auto [r0, r1, r2] = dpf::s2rss(s0, s2); EXPECT_EQ(dpf::reconstruct(r0, r1), secret + dpf::fp61{4}); auto shamir_again = dpf::rss2s(r1, r2, slope); EXPECT_EQ(dpf::reconstruct(std::get<1>(shamir_again), std::get<2>(shamir_again)), secret + dpf::fp61{4}); auto tagged = dpf::as_shamir_share(dpf3_party_tag{}, dpf::fp61{7}); EXPECT_EQ(decltype(tagged)::party, 2u); EXPECT_EQ(tagged.raw(), dpf::fp61{7}); auto runtime = dpf::shamir3::runtime_share(s0); EXPECT_EQ(runtime.party, 1); EXPECT_EQ(dpf::shamir3::typed_share<0>(runtime), s0); EXPECT_EQ(dpf::shamir3::reconstruct(runtime, dpf::shamir3::runtime_share(s1)), secret + dpf::fp61{4}); auto [x0, x1, x2] = dpf::make_replicated_shares( std::uint32_t{3}, std::uint32_t{5}, std::uint32_t{1}); auto [z0, z1, z2] = dpf::make_replicated_shares( std::uint32_t{4}, std::uint32_t{2}, std::uint32_t{6}); EXPECT_EQ(dpf::reconstruct(dpf::rss_mul(x0, z0), dpf::rss_mul(x1, z1), dpf::rss_mul(x2, z2)), 108u); auto [p0, p1, p2] = dpf::rss_mul(x0, x1, x2, z0, z1, z2); EXPECT_EQ(p0.next, p1.own); EXPECT_EQ(dpf::reconstruct(p0, p2), 108u); auto m0 = dpf::additive3_share::from_raw(9u); auto m1 = dpf::additive3_share::from_raw(1u); auto m2 = dpf::additive3_share::from_raw( static_cast(0u - 9u - 1u)); auto [q0, q1, q2] = dpf::rss_mul(x0, x1, x2, z0, z1, z2, m0, m1, m2); EXPECT_EQ(dpf::reconstruct(q1, q0), 108u); EXPECT_NE(q0.own, p0.own); (void)q2; auto [h0, h1, h2] = dpf::y2rss(dpf::rss2y(p0), dpf::rss2y(p1), dpf::rss2y(p2)); EXPECT_EQ(h0, p0); EXPECT_EQ(h1, p1); EXPECT_EQ(h2, p2); } TEST(SecretShare, ShamirKnSpecializesTwoOfThree) { using F = dpf::fp61; static_assert(std::is_same_v< dpf::shamir::share, dpf::shamir_share>); static_assert(std::is_same_v< dpf::shamir::share, dpf::shamir_share>); static_assert(dpf::shamir::two_of_three::threshold == 2); static_assert(dpf::shamir::two_of_three::parties == 3); static_assert(dpf::shamir::two_of_three::degree == 1); static_assert(dpf::shamir_share::threshold == 2); static_assert(dpf::shamir_share::parties == 3); static_assert(!dpf::is_secret_share_v>); static_assert(dpf::shamir::is_share_v>); static_assert(dpf::shamir::is_share_v>); EXPECT_EQ(sizeof(dpf::shamir::share), sizeof(F)); EXPECT_TRUE((std::is_trivially_copyable_v>)); EXPECT_TRUE((std::is_standard_layout_v>)); // p(x) = 10 + 2x + 3x^2. Points 1..5 are 15, 26, 43, 66, 95. const F secret{10}; const std::array coeff{{F{2}, F{3}}}; const F expect[5] = {F{15}, F{26}, F{43}, F{66}, F{95}}; auto dealt = dpf::make_shamir_shares<3, 5>(secret, coeff); const std::array raw{{ std::get<0>(dealt).raw(), std::get<1>(dealt).raw(), std::get<2>(dealt).raw(), std::get<3>(dealt).raw(), std::get<4>(dealt).raw()}}; for (int i = 0; i < 5; ++i) EXPECT_EQ(raw[static_cast(i)], expect[i]); EXPECT_EQ(std::get<4>(dealt).point, 5u); EXPECT_EQ(std::get<4>(dealt).party, 4u); for (int i = 0; i < 5; ++i) for (int j = i + 1; j < 5; ++j) for (int k = j + 1; k < 5; ++k) { const std::array, 3> subset{{ {i + 1, raw[static_cast(i)]}, {j + 1, raw[static_cast(j)]}, {k + 1, raw[static_cast(k)]}}}; const F opened = dpf::shamir::reconstruct(subset); EXPECT_EQ(opened, secret); } EXPECT_EQ(dpf::shamir::reconstruct( std::get<0>(dealt), std::get<2>(dealt), std::get<4>(dealt)), secret); auto scaled0 = std::get<0>(dealt) * F{3}; auto scaled1 = std::get<1>(dealt) * F{3}; auto scaled2 = std::get<2>(dealt) * F{3}; EXPECT_EQ(dpf::shamir::reconstruct(scaled0, scaled1, scaled2), secret * F{3}); auto sum0 = std::get<0>(dealt) + std::get<0>(dealt); auto sum1 = std::get<1>(dealt) + std::get<1>(dealt); auto sum2 = std::get<2>(dealt) + std::get<2>(dealt); EXPECT_EQ(dpf::shamir::reconstruct(sum0, sum1, sum2), secret + secret); std::get<0>(dealt) += F{4}; std::get<1>(dealt) += F{4}; std::get<2>(dealt) += F{4}; EXPECT_EQ(dpf::shamir::reconstruct( std::get<0>(dealt), std::get<1>(dealt), std::get<2>(dealt)), secret + F{4}); std::array, 4> extra{{ {1, raw[0]}, {2, raw[1]}, {3, raw[2]}, {4, raw[3]}}}; EXPECT_EQ((dpf::shamir::reconstruct(extra)), secret); extra[3].value = extra[3].value + F{1}; EXPECT_THROW((dpf::shamir::reconstruct(extra)), std::runtime_error); const std::array, 2> too_few{{ {1, raw[0]}, {2, raw[1]}}}; EXPECT_THROW((dpf::shamir::reconstruct(too_few)), std::invalid_argument); const std::array, 2> dup{{ {1, raw[0]}, {1, raw[0]}}}; EXPECT_THROW((dpf::shamir::reconstruct(dup)), std::invalid_argument); // Threshold 2 is not tied to three shareholders. p(x) = 8 + 5x. const auto line = dpf::shamir::deal(F{8}, std::array{{F{5}}}); EXPECT_EQ(std::get<0>(line).raw(), F{13}); EXPECT_EQ(std::get<3>(line).raw(), F{28}); EXPECT_EQ(dpf::shamir::reconstruct(std::get<1>(line), std::get<3>(line)), F{8}); // Threshold 1 copies the secret. Threshold N needs every share. const auto copied = dpf::shamir::deal(F{9}, std::array{}); EXPECT_EQ(std::get<0>(copied).raw(), F{9}); EXPECT_EQ(std::get<2>(copied).raw(), F{9}); EXPECT_EQ(dpf::shamir::reconstruct(std::get<1>(copied)), F{9}); const auto plain = dpf::shamir::share_secret(F{6}); EXPECT_EQ(std::get<0>(plain).raw(), F{6}); EXPECT_EQ(std::get<2>(plain).raw(), F{6}); const std::array dense{{F{1}, F{0}, F{4}}}; const auto all = dpf::shamir::deal(F{7}, dense); EXPECT_EQ(dpf::shamir::reconstruct(std::get<0>(all), std::get<1>(all), std::get<2>(all), std::get<3>(all)), F{7}); const std::array, 3> missing{{ {1, std::get<0>(all).raw()}, {2, std::get<1>(all).raw()}, {3, std::get<2>(all).raw()}}}; EXPECT_THROW((dpf::shamir::reconstruct(missing)), std::invalid_argument); const auto rnd = dpf::shamir::share_secret(F{11}); static_assert(std::is_same_v(rnd))>, dpf::shamir_share>); EXPECT_EQ(dpf::reconstruct(std::get<0>(rnd), std::get<2>(rnd)), F{11}); const auto wider = dpf::shamir::share_secret(F{12}); EXPECT_EQ(dpf::shamir::reconstruct( std::get<0>(wider), std::get<2>(wider), std::get<4>(wider)), F{12}); } } // namespace