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,113 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// BitMore, the DPF query only (Hafiz and Henry, PoPETs 2019 §5.2).
// ell = 2^L servers. The client samples L independent 1-bit DPFs at the
// secret row. Server j, whose label bits are j_{L-1} ... j_0, receives
// key j_e of DPF e and expands it. Concatenating those bits per row is
// the query string the information-theoretic response then consumes.
//
// `dpf::pack_bit_columns(keys...)` runs the full-domain bit walk once per
// key and writes lane e = key e into one integer per row, so the server
// loop reads `symbol[row]` instead of unpacking one int per bit.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/bitmore.cpp
namespace
{
constexpr int bits_l = 2;
constexpr int nservers = 1 << bits_l;
constexpr std::size_t nrows = 256;
} // namespace
int main()
{
constexpr std::uint8_t alpha = 42;
// L independent 1-bit DPFs at the secret row; keep both parties' keys.
auto [e0k0, e0k1] = dpf::make_dpf(alpha, dpf::bit::one);
auto [e1k0, e1k1] = dpf::make_dpf(alpha, dpf::bit::one);
// Server j reads key (j>>e)&1 of DPF e. Pack those L bits per row into
// one digit with pack_bit_columns; lane e is DPF e.
std::array<std::vector<std::uint64_t>, nservers> symbol{};
symbol[0] = dpf::pack_bit_columns(e0k0, e1k0); // parties (0,0)
symbol[1] = dpf::pack_bit_columns(e0k1, e1k0); // parties (1,0)
symbol[2] = dpf::pack_bit_columns(e0k0, e1k1); // parties (0,1)
symbol[3] = dpf::pack_bit_columns(e0k1, e1k1); // parties (1,1)
std::array<std::uint64_t, nservers> at_alpha{};
for (std::size_t row = 0; row < nrows; ++row)
{
if (row == alpha)
{
for (int j = 0; j < nservers; ++j)
at_alpha[static_cast<std::size_t>(j)] =
symbol[static_cast<std::size_t>(j)][row];
continue;
}
for (int j = 1; j < nservers; ++j)
{
if (symbol[static_cast<std::size_t>(j)][row]
!= symbol[0][row])
{
std::cerr << "bitmore off-row\n";
return 1;
}
}
}
// On the secret row the server digits are a translate of the server
// ids: symbol(j) = symbol(0) XOR j. That is a permutation of 0 .. ell-1.
for (int j = 0; j < nservers; ++j)
{
const std::uint64_t expect = at_alpha[0] ^ static_cast<std::uint64_t>(j);
if (at_alpha[static_cast<std::size_t>(j)] != expect)
{
std::cerr << "bitmore secret row\n";
return 1;
}
}
// L = 1 is the 2-server member of the same family: XOR the rows each
// party's bit selects, read off the packed digit's low bit.
std::vector<std::uint64_t> records(nrows);
records[alpha] = 99;
records[7] = 3;
auto [q0, q1] = dpf::make_dpf(alpha, dpf::bit::one);
const auto s0 = dpf::pack_bit_columns(q0);
const auto s1 = dpf::pack_bit_columns(q1);
std::uint64_t a0 = 0;
std::uint64_t a1 = 0;
for (std::size_t i = 0; i < nrows; ++i)
{
if (s0[i] & 1u)
a0 ^= records[i];
if (s1[i] & 1u)
a1 ^= records[i];
}
if ((a0 ^ a1) != records[alpha])
{
std::cerr << "bitmore two-server\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("bitmore",
dpf::protocol::bitmore_fan_plan(0, 4, 6), 6))
return rc;
}
std::cout << (a0 ^ a1) << "\n";
return 0;
}

View file

@ -0,0 +1,79 @@
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <type_traits>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// 3-party Duoram, the DPF steps only (Vadapalli, Henry, Goldberg, USENIX
// Security 2023). Online update: expand with `leaf_later`, rotate value and
// control together, then `apply_leaf_correction` once F is known.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/duoram3.cpp
namespace
{
constexpr std::size_t n = 256;
using output_t = simde_uint128;
template <typename Key>
output_t leaf_cw_of(const Key & key)
{
using exterior = typename Key::exterior_node;
return dpf::extract_leaf<exterior, output_t>(key.template leaf<0>(), 0);
}
} // namespace
int main()
{
constexpr std::uint8_t r = 10;
constexpr std::uint8_t i_star = 42;
constexpr unsigned shift = static_cast<unsigned>(i_star - r);
const output_t message = output_t{7};
std::vector<output_t> memory(n);
memory[i_star] = output_t{100};
memory[r] = output_t{5};
auto [u0, u1] = dpf::make_dpf(r, output_t{1});
const auto read = dpf::reconstruct(
dpf::eval_full_inner_product(dpf::paired, u0, memory, dpf::rotate{shift}),
dpf::eval_full_inner_product(dpf::paired, u1, memory, dpf::rotate{shift}));
if (read != memory[i_star])
{
std::cerr << "duoram read\n";
return 1;
}
auto [w0, w1] = dpf::make_dpf(r, message);
const output_t F = leaf_cw_of(w0);
std::vector<output_t> d0 = memory;
std::vector<output_t> d1(n);
std::vector<std::uint8_t> t0(n), t1(n);
dpf::eval_full_add_into(d0, t0, w0, dpf::leaf_later{}, dpf::rotate{shift});
dpf::eval_full_add_into(d1, t1, w1, dpf::leaf_later{}, dpf::rotate{shift});
dpf::apply_leaf_correction(d0, t0, F);
dpf::apply_leaf_correction(d1, t1, F);
if ((d0[i_star] - d1[i_star]) != memory[i_star] + message
|| (d0[r] - d1[r]) != memory[r])
{
std::cerr << "duoram update\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("duoram3",
dpf::protocol::duoram_update_plan(0), 8))
return rc;
}
std::cout << static_cast<unsigned long long>(d0[i_star] - d1[i_star]) << "\n";
return 0;
}

View file

@ -0,0 +1,222 @@
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
#include "dpf/bench_cells.hpp"
#include "dpf/experiment.hpp"
#include "dpf/party_runner.hpp"
#include "dpf/run_log.hpp"
// Every cell is a compose plan driven by `run_parties` on one `run_config`:
// the party mesh over in-process async memory, unix sockets, TCP mux, parallel
// TCP, or SCTP. `memory` and `stream` are the older paired sinks; this harness
// uses the mesh, so those two names run as async. Every `run_config` key is a
// flag (`--transport=mux --lanes=4 --window=262144 --warmup=2 --trials=9`) or
// the matching `DPF_*` variable; flags win. Lanes default to 1 here. Each cell
// runs `warmup` untimed and `trials` timed repetitions and records every
// party's trial times, party 0's and the slowest party's medians, the
// configuration, and party 0's wire counters in the CSVs. Every repetition
// draws from streams derived from the cell's master, so replaying the master
// replays the cell.
//
// c++ -std=c++17 -march=native -pthread -I include -I thirdparty \
// examples/applications/experiment_bench.cpp -lsctp
// DPF_EXPERIMENT_DIR=/tmp/libdpf_bench ./a.out --transport=mux --trials=5
//
// The first block is the DPF plans. The second block is the work that is not
// a key: word gadgets, stacked branches, short tables, and a hidden reorder
// of a column the parties already share.
namespace
{
dpf::app::run_config mesh_config(int argc, char ** argv, std::string & replaced)
{
dpf::app::run_config cfg;
cfg.n_lanes = 1;
cfg.merge_env();
const auto rest = cfg.apply_args(argc, argv);
if (!rest.empty())
throw std::invalid_argument("unexpected argument '" + rest.front()
+ "' (flags are --key=value)");
if (cfg.kind == dpf::net::transport::memory_sink
|| cfg.kind == dpf::net::transport::memory_stream)
{
std::cout << "transport "
<< dpf::net::transport_name(cfg.kind)
<< " is a paired sink; the battery uses the party mesh (async)\n";
replaced = dpf::net::transport_name(cfg.kind);
cfg.kind = dpf::net::transport::async_memory;
}
return cfg;
}
int measure_plans(const char * name, std::vector<dpf::protocol::plan> plans,
std::uint64_t run_id, const std::string & dir, const dpf::app::run_config & cfg,
dpf::protocol::cell_fn cell)
{
if (plans.empty() || plans[0].rounds() == 0)
{
std::cerr << name << " has no exchange rounds\n";
return 1;
}
dpf::experiment ex(name, "p0");
ex.set_run_id(run_id);
ex.ingest_plan(plans[0]);
ex.set_config(cfg.describe());
dpf::app::parties_result result;
const std::size_t total = cfg.warmup + std::max<std::size_t>(1, cfg.trials);
try
{
for (std::size_t t = 0; t < total; ++t)
{
std::vector<dpf::app::party_values> values(plans.size());
const bool last = t + 1 == total;
result = dpf::app::run_parties(plans, values, {}, cfg,
last ? &ex : nullptr, cell, &ex);
if (t >= cfg.warmup)
ex.add_trial(result.party0_wall_ns, result.party_wall_ns);
}
}
catch (const std::exception & err)
{
std::cerr << name << " flow: " << err.what() << "\n";
return 1;
}
const auto wire = result.wire.empty() ? dpf::net::stream_stats{} : result.wire[0];
dpf::experiment::wire_counts w;
w.bytes_out = wire.bytes_out;
w.bytes_in = wire.bytes_in;
w.payload_out = wire.payload_out;
w.payload_in = wire.payload_in;
w.frames_out = wire.frames_out;
w.frames_in = wire.frames_in;
w.write_calls = wire.write_calls;
ex.set_wire(w);
ex.write_csv(dir);
std::cout << name << " parties=" << plans.size()
<< " run_id=" << run_id
<< " rounds=" << ex.interactive_rounds()
<< " bytes=" << ex.plan_bytes_out()
<< " wire_out=" << wire.bytes_out
<< " wire_in=" << wire.bytes_in
<< " payload_out=" << wire.payload_out
<< " median_ns=" << ex.median_trial_ns()
<< " slowest_median_ns=" << ex.slowest_median_ns()
<< " trials=" << ex.trials().size()
<< " prg_evals=" << ex.prg_evals()
<< " seed=" << ex.seed_hex() << "\n";
return 0;
}
} // namespace
int main(int argc, char ** argv)
{
const char * env = std::getenv("DPF_EXPERIMENT_DIR");
const std::string dir = (env && env[0] != '\0') ? env
: "/tmp/libdpf_experiment_bench";
dpf::app::run_config cfg;
std::string replaced;
try
{
cfg = mesh_config(argc, argv, replaced);
dpf::app::start_logging(cfg);
}
catch (const std::exception & err)
{
std::cerr << "experiment_bench: " << err.what() << "\n";
return 2;
}
if (!replaced.empty())
DPF_LOG(warning, "config.override").kv("key", "transport")
.kv("requested", replaced).kv("used", "async")
.kv("detail", "paired sinks cannot carry the party mesh");
std::cout << cfg.summary() << "\n";
struct named
{
const char * name;
int parties;
dpf::protocol::plan (*make)(std::size_t party);
};
const named dpf_plans[] = {
{"keyword_pir", 2, [](std::size_t p) {
return dpf::protocol::keyword_pir_compose_plan(p, 8);
}},
{"express", 2, [](std::size_t p) {
return dpf::protocol::mailbox_write_fused_plan(p);
}},
{"subleq", 2, [](std::size_t p) {
return dpf::protocol::subleq_instruction_plan(p);
}},
{"pika", 2, [](std::size_t p) {
return dpf::protocol::pika_lookup_plan(p);
}},
{"duoram3", 2, [](std::size_t p) {
return dpf::protocol::duoram_update_plan(p);
}},
{"poplar", 2, [](std::size_t p) {
return dpf::protocol::poplar_prefix_plan(p);
}},
{"poplar_fan4", 2, [](std::size_t p) {
return dpf::protocol::poplar_prefix_fan_plan(p, 4);
}},
{"ledger23", 2, [](std::size_t p) {
return dpf::protocol::ledger23_append_plan(p);
}},
{"bitmore", 2, [](std::size_t p) {
return dpf::protocol::bitmore_fan_plan(p);
}},
{"floram", 2, [](std::size_t p) {
return dpf::protocol::floram_ds_plan(p);
}},
{"fss_point", 2, [](std::size_t p) {
return dpf::protocol::fss_point_plan(p);
}},
{"fss_cmp", 2, [](std::size_t p) {
return dpf::protocol::fss_cmp_plan(p);
}},
{"range_count", 2, [](std::size_t p) {
return dpf::protocol::range_count_plan(p);
}},
{"psi_cuckoo", 2, [](std::size_t p) {
return dpf::protocol::psi_cuckoo_plan(p, {0, 2, 5, 7});
}},
{"idpf_agg", 2, [](std::size_t p) {
return dpf::protocol::idpf_agg_plan(p, 8);
}},
};
std::uint64_t run_id = 0;
for (const auto & row : dpf_plans)
{
std::vector<dpf::protocol::plan> plans;
plans.reserve(static_cast<std::size_t>(row.parties));
for (int p = 0; p < row.parties; ++p)
plans.push_back(row.make(static_cast<std::size_t>(p)));
if (int rc = measure_plans(row.name, std::move(plans), run_id++, dir, cfg,
nullptr))
return rc;
}
for (const auto & cell : dpf::bench::battery())
{
std::vector<dpf::protocol::plan> plans;
plans.reserve(static_cast<std::size_t>(cell.parties));
for (int p = 0; p < cell.parties; ++p)
plans.push_back(dpf::bench::plan_for(static_cast<std::size_t>(p), cell.id));
if (int rc = measure_plans(cell.name, std::move(plans), run_id++, dir, cfg,
&dpf::bench::run_cell))
return rc;
}
std::cout << "wrote CSVs under " << dir << "\n";
return 0;
}

View file

@ -0,0 +1,84 @@
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Express, the mailbox write (Eskandarian, Corrigan-Gibbs, Zaharia, Boneh,
// USENIX Security 2021 §3.1). Two servers hold XOR shares of every mailbox
// row. The client sends one `blob` DPF key each. Each server adds its
// expansion into its share and folds the one-hot audit in the same walk.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/express.cpp
namespace
{
constexpr std::size_t nboxes = 256;
constexpr std::size_t row_bytes = 100;
using row_t = dpf::blob<row_bytes>;
} // namespace
int main()
{
constexpr std::uint8_t address = 9;
row_t message{};
for (std::size_t i = 0; i < row_bytes; ++i)
message.bytes[i] = static_cast<unsigned char>(i + 1);
auto [k0, k1] = dpf::make_dpf(address, message);
std::vector<row_t> box0(nboxes), box1(nboxes);
// One-pass caller fold: count how many non-zero shares each server sees.
std::size_t hot0 = 0, hot1 = 0;
dpf::eval_full_add_into(box0, k0, [&](std::size_t, const row_t & s) {
if (s != row_t{})
++hot0;
});
dpf::eval_full_add_into(box1, k1, [&](std::size_t, const row_t & s) {
if (s != row_t{})
++hot1;
});
(void)hot0;
(void)hot1;
if ((box0[address] ^ box1[address]) != message)
{
std::cerr << "express mailbox\n";
return 1;
}
if ((box0[0] ^ box1[0]) != row_t{})
{
std::cerr << "express neighbor\n";
return 1;
}
// fp61 one-hot audit on a parallel extractable key (same walk shape).
auto [a0, a1] = dpf::make_dpf(address, dpf::fp61{1}, dpf::extractable{});
std::vector<dpf::fp61> challenge(nboxes);
for (std::size_t i = 0; i < nboxes; ++i)
challenge[i] = dpf::fp61{static_cast<std::uint64_t>(i + 1)};
std::vector<dpf::fp61> audit0(nboxes), audit1(nboxes);
dpf::sketch_share s0{}, s1{};
dpf::eval_full_add_into(audit0, a0, dpf::sketch(s0, challenge));
dpf::eval_full_add_into(audit1, a1, dpf::sketch(s1, challenge));
if (!dpf::sketch_verify(s0, s1))
{
std::cerr << "express audit\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("express",
dpf::protocol::mailbox_write_fused_plan(0, 8), 8))
return rc;
}
std::cout << static_cast<unsigned>(message.bytes[0]) << "\n";
return 0;
}

View file

@ -0,0 +1,61 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Floram, the FSS read and write (Doerner and shelat, CCS 2017). Both
// parties see the memory. The address is secret-shared, so keygen is
// Doerner–Shelat rather than a dealer who knows the index. The read is
// the inner product of a unit key with that memory. The write adds a
// payload key, built from the same address shares, into subtractive
// copies of the array.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/floram.cpp
int main()
{
constexpr std::size_t n = 256;
constexpr std::uint8_t address = 42;
constexpr std::uint8_t a0 = 0x15;
constexpr std::uint8_t a1 = static_cast<std::uint8_t>(address ^ a0);
constexpr std::uint64_t message = 9;
std::vector<std::uint64_t> memory(n);
memory[address] = 100;
memory[7] = 3;
auto [r0, r1] = dpf::make_dpf_doerner_shelat(a0, a1, std::uint64_t{1});
const auto word = dpf::reconstruct(
dpf::eval_full_inner_product(dpf::paired, r0, memory),
dpf::eval_full_inner_product(dpf::paired, r1, memory));
if (word != memory[address])
{
std::cerr << "floram read\n";
return 1;
}
auto [w0, w1] = dpf::make_dpf_doerner_shelat(a0, a1, message);
std::vector<std::uint64_t> s0 = memory;
std::vector<std::uint64_t> s1(n);
dpf::eval_full_add_into(s0, w0);
dpf::eval_full_add_into(s1, w1);
if (s0[address] - s1[address] != memory[address] + message
|| s0[7] - s1[7] != memory[7])
{
std::cerr << "floram write\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("floram",
dpf::protocol::floram_ds_plan(0), 40))
return rc;
}
std::cout << word << "\n";
return 0;
}

View file

@ -0,0 +1,38 @@
#include <cstdint>
#include <iostream>
#include "dpf/online_session.hpp"
#include "dpf/prep_source.hpp"
// Hushmap KHM ADD: dealer tape + two online opens on async round sinks,
// then a real prep shipment (deal_views over async streams).
//
// c++ -std=c++17 -pthread -I include -I thirdparty \
// examples/applications/hushmap_add.cpp
int main()
{
constexpr std::size_t layers = 3;
try
{
dpf::session::drive_hushmap_add(layers);
dpf::prep::demand d;
d.ring_triples = static_cast<std::uint32_t>(layers);
const auto shipped = dpf::session::ship_prep(d);
std::uint8_t a0[8]{}, b0[8]{}, c0[8]{};
std::uint8_t a1[8]{}, b1[8]{}, c1[8]{};
auto c0p = shipped.party0;
auto c1p = shipped.party1;
c0p.take_ring(a0, b0, c0);
c1p.take_ring(a1, b1, c1);
std::cout << "hushmap_add layers=" << layers
<< " rounds=" << (layers + 2)
<< " prep_bytes=" << shipped.bytes0 << "\n";
}
catch (const std::exception & ex)
{
std::cerr << "hushmap_add: " << ex.what() << "\n";
return 1;
}
return 0;
}

View file

@ -0,0 +1,52 @@
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Max and k-th order statistic over secret uint16 values. Each value is
// one incremental DPF with a unit payload on every prefix length. Servers
// resume only the live prefixes with eval_until (ePrint 2024/1190).
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/idpf_agg.cpp
int main()
{
const std::vector<std::uint16_t> values{12, 80, 3, 80, 40};
using key0_t = decltype(dpf::make_dpf(std::uint16_t{0},
dpf::idpf_ones<16>()).first);
using key1_t = decltype(dpf::make_dpf(std::uint16_t{0},
dpf::idpf_ones<16>()).second);
std::vector<key0_t> k0;
std::vector<key1_t> k1;
for (auto v : values)
{
auto [a, b] = dpf::make_dpf(v, dpf::idpf_ones<16>());
k0.push_back(std::move(a));
k1.push_back(std::move(b));
}
const auto opened_max = dpf::idpf_agg_max(k0, k1);
const auto opened_k2 = dpf::idpf_agg_kth(k0, k1, 2);
auto sorted = values;
std::sort(sorted.begin(), sorted.end(), std::greater<>{});
if (opened_max != sorted[0] || opened_k2 != sorted[1])
{
std::cerr << "idpf_agg " << opened_max << " " << opened_k2 << "\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("idpf_agg",
dpf::protocol::idpf_agg_plan(0, 16), 16))
return rc;
}
std::cout << opened_max << "\n";
return 0;
}

View file

@ -0,0 +1,49 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Three-server index PIR with the information-theoretic DPF
// (ePrint 2023/028). The database is public and replicated. Each server
// dots its additive share with the table; the three dots sum to the record.
// make_dpf3 remains the computational (2,3) Shamir key.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/it_pir3.cpp
int main()
{
constexpr std::size_t n = 256;
constexpr std::uint8_t index = 42;
std::vector<std::uint64_t> database(n);
for (std::size_t i = 0; i < n; ++i)
database[i] = i * i + 1;
auto [k0, k1, k2] = dpf::make_it_dpf3(index, 1);
const auto s0 = dpf::eval_it_dpf3_inner_product(k0, database);
const auto s1 = dpf::eval_it_dpf3_inner_product(k1, database);
const auto s2 = dpf::eval_it_dpf3_inner_product(k2, database);
const auto opened = s0 + s1 + s2;
if (opened != database[index])
{
std::cerr << "it_pir3\n";
return 1;
}
{
constexpr std::size_t query_bytes =
dpf::it_dpf3_key::domain_size * sizeof(std::uint64_t);
if (int rc = dpf::app::run_measured("it_pir3",
dpf::protocol::n_server_pir_plan(0, 3, query_bytes,
sizeof(std::uint64_t)),
2))
return rc;
}
std::cout << opened << "\n";
return 0;
}

View file

@ -0,0 +1,60 @@
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Two-server keyword PIR, the DPF step (Gilboa and Ishai, EUROCRYPT 2014).
// `eval_sequence_xor` folds the selected records into one XOR accumulator
// without materializing a bit vector.
//
// c++ -std=c++17 -march=native -pthread -I include -I thirdparty \
// examples/applications/keyword_pir.cpp
// DPF_EXPERIMENT_DIR=/tmp/kw ./a.out # optional CSV dump
namespace
{
using keyword = dpf::keyword<3, dpf::alphabets::lowercase_alpha>;
} // namespace
int main()
{
const std::vector<keyword> dict{keyword{"bat"}, keyword{"cat"},
keyword{"dog"}, keyword{"pig"}};
const std::vector<int> records{56, 12, 34, 78};
auto [k0, k1] = dpf::make_dpf(keyword{"bat"}, dpf::bit::one);
const int selected = dpf::eval_sequence_xor(k0, dict.begin(), dict.end(),
records)
^ dpf::eval_sequence_xor(k1, dict.begin(), dict.end(), records);
if (selected != 56)
{
std::cerr << "keyword hit\n";
return 1;
}
auto [m0, m1] = dpf::make_dpf(keyword{"rat"}, dpf::bit::one);
const int missing = dpf::eval_sequence_xor(m0, dict.begin(), dict.end(),
records)
^ dpf::eval_sequence_xor(m1, dict.begin(), dict.end(), records);
if (missing != 0)
{
std::cerr << "keyword miss\n";
return 1;
}
{
constexpr std::size_t depth = dpf::utils::bitlength_of<keyword>::value;
if (int rc = dpf::app::run_measured("keyword_pir",
dpf::protocol::keyword_pir_compose_plan(0, depth), 2))
return rc;
}
std::cout << selected << "\n";
return 0;
}

View file

@ -0,0 +1,85 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// A (2,3) ledger, the DPF step. Three servers replicate a ledger as
// Shamir-style (2-of-3) shares (this group's dpf3 / VDPF+ construction). Each
// append writes one point (slot -> amount) into all three shares; any two
// servers reconstruct a slot. A verifiable proof (verify_dpf3) rejects an
// append that is not a single well-formed point before it is applied.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/ledger23.cpp
namespace
{
using dpf::fp61;
constexpr std::size_t nslots = 256; // uint8 slot domain
fp61 open2(fp61 a, fp61 b) // any two of three shares reconstruct
{
return dpf::shamir3::reconstruct(dpf::shamir3::share{1, a},
dpf::shamir3::share{2, b});
}
} // namespace
int main()
{
std::vector<fp61> l1(nslots), l2(nslots), l3(nslots);
// Each append is a verified (2,3) point key.
const std::pair<std::uint8_t, std::uint64_t> entries[] = {
{5, 100}, {40, 25}, {5, 7}};
for (auto [slot, amount] : entries)
{
auto [k1, k2, k3] = dpf::make_dpf3(slot, fp61{amount}, dpf::verifiable{});
if (!dpf::verify_dpf3(dpf::prove_dpf3(k1, slot),
dpf::prove_dpf3(k2, slot), dpf::prove_dpf3(k3, slot)))
{
std::cerr << "ledger append audit\n";
return 1;
}
// Fold the (2,3) expansion into each party's ledger shares.
dpf::eval_full_add_into(l1, k1);
dpf::eval_full_add_into(l2, k2);
dpf::eval_full_add_into(l3, k3);
}
// Slot 5 got two credits (100 + 7); slot 40 got 25; the rest are 0.
if (open2(l1[5], l2[5]) != fp61{107}
|| open2(l1[40], l2[40]) != fp61{25}
|| open2(l1[0], l2[0]).raw() != 0)
{
std::cerr << "ledger balance\n";
return 1;
}
// A proof that does not come from the same append is rejected: mixing one
// party's token from an independent key triple fails verification.
auto [b1, b2, b3] = dpf::make_dpf3(std::uint8_t{9}, fp61{1}, dpf::verifiable{});
auto [c1, c2, c3] = dpf::make_dpf3(std::uint8_t{9}, fp61{1}, dpf::verifiable{});
if (dpf::verify_dpf3(dpf::prove_dpf3(b1, std::uint8_t{9}),
dpf::prove_dpf3(b2, std::uint8_t{9}),
dpf::prove_dpf3(c3, std::uint8_t{9})))
{
std::cerr << "ledger accepted a bad append\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("ledger23",
dpf::protocol::ledger23_append_plan(0), 2))
return rc;
}
std::cout << open2(l1[5], l2[5]).raw() << "\n";
return 0;
}

View file

@ -0,0 +1,98 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
#include "grotto/carry.hpp"
#include "grotto/carry_plan.hpp"
// LLAMA, the FSS gates that touch a DPF (Gupta, Kumaraswamy, Chandran,
// and Gupta, ePrint 2022/793). Width gates call `grotto::sign_extend` and
// `grotto::truncate_reduce`. A degree-0 spline is one interval key per piece.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/llama.cpp
int main()
{
constexpr std::uint8_t r = 20;
constexpr std::uint8_t knot = 16;
const std::uint8_t threshold = static_cast<std::uint8_t>(knot + r);
auto [c0, c1] = dpf::make_dpf(threshold, dpf::gt(std::uint64_t{1}));
const auto above = dpf::reconstruct(
dpf::eval_point(dpf::cmp, c0, static_cast<std::uint8_t>(30 + r)),
dpf::eval_point(dpf::cmp, c1, static_cast<std::uint8_t>(30 + r)));
const auto below = dpf::reconstruct(
dpf::eval_point(dpf::cmp, c0, static_cast<std::uint8_t>(4 + r)),
dpf::eval_point(dpf::cmp, c1, static_cast<std::uint8_t>(4 + r)));
if (above != 1 || below != 0)
{
std::cerr << "llama comparison\n";
return 1;
}
auto [lo0, lo1] = dpf::make_dpf(r, dpf::ic(std::uint8_t{0}, std::uint8_t{15},
std::uint64_t{2}));
auto [hi0, hi1] = dpf::make_dpf(r, dpf::ic(std::uint8_t{16}, std::uint8_t{31},
std::uint64_t{5}));
auto piece = [&](std::uint8_t x) {
const std::uint8_t x_hat = static_cast<std::uint8_t>(x + r);
const auto lo = dpf::reconstruct(
dpf::eval_point(dpf::ic, lo0, x_hat),
dpf::eval_point(dpf::ic, lo1, x_hat));
const auto hi = dpf::reconstruct(
dpf::eval_point(dpf::ic, hi0, x_hat),
dpf::eval_point(dpf::ic, hi1, x_hat));
return lo + hi;
};
if (piece(4) != 2 || piece(20) != 5 || piece(40) != 0)
{
std::cerr << "llama spline\n";
return 1;
}
// Truncate-reduce: drop 3 low bits of an 8-bit opening.
{
auto keys = grotto::make_truncate_reduce_keys(8, 3);
const std::uint64_t x0 = 0x05, x1 = 0x03;
const std::uint64_t opened = (x0 + x1 + keys.rin) & 0xffu;
const auto y0 = grotto::truncate_reduce(keys, 0, opened);
const auto y1 = grotto::truncate_reduce(keys, 1, opened);
const auto got = (y0.value + y1.value) & 0x1fu;
const auto want = grotto::eval_carry_clear(keys.recipe, x0, x1);
if (got != want)
{
std::cerr << "llama truncate_reduce\n";
return 1;
}
}
// Sign-extend: 8 → 16 bits.
{
auto keys = grotto::make_sign_extend_keys(8, 16);
const std::uint64_t x0 = 0x80, x1 = 0;
const std::uint64_t opened = (x0 + x1 + keys.rin) & 0xffu;
const std::uint64_t msb_high = 1; // 0x80 is negative
const auto y0 = grotto::sign_extend(keys, 0, opened, msb_high);
const auto y1 = grotto::sign_extend(keys, 1, opened, msb_high);
const auto got = (y0.value + y1.value) & 0xffffu;
const auto want = grotto::carry_extend_clear(x0, x1, 8, 16);
if (got != want)
{
std::cerr << "llama sign_extend\n";
return 1;
}
}
{
if (int rc = dpf::app::run_measured("llama",
dpf::protocol::range_count_plan(0), 8))
return rc;
}
std::cout << above << " " << piece(20) << "\n";
return 0;
}

View file

@ -0,0 +1,94 @@
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Mastic, the DPF step (private weighted heavy-hitters / attribute-based
// metrics). Each client keys an incremental DPF whose payload is its weight
// instead of a plain 1. Servers sum the weighted prefix shares at each depth
// and keep the heavy prefixes. This is Poplar's prefix walk with a weight
// payload; VIDPF path-consistency is `verify_idpf_path` below.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/mastic.cpp
namespace
{
// Weighted counts of all 2^length prefixes, summed over the two clients.
template <typename Tag, typename A0, typename A1, typename Kb0, typename Kb1>
std::vector<std::uint64_t> weighted_level(Tag tag, std::size_t nprefix,
const A0 & a0, const A1 & a1, const Kb0 & b0, const Kb1 & b1)
{
auto [ba0, ia0] = dpf::eval_prefixes(tag, a0);
auto [ba1, ia1] = dpf::eval_prefixes(tag, a1);
auto [bb0, ib0] = dpf::eval_prefixes(tag, b0);
auto [bb1, ib1] = dpf::eval_prefixes(tag, b1);
std::vector<std::uint64_t> w(nprefix);
for (std::size_t p = 0; p < nprefix; ++p)
w[p] = dpf::reconstruct(ba0[p], ba1[p])
+ dpf::reconstruct(bb0[p], bb1[p]);
return w;
}
std::vector<dpf::fp61> path_challenges(std::size_t n)
{
std::vector<dpf::fp61> rs(n);
for (auto & r : rs)
r = dpf::uniform_sample<dpf::fp61>();
return rs;
}
} // namespace
int main()
{
// Two clients report strings 0xA0 and 0xB0 (both begin "101"), with
// weights 5 and 3. A heavy-hitter threshold of 6 should keep prefix 101.
auto [a0, a1] = dpf::make_dpf(std::uint8_t{0xA0},
dpf::idpf(std::uint64_t{5}, std::uint64_t{5}, std::uint64_t{5}));
auto [b0, b1] = dpf::make_dpf(std::uint8_t{0xB0},
dpf::idpf(std::uint64_t{3}, std::uint64_t{3}, std::uint64_t{3}));
// One-time VIDPF path check per client (weight-1 / parent consistency).
const auto rs = path_challenges(dpf::path_sketch_challenge_count(3));
if (!dpf::verify_idpf_path<3>(a0, a1, rs)
|| !dpf::verify_idpf_path<3>(b0, b1, rs))
{
std::cerr << "mastic path sketch\n";
return 1;
}
// Length 1: prefix "1" carries the full weight 8; "0" carries 0.
const auto lvl1 = weighted_level(dpf::out<0, 1>, 2, a0, a1, b0, b1);
if (lvl1[1] != 8 || lvl1[0] != 0)
{
std::cerr << "mastic level1\n";
return 1;
}
// Length 3: prefix 101 (=5) is the heavy hitter with weight 8.
const auto lvl3 = weighted_level(dpf::out<2, 3>, 8, a0, a1, b0, b1);
constexpr std::uint64_t threshold = 6;
std::size_t heavy = 0, nheavy = 0;
for (std::size_t p = 0; p < lvl3.size(); ++p)
if (lvl3[p] >= threshold) { heavy = p; ++nheavy; }
if (nheavy != 1 || heavy != 0b101 || lvl3[0b101] != 8)
{
std::cerr << "mastic heavy\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("mastic",
dpf::protocol::poplar_prefix_plan(0), 8))
return rc;
}
std::cout << lvl3[0b101] << "\n";
return 0;
}

View file

@ -0,0 +1,72 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Pika, the lookup (Wagh, PoPETs 2022, Fig. 1). Party P2 is the dealer.
// P2 keys a unit DPF at a fresh index r and shares r. P0 and P1 open
// x = r - a, and take the inner product of the DPF with the table rotated
// by x. A word payload of 1 reconstructs to +1. A 1-bit payload lifts to
// +1 or -1; the dealer reads that sign off Gen's final control bit.
//
// The rotation is folded into the walk with `dpf::rotate{s}` (no rotated
// copy of the table), and the sign is recorded at keygen with
// `dpf::unit_sign` (no evaluator-side eval_point).
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/pika.cpp
int main()
{
constexpr std::size_t n = 256;
constexpr std::uint8_t r = 50;
constexpr std::uint8_t a0 = 10;
constexpr std::uint8_t a1 = 7;
constexpr std::uint8_t a = static_cast<std::uint8_t>(a0 + a1);
constexpr std::uint8_t x = static_cast<std::uint8_t>(r - a);
std::vector<std::uint64_t> table(n);
for (std::size_t i = 0; i < n; ++i)
table[i] = static_cast<std::uint64_t>(i) * i;
// Lookup: DPF at r dotted with the table read at (i - x) mod n, i.e.
// rotated by s = (n - x) mod n. The walk applies the offset; no copy.
auto [k0, k1] = dpf::make_dpf(r, std::uint64_t{1});
const std::size_t s = (n - static_cast<std::size_t>(x)) % n;
const auto value = dpf::reconstruct(
dpf::eval_full_inner_product(dpf::paired, k0, table, dpf::rotate{s}),
dpf::eval_full_inner_product(dpf::paired, k1, table, dpf::rotate{s}));
if (value != table[a])
{
std::cerr << "pika lookup\n";
return 1;
}
// The early-stop bit leaf. The dealer, who sees both keys, records a
// sign of +1 or -1 at r via `unit_sign`; the evaluators never open r.
int w0 = 0, w1 = 0;
auto [b0, b1] = dpf::make_dpf(r, dpf::bit::one, dpf::unit_sign{w0, w1});
const int sign = w0 - w1;
if (sign != 1 && sign != -1)
{
std::cerr << "pika sign\n";
return 1;
}
if (dpf::reconstruct(*dpf::eval_point(k0, r), *dpf::eval_point(k1, r)) != 1)
{
std::cerr << "pika unit\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("pika",
dpf::protocol::pika_lookup_plan(0, 8, 3), 5))
return rc;
}
std::cout << value << "\n";
return 0;
}

View file

@ -0,0 +1,55 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Three-server index PIR. The database is public and replicated. The
// secret index is one (2,3) point key: each server holds one share and
// dots it with the database. Any two of those dots reconstruct the
// record. This is the library's three-evaluator key (ePrint 2024/1658),
// the same sharing the ledger appends with.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/pir3.cpp
int main()
{
constexpr std::size_t n = 256;
constexpr std::uint8_t index = 42;
std::vector<dpf::fp61> database(n);
for (std::size_t i = 0; i < n; ++i)
database[i] = dpf::fp61{static_cast<std::uint64_t>(i * i + 1)};
auto [k1, k2, k3] = dpf::make_dpf3(index, dpf::fp61{1});
const auto s1 = dpf::eval_full_inner_product(k1, database);
const auto s2 = dpf::eval_full_inner_product(k2, database);
const auto s3 = dpf::eval_full_inner_product(k3, database);
const auto opened = dpf::shamir3::reconstruct(
dpf::as_share(k1, s1), dpf::as_share(k2, s2));
const auto opened_13 = dpf::shamir3::reconstruct(
dpf::as_share(k1, s1), dpf::as_share(k3, s3));
if (opened != database[index] || opened_13 != database[index])
{
std::cerr << "pir3\n";
return 1;
}
{
// Client uploads one (2,3) key (two point-key spines) to each server.
constexpr std::size_t depth = 8;
constexpr std::size_t query_bytes = 2 * (16 + depth * 16);
if (int rc = dpf::app::run_measured("pir3",
dpf::protocol::n_server_pir_plan(0, 3, query_bytes,
sizeof(dpf::fp61)),
2))
return rc;
}
std::cout << opened.raw() << "\n";
return 0;
}

View file

@ -0,0 +1,42 @@
#include <cstdint>
#include <iostream>
#include <memory>
#include <vector>
#include "dpf/online_session.hpp"
// PIRsona BitMore fetch on a split-io async star (L=1 → 2 servers).
//
// c++ -std=c++17 -pthread -I include -I thirdparty \
// examples/applications/pirsona_fetch.cpp
int main()
{
constexpr std::size_t L = 1;
constexpr std::size_t n = 1u << L;
auto seeds = std::make_shared<std::vector<std::vector<std::uint8_t>>>(n);
auto answers = std::make_shared<std::vector<std::vector<std::uint8_t>>>(n);
for (std::size_t i = 0; i < n; ++i)
{
(*seeds)[i].assign(16 * L, static_cast<std::uint8_t>(i + 1));
(*answers)[i].assign(8, static_cast<std::uint8_t>(0x40 + i));
}
try
{
auto client = dpf::protocol::pirsona_bitmore_fetch(L, 16, 8, seeds, answers);
const std::vector<std::size_t> slots{16u * L, 8u};
dpf::session::drive_async_star(n, slots, std::move(client),
[&](std::size_t i) {
return dpf::protocol::star_server_reply_rounds(16 * L, 8,
(*answers)[i]);
});
std::cout << "pirsona_fetch rounds=" << (2 * n) << " servers=" << n
<< "\n";
}
catch (const std::exception & ex)
{
std::cerr << "pirsona_fetch: " << ex.what() << "\n";
return 1;
}
return 0;
}

View file

@ -0,0 +1,97 @@
#include <array>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// PRAC, the DPF steps that are not Duoram (Sasy, Vadapalli, and Goldberg,
// ePrint 2023/1897). Binary search builds the path with `make_dpf` /
// `extend`, one prefix at a time. Heapify uses a wide `vec` leaf.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/prac.cpp
namespace
{
constexpr std::size_t n = 256;
constexpr std::size_t bitlen = 8;
using beta_t = std::uint64_t;
using input_t = std::uint8_t;
using wide3 = dpf::vec<beta_t, 3>;
template <typename Key0, typename Key1>
beta_t open_prefix(const Key0 & k0, const Key1 & k1, std::size_t level,
input_t x)
{
auto one = [&](auto lvl) {
return dpf::reconstruct(
*dpf::eval_point(dpf::out<lvl.value>, k0, x),
*dpf::eval_point(dpf::out<lvl.value>, k1, x));
};
switch (level)
{
case 0: return one(std::integral_constant<std::size_t, 0>{});
case 1: return one(std::integral_constant<std::size_t, 1>{});
default: throw std::logic_error("prac: level");
}
}
} // namespace
int main()
{
constexpr std::array<std::uint64_t, 8> memory{
1, 3, 5, 7, 9, 11, 13, 15};
constexpr std::uint64_t needle = 10;
// Search path bits (MSB first): 1, then 0 → prefix 0b10......
constexpr input_t path = 0x80;
auto [p0, p1] = dpf::make_dpf(path, dpf::at<1>(beta_t{1}));
auto [q0, q1] = dpf::extend(p0, p1, path, dpf::at<2>(beta_t{1}));
constexpr std::uint64_t stride2[] = {memory[1], memory[5]};
constexpr std::uint64_t stride4[] = {memory[0], memory[2], memory[4], memory[6]};
const auto sel1 = open_prefix(q0, q1, 0, path);
const auto sel2 = open_prefix(q0, q1, 1, path);
const auto at_5 = sel1 * stride2[1];
const auto at_4 = sel2 * stride4[2];
if (memory[3] != 7 || at_5 != 11 || at_4 != 9
|| sel1 != 1 || sel2 != 1)
{
std::cerr << "prac search " << at_5 << " " << at_4 << "\n";
return 1;
}
constexpr unsigned answer = 0b101;
if (answer != 5 || memory[answer] < needle)
{
std::cerr << "prac index\n";
return 1;
}
// Heapify: one wide leaf of three lanes at the answer index.
wide3 payload{};
payload.lanes = {1, 2, 3};
auto [h0, h1] = dpf::make_dpf(static_cast<input_t>(answer), payload);
const auto w0 = *dpf::eval_point(h0, static_cast<input_t>(answer));
const auto w1 = *dpf::eval_point(h1, static_cast<input_t>(answer));
const auto opened = dpf::reconstruct(w0, w1);
if (opened.lanes[0] != 1 || opened.lanes[1] != 2 || opened.lanes[2] != 3)
{
std::cerr << "prac heapify\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("prac",
dpf::protocol::poplar_prefix_plan(0), 8))
return rc;
}
std::cout << "prac ok\n";
return 0;
}

View file

@ -0,0 +1,88 @@
#include <array>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Prio's frequency count, with the one-hot vector replaced by a DPF, and
// the prefix walk Poplar uses for heavy hitters (Boneh, Boyle,
// Corrigan-Gibbs, Gilboa, Ishai). Classic Prio proves an encoding with a
// SNIP; this file is only the DPF-shaped encoding.
// field64 is libprio's Field64.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/prio.cpp
namespace
{
constexpr int nbins = 256;
} // namespace
int main()
{
// Histogram. Each client sends one unit DPF at a secret bin.
// Each server adds the expansion into its running share with
// `eval_full_add_into` (no separate expansion buffer). The opened bin
// is the count.
const std::array<std::uint8_t, 4> bins{3, 3, 7, 3};
std::vector<dpf::field64> h0(nbins);
std::vector<dpf::field64> h1(nbins);
for (std::uint8_t bin : bins)
{
auto [k0, k1] = dpf::make_dpf(bin, dpf::field64{1});
dpf::eval_full_add_into(h0, k0);
dpf::eval_full_add_into(h1, k1);
}
// Leaf shares are subtractive, so the opened bin is share0 - share1.
const dpf::field64 c3 = h0[3] - h1[3];
const dpf::field64 c7 = h0[7] - h1[7];
const dpf::field64 c0 = h0[0] - h1[0];
if (c3.raw() != 3 || c7.raw() != 1 || c0.raw() != 0)
{
std::cerr << "prio histogram\n";
return 1;
}
// Heavy-hitter prefixes. idpf plants a 1 on each prefix length.
// Length 1 is the high bit. 0xA0 and 0xB0 share 101; they split at bit 4.
constexpr std::uint8_t left = 0xA0;
constexpr std::uint8_t right = 0xB0;
auto [a0, a1] = dpf::make_dpf(left,
dpf::idpf(std::uint64_t{1}, std::uint64_t{1}, std::uint64_t{1}));
auto [b0, b1] = dpf::make_dpf(right,
dpf::idpf(std::uint64_t{1}, std::uint64_t{1}, std::uint64_t{1}));
auto one = [](auto tag, auto k0, auto k1, std::uint8_t node) {
return dpf::reconstruct(*dpf::eval_point(tag, k0, node),
*dpf::eval_point(tag, k1, node));
};
auto count = [&](auto tag, std::uint8_t node) {
return one(tag, a0, a1, node) + one(tag, b0, b1, node);
};
// out<0> is prefix length 1, out<1> length 2, out<2> length 3.
const auto high = count(dpf::out<0, 1>, std::uint8_t{0x80});
const auto low = count(dpf::out<0, 1>, std::uint8_t{0x00});
const auto shared = count(dpf::out<2, 3>, std::uint8_t{0xA0});
const auto split = count(dpf::out<2, 3>, std::uint8_t{0x80});
if (high != 2 || low != 0 || shared != 2 || split != 0)
{
std::cerr << "prio prefixes " << high << " " << low << " " << shared
<< " " << split << "\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("prio",
dpf::protocol::poplar_prefix_plan(0), 8))
return rc;
}
std::cout << c3.raw() << "\n";
return 0;
}

View file

@ -0,0 +1,75 @@
#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Private set intersection, the DPF step (Kolesnikov, Kumaresan, Rosulek,
// and Trieu, CCS 2016). The sender keeps a puncturable-PRF master. Each
// receiver element is a puncture; the servers evaluate the punctured key
// and test whether the tag sits in the sender's image. No full-domain table.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/psi.cpp
namespace
{
using domain_t = std::uint8_t;
bool blocks_eq(simde__m128i a, simde__m128i b)
{
return std::memcmp(&a, &b, sizeof(a)) == 0;
}
bool in_image(simde__m128i tag, const std::vector<simde__m128i> & image)
{
for (auto v : image)
if (blocks_eq(v, tag))
return true;
return false;
}
} // namespace
int main()
{
auto master = dpf::make_pprf_master<domain_t>();
const domain_t sender[] = {4, 10, 42};
const domain_t receiver[] = {42, 7};
std::vector<simde__m128i> image;
for (auto x : sender)
image.push_back(dpf::pprf_eval(master, x));
auto punctured_hit = dpf::puncture(master, receiver[0]);
auto punctured_miss = dpf::puncture(master, receiver[1]);
// Off-path points agree with the master; the programmed leaf at alpha
// matches the master leaf (sender who keeps the master set it).
const auto hit = dpf::pprf_eval(punctured_hit, receiver[0]);
const auto miss_off = dpf::pprf_eval(punctured_miss, domain_t{0});
const auto master_miss_off = dpf::pprf_eval(master, domain_t{0});
if (!blocks_eq(hit, dpf::pprf_eval(master, receiver[0]))
|| !in_image(hit, image)
|| in_image(dpf::pprf_eval(master, receiver[1]), image)
|| !blocks_eq(miss_off, master_miss_off))
{
std::cerr << "psi\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("psi",
dpf::protocol::psi_cuckoo_plan(0, {0, 1, 0}), 9))
return rc;
}
std::cout << "1\n";
return 0;
}

View file

@ -0,0 +1,61 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// A private range count. Each secret value is one comparison key. The
// public interval is [lo, hi). The comparison opens to 1 at a query q
// when q is strictly above the secret value, so the two endpoints
// differ by 1 exactly on lo <= v < hi. The count is the sum of those
// bits. The servers never see a value, and the interval is public.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/range_count.cpp
namespace
{
std::uint64_t inside(std::uint8_t value, std::uint8_t lo, std::uint8_t hi)
{
auto [k0, k1] = dpf::make_dpf(value, dpf::gt(std::uint64_t{1}));
const auto above_lo = dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, lo),
dpf::eval_point(dpf::cmp, k1, lo));
const auto above_hi = dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, hi),
dpf::eval_point(dpf::cmp, k1, hi));
// eval(q) = 1 iff q > value, so eval(hi) - eval(lo) = 1{lo <= value < hi}.
return above_hi - above_lo;
}
} // namespace
int main()
{
constexpr std::uint8_t lo = 10;
constexpr std::uint8_t hi = 20;
const std::uint8_t values[] = {3, 10, 12, 19, 20, 40};
std::uint64_t count = 0;
for (auto v : values)
count += inside(v, lo, hi);
// 10, 12, and 19. 3 and 40 are outside. 20 is the open end.
if (count != 3 || inside(10, lo, hi) != 1 || inside(20, lo, hi) != 0
|| inside(9, lo, hi) != 0)
{
std::cerr << "range count " << count << "\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("range_count",
dpf::protocol::range_count_plan(0), 8))
return rc;
}
std::cout << count << "\n";
return 0;
}

View file

@ -0,0 +1,79 @@
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Sabre, the mailbox write with a fast audit (Vadapalli, Storrier, and Henry,
// S&P 2022). Sender-anonymous messaging: two servers hold subtractive shares
// of every mailbox and the client sends one key each. Like Express the write
// is a full-domain add; unlike Express the audit is a *verifiable* DPF proof
// (Boyle et al. once-per-node fold), a constant-size token per party that
// opens to accept iff the key is a single honest point.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/sabre.cpp
namespace
{
constexpr std::size_t nboxes = 256;
} // namespace
int main()
{
constexpr std::uint8_t address = 17;
constexpr std::uint64_t message = 42;
auto [k0, k1] = dpf::make_dpf(address, message, dpf::verifiable{});
// Each server folds the write into its mailbox shares (one full walk).
std::vector<std::uint64_t> box0(nboxes, 0), box1(nboxes, 0);
dpf::eval_full_add_into(box0, k0);
dpf::eval_full_add_into(box1, k1);
if (box0[address] - box1[address] != message)
{
std::cerr << "sabre mailbox\n";
return 1;
}
if (box0[0] - box1[0] != 0)
{
std::cerr << "sabre neighbor\n";
return 1;
}
// Fast audit: a full-domain VDPF proof. Each party folds a constant-size
// token; the tokens open to accept an honest single-point write.
dpf::proof_token pi0{}, pi1{};
dpf::prove_full(k0, dpf::prove(pi0));
dpf::prove_full(k1, dpf::prove(pi1));
if (!dpf::verify(pi0, pi1))
{
std::cerr << "sabre audit\n";
return 1;
}
// A proof folded over a mismatched pair of points (the shape a malformed,
// multi-point write produces) fails the same check.
dpf::proof_token bad0{}, bad1{};
dpf::prove_interval(k0, std::uint8_t{0}, std::uint8_t{7}, dpf::prove(bad0));
dpf::prove_interval(k1, std::uint8_t{8}, std::uint8_t{15}, dpf::prove(bad1));
if (dpf::verify(bad0, bad1))
{
std::cerr << "sabre audit accepted a mismatch\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("sabre",
dpf::protocol::mailbox_write_fused_plan(0, 8), 8))
return rc;
}
std::cout << (box0[address] - box1[address]) << "\n";
return 0;
}

View file

@ -0,0 +1,65 @@
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Splinter, the DPF query step (Wang, Yun, Goldwasser, Vaikuntanathan, and
// Zeldovich, NSDI 2017). Private queries on public data with two-server FSS.
// The client's private selector is a unit DPF at a secret attribute value.
// Each server dots that selector with a public aggregate column, so the
// answer is the SUM (or COUNT) for the private key without either server
// learning which key was asked.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/splinter.cpp
int main()
{
constexpr std::size_t domain = 256; // attribute values
// Public data, pre-aggregated by attribute: group_sum[v] is the SUM of a
// value column over the rows whose attribute equals v.
std::vector<std::uint64_t> group_sum(domain);
std::vector<std::uint64_t> group_cnt(domain, 1);
for (std::size_t v = 0; v < domain; ++v)
group_sum[v] = (v * 37 + 11) % 1000;
constexpr std::uint8_t secret_key = 88; // the private WHERE value
// One selector key per server. reconstruct = the two servers' shares.
auto [k0, k1] = dpf::make_dpf(secret_key, std::uint64_t{1});
// SELECT SUM(value) WHERE attribute = secret_key.
const auto sum = dpf::reconstruct(
dpf::eval_full_inner_product(dpf::paired, k0, group_sum),
dpf::eval_full_inner_product(dpf::paired, k1, group_sum));
if (sum != group_sum[secret_key])
{
std::cerr << "splinter sum\n";
return 1;
}
// SELECT COUNT(*) WHERE attribute = secret_key is the same selector on an
// all-ones column.
const auto cnt = dpf::reconstruct(
dpf::eval_full_inner_product(dpf::paired, k0, group_cnt),
dpf::eval_full_inner_product(dpf::paired, k1, group_cnt));
if (cnt != 1)
{
std::cerr << "splinter count\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("splinter",
dpf::protocol::fss_point_plan(0), 8))
return rc;
}
std::cout << sum << "\n";
return 0;
}

View file

@ -0,0 +1,132 @@
#include <cstdint>
#include <iostream>
#include <iterator>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// MPC SUBLEQ, the DPF steps of one instruction (Jiang and Henry).
// Offline: expand wildcard unit keys with `defer_eval_full` before the
// addresses are known. Online: assign each address into `offset_x`, read
// by rotating the prepaid buffer (no second AES pass), write by scaling
// the same `e_B` view, and branch with a path evaluation of `x ≤ 0`.
//
// Instruction fetch is the same prepaid unit dotted against three sliding
// windows of D; this listing starts after (A, B, C) are already shares.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/subleq.cpp
namespace
{
using addr_t = std::uint8_t;
using word_t = std::uint32_t;
constexpr std::size_t n = 256;
template <typename Key0, typename Key1, typename T>
void assign_input(Key0 & k0, Key1 & k1, T alpha)
{
const T a0 = static_cast<T>(0x12);
const T a1 = static_cast<T>(alpha - a0);
const auto s0 = k0.offset_x.compute_and_get_share(a0);
const auto s1 = k1.offset_x.compute_and_get_share(a1);
k0.offset_x.reconstruct(s1);
k1.offset_x.reconstruct(s0);
}
/// Opened unit · public memory over `[0, n)`.
template <typename View0, typename View1>
word_t dot_prefix(View0 && v0, View1 && v1, const std::vector<word_t> & mem)
{
word_t acc = 0;
auto it0 = std::begin(v0);
auto it1 = std::begin(v1);
for (std::size_t i = 0; i < n; ++i, ++it0, ++it1)
acc += dpf::reconstruct(*it0, *it1) * mem[i];
return acc;
}
template <typename View0, typename View1>
void add_scaled_prefix(std::vector<word_t> & mem, View0 && v0, View1 && v1,
word_t scale)
{
auto it0 = std::begin(v0);
auto it1 = std::begin(v1);
for (std::size_t i = 0; i < n; ++i, ++it0, ++it1)
mem[i] += scale * dpf::reconstruct(*it0, *it1);
}
} // namespace
int main()
{
// Two's-complement words in a uint32_t container (same bits as int32_t).
constexpr addr_t A = 3;
constexpr addr_t B = 7;
constexpr addr_t C = 2;
constexpr addr_t pc = 0;
std::vector<word_t> D(n);
D[A] = 5;
D[B] = 3; // after SUBLEQ: D[B] = 3 - 5 = -2 ≤ 0 → pc' = C
// --- Offline: wildcard unit keys, full-domain expand at identity -----
auto [kA0, kA1] = dpf::make_dpf(dpf::wildcard_value<addr_t>{}, word_t{1});
auto [kB0, kB1] = dpf::make_dpf(dpf::wildcard_value<addr_t>{}, word_t{1});
auto bufA0 = dpf::make_output_buffer_for_full(kA0);
auto bufA1 = dpf::make_output_buffer_for_full(kA1);
auto bufB0 = dpf::make_output_buffer_for_full(kB0);
auto bufB1 = dpf::make_output_buffer_for_full(kB1);
auto defA0 = dpf::defer_eval_full(kA0, bufA0);
auto defA1 = dpf::defer_eval_full(kA1, bufA1);
auto defB0 = dpf::defer_eval_full(kB0, bufB0);
auto defB1 = dpf::defer_eval_full(kB1, bufB1);
// --- Online: open addresses, rotate prepaid unit vectors -------------
assign_input(kA0, kA1, A);
assign_input(kB0, kB1, B);
const word_t DA = dot_prefix(defA0.get(), defA1.get(), D);
const word_t DB = dot_prefix(defB0.get(), defB1.get(), D);
if (DA != D[A] || DB != D[B])
{
std::cerr << "subleq read\n";
return 1;
}
const word_t x = static_cast<word_t>(DB - DA); // wraps to -2 as uint32_t
// Write D[B] ← D[B] - D[A] by adding (-DA) · e_B. The protocol Beavers
// the scale; the opened -DA stands in here.
add_scaled_prefix(D, defB0.get(), defB1.get(), static_cast<word_t>(-DA));
if (D[B] != static_cast<word_t>(3 - 5) || D[A] != 5)
{
std::cerr << "subleq write\n";
return 1;
}
// Branch: path eval only — never expand the word-domain key.
// `leq` at knot 0, evaluated at x: 1 iff x ≤ 0 in signed order.
auto [kZ0, kZ1] = dpf::make_dpf(std::int32_t{0}, dpf::leq(std::uint64_t{1}));
const auto b = dpf::reconstruct(
dpf::eval_point(dpf::cmp, kZ0, static_cast<std::int32_t>(x)),
dpf::eval_point(dpf::cmp, kZ1, static_cast<std::int32_t>(x)));
const addr_t pc_next = b ? C : static_cast<addr_t>(pc + 3);
if (b != 1 || pc_next != C)
{
std::cerr << "subleq branch\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("subleq",
dpf::protocol::subleq_instruction_plan(0), 8))
return rc;
}
std::cout << static_cast<std::int32_t>(D[B]) << " "
<< static_cast<unsigned>(pc_next) << "\n";
return 0;
}

View file

@ -0,0 +1,69 @@
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>
#include "dpf.hpp"
#include "dpf/app_flow.hpp"
#include "dpf/app_plans.hpp"
// Waldo, the FSS steps (Dauterman, Fang, Crooks, and Popa, S&P 2022). A
// private time-series database. The store is append-only: a new event writes
// a fresh point and never updates an old one. A range/threshold aggregate
// uses the comparison (DCF) channel: the parties dot the per-timestamp
// comparison shares with a public value column, so a SUM over the timestamps
// past a *secret* threshold reveals neither the threshold nor the matches.
//
// c++ -std=c++17 -march=native -I include -I thirdparty \
// examples/applications/waldo.cpp
int main()
{
constexpr std::size_t horizon = 256; // timestamp domain
// Append-only writes. Each event is a unit DPF at its timestamp; the
// servers fold it into their subtractive value shares. Never an update.
std::vector<std::uint64_t> col0(horizon, 0), col1(horizon, 0);
const std::pair<std::uint8_t, std::uint64_t> events[] = {
{30, 5}, {90, 8}, {200, 3}};
for (auto [ts, val] : events)
{
auto [k0, k1] = dpf::make_dpf(ts, val);
dpf::eval_full_add_into(col0, k0);
dpf::eval_full_add_into(col1, k1);
}
if (col0[90] - col1[90] != 8 || col0[30] - col1[30] != 5)
{
std::cerr << "waldo append\n";
return 1;
}
// Public per-timestamp magnitudes (metadata the response consumes).
std::vector<std::uint64_t> magnitude(horizon, 0);
for (auto [ts, val] : events)
magnitude[ts] = val;
// Private-threshold aggregate: SUM of magnitudes at timestamps > T, with
// T secret. Key a gt comparison at T and dot its per-timestamp shares
// with the public magnitude column in one comparison walk.
constexpr std::uint8_t secret_T = 50;
auto [c0, c1] = dpf::make_dpf(secret_T, dpf::gt(std::uint64_t{1}));
const auto h0 = dpf::eval_full_inner_product(dpf::cmp, c0, magnitude);
const auto h1 = dpf::eval_full_inner_product(dpf::cmp, c1, magnitude);
const auto after = dpf::reconstruct_cmp_halves(h0, h1).raw();
// Timestamps 90 and 200 are past T=50: 8 + 3 = 11.
if (after != 11)
{
std::cerr << "waldo threshold aggregate " << after << "\n";
return 1;
}
{
if (int rc = dpf::app::run_measured("waldo",
dpf::protocol::fss_cmp_plan(0), 8))
return rc;
}
std::cout << after << "\n";
return 0;
}

View file

@ -0,0 +1,57 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
/// Pre-assign full-domain expand, then rotate after the input wildcard opens.
int main()
{
using input_type = std::uint8_t;
using output_type = std::uint64_t;
const output_type beta = 7;
const input_type alpha = 42;
const input_type from = 40;
const input_type to = 50;
auto [k0, k1] = dpf::make_dpf(dpf::wildcard_value<input_type>{}, beta);
//! [defer-eval]
auto buf0 = dpf::make_output_buffer_for_full(k0);
auto buf1 = dpf::make_output_buffer_for_full(k1);
auto deferred0 = dpf::defer_eval_interval(k0, from, to, buf0);
auto deferred1 = dpf::defer_eval_interval(k1, from, to, buf1);
// Parties open mask - alpha into offset_x (local demo of the exchange).
const input_type a0 = 0x12;
const input_type a1 = static_cast<input_type>(alpha - a0);
const auto sh0 = k0.offset_x.compute_and_get_share(a0);
const auto sh1 = k1.offset_x.compute_and_get_share(a1);
k0.offset_x.reconstruct(sh1);
k1.offset_x.reconstruct(sh0);
auto view0 = deferred0.get();
auto view1 = deferred1.get();
//! [defer-eval]
auto it0 = std::begin(view0);
auto it1 = std::begin(view1);
for (input_type x = from; x <= to; ++x, ++it0, ++it1)
{
const output_type got = dpf::reconstruct(*it0, *it1);
const output_type expect = (x == alpha) ? beta : 0;
if (got != expect)
{
std::cerr << "defer_eval\n";
return 1;
}
}
if (it0 != std::end(view0) || it1 != std::end(view1))
{
std::cerr << "defer_eval length\n";
return 1;
}
std::cout << dpf::reconstruct(*std::begin(view0), *std::begin(view1))
<< "\n";
return 0;
}

View file

@ -0,0 +1,44 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
/// Three-party comparison and interval-containment keys (one DCF share each).
int main()
{
using Input = std::uint8_t;
const Input thresh = 100;
const std::uint64_t beta = 5;
//! [eval-dpf3-cmp]
auto [c1, c2, c3] = dpf::make_dpf3_cmp(thresh, beta);
// Parties 1 and 3 hold the k0 half; party 2 holds k1. Open any complementary pair.
const dpf::fp61 hot =
dpf::reconstruct_cmp_halves(dpf::eval_point(c1, Input{10}),
dpf::eval_point(c2, Input{10}));
const dpf::fp61 cold =
dpf::reconstruct_cmp_halves(dpf::eval_point(c3, Input{200}),
dpf::eval_point(c2, Input{200}));
//! [eval-dpf3-cmp]
if (hot.raw() != beta || cold.raw() != 0)
{
std::cerr << "dpf3 cmp\n";
return 1;
}
//! [eval-dpf3-ic]
auto [i1, i2, i3] = dpf::make_dpf3_ic(Input{10}, Input{20}, Input{40}, beta);
// Interval is relative to the public shift `r`; x=35 is on for (20,40)@r=10.
const dpf::fp61 inside =
dpf::reconstruct_cmp_halves(dpf::eval_point(i1, Input{35}),
dpf::eval_point(i2, Input{35}));
//! [eval-dpf3-ic]
if (inside.raw() != beta)
{
std::cerr << "dpf3 ic\n";
return 1;
}
std::cout << hot.raw() << "\n";
return 0;
}

View file

@ -0,0 +1,36 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
/// Dual-spine Doerner–Shelat (2,3) keygen: XOR shares of `α`, same clear `β`
/// as honest-dealer `make_dpf3(α, β)`.
int main()
{
using Input = std::uint8_t;
const Input alpha = 0x2a;
const Input x0 = 0x13;
const Input x1 = static_cast<Input>(alpha ^ x0);
const dpf::fp61 beta{99};
//! [eval-dpf3-ds]
auto [d1, d2, d3] = dpf::make_dpf3(alpha, beta);
auto [s1, s2, s3] = dpf::make_dpf3_doerner_shelat(x0, x1, beta);
const dpf::fp61 dealer = dpf::reconstruct(
dpf::as_share(d1, dpf::eval_point(d1, alpha)),
dpf::as_share(d2, dpf::eval_point(d2, alpha)),
dpf::as_share(d3, dpf::eval_point(d3, alpha)));
const dpf::fp61 dual = dpf::reconstruct(
dpf::as_share(s1, dpf::eval_point(s1, alpha)),
dpf::as_share(s2, dpf::eval_point(s2, alpha)),
dpf::as_share(s3, dpf::eval_point(s3, alpha)));
//! [eval-dpf3-ds]
if (dealer != beta || dual != beta)
{
std::cerr << "dealer vs dual-spine disagree\n";
return 1;
}
std::cout << dual.raw() << "\n";
return 0;
}

View file

@ -0,0 +1,42 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
/// Three-evaluator point DPF (ePrint 2024/1658 Fig. 3). Each key is a Shamir
/// share; open with any two (or all three) via `dpf::reconstruct`.
int main()
{
using Input = std::uint8_t;
const Input alpha = 42;
const dpf::fp61 beta{7};
//! [eval-dpf3-point]
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));
//! [eval-dpf3-point]
if (opened != beta)
{
std::cerr << "dpf3 at the programmed input\n";
return 1;
}
const dpf::fp61 z1 = dpf::eval_point(k1, Input{41});
const dpf::fp61 z2 = dpf::eval_point(k2, Input{41});
const dpf::fp61 z3 = dpf::eval_point(k3, Input{41});
if (dpf::reconstruct(dpf::as_share(k1, z1), dpf::as_share(k2, z2),
dpf::as_share(k3, z3))
.raw()
!= 0)
{
std::cerr << "dpf3 off the programmed input\n";
return 1;
}
std::cout << opened.raw() << "\n";
return 0;
}

View file

@ -0,0 +1,102 @@
#include <array>
#include <cstdint>
#include <iostream>
#include <tuple>
#include <vector>
#include "dpf.hpp"
/// Fused inner product: do not materialize the DPF vector.
/// A scalar weight vector dots with one output. A row of a tuple or
/// `std::array` dots with several outputs, including an ancestor slot
/// and the leaf, read off one path.
int main()
{
using In = std::uint8_t;
//! [eval-inner-product-scalar]
// Trivial: sum_x DPF(x) * w[x] over a short interval.
const In alpha = 42;
const std::uint64_t beta = 7;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
const In from = 40;
const In to = 50;
std::vector<std::uint64_t> w(to - from + 1);
for (std::size_t i = 0; i < w.size(); ++i)
w[i] = i + 1;
const auto s0 = dpf::eval_inner_product(dpf::paired, k0, from, to, w);
const auto s1 = dpf::eval_inner_product(dpf::paired, k1, from, to, w);
//! [eval-inner-product-scalar]
if (dpf::reconstruct(s0, s1) != beta * w[alpha - from])
{
std::cerr << "scalar interval\n";
return 1;
}
//! [eval-inner-product-full]
// Trivial full domain. Only α contributes.
std::vector<std::uint64_t> wall(256, 1);
const auto f0 = dpf::eval_full_inner_product(dpf::paired, k0, wall);
const auto f1 = dpf::eval_full_inner_product(dpf::paired, k1, wall);
//! [eval-inner-product-full]
if (dpf::reconstruct(f0, f1) != beta)
{
std::cerr << "full\n";
return 1;
}
//! [eval-inner-product-paired]
// Two outputs on the same leaf. rows[i] = {weight for output 0, output 1}.
auto [p0, p1] = dpf::make_dpf(In{9}, std::uint32_t{3}, std::uint32_t{5});
std::vector<std::array<std::uint32_t, 2>> rows;
for (In x = 8;; ++x)
{
rows.push_back({std::uint32_t{1}, std::uint32_t{x}});
if (x == 10)
break;
}
const auto a0 = dpf::eval_inner_product<0, 1>(dpf::paired, p0, In{8}, In{10}, rows);
const auto a1 = dpf::eval_inner_product<0, 1>(dpf::paired, p1, In{8}, In{10}, rows);
//! [eval-inner-product-paired]
// x=9 is hot: output0 * 1 + output1 * 9.
if (dpf::reconstruct(a0, a1) != std::uint64_t{3} * 1u + std::uint64_t{5} * 9u)
{
std::cerr << "paired leaf\n";
return 1;
}
//! [eval-inner-product-ancestor]
// Prefix slot at<4> and the full-domain leaf, one path per point.
// 0x2a and 0x2b share the high nibble 0x2, so both see payload 5 there.
// 0x10 is a different nibble. Only 0x2a is hot on the leaf.
auto [h0, h1] = dpf::make_dpf(In{0x2a}, dpf::at<4>(std::uint8_t{5}), std::uint8_t{9});
const std::vector<In> pts{0x10, 0x2a, 0x2b};
const std::vector<std::tuple<std::uint32_t, std::uint32_t>> hw{
{1u, 0u}, {1u, 1u}, {2u, 4u}};
const auto q0 = dpf::eval_sequence_inner_product<0, 1>(h0, pts.begin(), pts.end(), hw);
const auto q1 = dpf::eval_sequence_inner_product<0, 1>(h1, pts.begin(), pts.end(), hw);
//! [eval-inner-product-ancestor]
// 0x10 is off. 0x2a: 5*1 + 9*1. 0x2b: prefix still 5, leaf 0, times (2, 4).
const std::uint64_t ancestor_expect = 5u * 1u + 9u * 1u + 5u * 2u;
if (dpf::reconstruct(q0, q1) != ancestor_expect)
{
std::cerr << "ancestor\n";
return 1;
}
//! [eval-inner-product-recipe]
const auto recipe = dpf::make_sequence_recipe<decltype(h0)>(pts.begin(), pts.end());
const auto r0 = dpf::eval_sequence_inner_product<0, 1>(
h0, recipe, pts.begin(), pts.end(), hw);
const auto r1 = dpf::eval_sequence_inner_product<0, 1>(
h1, recipe, pts.begin(), pts.end(), hw);
//! [eval-inner-product-recipe]
if (dpf::reconstruct(r0, r1) != ancestor_expect)
{
std::cerr << "recipe\n";
return 1;
}
std::cout << dpf::reconstruct(s0, s1) << "\n";
return 0;
}

View file

@ -0,0 +1,58 @@
#include <cmath>
#include <cstdint>
#include <iostream>
#include <vector>
#include "grotto.hpp"
// Haar and bior(5,3) lookup tables (Reis, Ugurbil, Wagh, Henry, de Vega,
// PoPETs 2025, ePrint 2025/013). The grid is sigmoid on [0, 4), stored as
// Q4.4. Depth 2 keeps the top 4 bits of a 6-bit index.
//
// c++ -std=c++17 -march=native -I include -I thirdparty examples/grotto/dwt_lut.cpp
int main()
{
//! [dwt-lut]
constexpr unsigned domain_bits = 6;
constexpr unsigned fractional_bits = 4;
constexpr unsigned depth = 2;
auto samples = grotto::sample_dwt_signal(domain_bits, fractional_bits,
[](double x) {
return 1.0 / (1.0 + std::exp(-(x - 2.0)));
});
auto haar = grotto::make_haar_dwt_lut(samples, fractional_bits, depth);
auto bior = grotto::make_bior53_dwt_lut(samples, fractional_bits, depth);
// Haar is the mean of each block of 2^depth samples, then quantized.
const std::uint64_t raw = 32;
double block = 0;
for (unsigned k = 0; k < 4; ++k)
block += samples[(raw & ~std::uint64_t{3}) + k];
const auto haar_expect = static_cast<std::int64_t>(
std::floor(block / 4.0 * 16.0));
// bior(5,3), lsb = 0: only the first tap, at index msb+2, divided by 2^j.
const std::uint64_t msb = raw >> depth;
const auto c0 = bior.coeff[(msb + 2) % bior.coeff.size()];
const auto bior_at_32 = c0 / 4;
// lsb = 1: both taps, weights (2^j - lsb) and lsb, then divide by 2^{2j}.
const auto c1 = bior.coeff[(msb + 3) % bior.coeff.size()];
const auto bior_at_33 = (c0 * 3 + c1) / 16;
//! [dwt-lut]
if (haar(raw) != haar_expect || haar(raw) != 8)
{
std::cerr << "haar lut\n";
return 1;
}
if (bior(raw) != bior_at_32 || bior(33) != bior_at_33 || bior(raw) != 8)
{
std::cerr << "bior lut\n";
return 1;
}
std::cout << haar(raw) << " " << bior(raw) << " " << bior(33) << "\n";
return 0;
}

View file

@ -0,0 +1,98 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include "grotto.hpp"
/// Binomial jet readouts and an exact ring switch from one public offset.
int main()
{
//! [jet-and-ring]
// --- Binomial jet -------------------------------------------------------
// After eta opens, the jet is the binomial basis at the wrapped
// center+kappa. Degree 3 leaves room for a degree-2 hockey-stick prefix.
const std::uint8_t center = 12;
const std::uint8_t eta = 3;
const std::uint8_t point = static_cast<std::uint8_t>(center + eta); // 15
const std::size_t degree = 3;
auto jet_keys = grotto::make_offset_jet_keys<std::uint8_t>(center, degree);
// f(t) = 4 + 2 C(t,1) + C(t,2) (padded to degree 3).
const std::vector<std::uint64_t> poly{4, 2, 1};
std::vector<std::uint64_t> coeff = poly;
coeff.push_back(0);
const std::vector<std::uint8_t> knots{0};
const auto j0 = grotto::offset_jet_shares<0>(jet_keys, knots, eta);
const auto j1 = grotto::offset_jet_shares<1>(jet_keys, knots, eta);
std::vector<std::uint64_t> jet(degree + 1);
for (std::size_t k = 0; k <= degree; ++k)
jet[k] = j0[k] + j1[k];
const std::uint64_t value = grotto::offset_jet_dot(coeff, jet);
const std::uint64_t diff = grotto::offset_jet_dot(
grotto::offset_jet_difference_coeff(coeff), jet);
const std::uint64_t prefix = grotto::offset_jet_dot(
grotto::offset_jet_prefix_coeff(poly), jet);
// Padé / Newton are public dots against the same jet, then one reciprocal
// after the shares are opened. For a seed p(t)/p'(t):
// auto num = offset_jet_dot(coeff, jet);
// auto den = offset_jet_dot(offset_jet_difference_coeff(coeff), jet);
// // open num, den; one masked reciprocal; Newton: t - num/den.
// --- Exact ring switch --------------------------------------------------
// Same public-offset pattern: eta = x - r, then x lands in the residue.
const std::uint8_t r = 200;
const std::uint8_t x = 44;
const std::uint8_t ring_eta = static_cast<std::uint8_t>(x - r); // 100, wraps
using Z = grotto::zn64<1009>;
auto ring = grotto::make_ring_switch_keys<Z>(r);
const Z x_mod = grotto::ring_switch_eval<0>(ring, ring_eta)
+ grotto::ring_switch_eval<1>(ring, ring_eta);
auto field = grotto::make_ring_switch_keys<dpf::field128>(r);
const dpf::field128 x_field = grotto::ring_switch_eval<0>(field, ring_eta)
+ grotto::ring_switch_eval<1>(field, ring_eta);
//! [jet-and-ring]
auto c = [](std::uint64_t t, unsigned k) {
return grotto::offset_jet_binom(t, k);
};
const std::uint64_t expect_v =
4 + 2 * c(point, 1) + c(point, 2);
if (value != expect_v)
{
std::cerr << "jet value\n";
return 1;
}
const std::uint64_t expect_fx1 =
4 + 2 * c(static_cast<std::uint8_t>(point + 1), 1)
+ c(static_cast<std::uint8_t>(point + 1), 2);
if (diff != expect_fx1 - expect_v)
{
std::cerr << "jet difference\n";
return 1;
}
std::uint64_t expect_p = 0;
for (std::uint8_t i = 0; i < point; ++i)
expect_p += 4 + 2 * c(i, 1) + c(i, 2);
if (prefix != expect_p)
{
std::cerr << "jet prefix\n";
return 1;
}
if (x_mod.raw() != static_cast<std::uint64_t>(x) % 1009)
{
std::cerr << "ring zn64\n";
return 1;
}
if (x_field != dpf::field128{x})
{
std::cerr << "ring field128\n";
return 1;
}
std::cout << value << " " << diff << " " << prefix << " "
<< x_mod.raw() << "\n";
return 0;
}

View file

@ -0,0 +1,43 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include "grotto.hpp"
/// Two piecewise LUTs, one comparison, one prefix walk of the union.
int main()
{
//! [lut-union]
grotto::piecewise_lut<std::uint8_t> low{{0, 10}, {{1, 0}, {0, 2}}};
grotto::piecewise_lut<std::uint8_t> high{{0, 4, 12}, {{3, 0}, {1, 1}, {9, 4}}};
const std::uint8_t center = 12;
const std::uint8_t eta = 3;
auto plan = grotto::make_lut_union_plan({low, high}, eta);
auto mat = grotto::make_offset_poly_keys<std::uint8_t>(center, plan.degree);
auto s0 = grotto::lut_union_eval<0>(mat, plan);
auto s1 = grotto::lut_union_eval<1>(mat, plan);
dpf::protocol::composer composer(0);
grotto::schedule_lut_union(composer, plan);
//! [lut-union]
if (plan.comparisons != 1 || plan.prefix_walks != 1)
{
std::cerr << "plan shape\n";
return 1;
}
if (composer.default_plan().rounds() != plan.depth)
{
std::cerr << "geneval rounds\n";
return 1;
}
const std::uint64_t opened[2] = {s0[0] + s1[0], s0[1] + s1[1]};
if (opened[0] != grotto::offset_poly_clear<std::uint8_t>(center, low.knots, low.coeff, eta)
|| opened[1] != grotto::offset_poly_clear<std::uint8_t>(center, high.knots, high.coeff, eta))
{
std::cerr << "opened value\n";
return 1;
}
return 0;
}

View file

@ -0,0 +1,132 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include "grotto.hpp"
namespace
{
std::uint64_t pow_u64(std::uint64_t base, std::uint64_t exp)
{
std::uint64_t acc = 1;
while (exp != 0)
{
if (exp & 1u)
acc *= base;
base *= base;
exp >>= 1;
}
return acc;
}
} // namespace
/// Representation shift (Fibonacci / geometric / CRC) and twisted monomials.
int main()
{
//! [repr-and-twist]
// --- Representation shift: Fibonacci checkpoint ----------------------
// Dealer keys S_c = (F_{c+1}, F_c). After eta opens, each party applies
// the public companion-matrix power M^kappa to its share of S_c.
const std::uint8_t center = 10;
const std::uint8_t eta = 5;
const std::uint8_t point = static_cast<std::uint8_t>(center + eta); // 15
const auto fib_state = grotto::offset_repr_fibonacci_state(center);
const auto M = grotto::offset_repr_fibonacci_matrix();
auto fib_keys = grotto::make_offset_repr_keys<std::uint8_t>(center, fib_state);
const std::vector<std::uint8_t> knots{0};
const auto f0 = grotto::offset_repr_eval<0>(fib_keys, M, knots, eta);
const auto f1 = grotto::offset_repr_eval<1>(fib_keys, M, knots, eta);
const std::vector<std::uint64_t> S{f0[0] + f1[0], f0[1] + f1[1]};
// Geometric twin: 1x1 matrix [lambda] advances lambda^c by lambda^kappa.
const std::uint64_t lambda_geo = 3;
const auto G = grotto::offset_repr_geometric_matrix(lambda_geo);
auto geo_keys = grotto::make_offset_repr_keys<std::uint8_t>(
center, {pow_u64(lambda_geo, center)});
const auto g0 = grotto::offset_repr_eval<0>(geo_keys, G, knots, eta);
const auto g1 = grotto::offset_repr_eval<1>(geo_keys, G, knots, eta);
const std::uint64_t geo = g0[0] + g1[0];
// Clear CRC-32 jump documents the GF(2) twin (XOR shares, not additive).
const std::uint32_t crc_seed = 0x12345678u;
const std::uint32_t crc_jumped = grotto::offset_repr_crc32_jump(crc_seed, 64);
// --- Twisted monomials: (a0 + a1 x + a2 x^2) * lambda^x -------------
const std::uint64_t lambda = 3;
const std::size_t degree = 2;
auto twist_keys = grotto::make_offset_twist_keys<std::uint8_t>(
center, degree, lambda);
// h(x) = (2 + 5x + x^2) * 3^x
const std::vector<std::uint64_t> coeff{2, 5, 1};
const std::uint64_t twisted =
grotto::offset_twist_eval<0>(twist_keys, knots, coeff, eta)
+ grotto::offset_twist_eval<1>(twist_keys, knots, coeff, eta);
// Dyadic decay: masked carry shift. The sum of the shares is the shifted value.
auto half_keys = grotto::make_offset_twist_keys<std::uint8_t>(
center, degree, grotto::twist_half);
const std::uint64_t half =
grotto::offset_twist_eval<0>(half_keys, knots, coeff, eta)
+ grotto::offset_twist_eval<1>(half_keys, knots, coeff, eta);
// Closed form sum_{k=1}^n k * lambda^k from the same twisted table.
const std::uint64_t ag = grotto::offset_twist_arithmetico_geometric(point, lambda);
//! [repr-and-twist]
const auto expect_S = grotto::offset_repr_fibonacci_state(point);
if (S != expect_S)
{
std::cerr << "fibonacci state\n";
return 1;
}
if (geo != pow_u64(lambda_geo, point))
{
std::cerr << "geometric\n";
return 1;
}
std::uint64_t expect_t = 0;
std::uint64_t xp = 1;
for (std::uint64_t c : coeff)
{
expect_t += c * xp;
xp *= point;
}
expect_t *= pow_u64(lambda, point);
if (twisted != expect_t)
{
std::cerr << "twisted poly\n";
return 1;
}
std::uint64_t expect_h = 0;
xp = 1;
for (std::uint64_t c : coeff)
{
expect_h += c * xp;
xp *= point;
}
expect_h >>= point;
if (half != expect_h)
{
std::cerr << "twist half\n";
return 1;
}
std::uint64_t expect_ag = 0;
for (std::uint64_t k = 1; k <= point; ++k)
expect_ag += k * pow_u64(lambda, k);
if (ag != expect_ag)
{
std::cerr << "arithmetico-geometric\n";
return 1;
}
if (crc_jumped == 0 && crc_seed != 0)
{
// Jump can legally land on zero; only used as a smoke output.
}
std::cout << S[1] << " " << geo << " " << twisted << " " << half << " "
<< ag << " " << crc_jumped << "\n";
return 0;
}

131
examples/mwe/chooser.html Normal file
View file

@ -0,0 +1,131 @@
<div class="chooser">
<p class="q">Who knows the secret index?</p>
<input type="radio" name="holder" id="h-dealer">
<label class="choice" for="h-dealer"><strong>A dealer</strong><span>knows alpha and beta, and hands each party a key</span></label>
<input type="radio" name="holder" id="h-share">
<label class="choice" for="h-share"><strong>The two parties</strong><span>already share alpha. They want a key they can reuse</span></label>
<input type="radio" name="holder" id="h-answer">
<label class="choice" for="h-answer"><strong>The two parties</strong><span>already share alpha. They want the answer, not a key</span></label>
<input type="radio" name="holder" id="h-three">
<label class="choice" for="h-three"><strong>Three evaluators</strong><span>any two of them can open the value</span></label>
<div class="branch branch-dealer">
<p class="q">What should be nonzero?</p>
<input type="radio" name="dealer-what" id="d-point">
<label class="choice" for="d-point"><strong>One point</strong><span>beta at alpha, zero elsewhere</span></label>
<input type="radio" name="dealer-what" id="d-cmp">
<label class="choice" for="d-cmp"><strong>A comparison</strong><span>1 where x is above alpha</span></label>
<input type="radio" name="dealer-what" id="d-ic">
<label class="choice" for="d-ic"><strong>A public interval</strong><span>beta on a span of the unmasked input</span></label>
<div class="result result-point">
<h3>dpf::make_dpf</h3>
<p>Dealer point key. Leaf shares are subtractive, so reconstruct subtracts them. This prints <code>7 0</code>.</p>
<p><a href="incremental_8hpp.html">dpf/incremental.hpp</a></p>
<button type="button" class="mwe-copy">Copy</button>
<pre class="mwe"><code>#include &lt;cstdint&gt;
#include &lt;iostream&gt;
#include "dpf.hpp"
int main()
{
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
const std::uint64_t at = dpf::reconstruct(
*dpf::eval_point(k0, alpha),
*dpf::eval_point(k1, alpha));
const std::uint64_t off = dpf::reconstruct(
*dpf::eval_point(k0, std::uint8_t{0}),
*dpf::eval_point(k1, std::uint8_t{0}));
std::cout &lt;&lt; at &lt;&lt; " " &lt;&lt; off &lt;&lt; "\n";
return (at == beta &amp;&amp; off == 0) ? 0 : 1;
}</code></pre>
<p class="mwe-cmd"><code>c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/point.cpp</code></p>
</div>
<div class="result result-cmp">
<h3>dpf::gt</h3>
<p>One comparison on the same key. Comparison shares are additive, so reconstruct adds them. This prints <code>1 0</code>.</p>
<p><a href="dcf_8hpp.html">dpf/dcf.hpp</a></p>
<button type="button" class="mwe-copy">Copy</button>
<pre class="mwe"><code>#include &lt;cstdint&gt;
#include &lt;iostream&gt;
#include "dpf.hpp"
int main()
{
const std::uint8_t alpha = 40;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(std::uint64_t{1}));
const auto above = dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, std::uint8_t{50}),
dpf::eval_point(dpf::cmp, k1, std::uint8_t{50}));
const auto below = dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, std::uint8_t{10}),
dpf::eval_point(dpf::cmp, k1, std::uint8_t{10}));
std::cout &lt;&lt; above &lt;&lt; " " &lt;&lt; below &lt;&lt; "\n";
return (above == 1 &amp;&amp; below == 0) ? 0 : 1;
}</code></pre>
<p class="mwe-cmd"><code>c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/compare.cpp</code></p>
</div>
<div class="result result-ic">
<h3>dpf::ic</h3>
<p>The secret is a mask. The public interval is on <code>x - r</code>. This prints <code>9 0</code>: 14 - 10 = 4, which sits in [3, 5].</p>
<p><a href="interval_8hpp.html">dpf/interval.hpp</a></p>
<button type="button" class="mwe-copy">Copy</button>
<pre class="mwe"><code>#include &lt;cstdint&gt;
#include &lt;iostream&gt;
#include "dpf.hpp"
int main()
{
const std::uint8_t r = 10;
const std::uint64_t beta = 9;
auto [k0, k1] = dpf::make_dpf(r, dpf::ic(std::uint8_t{3}, std::uint8_t{5}, beta));
const auto inside = dpf::reconstruct(
dpf::eval_point(dpf::ic, k0, std::uint8_t{14}),
dpf::eval_point(dpf::ic, k1, std::uint8_t{14}));
const auto outside = dpf::reconstruct(
dpf::eval_point(dpf::ic, k0, std::uint8_t{0}),
dpf::eval_point(dpf::ic, k1, std::uint8_t{0}));
std::cout &lt;&lt; inside &lt;&lt; " " &lt;&lt; outside &lt;&lt; "\n";
return (inside == beta &amp;&amp; outside == 0) ? 0 : 1;
}</code></pre>
<p class="mwe-cmd"><code>c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/interval.cpp</code></p>
</div>
</div>
<div class="branch branch-share">
<h3>dpf::make_dpf_doerner_shelat</h3>
<p>Each party holds an XOR share of alpha. A pad dealer supplies the tape and does not learn alpha. The key is reusable. This prints <code>7</code>.</p>
<p><a href="doerner__shelat_8hpp.html">dpf/doerner_shelat.hpp</a></p>
<button type="button" class="mwe-copy">Copy</button>
<pre class="mwe"><code>#include &lt;cstdint&gt;
#include &lt;iostream&gt;
#include "dpf.hpp"
int main()
{
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
const std::uint8_t x0 = 7;
const std::uint8_t x1 = static_cast&lt;std::uint8_t&gt;(alpha ^ x0);
auto root = []() { return dpf::uniform_sample&lt;simde__m128i&gt;(); };
struct pad {
simde__m128i block() { return dpf::uniform_sample&lt;simde__m128i&gt;(); }
std::uint8_t bit() {
return static_cast&lt;std::uint8_t&gt;(dpf::uniform_sample&lt;unsigned&gt;() &amp; 1u);
}
};
dpf::ds_randomness&lt;decltype(root), pad&gt; rng{root, {}};
auto keys = dpf::make_dpf_doerner_shelat(x0, x1, rng, beta);
const std::uint64_t opened = dpf::reconstruct(
*dpf::eval_point(keys.first, alpha),
*dpf::eval_point(keys.second, alpha));
std::cout &lt;&lt; opened &lt;&lt; "\n";
return opened == beta ? 0 : 1;
}</code></pre>
<p class="mwe-cmd"><code>c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/shared_index.cpp</code></p>
</div>
<div class="branch branch-answer">
<h3>dpf::geneval_point</h3>
<p>Same share convention as the reusable key, and the parties open the value along the query. The call does not hand back a key you can evaluate again. The openings are on <a href="geneval_8hpp.html">dpf/geneval.hpp</a>: <code>geneval_point</code>, <code>geneval_interval</code>, <code>geneval_sequence</code>, <code>geneval_full</code>, and <code>geneval_cmp</code>.</p>
</div>
<div class="branch branch-three">
<h3>dpf::make_dpf3</h3>
<p>Three evaluators, any two open. Spines are Shamir shares in <code>fp61</code>. The dealerless form is <code>make_dpf3_doerner_shelat</code>. Comparisons for that setting are <code>make_dpf3_cmp</code> and <code>make_dpf3_ic</code>.</p>
<p><a href="dpf3_8hpp.html">dpf/dpf3.hpp</a> · <a href="dpf3__cmp_8hpp.html">dpf/dpf3_cmp.hpp</a></p>
</div>
</div>

24
examples/mwe/compare.cpp Normal file
View file

@ -0,0 +1,24 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
// Complete program. Comparison shares are additive: reconstruct is share0 + share1.
// gt(1) is 1 where x > alpha and 0 elsewhere.
//
// c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/compare.cpp
int main()
{
const std::uint8_t alpha = 40;
auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(std::uint64_t{1}));
const auto above = dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, std::uint8_t{50}),
dpf::eval_point(dpf::cmp, k1, std::uint8_t{50}));
const auto below = dpf::reconstruct(
dpf::eval_point(dpf::cmp, k0, std::uint8_t{10}),
dpf::eval_point(dpf::cmp, k1, std::uint8_t{10}));
std::cout << above << " " << below << "\n";
return (above == 1 && below == 0) ? 0 : 1;
}

28
examples/mwe/interval.cpp Normal file
View file

@ -0,0 +1,28 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
// Complete program. The secret is a mask r. The public interval is [p, q]
// on the unmasked input x - r. ic returns `beta` inside that interval.
//
// c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/interval.cpp
int main()
{
const std::uint8_t r = 10;
const std::uint8_t p = 3;
const std::uint8_t q = 5;
const std::uint64_t beta = 9;
auto [k0, k1] = dpf::make_dpf(r, dpf::ic(p, q, beta));
// x = 14 means x - r = 4, which is inside [3, 5].
const auto inside = dpf::reconstruct(
dpf::eval_point(dpf::ic, k0, std::uint8_t{14}),
dpf::eval_point(dpf::ic, k1, std::uint8_t{14}));
const auto outside = dpf::reconstruct(
dpf::eval_point(dpf::ic, k0, std::uint8_t{0}),
dpf::eval_point(dpf::ic, k1, std::uint8_t{0}));
std::cout << inside << " " << outside << "\n";
return (inside == beta && outside == 0) ? 0 : 1;
}

24
examples/mwe/point.cpp Normal file
View file

@ -0,0 +1,24 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
// Complete program. Leaf shares are subtractive: reconstruct is share0 - share1.
//
// c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/point.cpp
int main()
{
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
auto [k0, k1] = dpf::make_dpf(alpha, beta);
const std::uint64_t at = dpf::reconstruct(
*dpf::eval_point(k0, alpha),
*dpf::eval_point(k1, alpha));
const std::uint64_t off = dpf::reconstruct(
*dpf::eval_point(k0, std::uint8_t{0}),
*dpf::eval_point(k1, std::uint8_t{0}));
std::cout << at << " " << off << "\n";
return (at == beta && off == 0) ? 0 : 1;
}

43
examples/mwe/shamir.cpp Normal file
View file

@ -0,0 +1,43 @@
#include <array>
#include <cstdint>
#include <iostream>
#include <tuple>
#include <type_traits>
#include "dpf.hpp"
// (K,N) Shamir. The secret is the constant term of a degree K-1 polynomial.
// Party i holds that polynomial at x = i+1. Any K shares open it. (2,3) is
// shamir_share: make_shamir_shares(secret, slope) is deal<T, 2, 3>.
// fp61 and gf2n both open. For gf2n, N must be less than 2^k.
//
// c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/shamir.cpp
int main()
{
using F = dpf::fp61;
const F secret{10};
// p(x) = 10 + 2x + 3x^2. Parties 0, 2, and 4 are enough.
const std::array<F, 2> coeff{{F{2}, F{3}}};
auto shares = dpf::make_shamir_shares<3, 5>(secret, coeff);
const F opened = dpf::shamir::reconstruct(
std::get<0>(shares), std::get<2>(shares), std::get<4>(shares));
const F slope{3};
auto [s0, s1, s2] = dpf::make_shamir_shares(secret, slope);
static_assert(std::is_same_v<decltype(s0), dpf::shamir::share<F, 0, 2, 3>>);
const F opened23 = dpf::reconstruct(s1, s2);
const bool on_line = s0.raw() == secret + slope * F{1}
&& s2.raw() == secret + slope * F{3};
using G = dpf::gf28;
auto [g0, g1, g2] = dpf::make_shamir_shares(G{0x1b}, G{0x5a});
const G gopen = dpf::reconstruct(g0, g2);
const G gopen13 = dpf::reconstruct(g1, g2);
std::cout << opened.raw() << " " << opened23.raw() << " "
<< static_cast<unsigned>(gopen.raw()) << "\n";
return (opened == secret && opened23 == secret && on_line
&& gopen == G{0x1b} && gopen13 == G{0x1b})
? 0 : 1;
}

View file

@ -0,0 +1,36 @@
#include <cstdint>
#include <iostream>
#include "dpf.hpp"
// Complete program. The parties already hold XOR shares of alpha.
// The pad stream is local here; a real protocol would draw it from a dealer
// who never sees alpha.
//
// c++ -std=c++17 -march=native -I include -I thirdparty examples/mwe/shared_index.cpp
int main()
{
const std::uint8_t alpha = 42;
const std::uint64_t beta = 7;
const std::uint8_t x0 = 7;
const std::uint8_t x1 = static_cast<std::uint8_t>(alpha ^ x0);
auto root = []() { return dpf::uniform_sample<simde__m128i>(); };
struct pad
{
simde__m128i block() { return dpf::uniform_sample<simde__m128i>(); }
std::uint8_t bit()
{
return static_cast<std::uint8_t>(dpf::uniform_sample<unsigned>() & 1u);
}
};
dpf::ds_randomness<decltype(root), pad> rng{root, {}};
auto keys = dpf::make_dpf_doerner_shelat(x0, x1, rng, beta);
const std::uint64_t opened = dpf::reconstruct(
*dpf::eval_point(keys.first, alpha),
*dpf::eval_point(keys.second, alpha));
std::cout << opened << "\n";
return opened == beta ? 0 : 1;
}

View file

@ -0,0 +1,10 @@
#include <iostream>
#include "dpf.hpp"
int main()
{
auto [k0, k1] = dpf::make_dpf(std::uint8_t{3}, dpf::gf28{0x1b});
auto y0 = *dpf::eval_point(k0, std::uint8_t{3});
auto y1 = *dpf::eval_point(k1, std::uint8_t{3});
std::cout << dpf::reconstruct(y0, y1) << "\n";
}

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;
}
}

View file

@ -0,0 +1,42 @@
#include <cstring>
#include <iostream>
#include <string>
#include "dpf/net/identity.hpp"
// Party identity keys for encrypted, authenticated party links.
//
// c++ -std=c++17 -I include -I thirdparty examples/tools/dpf_keygen.cpp \
// -lssl -lcrypto -o dpf_keygen
// ./dpf_keygen p0.key # new key file (mode 600); prints the public key
// ./dpf_keygen --public p0.key # public key of an existing key file
//
// Give each party its own key file (`identity = p0.key` in its config) and
// give the parties that should authenticate it the printed public key
// (`peer.0 = <public key>`).
int main(int argc, char ** argv)
{
try
{
if (argc == 3 && std::strcmp(argv[1], "--public") == 0)
{
std::cout << dpf::net::identity::load(argv[2]).key().base64() << "\n";
return 0;
}
if (argc == 2 && argv[1][0] != '-')
{
const auto id = dpf::net::identity::generate();
id.save(argv[1]);
std::cout << id.key().base64() << "\n";
return 0;
}
std::cerr << "usage: dpf_keygen KEYFILE | dpf_keygen --public KEYFILE\n";
return 2;
}
catch (const std::exception & e)
{
std::cerr << "dpf_keygen: " << e.what() << "\n";
return 1;
}
}