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:
Ryan Henry 2026-09-28 05:59:19 -06:00
parent 695f8e84f7
commit 0d22946a0e
1835 changed files with 170291 additions and 2849 deletions

421
test/tests/yao_test.cpp Normal file
View file

@ -0,0 +1,421 @@
#include <gtest/gtest.h>
#include <array>
#include <cstdint>
#include <cstring>
#include <exception>
#include <filesystem>
#include <string>
#include <thread>
#include <unistd.h>
#include <utility>
#include <vector>
#include "aes_mmo_ref.hpp"
#include "dpf/net/trio.hpp"
#include "dpf/prg_aes.hpp"
#include "dpf/random.hpp"
#include "dpf/yao.hpp"
#include "dpf/yao_aes.hpp"
#include "dpf/verifiable.hpp"
#include "dpf/yao_share.hpp"
namespace
{
using dpf::yao::bit;
using dpf::yao::bits8;
using dpf::yao::netlist;
void bytes_to_bits(const std::uint8_t * bytes, int nbytes, std::uint8_t * bits)
{
for (int i = 0; i < nbytes; ++i)
for (int k = 0; k < 8; ++k)
bits[i * 8 + k] = static_cast<std::uint8_t>((bytes[i] >> (7 - k)) & 1u);
}
void bits_to_bytes(const std::uint8_t * bits, int nbytes, std::uint8_t * bytes)
{
for (int i = 0; i < nbytes; ++i)
{
bytes[i] = 0;
for (int k = 0; k < 8; ++k)
bytes[i] = static_cast<std::uint8_t>(bytes[i] | (bits[i * 8 + k] << (7 - k)));
}
}
std::uint8_t pack_byte(const std::uint8_t * bits)
{
std::uint8_t b = 0;
bits_to_bytes(bits, 1, &b);
return b;
}
netlist sbox_netlist()
{
netlist n;
const bits8 in = dpf::yao::shared_byte(n);
dpf::yao::out_byte(n, dpf::yao::sbox(n, in));
return n;
}
void expand_key(const std::uint8_t key[16], std::uint8_t rk[11][16])
{
std::uint8_t w[176];
std::memcpy(w, key, 16);
int n = 16;
std::uint8_t rcon = 1;
while (n < 176)
{
std::uint8_t t[4];
std::memcpy(t, w + n - 4, 4);
if (n % 16 == 0)
{
const std::uint8_t tmp = t[0];
t[0] = t[1];
t[1] = t[2];
t[2] = t[3];
t[3] = tmp;
for (int i = 0; i < 4; ++i)
t[i] = dpf::party::aes_ref::sbox_at(t[i]);
t[0] = static_cast<std::uint8_t>(t[0] ^ rcon);
rcon = dpf::party::aes_ref::xtime(rcon);
}
for (int i = 0; i < 4; ++i)
{
w[n] = static_cast<std::uint8_t>(w[n - 16] ^ t[i]);
++n;
}
}
std::memcpy(rk, w, 176);
}
void encrypt_aes128(std::uint8_t s[16], const std::uint8_t rk[11][16])
{
using namespace dpf::party::aes_ref;
add_round_key(s, rk[0]);
for (int r = 1; r < 10; ++r)
{
sub_bytes(s);
shift_rows(s);
mix_columns(s);
add_round_key(s, rk[r]);
}
sub_bytes(s);
shift_rows(s);
add_round_key(s, rk[10]);
}
std::vector<std::uint8_t> reconstruct(std::vector<std::uint8_t> a,
const std::vector<std::uint8_t> & b)
{
EXPECT_EQ(a.size(), b.size());
for (std::size_t i = 0; i < a.size(); ++i)
a[i] = static_cast<std::uint8_t>(a[i] ^ b[i]);
return a;
}
template <typename Fn0, typename Fn1>
void run_pair(Fn0 && fn0, Fn1 && fn1)
{
const auto dir = std::filesystem::temp_directory_path()
/ ("libdpf_yao_" + std::to_string(::getpid()));
std::filesystem::create_directories(dir);
std::exception_ptr ep0;
std::exception_ptr ep1;
std::thread t0([&] {
try
{
auto net = dpf::net::trio::connect_pair(dpf::net::role::p0, dir.string());
fn0(net);
}
catch (...)
{
ep0 = std::current_exception();
}
});
std::thread t1([&] {
try
{
auto net = dpf::net::trio::connect_pair(dpf::net::role::p1, dir.string());
fn1(net);
}
catch (...)
{
ep1 = std::current_exception();
}
});
t0.join();
t1.join();
std::filesystem::remove_all(dir);
if (ep0)
std::rethrow_exception(ep0);
if (ep1)
std::rethrow_exception(ep1);
}
} // namespace
TEST(Yao, AndXorNot)
{
netlist n;
const bit a = n.shared_in();
const bit b = n.shared_in();
const bit x = n.xor_(a, b);
const bit y = n.and_(a, b);
const bit z = n.not_(a);
const bit w = n.xnor_(a, b);
n.out(x);
n.out(y);
n.out(z);
n.out(w);
n.out(n.xor_public(y, 1));
EXPECT_EQ(n.n_and(), 1u);
for (int av = 0; av < 2; ++av)
{
for (int bv = 0; bv < 2; ++bv)
{
const std::uint8_t in[2] = {
static_cast<std::uint8_t>(av), static_cast<std::uint8_t>(bv)};
const auto plain = dpf::yao::eval_plain(n, in);
const auto got = dpf::yao::eval_local(n, in);
const std::uint8_t expect[5] = {
static_cast<std::uint8_t>(av ^ bv),
static_cast<std::uint8_t>(av & bv),
static_cast<std::uint8_t>(av ^ 1),
static_cast<std::uint8_t>(1 ^ av ^ bv),
static_cast<std::uint8_t>((av & bv) ^ 1)};
EXPECT_EQ(plain, (std::vector<std::uint8_t>(expect, expect + 5)));
EXPECT_EQ(got, plain);
}
}
}
TEST(Yao, SboxPlainAndGarbled)
{
const netlist n = sbox_netlist();
EXPECT_EQ(n.n_and(), static_cast<std::uint32_t>(aes_bp::and_count));
for (int x = 0; x < 256; ++x)
{
const auto byte = static_cast<std::uint8_t>(x);
std::uint8_t bits[8];
bytes_to_bits(&byte, 1, bits);
const auto plain = dpf::yao::eval_plain(n, bits);
const auto garbled = dpf::yao::eval_local(n, bits);
EXPECT_EQ(pack_byte(plain.data()), dpf::party::aes_ref::sbox_at(byte)) << x;
EXPECT_EQ(garbled, plain) << x;
}
}
TEST(Yao, SboxShares)
{
const netlist n = sbox_netlist();
const std::uint8_t byte = 0x53;
std::uint8_t semantic[8];
bytes_to_bits(&byte, 1, semantic);
std::uint8_t p0[8], p1[8];
for (int i = 0; i < 8; ++i)
{
p0[i] = static_cast<std::uint8_t>(dpf::uniform_sample<unsigned char>() & 1u);
p1[i] = static_cast<std::uint8_t>(semantic[i] ^ p0[i]);
}
auto [a, b] = dpf::yao::eval_pair(n, p0, p1);
EXPECT_EQ(pack_byte(reconstruct(a, b).data()), dpf::party::aes_ref::sbox_at(byte));
}
TEST(Yao, Aes128Nist)
{
const std::uint8_t key[16] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
const std::uint8_t pt[16] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
const std::uint8_t ct[16] = {
0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30,
0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a};
std::uint8_t rk[11][16];
expand_key(key, rk);
std::uint8_t state[16];
std::memcpy(state, pt, 16);
encrypt_aes128(state, rk);
EXPECT_EQ(std::vector<std::uint8_t>(state, state + 16),
std::vector<std::uint8_t>(ct, ct + 16));
const netlist n = dpf::yao::aes128_netlist();
EXPECT_EQ(n.n_and(), 6400u);
std::uint8_t bits[256];
bytes_to_bits(pt, 16, bits);
bytes_to_bits(key, 16, bits + 128);
const auto plain = dpf::yao::eval_plain(n, bits);
std::uint8_t got[16];
bits_to_bytes(plain.data(), 16, got);
EXPECT_EQ(std::vector<std::uint8_t>(got, got + 16),
std::vector<std::uint8_t>(ct, ct + 16));
const auto garbled = dpf::yao::eval_local(n, bits);
bits_to_bytes(garbled.data(), 16, got);
EXPECT_EQ(std::vector<std::uint8_t>(got, got + 16),
std::vector<std::uint8_t>(ct, ct + 16));
}
TEST(Yao, AesMmoMatchesPrg)
{
const std::uint32_t positions[] = {0u, 1u, 0x01020304u};
const std::uint8_t msg[16] = {
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
simde__m128i block{};
std::memcpy(&block, msg, 16);
std::uint8_t bits[128];
dpf::yao::block_to_bits(bits, block);
for (std::uint32_t pos : positions)
{
const auto expect = dpf::prg::aes128::eval(block, pos);
const netlist n = dpf::yao::aes_mmo_netlist(pos);
EXPECT_EQ(n.n_and(), 5120u);
const auto got_bits = dpf::yao::eval_local(n, bits);
const auto got = dpf::yao::bits_to_block(got_bits.data());
std::uint8_t a[16], b[16];
std::memcpy(a, &expect, 16);
std::memcpy(b, &got, 16);
EXPECT_EQ(std::vector<std::uint8_t>(a, a + 16),
std::vector<std::uint8_t>(b, b + 16)) << pos;
}
}
TEST(Yao, SessionSboxAndReuse)
{
const netlist gates = [] {
netlist n;
const bit a = n.priv_in(0);
const bit b = n.priv_in(1);
const bit c = n.shared_in();
n.out(n.xor_(n.and_(a, b), c));
n.out(n.not_(c));
return n;
}();
const netlist priv_xor = [] {
netlist n;
const bit a = n.priv_in(0);
const bit b = n.priv_in(0);
n.out(n.xor_(a, b));
return n;
}();
const netlist box = sbox_netlist();
const std::uint8_t byte = 0xa7;
std::uint8_t semantic[8];
bytes_to_bits(&byte, 1, semantic);
std::uint8_t sp0[8], sp1[8];
for (int i = 0; i < 8; ++i)
{
sp0[i] = static_cast<std::uint8_t>(i & 1u);
sp1[i] = static_cast<std::uint8_t>(semantic[i] ^ sp0[i]);
}
std::vector<std::uint8_t> g0, g1, s0, s1, x0, x1;
run_pair(
[&](dpf::net::trio & net) {
dpf::yao::session s;
const std::uint8_t xin[2] = {1, 1};
x0 = s.eval(0, priv_xor, xin, net.to(dpf::net::role::p1));
const std::uint8_t in0[3] = {1, 0, 1};
g0 = s.eval(0, gates, in0, net.to(dpf::net::role::p1));
const std::uint8_t in1[3] = {1, 0, 0};
auto second = s.eval(0, gates, in1, net.to(dpf::net::role::p1));
g0.insert(g0.end(), second.begin(), second.end());
s0 = s.eval(0, box, sp0, net.to(dpf::net::role::p1));
},
[&](dpf::net::trio & net) {
dpf::yao::session s;
const std::uint8_t xin[2] = {0, 0};
x1 = s.eval(1, priv_xor, xin, net.to(dpf::net::role::p0));
const std::uint8_t in0[3] = {0, 1, 0};
g1 = s.eval(1, gates, in0, net.to(dpf::net::role::p0));
const std::uint8_t in1[3] = {0, 0, 1};
auto second = s.eval(1, gates, in1, net.to(dpf::net::role::p0));
g1.insert(g1.end(), second.begin(), second.end());
s1 = s.eval(1, box, sp1, net.to(dpf::net::role::p0));
});
EXPECT_EQ(reconstruct(x0, x1), std::vector<std::uint8_t>{0});
const auto opened = reconstruct(g0, g1);
const std::uint8_t expect_gates[4] = {0, 0, 1, 0};
EXPECT_EQ(std::vector<std::uint8_t>(opened.begin(), opened.begin() + 4),
std::vector<std::uint8_t>(expect_gates, expect_gates + 4));
EXPECT_EQ(pack_byte(reconstruct(s0, s1).data()),
dpf::party::aes_ref::sbox_at(byte));
}
TEST(Yao, LeafBitsOnSession)
{
const std::uint32_t beta = 0x6bu;
const auto s0 = dpf::subtractive_share<std::uint32_t, 0>::from_raw(beta);
const auto s1 = dpf::subtractive_share<std::uint32_t, 1>::from_raw(0);
const auto [p0, p1] = dpf::yao::b2y(s0, s1, 8);
const dpf::yao::netlist n = [] {
dpf::yao::netlist c;
dpf::yao::bit in[8];
for (int i = 0; i < 8; ++i)
in[i] = c.shared_in();
c.out(c.and_(in[0], in[1]));
return c;
}();
std::vector<std::uint8_t> o0, o1;
bool locked = false;
run_pair(
[&](dpf::net::trio & net) {
dpf::yao::session s;
auto & link = net.to(dpf::net::role::p1);
o0 = s.eval(0, n, p0.data(), link);
try
{
s.eval(1, n, p0.data(), link);
}
catch (const std::logic_error &)
{
locked = true;
}
},
[&](dpf::net::trio & net) {
dpf::yao::session s;
o1 = s.eval(1, n, p1.data(), net.to(dpf::net::role::p0));
});
EXPECT_TRUE(locked);
ASSERT_EQ(o0.size(), 1u);
EXPECT_EQ(static_cast<std::uint8_t>(o0[0] ^ o1[0]), 1u);
const auto [z0, z1] = dpf::yao::y2b<std::uint32_t>(o0, o1, 1);
EXPECT_EQ(dpf::reconstruct(z0, z1), 1u);
}
TEST(Yao, CorrectionLevelMatchesMakeCs)
{
const simde__m128i s0 = dpf::uniform_sample<simde__m128i>();
const simde__m128i s1 = dpf::uniform_sample<simde__m128i>();
const std::uint64_t prefix = 0x0123456789abcdefull;
const std::uint64_t share0 = 0x1111222233334444ull;
const std::uint64_t share1 = prefix ^ share0;
const std::size_t level = 0x10ffu;
std::uint8_t in0[dpf::yao::correction_level_in_bits];
std::uint8_t in1[dpf::yao::correction_level_in_bits];
dpf::yao::pack_correction_level(0, s0, share0, level, in0);
dpf::yao::pack_correction_level(1, s1, share1, level, in1);
const auto nl = dpf::yao::correction_level_netlist();
EXPECT_EQ(nl.n_and(), dpf::yao::correction_level_ands);
EXPECT_EQ(nl.n_in(), static_cast<std::uint32_t>(dpf::yao::correction_level_in_bits));
auto [a, b] = dpf::yao::eval_pair(nl, in0, in1);
ASSERT_EQ(a.size(), dpf::yao::correction_level_out_bits);
const auto expect = dpf::detail::vdpf::make_cs(level, prefix, s0, s1);
for (int lane = 0; lane < 4; ++lane)
{
std::uint8_t bits[128];
for (int i = 0; i < 128; ++i)
bits[i] = static_cast<std::uint8_t>(
a[static_cast<std::size_t>(lane * 128 + i)]
^ b[static_cast<std::size_t>(lane * 128 + i)]);
const auto got = dpf::yao::bits_to_block(bits);
EXPECT_EQ(std::memcmp(&got, &expect[static_cast<std::size_t>(lane)], 16), 0)
<< lane;
}
}