libdpf/party/flows_recent.cpp

1655 lines
62 KiB
C++
Raw Permalink Normal View History

/// @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