libdpf/party/flows_recent.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

1654 lines
62 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// @file party/flows_recent.cpp
/// @brief (2+1) flows for the newest DPF and Grotto surfaces.
/// @details Amalgamated into run.cpp (do not compile as a second TU).
/// Full uint8 domains, and checks that a corrupted proof, seed,
/// correction word, MAC tag, or sketch fails closed.
#include "cases.hpp"
#include "dist_dpf3.hpp"
#include "dist_ds.hpp"
#include "flow_util.hpp"
#include "key_io.hpp"
#include "registry.hpp"
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <type_traits>
#include <vector>
#include "simde/simde/x86/avx2.h"
#include "dpf/blocked_dcf.hpp"
#include "dpf/dcf.hpp"
#include "dpf/dpf3.hpp"
#include "dpf/dpf3_cmp.hpp"
#include "dpf/dpf3_ds.hpp"
#include "dpf/dpf3_multipoint.hpp"
#include "dpf/eval_full.hpp"
#include "dpf/eval_point.hpp"
#include "dpf/fp61.hpp"
#include "dpf/geneval.hpp"
#include "dpf/interval.hpp"
#include "dpf/multipoint.hpp"
#include "dpf/prg_aes_ccr.hpp"
#include "dpf/shamir3.hpp"
#include "dpf/verifiable.hpp"
#include "grotto/closed_form.hpp"
#include "grotto/exact_steps.hpp"
#include "grotto/offset_poly.hpp"
#include "grotto/offset_jet.hpp"
#include "grotto/offset_repr.hpp"
#include "grotto/offset_twist.hpp"
#include "grotto/ring_switch.hpp"
#include "grotto/residue.hpp"
namespace dpf
{
namespace party
{
namespace recent
{
using util::open_additive;
using util::open_and_sketch;
using util::open_subtractive;
using util::require;
using util::role;
using util::share_bits;
using util::trio;
using u64 = std::uint64_t;
std::vector<u64> exchange_u64(trio & net, role self, const std::vector<u64> & mine)
{
role peer = self == role::p0 ? role::p1 : role::p0;
if (self == role::p0)
{
net.to(peer).send_vec(mine);
return net.to(peer).recv_vec<u64>();
}
auto theirs = net.to(peer).recv_vec<u64>();
net.to(peer).send_vec(mine);
return theirs;
}
std::vector<std::uint8_t> exchange_u8(trio & net, role self,
const std::vector<std::uint8_t> & mine)
{
role peer = self == role::p0 ? role::p1 : role::p0;
if (self == role::p0)
{
net.to(peer).send_vec(mine);
return net.to(peer).recv_vec<std::uint8_t>();
}
auto theirs = net.to(peer).recv_vec<std::uint8_t>();
net.to(peer).send_vec(mine);
return theirs;
}
void send_proofs(net::channel & c, const std::vector<proof_token> & v)
{
c.send_bytes(net::msg::proof_token,
reinterpret_cast<const std::uint8_t *>(v.data()),
v.size() * sizeof(proof_token));
}
std::vector<proof_token> recv_proofs(net::channel & c, std::size_t n)
{
auto body = c.recv_bytes(net::msg::proof_token);
require(body.size() == n * sizeof(proof_token), "proof bytes");
std::vector<proof_token> out(n);
std::memcpy(out.data(), body.data(), body.size());
return out;
}
std::vector<proof_token> exchange_proofs(trio & net, role self,
const std::vector<proof_token> & mine)
{
role peer = self == role::p0 ? role::p1 : role::p0;
if (self == role::p0)
{
send_proofs(net.to(peer), mine);
return recv_proofs(net.to(peer), mine.size());
}
auto theirs = recv_proofs(net.to(peer), mine.size());
send_proofs(net.to(peer), mine);
return theirs;
}
proof_token exchange_proof(trio & net, role self, proof_token mine)
{
std::vector<proof_token> one{mine};
return exchange_proofs(net, self, one)[0];
}
template <typename Key>
void flip_seeds(Key & key)
{
using arr = typename std::decay_t<Key>::correction_seeds_array;
for (auto & cs : const_cast<arr &>(key.correction_seeds()))
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
}
template <typename Key>
void flip_words(Key & key)
{
using arr = typename std::decay_t<Key>::correction_words_array;
for (auto & word : const_cast<arr &>(key.correction_words()))
word = simde_mm_xor_si128(word, simde_mm_set1_epi8(0x5a));
}
int recent_half_tree_domain(role self, trio & net)
{
using Input = std::uint8_t;
using Ht = prg::aes128_ccr;
const Input alpha = 0;
const Input x0 = 0x37;
const Input x1 = static_cast<Input>(alpha ^ x0);
const u64 beta = 0x1111;
auto on = [&](const auto & key) {
std::vector<u64> shares(256);
std::vector<proof_token> pis(256);
for (unsigned x = 0; x < 256; ++x)
{
shares[x] = share_bits(*eval_point(
key, static_cast<Input>(x), prove(pis[x])));
}
auto peer_s = exchange_u64(net, self, shares);
auto peer_p = exchange_proofs(net, self, pis);
if (self == role::p0)
{
for (unsigned x = 0; x < 256; ++x)
{
require(shares[x] - peer_s[x]
== (x == alpha ? beta : 0u),
"half-tree domain value");
require(verify(pis[x], peer_p[x]),
"half-tree domain proof");
}
}
};
require_tree_prefix(dist_with_point_key<Ht, Ht, true>(net, self, x0, x1, beta, on, on),
self, verifiable_tree_prefix<Ht, Ht, u64>(x0, x1));
return 0;
}
int recent_half_tree_seed_tamper(role self, trio & net)
{
using Input = std::uint8_t;
using Ht = prg::aes128_ccr;
const Input alpha = 7;
const Input x0 = 0x25;
const Input x1 = static_cast<Input>(alpha ^ x0);
const u64 beta = 9;
auto on = [&](auto key) {
if (self == role::p0)
flip_seeds(key);
std::vector<proof_token> pis(256);
for (unsigned x = 0; x < 256; ++x)
{
proof_token pi{};
(void)*eval_point(key, static_cast<Input>(x), prove(pi));
pis[x] = pi;
}
auto peer = exchange_proofs(net, self, pis);
if (self == role::p0)
{
int failed = 0;
for (unsigned x = 0; x < 256; ++x)
if (!verify(pis[x], peer[x]))
++failed;
require(failed > 0, "seed tamper invisible");
}
};
require_tree_prefix(dist_with_point_key<Ht, Ht, true>(net, self, x0, x1, beta, on, on),
self, verifiable_tree_prefix<Ht, Ht, u64>(x0, x1));
return 0;
}
int recent_word_tamper(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x2a;
const Input x0 = 0x10;
const Input x1 = static_cast<Input>(alpha ^ x0);
const u64 beta = 5;
auto on = [&](auto key) {
if (self == role::p0)
flip_words(key);
std::vector<u64> shares(256);
auto [bufs, iters] = eval_full(key);
(void)bufs;
auto it = std::begin(iters);
for (unsigned x = 0; x < 256; ++x, ++it)
shares[x] = share_bits(*it);
auto peer = exchange_u64(net, self, shares);
if (self == role::p0)
{
int failed = 0;
for (unsigned x = 0; x < 256; ++x)
{
const u64 expect = x == alpha ? beta : 0u;
if (shares[x] - peer[x] != expect)
++failed;
}
require(failed > 0, "word tamper invisible");
}
};
require_tree_prefix(dist_with_point_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, beta, on, on),
self, verifiable_tree_prefix<prg::aes128, prg::aes128, u64>(x0, x1));
return 0;
}
int recent_cmp_domain(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x40;
const Input x0 = 0x19;
const Input x1 = static_cast<Input>(alpha ^ x0);
auto on = [&](const auto & key) {
const u64 mask = key.cmp().mask;
std::vector<u64> shares(256);
std::vector<proof_token> pis(256);
for (unsigned x = 0; x < 256; ++x)
{
shares[x] = share_bits(eval_point(
cmp, key, static_cast<Input>(x), prove(pis[x])));
}
auto peer_s = exchange_u64(net, self, shares);
auto peer_p = exchange_proofs(net, self, pis);
if (self == role::p0)
{
for (unsigned x = 0; x < 256; ++x)
{
require(((shares[x] + peer_s[x]) & mask)
== (x < alpha ? 1u : 0u),
"cmp domain value");
require(verify(pis[x], peer_p[x]), "cmp domain proof");
}
}
};
dist_with_cmp_key(net, self, x0, x1, lt(u64{1}), on, on, verifiable{});
return 0;
}
int recent_blocked_domain(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x2a;
const Input x0 = 0x11;
const Input x1 = static_cast<Input>(alpha ^ x0);
auto on = [&](const auto & key) {
const u64 mask = key.cmp().mask;
std::vector<u64> shares(256);
std::vector<proof_token> pis(256);
for (unsigned x = 0; x < 256; ++x)
{
shares[x] = share_bits(eval_point(
cmp, key, static_cast<Input>(x), prove(pis[x])));
}
auto peer_s = exchange_u64(net, self, shares);
auto peer_p = exchange_proofs(net, self, pis);
if (self == role::p0)
{
for (unsigned x = 0; x < 256; ++x)
{
require(((shares[x] + peer_s[x]) & mask)
== (x < alpha ? 1u : 0u),
"blocked value");
require(verify(pis[x], peer_p[x]), "blocked proof");
}
}
};
dist_with_cmp_key(net, self, x0, x1,
block_width<4>(lt(u64{1})), on, on, verifiable{});
return 0;
}
int recent_multipoint_domain(role self, trio & net)
{
using Input = std::uint8_t;
const std::vector<Input> alphas{1, 9, 40, 255};
const std::vector<u64> betas{7, 11, 3, 1};
if (self == role::p2)
{
auto keys = make_multipoint(alphas, betas, verifiable{});
std::vector<u64> s0(256), s1(256);
std::vector<proof_token> p0(256), p1(256);
for (unsigned x = 0; x < 256; ++x)
{
const Input q = static_cast<Input>(x);
s0[x] = share_bits(eval_multipoint(keys.first, q, prove(p0[x])));
s1[x] = share_bits(eval_multipoint(keys.second, q, prove(p1[x])));
}
net.to(role::p0).send_vec(s0);
net.to(role::p1).send_vec(s1);
send_proofs(net.to(role::p0), p0);
send_proofs(net.to(role::p1), p1);
require(!keys.first.buckets.empty(), "multipoint buckets");
for (auto & bucket : keys.first.buckets)
flip_seeds(bucket);
proof_token a0{}, a1{};
audit_multipoint(keys.first, prove(a0));
audit_multipoint(keys.second, prove(a1));
require(!verify(a0, a1), "dealer audit");
send_proofs(net.to(role::p0), std::vector<proof_token>{a0});
send_proofs(net.to(role::p1), std::vector<proof_token>{a1});
return 0;
}
auto shares = net.to(role::p2).recv_vec<u64>();
auto proofs = recv_proofs(net.to(role::p2), 256);
auto peer_s = exchange_u64(net, self, shares);
auto peer_p = exchange_proofs(net, self, proofs);
if (self == role::p0)
{
for (unsigned x = 0; x < 256; ++x)
{
u64 want = 0;
for (std::size_t i = 0; i < alphas.size(); ++i)
if (alphas[i] == static_cast<Input>(x))
want = betas[i];
require(shares[x] - peer_s[x] == want, "multipoint value");
require(verify(proofs[x], peer_p[x]), "multipoint proof");
}
}
auto mine_audit = recv_proofs(net.to(role::p2), 1);
auto peer_audit = exchange_proofs(net, self, mine_audit);
if (self == role::p0)
require(!verify(mine_audit[0], peer_audit[0]), "bucket seed tamper");
return 0;
}
int recent_fp61_mac(role self, trio & net)
{
const fp61 y{20};
const fp61 scale{2};
if (self == role::p2)
{
auto key = sample_mac_key<fp61>();
auto shares = mac_share_value(y, key);
std::vector<u64> a{
shares.first.value.raw(), shares.first.tag.raw(), key.delta.raw()};
std::vector<u64> b{
shares.second.value.raw(), shares.second.tag.raw(), key.delta.raw()};
net.to(role::p0).send_vec(a);
net.to(role::p1).send_vec(b);
return 0;
}
auto mine = net.to(role::p2).recv_vec<u64>();
require(mine.size() == 3u, "mac wire");
mac_share<fp61> local{fp61{mine[0]}, fp61{mine[1]}};
mac_key<fp61> key{fp61{mine[2]}};
std::vector<u64> body{local.value.raw(), local.tag.raw()};
auto peer_body = exchange_u64(net, self, body);
mac_share<fp61> peer{fp61{peer_body[0]}, fp61{peer_body[1]}};
if (self == role::p0)
{
require(mac_verify(local, peer, key), "honest mac");
auto opened = local.value + peer.value;
require(opened == y, "opened y");
auto scaled0 = mac_scale(local, scale);
auto scaled1 = mac_scale(peer, scale);
require(mac_verify(scaled0, scaled1, key), "scaled mac");
require(scaled0.value + scaled1.value == fp61{40}, "scaled open");
}
if (self == role::p0)
local.value = local.value + fp61{1};
body = {local.value.raw(), local.tag.raw()};
peer_body = exchange_u64(net, self, body);
peer = mac_share<fp61>{fp61{peer_body[0]}, fp61{peer_body[1]}};
if (self == role::p0)
require(!mac_verify(local, peer, key), "value tamper");
local = mac_share<fp61>{fp61{mine[0]}, fp61{mine[1]}};
if (self == role::p0)
local.tag = local.tag + fp61{1};
body = {local.value.raw(), local.tag.raw()};
peer_body = exchange_u64(net, self, body);
peer = mac_share<fp61>{fp61{peer_body[0]}, fp61{peer_body[1]}};
if (self == role::p0)
require(!mac_verify(local, peer, key), "tag tamper");
return 0;
}
int recent_offset_poly(role self, trio & net)
{
const std::uint8_t center = 2;
const std::size_t degree = 2;
const std::uint8_t eta = 5;
if (self == role::p2)
{
auto mat = grotto::make_offset_poly_keys<std::uint8_t>(center, degree, verifiable{});
const std::vector<std::uint64_t> coeff{1, 0, 3};
const std::vector<std::uint8_t> knots{0};
std::vector<proof_token> t0(degree + 1), t1(degree + 1);
const u64 s0 = grotto::offset_poly_eval<0>(mat, knots, {coeff}, eta, t0.data());
const u64 s1 = grotto::offset_poly_eval<1>(mat, knots, {coeff}, eta, t1.data());
const u64 clear = grotto::offset_poly_clear<std::uint8_t>(center, knots, {coeff}, eta);
net.to(role::p0).send(net::msg::ring_vector, s0);
net.to(role::p1).send(net::msg::ring_vector, s1);
net.to(role::p0).send(net::msg::ring_vector, clear);
net.to(role::p1).send(net::msg::ring_vector, clear);
send_proofs(net.to(role::p0), t0);
send_proofs(net.to(role::p1), t1);
t0[1][0] = simde_mm_xor_si128(t0[1][0], simde_mm_set1_epi8(1));
send_proofs(net.to(role::p0), t0);
send_proofs(net.to(role::p1), t1);
return 0;
}
const u64 mine = net.to(role::p2).recv<u64>(net::msg::ring_vector);
const u64 clear = net.to(role::p2).recv<u64>(net::msg::ring_vector);
auto proofs = recv_proofs(net.to(role::p2), degree + 1);
const u64 peer = open_additive(net, self, mine);
auto peer_p = exchange_proofs(net, self, proofs);
if (self == role::p0)
{
require(peer == clear, "offset poly");
for (std::size_t m = 0; m <= degree; ++m)
require(verify(proofs[m], peer_p[m]), "offset proof");
}
auto bad = recv_proofs(net.to(role::p2), degree + 1);
auto bad_peer = exchange_proofs(net, self, bad);
if (self == role::p0)
{
require(verify(bad[0], bad_peer[0]), "untampered power");
require(!verify(bad[1], bad_peer[1]), "tampered power");
}
return 0;
}
int recent_offset_jet(role self, trio & net)
{
const std::uint8_t center = 9;
const std::size_t degree = 2;
const std::uint8_t eta = 4;
if (self == role::p2)
{
auto mat = grotto::make_offset_jet_keys<std::uint8_t>(center, degree, verifiable{});
const std::vector<std::uint64_t> coeff{2, 3, 1};
const std::vector<std::uint8_t> knots{0};
std::vector<proof_token> t0(degree + 1), t1(degree + 1);
const u64 s0 = grotto::offset_jet_eval<0>(mat, knots, coeff, eta, t0.data());
const u64 s1 = grotto::offset_jet_eval<1>(mat, knots, coeff, eta, t1.data());
const u64 clear = grotto::offset_jet_clear<std::uint8_t>(center, knots, coeff, eta);
net.to(role::p0).send(net::msg::ring_vector, s0);
net.to(role::p1).send(net::msg::ring_vector, s1);
net.to(role::p0).send(net::msg::ring_vector, clear);
net.to(role::p1).send(net::msg::ring_vector, clear);
send_proofs(net.to(role::p0), t0);
send_proofs(net.to(role::p1), t1);
return 0;
}
const u64 mine = net.to(role::p2).recv<u64>(net::msg::ring_vector);
const u64 clear = net.to(role::p2).recv<u64>(net::msg::ring_vector);
auto proofs = recv_proofs(net.to(role::p2), degree + 1);
const u64 peer = open_additive(net, self, mine);
auto peer_p = exchange_proofs(net, self, proofs);
if (self == role::p0)
{
require(peer == clear, "offset jet");
for (std::size_t m = 0; m <= degree; ++m)
require(verify(proofs[m], peer_p[m]), "jet proof");
}
return 0;
}
int recent_ring_switch(role self, trio & net)
{
using Z = grotto::zn64<1009>;
const std::uint8_t r = 200;
const std::uint8_t eta = 100;
const std::uint8_t x = static_cast<std::uint8_t>(r + eta);
if (self == role::p2)
{
auto mat = grotto::make_ring_switch_keys<Z>(r, verifiable{});
proof_token t0{}, t1{};
const Z s0 = grotto::ring_switch_eval<0>(mat, eta, &t0);
const Z s1 = grotto::ring_switch_eval<1>(mat, eta, &t1);
const Z clear = grotto::ring_switch_clear<Z>(x, r, eta);
net.to(role::p0).send(net::msg::ring_vector, s0.raw());
net.to(role::p1).send(net::msg::ring_vector, s1.raw());
net.to(role::p0).send(net::msg::ring_vector, clear.raw());
net.to(role::p1).send(net::msg::ring_vector, clear.raw());
send_proofs(net.to(role::p0), std::vector<proof_token>{t0});
send_proofs(net.to(role::p1), std::vector<proof_token>{t1});
return 0;
}
const u64 mine = net.to(role::p2).recv<u64>(net::msg::ring_vector);
const u64 clear = net.to(role::p2).recv<u64>(net::msg::ring_vector);
auto proofs = recv_proofs(net.to(role::p2), 1);
const u64 peer = open_additive(net, self, mine);
auto peer_p = exchange_proofs(net, self, proofs);
if (self == role::p0)
{
require(Z{peer} == Z{clear}, "ring switch");
require(verify(proofs[0], peer_p[0]), "ring proof");
}
return 0;
}
int recent_offset_repr(role self, trio & net)
{
const std::uint8_t center = 10;
const std::uint8_t eta = 5;
const auto state = grotto::offset_repr_fibonacci_state(center);
const auto M = grotto::offset_repr_fibonacci_matrix();
const std::size_t dim = 2;
if (self == role::p2)
{
auto mat = grotto::make_offset_repr_keys<std::uint8_t>(
center, state, verifiable{});
const std::vector<std::uint8_t> knots{0};
std::vector<proof_token> t0(dim), t1(dim);
const auto s0 = grotto::offset_repr_eval<0>(mat, M, knots, eta, t0.data());
const auto s1 = grotto::offset_repr_eval<1>(mat, M, knots, eta, t1.data());
const auto clear = grotto::offset_repr_clear(center, state, M, knots, eta);
net.to(role::p0).send(net::msg::ring_vector, s0[1]);
net.to(role::p1).send(net::msg::ring_vector, s1[1]);
net.to(role::p0).send(net::msg::ring_vector, clear[1]);
net.to(role::p1).send(net::msg::ring_vector, clear[1]);
net.to(role::p0).send(net::msg::ring_vector, s0[0]);
net.to(role::p1).send(net::msg::ring_vector, s1[0]);
net.to(role::p0).send(net::msg::ring_vector, clear[0]);
net.to(role::p1).send(net::msg::ring_vector, clear[0]);
send_proofs(net.to(role::p0), t0);
send_proofs(net.to(role::p1), t1);
return 0;
}
const u64 mine_fn = net.to(role::p2).recv<u64>(net::msg::ring_vector);
const u64 clear_fn = net.to(role::p2).recv<u64>(net::msg::ring_vector);
const u64 mine_fn1 = net.to(role::p2).recv<u64>(net::msg::ring_vector);
const u64 clear_fn1 = net.to(role::p2).recv<u64>(net::msg::ring_vector);
auto proofs = recv_proofs(net.to(role::p2), dim);
const u64 peer_fn = open_additive(net, self, mine_fn);
const u64 peer_fn1 = open_additive(net, self, mine_fn1);
auto peer_p = exchange_proofs(net, self, proofs);
if (self == role::p0)
{
require(peer_fn == clear_fn, "offset repr F_n");
require(peer_fn1 == clear_fn1, "offset repr F_{n+1}");
for (std::size_t i = 0; i < dim; ++i)
require(verify(proofs[i], peer_p[i]), "repr proof");
}
return 0;
}
int recent_offset_twist(role self, trio & net)
{
const std::uint8_t center = 9;
const std::size_t degree = 2;
const std::uint8_t eta = 4;
const u64 lambda = 3;
if (self == role::p2)
{
auto mat = grotto::make_offset_twist_keys<std::uint8_t>(
center, degree, lambda, verifiable{});
const std::vector<u64> coeff{2, 5, 1};
const std::vector<std::uint8_t> knots{0};
std::vector<proof_token> t0(degree + 1), t1(degree + 1);
const u64 s0 = grotto::offset_twist_eval<0>(mat, knots, coeff, eta, t0.data());
const u64 s1 = grotto::offset_twist_eval<1>(mat, knots, coeff, eta, t1.data());
const u64 clear = grotto::offset_twist_clear(
center, lambda, knots, coeff, eta);
net.to(role::p0).send(net::msg::ring_vector, s0);
net.to(role::p1).send(net::msg::ring_vector, s1);
net.to(role::p0).send(net::msg::ring_vector, clear);
net.to(role::p1).send(net::msg::ring_vector, clear);
send_proofs(net.to(role::p0), t0);
send_proofs(net.to(role::p1), t1);
return 0;
}
const u64 mine = net.to(role::p2).recv<u64>(net::msg::ring_vector);
const u64 clear = net.to(role::p2).recv<u64>(net::msg::ring_vector);
auto proofs = recv_proofs(net.to(role::p2), degree + 1);
const u64 peer = open_additive(net, self, mine);
auto peer_p = exchange_proofs(net, self, proofs);
if (self == role::p0)
{
require(peer == clear, "offset twist");
for (std::size_t m = 0; m <= degree; ++m)
require(verify(proofs[m], peer_p[m]), "twist proof");
}
return 0;
}
int recent_closed_form(role self, trio & net)
{
const unsigned bits = 16;
const std::int64_t raw = std::int64_t{1} << bits;
const std::int64_t soft = grotto::eval_closed(grotto::closed::softsign, bits, raw);
const std::int64_t selu = grotto::eval_closed(grotto::closed::selu, bits, -raw);
bool bad_precision = false;
bool pole = false;
try
{
(void)grotto::eval_closed(grotto::closed::atan, 7, raw);
}
catch (const std::invalid_argument &)
{
bad_precision = true;
}
try
{
(void)grotto::eval_closed(grotto::closed::acsch, bits, 0);
}
catch (const std::domain_error &)
{
pole = true;
}
require(bad_precision && pole, "closed form rejects");
if (self == role::p2)
{
net.to(role::p0).send(net::msg::ring_vector, static_cast<u64>(soft));
net.to(role::p0).send(net::msg::ring_vector, static_cast<u64>(selu));
net.to(role::p1).send(net::msg::ring_vector, static_cast<u64>(soft));
net.to(role::p1).send(net::msg::ring_vector, static_cast<u64>(selu));
return 0;
}
const auto peer_soft = static_cast<std::int64_t>(
net.to(role::p2).recv<u64>(net::msg::ring_vector));
const auto peer_selu = static_cast<std::int64_t>(
net.to(role::p2).recv<u64>(net::msg::ring_vector));
require(soft == peer_soft && selu == peer_selu, "closed form agree");
return 0;
}
int recent_exact_steps(role self, trio & net)
{
const unsigned bits = 16;
const std::int64_t width = grotto::eval_dec_width(std::int64_t{3} << bits, bits);
const std::int64_t log16 = grotto::eval_ilog16<std::int64_t>(0, bits);
const std::int64_t angle = grotto::eval_deg2rad(std::int64_t{180} << bits, bits);
bool wide = false;
try
{
(void)grotto::eval_dec_width(1, 63);
}
catch (const std::invalid_argument &)
{
wide = true;
}
require(wide, "exact width");
if (self == role::p2)
{
std::vector<u64> pack{
static_cast<u64>(width), static_cast<u64>(log16), static_cast<u64>(angle)};
net.to(role::p0).send_vec(pack);
net.to(role::p1).send_vec(pack);
return 0;
}
auto pack = net.to(role::p2).recv_vec<u64>();
require(pack.size() == 3u, "exact pack");
require(static_cast<u64>(width) == pack[0], "dec width");
require(static_cast<u64>(log16) == pack[1], "ilog16");
require(static_cast<u64>(angle) == pack[2], "deg2rad");
return 0;
}
int recent_ic_domain(role self, trio & net)
{
using Input = std::uint8_t;
const Input r = 40, p = 7, q = 90;
const Input r0 = 0x13;
const Input r1 = static_cast<Input>(r ^ r0);
const std::uint32_t if_true = 11, if_false = 2;
auto on = [&](const auto & key) {
const u64 nmask = key.input_mask;
const u64 gmask = key.group_mask;
std::vector<u64> shares(256);
for (unsigned x = 0; x < 256; ++x)
shares[x] =
share_bits(eval_point(ic, key, static_cast<Input>(x)));
auto peer = exchange_u64(net, self, shares);
if (self == role::p0)
{
auto dealer = make_dpf(r, ic(p, q, if_true, if_false));
for (unsigned x = 0; x < 256; ++x)
{
const u64 w = (x - static_cast<unsigned>(r)) & nmask;
const bool inside = w >= p && w <= q;
const u64 want = (inside ? if_true : if_false) & gmask;
const u64 got = (shares[x] + peer[x]) & gmask;
require(got == want, "ic");
const u64 d0 = share_bits(
eval_point(ic, dealer.first, static_cast<Input>(x)));
const u64 d1 = share_bits(
eval_point(ic, dealer.second, static_cast<Input>(x)));
require(((d0 + d1) & gmask) == got, "ic matches dealer");
}
}
};
// Default path keeps mask `r` shared (F_IC); still matches a dealer key.
dist_with_ic_key(net, self, r0, r1, ic(p, q, if_true, if_false), on, on);
// Opt-in Reveal reconstructs `r`.
require_opened(dist_with_ic_key<prg::aes128, prg::aes128, true>(net, self,
r0, r1, ic(p, q, if_true, if_false), on, on),
self, utils::xor_input_shares(r0, r1));
return 0;
}
int recent_cmp_edge_default(role self, trio & net)
{
using Input = std::uint8_t;
// Domain-edge point for leq/gt: α = 2^n-1. Default path must not open the
// edge bit, and the key must still evaluate correctly.
const Input alpha = 0xff;
const Input x0 = 0x19;
const Input x1 = static_cast<Input>(alpha ^ x0);
auto check = [&](auto kind, auto pred) {
auto on = [&](const auto & key) {
const u64 mask = key.cmp().mask;
require(key.cmp().trivial == cmp_trivial::none, "edge trivial unset");
std::vector<u64> shares(256);
for (unsigned x = 0; x < 256; ++x)
shares[x] = share_bits(
eval_point(cmp, key, static_cast<Input>(x)));
auto peer = exchange_u64(net, self, shares);
if (self == role::p0)
{
for (unsigned x = 0; x < 256; ++x)
{
const u64 want = pred(x) ? 1u : 0u;
require(((shares[x] + peer[x]) & mask) == want, "edge cmp");
}
}
};
dist_with_cmp_key(net, self, x0, x1, kind, on, on);
};
check(leq(u64{1}), [](unsigned x) { return x <= 255u; });
check(gt(u64{1}), [](unsigned x) { return false; });
// Non-edge leq/gt also stay on the default (no Reveal) path.
const Input mid = 0x40;
const Input m0 = 0x11;
const Input m1 = static_cast<Input>(mid ^ m0);
auto on_mid = [&](const auto & key) {
const u64 mask = key.cmp().mask;
std::vector<u64> shares(256);
for (unsigned x = 0; x < 256; ++x)
shares[x] = share_bits(eval_point(cmp, key, static_cast<Input>(x)));
auto peer = exchange_u64(net, self, shares);
if (self == role::p0)
{
for (unsigned x = 0; x < 256; ++x)
require(((shares[x] + peer[x]) & mask)
== (x <= mid ? 1u : 0u),
"leq mid");
}
};
dist_with_cmp_key(net, self, m0, m1, leq(u64{1}), on_mid, on_mid);
return 0;
}
int recent_point_default(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x3c;
const Input x0 = 0x10;
const Input x1 = static_cast<Input>(alpha ^ x0);
const u64 beta = 0x55;
auto on = [&](const auto & key) {
std::vector<u64> mine(256);
auto [bufs, iters] = eval_full(key);
(void)bufs;
auto it = std::begin(iters);
for (unsigned x = 0; x < 256; ++x, ++it)
mine[x] = share_bits(*it);
auto peer = exchange_u64(net, self, mine);
if (self == role::p0)
{
for (unsigned x = 0; x < 256; ++x)
{
const u64 got = mine[x] - peer[x];
require(got == (x == alpha ? beta : 0u), "point default");
}
}
};
// Default path returns nothing (prefix stays hidden).
dist_with_point_key(net, self, x0, x1, beta, on, on);
return 0;
}
int recent_geneval_domain(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x3c;
const Input x0 = 0x10;
const Input x1 = static_cast<Input>(alpha ^ x0);
const Input y = 0x7e;
auto on = [&](const auto & key) {
std::vector<Input> mine(256);
std::vector<proof_token> pis(256);
for (unsigned x = 0; x < 256; ++x)
{
mine[x] = share_bits(
*eval_point(key, static_cast<Input>(x), prove(pis[x])));
}
auto peer = exchange_u8(net, self, mine);
auto peer_p = exchange_proofs(net, self, pis);
if (self == role::p0)
{
require(mine.size() == 256u, "geneval shares");
require(static_cast<Input>(mine[alpha] - peer[alpha]) == y, "geneval on");
require(static_cast<Input>(mine[0] - peer[0]) == Input{0}, "geneval off");
require(static_cast<Input>(peer[alpha] - mine[alpha]) != y, "party order");
for (unsigned x = 0; x < 256; ++x)
require(verify(pis[x], peer_p[x]), "geneval proof");
require(!verify(detail::vdpf::zero_proof(), peer_p[alpha]),
"zero token must fail");
require(!verify(pis[alpha], detail::vdpf::zero_proof()),
"zero peer token must fail");
auto flipped = peer_p[alpha];
flipped[0] = simde_mm_xor_si128(flipped[0], simde_mm_set1_epi8(1));
require(!verify(pis[alpha], flipped), "tampered token");
}
auto corrupted = key;
if (self == role::p0)
flip_words(corrupted);
proof_token bad_pi{};
const Input bad = open_subtractive(net, self,
share_bits(*eval_point(corrupted, alpha, prove(bad_pi))));
auto peer_bad = exchange_proof(net, self, bad_pi);
if (self == role::p0)
{
require(bad != y, "dist word tamper");
require(!verify(bad_pi, peer_bad), "dist proof after word tamper");
}
};
require_tree_prefix(dist_with_point_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, y, on, on),
self, verifiable_tree_prefix<prg::aes128, prg::aes128, decltype(y)>(x0, x1));
return 0;
}
int recent_dist_half_tree_domain(role self, trio & net)
{
using Input = std::uint8_t;
using Ht = prg::aes128_ccr;
const Input alpha = 9;
const Input x0 = 0x3;
const Input x1 = static_cast<Input>(alpha ^ x0);
const u64 beta = 0x1111;
auto on = [&](const auto & key) {
int hits = 0;
for (unsigned x = 0; x < 256; ++x)
{
const u64 opened = open_subtractive(net, self,
share_bits(*eval_point(key, static_cast<Input>(x))));
if (self == role::p0)
{
require(opened == (x == alpha ? beta : 0u), "dist half-tree");
if (opened == beta)
++hits;
}
}
if (self == role::p0)
require(hits == 1, "dist half-tree hits");
};
require_tree_prefix(dist_with_point_key<Ht, Ht, true>(net, self, x0, x1, beta, on, on),
self, verifiable_tree_prefix<Ht, Ht, u64>(x0, x1));
return 0;
}
int recent_dist_packed_leaf(role self, trio & net)
{
using Input = std::uint8_t;
using Out = std::uint16_t;
const Input alpha = 0x2a;
const Input x0 = 0x11;
const Input x1 = static_cast<Input>(alpha ^ x0);
const Out beta = 0x1234;
const Input neighbor = static_cast<Input>(alpha ^ 0x1);
auto on = [&](const auto & key) {
const Out on_v = open_subtractive(net, self,
share_bits(*eval_point(key, alpha)));
const Out lane = open_subtractive(net, self,
share_bits(*eval_point(key, neighbor)));
const Out far = open_subtractive(net, self,
share_bits(*eval_point(key, Input{0})));
if (self == role::p0)
{
require(on_v == beta, "packed on");
require(lane == Out{0}, "packed neighbor");
require(far == Out{0}, "packed far");
}
};
require_tree_prefix(dist_with_point_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, beta, on, on),
self, verifiable_tree_prefix<prg::aes128, prg::aes128, Out>(x0, x1));
return 0;
}
int recent_extractable_second_hot(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x2a;
const Input x0 = 0x10;
const Input x1 = static_cast<Input>(alpha ^ x0);
const fp61 beta{7};
auto on = [&](const auto & key) {
const std::array<fp61, 2> rs{fp61{3}, fp61{5}};
sketch_share local{};
auto sk = sketch(local, rs);
(void)*eval_point(key, Input{0}, sk);
(void)*eval_point(key, alpha, sk);
role peer = self == role::p0 ? role::p1 : role::p0;
sketch_share theirs =
net.exchange_with(peer, local, net::msg::sketch_share);
bool honest = self == role::p0 ? sketch_verify(local, theirs)
: sketch_verify(theirs, local);
if (self == role::p0)
require(honest, "honest sketch");
sketch_share forged{};
auto bad = sketch(forged, rs);
fp61 y0 = (*eval_point(key, Input{0})).raw();
const fp61 y1 = (*eval_point(key, alpha)).raw();
if (self == role::p0)
y0 = y0 + beta;
bad.absorb(y0);
bad.absorb(y1);
sketch_share peer_forged =
net.exchange_with(peer, forged, net::msg::sketch_share);
bool second = self == role::p0
? sketch_verify(forged, peer_forged)
: sketch_verify(peer_forged, forged);
if (self == role::p0)
require(!second, "second hot point");
};
dist_with_extractable_point_key(
net, self, x0, x1, beta, on, on);
return 0;
}
// ---------------------------------------------------------------------------
// Three-evaluator (2,3) DPF.
// Dealer flows: p2 runs make_dpf3* and ships keys (α clear to dealer).
// Dist flows: dist_with_dpf3_key (α XOR-shared; role map dpf3_role_map::dist).
// ---------------------------------------------------------------------------
/// @brief Collect three Shamir shares at p2 and reconstruct.
/// @details Party indices follow `dpf3_party_of(role, map)`. Only p2 returns
/// the opened value; p0/p1 return zero.
fp61 open_shamir3(trio & net, role self, fp61 mine,
dpf3_role_map map = dpf3_role_map::dealer)
{
if (self == role::p2)
{
const auto from_p0 = net.to(role::p0).recv<fp61>(net::msg::delta);
const auto from_p1 = net.to(role::p1).recv<fp61>(net::msg::delta);
return shamir3::reconstruct(
shamir3::share{dpf3_party_of(role::p0, map), from_p0},
shamir3::share{dpf3_party_of(role::p1, map), from_p1},
shamir3::share{dpf3_party_of(role::p2, map), mine});
}
net.to(role::p2).send(net::msg::delta, mine);
return fp61{};
}
/// @brief Open F_DPF3CMP complementary halves at p2 (dealer role map).
/// @details p0 holds the k0 half, p1 the k1 half. p2's `mine` is unused for
/// the open (party 3 also holds k0) and may be a dummy.
fp61 open_cmp3(trio & net, role self, std::uint64_t mine)
{
if (self == role::p2)
{
const auto k0 = net.to(role::p0).recv<std::uint64_t>(net::msg::delta);
const auto k1 = net.to(role::p1).recv<std::uint64_t>(net::msg::delta);
(void)mine;
return reconstruct_cmp_halves(k0, k1);
}
net.to(role::p2).send(net::msg::delta, mine);
return fp61{};
}
int recent_dpf3_point_domain(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x2a;
const fp61 beta{17};
if (self == role::p2)
{
auto [k1, k2, k3] = make_dpf3(alpha, beta, verifiable{});
send_key(net.to(role::p0), k1);
send_key(net.to(role::p1), k2);
for (unsigned x = 0; x < 256; ++x)
{
const fp61 y = eval_point(k3, static_cast<Input>(x));
const fp61 got = open_shamir3(net, self, y);
require(got == (static_cast<Input>(x) == alpha ? beta : fp61{}),
"dpf3 point");
}
return 0;
}
if (self == role::p0)
{
using K = std::decay_t<decltype(std::get<0>(
make_dpf3(Input{}, fp61{}, verifiable{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_shamir3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
using K = std::decay_t<decltype(std::get<1>(
make_dpf3(Input{}, fp61{}, verifiable{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_shamir3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
int recent_dpf3_proof_fail(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x11;
if (self == role::p2)
{
auto [k1, k2, k3] = make_dpf3(alpha, fp61{3}, verifiable{});
using arr = typename decltype(k1.a.dpf_key)::correction_seeds_array;
for (auto & cs : const_cast<arr &>(k1.a.dpf_key.correction_seeds()))
cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1));
auto p1 = prove_dpf3(k1, alpha);
auto p2 = prove_dpf3(k2, alpha);
auto p3 = prove_dpf3(k3, alpha);
require(!verify_dpf3(p1, p2, p3), "dpf3 proof fail");
net.to(role::p0).send(net::msg::delta, std::uint8_t{1});
net.to(role::p1).send(net::msg::delta, std::uint8_t{1});
return 0;
}
(void)net.to(role::p2).recv<std::uint8_t>(net::msg::delta);
return 0;
}
int recent_dpf3_update(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x07;
if (self == role::p2)
{
auto [k1, k2, k3] = make_dpf3(alpha, fp61{5}, updatable{});
update_payload(k1, k2, k3, alpha, fp61{9});
send_key(net.to(role::p0), k1);
send_key(net.to(role::p1), k2);
const fp61 y = eval_point(k3, alpha);
const fp61 got = open_shamir3(net, self, y);
require(got == fp61{9}, "dpf3 update");
return 0;
}
if (self == role::p0)
{
using K = std::decay_t<decltype(std::get<0>(
make_dpf3(Input{}, fp61{}, updatable{})))>;
auto key = recv_key<K>(net.to(role::p2));
(void)open_shamir3(net, self, eval_point(key, alpha));
return 0;
}
using K = std::decay_t<decltype(std::get<1>(
make_dpf3(Input{}, fp61{}, updatable{})))>;
auto key = recv_key<K>(net.to(role::p2));
(void)open_shamir3(net, self, eval_point(key, alpha));
return 0;
}
int recent_dpf3_cmp_domain(role self, trio & net)
{
using Input = std::uint8_t;
const Input thresh = 100;
const u64 beta = 5;
if (self == role::p2)
{
auto [k1, k2, k3] = make_dpf3_cmp(thresh, beta);
send_key(net.to(role::p0), k1);
send_key(net.to(role::p1), k2);
for (unsigned x = 0; x < 256; ++x)
{
const auto y = eval_point(k3, static_cast<Input>(x));
const fp61 got = open_cmp3(net, self, y);
require(got.raw() == (x < thresh ? beta : 0u), "dpf3 cmp");
}
return 0;
}
if (self == role::p0)
{
using K = std::decay_t<decltype(std::get<0>(make_dpf3_cmp(Input{}, u64{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_cmp3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
using K = std::decay_t<decltype(std::get<1>(make_dpf3_cmp(Input{}, u64{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_cmp3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
int recent_dist_dpf3_point(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x2a;
const Input x0 = 0x11;
const Input x1 = static_cast<Input>(alpha ^ x0);
const fp61 beta{17};
constexpr auto map = dpf3_role_map::dist;
require(x0 != alpha && x1 != alpha, "dist dpf3: alpha shares only");
const auto opened = dist_with_dpf3_key(net, self, x0, x1, beta,
[&](auto key) {
// Party 1 (p0): eval is a Shamir share, not clear β.
require(eval_point(key, alpha) != beta,
"dist dpf3: p0 beta hidden");
for (unsigned x = 0; x < 256; ++x)
(void)open_shamir3(net, self,
eval_point(key, static_cast<Input>(x)), map);
},
[&](auto key) {
for (unsigned x = 0; x < 256; ++x)
{
const fp61 got = open_shamir3(net, self,
eval_point(key, static_cast<Input>(x)), map);
require(got == (static_cast<Input>(x) == alpha ? beta : fp61{}),
"dist dpf3 point");
}
},
[&](auto key) {
// Party 3 (p1): only τ halves were received; eval ≠ clear β.
require(eval_point(key, alpha) != beta,
"dist dpf3: p1 beta is share only");
for (unsigned x = 0; x < 256; ++x)
(void)open_shamir3(net, self,
eval_point(key, static_cast<Input>(x)), map);
});
require(!opened.has_value(), "dist dpf3: no clear prefix");
return 0;
}
/// @brief Dist updatable keys: Fig-10 over the wire, then open at `α`.
int recent_dist_dpf3_update(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x07;
const Input x0 = 0x03;
const Input x1 = static_cast<Input>(alpha ^ x0);
const fp61 beta0{5};
const fp61 beta1{9};
constexpr auto map = dpf3_role_map::dist;
dist_with_dpf3_key(net, self, x0, x1, beta0, updatable{},
[&](auto key) {
require(key.updatable, "dist dpf3 update: p0 updatable");
dist_update_payload(net, self, key, alpha, beta1);
(void)open_shamir3(net, self, eval_point(key, alpha), map);
},
[&](auto key) {
require(key.updatable, "dist dpf3 update: p2 updatable");
dist_update_payload(net, self, key, alpha, beta1);
const fp61 got =
open_shamir3(net, self, eval_point(key, alpha), map);
require(got == beta1, "dist dpf3 update");
},
[&](auto key) {
require(key.updatable, "dist dpf3 update: p1 updatable");
dist_update_payload(net, self, key, alpha, beta1);
(void)open_shamir3(net, self, eval_point(key, alpha), map);
});
return 0;
}
/// @brief Dist dual-spine keys: prove at `α` and verify on p2 (party 2).
int recent_dist_dpf3_proof(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x19;
const Input x0 = 0x07;
const Input x1 = static_cast<Input>(alpha ^ x0);
const fp61 beta{4};
dist_with_dpf3_key(net, self, x0, x1, beta,
[&](auto key) {
require(key.verifiable, "dist dpf3 proof: p0 verifiable");
const auto p = prove_dpf3(key, alpha);
net.to(role::p2).send(net::msg::delta, p);
},
[&](auto key) {
require(key.verifiable, "dist dpf3 proof: p2 verifiable");
const auto p1 = net.to(role::p0).recv<dpf3_proof>(net::msg::delta);
const auto p3 = net.to(role::p1).recv<dpf3_proof>(net::msg::delta);
const auto p2 = prove_dpf3(key, alpha);
require(verify_dpf3(p1, p2, p3), "dist dpf3 proof ok");
// Corrupt party-1 A-half token; verify must fail closed.
dpf3_proof bad = p1;
bad.a[0] = simde_mm_xor_si128(bad.a[0], simde_mm_set1_epi8(1));
require(!verify_dpf3(bad, p2, p3), "dist dpf3 proof fail");
},
[&](auto key) {
require(key.verifiable, "dist dpf3 proof: p1 verifiable");
const auto p = prove_dpf3(key, alpha);
net.to(role::p2).send(net::msg::delta, p);
});
return 0;
}
int recent_dpf3_ic_domain(role self, trio & net)
{
using Input = std::uint8_t;
const Input r = 10, p = 20, q = 40;
const u64 beta = 3;
if (self == role::p2)
{
auto [k1, k2, k3] = make_dpf3_ic(r, p, q, beta);
auto two = make_dpf(r, ic(p, q, beta));
send_key(net.to(role::p0), k1);
send_key(net.to(role::p1), k2);
for (unsigned x = 0; x < 256; ++x)
{
const auto want = reconstruct(
eval_point(ic, two.first, static_cast<Input>(x)),
eval_point(ic, two.second, static_cast<Input>(x)));
const auto y = eval_point(k3, static_cast<Input>(x));
const fp61 got = open_cmp3(net, self, y);
require(got.raw() == static_cast<u64>(want), "dpf3 ic");
}
return 0;
}
if (self == role::p0)
{
using K = std::decay_t<decltype(std::get<0>(
make_dpf3_ic(Input{}, Input{}, Input{}, u64{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_cmp3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
using K = std::decay_t<decltype(std::get<1>(
make_dpf3_ic(Input{}, Input{}, Input{}, u64{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_cmp3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
/// @brief Dealer cmp update (fp61 delta) then full-domain open on all three.
int recent_dpf3_cmp_update(role self, trio & net)
{
using Input = std::uint8_t;
const Input thresh = 80;
if (self == role::p2)
{
auto [k1, k2, k3] = make_dpf3_cmp(thresh, 11u);
update_payload_cmp(k1, k2, k3, 11u, 0u);
send_key(net.to(role::p0), k1);
send_key(net.to(role::p1), k2);
for (unsigned x = 0; x < 256; ++x)
{
const fp61 got =
open_cmp3(net, self, eval_point(k3, static_cast<Input>(x)));
require(got.raw() == 0u, "dpf3 cmp update clear");
}
return 0;
}
if (self == role::p0)
{
using K = std::decay_t<decltype(std::get<0>(make_dpf3_cmp(Input{}, u64{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_cmp3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
using K = std::decay_t<decltype(std::get<1>(make_dpf3_cmp(Input{}, u64{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_cmp3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
/// @brief Dealer blocked comparison full domain.
int recent_dpf3_blocked_cmp(role self, trio & net)
{
using Input = std::uint8_t;
const Input thresh = 50;
const u64 beta = 9;
if (self == role::p2)
{
auto [k1, k2, k3] = make_dpf3_cmp_blocked<4>(thresh, beta);
send_key(net.to(role::p0), k1);
send_key(net.to(role::p1), k2);
for (unsigned x = 0; x < 256; ++x)
{
const fp61 got =
open_cmp3(net, self, eval_point(k3, static_cast<Input>(x)));
require(got.raw() == (x < thresh ? beta : 0u), "dpf3 blocked cmp");
}
return 0;
}
if (self == role::p0)
{
using K = std::decay_t<decltype(std::get<0>(
make_dpf3_cmp_blocked<4>(Input{}, u64{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_cmp3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
using K = std::decay_t<decltype(std::get<1>(
make_dpf3_cmp_blocked<4>(Input{}, u64{})))>;
auto key = recv_key<K>(net.to(role::p2));
for (unsigned x = 0; x < 256; ++x)
(void)open_cmp3(net, self, eval_point(key, static_cast<Input>(x)));
return 0;
}
/// @brief Dealer multipoint3 full domain on all three evaluators.
/// @details `multipoint3_key` embeds a `std::vector` of buckets; ship σ/meta
/// then each bucket via `send_key` (not a flat memcpy of the parent).
int recent_dpf3_multipoint_domain(role self, trio & net)
{
using Input = std::uint8_t;
const std::vector<Input> alphas{1, 2, 9, 40};
const std::vector<fp61> betas{fp61{7}, fp61{11}, fp61{3}, fp61{4}};
using K1 = std::decay_t<decltype(std::get<0>(
make_multipoint3(alphas, betas, verifiable{})))>;
using K2 = std::decay_t<decltype(std::get<1>(
make_multipoint3(alphas, betas, verifiable{})))>;
using Bucket1 = typename K1::bucket_key;
using Bucket2 = typename K2::bucket_key;
auto send_mp = [&](role peer, const auto & key) {
net.to(peer).send(net::msg::delta, key.sigma);
net.to(peer).send(net::msg::delta, key.bucket_count);
net.to(peer).send(net::msg::delta, key.bucket_domain);
const std::uint64_t nb = key.buckets.size();
net.to(peer).send(net::msg::delta, nb);
for (const auto & b : key.buckets)
send_key(net.to(peer), b);
};
auto recv_mp = [&](auto empty_key) {
using Key = decltype(empty_key);
Key key = std::move(empty_key);
key.sigma = net.to(role::p2).template recv<simde__m128i>(net::msg::delta);
key.bucket_count =
net.to(role::p2).template recv<std::uint64_t>(net::msg::delta);
key.bucket_domain =
net.to(role::p2).template recv<mpf_word>(net::msg::delta);
const auto nb =
net.to(role::p2).template recv<std::uint64_t>(net::msg::delta);
key.buckets.clear();
key.buckets.reserve(static_cast<std::size_t>(nb));
using Bucket = typename Key::bucket_key;
for (std::uint64_t i = 0; i < nb; ++i)
key.buckets.push_back(recv_key<Bucket>(net.to(role::p2)));
key.verifiable = true;
return key;
};
if (self == role::p2)
{
auto [k1, k2, k3] = make_multipoint3(alphas, betas, verifiable{});
send_mp(role::p0, k1);
send_mp(role::p1, k2);
for (unsigned x = 0; x < 256; ++x)
{
fp61 want{};
for (std::size_t i = 0; i < alphas.size(); ++i)
if (alphas[i] == static_cast<Input>(x))
want = betas[i];
const fp61 got = open_shamir3(net, self,
eval_multipoint(k3, static_cast<Input>(x)));
require(got == want, "dpf3 multipoint");
}
return 0;
}
if (self == role::p0)
{
K1 empty{};
auto key = recv_mp(std::move(empty));
(void)sizeof(Bucket1);
for (unsigned x = 0; x < 256; ++x)
(void)open_shamir3(net, self,
eval_multipoint(key, static_cast<Input>(x)));
return 0;
}
K2 empty{};
auto key = recv_mp(std::move(empty));
(void)sizeof(Bucket2);
for (unsigned x = 0; x < 256; ++x)
(void)open_shamir3(net, self,
eval_multipoint(key, static_cast<Input>(x)));
return 0;
}
/// @brief Dist Fig-10 then full-domain open (not only α).
int recent_dist_dpf3_update_domain(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x2b;
const Input x0 = 0x05;
const Input x1 = static_cast<Input>(alpha ^ x0);
const fp61 beta0{3};
const fp61 beta1{13};
constexpr auto map = dpf3_role_map::dist;
dist_with_dpf3_key(net, self, x0, x1, beta0, updatable{},
[&](auto key) {
dist_update_payload(net, self, key, alpha, beta1);
for (unsigned x = 0; x < 256; ++x)
(void)open_shamir3(net, self,
eval_point(key, static_cast<Input>(x)), map);
},
[&](auto key) {
dist_update_payload(net, self, key, alpha, beta1);
for (unsigned x = 0; x < 256; ++x)
{
const fp61 got = open_shamir3(net, self,
eval_point(key, static_cast<Input>(x)), map);
require(got == (static_cast<Input>(x) == alpha ? beta1 : fp61{}),
"dist dpf3 update domain");
}
},
[&](auto key) {
dist_update_payload(net, self, key, alpha, beta1);
for (unsigned x = 0; x < 256; ++x)
(void)open_shamir3(net, self,
eval_point(key, static_cast<Input>(x)), map);
});
return 0;
}
/// @brief Dist update then prove/verify still succeeds on all three parties.
int recent_dist_dpf3_update_proof(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x1c;
const Input x0 = 0x09;
const Input x1 = static_cast<Input>(alpha ^ x0);
dist_with_dpf3_key(net, self, x0, x1, fp61{2}, updatable{},
[&](auto key) {
dist_update_payload(net, self, key, alpha, fp61{8});
require(key.verifiable, "dist update proof: p0 V");
net.to(role::p2).send(net::msg::delta, prove_dpf3(key, alpha));
},
[&](auto key) {
dist_update_payload(net, self, key, alpha, fp61{8});
const auto p1 = net.to(role::p0).recv<dpf3_proof>(net::msg::delta);
const auto p3 = net.to(role::p1).recv<dpf3_proof>(net::msg::delta);
const auto p2 = prove_dpf3(key, alpha);
require(verify_dpf3(p1, p2, p3), "dist update proof ok");
},
[&](auto key) {
dist_update_payload(net, self, key, alpha, fp61{8});
net.to(role::p2).send(net::msg::delta, prove_dpf3(key, alpha));
});
return 0;
}
/// @brief Dist shares opened under the dealer role map must not reconstruct.
int recent_dist_dpf3_wrong_map(role self, trio & net)
{
using Input = std::uint8_t;
const Input alpha = 0x33;
const Input x0 = 0x0a;
const Input x1 = static_cast<Input>(alpha ^ x0);
const fp61 beta{6};
dist_with_dpf3_key(net, self, x0, x1, beta,
[&](auto key) {
(void)open_shamir3(net, self, eval_point(key, alpha),
dpf3_role_map::dealer);
},
[&](auto key) {
bool rejected = false;
try
{
const fp61 got = open_shamir3(net, self, eval_point(key, alpha),
dpf3_role_map::dealer);
rejected = got != beta;
}
catch (const std::runtime_error &)
{
rejected = true;
}
require(rejected, "dealer map on dist shares");
},
[&](auto key) {
(void)open_shamir3(net, self, eval_point(key, alpha),
dpf3_role_map::dealer);
});
return 0;
}
/// @brief Static role-map contract used by open_shamir3 (p0/p1/p2 all check).
int recent_dpf3_role_map(role self, trio & net)
{
(void)net;
require(dpf3_party_of(role::p0, dpf3_role_map::dealer) == 1, "dealer p0");
require(dpf3_party_of(role::p1, dpf3_role_map::dealer) == 2, "dealer p1");
require(dpf3_party_of(role::p2, dpf3_role_map::dealer) == 3, "dealer p2");
require(dpf3_party_of(role::p0, dpf3_role_map::dist) == 1, "dist p0");
require(dpf3_party_of(role::p2, dpf3_role_map::dist) == 2, "dist p2");
require(dpf3_party_of(role::p1, dpf3_role_map::dist) == 3, "dist p1");
// Cross-wire check: dealer indices ≠ dist for p1/p2.
require(dpf3_party_of(role::p1, dpf3_role_map::dealer)
!= dpf3_party_of(role::p1, dpf3_role_map::dist),
"p1 map differs");
require(dpf3_party_of(role::p2, dpf3_role_map::dealer)
!= dpf3_party_of(role::p2, dpf3_role_map::dist),
"p2 map differs");
(void)self;
return 0;
}
/// @brief Oblivious correction-seed hash matches in-process `make_cs`.
/// @details One level of the shared AES circuit. Prefix shares are split so
/// neither party holds the clear prefix, and the seeds differ.
int recent_oblivious_cs_matches(role self, trio & net)
{
auto block_from = [](std::uint64_t hi, std::uint64_t lo) {
const std::uint64_t limbs[2] = {lo, hi};
simde__m128i v;
std::memcpy(&v, limbs, sizeof(v));
return v;
};
const simde__m128i s0 = block_from(0x1111222233334444ULL, 0x5555666677778888ULL);
const simde__m128i s1 = block_from(0x0102030405060708ULL, 0x89abcdef00112233ULL);
struct case_t
{
std::size_t level;
std::uint64_t prefix;
std::uint64_t share0;
};
const case_t cases[] = {
{0, 0, 0},
{1, 1, 0},
{3, 0x2a, 0x11},
{7, 0xff, 0x5a},
{dpf::detail::blocked::fold_spine_tag | 2, 0x15, 0x01},
};
for (const auto & c : cases)
{
if (self == role::p2)
{
dist::send_hash_tape(net);
continue;
}
auto tape = net.to(role::p2).template recv_vec<dist::bit_and_pad_msg>(
dpf::net::msg::beaver_tape);
require(tape.size() == dist::hash_level_and_count(), "hash tape");
const std::uint64_t share = self == role::p0
? c.share0 : (c.prefix ^ c.share0);
const simde__m128i seed = self == role::p0 ? s0 : s1;
dpf::cs_block got{};
if (self == role::p0)
got = dist::oblivious_cs<0>(net, c.level, share, seed, tape.data());
else
got = dist::oblivious_cs<1>(net, c.level, share, seed, tape.data());
const auto expect = dpf::detail::vdpf::make_cs(
c.level, c.prefix, s0, s1);
require(std::memcmp(got.data(), expect.data(), sizeof(got)) == 0,
"oblivious cs");
}
return 0;
}
int recent_grow_extend(role self, trio & net)
{
using In = std::uint8_t;
const In alpha = 0xB2;
const std::uint64_t beta = 9;
dist_with_grow_extend(net, self, alpha, beta,
[&](const auto & key) {
require(std::decay_t<decltype(key)>::depth == 1, "grown depth");
require(std::decay_t<decltype(key)>::num_outputs == 2, "grown outs");
(void)key;
},
[&](const auto & key) {
require(std::decay_t<decltype(key)>::depth == 1, "grown depth");
(void)key;
});
return 0;
}
#define REG(name, tags, fn) \
register_flow(flow{#name, tags, fn, false})
} // namespace recent
void register_recent_flows()
{
using namespace recent;
REG(recent_half_tree_domain, "recent verifiable half-tree", recent_half_tree_domain);
REG(recent_half_tree_seed_tamper, "recent verifiable half-tree", recent_half_tree_seed_tamper);
REG(recent_word_tamper, "recent verifiable", recent_word_tamper);
REG(recent_cmp_domain, "recent verifiable dcf", recent_cmp_domain);
REG(recent_blocked_domain, "recent verifiable dcf", recent_blocked_domain);
REG(recent_multipoint_domain, "recent verifiable multipoint", recent_multipoint_domain);
REG(recent_fp61_mac, "recent mac", recent_fp61_mac);
REG(recent_offset_poly, "recent grotto verifiable", recent_offset_poly);
REG(recent_offset_jet, "recent grotto verifiable", recent_offset_jet);
REG(recent_ring_switch, "recent grotto verifiable", recent_ring_switch);
REG(recent_offset_repr, "recent grotto verifiable", recent_offset_repr);
REG(recent_offset_twist, "recent grotto verifiable", recent_offset_twist);
REG(recent_closed_form, "recent grotto", recent_closed_form);
REG(recent_exact_steps, "recent grotto", recent_exact_steps);
REG(recent_ic_domain, "recent ic", recent_ic_domain);
REG(recent_cmp_edge_default, "recent dcf", recent_cmp_edge_default);
REG(recent_point_default, "recent dist", recent_point_default);
REG(recent_geneval_domain, "recent dist", recent_geneval_domain);
REG(recent_dist_half_tree_domain, "recent dist half-tree", recent_dist_half_tree_domain);
REG(recent_dist_packed_leaf, "recent dist", recent_dist_packed_leaf);
REG(recent_extractable_second_hot, "recent extractable", recent_extractable_second_hot);
REG(recent_dpf3_point_domain, "recent dpf3 dealer", recent_dpf3_point_domain);
REG(recent_dpf3_proof_fail, "recent dpf3 dealer", recent_dpf3_proof_fail);
REG(recent_dpf3_update, "recent dpf3 dealer", recent_dpf3_update);
REG(recent_dpf3_cmp_domain, "recent dpf3 dealer", recent_dpf3_cmp_domain);
REG(recent_dpf3_ic_domain, "recent dpf3 dealer", recent_dpf3_ic_domain);
REG(recent_dpf3_cmp_update, "recent dpf3 dealer", recent_dpf3_cmp_update);
REG(recent_dpf3_blocked_cmp, "recent dpf3 dealer", recent_dpf3_blocked_cmp);
REG(recent_dpf3_multipoint_domain, "recent dpf3 dealer", recent_dpf3_multipoint_domain);
REG(recent_dpf3_role_map, "recent dpf3 dealer", recent_dpf3_role_map);
REG(recent_dist_dpf3_point, "recent dpf3 dist", recent_dist_dpf3_point);
REG(recent_dist_dpf3_proof, "recent dpf3 dist", recent_dist_dpf3_proof);
REG(recent_dist_dpf3_update, "recent dpf3 dist", recent_dist_dpf3_update);
REG(recent_dist_dpf3_update_domain, "recent dpf3 dist", recent_dist_dpf3_update_domain);
REG(recent_dist_dpf3_update_proof, "recent dpf3 dist", recent_dist_dpf3_update_proof);
REG(recent_dist_dpf3_wrong_map, "recent dpf3 dist", recent_dist_dpf3_wrong_map);
REG(recent_oblivious_cs_matches, "recent verifiable hash", recent_oblivious_cs_matches);
REG(recent_grow_extend, "recent grow ds", recent_grow_extend);
}
#undef REG
} // namespace party
} // namespace dpf