/// @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 #include #include #include #include #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 exchange_u64(trio & net, role self, const std::vector & 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(); } auto theirs = net.to(peer).recv_vec(); net.to(peer).send_vec(mine); return theirs; } std::vector exchange_u8(trio & net, role self, const std::vector & 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(); } auto theirs = net.to(peer).recv_vec(); net.to(peer).send_vec(mine); return theirs; } void send_proofs(net::channel & c, const std::vector & v) { c.send_bytes(net::msg::proof_token, reinterpret_cast(v.data()), v.size() * sizeof(proof_token)); } std::vector 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 out(n); std::memcpy(out.data(), body.data(), body.size()); return out; } std::vector exchange_proofs(trio & net, role self, const std::vector & 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 one{mine}; return exchange_proofs(net, self, one)[0]; } template void flip_seeds(Key & key) { using arr = typename std::decay_t::correction_seeds_array; for (auto & cs : const_cast(key.correction_seeds())) cs[0] = simde_mm_xor_si128(cs[0], simde_mm_set1_epi8(1)); } template void flip_words(Key & key) { using arr = typename std::decay_t::correction_words_array; for (auto & word : const_cast(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(alpha ^ x0); const u64 beta = 0x1111; auto on = [&](const auto & key) { std::vector shares(256); std::vector pis(256); for (unsigned x = 0; x < 256; ++x) { shares[x] = share_bits(*eval_point( key, static_cast(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(net, self, x0, x1, beta, on, on), self, verifiable_tree_prefix(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(alpha ^ x0); const u64 beta = 9; auto on = [&](auto key) { if (self == role::p0) flip_seeds(key); std::vector pis(256); for (unsigned x = 0; x < 256; ++x) { proof_token pi{}; (void)*eval_point(key, static_cast(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(net, self, x0, x1, beta, on, on), self, verifiable_tree_prefix(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(alpha ^ x0); const u64 beta = 5; auto on = [&](auto key) { if (self == role::p0) flip_words(key); std::vector 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(net, self, x0, x1, beta, on, on), self, verifiable_tree_prefix(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(alpha ^ x0); auto on = [&](const auto & key) { const u64 mask = key.cmp().mask; std::vector shares(256); std::vector pis(256); for (unsigned x = 0; x < 256; ++x) { shares[x] = share_bits(eval_point( cmp, key, static_cast(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(alpha ^ x0); auto on = [&](const auto & key) { const u64 mask = key.cmp().mask; std::vector shares(256); std::vector pis(256); for (unsigned x = 0; x < 256; ++x) { shares[x] = share_bits(eval_point( cmp, key, static_cast(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 alphas{1, 9, 40, 255}; const std::vector betas{7, 11, 3, 1}; if (self == role::p2) { auto keys = make_multipoint(alphas, betas, verifiable{}); std::vector s0(256), s1(256); std::vector p0(256), p1(256); for (unsigned x = 0; x < 256; ++x) { const Input q = static_cast(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{a0}); send_proofs(net.to(role::p1), std::vector{a1}); return 0; } auto shares = net.to(role::p2).recv_vec(); 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(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(); auto shares = mac_share_value(y, key); std::vector a{ shares.first.value.raw(), shares.first.tag.raw(), key.delta.raw()}; std::vector 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(); require(mine.size() == 3u, "mac wire"); mac_share local{fp61{mine[0]}, fp61{mine[1]}}; mac_key key{fp61{mine[2]}}; std::vector body{local.value.raw(), local.tag.raw()}; auto peer_body = exchange_u64(net, self, body); mac_share 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{peer_body[0]}, fp61{peer_body[1]}}; if (self == role::p0) require(!mac_verify(local, peer, key), "value tamper"); local = mac_share{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{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(center, degree, verifiable{}); const std::vector coeff{1, 0, 3}; const std::vector knots{0}; std::vector 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(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(net::msg::ring_vector); const u64 clear = net.to(role::p2).recv(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(center, degree, verifiable{}); const std::vector coeff{2, 3, 1}; const std::vector knots{0}; std::vector 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(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(net::msg::ring_vector); const u64 clear = net.to(role::p2).recv(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(r + eta); if (self == role::p2) { auto mat = grotto::make_ring_switch_keys(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(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{t0}); send_proofs(net.to(role::p1), std::vector{t1}); return 0; } const u64 mine = net.to(role::p2).recv(net::msg::ring_vector); const u64 clear = net.to(role::p2).recv(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( center, state, verifiable{}); const std::vector knots{0}; std::vector 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(net::msg::ring_vector); const u64 clear_fn = net.to(role::p2).recv(net::msg::ring_vector); const u64 mine_fn1 = net.to(role::p2).recv(net::msg::ring_vector); const u64 clear_fn1 = net.to(role::p2).recv(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( center, degree, lambda, verifiable{}); const std::vector coeff{2, 5, 1}; const std::vector knots{0}; std::vector 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(net::msg::ring_vector); const u64 clear = net.to(role::p2).recv(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(soft)); net.to(role::p0).send(net::msg::ring_vector, static_cast(selu)); net.to(role::p1).send(net::msg::ring_vector, static_cast(soft)); net.to(role::p1).send(net::msg::ring_vector, static_cast(selu)); return 0; } const auto peer_soft = static_cast( net.to(role::p2).recv(net::msg::ring_vector)); const auto peer_selu = static_cast( net.to(role::p2).recv(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(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 pack{ static_cast(width), static_cast(log16), static_cast(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(); require(pack.size() == 3u, "exact pack"); require(static_cast(width) == pack[0], "dec width"); require(static_cast(log16) == pack[1], "ilog16"); require(static_cast(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(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 shares(256); for (unsigned x = 0; x < 256; ++x) shares[x] = share_bits(eval_point(ic, key, static_cast(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(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(x))); const u64 d1 = share_bits( eval_point(ic, dealer.second, static_cast(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(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(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 shares(256); for (unsigned x = 0; x < 256; ++x) shares[x] = share_bits( eval_point(cmp, key, static_cast(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(mid ^ m0); auto on_mid = [&](const auto & key) { const u64 mask = key.cmp().mask; std::vector shares(256); for (unsigned x = 0; x < 256; ++x) shares[x] = share_bits(eval_point(cmp, key, static_cast(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(alpha ^ x0); const u64 beta = 0x55; auto on = [&](const auto & key) { std::vector 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(alpha ^ x0); const Input y = 0x7e; auto on = [&](const auto & key) { std::vector mine(256); std::vector pis(256); for (unsigned x = 0; x < 256; ++x) { mine[x] = share_bits( *eval_point(key, static_cast(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(mine[alpha] - peer[alpha]) == y, "geneval on"); require(static_cast(mine[0] - peer[0]) == Input{0}, "geneval off"); require(static_cast(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(net, self, x0, x1, y, on, on), self, verifiable_tree_prefix(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(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(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(net, self, x0, x1, beta, on, on), self, verifiable_tree_prefix(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(alpha ^ x0); const Out beta = 0x1234; const Input neighbor = static_cast(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(net, self, x0, x1, beta, on, on), self, verifiable_tree_prefix(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(alpha ^ x0); const fp61 beta{7}; auto on = [&](const auto & key) { const std::array 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(net::msg::delta); const auto from_p1 = net.to(role::p1).recv(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(net::msg::delta); const auto k1 = net.to(role::p1).recv(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(x)); const fp61 got = open_shamir3(net, self, y); require(got == (static_cast(x) == alpha ? beta : fp61{}), "dpf3 point"); } return 0; } if (self == role::p0) { using K = std::decay_t( make_dpf3(Input{}, fp61{}, verifiable{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_shamir3(net, self, eval_point(key, static_cast(x))); return 0; } using K = std::decay_t( make_dpf3(Input{}, fp61{}, verifiable{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_shamir3(net, self, eval_point(key, static_cast(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(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(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( make_dpf3(Input{}, fp61{}, updatable{})))>; auto key = recv_key(net.to(role::p2)); (void)open_shamir3(net, self, eval_point(key, alpha)); return 0; } using K = std::decay_t( make_dpf3(Input{}, fp61{}, updatable{})))>; auto key = recv_key(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(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(make_dpf3_cmp(Input{}, u64{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_cmp3(net, self, eval_point(key, static_cast(x))); return 0; } using K = std::decay_t(make_dpf3_cmp(Input{}, u64{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_cmp3(net, self, eval_point(key, static_cast(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(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(x)), map); }, [&](auto key) { for (unsigned x = 0; x < 256; ++x) { const fp61 got = open_shamir3(net, self, eval_point(key, static_cast(x)), map); require(got == (static_cast(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(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(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(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(net::msg::delta); const auto p3 = net.to(role::p1).recv(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(x)), eval_point(ic, two.second, static_cast(x))); const auto y = eval_point(k3, static_cast(x)); const fp61 got = open_cmp3(net, self, y); require(got.raw() == static_cast(want), "dpf3 ic"); } return 0; } if (self == role::p0) { using K = std::decay_t( make_dpf3_ic(Input{}, Input{}, Input{}, u64{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_cmp3(net, self, eval_point(key, static_cast(x))); return 0; } using K = std::decay_t( make_dpf3_ic(Input{}, Input{}, Input{}, u64{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_cmp3(net, self, eval_point(key, static_cast(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(x))); require(got.raw() == 0u, "dpf3 cmp update clear"); } return 0; } if (self == role::p0) { using K = std::decay_t(make_dpf3_cmp(Input{}, u64{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_cmp3(net, self, eval_point(key, static_cast(x))); return 0; } using K = std::decay_t(make_dpf3_cmp(Input{}, u64{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_cmp3(net, self, eval_point(key, static_cast(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(x))); require(got.raw() == (x < thresh ? beta : 0u), "dpf3 blocked cmp"); } return 0; } if (self == role::p0) { using K = std::decay_t( make_dpf3_cmp_blocked<4>(Input{}, u64{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_cmp3(net, self, eval_point(key, static_cast(x))); return 0; } using K = std::decay_t( make_dpf3_cmp_blocked<4>(Input{}, u64{})))>; auto key = recv_key(net.to(role::p2)); for (unsigned x = 0; x < 256; ++x) (void)open_cmp3(net, self, eval_point(key, static_cast(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 alphas{1, 2, 9, 40}; const std::vector betas{fp61{7}, fp61{11}, fp61{3}, fp61{4}}; using K1 = std::decay_t( make_multipoint3(alphas, betas, verifiable{})))>; using K2 = std::decay_t( 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(net::msg::delta); key.bucket_count = net.to(role::p2).template recv(net::msg::delta); key.bucket_domain = net.to(role::p2).template recv(net::msg::delta); const auto nb = net.to(role::p2).template recv(net::msg::delta); key.buckets.clear(); key.buckets.reserve(static_cast(nb)); using Bucket = typename Key::bucket_key; for (std::uint64_t i = 0; i < nb; ++i) key.buckets.push_back(recv_key(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(x)) want = betas[i]; const fp61 got = open_shamir3(net, self, eval_multipoint(k3, static_cast(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(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(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(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(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(x)), map); require(got == (static_cast(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(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(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(net::msg::delta); const auto p3 = net.to(role::p1).recv(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(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( 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::depth == 1, "grown depth"); require(std::decay_t::num_outputs == 2, "grown outs"); (void)key; }, [&](const auto & key) { require(std::decay_t::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