Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
113
examples/applications/bitmore.cpp
Normal file
113
examples/applications/bitmore.cpp
Normal 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;
|
||||
}
|
||||
79
examples/applications/duoram3.cpp
Normal file
79
examples/applications/duoram3.cpp
Normal 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;
|
||||
}
|
||||
222
examples/applications/experiment_bench.cpp
Normal file
222
examples/applications/experiment_bench.cpp
Normal 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;
|
||||
}
|
||||
84
examples/applications/express.cpp
Normal file
84
examples/applications/express.cpp
Normal 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;
|
||||
}
|
||||
61
examples/applications/floram.cpp
Normal file
61
examples/applications/floram.cpp
Normal 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;
|
||||
}
|
||||
38
examples/applications/hushmap_add.cpp
Normal file
38
examples/applications/hushmap_add.cpp
Normal 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;
|
||||
}
|
||||
52
examples/applications/idpf_agg.cpp
Normal file
52
examples/applications/idpf_agg.cpp
Normal 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;
|
||||
}
|
||||
49
examples/applications/it_pir3.cpp
Normal file
49
examples/applications/it_pir3.cpp
Normal 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;
|
||||
}
|
||||
60
examples/applications/keyword_pir.cpp
Normal file
60
examples/applications/keyword_pir.cpp
Normal 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;
|
||||
}
|
||||
85
examples/applications/ledger23.cpp
Normal file
85
examples/applications/ledger23.cpp
Normal 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;
|
||||
}
|
||||
98
examples/applications/llama.cpp
Normal file
98
examples/applications/llama.cpp
Normal 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;
|
||||
}
|
||||
94
examples/applications/mastic.cpp
Normal file
94
examples/applications/mastic.cpp
Normal 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;
|
||||
}
|
||||
72
examples/applications/pika.cpp
Normal file
72
examples/applications/pika.cpp
Normal 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;
|
||||
}
|
||||
55
examples/applications/pir3.cpp
Normal file
55
examples/applications/pir3.cpp
Normal 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;
|
||||
}
|
||||
42
examples/applications/pirsona_fetch.cpp
Normal file
42
examples/applications/pirsona_fetch.cpp
Normal 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;
|
||||
}
|
||||
97
examples/applications/prac.cpp
Normal file
97
examples/applications/prac.cpp
Normal 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;
|
||||
}
|
||||
88
examples/applications/prio.cpp
Normal file
88
examples/applications/prio.cpp
Normal 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;
|
||||
}
|
||||
75
examples/applications/psi.cpp
Normal file
75
examples/applications/psi.cpp
Normal 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;
|
||||
}
|
||||
61
examples/applications/range_count.cpp
Normal file
61
examples/applications/range_count.cpp
Normal 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;
|
||||
}
|
||||
79
examples/applications/sabre.cpp
Normal file
79
examples/applications/sabre.cpp
Normal 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;
|
||||
}
|
||||
65
examples/applications/splinter.cpp
Normal file
65
examples/applications/splinter.cpp
Normal 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;
|
||||
}
|
||||
132
examples/applications/subleq.cpp
Normal file
132
examples/applications/subleq.cpp
Normal 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;
|
||||
}
|
||||
69
examples/applications/waldo.cpp
Normal file
69
examples/applications/waldo.cpp
Normal 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;
|
||||
}
|
||||
57
examples/evaluation/defer_eval.cpp
Normal file
57
examples/evaluation/defer_eval.cpp
Normal 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;
|
||||
}
|
||||
44
examples/evaluation/eval_dpf3_cmp_ic.cpp
Normal file
44
examples/evaluation/eval_dpf3_cmp_ic.cpp
Normal 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;
|
||||
}
|
||||
36
examples/evaluation/eval_dpf3_doerner_shelat.cpp
Normal file
36
examples/evaluation/eval_dpf3_doerner_shelat.cpp
Normal 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;
|
||||
}
|
||||
42
examples/evaluation/eval_dpf3_point.cpp
Normal file
42
examples/evaluation/eval_dpf3_point.cpp
Normal 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;
|
||||
}
|
||||
102
examples/evaluation/eval_inner_product.cpp
Normal file
102
examples/evaluation/eval_inner_product.cpp
Normal 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;
|
||||
}
|
||||
58
examples/grotto/dwt_lut.cpp
Normal file
58
examples/grotto/dwt_lut.cpp
Normal 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;
|
||||
}
|
||||
98
examples/grotto/jet_and_ring.cpp
Normal file
98
examples/grotto/jet_and_ring.cpp
Normal 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;
|
||||
}
|
||||
43
examples/grotto/lut_union.cpp
Normal file
43
examples/grotto/lut_union.cpp
Normal 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;
|
||||
}
|
||||
132
examples/grotto/repr_and_twist.cpp
Normal file
132
examples/grotto/repr_and_twist.cpp
Normal 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
131
examples/mwe/chooser.html
Normal 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 <cstdint>
|
||||
#include <iostream>
|
||||
#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 << at << " " << off << "\n";
|
||||
return (at == beta && 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 <cstdint>
|
||||
#include <iostream>
|
||||
#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 << above << " " << below << "\n";
|
||||
return (above == 1 && 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 <cstdint>
|
||||
#include <iostream>
|
||||
#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 << inside << " " << outside << "\n";
|
||||
return (inside == beta && 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 <cstdint>
|
||||
#include <iostream>
|
||||
#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<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;
|
||||
}</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
24
examples/mwe/compare.cpp
Normal 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
28
examples/mwe/interval.cpp
Normal 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
24
examples/mwe/point.cpp
Normal 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
43
examples/mwe/shamir.cpp
Normal 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;
|
||||
}
|
||||
36
examples/mwe/shared_index.cpp
Normal file
36
examples/mwe/shared_index.cpp
Normal 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;
|
||||
}
|
||||
10
examples/output_types/gf2.cpp
Normal file
10
examples/output_types/gf2.cpp
Normal 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";
|
||||
}
|
||||
154
examples/protocol/client_shares.cpp
Normal file
154
examples/protocol/client_shares.cpp
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/launch.hpp"
|
||||
#include "dpf/net/client_link.hpp"
|
||||
#include "dpf/run_log.hpp"
|
||||
|
||||
// A client splits a secret into two additive shares and sends share i to party
|
||||
// i over a client link; the two parties then open the sum over their own
|
||||
// party link. Both links are TLS 1.3; each end logs how it authenticated the
|
||||
// other.
|
||||
//
|
||||
// c++ -std=c++17 -march=native -pthread -I include -I thirdparty \
|
||||
// examples/protocol/client_shares.cpp -lsctp -lssl -lcrypto -o client_shares
|
||||
// ./client_shares # development certificate (logged)
|
||||
// ./dpf_keygen srv.key # prints srv's public key
|
||||
// ./client_shares --server_identity=srv.key --client_pin=<srv public key>
|
||||
// ./client_shares --client_verify=off # accepts any server (logged)
|
||||
//
|
||||
// With --server_identity and no --client_pin the client refuses the server:
|
||||
// clients always verify unless told not to.
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto cfg = dpf::app::run_config::from_env();
|
||||
for (const auto & extra : cfg.apply_args(argc, argv))
|
||||
throw std::invalid_argument("unknown argument " + extra);
|
||||
if (cfg.kind != dpf::net::transport::mux && cfg.kind != dpf::net::transport::parallel)
|
||||
cfg.kind = dpf::net::transport::mux;
|
||||
dpf::app::start_logging(cfg);
|
||||
|
||||
// Each party accepts one client and keeps the share it sends.
|
||||
std::atomic<unsigned short> ports[2] = {{0}, {0}};
|
||||
std::uint64_t shares[2] = {0, 0};
|
||||
std::string errors[2];
|
||||
std::vector<std::thread> parties;
|
||||
for (int p = 0; p < 2; ++p)
|
||||
parties.emplace_back([&, p] {
|
||||
try
|
||||
{
|
||||
asio::io_context io;
|
||||
dpf::net::client_listener l(io, cfg.server, 1, cfg.policy, cfg.limits);
|
||||
ports[p].store(l.listen());
|
||||
auto c = l.accept();
|
||||
bool done = false;
|
||||
std::error_code ec;
|
||||
c.link->async_read(0, &shares[p], 8, [&](const std::error_code & e) {
|
||||
ec = e;
|
||||
done = true;
|
||||
});
|
||||
while (!done)
|
||||
{
|
||||
if (io.stopped())
|
||||
io.restart();
|
||||
io.run_one();
|
||||
}
|
||||
if (ec)
|
||||
throw std::system_error(ec, "reading the client's share");
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
errors[p] = e.what();
|
||||
ports[p].store(1);
|
||||
}
|
||||
});
|
||||
|
||||
// The client: one fresh share per party, each on its own verified link.
|
||||
const std::uint64_t secret = 42;
|
||||
std::random_device rd;
|
||||
const std::uint64_t r = (static_cast<std::uint64_t>(rd()) << 32) | rd();
|
||||
const std::uint64_t mine[2] = {r, secret - r};
|
||||
std::string client_error;
|
||||
for (int p = 0; p < 2 && client_error.empty(); ++p)
|
||||
{
|
||||
while (ports[p].load() == 0)
|
||||
std::this_thread::yield();
|
||||
try
|
||||
{
|
||||
asio::io_context io;
|
||||
auto c = dpf::net::connect_server(io, cfg.host, ports[p].load(), cfg.client,
|
||||
1, cfg.policy, cfg.limits);
|
||||
std::cout << "client -> party " << p << ": " << c.security.protocol << " "
|
||||
<< c.security.cipher << ", server auth=" << c.security.peer_auth
|
||||
<< "\n";
|
||||
bool done = false;
|
||||
c.link->async_write(0, &mine[p], 8, [&](const std::error_code &) {
|
||||
done = true;
|
||||
});
|
||||
while (!done)
|
||||
{
|
||||
if (io.stopped())
|
||||
io.restart();
|
||||
io.run_one();
|
||||
}
|
||||
c.link.reset();
|
||||
io.poll();
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
client_error = e.what();
|
||||
}
|
||||
}
|
||||
if (!client_error.empty())
|
||||
{
|
||||
// Unblock the listeners that are still waiting for this client.
|
||||
for (int p = 0; p < 2; ++p)
|
||||
{
|
||||
std::error_code ec;
|
||||
asio::io_context io;
|
||||
asio::ip::tcp::socket s(io);
|
||||
if (ports[p].load() > 1)
|
||||
s.connect({asio::ip::make_address("127.0.0.1"), ports[p].load()}, ec);
|
||||
}
|
||||
}
|
||||
for (auto & t : parties)
|
||||
t.join();
|
||||
if (!client_error.empty())
|
||||
throw std::runtime_error("client: " + client_error);
|
||||
for (const auto & e : errors)
|
||||
if (!e.empty())
|
||||
throw std::runtime_error("party: " + e);
|
||||
|
||||
// The parties open the sum over their party link.
|
||||
dpf::protocol::composer c0(0), c1(1);
|
||||
auto x0 = c0.input(dpf::protocol::domain::a, 8);
|
||||
auto x1 = c1.input(dpf::protocol::domain::a, 8);
|
||||
auto o0 = c0.exchange(x0);
|
||||
(void)c1.exchange(x1);
|
||||
auto p0 = c0.schedule();
|
||||
auto p1 = c1.schedule();
|
||||
dpf::app::party_values v0(p0.nodes().size()), v1(p1.nodes().size());
|
||||
v0[x0.id].assign(8, 0);
|
||||
v1[x1.id].assign(8, 0);
|
||||
std::memcpy(v0[x0.id].data(), &shares[0], 8);
|
||||
std::memcpy(v1[x1.id].data(), &shares[1], 8);
|
||||
(void)dpf::run_two_party(p0, p1, v0, v1, {}, cfg);
|
||||
std::uint64_t open = 0;
|
||||
std::memcpy(&open, v0[o0.id].data(), 8);
|
||||
std::cout << "parties opened " << open << " (share 0 = " << shares[0] << ")\n";
|
||||
return open == secret ? 0 : 1;
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
std::cerr << "client_shares: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
285
examples/protocol/compose_schedule.cpp
Normal file
285
examples/protocol/compose_schedule.cpp
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/net/stream_array.hpp"
|
||||
#include "dpf/protocol_factory.hpp"
|
||||
|
||||
// Protocol composition schedules (compose.hpp). Records FSS walks, ABY
|
||||
// products, and RSS refreshes on one RoundSink plan — the shapes Express,
|
||||
// Sabre, Pika early-stop, Poplar prefixes, and Duoram scale use by hand.
|
||||
//
|
||||
// The final block drives a composed open on the stream framework
|
||||
// (drive_both_on_streams). The identical schedule also runs over the truly
|
||||
// asynchronous backends (dpf::net::async_stream_array + async_round_sink, or
|
||||
// dpf::async::overlapped_byte_protocol) and, on Linux, over real SCTP
|
||||
// (async_sctp_stream_array). The experiment_bench harness picks the transport
|
||||
// via DPF_TRANSPORT=memory|stream|async|mux|parallel|sctp.
|
||||
//
|
||||
// c++ -std=c++17 -march=native -I include -I thirdparty
|
||||
// examples/protocol/compose_schedule.cpp
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using dpf::protocol::composer;
|
||||
using dpf::protocol::domain;
|
||||
using dpf::protocol::effect;
|
||||
namespace opcodes = dpf::protocol::opcodes;
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
// Express / Sabre: audit rides in the last CW flush (depth exchanges, not
|
||||
// depth+1).
|
||||
{
|
||||
composer naive(0);
|
||||
auto seed = naive.input(domain::fss, 16);
|
||||
auto sketch = naive.input(domain::a, 8);
|
||||
auto leaf = naive.fss_point(seed, 4, 16);
|
||||
auto dep = naive.compute(opcodes::user_base + 1, {leaf, sketch},
|
||||
domain::a, 8);
|
||||
(void)naive.exchange(dep);
|
||||
const auto naive_ex = naive.schedule().exchange_waves();
|
||||
|
||||
composer fused(0);
|
||||
auto seed_f = fused.input(domain::fss, 16);
|
||||
auto sketch_f = fused.input(domain::a, 8);
|
||||
auto wr = fused.fss_point_fused(seed_f, 4, 16, sketch_f);
|
||||
const auto fused_ex = fused.schedule().exchange_waves();
|
||||
if (fused_ex >= naive_ex || fused.domain_of(wr.trailer_open) != domain::a)
|
||||
{
|
||||
std::cerr << "compose fuse\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "express_fuse " << naive_ex << "->" << fused_ex << "\n";
|
||||
}
|
||||
|
||||
// Pika / small-output PIR: BGI Remark 3.4 early-stop drops ν CW rounds.
|
||||
{
|
||||
composer full(0);
|
||||
auto seed = full.input(domain::fss, 16);
|
||||
(void)full.fss_point(seed, 8, 16);
|
||||
composer early(0);
|
||||
auto seed_e = early.input(domain::fss, 16);
|
||||
(void)early.fss_point_early_stop(seed_e, 8, /*early_stop=*/3, 16);
|
||||
const auto a = full.schedule().exchange_waves();
|
||||
const auto b = early.schedule().exchange_waves();
|
||||
if (b != a - 3)
|
||||
{
|
||||
std::cerr << "compose early_stop\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "early_stop " << a << "->" << b << "\n";
|
||||
}
|
||||
|
||||
// Poplar: prefix share after each CW, same exchange-wave depth.
|
||||
{
|
||||
composer c(0);
|
||||
auto seed = c.input(domain::fss, 16);
|
||||
auto wr = c.level_walk_prefixes(seed, 5, 16, /*prefix_bytes=*/8);
|
||||
auto p = c.schedule();
|
||||
if (wr.at_level.size() != 5 || p.exchange_waves() != 5)
|
||||
{
|
||||
std::cerr << "compose prefixes\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "prefixes " << p.exchange_waves() << "\n";
|
||||
}
|
||||
|
||||
// DCF block_width: variable CW slot sizes.
|
||||
{
|
||||
composer c(0);
|
||||
auto seed = c.input(domain::fss, 16);
|
||||
const std::vector<std::size_t> slots = {16, 4, 4, 16};
|
||||
(void)c.level_walk_sized(seed, slots);
|
||||
auto p = c.schedule();
|
||||
if (p.value_bytes_of(p.wave(1).exchanges[0].id) != 4)
|
||||
{
|
||||
std::cerr << "compose sized\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "sized_slots ok\n";
|
||||
}
|
||||
|
||||
// RSS product → neighbor y-exchange → RSS (one round).
|
||||
{
|
||||
composer c(0);
|
||||
auto x = c.input(domain::rss, 16);
|
||||
auto y = c.input(domain::rss, 16);
|
||||
auto z = c.rss_product_replicated(x, y);
|
||||
if (c.domain_of(z) != domain::rss || c.schedule().exchange_waves() != 1)
|
||||
{
|
||||
std::cerr << "compose rss\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "rss_refresh 1\n";
|
||||
}
|
||||
|
||||
// Duoram write-scale: FSS leaf feeds ABY; beaver waits for the leaf wave.
|
||||
{
|
||||
composer c(0);
|
||||
auto seed = c.input(domain::fss, 16);
|
||||
auto leaf = c.fss_point(seed, 4, 16);
|
||||
auto scale = c.input(domain::a, 8);
|
||||
auto scaled = c.aby_product<std::uint64_t>(leaf, scale);
|
||||
auto p = c.schedule();
|
||||
if (p.wave_of(scaled) < p.wave_of(leaf))
|
||||
{
|
||||
std::cerr << "compose duoram_scale\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "duoram_scale wave " << p.wave_of(scaled) << "\n";
|
||||
}
|
||||
|
||||
// Round-aware ABY: sign×linear stays one online round.
|
||||
{
|
||||
composer c(0);
|
||||
auto & s = c.aby<std::uint64_t>();
|
||||
auto sgn = s.input();
|
||||
auto x = s.input();
|
||||
auto a0 = s.input();
|
||||
auto a1 = s.input();
|
||||
auto lin = s(sgn * (a1 * x + a0));
|
||||
if (s.round_of(lin) != 1)
|
||||
{
|
||||
std::cerr << "compose aby_rounds\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "aby_rounds 1\n";
|
||||
}
|
||||
|
||||
// Doerner–Shelat: 5 opens per level; OH adds 80 AND-layers / level.
|
||||
{
|
||||
composer c(0);
|
||||
auto seed = c.input(domain::fss, 16);
|
||||
auto tip = c.level_walk_ds(seed, 2, 16);
|
||||
if (c.schedule().exchange_waves() != 10 || tip.id == seed.id)
|
||||
{
|
||||
std::cerr << "compose ds_walk\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "ds_walk 10\n";
|
||||
composer c_oh(0);
|
||||
auto tip_oh = c_oh.level_walk_ds(c_oh.input(domain::fss, 16), 1, 16,
|
||||
/*oh=*/true);
|
||||
(void)tip_oh;
|
||||
const auto want = dpf::net::compose_ds_slot_bytes(1, 16, true).size();
|
||||
if (c_oh.schedule().exchange_waves() != want)
|
||||
{
|
||||
std::cerr << "compose ds_oh\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "ds_oh " << want << "\n";
|
||||
}
|
||||
|
||||
// Adaptive idpf: one packed L‖R open per step.
|
||||
{
|
||||
composer c(0);
|
||||
auto seed = c.input(domain::fss, 16);
|
||||
auto f = c.begin_adaptive_prefix(seed);
|
||||
f = c.step_adaptive_prefix(f, 16, 8);
|
||||
f = c.retain_adaptive_prefix(f, 0);
|
||||
if (c.schedule().exchange_waves() != 1)
|
||||
{
|
||||
std::cerr << "compose adaptive\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "adaptive 1\n";
|
||||
}
|
||||
|
||||
// default_plan: rounds == exchange_waves (sink-aligned).
|
||||
{
|
||||
composer c(0);
|
||||
auto leaf = c.fss_point(c.input(domain::fss, 16), 4, 16);
|
||||
(void)leaf;
|
||||
auto p = c.default_plan();
|
||||
if (p.rounds() != p.exchange_waves()
|
||||
|| p.rounds() != p.slot_bytes_all().size() || p.rounds() != 4)
|
||||
{
|
||||
std::cerr << "compose default_plan\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "default_plan 4\n";
|
||||
}
|
||||
|
||||
// Multipoint buckets: CW waves pack; answers one open.
|
||||
{
|
||||
composer c(0);
|
||||
auto mr = c.multipoint_fan(2,
|
||||
[&](std::size_t) { return c.input(domain::fss, 16); }, 3, 16, 8);
|
||||
(void)mr;
|
||||
if (c.schedule().exchange_waves() != 4)
|
||||
{
|
||||
std::cerr << "compose multipoint\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "multipoint 4\n";
|
||||
}
|
||||
|
||||
// Multi-lane ABY: independent barriers, one wave.
|
||||
{
|
||||
composer c(0);
|
||||
auto z0 = c.aby_product<std::uint64_t>(c.input(domain::a, 8),
|
||||
c.input(domain::a, 8), 0);
|
||||
auto z1 = c.aby_product<std::uint64_t>(c.input(domain::a, 8),
|
||||
c.input(domain::a, 8), 1);
|
||||
if (c.schedule().exchange_waves() != 1
|
||||
|| c.schedule().effect_count(effect::exchange) != 2
|
||||
|| z0.id == z1.id)
|
||||
{
|
||||
std::cerr << "compose multilane\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "multilane 1\n";
|
||||
}
|
||||
|
||||
// Prepaid defer + rotate: zero online FSS rounds.
|
||||
{
|
||||
composer c(0);
|
||||
auto buf = c.defer_expand(c.input(domain::fss, 16), 4, 16);
|
||||
auto rot = c.rotate_share(buf, 7);
|
||||
if (c.schedule().exchange_waves() != 0 || rot.id == buf.id)
|
||||
{
|
||||
std::cerr << "compose defer\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "defer 0\n";
|
||||
}
|
||||
|
||||
// stream_array: drive a compose open through drive_both_on_streams.
|
||||
{
|
||||
composer c0(0);
|
||||
composer c1(1);
|
||||
auto x0 = c0.input(domain::a, 8);
|
||||
auto x1 = c1.input(domain::a, 8);
|
||||
auto e0 = c0.exchange(x0);
|
||||
auto e1 = c1.exchange(x1);
|
||||
auto p0 = c0.schedule();
|
||||
auto p1 = c1.schedule();
|
||||
std::vector<std::vector<std::uint8_t>> v0(p0.nodes().size()),
|
||||
v1(p1.nodes().size());
|
||||
const std::uint64_t a = 3, b = 5;
|
||||
v0[x0.id].assign(8, 0);
|
||||
v1[x1.id].assign(8, 0);
|
||||
std::memcpy(v0[x0.id].data(), &a, 8);
|
||||
std::memcpy(v1[x1.id].data(), &b, 8);
|
||||
dpf::protocol::drive_both_on_streams(p0, p1, v0, v1);
|
||||
std::uint64_t open0 = 0, open1 = 0;
|
||||
std::memcpy(&open0, v0[e0.id].data(), 8);
|
||||
std::memcpy(&open1, v1[e1.id].data(), 8);
|
||||
if (open0 != 8 || open1 != 8)
|
||||
{
|
||||
std::cerr << "drive_plan_on_streams open\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "drive_plan_on_streams_ok " << open0 << "\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
86
examples/protocol/party_node.cpp
Normal file
86
examples/protocol/party_node.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/launch.hpp"
|
||||
#include "dpf/run_log.hpp"
|
||||
|
||||
// One party of a two- or three-party run, one process per party.
|
||||
//
|
||||
// c++ -std=c++17 -march=native -pthread -I include -I thirdparty \
|
||||
// -DLIBDPF_GIT_REV="\"$(git describe --always --dirty)\"" \
|
||||
// examples/protocol/party_node.cpp -lsctp -lssl -lcrypto -o party_node
|
||||
// ./party_node --party=0 --peers=127.0.0.1:9100,127.0.0.1:9101 &
|
||||
// ./party_node --party=1 --peers=127.0.0.1:9100,127.0.0.1:9101
|
||||
//
|
||||
// Any run_config key works as a flag (--transport=parallel --lanes=2 ...).
|
||||
// Start order does not matter: connects retry until --connect_ms.
|
||||
//
|
||||
// Links are TLS 1.3. With no keys they are encrypted but unauthenticated (and
|
||||
// the log says so). To authenticate, give each party a key and the others'
|
||||
// public keys (dpf_keygen p0.key prints p0's), for example in p0.conf:
|
||||
// identity = p0.key
|
||||
// peer.1 = <p1's public key>
|
||||
// and run ./party_node --config=p0.conf --party=0 --peers=...
|
||||
// The run log goes to stderr at info; --log=file:/tmp/p0.log --log_level=debug
|
||||
// or DPF_LOG / DPF_LOG_LEVEL redirect it (see dpf/run_log.hpp).
|
||||
//
|
||||
// With three peers the protocol adds a dealer-delivered mask: party 2 is the
|
||||
// dealer, and parties 0 and 1 each open (input + mask share).
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto args = dpf::app::parse_node_args(argc, argv);
|
||||
dpf::app::start_logging(args.cfg);
|
||||
const unsigned me = args.party;
|
||||
const bool dealer = args.peers.size() == 3;
|
||||
|
||||
dpf::protocol::composer c(me);
|
||||
auto x = c.input(dpf::protocol::domain::a, 8);
|
||||
dpf::protocol::node opened{};
|
||||
dpf::protocol::node mask{};
|
||||
if (dealer)
|
||||
{
|
||||
auto pad = c.input(dpf::protocol::domain::a, 8);
|
||||
mask = c.dealer_deliver(pad);
|
||||
opened = c.exchange(x);
|
||||
(void)pad;
|
||||
}
|
||||
else
|
||||
opened = c.exchange(x);
|
||||
auto plan = c.schedule();
|
||||
dpf::app::party_values values(plan.nodes().size());
|
||||
for (auto & v : values)
|
||||
v.assign(8, 0);
|
||||
const std::uint64_t mine = me == 0 ? 11 : 31;
|
||||
std::memcpy(values[x.id].data(), &mine, 8);
|
||||
if (dealer && me == 2)
|
||||
{
|
||||
const std::uint64_t pad = 7;
|
||||
std::memcpy(values[plan.inputs_of(mask.id)[0]].data(), &pad, 8);
|
||||
}
|
||||
|
||||
const auto wire = dpf::app::run_node(args, plan, values);
|
||||
std::uint64_t got = 0;
|
||||
std::memcpy(&got, values[opened.id].data(), 8);
|
||||
std::cout << "party " << me << " open=" << got;
|
||||
if (dealer && me < 2)
|
||||
{
|
||||
std::uint64_t m = 0;
|
||||
std::memcpy(&m, values[mask.id].data(), 8);
|
||||
std::cout << " mask=" << m;
|
||||
}
|
||||
std::cout << " wire_out=" << wire.bytes_out << " wire_in=" << wire.bytes_in
|
||||
<< "\n";
|
||||
return (me == 2 || got == 42) ? 0 : 1;
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
DPF_LOG(error, "node.failed").kv("what", e.what());
|
||||
std::cerr << "party_node: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
99
examples/protocol/share_runtime.cpp
Normal file
99
examples/protocol/share_runtime.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/// @file examples/protocol/share_runtime.cpp
|
||||
/// @brief Smoke demo: stream_array dealer/peer gadgets and clear sanity checks.
|
||||
/// @details Also drives a composed open on the stream framework
|
||||
/// (`drive_both_on_streams`). The same protocol runs unchanged over the
|
||||
/// truly asynchronous backends (`dpf::net::async_stream_array` +
|
||||
/// `dpf::async::overlapped_byte_protocol`, or `async_round_sink`), and
|
||||
/// on Linux over real SCTP (`async_sctp_stream_array`, index i -> SCTP
|
||||
/// stream i). The performance harness (experiment_bench) selects any of
|
||||
/// these with `DPF_TRANSPORT=memory|stream|async|mux|parallel|sctp`.
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/compose.hpp"
|
||||
#include "dpf/factory_gadgets.hpp"
|
||||
#include "dpf/net/stream_array.hpp"
|
||||
#include "dpf/protocol_factory.hpp"
|
||||
#include "dpf/rss_seed.hpp"
|
||||
|
||||
int main()
|
||||
{
|
||||
auto bundle = dpf::rss::sample_seed_bundle();
|
||||
auto z0 = dpf::rss::zero_share<std::uint64_t>(
|
||||
dpf::rss::party_seeds::from_bundle(bundle, 0), 0);
|
||||
auto z1 = dpf::rss::zero_share<std::uint64_t>(
|
||||
dpf::rss::party_seeds::from_bundle(bundle, 1), 0);
|
||||
auto z2 = dpf::rss::zero_share<std::uint64_t>(
|
||||
dpf::rss::party_seeds::from_bundle(bundle, 2), 0);
|
||||
std::cout << "rss_zero_sum=" << (z0 + z1 + z2) << "\n";
|
||||
|
||||
// Dealer: one ring triple per party; peer: d and e opens.
|
||||
auto peer = dpf::net::make_memory_stream_pair(2);
|
||||
auto d0 = dpf::net::make_memory_stream_pair(1);
|
||||
auto d1 = dpf::net::make_memory_stream_pair(1);
|
||||
constexpr std::uint16_t limb = 8;
|
||||
dpf::factory::make_dealer(d0.first, d1.first, 1, [limb] {
|
||||
return dpf::factory::deal_ring_triple(limb);
|
||||
});
|
||||
|
||||
std::uint64_t prod0 = 0, prod1 = 0;
|
||||
std::thread t0([&] {
|
||||
prod0 = dpf::factory::beaver_mul_online(0, 6, 7, peer.first, d0.second,
|
||||
0, 0, 1, limb);
|
||||
});
|
||||
std::thread t1([&] {
|
||||
prod1 = dpf::factory::beaver_mul_online(1, 0, 0, peer.second, d1.second,
|
||||
0, 0, 1, limb);
|
||||
});
|
||||
t0.join();
|
||||
t1.join();
|
||||
std::cout << "beaver_mul_open=" << (prod0 + prod1) << "\n";
|
||||
|
||||
// GMW AND 1∧1 via make_protocol_factory.
|
||||
auto and_peer = dpf::net::make_memory_stream_pair(1);
|
||||
auto ad0 = dpf::net::make_memory_stream_pair(1);
|
||||
auto ad1 = dpf::net::make_memory_stream_pair(1);
|
||||
dpf::factory::make_dealer(ad0.first, ad1.first, 1,
|
||||
dpf::factory::make_gmw_and_dealer_functor());
|
||||
std::uint8_t z0b = 0, z1b = 0;
|
||||
std::thread a0([&] {
|
||||
z0b = dpf::factory::gmw_and_online(0, 1, 1, and_peer.first, ad0.second);
|
||||
});
|
||||
std::thread a1([&] {
|
||||
z1b = dpf::factory::gmw_and_online(1, 0, 0, and_peer.second, ad1.second);
|
||||
});
|
||||
a0.join();
|
||||
a1.join();
|
||||
std::cout << "gmw_and_xor=" << static_cast<unsigned>(z0b ^ z1b) << "\n";
|
||||
|
||||
// Compose an open and drive both parties on the stream framework. This is
|
||||
// the same schedule the async backends run; here it uses in-process memory
|
||||
// stream arrays via drive_both_on_streams.
|
||||
{
|
||||
using dpf::protocol::domain;
|
||||
dpf::protocol::composer comp0(0), comp1(1);
|
||||
auto a = comp0.input(domain::a, 8);
|
||||
auto b = comp1.input(domain::a, 8);
|
||||
auto oa = comp0.exchange(a);
|
||||
auto ob = comp1.exchange(b);
|
||||
auto p0 = comp0.schedule();
|
||||
auto p1 = comp1.schedule();
|
||||
std::vector<std::vector<std::uint8_t>> va(p0.nodes().size()),
|
||||
vb(p1.nodes().size());
|
||||
const std::uint64_t xa = 17, xb = 25;
|
||||
va[a.id].assign(8, 0);
|
||||
vb[b.id].assign(8, 0);
|
||||
std::memcpy(va[a.id].data(), &xa, 8);
|
||||
std::memcpy(vb[b.id].data(), &xb, 8);
|
||||
dpf::protocol::drive_both_on_streams(p0, p1, va, vb);
|
||||
std::uint64_t open0 = 0;
|
||||
std::memcpy(&open0, va[oa.id].data(), 8);
|
||||
(void)ob;
|
||||
std::cout << "compose_open_on_streams=" << open0 << "\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
83
examples/protocol/stream_app_smoke.cpp
Normal file
83
examples/protocol/stream_app_smoke.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/// @file examples/protocol/stream_app_smoke.cpp
|
||||
/// @brief Thin app-runtime smoke: prep files or memory dealer + compose on streams.
|
||||
/// @details Drives a composed open via `drive_both_on_streams` (the stream
|
||||
/// framework). The same schedule runs unchanged over the event-driven
|
||||
/// backends (`dpf::net::async_stream_array` + `async_round_sink` /
|
||||
/// `dpf::async::overlapped_byte_protocol`) and, on Linux, over real
|
||||
/// SCTP (`async_sctp_stream_array`). The `experiment_bench` harness
|
||||
/// selects the transport with
|
||||
/// `DPF_TRANSPORT=memory|stream|async|mux|parallel|sctp`.
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/app_runtime.hpp"
|
||||
#include "dpf/compose.hpp"
|
||||
#include "dpf/prep_source.hpp"
|
||||
#include "dpf/protocol_roles.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void put_raw(std::vector<std::vector<std::uint8_t>> & values,
|
||||
dpf::protocol::node n, const void * src, std::size_t nbyte)
|
||||
{
|
||||
values[n.id].assign(nbyte, 0);
|
||||
std::memcpy(values[n.id].data(), src, nbyte);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
// Prep: file dealer round-trip (roles helper + app_runtime reader).
|
||||
{
|
||||
const std::string base = "/tmp/libdpf_stream_app_smoke_prep";
|
||||
dpf::prep::demand d{};
|
||||
d.ring_triples = 1;
|
||||
dpf::roles::dealer_write_files(base, d);
|
||||
auto c0 = dpf::app::open_file_prep_cursor(base, 0);
|
||||
auto c1 = dpf::app::open_file_prep_cursor(base, 1);
|
||||
if (c0.limb() != 8 || c1.limb() != 8)
|
||||
{
|
||||
std::cerr << "prep cursor\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Online: memory dealer + single-wave exchange plan on stream arrays.
|
||||
dpf::protocol::composer c0(0);
|
||||
dpf::protocol::composer c1(1);
|
||||
auto x0 = c0.input(dpf::protocol::domain::a, 8);
|
||||
auto x1 = c1.input(dpf::protocol::domain::a, 8);
|
||||
auto e0 = c0.exchange(x0);
|
||||
auto e1 = c1.exchange(x1);
|
||||
auto p0 = c0.schedule();
|
||||
auto p1 = c1.schedule();
|
||||
|
||||
dpf::prep::demand d{};
|
||||
d.ring_triples = 1;
|
||||
auto [d0, d1] = dpf::prep::deal_memory_pair(d);
|
||||
(void)d0;
|
||||
(void)d1;
|
||||
|
||||
std::vector<std::vector<std::uint8_t>> v0(p0.nodes().size());
|
||||
std::vector<std::vector<std::uint8_t>> v1(p1.nodes().size());
|
||||
const std::uint64_t a = 5, b = 9;
|
||||
put_raw(v0, x0, &a, 8);
|
||||
put_raw(v1, x1, &b, 8);
|
||||
dpf::protocol::drive_both_on_streams(p0, p1, v0, v1);
|
||||
|
||||
std::uint64_t open0 = 0, open1 = 0;
|
||||
std::memcpy(&open0, v0[e0.id].data(), 8);
|
||||
std::memcpy(&open1, v1[e1.id].data(), 8);
|
||||
if (open0 != 14u || open1 != 14u)
|
||||
{
|
||||
std::cerr << "exchange " << open0 << " " << open1 << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "ok\n";
|
||||
return 0;
|
||||
}
|
||||
145
examples/protocol/stream_dpf3_smoke.cpp
Normal file
145
examples/protocol/stream_dpf3_smoke.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/// @file examples/protocol/stream_dpf3_smoke.cpp
|
||||
/// @brief Local DPF3 eval plus trio-shaped stream edges (peer / rss_next / dealer).
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/net/stream_array.hpp"
|
||||
#include "dpf/net/stream_mesh.hpp"
|
||||
#include "dpf/protocol_factory.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct trio_edge_ping
|
||||
{
|
||||
std::uint32_t from = 0;
|
||||
std::uint32_t to = 0;
|
||||
std::uint64_t nonce = 0;
|
||||
};
|
||||
|
||||
struct trio_party_streams
|
||||
{
|
||||
dpf::net::memory_stream_array & peer;
|
||||
dpf::net::memory_stream_array & rss_next;
|
||||
dpf::net::memory_stream_array & dealer;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void exchange_pod(dpf::net::memory_stream_array & link, std::size_t stream,
|
||||
const T & mine, T & theirs)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "pod");
|
||||
link.write(stream, &mine, sizeof(T));
|
||||
link.flush(stream);
|
||||
link.read(stream, &theirs, sizeof(T));
|
||||
}
|
||||
|
||||
void ping_edge(trio_party_streams s, unsigned me, unsigned peer_id,
|
||||
std::uint64_t nonce, std::size_t stream_idx)
|
||||
{
|
||||
trio_edge_ping mine{me, peer_id, nonce};
|
||||
trio_edge_ping theirs{};
|
||||
exchange_pod(s.peer, stream_idx, mine, theirs);
|
||||
if (theirs.from != peer_id || theirs.to != me)
|
||||
throw std::runtime_error("stream_dpf3_smoke: peer edge");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 42;
|
||||
const dpf::fp61 beta{7};
|
||||
|
||||
auto [k1, k2, k3] = dpf::make_dpf3(alpha, beta);
|
||||
const dpf::fp61 y1 = dpf::eval_point(k1, alpha);
|
||||
const dpf::fp61 y2 = dpf::eval_point(k2, alpha);
|
||||
const dpf::fp61 y3 = dpf::eval_point(k3, alpha);
|
||||
const dpf::fp61 opened = dpf::reconstruct(
|
||||
dpf::as_share(k1, y1), dpf::as_share(k2, y2), dpf::as_share(k3, y3));
|
||||
if (opened != beta)
|
||||
{
|
||||
std::cerr << "dpf3 local eval\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Dealer delivers a keyed marker on stream 0 to each evaluator.
|
||||
auto dealer0 = dpf::net::make_memory_stream_pair(1);
|
||||
auto dealer1 = dpf::net::make_memory_stream_pair(1);
|
||||
struct key_delivery
|
||||
{
|
||||
std::uint32_t party = 0;
|
||||
std::uint64_t beta_raw = 0;
|
||||
};
|
||||
key_delivery m0{0, beta.raw()}, m1{1, beta.raw()};
|
||||
dealer0.first.write(0, &m0, sizeof(m0));
|
||||
dealer0.first.flush(0);
|
||||
dealer1.first.write(0, &m1, sizeof(m1));
|
||||
dealer1.first.flush(0);
|
||||
key_delivery got0{}, got1{};
|
||||
dealer0.second.read(0, &got0, sizeof(got0));
|
||||
dealer1.second.read(0, &got1, sizeof(got1));
|
||||
if (got0.beta_raw != beta.raw() || got1.beta_raw != beta.raw())
|
||||
{
|
||||
std::cerr << "dealer stream delivery\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Trio-shaped edges on a 3-party clique (p2 = dealer): peer + rss_next + dealer.
|
||||
constexpr std::size_t k_peer = 0;
|
||||
constexpr std::size_t k_rss = 1;
|
||||
auto clique = dpf::net::make_memory_stream_clique(3, 2);
|
||||
std::exception_ptr err;
|
||||
std::thread t0([&] {
|
||||
try
|
||||
{
|
||||
trio_party_streams s{clique.end(0, 1), clique.end(0, 1), clique.end(0, 2)};
|
||||
ping_edge(s, 0, 1, 11, k_peer);
|
||||
const trio_edge_ping rss_out{0, 1, 99};
|
||||
s.rss_next.write(k_rss, &rss_out, sizeof(rss_out));
|
||||
s.rss_next.flush(k_rss);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
err = std::current_exception();
|
||||
}
|
||||
});
|
||||
std::thread t1([&] {
|
||||
try
|
||||
{
|
||||
trio_party_streams s{clique.end(1, 0), clique.end(1, 0), clique.end(1, 2)};
|
||||
ping_edge(s, 1, 0, 22, k_peer);
|
||||
trio_edge_ping rss_in{};
|
||||
s.rss_next.read(k_rss, &rss_in, sizeof(rss_in));
|
||||
if (rss_in.from != 0u)
|
||||
throw std::runtime_error("stream_dpf3_smoke: rss_next");
|
||||
const trio_edge_ping dealer_out{1, 2, 88};
|
||||
s.dealer.write(k_rss, &dealer_out, sizeof(dealer_out));
|
||||
s.dealer.flush(k_rss);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
err = std::current_exception();
|
||||
}
|
||||
});
|
||||
t0.join();
|
||||
t1.join();
|
||||
if (err)
|
||||
std::rethrow_exception(err);
|
||||
trio_edge_ping dealer_in{};
|
||||
clique.end(2, 1).read(k_rss, &dealer_in, sizeof(dealer_in));
|
||||
if (dealer_in.from != 1u || dealer_in.to != 2u)
|
||||
{
|
||||
std::cerr << "dealer edge\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "stream_trio_ok\n";
|
||||
return 0;
|
||||
}
|
||||
64
examples/protocol/two_party_exchange.cpp
Normal file
64
examples/protocol/two_party_exchange.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/launch.hpp"
|
||||
#include "dpf/run_log.hpp"
|
||||
|
||||
// Two-party open. Every run choice is a flag:
|
||||
//
|
||||
// c++ -std=c++17 -march=native -pthread -I include -I thirdparty \
|
||||
// examples/protocol/two_party_exchange.cpp -lsctp
|
||||
// ./a.out # in-process async memory
|
||||
// ./a.out --transport=mux --lanes=1 # TCP mux on localhost
|
||||
// ./a.out --transport=parallel --window=65536
|
||||
//
|
||||
// For separate processes, see party_node.cpp.
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto cfg = dpf::app::run_config::from_env();
|
||||
for (const auto & extra : cfg.apply_args(argc, argv))
|
||||
throw std::invalid_argument("unknown argument " + extra);
|
||||
dpf::app::start_logging(cfg);
|
||||
|
||||
dpf::protocol::composer c0(0);
|
||||
dpf::protocol::composer c1(1);
|
||||
auto x0 = c0.input(dpf::protocol::domain::a, 8);
|
||||
auto x1 = c1.input(dpf::protocol::domain::a, 8);
|
||||
auto o0 = c0.exchange(x0);
|
||||
(void)c1.exchange(x1);
|
||||
auto p0 = c0.schedule();
|
||||
auto p1 = c1.schedule();
|
||||
// One 8-byte input per instance; every instance opens to 42.
|
||||
dpf::app::party_values v0(p0.nodes().size()), v1(p1.nodes().size());
|
||||
v0[x0.id].assign(8 * cfg.instances, 0);
|
||||
v1[x1.id].assign(8 * cfg.instances, 0);
|
||||
for (std::size_t i = 0; i < cfg.instances; ++i)
|
||||
{
|
||||
const std::uint64_t a = 11 + i, b = 31 - i;
|
||||
std::memcpy(v0[x0.id].data() + 8 * i, &a, 8);
|
||||
std::memcpy(v1[x1.id].data() + 8 * i, &b, 8);
|
||||
}
|
||||
|
||||
const auto r = dpf::run_two_party(p0, p1, v0, v1, {}, cfg);
|
||||
bool all = true;
|
||||
std::uint64_t open = 0;
|
||||
for (std::size_t i = 0; i < cfg.instances; ++i)
|
||||
{
|
||||
std::memcpy(&open, v0[o0.id].data() + 8 * i, 8);
|
||||
all = all && open == 42;
|
||||
}
|
||||
std::cout << "two_party_exchange " << (all ? 42 : open) << " "
|
||||
<< cfg.summary() << " wire_out=" << r.wire[0].bytes_out << "\n";
|
||||
return all ? 0 : 1;
|
||||
}
|
||||
catch (const std::exception & e)
|
||||
{
|
||||
std::cerr << "two_party_exchange: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
42
examples/tools/dpf_keygen.cpp
Normal file
42
examples/tools/dpf_keygen.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue