libdpf/examples/applications/prio.cpp
Ryan Henry 0d22946a0e 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>
2026-09-28 05:59:19 -06:00

88 lines
2.9 KiB
C++

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