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

View file

@ -0,0 +1,154 @@
#include <atomic>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <random>
#include <thread>
#include <vector>
#include "dpf/launch.hpp"
#include "dpf/net/client_link.hpp"
#include "dpf/run_log.hpp"
// A client splits a secret into two additive shares and sends share i to party
// i over a client link; the two parties then open the sum over their own
// party link. Both links are TLS 1.3; each end logs how it authenticated the
// other.
//
// c++ -std=c++17 -march=native -pthread -I include -I thirdparty \
// examples/protocol/client_shares.cpp -lsctp -lssl -lcrypto -o client_shares
// ./client_shares # development certificate (logged)
// ./dpf_keygen srv.key # prints srv's public key
// ./client_shares --server_identity=srv.key --client_pin=<srv public key>
// ./client_shares --client_verify=off # accepts any server (logged)
//
// With --server_identity and no --client_pin the client refuses the server:
// clients always verify unless told not to.
int main(int argc, char ** argv)
{
try
{
auto cfg = dpf::app::run_config::from_env();
for (const auto & extra : cfg.apply_args(argc, argv))
throw std::invalid_argument("unknown argument " + extra);
if (cfg.kind != dpf::net::transport::mux && cfg.kind != dpf::net::transport::parallel)
cfg.kind = dpf::net::transport::mux;
dpf::app::start_logging(cfg);
// Each party accepts one client and keeps the share it sends.
std::atomic<unsigned short> ports[2] = {{0}, {0}};
std::uint64_t shares[2] = {0, 0};
std::string errors[2];
std::vector<std::thread> parties;
for (int p = 0; p < 2; ++p)
parties.emplace_back([&, p] {
try
{
asio::io_context io;
dpf::net::client_listener l(io, cfg.server, 1, cfg.policy, cfg.limits);
ports[p].store(l.listen());
auto c = l.accept();
bool done = false;
std::error_code ec;
c.link->async_read(0, &shares[p], 8, [&](const std::error_code & e) {
ec = e;
done = true;
});
while (!done)
{
if (io.stopped())
io.restart();
io.run_one();
}
if (ec)
throw std::system_error(ec, "reading the client's share");
}
catch (const std::exception & e)
{
errors[p] = e.what();
ports[p].store(1);
}
});
// The client: one fresh share per party, each on its own verified link.
const std::uint64_t secret = 42;
std::random_device rd;
const std::uint64_t r = (static_cast<std::uint64_t>(rd()) << 32) | rd();
const std::uint64_t mine[2] = {r, secret - r};
std::string client_error;
for (int p = 0; p < 2 && client_error.empty(); ++p)
{
while (ports[p].load() == 0)
std::this_thread::yield();
try
{
asio::io_context io;
auto c = dpf::net::connect_server(io, cfg.host, ports[p].load(), cfg.client,
1, cfg.policy, cfg.limits);
std::cout << "client -> party " << p << ": " << c.security.protocol << " "
<< c.security.cipher << ", server auth=" << c.security.peer_auth
<< "\n";
bool done = false;
c.link->async_write(0, &mine[p], 8, [&](const std::error_code &) {
done = true;
});
while (!done)
{
if (io.stopped())
io.restart();
io.run_one();
}
c.link.reset();
io.poll();
}
catch (const std::exception & e)
{
client_error = e.what();
}
}
if (!client_error.empty())
{
// Unblock the listeners that are still waiting for this client.
for (int p = 0; p < 2; ++p)
{
std::error_code ec;
asio::io_context io;
asio::ip::tcp::socket s(io);
if (ports[p].load() > 1)
s.connect({asio::ip::make_address("127.0.0.1"), ports[p].load()}, ec);
}
}
for (auto & t : parties)
t.join();
if (!client_error.empty())
throw std::runtime_error("client: " + client_error);
for (const auto & e : errors)
if (!e.empty())
throw std::runtime_error("party: " + e);
// The parties open the sum over their party link.
dpf::protocol::composer c0(0), c1(1);
auto x0 = c0.input(dpf::protocol::domain::a, 8);
auto x1 = c1.input(dpf::protocol::domain::a, 8);
auto o0 = c0.exchange(x0);
(void)c1.exchange(x1);
auto p0 = c0.schedule();
auto p1 = c1.schedule();
dpf::app::party_values v0(p0.nodes().size()), v1(p1.nodes().size());
v0[x0.id].assign(8, 0);
v1[x1.id].assign(8, 0);
std::memcpy(v0[x0.id].data(), &shares[0], 8);
std::memcpy(v1[x1.id].data(), &shares[1], 8);
(void)dpf::run_two_party(p0, p1, v0, v1, {}, cfg);
std::uint64_t open = 0;
std::memcpy(&open, v0[o0.id].data(), 8);
std::cout << "parties opened " << open << " (share 0 = " << shares[0] << ")\n";
return open == secret ? 0 : 1;
}
catch (const std::exception & e)
{
std::cerr << "client_shares: " << e.what() << "\n";
return 1;
}
}

View file

@ -0,0 +1,285 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>
#include "dpf.hpp"
#include "dpf/net/stream_array.hpp"
#include "dpf/protocol_factory.hpp"
// Protocol composition schedules (compose.hpp). Records FSS walks, ABY
// products, and RSS refreshes on one RoundSink plan — the shapes Express,
// Sabre, Pika early-stop, Poplar prefixes, and Duoram scale use by hand.
//
// The final block drives a composed open on the stream framework
// (drive_both_on_streams). The identical schedule also runs over the truly
// asynchronous backends (dpf::net::async_stream_array + async_round_sink, or
// dpf::async::overlapped_byte_protocol) and, on Linux, over real SCTP
// (async_sctp_stream_array). The experiment_bench harness picks the transport
// via DPF_TRANSPORT=memory|stream|async|mux|parallel|sctp.
//
// c++ -std=c++17 -march=native -I include -I thirdparty
// examples/protocol/compose_schedule.cpp
namespace
{
using dpf::protocol::composer;
using dpf::protocol::domain;
using dpf::protocol::effect;
namespace opcodes = dpf::protocol::opcodes;
} // namespace
int main()
{
// Express / Sabre: audit rides in the last CW flush (depth exchanges, not
// depth+1).
{
composer naive(0);
auto seed = naive.input(domain::fss, 16);
auto sketch = naive.input(domain::a, 8);
auto leaf = naive.fss_point(seed, 4, 16);
auto dep = naive.compute(opcodes::user_base + 1, {leaf, sketch},
domain::a, 8);
(void)naive.exchange(dep);
const auto naive_ex = naive.schedule().exchange_waves();
composer fused(0);
auto seed_f = fused.input(domain::fss, 16);
auto sketch_f = fused.input(domain::a, 8);
auto wr = fused.fss_point_fused(seed_f, 4, 16, sketch_f);
const auto fused_ex = fused.schedule().exchange_waves();
if (fused_ex >= naive_ex || fused.domain_of(wr.trailer_open) != domain::a)
{
std::cerr << "compose fuse\n";
return 1;
}
std::cout << "express_fuse " << naive_ex << "->" << fused_ex << "\n";
}
// Pika / small-output PIR: BGI Remark 3.4 early-stop drops ν CW rounds.
{
composer full(0);
auto seed = full.input(domain::fss, 16);
(void)full.fss_point(seed, 8, 16);
composer early(0);
auto seed_e = early.input(domain::fss, 16);
(void)early.fss_point_early_stop(seed_e, 8, /*early_stop=*/3, 16);
const auto a = full.schedule().exchange_waves();
const auto b = early.schedule().exchange_waves();
if (b != a - 3)
{
std::cerr << "compose early_stop\n";
return 1;
}
std::cout << "early_stop " << a << "->" << b << "\n";
}
// Poplar: prefix share after each CW, same exchange-wave depth.
{
composer c(0);
auto seed = c.input(domain::fss, 16);
auto wr = c.level_walk_prefixes(seed, 5, 16, /*prefix_bytes=*/8);
auto p = c.schedule();
if (wr.at_level.size() != 5 || p.exchange_waves() != 5)
{
std::cerr << "compose prefixes\n";
return 1;
}
std::cout << "prefixes " << p.exchange_waves() << "\n";
}
// DCF block_width: variable CW slot sizes.
{
composer c(0);
auto seed = c.input(domain::fss, 16);
const std::vector<std::size_t> slots = {16, 4, 4, 16};
(void)c.level_walk_sized(seed, slots);
auto p = c.schedule();
if (p.value_bytes_of(p.wave(1).exchanges[0].id) != 4)
{
std::cerr << "compose sized\n";
return 1;
}
std::cout << "sized_slots ok\n";
}
// RSS product → neighbor y-exchange → RSS (one round).
{
composer c(0);
auto x = c.input(domain::rss, 16);
auto y = c.input(domain::rss, 16);
auto z = c.rss_product_replicated(x, y);
if (c.domain_of(z) != domain::rss || c.schedule().exchange_waves() != 1)
{
std::cerr << "compose rss\n";
return 1;
}
std::cout << "rss_refresh 1\n";
}
// Duoram write-scale: FSS leaf feeds ABY; beaver waits for the leaf wave.
{
composer c(0);
auto seed = c.input(domain::fss, 16);
auto leaf = c.fss_point(seed, 4, 16);
auto scale = c.input(domain::a, 8);
auto scaled = c.aby_product<std::uint64_t>(leaf, scale);
auto p = c.schedule();
if (p.wave_of(scaled) < p.wave_of(leaf))
{
std::cerr << "compose duoram_scale\n";
return 1;
}
std::cout << "duoram_scale wave " << p.wave_of(scaled) << "\n";
}
// Round-aware ABY: sign×linear stays one online round.
{
composer c(0);
auto & s = c.aby<std::uint64_t>();
auto sgn = s.input();
auto x = s.input();
auto a0 = s.input();
auto a1 = s.input();
auto lin = s(sgn * (a1 * x + a0));
if (s.round_of(lin) != 1)
{
std::cerr << "compose aby_rounds\n";
return 1;
}
std::cout << "aby_rounds 1\n";
}
// Doerner–Shelat: 5 opens per level; OH adds 80 AND-layers / level.
{
composer c(0);
auto seed = c.input(domain::fss, 16);
auto tip = c.level_walk_ds(seed, 2, 16);
if (c.schedule().exchange_waves() != 10 || tip.id == seed.id)
{
std::cerr << "compose ds_walk\n";
return 1;
}
std::cout << "ds_walk 10\n";
composer c_oh(0);
auto tip_oh = c_oh.level_walk_ds(c_oh.input(domain::fss, 16), 1, 16,
/*oh=*/true);
(void)tip_oh;
const auto want = dpf::net::compose_ds_slot_bytes(1, 16, true).size();
if (c_oh.schedule().exchange_waves() != want)
{
std::cerr << "compose ds_oh\n";
return 1;
}
std::cout << "ds_oh " << want << "\n";
}
// Adaptive idpf: one packed L‖R open per step.
{
composer c(0);
auto seed = c.input(domain::fss, 16);
auto f = c.begin_adaptive_prefix(seed);
f = c.step_adaptive_prefix(f, 16, 8);
f = c.retain_adaptive_prefix(f, 0);
if (c.schedule().exchange_waves() != 1)
{
std::cerr << "compose adaptive\n";
return 1;
}
std::cout << "adaptive 1\n";
}
// default_plan: rounds == exchange_waves (sink-aligned).
{
composer c(0);
auto leaf = c.fss_point(c.input(domain::fss, 16), 4, 16);
(void)leaf;
auto p = c.default_plan();
if (p.rounds() != p.exchange_waves()
|| p.rounds() != p.slot_bytes_all().size() || p.rounds() != 4)
{
std::cerr << "compose default_plan\n";
return 1;
}
std::cout << "default_plan 4\n";
}
// Multipoint buckets: CW waves pack; answers one open.
{
composer c(0);
auto mr = c.multipoint_fan(2,
[&](std::size_t) { return c.input(domain::fss, 16); }, 3, 16, 8);
(void)mr;
if (c.schedule().exchange_waves() != 4)
{
std::cerr << "compose multipoint\n";
return 1;
}
std::cout << "multipoint 4\n";
}
// Multi-lane ABY: independent barriers, one wave.
{
composer c(0);
auto z0 = c.aby_product<std::uint64_t>(c.input(domain::a, 8),
c.input(domain::a, 8), 0);
auto z1 = c.aby_product<std::uint64_t>(c.input(domain::a, 8),
c.input(domain::a, 8), 1);
if (c.schedule().exchange_waves() != 1
|| c.schedule().effect_count(effect::exchange) != 2
|| z0.id == z1.id)
{
std::cerr << "compose multilane\n";
return 1;
}
std::cout << "multilane 1\n";
}
// Prepaid defer + rotate: zero online FSS rounds.
{
composer c(0);
auto buf = c.defer_expand(c.input(domain::fss, 16), 4, 16);
auto rot = c.rotate_share(buf, 7);
if (c.schedule().exchange_waves() != 0 || rot.id == buf.id)
{
std::cerr << "compose defer\n";
return 1;
}
std::cout << "defer 0\n";
}
// stream_array: drive a compose open through drive_both_on_streams.
{
composer c0(0);
composer c1(1);
auto x0 = c0.input(domain::a, 8);
auto x1 = c1.input(domain::a, 8);
auto e0 = c0.exchange(x0);
auto e1 = c1.exchange(x1);
auto p0 = c0.schedule();
auto p1 = c1.schedule();
std::vector<std::vector<std::uint8_t>> v0(p0.nodes().size()),
v1(p1.nodes().size());
const std::uint64_t a = 3, b = 5;
v0[x0.id].assign(8, 0);
v1[x1.id].assign(8, 0);
std::memcpy(v0[x0.id].data(), &a, 8);
std::memcpy(v1[x1.id].data(), &b, 8);
dpf::protocol::drive_both_on_streams(p0, p1, v0, v1);
std::uint64_t open0 = 0, open1 = 0;
std::memcpy(&open0, v0[e0.id].data(), 8);
std::memcpy(&open1, v1[e1.id].data(), 8);
if (open0 != 8 || open1 != 8)
{
std::cerr << "drive_plan_on_streams open\n";
return 1;
}
std::cout << "drive_plan_on_streams_ok " << open0 << "\n";
}
return 0;
}

View file

@ -0,0 +1,86 @@
#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>
#include "dpf/launch.hpp"
#include "dpf/run_log.hpp"
// One party of a two- or three-party run, one process per party.
//
// c++ -std=c++17 -march=native -pthread -I include -I thirdparty \
// -DLIBDPF_GIT_REV="\"$(git describe --always --dirty)\"" \
// examples/protocol/party_node.cpp -lsctp -lssl -lcrypto -o party_node
// ./party_node --party=0 --peers=127.0.0.1:9100,127.0.0.1:9101 &
// ./party_node --party=1 --peers=127.0.0.1:9100,127.0.0.1:9101
//
// Any run_config key works as a flag (--transport=parallel --lanes=2 ...).
// Start order does not matter: connects retry until --connect_ms.
//
// Links are TLS 1.3. With no keys they are encrypted but unauthenticated (and
// the log says so). To authenticate, give each party a key and the others'
// public keys (dpf_keygen p0.key prints p0's), for example in p0.conf:
// identity = p0.key
// peer.1 = <p1's public key>
// and run ./party_node --config=p0.conf --party=0 --peers=...
// The run log goes to stderr at info; --log=file:/tmp/p0.log --log_level=debug
// or DPF_LOG / DPF_LOG_LEVEL redirect it (see dpf/run_log.hpp).
//
// With three peers the protocol adds a dealer-delivered mask: party 2 is the
// dealer, and parties 0 and 1 each open (input + mask share).
int main(int argc, char ** argv)
{
try
{
const auto args = dpf::app::parse_node_args(argc, argv);
dpf::app::start_logging(args.cfg);
const unsigned me = args.party;
const bool dealer = args.peers.size() == 3;
dpf::protocol::composer c(me);
auto x = c.input(dpf::protocol::domain::a, 8);
dpf::protocol::node opened{};
dpf::protocol::node mask{};
if (dealer)
{
auto pad = c.input(dpf::protocol::domain::a, 8);
mask = c.dealer_deliver(pad);
opened = c.exchange(x);
(void)pad;
}
else
opened = c.exchange(x);
auto plan = c.schedule();
dpf::app::party_values values(plan.nodes().size());
for (auto & v : values)
v.assign(8, 0);
const std::uint64_t mine = me == 0 ? 11 : 31;
std::memcpy(values[x.id].data(), &mine, 8);
if (dealer && me == 2)
{
const std::uint64_t pad = 7;
std::memcpy(values[plan.inputs_of(mask.id)[0]].data(), &pad, 8);
}
const auto wire = dpf::app::run_node(args, plan, values);
std::uint64_t got = 0;
std::memcpy(&got, values[opened.id].data(), 8);
std::cout << "party " << me << " open=" << got;
if (dealer && me < 2)
{
std::uint64_t m = 0;
std::memcpy(&m, values[mask.id].data(), 8);
std::cout << " mask=" << m;
}
std::cout << " wire_out=" << wire.bytes_out << " wire_in=" << wire.bytes_in
<< "\n";
return (me == 2 || got == 42) ? 0 : 1;
}
catch (const std::exception & e)
{
DPF_LOG(error, "node.failed").kv("what", e.what());
std::cerr << "party_node: " << e.what() << "\n";
return 1;
}
}

View file

@ -0,0 +1,99 @@
/// @file examples/protocol/share_runtime.cpp
/// @brief Smoke demo: stream_array dealer/peer gadgets and clear sanity checks.
/// @details Also drives a composed open on the stream framework
/// (`drive_both_on_streams`). The same protocol runs unchanged over the
/// truly asynchronous backends (`dpf::net::async_stream_array` +
/// `dpf::async::overlapped_byte_protocol`, or `async_round_sink`), and
/// on Linux over real SCTP (`async_sctp_stream_array`, index i -> SCTP
/// stream i). The performance harness (experiment_bench) selects any of
/// these with `DPF_TRANSPORT=memory|stream|async|mux|parallel|sctp`.
#include <cstdint>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>
#include "dpf/compose.hpp"
#include "dpf/factory_gadgets.hpp"
#include "dpf/net/stream_array.hpp"
#include "dpf/protocol_factory.hpp"
#include "dpf/rss_seed.hpp"
int main()
{
auto bundle = dpf::rss::sample_seed_bundle();
auto z0 = dpf::rss::zero_share<std::uint64_t>(
dpf::rss::party_seeds::from_bundle(bundle, 0), 0);
auto z1 = dpf::rss::zero_share<std::uint64_t>(
dpf::rss::party_seeds::from_bundle(bundle, 1), 0);
auto z2 = dpf::rss::zero_share<std::uint64_t>(
dpf::rss::party_seeds::from_bundle(bundle, 2), 0);
std::cout << "rss_zero_sum=" << (z0 + z1 + z2) << "\n";
// Dealer: one ring triple per party; peer: d and e opens.
auto peer = dpf::net::make_memory_stream_pair(2);
auto d0 = dpf::net::make_memory_stream_pair(1);
auto d1 = dpf::net::make_memory_stream_pair(1);
constexpr std::uint16_t limb = 8;
dpf::factory::make_dealer(d0.first, d1.first, 1, [limb] {
return dpf::factory::deal_ring_triple(limb);
});
std::uint64_t prod0 = 0, prod1 = 0;
std::thread t0([&] {
prod0 = dpf::factory::beaver_mul_online(0, 6, 7, peer.first, d0.second,
0, 0, 1, limb);
});
std::thread t1([&] {
prod1 = dpf::factory::beaver_mul_online(1, 0, 0, peer.second, d1.second,
0, 0, 1, limb);
});
t0.join();
t1.join();
std::cout << "beaver_mul_open=" << (prod0 + prod1) << "\n";
// GMW AND 1∧1 via make_protocol_factory.
auto and_peer = dpf::net::make_memory_stream_pair(1);
auto ad0 = dpf::net::make_memory_stream_pair(1);
auto ad1 = dpf::net::make_memory_stream_pair(1);
dpf::factory::make_dealer(ad0.first, ad1.first, 1,
dpf::factory::make_gmw_and_dealer_functor());
std::uint8_t z0b = 0, z1b = 0;
std::thread a0([&] {
z0b = dpf::factory::gmw_and_online(0, 1, 1, and_peer.first, ad0.second);
});
std::thread a1([&] {
z1b = dpf::factory::gmw_and_online(1, 0, 0, and_peer.second, ad1.second);
});
a0.join();
a1.join();
std::cout << "gmw_and_xor=" << static_cast<unsigned>(z0b ^ z1b) << "\n";
// Compose an open and drive both parties on the stream framework. This is
// the same schedule the async backends run; here it uses in-process memory
// stream arrays via drive_both_on_streams.
{
using dpf::protocol::domain;
dpf::protocol::composer comp0(0), comp1(1);
auto a = comp0.input(domain::a, 8);
auto b = comp1.input(domain::a, 8);
auto oa = comp0.exchange(a);
auto ob = comp1.exchange(b);
auto p0 = comp0.schedule();
auto p1 = comp1.schedule();
std::vector<std::vector<std::uint8_t>> va(p0.nodes().size()),
vb(p1.nodes().size());
const std::uint64_t xa = 17, xb = 25;
va[a.id].assign(8, 0);
vb[b.id].assign(8, 0);
std::memcpy(va[a.id].data(), &xa, 8);
std::memcpy(vb[b.id].data(), &xb, 8);
dpf::protocol::drive_both_on_streams(p0, p1, va, vb);
std::uint64_t open0 = 0;
std::memcpy(&open0, va[oa.id].data(), 8);
(void)ob;
std::cout << "compose_open_on_streams=" << open0 << "\n";
}
return 0;
}

View file

@ -0,0 +1,83 @@
/// @file examples/protocol/stream_app_smoke.cpp
/// @brief Thin app-runtime smoke: prep files or memory dealer + compose on streams.
/// @details Drives a composed open via `drive_both_on_streams` (the stream
/// framework). The same schedule runs unchanged over the event-driven
/// backends (`dpf::net::async_stream_array` + `async_round_sink` /
/// `dpf::async::overlapped_byte_protocol`) and, on Linux, over real
/// SCTP (`async_sctp_stream_array`). The `experiment_bench` harness
/// selects the transport with
/// `DPF_TRANSPORT=memory|stream|async|mux|parallel|sctp`.
#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>
#include "dpf/app_runtime.hpp"
#include "dpf/compose.hpp"
#include "dpf/prep_source.hpp"
#include "dpf/protocol_roles.hpp"
namespace
{
void put_raw(std::vector<std::vector<std::uint8_t>> & values,
dpf::protocol::node n, const void * src, std::size_t nbyte)
{
values[n.id].assign(nbyte, 0);
std::memcpy(values[n.id].data(), src, nbyte);
}
} // namespace
int main()
{
// Prep: file dealer round-trip (roles helper + app_runtime reader).
{
const std::string base = "/tmp/libdpf_stream_app_smoke_prep";
dpf::prep::demand d{};
d.ring_triples = 1;
dpf::roles::dealer_write_files(base, d);
auto c0 = dpf::app::open_file_prep_cursor(base, 0);
auto c1 = dpf::app::open_file_prep_cursor(base, 1);
if (c0.limb() != 8 || c1.limb() != 8)
{
std::cerr << "prep cursor\n";
return 1;
}
}
// Online: memory dealer + single-wave exchange plan on stream arrays.
dpf::protocol::composer c0(0);
dpf::protocol::composer c1(1);
auto x0 = c0.input(dpf::protocol::domain::a, 8);
auto x1 = c1.input(dpf::protocol::domain::a, 8);
auto e0 = c0.exchange(x0);
auto e1 = c1.exchange(x1);
auto p0 = c0.schedule();
auto p1 = c1.schedule();
dpf::prep::demand d{};
d.ring_triples = 1;
auto [d0, d1] = dpf::prep::deal_memory_pair(d);
(void)d0;
(void)d1;
std::vector<std::vector<std::uint8_t>> v0(p0.nodes().size());
std::vector<std::vector<std::uint8_t>> v1(p1.nodes().size());
const std::uint64_t a = 5, b = 9;
put_raw(v0, x0, &a, 8);
put_raw(v1, x1, &b, 8);
dpf::protocol::drive_both_on_streams(p0, p1, v0, v1);
std::uint64_t open0 = 0, open1 = 0;
std::memcpy(&open0, v0[e0.id].data(), 8);
std::memcpy(&open1, v1[e1.id].data(), 8);
if (open0 != 14u || open1 != 14u)
{
std::cerr << "exchange " << open0 << " " << open1 << "\n";
return 1;
}
std::cout << "ok\n";
return 0;
}

View file

@ -0,0 +1,145 @@
/// @file examples/protocol/stream_dpf3_smoke.cpp
/// @brief Local DPF3 eval plus trio-shaped stream edges (peer / rss_next / dealer).
#include <cstdint>
#include <cstring>
#include <iostream>
#include <thread>
#include <type_traits>
#include <vector>
#include "dpf.hpp"
#include "dpf/net/stream_array.hpp"
#include "dpf/net/stream_mesh.hpp"
#include "dpf/protocol_factory.hpp"
namespace
{
struct trio_edge_ping
{
std::uint32_t from = 0;
std::uint32_t to = 0;
std::uint64_t nonce = 0;
};
struct trio_party_streams
{
dpf::net::memory_stream_array & peer;
dpf::net::memory_stream_array & rss_next;
dpf::net::memory_stream_array & dealer;
};
template <typename T>
void exchange_pod(dpf::net::memory_stream_array & link, std::size_t stream,
const T & mine, T & theirs)
{
static_assert(std::is_trivially_copyable_v<T>, "pod");
link.write(stream, &mine, sizeof(T));
link.flush(stream);
link.read(stream, &theirs, sizeof(T));
}
void ping_edge(trio_party_streams s, unsigned me, unsigned peer_id,
std::uint64_t nonce, std::size_t stream_idx)
{
trio_edge_ping mine{me, peer_id, nonce};
trio_edge_ping theirs{};
exchange_pod(s.peer, stream_idx, mine, theirs);
if (theirs.from != peer_id || theirs.to != me)
throw std::runtime_error("stream_dpf3_smoke: peer edge");
}
} // namespace
int main()
{
using Input = std::uint8_t;
const Input alpha = 42;
const dpf::fp61 beta{7};
auto [k1, k2, k3] = dpf::make_dpf3(alpha, beta);
const dpf::fp61 y1 = dpf::eval_point(k1, alpha);
const dpf::fp61 y2 = dpf::eval_point(k2, alpha);
const dpf::fp61 y3 = dpf::eval_point(k3, alpha);
const dpf::fp61 opened = dpf::reconstruct(
dpf::as_share(k1, y1), dpf::as_share(k2, y2), dpf::as_share(k3, y3));
if (opened != beta)
{
std::cerr << "dpf3 local eval\n";
return 1;
}
// Dealer delivers a keyed marker on stream 0 to each evaluator.
auto dealer0 = dpf::net::make_memory_stream_pair(1);
auto dealer1 = dpf::net::make_memory_stream_pair(1);
struct key_delivery
{
std::uint32_t party = 0;
std::uint64_t beta_raw = 0;
};
key_delivery m0{0, beta.raw()}, m1{1, beta.raw()};
dealer0.first.write(0, &m0, sizeof(m0));
dealer0.first.flush(0);
dealer1.first.write(0, &m1, sizeof(m1));
dealer1.first.flush(0);
key_delivery got0{}, got1{};
dealer0.second.read(0, &got0, sizeof(got0));
dealer1.second.read(0, &got1, sizeof(got1));
if (got0.beta_raw != beta.raw() || got1.beta_raw != beta.raw())
{
std::cerr << "dealer stream delivery\n";
return 1;
}
// Trio-shaped edges on a 3-party clique (p2 = dealer): peer + rss_next + dealer.
constexpr std::size_t k_peer = 0;
constexpr std::size_t k_rss = 1;
auto clique = dpf::net::make_memory_stream_clique(3, 2);
std::exception_ptr err;
std::thread t0([&] {
try
{
trio_party_streams s{clique.end(0, 1), clique.end(0, 1), clique.end(0, 2)};
ping_edge(s, 0, 1, 11, k_peer);
const trio_edge_ping rss_out{0, 1, 99};
s.rss_next.write(k_rss, &rss_out, sizeof(rss_out));
s.rss_next.flush(k_rss);
}
catch (...)
{
err = std::current_exception();
}
});
std::thread t1([&] {
try
{
trio_party_streams s{clique.end(1, 0), clique.end(1, 0), clique.end(1, 2)};
ping_edge(s, 1, 0, 22, k_peer);
trio_edge_ping rss_in{};
s.rss_next.read(k_rss, &rss_in, sizeof(rss_in));
if (rss_in.from != 0u)
throw std::runtime_error("stream_dpf3_smoke: rss_next");
const trio_edge_ping dealer_out{1, 2, 88};
s.dealer.write(k_rss, &dealer_out, sizeof(dealer_out));
s.dealer.flush(k_rss);
}
catch (...)
{
err = std::current_exception();
}
});
t0.join();
t1.join();
if (err)
std::rethrow_exception(err);
trio_edge_ping dealer_in{};
clique.end(2, 1).read(k_rss, &dealer_in, sizeof(dealer_in));
if (dealer_in.from != 1u || dealer_in.to != 2u)
{
std::cerr << "dealer edge\n";
return 1;
}
std::cout << "stream_trio_ok\n";
return 0;
}

