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
154
examples/protocol/client_shares.cpp
Normal file
154
examples/protocol/client_shares.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
285
examples/protocol/compose_schedule.cpp
Normal file
285
examples/protocol/compose_schedule.cpp
Normal 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;
|
||||
}
|
||||
86
examples/protocol/party_node.cpp
Normal file
86
examples/protocol/party_node.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
99
examples/protocol/share_runtime.cpp
Normal file
99
examples/protocol/share_runtime.cpp
Normal 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;
|
||||
}
|
||||
83
examples/protocol/stream_app_smoke.cpp
Normal file
83
examples/protocol/stream_app_smoke.cpp
Normal 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;
|
||||
}
|
||||
145
examples/protocol/stream_dpf3_smoke.cpp
Normal file
145
examples/protocol/stream_dpf3_smoke.cpp
Normal 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;
|
||||
}
|
||||
64
examples/protocol/two_party_exchange.cpp
Normal file
64
examples/protocol/two_party_exchange.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue