351 lines
13 KiB
C++
351 lines
13 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
|
||
|
|
#include <cstdint>
|
||
|
|
#include <limits>
|
||
|
|
#include <stdexcept>
|
||
|
|
#include <tuple>
|
||
|
|
#include <utility>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
#include "dpf.hpp"
|
||
|
|
#include "dpf/yao.hpp"
|
||
|
|
#include "dpf/yao_share.hpp"
|
||
|
|
|
||
|
|
namespace
|
||
|
|
{
|
||
|
|
|
||
|
|
template <typename Ring>
|
||
|
|
Ring xor_bits(const std::vector<std::uint8_t> & a, const std::vector<std::uint8_t> & b)
|
||
|
|
{
|
||
|
|
Ring v{};
|
||
|
|
for (std::size_t i = 0; i < a.size() && i < 8u * sizeof(Ring); ++i)
|
||
|
|
v = static_cast<Ring>(v | (static_cast<Ring>(a[i] ^ b[i]) << i));
|
||
|
|
return v;
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Ring>
|
||
|
|
Ring low_mask(unsigned width)
|
||
|
|
{
|
||
|
|
if (width == 0 || width >= 8u * sizeof(Ring))
|
||
|
|
return static_cast<Ring>(~Ring{0});
|
||
|
|
return static_cast<Ring>((Ring{1} << width) - Ring{1});
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Ring>
|
||
|
|
void expect_additive(Ring secret, unsigned width)
|
||
|
|
{
|
||
|
|
const unsigned bits = width == 0 ? static_cast<unsigned>(8u * sizeof(Ring)) : width;
|
||
|
|
const Ring r = dpf::uniform_sample<Ring>();
|
||
|
|
const auto a0 = dpf::additive_share<Ring, 0>::from_raw(r);
|
||
|
|
const auto a1 = dpf::additive_share<Ring, 1>::from_raw(static_cast<Ring>(secret - r));
|
||
|
|
const auto [y0, y1] = dpf::yao::a2y(a0, a1, width);
|
||
|
|
ASSERT_EQ(y0.size(), bits);
|
||
|
|
ASSERT_EQ(y1.size(), bits);
|
||
|
|
EXPECT_EQ(xor_bits<Ring>(y0, y1), static_cast<Ring>(secret & low_mask<Ring>(bits)));
|
||
|
|
const auto [b0, b1] = dpf::yao::y2a<Ring>(y0, y1, bits);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(b0, b1), static_cast<Ring>(secret & low_mask<Ring>(bits)));
|
||
|
|
}
|
||
|
|
|
||
|
|
template <typename Ring>
|
||
|
|
void expect_subtractive(Ring s0, Ring s1, unsigned width)
|
||
|
|
{
|
||
|
|
const unsigned bits = width == 0 ? static_cast<unsigned>(8u * sizeof(Ring)) : width;
|
||
|
|
const Ring opened = static_cast<Ring>(s0 - s1);
|
||
|
|
const auto b0 = dpf::subtractive_share<Ring, 0>::from_raw(s0);
|
||
|
|
const auto b1 = dpf::subtractive_share<Ring, 1>::from_raw(s1);
|
||
|
|
const auto [y0, y1] = dpf::yao::b2y(b0, b1, width);
|
||
|
|
EXPECT_EQ(xor_bits<Ring>(y0, y1), static_cast<Ring>(opened & low_mask<Ring>(bits)));
|
||
|
|
const auto [z0, z1] = dpf::yao::y2b<Ring>(y0, y1, bits);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(z0, z1), static_cast<Ring>(opened & low_mask<Ring>(bits)));
|
||
|
|
|
||
|
|
const auto f0 = b0.as_fss();
|
||
|
|
const auto f1 = b1.as_fss();
|
||
|
|
const auto [g0, g1] = dpf::yao::fss2y(f0, f1, width);
|
||
|
|
EXPECT_EQ(xor_bits<Ring>(g0, g1), static_cast<Ring>(opened & low_mask<Ring>(bits)));
|
||
|
|
const auto [h0, h1] = dpf::yao::y2fss<Ring>(g0, g1, bits);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(h0, h1), static_cast<Ring>(opened & low_mask<Ring>(bits)));
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
TEST(YaoShare, WidthSweep)
|
||
|
|
{
|
||
|
|
const unsigned widths64[] = {1, 2, 7, 8, 9, 16, 31, 32, 63, 64, 0};
|
||
|
|
for (unsigned w : widths64)
|
||
|
|
expect_additive<std::uint64_t>(0x1234abcd5ull, w);
|
||
|
|
const unsigned widths32[] = {1, 8, 31, 32, 0};
|
||
|
|
for (unsigned w : widths32)
|
||
|
|
expect_additive<std::uint32_t>(0x89abcdefu, w);
|
||
|
|
expect_additive<std::uint16_t>(0xBEEFu, 0);
|
||
|
|
expect_additive<std::uint16_t>(0xBEEFu, 9);
|
||
|
|
expect_additive<std::uint8_t>(0xA5u, 0);
|
||
|
|
expect_additive<std::uint8_t>(0xA5u, 3);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, CornerSecrets)
|
||
|
|
{
|
||
|
|
expect_additive<std::uint64_t>(0, 64);
|
||
|
|
expect_additive<std::uint64_t>(1, 64);
|
||
|
|
expect_additive<std::uint64_t>(~std::uint64_t{0}, 64);
|
||
|
|
expect_additive<std::uint64_t>(std::uint64_t{1} << 63, 64);
|
||
|
|
expect_additive<std::uint64_t>(0x105u, 8);
|
||
|
|
expect_subtractive<std::uint64_t>(0, 0, 64);
|
||
|
|
expect_subtractive<std::uint64_t>(1, 3, 64);
|
||
|
|
expect_subtractive<std::uint64_t>(3, 1, 16);
|
||
|
|
expect_subtractive<std::uint32_t>(0, 1, 32);
|
||
|
|
expect_subtractive<std::uint8_t>(0x10, 0x10, 8);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, RandomRoundTrips)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < 24; ++i)
|
||
|
|
{
|
||
|
|
const auto secret = dpf::uniform_sample<std::uint64_t>();
|
||
|
|
expect_additive<std::uint64_t>(secret, 64);
|
||
|
|
const auto s0 = dpf::uniform_sample<std::uint32_t>();
|
||
|
|
const auto s1 = dpf::uniform_sample<std::uint32_t>();
|
||
|
|
expect_subtractive<std::uint32_t>(s0, s1, 32);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, BitFlipChangesThatPlace)
|
||
|
|
{
|
||
|
|
const auto a0 = dpf::additive_share<std::uint64_t, 0>::from_raw(0x111u);
|
||
|
|
const auto a1 = dpf::additive_share<std::uint64_t, 1>::from_raw(0x222u);
|
||
|
|
auto [y0, y1] = dpf::yao::a2y(a0, a1, 12);
|
||
|
|
const auto opened = xor_bits<std::uint64_t>(y0, y1);
|
||
|
|
for (unsigned i = 0; i < 12; ++i)
|
||
|
|
{
|
||
|
|
auto flipped = y0;
|
||
|
|
flipped[i] = static_cast<std::uint8_t>(flipped[i] ^ 1u);
|
||
|
|
EXPECT_EQ(xor_bits<std::uint64_t>(flipped, y1), opened ^ (std::uint64_t{1} << i));
|
||
|
|
const auto [b0, b1] = dpf::yao::y2a<std::uint64_t>(flipped, y1, 12);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(b0, b1), opened ^ (std::uint64_t{1} << i));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, DefaultWidthUsesTheVector)
|
||
|
|
{
|
||
|
|
const auto a0 = dpf::additive_share<std::uint16_t, 0>::from_raw(0x00FFu);
|
||
|
|
const auto a1 = dpf::additive_share<std::uint16_t, 1>::from_raw(0);
|
||
|
|
const auto [y0, y1] = dpf::yao::a2y(a0, a1, 8);
|
||
|
|
const auto [b0, b1] = dpf::yao::y2a<std::uint16_t>(y0, y1);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(b0, b1), 0x00FFu);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, RejectsBadInputs)
|
||
|
|
{
|
||
|
|
const auto a0 = dpf::additive_share<std::uint64_t, 0>::from_raw(1);
|
||
|
|
const auto a1 = dpf::additive_share<std::uint64_t, 1>::from_raw(0);
|
||
|
|
EXPECT_THROW(dpf::yao::a2y(a0, a1, 65), std::invalid_argument);
|
||
|
|
EXPECT_THROW(dpf::yao::a2y(
|
||
|
|
dpf::additive_share<std::uint8_t, 0>::from_raw(1),
|
||
|
|
dpf::additive_share<std::uint8_t, 1>::from_raw(0), 9),
|
||
|
|
std::invalid_argument);
|
||
|
|
|
||
|
|
std::vector<std::uint8_t> bits(8, 0);
|
||
|
|
std::vector<std::uint8_t> shortv(3, 0);
|
||
|
|
EXPECT_THROW(dpf::yao::y2a<std::uint64_t>(shortv, bits, 8), std::invalid_argument);
|
||
|
|
bits[4] = 2;
|
||
|
|
EXPECT_THROW(dpf::yao::y2a<std::uint64_t>(bits, std::vector<std::uint8_t>(8, 0), 8),
|
||
|
|
std::invalid_argument);
|
||
|
|
|
||
|
|
const auto rss = dpf::make_replicated_shares<std::uint32_t>(1, 2, 3);
|
||
|
|
auto r0 = std::get<0>(rss);
|
||
|
|
r0.next = 99;
|
||
|
|
EXPECT_THROW(dpf::yao::rss2y(r0, std::get<1>(rss)), std::invalid_argument);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, ReplicatedManyAndFresh)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < 12; ++i)
|
||
|
|
{
|
||
|
|
const auto x0 = dpf::uniform_sample<std::uint32_t>();
|
||
|
|
const auto x1 = dpf::uniform_sample<std::uint32_t>();
|
||
|
|
const auto x2 = dpf::uniform_sample<std::uint32_t>();
|
||
|
|
const auto secret = static_cast<std::uint32_t>(x0 + x1 + x2);
|
||
|
|
const auto shares = dpf::make_replicated_shares(x0, x1, x2);
|
||
|
|
const auto [y0, y1] = dpf::yao::rss2y(std::get<0>(shares), std::get<1>(shares));
|
||
|
|
EXPECT_EQ(xor_bits<std::uint32_t>(y0, y1), secret);
|
||
|
|
const auto back = dpf::yao::y2rss<std::uint32_t>(y0, y1);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(std::get<0>(back), std::get<1>(back)), secret);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(std::get<1>(back), std::get<2>(back)), secret);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(std::get<0>(back), std::get<2>(back)), secret);
|
||
|
|
const auto again = dpf::yao::y2rss<std::uint32_t>(y0, y1);
|
||
|
|
EXPECT_NE(std::get<0>(back).own, std::get<0>(again).own);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, ConcreteAdditiveSum)
|
||
|
|
{
|
||
|
|
const auto a0 = dpf::additive_share<std::uint16_t, 0>::from_raw(0x1234);
|
||
|
|
const auto a1 = dpf::additive_share<std::uint16_t, 1>::from_raw(0x0100);
|
||
|
|
const auto [y0, y1] = dpf::yao::a2y(a0, a1, 16);
|
||
|
|
EXPECT_EQ(xor_bits<std::uint16_t>(y0, y1), static_cast<std::uint16_t>(0x1334));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, PointLeafOnAndOff)
|
||
|
|
{
|
||
|
|
for (int n = 0; n < 16; ++n)
|
||
|
|
{
|
||
|
|
const auto alpha = dpf::uniform_sample<std::uint8_t>();
|
||
|
|
const auto beta = dpf::uniform_sample<std::uint32_t>();
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||
|
|
auto s0 = *dpf::eval_point(k0, alpha);
|
||
|
|
auto s1 = *dpf::eval_point(k1, alpha);
|
||
|
|
const auto [y0, y1] = dpf::yao::b2y(s0, s1, 32);
|
||
|
|
EXPECT_EQ(xor_bits<std::uint32_t>(y0, y1), beta);
|
||
|
|
const auto [z0, z1] = dpf::yao::y2b<std::uint32_t>(y0, y1, 32);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(z0, z1), beta);
|
||
|
|
|
||
|
|
const auto other = static_cast<std::uint8_t>(alpha + 1u);
|
||
|
|
auto t0 = *dpf::eval_point(k0, other);
|
||
|
|
auto t1 = *dpf::eval_point(k1, other);
|
||
|
|
const auto [u0, u1] = dpf::yao::b2y(t0, t1, 32);
|
||
|
|
EXPECT_EQ(xor_bits<std::uint32_t>(u0, u1), 0u);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, PointLeafPlusPublicAndSecondLeaf)
|
||
|
|
{
|
||
|
|
const std::uint8_t alpha = 9;
|
||
|
|
const std::uint32_t beta = 40;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||
|
|
auto s0 = *dpf::eval_point(k0, alpha);
|
||
|
|
auto s1 = *dpf::eval_point(k1, alpha);
|
||
|
|
s0 += std::uint32_t{5};
|
||
|
|
const auto [y0, y1] = dpf::yao::b2y(s0, s1, 32);
|
||
|
|
EXPECT_EQ(xor_bits<std::uint32_t>(y0, y1), 45u);
|
||
|
|
|
||
|
|
auto [j0, j1] = dpf::make_dpf(alpha, std::uint32_t{7});
|
||
|
|
auto u0 = *dpf::eval_point(k0, alpha) + *dpf::eval_point(j0, alpha);
|
||
|
|
auto u1 = *dpf::eval_point(k1, alpha) + *dpf::eval_point(j1, alpha);
|
||
|
|
const auto [v0, v1] = dpf::yao::b2y(u0, u1, 32);
|
||
|
|
EXPECT_EQ(xor_bits<std::uint32_t>(v0, v1), 47u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, ComparisonLeaf)
|
||
|
|
{
|
||
|
|
const std::uint32_t alpha = 100u;
|
||
|
|
const std::uint64_t yt = 5u;
|
||
|
|
const std::uint64_t yf = 9u;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf));
|
||
|
|
const std::uint64_t mask = k0.cmp().mask;
|
||
|
|
auto c0 = dpf::eval_point(dpf::cmp, k0, 50u);
|
||
|
|
auto c1 = dpf::eval_point(dpf::cmp, k1, 50u);
|
||
|
|
const auto [y0, y1] = dpf::yao::a2y(c0, c1, 64);
|
||
|
|
const auto bits = xor_bits<std::uint64_t>(y0, y1);
|
||
|
|
EXPECT_EQ(bits, dpf::reconstruct(c0, c1));
|
||
|
|
EXPECT_EQ(bits & mask, yt);
|
||
|
|
|
||
|
|
auto d0 = dpf::eval_point(dpf::cmp, k0, alpha);
|
||
|
|
auto d1 = dpf::eval_point(dpf::cmp, k1, alpha);
|
||
|
|
const auto [z0, z1] = dpf::yao::a2y(d0, d1, 64);
|
||
|
|
EXPECT_EQ(xor_bits<std::uint64_t>(z0, z1) & mask, yf);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, LeafBitsThroughANetlist)
|
||
|
|
{
|
||
|
|
const std::uint8_t alpha = 42;
|
||
|
|
const std::uint32_t beta = 0x6bu;
|
||
|
|
auto [k0, k1] = dpf::make_dpf(alpha, beta);
|
||
|
|
auto s0 = *dpf::eval_point(k0, alpha);
|
||
|
|
auto s1 = *dpf::eval_point(k1, alpha);
|
||
|
|
const auto [p0, p1] = dpf::yao::b2y(s0, s1, 8);
|
||
|
|
|
||
|
|
dpf::yao::netlist n;
|
||
|
|
dpf::yao::bit in[8];
|
||
|
|
for (int i = 0; i < 8; ++i)
|
||
|
|
in[i] = n.shared_in();
|
||
|
|
n.out(n.and_(in[0], in[1]));
|
||
|
|
for (int i = 0; i < 8; ++i)
|
||
|
|
n.out(in[i]);
|
||
|
|
|
||
|
|
auto [o0, o1] = dpf::yao::eval_pair(n, p0.data(), p1.data());
|
||
|
|
ASSERT_EQ(o0.size(), 9u);
|
||
|
|
EXPECT_EQ(o0[0] ^ o1[0], static_cast<std::uint8_t>((beta & 1u) & ((beta >> 1) & 1u)));
|
||
|
|
for (int i = 0; i < 8; ++i)
|
||
|
|
EXPECT_EQ(o0[static_cast<std::size_t>(i + 1)] ^ o1[static_cast<std::size_t>(i + 1)],
|
||
|
|
static_cast<std::uint8_t>((beta >> i) & 1u));
|
||
|
|
|
||
|
|
std::vector<std::uint8_t> and0{o0[0]};
|
||
|
|
std::vector<std::uint8_t> and1{o1[0]};
|
||
|
|
const auto [z0, z1] = dpf::yao::y2b<std::uint32_t>(and0, and1, 1);
|
||
|
|
EXPECT_EQ(dpf::reconstruct(z0, z1), 1u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, RandomNetlistMatchesPlain)
|
||
|
|
{
|
||
|
|
std::uint32_t rng = 0xC0FFEEu;
|
||
|
|
auto nxt = [&] {
|
||
|
|
rng = rng * 1664525u + 1013904223u;
|
||
|
|
return rng;
|
||
|
|
};
|
||
|
|
for (int trial = 0; trial < 20; ++trial)
|
||
|
|
{
|
||
|
|
dpf::yao::netlist n;
|
||
|
|
dpf::yao::bit w[24];
|
||
|
|
std::uint8_t sem[4];
|
||
|
|
std::uint8_t p0[4];
|
||
|
|
std::uint8_t p1[4];
|
||
|
|
int nwire = 4;
|
||
|
|
for (int i = 0; i < 4; ++i)
|
||
|
|
{
|
||
|
|
w[i] = n.shared_in();
|
||
|
|
sem[i] = static_cast<std::uint8_t>(nxt() & 1u);
|
||
|
|
p0[i] = static_cast<std::uint8_t>(nxt() & 1u);
|
||
|
|
p1[i] = static_cast<std::uint8_t>(sem[i] ^ p0[i]);
|
||
|
|
}
|
||
|
|
for (int g = 0; g < 32; ++g)
|
||
|
|
{
|
||
|
|
const int ia = static_cast<int>(nxt() % static_cast<std::uint32_t>(nwire));
|
||
|
|
const int ib = static_cast<int>(nxt() % static_cast<std::uint32_t>(nwire));
|
||
|
|
dpf::yao::bit d;
|
||
|
|
switch (nxt() % 3u)
|
||
|
|
{
|
||
|
|
case 0: d = n.xor_(w[ia], w[ib]); break;
|
||
|
|
case 1: d = n.and_(w[ia], w[ib]); break;
|
||
|
|
default: d = n.not_(w[ia]); break;
|
||
|
|
}
|
||
|
|
if (nwire < 24)
|
||
|
|
w[nwire++] = d;
|
||
|
|
}
|
||
|
|
for (int i = 0; i < 4; ++i)
|
||
|
|
n.out(w[nwire - 1 - i]);
|
||
|
|
const auto plain = dpf::yao::eval_plain(n, sem);
|
||
|
|
EXPECT_EQ(dpf::yao::eval_local(n, sem), plain) << trial;
|
||
|
|
auto [a, b] = dpf::yao::eval_pair(n, p0, p1);
|
||
|
|
for (std::size_t i = 0; i < plain.size(); ++i)
|
||
|
|
EXPECT_EQ(static_cast<std::uint8_t>(a[i] ^ b[i]), plain[i]) << trial;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, MixedPrivateInputs)
|
||
|
|
{
|
||
|
|
dpf::yao::netlist n;
|
||
|
|
const auto a = n.priv_in(0);
|
||
|
|
const auto b = n.priv_in(1);
|
||
|
|
const auto c = n.shared_in();
|
||
|
|
n.out(n.xor_(n.and_(a, b), c));
|
||
|
|
n.out(n.xor_public(c, 0));
|
||
|
|
n.out(n.not_(n.not_(a)));
|
||
|
|
const std::uint8_t p0[3] = {1, 0, 1};
|
||
|
|
const std::uint8_t p1[3] = {9, 1, 0};
|
||
|
|
auto [o0, o1] = dpf::yao::eval_pair(n, p0, p1);
|
||
|
|
EXPECT_EQ(static_cast<std::uint8_t>(o0[0] ^ o1[0]), 0u);
|
||
|
|
EXPECT_EQ(static_cast<std::uint8_t>(o0[1] ^ o1[1]), 1u);
|
||
|
|
EXPECT_EQ(static_cast<std::uint8_t>(o0[2] ^ o1[2]), 1u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(YaoShare, NetlistRejects)
|
||
|
|
{
|
||
|
|
dpf::yao::netlist n;
|
||
|
|
EXPECT_THROW(n.priv_in(2), std::invalid_argument);
|
||
|
|
EXPECT_THROW(n.xor_(dpf::yao::bit{3}, dpf::yao::bit{0}), std::invalid_argument);
|
||
|
|
const auto a = n.shared_in();
|
||
|
|
n.out(a);
|
||
|
|
EXPECT_THROW(n.shared_in(), std::logic_error);
|
||
|
|
std::uint8_t bad = 2;
|
||
|
|
EXPECT_THROW(dpf::yao::eval_local(n, &bad), std::invalid_argument);
|
||
|
|
}
|