View file

@ -0,0 +1,64 @@
#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>
#include "dpf/launch.hpp"
#include "dpf/run_log.hpp"
// Two-party open. Every run choice is a flag:
//
// c++ -std=c++17 -march=native -pthread -I include -I thirdparty \
// examples/protocol/two_party_exchange.cpp -lsctp
// ./a.out # in-process async memory
// ./a.out --transport=mux --lanes=1 # TCP mux on localhost
// ./a.out --transport=parallel --window=65536
//
// For separate processes, see party_node.cpp.
int main(int argc, char ** argv)
{
try
{
auto cfg = dpf::app::run_config::from_env();
for (const auto & extra : cfg.apply_args(argc, argv))
throw std::invalid_argument("unknown argument " + extra);
dpf::app::start_logging(cfg);
dpf::protocol::composer c0(0);
dpf::protocol::composer c1(1);
auto x0 = c0.input(dpf::protocol::domain::a, 8);
auto x1 = c1.input(dpf::protocol::domain::a, 8);
auto o0 = c0.exchange(x0);
(void)c1.exchange(x1);
auto p0 = c0.schedule();
auto p1 = c1.schedule();
// One 8-byte input per instance; every instance opens to 42.
dpf::app::party_values v0(p0.nodes().size()), v1(p1.nodes().size());
v0[x0.id].assign(8 * cfg.instances, 0);
v1[x1.id].assign(8 * cfg.instances, 0);
for (std::size_t i = 0; i < cfg.instances; ++i)
{
const std::uint64_t a = 11 + i, b = 31 - i;
std::memcpy(v0[x0.id].data() + 8 * i, &a, 8);
std::memcpy(v1[x1.id].data() + 8 * i, &b, 8);
}
const auto r = dpf::run_two_party(p0, p1, v0, v1, {}, cfg);
bool all = true;
std::uint64_t open = 0;
for (std::size_t i = 0; i < cfg.instances; ++i)
{
std::memcpy(&open, v0[o0.id].data() + 8 * i, 8);
all = all && open == 42;
}
std::cout << "two_party_exchange " << (all ? 42 : open) << " "
<< cfg.summary() << " wire_out=" << r.wire[0].bytes_out << "\n";
return all ? 0 : 1;
}
catch (const std::exception & e)
{
std::cerr << "two_party_exchange: " << e.what() << "\n";
return 1;
}
}