Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
347
test/tests/shamir_adversarial_test.cpp
Normal file
347
test/tests/shamir_adversarial_test.cpp
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
/// @file shamir_adversarial_test.cpp
|
||||
/// @brief Corruption and misuse checks for (K,N) Shamir sharing.
|
||||
/// @details Exactly K shares accept any values, including a mislabeled point.
|
||||
/// Each further share is checked against the polynomial of the first
|
||||
/// K, wherever the bad share sits in that list. A full set of shares
|
||||
/// of a different secret is consistent and opens that secret. A zero
|
||||
/// leading coefficient drops the real threshold. (2,3) shamir3
|
||||
/// rejects a party outside 1..3 and an inconsistent third share.
|
||||
/// Threshold 1 hides nothing: every share is the secret. When K = N
|
||||
/// there is no extra share, so a tampered full set is not detected.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "dpf.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using F = dpf::fp61;
|
||||
|
||||
template <typename Tuple, std::size_t... I>
|
||||
std::array<F, sizeof...(I)> raws(const Tuple & shares, std::index_sequence<I...>)
|
||||
{
|
||||
return {{std::get<I>(shares).raw()...}};
|
||||
}
|
||||
|
||||
template <std::size_t N, typename Tuple>
|
||||
std::array<F, N> raws(const Tuple & shares)
|
||||
{
|
||||
return raws(shares, std::make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
std::array<dpf::shamir::point_share<F>, N> points(const std::array<F, N> & value)
|
||||
{
|
||||
std::array<dpf::shamir::point_share<F>, N> out{};
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
out[i] = {static_cast<int>(i + 1), value[i]};
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ShamirAdversarial, ExactlyKAcceptsALie)
|
||||
{
|
||||
const std::array<F, 2> coeff{{F{2}, F{3}}};
|
||||
const auto dealt = dpf::shamir::deal<F, 3, 5>(F{10}, coeff);
|
||||
auto bad = std::get<0>(dealt);
|
||||
bad += F{1};
|
||||
const F opened = dpf::shamir::reconstruct(bad, std::get<1>(dealt), std::get<2>(dealt));
|
||||
EXPECT_NE(opened, F{10});
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, ExtraShareCatchesALieInAnySlot)
|
||||
{
|
||||
const std::array<F, 2> coeff{{F{2}, F{3}}};
|
||||
const auto dealt = dpf::shamir::deal<F, 3, 5>(F{10}, coeff);
|
||||
const auto honest = points<5>(raws<5>(dealt));
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(honest)), F{10});
|
||||
|
||||
for (std::size_t slot = 0; slot < 5; ++slot)
|
||||
{
|
||||
auto lied = honest;
|
||||
lied[slot].value = lied[slot].value + F{1};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(lied)), std::runtime_error)
|
||||
<< "slot " << slot;
|
||||
|
||||
// The same lie, moved to the front of the argument list.
|
||||
std::array<dpf::shamir::point_share<F>, 5> front{};
|
||||
front[0] = lied[slot];
|
||||
std::size_t n = 1;
|
||||
for (std::size_t i = 0; i < 5; ++i)
|
||||
if (i != slot)
|
||||
front[n++] = honest[i];
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(front)), std::runtime_error)
|
||||
<< "front slot " << slot;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, ConsistentForgeryOpensTheForgedSecret)
|
||||
{
|
||||
const std::array<F, 2> coeff{{F{1}, F{4}}};
|
||||
const auto forged = dpf::shamir::deal<F, 3, 5>(F{99}, coeff);
|
||||
const auto held = points<5>(raws<5>(forged));
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(held)), F{99});
|
||||
|
||||
const auto real = dpf::shamir::deal<F, 3, 5>(F{10}, std::array<F, 2>{{F{2}, F{3}}});
|
||||
auto mixed = points<5>(raws<5>(real));
|
||||
mixed[4] = held[4];
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(mixed)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, OrderOfAConsistentSetDoesNotMatter)
|
||||
{
|
||||
const std::array<F, 2> coeff{{F{2}, F{3}}};
|
||||
const auto dealt = dpf::shamir::deal<F, 3, 5>(F{10}, coeff);
|
||||
const F values[5] = {
|
||||
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()};
|
||||
int idx[3] = {0, 2, 4};
|
||||
do
|
||||
{
|
||||
const std::array<dpf::shamir::point_share<F>, 3> subset{{
|
||||
{idx[0] + 1, values[idx[0]]},
|
||||
{idx[1] + 1, values[idx[1]]},
|
||||
{idx[2] + 1, values[idx[2]]}}};
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(subset)), F{10});
|
||||
}
|
||||
while (std::next_permutation(idx, idx + 3));
|
||||
|
||||
const auto forward = points<5>(raws<5>(dealt));
|
||||
std::array<dpf::shamir::point_share<F>, 5> backward{};
|
||||
for (std::size_t i = 0; i < 5; ++i)
|
||||
backward[i] = forward[4 - i];
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(backward)), F{10});
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, WrongThresholdAndMislabeledPoint)
|
||||
{
|
||||
// p(x) = 8 + 5x + x^2. The line through p(1)=14 and p(2)=22 opens 6, not 8.
|
||||
const auto steep = dpf::shamir::deal<F, 3, 5>(F{8}, std::array<F, 2>{{F{5}, F{1}}});
|
||||
const std::array<dpf::shamir::point_share<F>, 2> line{{
|
||||
{1, std::get<0>(steep).raw()}, {2, std::get<1>(steep).raw()}}};
|
||||
EXPECT_EQ(std::get<0>(steep).raw(), F{14});
|
||||
EXPECT_EQ(std::get<1>(steep).raw(), F{22});
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 2, 5>(line)), F{6});
|
||||
EXPECT_NE((dpf::shamir::reconstruct<F, 2, 5>(line)), F{8});
|
||||
|
||||
// A zero leading coefficient is only a (2,5) sharing.
|
||||
const auto flat = dpf::shamir::deal<F, 3, 5>(F{8}, std::array<F, 2>{{F{5}, F{0}}});
|
||||
const std::array<dpf::shamir::point_share<F>, 2> still_line{{
|
||||
{1, std::get<0>(flat).raw()}, {4, std::get<3>(flat).raw()}}};
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 2, 5>(still_line)), F{8});
|
||||
|
||||
auto [s0, s1, s2] = dpf::make_shamir_shares(F{20}, F{3});
|
||||
const std::array<dpf::shamir::point_share<F>, 2> swapped{{
|
||||
{1, s1.raw()}, {2, s0.raw()}}};
|
||||
EXPECT_NE((dpf::shamir::reconstruct<F, 2, 3>(swapped)), F{20});
|
||||
const std::array<dpf::shamir::point_share<F>, 3> swapped3{{
|
||||
{1, s1.raw()}, {2, s0.raw()}, {3, s2.raw()}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 2, 3>(swapped3)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, OneShareDoesNotFixTheSecret)
|
||||
{
|
||||
// p(1) = 13 for both (secret, slope) = (10, 3) and (11, 2).
|
||||
auto [a0, a1, a2] = dpf::make_shamir_shares(F{10}, F{3});
|
||||
auto [b0, b1, b2] = dpf::make_shamir_shares(F{11}, F{2});
|
||||
EXPECT_EQ(a0.raw(), b0.raw());
|
||||
EXPECT_EQ(a0.raw(), F{13});
|
||||
EXPECT_EQ((dpf::reconstruct(a0, a1)), F{10});
|
||||
EXPECT_EQ((dpf::reconstruct(b0, b1)), F{11});
|
||||
EXPECT_NE((dpf::reconstruct(a0, b1)), F{10});
|
||||
(void)a2;
|
||||
(void)b2;
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, RejectsCountPointsAndOneSidedEdit)
|
||||
{
|
||||
const auto dealt = dpf::shamir::deal<F, 3, 5>(F{10}, std::array<F, 2>{{F{2}, F{3}}});
|
||||
const auto honest = points<5>(raws<5>(dealt));
|
||||
const std::array<dpf::shamir::point_share<F>, 2> too_few{{honest[0], honest[1]}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(too_few)), std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(honest.data(), 0)),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(honest.data(), 6)),
|
||||
std::invalid_argument);
|
||||
|
||||
const std::array<dpf::shamir::point_share<F>, 3> dup{{
|
||||
{1, honest[0].value}, {1, honest[0].value}, {3, honest[2].value}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(dup)), std::invalid_argument);
|
||||
const std::array<dpf::shamir::point_share<F>, 3> zero_pt{{
|
||||
{0, honest[0].value}, {2, honest[1].value}, {3, honest[2].value}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(zero_pt)), std::invalid_argument);
|
||||
const std::array<dpf::shamir::point_share<F>, 3> negative{{
|
||||
{-1, honest[0].value}, {2, honest[1].value}, {3, honest[2].value}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(negative)), std::invalid_argument);
|
||||
const std::array<dpf::shamir::point_share<F>, 3> past{{
|
||||
{1, honest[0].value}, {2, honest[1].value}, {6, honest[2].value}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(past)), std::invalid_argument);
|
||||
|
||||
auto one = std::get<0>(dealt);
|
||||
one += F{4};
|
||||
const F sided = dpf::shamir::reconstruct(one, std::get<1>(dealt), std::get<2>(dealt));
|
||||
EXPECT_NE(sided, F{10});
|
||||
auto with_extra = honest;
|
||||
with_extra[0].value = one.raw();
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 3, 5>(with_extra)), std::runtime_error);
|
||||
|
||||
auto all = dealt;
|
||||
std::get<0>(all) += F{4};
|
||||
std::get<1>(all) += F{4};
|
||||
std::get<2>(all) += F{4};
|
||||
std::get<3>(all) += F{4};
|
||||
std::get<4>(all) += F{4};
|
||||
EXPECT_EQ((dpf::shamir::reconstruct<F, 3, 5>(points<5>(raws<5>(all)))), F{14});
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, ThresholdOneHidesNothingAndFullThresholdNeedsEveryShare)
|
||||
{
|
||||
const auto copied = dpf::shamir::deal<F, 1, 4>(F{4}, std::array<F, 0>{});
|
||||
EXPECT_EQ(std::get<0>(copied).raw(), F{4});
|
||||
EXPECT_EQ(std::get<3>(copied).raw(), F{4});
|
||||
auto lie = std::get<0>(copied);
|
||||
lie += F{1};
|
||||
EXPECT_EQ(dpf::shamir::reconstruct(lie), F{5});
|
||||
auto all = points<4>(raws<4>(copied));
|
||||
all[2].value = all[2].value + F{1};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 1, 4>(all)), std::runtime_error);
|
||||
|
||||
const auto quad = dpf::shamir::deal<F, 4, 4>(
|
||||
F{9}, std::array<F, 3>{{F{1}, F{2}, F{3}}});
|
||||
const F opened4 = dpf::shamir::reconstruct(std::get<0>(quad), std::get<1>(quad),
|
||||
std::get<2>(quad), std::get<3>(quad));
|
||||
EXPECT_EQ(opened4, F{9});
|
||||
const std::array<dpf::shamir::point_share<F>, 3> missing{{
|
||||
{1, std::get<0>(quad).raw()},
|
||||
{2, std::get<1>(quad).raw()},
|
||||
{3, std::get<2>(quad).raw()}}};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 4, 4>(missing)), std::invalid_argument);
|
||||
// K = N, so the full set is exactly K shares and a lie is not detected.
|
||||
auto bad = std::get<3>(quad);
|
||||
bad += F{1};
|
||||
const F lied = dpf::shamir::reconstruct(std::get<0>(quad), std::get<1>(quad),
|
||||
std::get<2>(quad), bad);
|
||||
EXPECT_NE(lied, F{9});
|
||||
|
||||
const auto room = dpf::shamir::deal<F, 3, 4>(F{9}, std::array<F, 2>{{F{1}, F{2}}});
|
||||
auto bad_extra = std::get<3>(room);
|
||||
bad_extra += F{1};
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(room), std::get<1>(room),
|
||||
std::get<2>(room), bad_extra)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, Shamir3RejectsBadPartiesAndMatchesTheGeneralOpen)
|
||||
{
|
||||
const auto s = dpf::shamir3::share_secret(F{42});
|
||||
const F from_general = dpf::shamir::reconstruct<F, 2, 3>(
|
||||
std::array<dpf::shamir::point_share<F>, 2>{{
|
||||
{s[0].party, s[0].value}, {s[2].party, s[2].value}}});
|
||||
const F from_shamir3 = dpf::shamir3::reconstruct(s[0], s[2]);
|
||||
EXPECT_EQ(from_shamir3, from_general);
|
||||
const F from_three = dpf::shamir3::reconstruct(s[0], s[1], s[2]);
|
||||
EXPECT_EQ(from_three, F{42});
|
||||
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(
|
||||
dpf::shamir3::share{0, F{1}}, dpf::shamir3::share{1, F{1}})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(
|
||||
dpf::shamir3::share{1, F{1}}, dpf::shamir3::share{4, F{1}})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(
|
||||
dpf::shamir3::share{2, F{1}}, dpf::shamir3::share{2, F{2}})),
|
||||
std::invalid_argument);
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(s[0], s[1], dpf::shamir3::share{4, s[2].value})),
|
||||
std::invalid_argument);
|
||||
|
||||
auto bad = s[2];
|
||||
bad.value = bad.value + F{1};
|
||||
EXPECT_THROW((dpf::shamir3::reconstruct(s[0], s[1], bad)), std::runtime_error);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct<F, 2, 3>(
|
||||
std::array<dpf::shamir::point_share<F>, 3>{{
|
||||
{s[0].party, s[0].value},
|
||||
{s[1].party, s[1].value},
|
||||
{bad.party, bad.value}}})),
|
||||
std::runtime_error);
|
||||
|
||||
EXPECT_THROW((dpf::shamir3::add(s[0], s[1])), std::invalid_argument);
|
||||
const auto doubled = dpf::shamir3::add(s[0], s[0]);
|
||||
EXPECT_EQ(doubled.value, s[0].value + s[0].value);
|
||||
EXPECT_THROW((dpf::shamir3::typed_share<0>(s[1])), std::invalid_argument);
|
||||
const auto typed = dpf::shamir3::typed_share<0>(s[0]);
|
||||
EXPECT_EQ(typed.raw(), s[0].value);
|
||||
|
||||
auto [t0, t1, t2] = dpf::make_shamir_shares(F{42}, F{0});
|
||||
t2 += F{1};
|
||||
EXPECT_THROW((dpf::reconstruct(t0, t1, t2)), std::runtime_error);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(t2, t0, t1)), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, ZeroAndFieldEdge)
|
||||
{
|
||||
const auto zeros = dpf::shamir::deal<F, 3, 4>(F{0}, std::array<F, 2>{{F{0}, F{0}}});
|
||||
EXPECT_EQ(std::get<0>(zeros).raw(), F{0});
|
||||
EXPECT_EQ(std::get<3>(zeros).raw(), F{0});
|
||||
const F opened0 = dpf::shamir::reconstruct(
|
||||
std::get<1>(zeros), std::get<2>(zeros), std::get<3>(zeros));
|
||||
EXPECT_EQ(opened0, F{0});
|
||||
|
||||
const F edge{dpf::fp61_mod - 1};
|
||||
const auto high = dpf::shamir::deal<F, 2, 3>(edge, std::array<F, 1>{{edge}});
|
||||
const F opened_edge = dpf::reconstruct(std::get<0>(high), std::get<2>(high));
|
||||
EXPECT_EQ(opened_edge, edge);
|
||||
static_assert(std::is_same_v<dpf::shamir::share<F, 0, 2, 3>, dpf::shamir_share<F, 0>>);
|
||||
}
|
||||
|
||||
TEST(ShamirAdversarial, Gf2PointsMustFitAndLiesAreXor)
|
||||
{
|
||||
// GF(2^8) has room for points 1..5. A one-bit lie is invisible at K=3
|
||||
// and caught when an honest extra share is present.
|
||||
using G = dpf::gf28;
|
||||
const auto dealt = dpf::shamir::deal<G, 3, 5>(
|
||||
G{0x1b}, std::array<G, 2>{{G{2}, G{9}}});
|
||||
auto bad = std::get<0>(dealt);
|
||||
bad += G{1};
|
||||
const G sided = dpf::shamir::reconstruct(bad, std::get<1>(dealt), std::get<2>(dealt));
|
||||
EXPECT_NE(sided, G{0x1b});
|
||||
auto extra = std::get<3>(dealt);
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(bad, std::get<1>(dealt), std::get<2>(dealt), extra)),
|
||||
std::runtime_error);
|
||||
|
||||
// Adding a share to itself is XOR, so the opened secret is 0, not twice.
|
||||
const G doubled = dpf::shamir::reconstruct(
|
||||
std::get<0>(dealt) + std::get<0>(dealt),
|
||||
std::get<1>(dealt) + std::get<1>(dealt),
|
||||
std::get<2>(dealt) + std::get<2>(dealt));
|
||||
EXPECT_EQ(doubled, G{0});
|
||||
|
||||
// GF(16) holds points 1..15. Point 16 is 0. Point 17 aliases point 1.
|
||||
using H = dpf::gf24;
|
||||
const auto fit = dpf::shamir::deal<H, 2, 15>(H{0xa}, std::array<H, 1>{{H{0x3}}});
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(std::get<0>(fit), std::get<14>(fit))), H{0xa});
|
||||
const auto zero_pt = dpf::shamir::deal<H, 2, 16>(H{0xa}, std::array<H, 1>{{H{0x3}}});
|
||||
// Point 16 is 0 in GF(16), so that share is p(0), the secret.
|
||||
EXPECT_EQ(std::get<15>(zero_pt).raw(), H{0xa}.raw());
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(zero_pt), std::get<15>(zero_pt))),
|
||||
std::invalid_argument);
|
||||
const auto alias = dpf::shamir::deal<H, 2, 17>(H{0xa}, std::array<H, 1>{{H{0x3}}});
|
||||
EXPECT_EQ(std::get<0>(alias).raw(), std::get<16>(alias).raw());
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(alias), std::get<16>(alias))),
|
||||
std::invalid_argument);
|
||||
|
||||
// GF(4) holds a (2,3) sharing. Point 4 is 0.
|
||||
const auto small = dpf::shamir::deal<dpf::gf22, 2, 4>(
|
||||
dpf::gf22{1}, std::array<dpf::gf22, 1>{{dpf::gf22{2}}});
|
||||
EXPECT_THROW((dpf::shamir::reconstruct(std::get<0>(small), std::get<3>(small))),
|
||||
std::invalid_argument);
|
||||
EXPECT_EQ((dpf::shamir::reconstruct(std::get<0>(small), std::get<2>(small))), dpf::gf22{1});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue