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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue