Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
170
party/aes_mmo_ref.hpp
Normal file
170
party/aes_mmo_ref.hpp
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/// @file party/aes_mmo_ref.hpp
|
||||
/// @brief Portable AES-128 MMO matching `dpf::prg::aes128::eval` on the zero key.
|
||||
/// @details Used to check an oblivious evaluation against the hardware PRG.
|
||||
/// The cipher key is the all-zero key installed by `prg::aes128`.
|
||||
|
||||
#ifndef LIBDPF_PARTY_AES_MMO_REF_HPP__
|
||||
#define LIBDPF_PARTY_AES_MMO_REF_HPP__
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "simde/simde/x86/avx2.h"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
namespace aes_ref
|
||||
{
|
||||
|
||||
inline std::uint8_t gmul(std::uint8_t a, std::uint8_t b)
|
||||
{
|
||||
std::uint8_t p = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if (b & 1u)
|
||||
p = static_cast<std::uint8_t>(p ^ a);
|
||||
const bool hi = (a & 0x80u) != 0;
|
||||
a = static_cast<std::uint8_t>(a << 1);
|
||||
if (hi)
|
||||
a = static_cast<std::uint8_t>(a ^ 0x1b);
|
||||
b = static_cast<std::uint8_t>(b >> 1);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
inline std::uint8_t ginv(std::uint8_t a)
|
||||
{
|
||||
if (a == 0)
|
||||
return 0;
|
||||
// a^254 via square-and-multiply.
|
||||
std::uint8_t r = 1;
|
||||
std::uint8_t b = a;
|
||||
for (int e = 0; e < 7; ++e)
|
||||
{
|
||||
b = gmul(b, b);
|
||||
r = gmul(r, b);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
inline std::uint8_t sbox_at(std::uint8_t x)
|
||||
{
|
||||
std::uint8_t b = ginv(x);
|
||||
std::uint8_t s = b;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
b = static_cast<std::uint8_t>((b << 1) | (b >> 7));
|
||||
s = static_cast<std::uint8_t>(s ^ b);
|
||||
}
|
||||
return static_cast<std::uint8_t>(s ^ 0x63);
|
||||
}
|
||||
|
||||
inline std::uint8_t xtime(std::uint8_t x)
|
||||
{
|
||||
return static_cast<std::uint8_t>((x << 1) ^ ((x & 0x80) ? 0x1b : 0));
|
||||
}
|
||||
|
||||
inline const std::uint8_t * sbox()
|
||||
{
|
||||
static std::uint8_t table[256];
|
||||
static bool ready = false;
|
||||
if (!ready)
|
||||
{
|
||||
for (int i = 0; i < 256; ++i)
|
||||
table[i] = sbox_at(static_cast<std::uint8_t>(i));
|
||||
ready = true;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
inline void sub_bytes(std::uint8_t s[16])
|
||||
{
|
||||
const std::uint8_t * box = sbox();
|
||||
for (int i = 0; i < 16; ++i)
|
||||
s[i] = box[s[i]];
|
||||
}
|
||||
|
||||
inline void shift_rows(std::uint8_t s[16])
|
||||
{
|
||||
std::uint8_t t = s[1];
|
||||
s[1] = s[5];
|
||||
s[5] = s[9];
|
||||
s[9] = s[13];
|
||||
s[13] = t;
|
||||
t = s[2];
|
||||
s[2] = s[10];
|
||||
s[10] = t;
|
||||
t = s[6];
|
||||
s[6] = s[14];
|
||||
s[14] = t;
|
||||
t = s[15];
|
||||
s[15] = s[11];
|
||||
s[11] = s[7];
|
||||
s[7] = s[3];
|
||||
s[3] = t;
|
||||
}
|
||||
|
||||
inline void mix_columns(std::uint8_t s[16])
|
||||
{
|
||||
for (int c = 0; c < 4; ++c)
|
||||
{
|
||||
const std::uint8_t i = static_cast<std::uint8_t>(c * 4);
|
||||
const std::uint8_t a = s[i], b = s[i + 1], c0 = s[i + 2], d = s[i + 3];
|
||||
const std::uint8_t xa = xtime(a), xb = xtime(b), xc = xtime(c0), xd = xtime(d);
|
||||
s[i] = static_cast<std::uint8_t>(xa ^ xb ^ b ^ c0 ^ d);
|
||||
s[i + 1] = static_cast<std::uint8_t>(a ^ xb ^ xc ^ c0 ^ d);
|
||||
s[i + 2] = static_cast<std::uint8_t>(a ^ b ^ xc ^ xd ^ d);
|
||||
s[i + 3] = static_cast<std::uint8_t>(xa ^ a ^ b ^ c0 ^ xd);
|
||||
}
|
||||
}
|
||||
|
||||
inline void add_round_key(std::uint8_t s[16], const std::uint8_t rk[16])
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
s[i] = static_cast<std::uint8_t>(s[i] ^ rk[i]);
|
||||
}
|
||||
|
||||
inline std::array<std::uint8_t, 16> block_bytes(simde__m128i v)
|
||||
{
|
||||
std::array<std::uint8_t, 16> b{};
|
||||
std::memcpy(b.data(), &v, 16);
|
||||
return b;
|
||||
}
|
||||
|
||||
/// @brief One MMO block: AES-128 encrypt `msg` under the zero key, then XOR `msg`.
|
||||
inline simde__m128i encrypt_mmo(simde__m128i msg, std::uint32_t pos,
|
||||
const std::array<simde__m128i, 11> & round_keys)
|
||||
{
|
||||
auto s = block_bytes(msg);
|
||||
auto rk0 = block_bytes(round_keys[0]);
|
||||
// eval XORs set_epi64x(0, pos) into rd_key[0] before the first AddRoundKey.
|
||||
rk0[0] = static_cast<std::uint8_t>(rk0[0] ^ static_cast<std::uint8_t>(pos));
|
||||
rk0[1] = static_cast<std::uint8_t>(rk0[1] ^ static_cast<std::uint8_t>(pos >> 8));
|
||||
rk0[2] = static_cast<std::uint8_t>(rk0[2] ^ static_cast<std::uint8_t>(pos >> 16));
|
||||
rk0[3] = static_cast<std::uint8_t>(rk0[3] ^ static_cast<std::uint8_t>(pos >> 24));
|
||||
add_round_key(s.data(), rk0.data());
|
||||
for (int r = 1; r < 10; ++r)
|
||||
{
|
||||
sub_bytes(s.data());
|
||||
shift_rows(s.data());
|
||||
mix_columns(s.data());
|
||||
auto rk = block_bytes(round_keys[static_cast<std::size_t>(r)]);
|
||||
add_round_key(s.data(), rk.data());
|
||||
}
|
||||
sub_bytes(s.data());
|
||||
shift_rows(s.data());
|
||||
auto last = block_bytes(round_keys[10]);
|
||||
add_round_key(s.data(), last.data());
|
||||
simde__m128i out{};
|
||||
std::memcpy(&out, s.data(), 16);
|
||||
return simde_mm_xor_si128(out, msg);
|
||||
}
|
||||
|
||||
} // namespace aes_ref
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
|
||||
#endif
|
||||
6
party/aes_sbox_bp.hpp
Normal file
6
party/aes_sbox_bp.hpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/// @file party/aes_sbox_bp.hpp
|
||||
/// @brief Compatibility include. The S-box table lives in dpf/aes_sbox_bp.hpp.
|
||||
#ifndef LIBDPF_PARTY_AES_SBOX_BP_HPP__
|
||||
#define LIBDPF_PARTY_AES_SBOX_BP_HPP__
|
||||
#include "dpf/aes_sbox_bp.hpp"
|
||||
#endif
|
||||
46
party/cases.hpp
Normal file
46
party/cases.hpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/// @file party/cases.hpp
|
||||
/// @brief CLI and flow registration for (2+1) party processes.
|
||||
#ifndef LIBDPF_PARTY_CASES_HPP__
|
||||
#define LIBDPF_PARTY_CASES_HPP__
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "dpf/net/trio.hpp"
|
||||
#include "registry.hpp"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
|
||||
void register_core_flows();
|
||||
void register_bulk_flows();
|
||||
void register_extreme_flows();
|
||||
void register_recent_flows();
|
||||
void register_coverage_flows();
|
||||
void register_gadget_flows();
|
||||
|
||||
inline void register_all_flows()
|
||||
{
|
||||
static bool once = false;
|
||||
if (once)
|
||||
return;
|
||||
once = true;
|
||||
register_core_flows();
|
||||
register_bulk_flows();
|
||||
register_extreme_flows();
|
||||
register_recent_flows();
|
||||
register_coverage_flows();
|
||||
register_gadget_flows();
|
||||
}
|
||||
|
||||
/// @brief Parse argv and run. Supports:
|
||||
/// --dir DIR --case NAME [--repeat N] [--warmup W] [--metrics]
|
||||
/// --dir DIR --tag TAGS [--repeat N] [--warmup W] [--metrics]
|
||||
/// --list [--tag TAGS]
|
||||
int run(net::role self, int argc, char ** argv);
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
|
||||
#endif // LIBDPF_PARTY_CASES_HPP__
|
||||
503
party/dist_dpf3.hpp
Normal file
503
party/dist_dpf3.hpp
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
/// @file party/dist_dpf3.hpp
|
||||
/// @brief Networked (2,3) Shamir DPF keygen (three key holders).
|
||||
/// @details A pure two-party variant is not meaningful: after keygen each of
|
||||
/// three parties must hold a Shamir share of the evaluation. Use
|
||||
/// `dist_with_*_iknp` in `iknp_deal.hpp` for two-party VDPF keygen
|
||||
/// without a pad dealer.
|
||||
/// @brief Distributed dual-spine Doerner–Shelat keygen for (2,3) point keys.
|
||||
/// @details Two `deal_point` / `point_party` runs (spines A and B) with Fig-3
|
||||
/// `τ` payloads. p0 samples `τ` and `π`; overlapping halves are shipped
|
||||
/// so p0 holds party-1, p2 holds party-2, p1 holds party-3.
|
||||
/// Produced keys are always verifiable. Default spines keep `α` as XOR
|
||||
/// shares (F_DPF3DS): `π` is peeled from the leaf seed of the shared
|
||||
/// path, not from an opened point. p0 sends p1 only its `τ` halves.
|
||||
/// Pass `RevealPoint=true` only when the caller wants the tree prefix
|
||||
/// (and packed lane on updatable keys). Pass `dpf::updatable` for
|
||||
/// beaver leaves and a later networked Fig-10 update via
|
||||
/// `dist_update_payload`.
|
||||
|
||||
#ifndef LIBDPF_PARTY_DIST_DPF3_HPP__
|
||||
#define LIBDPF_PARTY_DIST_DPF3_HPP__
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "hedley/hedley.h"
|
||||
|
||||
#include "dist_ds.hpp"
|
||||
#include "key_io.hpp"
|
||||
|
||||
#include "dpf/dpf3.hpp"
|
||||
#include "dpf/dpf3_ds.hpp"
|
||||
#include "dpf/fp61.hpp"
|
||||
#include "dpf/shamir3.hpp"
|
||||
#include "dpf/wildcard.hpp"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
|
||||
/// @brief How trio roles map onto Shamir party indices `{1,2,3}`.
|
||||
/// @details **dealer**: p0→1, p1→2, p2→3 (trusted keygen on p2, then ship).
|
||||
/// **dist**: p0→1, p2→2, p1→3 (`dist_with_dpf3_key` assembly).
|
||||
enum class dpf3_role_map : unsigned
|
||||
{
|
||||
dealer = 0,
|
||||
dist = 1,
|
||||
};
|
||||
|
||||
/// @brief Shamir party index for a trio role under `map`.
|
||||
HEDLEY_CONST
|
||||
HEDLEY_NO_THROW
|
||||
constexpr int dpf3_party_of(role r, dpf3_role_map map) noexcept
|
||||
{
|
||||
if (map == dpf3_role_map::dist)
|
||||
{
|
||||
if (r == role::p0)
|
||||
return 1;
|
||||
if (r == role::p2)
|
||||
return 2;
|
||||
return 3; // p1
|
||||
}
|
||||
// dealer
|
||||
if (r == role::p0)
|
||||
return 1;
|
||||
if (r == role::p1)
|
||||
return 2;
|
||||
return 3; // p2
|
||||
}
|
||||
|
||||
/// @brief Public VDPF+ offsets for one (2,3) keygen.
|
||||
struct dpf3_pi_msg
|
||||
{
|
||||
shamir3::xor61 pi_a{};
|
||||
shamir3::xor61 pi_b{};
|
||||
};
|
||||
|
||||
/// @brief Fig-10 patch broadcast (leaf patches so p2 need not learn `α`).
|
||||
template <typename Leaf>
|
||||
struct dpf3_fig10_msg
|
||||
{
|
||||
Leaf patch_a{};
|
||||
Leaf patch_b{};
|
||||
shamir3::xor61 pi_a{};
|
||||
shamir3::xor61 pi_b{};
|
||||
};
|
||||
|
||||
/// @brief p1's τ halves for spines A and B (party-3 strings). Does not reveal β.
|
||||
struct dpf3_tau_share
|
||||
{
|
||||
shamir3::xor61 t1{};
|
||||
shamir3::xor61 t3{};
|
||||
};
|
||||
|
||||
namespace detail_dist_dpf3
|
||||
{
|
||||
|
||||
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
|
||||
typename OutputT>
|
||||
using spine_out0 = decltype(dist::point_party<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, role::p0>(std::declval<trio &>(), std::declval<InputT>(),
|
||||
std::declval<OutputT>()));
|
||||
|
||||
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
|
||||
typename OutputT>
|
||||
using spine_key0 = spine_out0<InteriorPRG, ExteriorPRG, InputT, OutputT>;
|
||||
|
||||
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
|
||||
typename OutputT>
|
||||
using spine_out1 = decltype(dist::point_party<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, role::p1>(std::declval<trio &>(), std::declval<InputT>(),
|
||||
std::declval<OutputT>()));
|
||||
|
||||
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
|
||||
typename OutputT>
|
||||
using spine_key1 = spine_out1<InteriorPRG, ExteriorPRG, InputT, OutputT>;
|
||||
|
||||
template <typename Key, typename Share>
|
||||
void assign_wildcard_over_link(Key & key, Share my_share, trio & net, role self)
|
||||
{
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
auto & wrap = std::get<0>(key.leaf_nodes);
|
||||
auto blinded = wrap.compute_and_get_blinded_output_share(my_share);
|
||||
auto peer_blinded = net.exchange_with(peer, blinded);
|
||||
auto leaf = wrap.compute_and_get_leaf_share(peer_blinded);
|
||||
auto peer_leaf = net.exchange_with(peer, leaf);
|
||||
wrap.reconstruct_correction_word(peer_leaf);
|
||||
}
|
||||
|
||||
/// @brief Assign XOR payload from each party's τ half (payload never opened).
|
||||
template <typename Key>
|
||||
void assign_spine_tau_halves(trio & net, role self, Key & key,
|
||||
shamir3::xor61 my_half)
|
||||
{
|
||||
assign_wildcard_over_link(key, my_half, net, self);
|
||||
}
|
||||
|
||||
template <typename Key, typename Leaf>
|
||||
void apply_fig10_leaves(Key & key, const dpf3_fig10_msg<Leaf> & msg)
|
||||
{
|
||||
detail::dpf3_impl::apply_leaf_patch(key.a.dpf_key, msg.patch_a);
|
||||
detail::dpf3_impl::apply_leaf_patch(key.b.dpf_key, msg.patch_b);
|
||||
}
|
||||
|
||||
template <typename Key, typename Leaf>
|
||||
void apply_fig10(Key & key, const dpf3_fig10_msg<Leaf> & msg)
|
||||
{
|
||||
apply_fig10_leaves(key, msg);
|
||||
key.a.offset = msg.pi_a;
|
||||
key.b.offset = msg.pi_b;
|
||||
}
|
||||
|
||||
template <typename Key, typename Node>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
shamir3::xor61 peel_from_seed(const Key & key, const Node & seed)
|
||||
{
|
||||
const auto y = key.template traverse_exterior<0>(seed);
|
||||
using Y = std::decay_t<decltype(y)>;
|
||||
if constexpr (is_secret_share_v<Y>)
|
||||
return shamir3::xor61{y.raw()};
|
||||
else if constexpr (std::is_integral_v<Y>
|
||||
|| std::is_convertible_v<Y, std::uint64_t>)
|
||||
return shamir3::xor61{static_cast<std::uint64_t>(y)};
|
||||
else
|
||||
{
|
||||
std::uint64_t w = 0;
|
||||
static_assert(sizeof(Y) >= sizeof(w),
|
||||
"peel_from_seed: leaf narrower than xor61");
|
||||
std::memcpy(&w, &y, sizeof(w));
|
||||
return shamir3::xor61{w};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InputT>
|
||||
struct dpf3_opened
|
||||
{
|
||||
InputT opened_prefix{};
|
||||
/// Meaningful when the spine payload is a packed wildcard.
|
||||
unsigned opened_lane = 0;
|
||||
bool lane_opened = false;
|
||||
};
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_nested_key : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_nested_key<T, std::void_t<decltype(std::declval<T &>().key)>>
|
||||
: std::true_type {};
|
||||
template <typename T>
|
||||
inline constexpr bool has_nested_key_v = has_nested_key<T>::value;
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_opened_prefix : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_opened_prefix<T,
|
||||
std::void_t<decltype(std::declval<const T &>().opened_prefix)>>
|
||||
: std::true_type {};
|
||||
template <typename T>
|
||||
inline constexpr bool has_opened_prefix_v = has_opened_prefix<T>::value;
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_opened_lane : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_opened_lane<T,
|
||||
std::void_t<decltype(std::declval<const T &>().opened_lane)>>
|
||||
: std::true_type {};
|
||||
template <typename T>
|
||||
inline constexpr bool has_opened_lane_v = has_opened_lane<T>::value;
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_member_dpf_key : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_member_dpf_key<T, std::void_t<decltype(std::declval<T &>().dpf_key)>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename Held>
|
||||
auto spine_key_of(Held && held)
|
||||
{
|
||||
using H = std::decay_t<Held>;
|
||||
if constexpr (has_member_dpf_key<H>::value)
|
||||
return std::move(held.dpf_key);
|
||||
else if constexpr (has_nested_key_v<H>)
|
||||
return std::move(held.key);
|
||||
else
|
||||
return std::move(held);
|
||||
}
|
||||
|
||||
template <typename Held, typename InputT>
|
||||
void note_opened(dpf3_opened<InputT> & learned, const Held & held)
|
||||
{
|
||||
if constexpr (has_opened_prefix_v<std::decay_t<Held>>)
|
||||
{
|
||||
learned.opened_prefix = held.opened_prefix;
|
||||
if constexpr (has_opened_lane_v<std::decay_t<Held>>)
|
||||
{
|
||||
learned.opened_lane = held.opened_lane;
|
||||
learned.lane_opened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
|
||||
typename OutputT, bool Updatable, bool RevealPoint, typename Fn1,
|
||||
typename Fn2, typename Fn3>
|
||||
std::optional<dpf3_opened<InputT>> dist_with_dpf3_key_impl(trio & net, role self,
|
||||
InputT x0, InputT x1,
|
||||
const fp61 beta, Fn1 && on1, Fn2 && on2, Fn3 && on3)
|
||||
{
|
||||
using X = shamir3::xor61;
|
||||
using tau_quad = detail::dpf3_impl::tau_quad;
|
||||
using Key0 = spine_key0<InteriorPRG, ExteriorPRG, InputT, OutputT>;
|
||||
using Key1 = spine_key1<InteriorPRG, ExteriorPRG, InputT, OutputT>;
|
||||
using node = typename dpf::tree_traits<InteriorPRG>::node;
|
||||
constexpr bool V = true; // dist point_party always opens correction seeds
|
||||
|
||||
if (self == role::p2)
|
||||
{
|
||||
dist::deal_point<InteriorPRG, InputT, OutputT, !RevealPoint>(net);
|
||||
dist::deal_point<InteriorPRG, InputT, OutputT, !RevealPoint>(net);
|
||||
const auto pi = net.recv_from<dpf3_pi_msg>(role::p0, net::msg::delta);
|
||||
auto key_b0 = recv_key<Key0>(net, role::p0);
|
||||
auto key_a1 = recv_key<Key1>(net, role::p1);
|
||||
detail::dpf3_impl::vdpf_plus_key<Key1> plus_a1{std::move(key_a1),
|
||||
pi.pi_a};
|
||||
detail::dpf3_impl::vdpf_plus_key<Key0> plus_b0{std::move(key_b0),
|
||||
pi.pi_b};
|
||||
dpf3_key<2, decltype(plus_a1), decltype(plus_b0)> k2{
|
||||
std::move(plus_a1), std::move(plus_b0), V, false, Updatable};
|
||||
std::forward<Fn2>(on2)(std::move(k2));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// p0 samples τ from a Shamir split of β; p1 receives only (t1, t3).
|
||||
tau_quad t{};
|
||||
X my_a{};
|
||||
X my_b{};
|
||||
if (self == role::p0)
|
||||
{
|
||||
t = detail::dpf3_impl::sample_taus(beta);
|
||||
my_a = t.t0;
|
||||
my_b = t.t2;
|
||||
net.send_to(role::p1, net::msg::delta, dpf3_tau_share{t.t1, t.t3});
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto sh = net.recv_from<dpf3_tau_share>(role::p0, net::msg::delta);
|
||||
my_a = sh.t1;
|
||||
my_b = sh.t3;
|
||||
t.t1 = sh.t1;
|
||||
t.t3 = sh.t3;
|
||||
}
|
||||
|
||||
dpf3_opened<InputT> learned{};
|
||||
node seed_a{};
|
||||
node seed_b{};
|
||||
|
||||
// Non-updatable and updatable both plant beaver leaves; τ halves are
|
||||
// assigned without opening the spine payload. The outer Updatable flag
|
||||
// only gates Fig-10.
|
||||
// Spines still use per-message `exchange_with`. Wiring RoundSink here
|
||||
// needs a round budget that covers wild+hash leaf mux; undersizing hangs
|
||||
// the peer on `msg::round_batch`.
|
||||
OutputT wild{};
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto out_a0 = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, role::p0, false, RevealPoint>(net, x0, wild, false,
|
||||
&seed_a);
|
||||
note_opened(learned, out_a0);
|
||||
auto key_a0 = spine_key_of(std::move(out_a0));
|
||||
auto out_b0 = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, role::p0, false, RevealPoint>(net, x0, wild, false,
|
||||
&seed_b);
|
||||
auto key_b0 = spine_key_of(std::move(out_b0));
|
||||
assign_spine_tau_halves(net, self, key_a0, my_a);
|
||||
assign_spine_tau_halves(net, self, key_b0, my_b);
|
||||
dpf3_pi_msg pi{};
|
||||
pi.pi_a = t.t0 + peel_from_seed(key_a0, seed_a);
|
||||
pi.pi_b = t.t2 + peel_from_seed(key_b0, seed_b);
|
||||
net.send_to(role::p1, net::msg::delta, pi);
|
||||
net.send_to(role::p2, net::msg::delta, pi);
|
||||
send_key(net, role::p2, key_b0);
|
||||
detail::dpf3_impl::vdpf_plus_key<Key0> plus_a0{std::move(key_a0),
|
||||
pi.pi_a};
|
||||
detail::dpf3_impl::vdpf_plus_key<Key0> plus_b0{std::move(key_b0),
|
||||
pi.pi_b};
|
||||
dpf3_key<1, decltype(plus_a0), decltype(plus_b0)> k1{
|
||||
std::move(plus_a0), std::move(plus_b0), V, false, Updatable};
|
||||
std::forward<Fn1>(on1)(std::move(k1));
|
||||
if constexpr (RevealPoint)
|
||||
return learned;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto out_a1 = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, role::p1, false, RevealPoint>(net, x1, wild, false,
|
||||
&seed_a);
|
||||
note_opened(learned, out_a1);
|
||||
auto key_a1 = spine_key_of(std::move(out_a1));
|
||||
auto out_b1 = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, role::p1, false, RevealPoint>(net, x1, wild, false,
|
||||
&seed_b);
|
||||
auto key_b1 = spine_key_of(std::move(out_b1));
|
||||
assign_spine_tau_halves(net, self, key_a1, my_a);
|
||||
assign_spine_tau_halves(net, self, key_b1, my_b);
|
||||
const auto pi = net.recv_from<dpf3_pi_msg>(role::p0, net::msg::delta);
|
||||
send_key(net, role::p2, key_a1);
|
||||
detail::dpf3_impl::vdpf_plus_key<Key1> plus_a1{std::move(key_a1),
|
||||
pi.pi_a};
|
||||
detail::dpf3_impl::vdpf_plus_key<Key1> plus_b1{std::move(key_b1),
|
||||
pi.pi_b};
|
||||
dpf3_key<3, decltype(plus_a1), decltype(plus_b1)> k3{
|
||||
std::move(plus_a1), std::move(plus_b1), V, false, Updatable};
|
||||
std::forward<Fn3>(on3)(std::move(k3));
|
||||
if constexpr (RevealPoint)
|
||||
return learned;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace detail_dist_dpf3
|
||||
|
||||
/// @brief Distributed dual-spine (2,3) keygen over the trio.
|
||||
/// @details Role map after assembly: **p0 → party 1**, **p2 → party 2**,
|
||||
/// **p1 → party 3**. Keys are always verifiable. Each computing
|
||||
/// party's view of `α` is only its XOR share; p1's view of `β` is
|
||||
/// only its τ halves (not a clear payload). Default return is empty.
|
||||
/// @tparam RevealPoint when true, p0/p1 also reconstruct the tree prefix
|
||||
/// @return Opened prefix when `RevealPoint`, else empty. Empty on p2.
|
||||
/// @throws std::runtime_error if a frame is truncated or tagged wrong
|
||||
/// \complexity Two `point_party` spines, so the local work is two O(n) walks, plus O(1) τ arithmetic.
|
||||
/// \rounds The rounds of two `point_party` calls, then one τ-share send (p0 to p1), one `dpf3_pi_msg` to p1 and p2, and one key send to p2. p2 runs two `deal_point` calls first. Counted in `dist_with_dpf3_key_impl`. Fig-10 update is `dist_update_payload`, not this function.
|
||||
/// \communication Two dealer tapes (see `deal_point` / `point_party`), one `dpf3_tau_share`, one `dpf3_pi_msg` (two `xor61` values), and one key blob (`send_key`, `sizeof` of the spine key).
|
||||
/// \preprocessing p2's `deal_point` pads for both spines. p0 samples the four τ strings locally from a Shamir split of β.
|
||||
template <typename InteriorPRG = dpf::prg::aes128,
|
||||
typename ExteriorPRG = InteriorPRG,
|
||||
bool RevealPoint = false,
|
||||
typename InputT,
|
||||
typename Fn1,
|
||||
typename Fn2,
|
||||
typename Fn3>
|
||||
[[nodiscard]] std::optional<InputT> dist_with_dpf3_key(trio & net, role self,
|
||||
InputT x0, InputT x1, const fp61 beta, Fn1 && on1, Fn2 && on2, Fn3 && on3)
|
||||
{
|
||||
auto opened = detail_dist_dpf3::dist_with_dpf3_key_impl<InteriorPRG,
|
||||
ExteriorPRG, InputT, dpf::wildcard_value<shamir3::xor61>, false,
|
||||
RevealPoint>(net, self, x0, x1, beta, std::forward<Fn1>(on1),
|
||||
std::forward<Fn2>(on2), std::forward<Fn3>(on3));
|
||||
if (!opened)
|
||||
return std::nullopt;
|
||||
return opened->opened_prefix;
|
||||
}
|
||||
|
||||
/// @brief What an updatable (2,3) keygen reconstructs when `RevealPoint`.
|
||||
template <typename InputT>
|
||||
struct dpf3_updatable_opened
|
||||
{
|
||||
InputT opened_prefix{};
|
||||
unsigned opened_lane = 0;
|
||||
};
|
||||
|
||||
/// @brief Distributed dual-spine (2,3) keygen with beaver leaves (Fig-10-ready).
|
||||
/// @tparam RevealPoint when true, return prefix and packed lane on p0/p1
|
||||
/// @return Opened values when `RevealPoint`, else empty. Empty on p2.
|
||||
/// \complexity Two `point_party` spines, so the local work is two O(n) walks, plus O(1) τ arithmetic.
|
||||
/// \rounds The rounds of two `point_party` calls, then one τ-share send (p0 to p1), one `dpf3_pi_msg` to p1 and p2, and one key send to p2. p2 runs two `deal_point` calls first. Counted in `dist_with_dpf3_key_impl`. Fig-10 update is `dist_update_payload`, not this function.
|
||||
/// \communication Two dealer tapes (see `deal_point` / `point_party`), one `dpf3_tau_share`, one `dpf3_pi_msg` (two `xor61` values), and one key blob (`send_key`, `sizeof` of the spine key).
|
||||
/// \preprocessing p2's `deal_point` pads for both spines. p0 samples the four τ strings locally from a Shamir split of β.
|
||||
template <typename InteriorPRG = dpf::prg::aes128,
|
||||
typename ExteriorPRG = InteriorPRG,
|
||||
bool RevealPoint = false,
|
||||
typename InputT,
|
||||
typename Fn1,
|
||||
typename Fn2,
|
||||
typename Fn3>
|
||||
[[nodiscard]] std::optional<dpf3_updatable_opened<InputT>> dist_with_dpf3_key(
|
||||
trio & net, role self, InputT x0, InputT x1, const fp61 beta, updatable,
|
||||
Fn1 && on1, Fn2 && on2, Fn3 && on3)
|
||||
{
|
||||
auto opened = detail_dist_dpf3::dist_with_dpf3_key_impl<InteriorPRG,
|
||||
ExteriorPRG, InputT, dpf::wildcard_value<shamir3::xor61>, true,
|
||||
RevealPoint>(net, self, x0, x1, beta, std::forward<Fn1>(on1),
|
||||
std::forward<Fn2>(on2), std::forward<Fn3>(on3));
|
||||
if (!opened)
|
||||
return std::nullopt;
|
||||
return dpf3_updatable_opened<InputT>{opened->opened_prefix,
|
||||
opened->opened_lane};
|
||||
}
|
||||
|
||||
/// @brief Networked Fig-10 payload update for keys from `dist_with_dpf3_key`.
|
||||
/// @details p0 and p1 (who are given `α`) exchange peels, p0 samples fresh `τ`
|
||||
/// and broadcasts leaf patches + new public `π`. p2 applies patches
|
||||
/// without learning `α`. Requires `key.updatable`.
|
||||
/// @throws std::invalid_argument if the key is not updatable
|
||||
template <typename Key, typename InputT>
|
||||
void dist_update_payload(trio & net, role self, Key & key, InputT alpha,
|
||||
fp61 beta_new)
|
||||
{
|
||||
static_assert(Key::is_dpf3, "dist_update_payload: dpf3 key");
|
||||
if (!key.updatable)
|
||||
throw std::invalid_argument(
|
||||
"dist_update_payload: key was not generated with dpf::updatable");
|
||||
|
||||
using X = shamir3::xor61;
|
||||
using InnerA = typename Key::plus_a_type::inner_type;
|
||||
using Leaf = decltype(detail::dpf3_impl::make_leaf_patch<InnerA>(alpha,
|
||||
X{}));
|
||||
using Msg = dpf3_fig10_msg<Leaf>;
|
||||
|
||||
struct peel_pair
|
||||
{
|
||||
X peel_a{};
|
||||
X peel_b{};
|
||||
};
|
||||
|
||||
Msg msg{};
|
||||
if (self == role::p0 || self == role::p1)
|
||||
{
|
||||
peel_pair mine{detail::dpf3_impl::peel(key.a.dpf_key, alpha),
|
||||
detail::dpf3_impl::peel(key.b.dpf_key, alpha)};
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
const peel_pair theirs = net.exchange_with(peer, mine);
|
||||
|
||||
if (self == role::p0)
|
||||
{
|
||||
// p0 = party 1 (A0,B0); p1 = party 3 (A1,B1).
|
||||
// Leaf CW patches only move the half whose path control bit is set;
|
||||
// compute π from a post-patch peel (same as dealer refresh_offset).
|
||||
detail::dpf3_impl::tau_quad told{};
|
||||
told.t0 = mine.peel_a + key.a.offset;
|
||||
told.t1 = theirs.peel_a + key.a.offset;
|
||||
told.t2 = mine.peel_b + key.b.offset;
|
||||
told.t3 = theirs.peel_b + key.b.offset;
|
||||
const auto tnew = detail::dpf3_impl::sample_taus(beta_new);
|
||||
const X dA = (tnew.t0 + tnew.t1) + (told.t0 + told.t1);
|
||||
const X dB = (tnew.t2 + tnew.t3) + (told.t2 + told.t3);
|
||||
msg.patch_a =
|
||||
detail::dpf3_impl::make_leaf_patch<InnerA>(alpha, dA);
|
||||
msg.patch_b =
|
||||
detail::dpf3_impl::make_leaf_patch<InnerA>(alpha, dB);
|
||||
detail_dist_dpf3::apply_fig10_leaves(key, msg);
|
||||
msg.pi_a = tnew.t0 + detail::dpf3_impl::peel(key.a.dpf_key, alpha);
|
||||
msg.pi_b = tnew.t2 + detail::dpf3_impl::peel(key.b.dpf_key, alpha);
|
||||
key.a.offset = msg.pi_a;
|
||||
key.b.offset = msg.pi_b;
|
||||
net.send_to(role::p1, net::msg::delta, msg);
|
||||
net.send_to(role::p2, net::msg::delta, msg);
|
||||
return;
|
||||
}
|
||||
msg = net.recv_from<Msg>(role::p0, net::msg::delta);
|
||||
detail_dist_dpf3::apply_fig10(key, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
msg = net.recv_from<Msg>(role::p0, net::msg::delta);
|
||||
detail_dist_dpf3::apply_fig10(key, msg);
|
||||
}
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
|
||||
#endif // LIBDPF_PARTY_DIST_DPF3_HPP__
|
||||
2726
party/dist_ds.hpp
Normal file
2726
party/dist_ds.hpp
Normal file
File diff suppressed because it is too large
Load diff
810
party/flow_util.hpp
Normal file
810
party/flow_util.hpp
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
/// @file party/flow_util.hpp
|
||||
/// @brief Shared helpers for (2+1) party flow bodies.
|
||||
#ifndef LIBDPF_PARTY_FLOW_UTIL_HPP__
|
||||
#define LIBDPF_PARTY_FLOW_UTIL_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/beaver.hpp"
|
||||
#include "dpf/compose.hpp"
|
||||
#include "dpf/net/mux_sink.hpp" // trio::batch default
|
||||
#include "dpf/net/party_tape_io.hpp"
|
||||
#include "dpf/net/round_sink.hpp"
|
||||
#include "dpf/net/trio.hpp"
|
||||
#include "dpf/secret_share.hpp"
|
||||
#include "dpf/verifiable.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
namespace util
|
||||
{
|
||||
|
||||
using net::role;
|
||||
using net::trio;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
struct Counter
|
||||
{
|
||||
int draws = 0;
|
||||
u64 operator()()
|
||||
{
|
||||
++draws;
|
||||
return 0x9e3779b97f4a7c15ull * static_cast<u64>(draws);
|
||||
}
|
||||
};
|
||||
|
||||
inline void require(bool cond, const char * msg)
|
||||
{
|
||||
if (!cond)
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
inline std::pair<u64, u64> split_u64(u64 secret, unsigned tag = 1)
|
||||
{
|
||||
u64 p0 = 0x9e3779b97f4a7c15ull * (tag + 1u);
|
||||
return {p0, secret - p0};
|
||||
}
|
||||
|
||||
template <typename Ring>
|
||||
std::pair<Ring, Ring> split_ring(Ring secret, unsigned tag = 1)
|
||||
{
|
||||
using traits = beavers::ring_traits<Ring>;
|
||||
Ring p0 = Ring{static_cast<u64>(0x9e3779b97f4a7c15ull * (tag + 1u))};
|
||||
return {p0, traits::sub(secret, p0)};
|
||||
}
|
||||
|
||||
template <typename Ring>
|
||||
Ring open_additive(trio & net, role self, Ring mine)
|
||||
{
|
||||
role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
return net.open_with(peer, mine);
|
||||
}
|
||||
|
||||
/// @brief Open many independent additive shares in one vector exchange.
|
||||
template <typename Ring>
|
||||
std::vector<Ring> open_additive(trio & net, role self, const std::vector<Ring> & mine)
|
||||
{
|
||||
role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
return net.open_vec_with(peer, mine);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T open_subtractive(trio & net, role self, T mine)
|
||||
{
|
||||
role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
T theirs = net.exchange_with(peer, mine);
|
||||
return self == role::p0 ? static_cast<T>(mine - theirs)
|
||||
: static_cast<T>(theirs - mine);
|
||||
}
|
||||
|
||||
/// @brief Open many independent subtractive shares in one vector exchange.
|
||||
template <typename T>
|
||||
std::vector<T> open_subtractive(trio & net, role self, const std::vector<T> & mine)
|
||||
{
|
||||
role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
auto theirs = net.exchange_vec_with(peer, mine);
|
||||
require(theirs.size() == mine.size(), "open_subtractive vec size");
|
||||
std::vector<T> out(mine.size());
|
||||
for (std::size_t i = 0; i < mine.size(); ++i)
|
||||
{
|
||||
out[i] = self == role::p0 ? static_cast<T>(mine[i] - theirs[i])
|
||||
: static_cast<T>(theirs[i] - mine[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T share_bits(T s) noexcept
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename T, std::size_t Party, sharing Scheme>
|
||||
constexpr T share_bits(const secret_share<T, Party, Scheme> & s) noexcept
|
||||
{
|
||||
return s.raw();
|
||||
}
|
||||
|
||||
inline void install_and_bind_u64(beavers::session<u64> & s, trio & net, role self,
|
||||
const std::vector<beavers::session<u64>::wire> & inputs,
|
||||
const std::vector<u64> & secrets)
|
||||
{
|
||||
auto tape = net::accept_session<u64>(net);
|
||||
s.install_party(self == role::p0 ? 0u : 1u, tape);
|
||||
for (std::size_t i = 0; i < inputs.size(); ++i)
|
||||
{
|
||||
auto [p0, p1] = split_u64(secrets[i], static_cast<unsigned>(i + 1));
|
||||
s.bind_party(inputs[i], self == role::p0 ? p0 : p1);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Ring>
|
||||
void install_and_bind(beavers::session<Ring> & s, trio & net, role self,
|
||||
const std::vector<typename beavers::session<Ring>::wire> & inputs,
|
||||
const std::vector<Ring> & secrets)
|
||||
{
|
||||
auto tape = net::accept_session<Ring>(net);
|
||||
s.install_party(self == role::p0 ? 0u : 1u, tape);
|
||||
for (std::size_t i = 0; i < inputs.size(); ++i)
|
||||
{
|
||||
auto [p0, p1] = split_ring<Ring>(secrets[i], static_cast<unsigned>(i + 1));
|
||||
s.bind_party(inputs[i], self == role::p0 ? p0 : p1);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Host `evaluate_party_batch` on a RoundSink (one instance).
|
||||
/// @details Each vector exchange is one schedule round. The sink must have been
|
||||
/// sized with enough rounds and `slot_bytes >= 4 + n * sizeof(Ring)`
|
||||
/// for the largest batch this session will open. `index` selects the
|
||||
/// instance lane when the sink holds many circuits; all parties must
|
||||
/// drive the same `index` sequence. Prefer `count == 1` sinks.
|
||||
template <typename Ring>
|
||||
void evaluate_online_on_sink(beavers::session<Ring> & s, net::RoundSink & sink,
|
||||
std::size_t index = 0)
|
||||
{
|
||||
std::uint16_t flush_round = 0;
|
||||
s.evaluate_party_batch([&](std::vector<Ring> mine) {
|
||||
if (flush_round >= sink.rounds())
|
||||
throw std::runtime_error("evaluate_online_on_sink: out of rounds");
|
||||
const std::size_t slot = sink.slot_bytes(flush_round);
|
||||
const std::size_t need = sizeof(std::uint32_t) + mine.size() * sizeof(Ring);
|
||||
if (need > slot)
|
||||
throw std::runtime_error("evaluate_online_on_sink: slot too small");
|
||||
std::vector<std::uint8_t> buf(slot, 0);
|
||||
const std::uint32_t n = static_cast<std::uint32_t>(mine.size());
|
||||
std::memcpy(buf.data(), &n, 4);
|
||||
if (!mine.empty())
|
||||
std::memcpy(buf.data() + 4, mine.data(), mine.size() * sizeof(Ring));
|
||||
sink.submit(flush_round, index, buf.data(), buf.size());
|
||||
sink.flush_round(flush_round);
|
||||
sink.poll();
|
||||
if (!sink.peer_ready(flush_round, index))
|
||||
throw std::runtime_error("evaluate_online_on_sink: peer missing");
|
||||
std::vector<std::uint8_t> peer_buf(slot);
|
||||
sink.read_peer(flush_round, index, peer_buf.data(), slot);
|
||||
std::uint32_t pn = 0;
|
||||
std::memcpy(&pn, peer_buf.data(), 4);
|
||||
if (pn != n)
|
||||
throw std::runtime_error("evaluate_online_on_sink: peer size");
|
||||
std::vector<Ring> peer(n);
|
||||
if (n != 0)
|
||||
std::memcpy(peer.data(), peer_buf.data() + 4, n * sizeof(Ring));
|
||||
++flush_round;
|
||||
return peer;
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief Sink round budget for `evaluate_party_batch` / auth-batch.
|
||||
/// @details One input flush and one output flush per ready-round, plus a small
|
||||
/// slack for same-round dependency flushes. Must not grow with
|
||||
/// `wire_count`: a 2k-wire depth-1 circuit still needs only a handful
|
||||
/// of vector exchanges, and allocating O(wires) round windows is a
|
||||
/// multi-megabyte tax on every online evaluate.
|
||||
inline int beaver_batch_round_budget(int max_ready_round,
|
||||
std::size_t /*wire_count*/) noexcept
|
||||
{
|
||||
return std::max(1, max_ready_round * 2 + 16);
|
||||
}
|
||||
|
||||
/// @brief Online beaver opens hosted on a count-1 batch sink from `trio::batch`.
|
||||
template <typename Ring>
|
||||
void evaluate_online(beavers::session<Ring> & s, trio & net, role self)
|
||||
{
|
||||
(void)self;
|
||||
const int rounds =
|
||||
beaver_batch_round_budget(s.max_ready_round(), s.wire_count());
|
||||
// Upper bound: every wire δ in one vector exchange, plus length prefix.
|
||||
const std::size_t slot =
|
||||
sizeof(std::uint32_t) + s.wire_count() * sizeof(Ring);
|
||||
std::vector<std::size_t> slots(static_cast<std::size_t>(rounds), slot);
|
||||
auto sink = net.batch(1, std::move(slots));
|
||||
evaluate_online_on_sink(s, *sink, 0);
|
||||
}
|
||||
|
||||
/// @brief Drive many independent circuits on one RoundSink (one index each).
|
||||
/// @details Each circuit runs to completion on its lane. Callers that need a
|
||||
/// single prefix flush across instances must submit lockstep themselves
|
||||
/// via `schedule_session`; this helper is the portable multi-lane host
|
||||
/// of `evaluate_party_batch` on an existing sink.
|
||||
template <typename Ring>
|
||||
void evaluate_online_multi_on_sink(
|
||||
std::vector<beavers::session<Ring> *> sessions, net::RoundSink & sink)
|
||||
{
|
||||
if (sessions.size() != sink.count())
|
||||
throw std::invalid_argument("evaluate_online_multi_on_sink: count");
|
||||
for (std::size_t i = 0; i < sessions.size(); ++i)
|
||||
{
|
||||
if (!sessions[i])
|
||||
throw std::invalid_argument("evaluate_online_multi_on_sink: null");
|
||||
evaluate_online_on_sink(*sessions[i], sink, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Authenticated `evaluate_party_auth_batch` on a RoundSink.
|
||||
template <typename Ring>
|
||||
void evaluate_online_auth_on_sink(beavers::session<Ring> & s,
|
||||
net::RoundSink & sink, std::size_t index = 0)
|
||||
{
|
||||
using opening = beavers::auth_opening<Ring>;
|
||||
std::uint16_t flush_round = 0;
|
||||
s.evaluate_party_auth_batch([&](std::vector<opening> mine) {
|
||||
if (flush_round >= sink.rounds())
|
||||
throw std::runtime_error("evaluate_online_auth_on_sink: out of rounds");
|
||||
const std::size_t slot = sink.slot_bytes(flush_round);
|
||||
const std::size_t need =
|
||||
sizeof(std::uint32_t) + mine.size() * sizeof(opening);
|
||||
if (need > slot)
|
||||
throw std::runtime_error("evaluate_online_auth_on_sink: slot too small");
|
||||
std::vector<std::uint8_t> buf(slot, 0);
|
||||
const std::uint32_t n = static_cast<std::uint32_t>(mine.size());
|
||||
std::memcpy(buf.data(), &n, 4);
|
||||
if (!mine.empty())
|
||||
std::memcpy(buf.data() + 4, mine.data(), mine.size() * sizeof(opening));
|
||||
sink.submit(flush_round, index, buf.data(), buf.size());
|
||||
sink.flush_round(flush_round);
|
||||
sink.poll();
|
||||
if (!sink.peer_ready(flush_round, index))
|
||||
throw std::runtime_error("evaluate_online_auth_on_sink: peer missing");
|
||||
std::vector<std::uint8_t> peer_buf(slot);
|
||||
sink.read_peer(flush_round, index, peer_buf.data(), slot);
|
||||
std::uint32_t pn = 0;
|
||||
std::memcpy(&pn, peer_buf.data(), 4);
|
||||
if (pn != n)
|
||||
throw std::runtime_error("evaluate_online_auth_on_sink: peer size");
|
||||
std::vector<opening> peer(n);
|
||||
if (n != 0)
|
||||
std::memcpy(peer.data(), peer_buf.data() + 4, n * sizeof(opening));
|
||||
++flush_round;
|
||||
return peer;
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief Authenticated opening path when the session tape carries IT-MACs.
|
||||
/// @details Call only after the dealer ran `set_mac_key` and `deal_session`.
|
||||
/// Uses `trio::batch` so a installed `comm_hook` can replace the sink.
|
||||
template <typename Ring>
|
||||
void evaluate_online_auth(beavers::session<Ring> & s, trio & net, role self)
|
||||
{
|
||||
(void)self;
|
||||
using opening = beavers::auth_opening<Ring>;
|
||||
const int rounds =
|
||||
beaver_batch_round_budget(s.max_ready_round(), s.wire_count());
|
||||
const std::size_t slot =
|
||||
sizeof(std::uint32_t) + s.wire_count() * sizeof(opening);
|
||||
std::vector<std::size_t> slots(static_cast<std::size_t>(rounds), slot);
|
||||
auto sink = net.batch(1, std::move(slots));
|
||||
evaluate_online_auth_on_sink(s, *sink, 0);
|
||||
}
|
||||
|
||||
/// @brief u64 beaver δ host using `party_batch_stepper` (one per ABY session).
|
||||
struct u64_beaver_host final : protocol::beaver_host
|
||||
{
|
||||
explicit u64_beaver_host(
|
||||
std::vector<beavers::session<std::uint64_t> *> sessions)
|
||||
{
|
||||
steppers_.reserve(sessions.size());
|
||||
for (auto * s : sessions)
|
||||
{
|
||||
if (s == nullptr)
|
||||
steppers_.push_back(nullptr);
|
||||
else
|
||||
steppers_.push_back(
|
||||
std::make_unique<beavers::party_batch_stepper<std::uint64_t>>(
|
||||
*s));
|
||||
}
|
||||
}
|
||||
|
||||
void pack(std::size_t session_index, std::size_t /*barrier_index*/,
|
||||
std::uint8_t * dst, std::size_t n) override
|
||||
{
|
||||
if (session_index >= steppers_.size() || !steppers_[session_index])
|
||||
throw std::runtime_error("u64_beaver_host: missing session");
|
||||
auto mine = steppers_[session_index]->take_local();
|
||||
const std::uint32_t count = static_cast<std::uint32_t>(mine.size());
|
||||
const std::size_t need =
|
||||
sizeof(std::uint32_t) + mine.size() * sizeof(std::uint64_t);
|
||||
if (n < need)
|
||||
throw std::runtime_error("u64_beaver_host: slot too small");
|
||||
std::memset(dst, 0, n);
|
||||
std::memcpy(dst, &count, sizeof(count));
|
||||
if (!mine.empty())
|
||||
std::memcpy(dst + sizeof(count), mine.data(),
|
||||
mine.size() * sizeof(std::uint64_t));
|
||||
}
|
||||
|
||||
void apply(std::size_t session_index, std::size_t /*barrier_index*/,
|
||||
const std::uint8_t * src, std::size_t n) override
|
||||
{
|
||||
if (session_index >= steppers_.size() || !steppers_[session_index])
|
||||
throw std::runtime_error("u64_beaver_host: missing session");
|
||||
if (n < sizeof(std::uint32_t))
|
||||
throw std::runtime_error("u64_beaver_host: peer short");
|
||||
std::uint32_t count = 0;
|
||||
std::memcpy(&count, src, sizeof(count));
|
||||
const std::size_t need =
|
||||
sizeof(std::uint32_t) + static_cast<std::size_t>(count)
|
||||
* sizeof(std::uint64_t);
|
||||
if (n < need)
|
||||
throw std::runtime_error("u64_beaver_host: peer size");
|
||||
std::vector<std::uint64_t> peer(count);
|
||||
if (count != 0)
|
||||
std::memcpy(peer.data(), src + sizeof(count),
|
||||
peer.size() * sizeof(std::uint64_t));
|
||||
steppers_[session_index]->apply_peer(peer);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<beavers::party_batch_stepper<std::uint64_t>>>
|
||||
steppers_;
|
||||
};
|
||||
|
||||
/// @brief IT-MAC δ host: `uint32 n ‖ auth_opening<Ring>[n]`.
|
||||
/// @details `apply` checks each tag via `party_auth_batch_stepper`.
|
||||
template <typename Ring>
|
||||
struct auth_beaver_host final : protocol::beaver_host
|
||||
{
|
||||
using opening = beavers::auth_opening<Ring>;
|
||||
|
||||
explicit auth_beaver_host(std::vector<beavers::session<Ring> *> sessions)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<opening>,
|
||||
"auth_opening must be trivially copyable");
|
||||
steppers_.reserve(sessions.size());
|
||||
for (auto * s : sessions)
|
||||
{
|
||||
if (s == nullptr)
|
||||
steppers_.push_back(nullptr);
|
||||
else
|
||||
steppers_.push_back(
|
||||
std::make_unique<beavers::party_auth_batch_stepper<Ring>>(*s));
|
||||
}
|
||||
}
|
||||
|
||||
void pack(std::size_t session_index, std::size_t, std::uint8_t * dst,
|
||||
std::size_t n) override
|
||||
{
|
||||
if (session_index >= steppers_.size() || !steppers_[session_index])
|
||||
throw std::runtime_error("auth_beaver_host: missing session");
|
||||
auto mine = steppers_[session_index]->take_local();
|
||||
const std::uint32_t count = static_cast<std::uint32_t>(mine.size());
|
||||
const std::size_t need = sizeof(std::uint32_t) + mine.size() * sizeof(opening);
|
||||
if (n < need)
|
||||
throw std::runtime_error("auth_beaver_host: slot too small");
|
||||
std::memset(dst, 0, n);
|
||||
std::memcpy(dst, &count, sizeof(count));
|
||||
if (!mine.empty())
|
||||
std::memcpy(dst + sizeof(count), mine.data(),
|
||||
mine.size() * sizeof(opening));
|
||||
}
|
||||
|
||||
void apply(std::size_t session_index, std::size_t, const std::uint8_t * src,
|
||||
std::size_t n) override
|
||||
{
|
||||
if (session_index >= steppers_.size() || !steppers_[session_index])
|
||||
throw std::runtime_error("auth_beaver_host: missing session");
|
||||
if (n < sizeof(std::uint32_t))
|
||||
throw std::runtime_error("auth_beaver_host: peer short");
|
||||
std::uint32_t count = 0;
|
||||
std::memcpy(&count, src, sizeof(count));
|
||||
const std::size_t need =
|
||||
sizeof(std::uint32_t) + static_cast<std::size_t>(count) * sizeof(opening);
|
||||
if (n < need)
|
||||
throw std::runtime_error("auth_beaver_host: peer size");
|
||||
std::vector<opening> peer(count);
|
||||
if (count != 0)
|
||||
std::memcpy(peer.data(), src + sizeof(count),
|
||||
peer.size() * sizeof(opening));
|
||||
steppers_[session_index]->apply_peer(peer);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<beavers::party_auth_batch_stepper<Ring>>> steppers_;
|
||||
};
|
||||
|
||||
using u64_auth_beaver_host = auth_beaver_host<std::uint64_t>;
|
||||
|
||||
/// @brief Schedule a composer and drive it on a trio batch sink.
|
||||
/// @details Uses `default_plan()`: RoundSink rounds == `plan::rounds()` ==
|
||||
/// exchange-bearing waves. Pass `opt.from_exchange_wave` /
|
||||
/// `compact_sink` for adaptive tails; `opt.beavers` for live δ.
|
||||
inline void drive_composed(protocol::composer & c, trio & net,
|
||||
std::vector<std::vector<std::uint8_t>> & values,
|
||||
const std::map<std::uint32_t, protocol::kernel_fn> & kernels,
|
||||
std::size_t count = 1, protocol::drive_options opt = {})
|
||||
{
|
||||
auto p = c.default_plan();
|
||||
auto slots = opt.compact_sink ? p.slot_bytes_from(opt.from_exchange_wave)
|
||||
: p.slot_bytes_all();
|
||||
if (slots.empty())
|
||||
slots.push_back(0);
|
||||
auto sink = net.batch(count, std::move(slots));
|
||||
protocol::drive(p, *sink, values, kernels, c.party(), opt);
|
||||
}
|
||||
|
||||
/// @brief Drive a compose plan on the trio mesh (2PC peer + 3PC RSS neighbor).
|
||||
/// @details Shared semantics with `protocol::drive`: throws on missing kernels,
|
||||
/// domain-correct opens, optional beaver host, incremental
|
||||
/// `from_exchange_wave`. Per-exchange peer maps: `y` → neighbor ring,
|
||||
/// else p0↔p1 (p2 idle).
|
||||
inline void drive_composed_trio(protocol::composer & c, trio & net, role self,
|
||||
std::vector<std::vector<std::uint8_t>> & values,
|
||||
const std::map<std::uint32_t, protocol::kernel_fn> & kernels,
|
||||
protocol::drive_options opt = {})
|
||||
{
|
||||
using protocol::domain;
|
||||
using protocol::effect;
|
||||
namespace opcodes = protocol::opcodes;
|
||||
auto p = c.default_plan();
|
||||
if (values.size() < p.nodes().size())
|
||||
values.resize(p.nodes().size());
|
||||
auto ensure = [&](std::uint32_t id) {
|
||||
const std::size_t need = p.value_bytes_of(id);
|
||||
if (values[id].size() < need)
|
||||
values[id].assign(need, 0);
|
||||
};
|
||||
for (std::uint32_t id = 0; id < p.nodes().size(); ++id)
|
||||
ensure(id);
|
||||
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
const role rss_next =
|
||||
static_cast<role>((static_cast<unsigned>(self) + 1u) % 3u);
|
||||
|
||||
auto run_groups = [&](const protocol::wave_info & wave) {
|
||||
for (const auto & g : wave.groups)
|
||||
{
|
||||
if (g.kind == effect::convert)
|
||||
{
|
||||
for (auto n : g.nodes)
|
||||
{
|
||||
ensure(n.id);
|
||||
const auto & ins = p.inputs_of(n.id);
|
||||
if (ins.empty())
|
||||
continue;
|
||||
if (p.alias_of(n.id) && ins.size() == 1
|
||||
&& p.value_bytes_of(n.id) == p.value_bytes_of(ins[0]))
|
||||
{
|
||||
values[n.id] = values[ins[0]];
|
||||
continue;
|
||||
}
|
||||
if (ins.size() == 1)
|
||||
{
|
||||
protocol::detail::run_builtin_convert(g.opcode,
|
||||
c.party(), values[ins[0]].data(),
|
||||
p.value_bytes_of(ins[0]), values[n.id].data(),
|
||||
p.value_bytes_of(n.id));
|
||||
}
|
||||
else if (ins.size() == 2
|
||||
&& g.opcode == opcodes::conv_y2rss)
|
||||
{
|
||||
const auto half = p.value_bytes_of(ins[0]);
|
||||
std::memcpy(values[n.id].data(),
|
||||
values[ins[0]].data(), half);
|
||||
std::memcpy(values[n.id].data() + half,
|
||||
values[ins[1]].data(), half);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (protocol::detail::is_beaver_opcode(g.opcode))
|
||||
continue;
|
||||
if (g.opcode == opcodes::fss_fuse_segment)
|
||||
{
|
||||
for (auto n : g.nodes)
|
||||
{
|
||||
ensure(n.id);
|
||||
const auto & ins = p.inputs_of(n.id);
|
||||
if (!ins.empty())
|
||||
ensure(ins[0]);
|
||||
protocol::detail::apply_fuse_segment(p, values, n.id, 1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto kit = kernels.find(g.opcode);
|
||||
protocol::kernel_fn builtin;
|
||||
const protocol::kernel_fn * fn = nullptr;
|
||||
if (kit != kernels.end())
|
||||
fn = &kit->second;
|
||||
else if (protocol::detail::has_builtin_walk_kernel(g.opcode))
|
||||
{
|
||||
builtin = protocol::detail::builtin_walk_kernel(g.opcode);
|
||||
fn = &builtin;
|
||||
}
|
||||
else if (g.kind == effect::blind)
|
||||
{
|
||||
for (auto n : g.nodes)
|
||||
{
|
||||
ensure(n.id);
|
||||
std::fill(values[n.id].begin(), values[n.id].end(), 0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"drive_composed_trio missing kernel for opcode "
|
||||
+ std::to_string(g.opcode));
|
||||
}
|
||||
for (auto n : g.nodes)
|
||||
{
|
||||
ensure(n.id);
|
||||
std::vector<protocol::block_span> ins;
|
||||
for (auto in_id : p.inputs_of(n.id))
|
||||
{
|
||||
ensure(in_id);
|
||||
ins.push_back(protocol::block_span{values[in_id].data(), 1,
|
||||
p.value_bytes_of(in_id)});
|
||||
}
|
||||
protocol::block_span out{values[n.id].data(), 1,
|
||||
p.value_bytes_of(n.id)};
|
||||
(*fn)(g.opcode, {n}, ins, out, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::size_t exchange_i = 0;
|
||||
for (std::size_t w = 0; w < p.waves(); ++w)
|
||||
{
|
||||
const auto & wave = p.wave(w);
|
||||
const bool skip = !wave.exchanges.empty()
|
||||
&& exchange_i < opt.from_exchange_wave;
|
||||
if (!skip)
|
||||
run_groups(wave);
|
||||
if (wave.exchanges.empty() || wave.slot_bytes == 0)
|
||||
continue;
|
||||
if (exchange_i < opt.from_exchange_wave)
|
||||
{
|
||||
++exchange_i;
|
||||
continue;
|
||||
}
|
||||
++exchange_i;
|
||||
|
||||
// Split peer maps: y → RSS neighbor; dealer_pad → p2; else → 2PC peer.
|
||||
std::vector<std::uint8_t> two_pc;
|
||||
std::vector<std::uint8_t> rss;
|
||||
std::vector<std::uint8_t> dealer;
|
||||
struct zero_mask { std::size_t n = 0; domain dom = domain::a; };
|
||||
std::vector<zero_mask> zeros;
|
||||
std::vector<std::uint8_t> kinds; // 0 = 2pc, 1 = rss, 2 = dealer, 3 = zero
|
||||
kinds.reserve(wave.exchanges.size());
|
||||
for (auto ex : wave.exchanges)
|
||||
{
|
||||
ensure(ex.id);
|
||||
const auto nb = p.value_bytes_of(ex.id);
|
||||
const auto op = p.opcode_of(ex.id);
|
||||
const bool neighbor = protocol::detail::is_rss_neighbor_open(p, ex.id);
|
||||
const bool from_dealer = op == opcodes::dealer_pad;
|
||||
const bool zero = op == opcodes::dealer_zero;
|
||||
kinds.push_back(zero ? 3 : (from_dealer ? 2 : (neighbor ? 1 : 0)));
|
||||
if (zero)
|
||||
zeros.push_back(zero_mask{nb, p.domain_of(ex.id)});
|
||||
std::vector<std::uint8_t> chunk(nb, 0);
|
||||
if (protocol::detail::is_beaver_opcode(op) && opt.beavers != nullptr)
|
||||
{
|
||||
const auto sess = static_cast<std::size_t>(p.level_of(ex.id));
|
||||
const auto bi =
|
||||
static_cast<std::size_t>(op - opcodes::beaver_delta);
|
||||
opt.beavers->pack(sess, bi, chunk.data(), nb);
|
||||
std::memcpy(values[ex.id].data(), chunk.data(), nb);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto & ins = p.inputs_of(ex.id);
|
||||
if (!ins.empty() && !protocol::detail::is_beaver_opcode(op))
|
||||
{
|
||||
const auto src = ins[0];
|
||||
ensure(src);
|
||||
const auto sb = p.value_bytes_of(src);
|
||||
std::memcpy(chunk.data(), values[src].data(),
|
||||
std::min(sb, nb));
|
||||
std::memcpy(values[ex.id].data(), values[src].data(),
|
||||
std::min(sb, nb));
|
||||
}
|
||||
else
|
||||
std::memcpy(chunk.data(), values[ex.id].data(), nb);
|
||||
}
|
||||
if (from_dealer)
|
||||
dealer.insert(dealer.end(), chunk.begin(), chunk.end());
|
||||
else if (zero)
|
||||
continue;
|
||||
else if (neighbor)
|
||||
rss.insert(rss.end(), chunk.begin(), chunk.end());
|
||||
else
|
||||
two_pc.insert(two_pc.end(), chunk.begin(), chunk.end());
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> two_pc_peer;
|
||||
std::vector<std::uint8_t> rss_peer;
|
||||
std::vector<std::uint8_t> dealer_peer;
|
||||
std::vector<std::vector<std::uint8_t>> zero_recv(zeros.size());
|
||||
if (!rss.empty())
|
||||
rss_peer = net.exchange_vec_with(rss_next, rss);
|
||||
if (!two_pc.empty())
|
||||
{
|
||||
if (self == role::p2)
|
||||
{
|
||||
// Dealer idle on 2PC opens — leave peer zeros; reconstruct
|
||||
// will not run for p2 on those slots.
|
||||
two_pc_peer.assign(two_pc.size(), 0);
|
||||
}
|
||||
else
|
||||
two_pc_peer = net.exchange_vec_with(peer, two_pc);
|
||||
}
|
||||
if (!dealer.empty())
|
||||
{
|
||||
if (self == role::p2)
|
||||
{
|
||||
net.send_bytes_to(role::p0, net::msg::bytes, dealer.data(),
|
||||
dealer.size());
|
||||
net.send_bytes_to(role::p1, net::msg::bytes, dealer.data(),
|
||||
dealer.size());
|
||||
dealer_peer = dealer;
|
||||
}
|
||||
else
|
||||
dealer_peer = net.recv_bytes_from(role::p2, net::msg::bytes);
|
||||
}
|
||||
if (!zeros.empty())
|
||||
{
|
||||
if (self == role::p2)
|
||||
{
|
||||
for (std::size_t zi = 0; zi < zeros.size(); ++zi)
|
||||
{
|
||||
std::vector<std::uint8_t> a(zeros[zi].n), b(zeros[zi].n);
|
||||
for (auto & byte : a)
|
||||
byte = dpf::uniform_sample<std::uint8_t>();
|
||||
if (zeros[zi].dom == domain::b)
|
||||
b = a;
|
||||
else if (zeros[zi].n % 8 == 0)
|
||||
{
|
||||
for (std::size_t i = 0; i < a.size(); i += 8)
|
||||
{
|
||||
std::uint64_t w = 0;
|
||||
std::memcpy(&w, a.data() + i, 8);
|
||||
w = static_cast<std::uint64_t>(0) - w;
|
||||
std::memcpy(b.data() + i, &w, 8);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::size_t i = 0; i < a.size(); ++i)
|
||||
b[i] = a[i];
|
||||
}
|
||||
net.send_bytes_to(role::p0, net::msg::bytes, a.data(), a.size());
|
||||
net.send_bytes_to(role::p1, net::msg::bytes, b.data(), b.size());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::size_t zi = 0; zi < zeros.size(); ++zi)
|
||||
zero_recv[zi] = net.recv_bytes_from(role::p2, net::msg::bytes);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t off2 = 0, offr = 0, offd = 0, zi = 0;
|
||||
for (std::size_t i = 0; i < wave.exchanges.size(); ++i)
|
||||
{
|
||||
auto ex = wave.exchanges[i];
|
||||
const auto nb = p.value_bytes_of(ex.id);
|
||||
ensure(ex.id);
|
||||
const auto op = p.opcode_of(ex.id);
|
||||
const bool neighbor = kinds[i] == 1;
|
||||
const bool from_dealer = kinds[i] == 2;
|
||||
if (kinds[i] == 3)
|
||||
{
|
||||
if (self != role::p2)
|
||||
{
|
||||
if (zi >= zero_recv.size() || zero_recv[zi].size() < nb)
|
||||
throw std::runtime_error("drive_composed_trio: zero mask");
|
||||
std::memcpy(values[ex.id].data(), zero_recv[zi].data(), nb);
|
||||
}
|
||||
++zi;
|
||||
continue;
|
||||
}
|
||||
if (from_dealer)
|
||||
{
|
||||
if (dealer_peer.size() < offd + nb)
|
||||
throw std::runtime_error("drive_composed_trio: dealer size");
|
||||
std::memcpy(values[ex.id].data(), dealer_peer.data() + offd, nb);
|
||||
offd += nb;
|
||||
continue;
|
||||
}
|
||||
const std::uint8_t * peer_bytes =
|
||||
neighbor ? rss_peer.data() + offr : two_pc_peer.data() + off2;
|
||||
if (neighbor)
|
||||
offr += nb;
|
||||
else
|
||||
off2 += nb;
|
||||
|
||||
if (self == role::p2 && !neighbor)
|
||||
continue;
|
||||
|
||||
if (protocol::detail::is_beaver_opcode(op) && opt.beavers != nullptr)
|
||||
{
|
||||
const auto sess = static_cast<std::size_t>(p.level_of(ex.id));
|
||||
const auto bi =
|
||||
static_cast<std::size_t>(op - opcodes::beaver_delta);
|
||||
opt.beavers->apply(sess, bi, peer_bytes, nb);
|
||||
}
|
||||
|
||||
domain d = p.domain_of(ex.id);
|
||||
const auto & ins = p.inputs_of(ex.id);
|
||||
if (!ins.empty())
|
||||
d = p.domain_of(ins[0]);
|
||||
if (neighbor)
|
||||
{
|
||||
std::memcpy(values[ex.id].data(), peer_bytes, nb);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::uint8_t> mine(nb, 0);
|
||||
if (!ins.empty() && !protocol::detail::is_beaver_opcode(op))
|
||||
{
|
||||
const auto sb = p.value_bytes_of(ins[0]);
|
||||
std::memcpy(mine.data(), values[ins[0]].data(),
|
||||
std::min(sb, nb));
|
||||
}
|
||||
else
|
||||
std::memcpy(mine.data(), values[ex.id].data(), nb);
|
||||
protocol::detail::reconstruct_open(d, c.party(), mine.data(),
|
||||
peer_bytes, nb, values[ex.id].data(), opt.field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Schedule a fused Express/Sabre point+audit walk on a composer.
|
||||
/// @details Prefer this over `open_and_sketch` when the sketch can ride in the
|
||||
/// last CW flush — one fewer exchange-bearing wave.
|
||||
inline protocol::composer::walk_result schedule_fused_audit(
|
||||
protocol::composer & c, protocol::node seed, std::size_t depth,
|
||||
std::size_t slot_bytes, protocol::node sketch)
|
||||
{
|
||||
return c.fss_point_fused(seed, depth, slot_bytes, sketch);
|
||||
}
|
||||
|
||||
/// @brief Fold opened outputs into a sketch and exchange for `sketch_verify`.
|
||||
/// @details Used only by extractable party flows. Prefer Express/Sabre
|
||||
/// `schedule_fused_audit` / `fss_point_fused` so the sketch rides in
|
||||
/// the last CW flush instead of adding a round. Non-extractable keys
|
||||
/// leave `note_sketch` as a no-op; callers still exchange empty shares.
|
||||
template <typename KeyT, typename YRange, typename RRange>
|
||||
bool open_and_sketch(trio & net, role self, sketch_share & local,
|
||||
YRange && ys, RRange && rs)
|
||||
{
|
||||
note_sketch<KeyT>(local, std::forward<YRange>(ys), std::forward<RRange>(rs));
|
||||
role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
sketch_share theirs = net.exchange_with(peer, local, net::msg::sketch_share);
|
||||
if (self == role::p0)
|
||||
return sketch_verify(local, theirs);
|
||||
return sketch_verify(theirs, local);
|
||||
}
|
||||
|
||||
/// @brief Keep parties aligned across `--repeat` iterations.
|
||||
inline void sync_round(trio & net, role self, std::uint64_t round)
|
||||
{
|
||||
if (self == role::p2)
|
||||
{
|
||||
net.to(role::p0).send(net::msg::case_ok, round);
|
||||
net.to(role::p1).send(net::msg::case_ok, round);
|
||||
return;
|
||||
}
|
||||
auto got = net.to(role::p2).recv<std::uint64_t>(net::msg::case_ok);
|
||||
require(got == round, "repeat sync");
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
|
||||
#endif // LIBDPF_PARTY_FLOW_UTIL_HPP__
|
||||
465
party/flows_bulk.cpp
Normal file
465
party/flows_bulk.cpp
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
/// @file party/flows_bulk.cpp
|
||||
/// @brief Parametric beaver / DCF / Horner flows for coverage and benchmarks.
|
||||
|
||||
#include "cases.hpp"
|
||||
#include "flow_util.hpp"
|
||||
#include "key_io.hpp"
|
||||
#include "registry.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "simde/simde/x86/avx2.h"
|
||||
|
||||
#include "dpf/beaver.hpp"
|
||||
#include "dpf/dcf.hpp"
|
||||
#include "dpf/dpf_key.hpp"
|
||||
#include "dpf/eval_point.hpp"
|
||||
#include "dpf/eval_unified.hpp"
|
||||
#include "dpf/incremental.hpp"
|
||||
#include "dpf/prg_aes.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
namespace bulk
|
||||
{
|
||||
|
||||
using util::Counter;
|
||||
using util::evaluate_online;
|
||||
using util::install_and_bind_u64;
|
||||
using util::open_additive;
|
||||
using util::require;
|
||||
using util::role;
|
||||
using util::share_bits;
|
||||
using util::trio;
|
||||
using util::u64;
|
||||
|
||||
// ---- parametric beaver product ----
|
||||
|
||||
template <u64 X, u64 Y>
|
||||
int beaver_product_xy(role self, trio & net)
|
||||
{
|
||||
beavers::session<u64> s;
|
||||
auto x = s.input();
|
||||
auto y = s.input();
|
||||
auto z = s(x * y);
|
||||
if (self == role::p2)
|
||||
{
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
net::deal_session(net, s);
|
||||
return 0;
|
||||
}
|
||||
install_and_bind_u64(s, net, self, {x, y}, {X, Y});
|
||||
evaluate_online(s, net, self);
|
||||
auto o = open_additive(net, self, s.value_party(z));
|
||||
if (self == role::p0)
|
||||
require(o == X * Y, "product_xy");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- parametric dot ----
|
||||
|
||||
template <std::size_t N>
|
||||
int beaver_dot_n(role self, trio & net)
|
||||
{
|
||||
beavers::session<u64> s;
|
||||
std::vector<beavers::session<u64>::wire> xs, ys;
|
||||
std::vector<u64> secrets;
|
||||
u64 expect = 0;
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
xs.push_back(s.input());
|
||||
ys.push_back(s.input());
|
||||
u64 a = static_cast<u64>(i + 1);
|
||||
u64 b = static_cast<u64>(N - i);
|
||||
secrets.push_back(a);
|
||||
expect += a * b;
|
||||
}
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
secrets.push_back(static_cast<u64>(N - i));
|
||||
auto z = s.dot(xs, ys);
|
||||
if (self == role::p2)
|
||||
{
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
require(s.round_of(z) == 1, "dot round");
|
||||
net::deal_session(net, s);
|
||||
return 0;
|
||||
}
|
||||
std::vector<beavers::session<u64>::wire> wires = xs;
|
||||
wires.insert(wires.end(), ys.begin(), ys.end());
|
||||
install_and_bind_u64(s, net, self, wires, secrets);
|
||||
evaluate_online(s, net, self);
|
||||
auto o = open_additive(net, self, s.value_party(z));
|
||||
if (self == role::p0)
|
||||
require(o == expect, "dot_n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- parametric scale ----
|
||||
|
||||
template <std::size_t N>
|
||||
int beaver_scale_n(role self, trio & net)
|
||||
{
|
||||
beavers::session<u64> s;
|
||||
auto sc = s.input();
|
||||
std::vector<beavers::session<u64>::wire> lanes;
|
||||
std::vector<u64> secrets{3u};
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
lanes.push_back(s.input());
|
||||
secrets.push_back(static_cast<u64>(i + 1));
|
||||
}
|
||||
auto out = s.scale(sc, lanes);
|
||||
if (self == role::p2)
|
||||
{
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
net::deal_session(net, s);
|
||||
return 0;
|
||||
}
|
||||
std::vector<beavers::session<u64>::wire> wires{sc};
|
||||
wires.insert(wires.end(), lanes.begin(), lanes.end());
|
||||
install_and_bind_u64(s, net, self, wires, secrets);
|
||||
evaluate_online(s, net, self);
|
||||
std::vector<u64> mine;
|
||||
mine.reserve(N);
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
mine.push_back(s.value_party(out[i]));
|
||||
auto opened = open_additive(net, self, mine);
|
||||
if (self == role::p0)
|
||||
{
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
require(opened[i] == 3u * static_cast<u64>(i + 1), "scale_n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- parametric stream ----
|
||||
|
||||
template <std::size_t N>
|
||||
int beaver_stream_n(role self, trio & net)
|
||||
{
|
||||
using block = prg::aes128::block_type;
|
||||
block seed = simde_mm_set_epi64x(0x9, static_cast<long long>(N));
|
||||
if (self == role::p2)
|
||||
{
|
||||
beavers::oracle<u64> src(seed, 8);
|
||||
std::vector<beavers::beaver2<u64>> bulk(N);
|
||||
beavers::fill_beaver2(src, 0, bulk.data(), N);
|
||||
require(bulk[0].ab.open() == bulk[0].a.open() * bulk[0].b.open(), "s0");
|
||||
require(bulk[N - 1].ab.open() == bulk[N - 1].a.open() * bulk[N - 1].b.open(),
|
||||
"sN");
|
||||
std::vector<u64> p0, p1;
|
||||
p0.reserve(N * 4);
|
||||
p1.reserve(N * 4);
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
p0.insert(p0.end(),
|
||||
{bulk[i].a.p0, bulk[i].b.p0, bulk[i].ab.p0, bulk[i].out.p0});
|
||||
p1.insert(p1.end(),
|
||||
{bulk[i].a.p1, bulk[i].b.p1, bulk[i].ab.p1, bulk[i].out.p1});
|
||||
}
|
||||
net.to(role::p0).send_vec(p0);
|
||||
net.to(role::p1).send_vec(p1);
|
||||
return 0;
|
||||
}
|
||||
auto shares = net.to(role::p2).recv_vec<u64>();
|
||||
require(shares.size() == N * 4, "stream size");
|
||||
// Online open copies 0 and N/2.
|
||||
auto open_copy = [&](std::size_t i, u64 sx, u64 sy) {
|
||||
u64 a = shares[i * 4 + 0];
|
||||
u64 b = shares[i * 4 + 1];
|
||||
u64 ab = shares[i * 4 + 2];
|
||||
auto [x0, x1] = util::split_u64(sx, static_cast<unsigned>(i * 2 + 1));
|
||||
auto [y0, y1] = util::split_u64(sy, static_cast<unsigned>(i * 2 + 2));
|
||||
u64 xs = self == role::p0 ? x0 : x1;
|
||||
u64 ys = self == role::p0 ? y0 : y1;
|
||||
role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
u64 dx = (xs + a) + net.exchange_with(peer, xs + a);
|
||||
u64 dy = (ys + b) + net.exchange_with(peer, ys + b);
|
||||
u64 z = ab;
|
||||
z -= dx * b;
|
||||
z -= dy * a;
|
||||
if (self == role::p0)
|
||||
z += dx * dy;
|
||||
auto open = open_additive(net, self, z);
|
||||
if (self == role::p0)
|
||||
require(open == sx * sy, "stream online");
|
||||
};
|
||||
open_copy(0, 6u, 7u);
|
||||
open_copy(N / 2, 3u, 11u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- parametric public horner (one round) ----
|
||||
|
||||
template <std::size_t Degree>
|
||||
int beaver_horner_d(role self, trio & net)
|
||||
{
|
||||
beavers::session<u64> s;
|
||||
auto x = s.input();
|
||||
beavers::session<u64>::wire z;
|
||||
if constexpr (Degree == 1)
|
||||
z = s.horner(x, {1ull, 2ull});
|
||||
else if constexpr (Degree == 2)
|
||||
z = s.horner(x, {1ull, 2ull, 3ull});
|
||||
else if constexpr (Degree == 3)
|
||||
z = s.horner(x, {1ull, 2ull, 3ull, 4ull});
|
||||
else if constexpr (Degree == 4)
|
||||
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull});
|
||||
else if constexpr (Degree == 5)
|
||||
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull, 6ull});
|
||||
else if constexpr (Degree == 6)
|
||||
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull});
|
||||
else if constexpr (Degree == 7)
|
||||
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull, 8ull});
|
||||
else
|
||||
z = s.horner(x, {1ull, 2ull, 3ull, 4ull, 5ull, 6ull, 7ull, 8ull, 9ull});
|
||||
|
||||
const u64 xv = 5u;
|
||||
u64 expect = 0;
|
||||
u64 pow = 1;
|
||||
for (std::size_t i = 0; i <= Degree; ++i)
|
||||
{
|
||||
expect += static_cast<u64>(i + 1) * pow;
|
||||
pow *= xv;
|
||||
}
|
||||
if (self == role::p2)
|
||||
{
|
||||
require(s.round_of(z) == 1, "horner one round");
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
net::deal_session(net, s);
|
||||
return 0;
|
||||
}
|
||||
install_and_bind_u64(s, net, self, {x}, {xv});
|
||||
evaluate_online(s, net, self);
|
||||
auto o = open_additive(net, self, s.value_party(z));
|
||||
if (self == role::p0)
|
||||
require(o == expect, "horner_d");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- sign * horner one round ----
|
||||
|
||||
int beaver_sign_horner(role self, trio & net)
|
||||
{
|
||||
beavers::session<u64> s;
|
||||
auto sgn = s.input();
|
||||
auto x = s.input();
|
||||
auto z = s.horner(sgn, x, {u64{4}, u64{5}, u64{6}}); // sgn*(4+5x+6x^2)
|
||||
if (self == role::p2)
|
||||
{
|
||||
require(s.round_of(z) == 1, "sign horner round");
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
net::deal_session(net, s);
|
||||
return 0;
|
||||
}
|
||||
install_and_bind_u64(s, net, self, {sgn, x}, {2u, 3u});
|
||||
evaluate_online(s, net, self);
|
||||
auto o = open_additive(net, self, s.value_party(z));
|
||||
if (self == role::p0)
|
||||
require(o == 2u * (4u + 5u * 3u + 6u * 9u), "sign_horner");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- DCF kinds ----
|
||||
|
||||
template <int Kind> // 0=lt 1=leq 2=gt 3=geq
|
||||
int dcf_kind_point(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0x40;
|
||||
const Input x0 = 0x15;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
const std::uint64_t beta = 9;
|
||||
|
||||
auto check = [&](const auto & key) {
|
||||
auto below = eval_point(cmp, key, static_cast<Input>(alpha - 1));
|
||||
auto on = eval_point(cmp, key, alpha);
|
||||
auto above = eval_point(cmp, key, static_cast<Input>(alpha + 1));
|
||||
std::vector<u64> mine{
|
||||
share_bits(below) & key.cmp().mask,
|
||||
share_bits(on) & key.cmp().mask,
|
||||
share_bits(above) & key.cmp().mask};
|
||||
auto opened = open_additive(net, self, mine);
|
||||
const auto ob = opened[0] & key.cmp().mask;
|
||||
const auto oo = opened[1] & key.cmp().mask;
|
||||
const auto oa = opened[2] & key.cmp().mask;
|
||||
if (self == role::p0)
|
||||
{
|
||||
if constexpr (Kind == 0)
|
||||
require(ob == beta && oo == 0 && oa == 0, "lt");
|
||||
else if constexpr (Kind == 1)
|
||||
require(ob == beta && oo == beta && oa == 0, "leq");
|
||||
else if constexpr (Kind == 2)
|
||||
require(ob == 0 && oo == 0 && oa == beta, "gt");
|
||||
else
|
||||
require(ob == 0 && oo == beta && oa == beta, "geq");
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr (Kind == 0)
|
||||
{
|
||||
require_opened(dist_with_cmp_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, lt(beta), check, check),
|
||||
self, encoded_xor_point(x0, x1));
|
||||
}
|
||||
else if constexpr (Kind == 1)
|
||||
{
|
||||
require_opened(dist_with_cmp_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, leq(beta), check, check),
|
||||
self, encoded_xor_point(x0, x1));
|
||||
}
|
||||
else if constexpr (Kind == 2)
|
||||
{
|
||||
require_opened(dist_with_cmp_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, gt(beta), check, check),
|
||||
self, encoded_xor_point(x0, x1));
|
||||
}
|
||||
else
|
||||
{
|
||||
require_opened(dist_with_cmp_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, geq(beta), check, check),
|
||||
self, encoded_xor_point(x0, x1));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- classic point eval bench ----
|
||||
|
||||
template <std::uint8_t Alpha, std::uint64_t Beta>
|
||||
int dpf_point_ab(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha{Alpha};
|
||||
const Input x0 = static_cast<Input>(alpha ^ Input{0x5a});
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
auto on = [&](const auto & key) {
|
||||
auto y = *eval_point(key, alpha);
|
||||
auto open = util::open_subtractive(net, self, share_bits(y));
|
||||
if (self == role::p0)
|
||||
require(open == Beta, "point");
|
||||
auto yz = *eval_point(key, static_cast<Input>(Alpha ^ 1u));
|
||||
auto oz = util::open_subtractive(net, self, share_bits(yz));
|
||||
if (self == role::p0)
|
||||
require(oz == 0u, "point off");
|
||||
};
|
||||
require_tree_prefix(
|
||||
dist_with_point_key<prg::aes128, prg::aes128, true>(net, self, x0, x1, std::uint64_t{Beta}, on, on),
|
||||
self, verifiable_tree_prefix<prg::aes128, prg::aes128, std::uint64_t>(
|
||||
x0, x1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- multivariate poly one-round ----
|
||||
|
||||
int beaver_poly_cubic_cluster(role self, trio & net)
|
||||
{
|
||||
beavers::session<u64> s;
|
||||
auto x = s.input();
|
||||
auto y = s.input();
|
||||
auto z = s.input();
|
||||
auto out = s(x * y * z + x * y + z);
|
||||
if (self == role::p2)
|
||||
{
|
||||
// Degree-3 monomials need two interactive rounds (see beaver_test xyz).
|
||||
require(s.round_of(out) == 2, "cluster round");
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
net::deal_session(net, s);
|
||||
return 0;
|
||||
}
|
||||
install_and_bind_u64(s, net, self, {x, y, z}, {2u, 3u, 5u});
|
||||
evaluate_online(s, net, self);
|
||||
auto o = open_additive(net, self, s.value_party(out));
|
||||
if (self == role::p0)
|
||||
require(o == 2u * 3u * 5u + 2u * 3u + 5u, "cluster");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int beaver_like_terms(role self, trio & net)
|
||||
{
|
||||
beavers::session<u64> s;
|
||||
auto x = s.input();
|
||||
auto y = s.input();
|
||||
auto z = s(3 * x * y + 2 * x * y);
|
||||
if (self == role::p2)
|
||||
{
|
||||
Counter rng;
|
||||
s.sample(rng);
|
||||
net::deal_session(net, s);
|
||||
return 0;
|
||||
}
|
||||
install_and_bind_u64(s, net, self, {x, y}, {4u, 5u});
|
||||
evaluate_online(s, net, self);
|
||||
auto o = open_additive(net, self, s.value_party(z));
|
||||
if (self == role::p0)
|
||||
require(o == 5u * 4u * 5u, "like");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define REG(name, tags, fn, benchable) \
|
||||
register_flow(flow{#name, tags, fn, benchable})
|
||||
|
||||
} // namespace bulk
|
||||
|
||||
void register_bulk_flows()
|
||||
{
|
||||
using namespace bulk;
|
||||
REG(beaver_product_3_5, "beaver product bench", (beaver_product_xy<3, 5>), true);
|
||||
REG(beaver_product_11_13, "beaver product bench", (beaver_product_xy<11, 13>), true);
|
||||
REG(beaver_product_100_200, "beaver product bench", (beaver_product_xy<100, 200>), true);
|
||||
REG(beaver_product_max8, "beaver product bench",
|
||||
(beaver_product_xy<0xffffffffffffffffull, 2>), true);
|
||||
|
||||
REG(beaver_dot_n1, "beaver dot bench", beaver_dot_n<1>, true);
|
||||
REG(beaver_dot_n2, "beaver dot bench", beaver_dot_n<2>, true);
|
||||
REG(beaver_dot_n4, "beaver dot bench", beaver_dot_n<4>, true);
|
||||
REG(beaver_dot_n8, "beaver dot bench", beaver_dot_n<8>, true);
|
||||
REG(beaver_dot_n16, "beaver dot bench", beaver_dot_n<16>, true);
|
||||
REG(beaver_dot_n32, "beaver dot bench", beaver_dot_n<32>, true);
|
||||
|
||||
REG(beaver_scale_n1, "beaver scale bench", beaver_scale_n<1>, true);
|
||||
REG(beaver_scale_n4, "beaver scale bench", beaver_scale_n<4>, true);
|
||||
REG(beaver_scale_n16, "beaver scale bench", beaver_scale_n<16>, true);
|
||||
REG(beaver_scale_n64, "beaver scale bench", beaver_scale_n<64>, true);
|
||||
|
||||
REG(beaver_stream_n8, "beaver stream bench", beaver_stream_n<8>, true);
|
||||
REG(beaver_stream_n32, "beaver stream bench", beaver_stream_n<32>, true);
|
||||
REG(beaver_stream_n128, "beaver stream bench", beaver_stream_n<128>, true);
|
||||
REG(beaver_stream_n512, "beaver stream bench", beaver_stream_n<512>, true);
|
||||
REG(beaver_stream_n2048, "beaver stream bench", beaver_stream_n<2048>, true);
|
||||
|
||||
REG(beaver_horner_d1, "beaver horner grotto bench", beaver_horner_d<1>, true);
|
||||
REG(beaver_horner_d2, "beaver horner grotto bench", beaver_horner_d<2>, true);
|
||||
REG(beaver_horner_d3, "beaver horner grotto bench", beaver_horner_d<3>, true);
|
||||
REG(beaver_horner_d4, "beaver horner grotto bench", beaver_horner_d<4>, true);
|
||||
REG(beaver_horner_d5, "beaver horner grotto bench", beaver_horner_d<5>, true);
|
||||
REG(beaver_horner_d6, "beaver horner grotto bench", beaver_horner_d<6>, true);
|
||||
REG(beaver_horner_d7, "beaver horner grotto bench", beaver_horner_d<7>, true);
|
||||
REG(beaver_horner_d8, "beaver horner grotto bench", beaver_horner_d<8>, true);
|
||||
REG(beaver_sign_horner, "beaver horner grotto bench", beaver_sign_horner, true);
|
||||
|
||||
REG(beaver_poly_cubic_cluster, "beaver poly bench", beaver_poly_cubic_cluster, true);
|
||||
REG(beaver_like_terms, "beaver poly bench", beaver_like_terms, true);
|
||||
|
||||
REG(dcf_lt, "dcf grotto bench", dcf_kind_point<0>, true);
|
||||
REG(dcf_leq, "dcf grotto bench", dcf_kind_point<1>, true);
|
||||
REG(dcf_gt, "dcf grotto bench", dcf_kind_point<2>, true);
|
||||
REG(dcf_geq, "dcf grotto bench", dcf_kind_point<3>, true);
|
||||
|
||||
REG(dpf_point_2a_7, "dpf geneval bench", (dpf_point_ab<0x2a, 7>), true);
|
||||
REG(dpf_point_00_1, "dpf geneval bench", (dpf_point_ab<0x00, 1>), true);
|
||||
REG(dpf_point_ff_99, "dpf geneval bench", (dpf_point_ab<0xff, 99>), true);
|
||||
REG(dpf_point_80_42, "dpf geneval bench", (dpf_point_ab<0x80, 42>), true);
|
||||
}
|
||||
|
||||
#undef REG
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
792
party/flows_coverage.cpp
Normal file
792
party/flows_coverage.cpp
Normal file
|
|
@ -0,0 +1,792 @@
|
|||
/// @file party/flows_coverage.cpp
|
||||
/// @brief Party flows that close protocol / type gaps not hit by other amalgams.
|
||||
/// @details Amalgamated into run.cpp (do not compile as a second TU). Dealer
|
||||
/// (p2) distributes keys for paint / incremental / exotic payloads
|
||||
/// because oblivious DS rejects paint kinds. Geneval full/sequence and
|
||||
/// arith_output are dealer-run and share-split. CCMP is a 2-party mesh.
|
||||
|
||||
#include "cases.hpp"
|
||||
#include "dist_dpf3.hpp"
|
||||
#include "flow_util.hpp"
|
||||
#include "key_io.hpp"
|
||||
#include "registry.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "simde/simde/x86/avx2.h"
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "dpf/bitstring.hpp"
|
||||
#include "dpf/constrained_cmp.hpp"
|
||||
#include "dpf/dcf.hpp"
|
||||
#include "dpf/dpf3_ds.hpp"
|
||||
#include "dpf/eval_full.hpp"
|
||||
#include "dpf/eval_inner_product.hpp"
|
||||
#include "dpf/eval_point.hpp"
|
||||
#include "dpf/eval_sequence.hpp"
|
||||
#include "dpf/field128.hpp"
|
||||
#include "dpf/geneval.hpp"
|
||||
#include "dpf/json.hpp"
|
||||
#include "dpf/keyword2.hpp"
|
||||
#include "dpf/modint.hpp"
|
||||
#include "dpf/p256.hpp"
|
||||
#include "grotto/fixedpoint.hpp"
|
||||
#include "grotto/nmod.hpp"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
namespace coverage
|
||||
{
|
||||
|
||||
using util::open_additive;
|
||||
using util::open_subtractive;
|
||||
using util::require;
|
||||
using util::role;
|
||||
using util::share_bits;
|
||||
using util::trio;
|
||||
using util::u64;
|
||||
|
||||
static constexpr char cov_kw_pat[] = "[ab]{8}";
|
||||
|
||||
// ---- shared helpers --------------------------------------------------------
|
||||
|
||||
std::uint64_t paint_matched_unit(std::size_t matched, std::uint64_t, bool)
|
||||
{
|
||||
return static_cast<std::uint64_t>(matched);
|
||||
}
|
||||
|
||||
std::uint64_t lcp_len(std::uint8_t x, std::uint8_t alpha, std::size_t n = 8)
|
||||
{
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const std::uint8_t shift = static_cast<std::uint8_t>(n - 1 - i);
|
||||
if (((x >> shift) & 1) != ((alpha >> shift) & 1))
|
||||
return static_cast<std::uint64_t>(i);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename Spec, typename Fn0, typename Fn1>
|
||||
void deal_cmp_keys(trio & net, role self, Spec spec, const Fn0 & on0,
|
||||
const Fn1 & on1)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0xB4;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha, spec);
|
||||
send_key(net.to(role::p0), k0);
|
||||
send_key(net.to(role::p1), k1);
|
||||
return;
|
||||
}
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(make_dpf(Input{}, spec)))>;
|
||||
auto key = recv_key<K>(net.to(role::p2));
|
||||
on0(key, alpha);
|
||||
return;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(make_dpf(Input{}, spec)))>;
|
||||
auto key = recv_key<K>(net.to(role::p2));
|
||||
on1(key, alpha);
|
||||
}
|
||||
|
||||
template <typename Key>
|
||||
void check_paint_grid(trio & net, role self, const Key & key, std::uint8_t alpha,
|
||||
auto expect_fn, const char * tag)
|
||||
{
|
||||
const u64 mask = key.cmp().mask;
|
||||
const std::array<std::uint8_t, 8> pts{
|
||||
0, 1, alpha, static_cast<std::uint8_t>(alpha ^ 1u), 0x55, 0xAA, 0xFE, 0xFF};
|
||||
for (std::uint8_t x : pts)
|
||||
{
|
||||
const u64 mine = share_bits(eval_point(cmp, key, x)) & mask;
|
||||
const u64 opened = open_additive(net, self, mine) & mask;
|
||||
if (self == role::p0)
|
||||
require(opened == (expect_fn(x, alpha) & mask), tag);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- paints (F_DCF path paints; dealer distribute) --------------------------
|
||||
|
||||
int cov_paint_lcp(role self, trio & net)
|
||||
{
|
||||
auto on = [&](const auto & key, std::uint8_t alpha) {
|
||||
check_paint_grid(net, self, key, alpha,
|
||||
[](std::uint8_t x, std::uint8_t a) { return lcp_len(x, a); },
|
||||
"paint lcp");
|
||||
};
|
||||
deal_cmp_keys(net, self, lcp(u64{1}), on, on);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_paint_one_hot(role self, trio & net)
|
||||
{
|
||||
auto on = [&](const auto & key, std::uint8_t alpha) {
|
||||
check_paint_grid(net, self, key, alpha,
|
||||
[](std::uint8_t x, std::uint8_t a) {
|
||||
return 1ULL << lcp_len(x, a);
|
||||
},
|
||||
"paint one_hot");
|
||||
};
|
||||
deal_cmp_keys(net, self, diverge_one_hot(u64{1}), on, on);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_paint_break_bit(role self, trio & net)
|
||||
{
|
||||
auto on = [&](const auto & key, std::uint8_t alpha) {
|
||||
check_paint_grid(net, self, key, alpha,
|
||||
[](std::uint8_t x, std::uint8_t a) {
|
||||
const auto d = lcp_len(x, a);
|
||||
if (d >= 8)
|
||||
return 0ULL;
|
||||
return 3ULL * ((a >> (7 - d)) & 1);
|
||||
},
|
||||
"paint break_bit");
|
||||
};
|
||||
deal_cmp_keys(net, self, break_bit(u64{3}), on, on);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_paint_path(role self, trio & net)
|
||||
{
|
||||
auto on = [&](const auto & key, std::uint8_t alpha) {
|
||||
check_paint_grid(net, self, key, alpha,
|
||||
[](std::uint8_t x, std::uint8_t a) { return lcp_len(x, a); },
|
||||
"paint path");
|
||||
};
|
||||
deal_cmp_keys(net, self, path_paint(paint_matched_unit), on, on);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_paint_prefix_with_length(role self, trio & net)
|
||||
{
|
||||
auto on = [&](const auto & key, std::uint8_t alpha) {
|
||||
check_paint_grid(net, self, key, alpha,
|
||||
[](std::uint8_t x, std::uint8_t a) {
|
||||
const auto d = lcp_len(x, a);
|
||||
const u64 low = d == 0 ? 0
|
||||
: (d >= 8 ? a : static_cast<u64>(a) >> (8 - d));
|
||||
return (low << 4) | d;
|
||||
},
|
||||
"paint prefix_with_length");
|
||||
};
|
||||
deal_cmp_keys(net, self, prefix_with_length<4>(u64{1}), on, on);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- F_IDPF / eq_at / idcf -------------------------------------------------
|
||||
|
||||
int cov_idpf_at(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint16_t;
|
||||
const Input alpha = 0xa5c3;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha, at<8>(std::uint8_t{42}),
|
||||
at<12>(std::uint16_t{7}));
|
||||
send_key(net.to(role::p0), k0);
|
||||
send_key(net.to(role::p1), k1);
|
||||
return 0;
|
||||
}
|
||||
auto check = [&](const auto & key) {
|
||||
const auto y8 = *eval_point(out<0, 8>, key, alpha);
|
||||
const auto o8 = open_subtractive(net, self, share_bits(y8));
|
||||
if (self == role::p0)
|
||||
require(o8 == std::uint8_t{42}, "idpf at8");
|
||||
const auto y12 = *eval_point(out<1, 12>, key, alpha);
|
||||
const auto o12 = open_subtractive(net, self, share_bits(y12));
|
||||
if (self == role::p0)
|
||||
require(o12 == std::uint16_t{7}, "idpf at12");
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(
|
||||
make_dpf(Input{}, at<8>(std::uint8_t{}), at<12>(std::uint16_t{}))))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(
|
||||
make_dpf(Input{}, at<8>(std::uint8_t{}), at<12>(std::uint16_t{}))))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_eq_at(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint16_t;
|
||||
const Input alpha = 0x1234;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha,
|
||||
eq_at<8>(std::uint16_t{99}, std::uint16_t{7}));
|
||||
send_key(net.to(role::p0), k0);
|
||||
send_key(net.to(role::p1), k1);
|
||||
return 0;
|
||||
}
|
||||
auto check = [&](const auto & key) {
|
||||
// High 8 bits of alpha match → true branch 99 on that prefix lane.
|
||||
const auto y_on = *eval_point(out<0, 8>, key, alpha);
|
||||
const auto o_on = open_subtractive(net, self, share_bits(y_on));
|
||||
if (self == role::p0)
|
||||
require(o_on == std::uint16_t{99}, "eq_at on");
|
||||
const Input miss = static_cast<Input>(alpha ^ 0x0100);
|
||||
const auto y_off = *eval_point(out<0, 8>, key, miss);
|
||||
const auto o_off = open_subtractive(net, self, share_bits(y_off));
|
||||
if (self == role::p0)
|
||||
require(o_off == std::uint16_t{7}, "eq_at off");
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(
|
||||
make_dpf(Input{}, eq_at<8>(std::uint16_t{}, std::uint16_t{}))))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(
|
||||
make_dpf(Input{}, eq_at<8>(std::uint16_t{}, std::uint16_t{}))))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_idcf(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0x40;
|
||||
const u64 beta = 5;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha, idcf(gt(beta)));
|
||||
send_key(net.to(role::p0), k0);
|
||||
send_key(net.to(role::p1), k1);
|
||||
return 0;
|
||||
}
|
||||
auto check = [&](const auto & key) {
|
||||
const u64 mask = key.cmp().mask;
|
||||
for (Input x : {Input{0x3f}, alpha, Input{0x41}})
|
||||
{
|
||||
const u64 mine = share_bits(eval_point(cmp, key, x)) & mask;
|
||||
const u64 opened = open_additive(net, self, mine) & mask;
|
||||
if (self == role::p0)
|
||||
{
|
||||
const u64 want = x > alpha ? beta : 0u;
|
||||
require(opened == want, "idcf gt");
|
||||
}
|
||||
}
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(
|
||||
make_dpf(Input{}, idcf(gt(u64{})))))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(
|
||||
make_dpf(Input{}, idcf(gt(u64{})))))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- F_GenEval full / sequence / arith_output ------------------------------
|
||||
|
||||
struct CovPad
|
||||
{
|
||||
std::uint64_t n = 1;
|
||||
simde__m128i block()
|
||||
{
|
||||
auto v = simde_mm_set_epi64x(static_cast<long long>(n),
|
||||
static_cast<long long>(n * 9 + 3));
|
||||
n += 2;
|
||||
return v;
|
||||
}
|
||||
std::uint8_t bit() { return static_cast<std::uint8_t>(n++ & 1u); }
|
||||
};
|
||||
|
||||
simde__m128i cov_roots[32];
|
||||
int cov_ri = 0;
|
||||
simde__m128i cov_take_root() { return cov_roots[cov_ri++]; }
|
||||
|
||||
void cov_reset_roots()
|
||||
{
|
||||
cov_ri = 0;
|
||||
for (int i = 0; i < 32; ++i)
|
||||
cov_roots[i] = simde_mm_set_epi64x(0x2222 * (i + 1), 0xBEEF0000u + i * 13);
|
||||
}
|
||||
|
||||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||||
ds_randomness<simde__m128i (*)(), CovPad> cov_rng()
|
||||
{
|
||||
return {cov_take_root, CovPad{}};
|
||||
}
|
||||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||||
|
||||
int cov_geneval_full(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
using Output = std::uint8_t;
|
||||
const Input alpha = 0x3c;
|
||||
const Input x0 = 0x10;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
const Output y = 0x7e;
|
||||
if (self == role::p2)
|
||||
{
|
||||
cov_reset_roots();
|
||||
auto g = geneval_full(x0, x1, cov_rng(), y);
|
||||
require(g.party0.size() == 256u, "geneval_full size");
|
||||
net.to(role::p0).send_vec(g.party0);
|
||||
net.to(role::p1).send_vec(g.party1);
|
||||
return 0;
|
||||
}
|
||||
auto mine = net.to(role::p2).recv_vec<Output>();
|
||||
require(mine.size() == 256u, "geneval_full recv");
|
||||
const Output opened = open_subtractive(net, self, mine[alpha]);
|
||||
const Output off = open_subtractive(net, self, mine[0]);
|
||||
if (self == role::p0)
|
||||
{
|
||||
require(opened == y, "geneval_full on");
|
||||
require(off == Output{0}, "geneval_full off");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_geneval_sequence(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
using Output = std::uint16_t;
|
||||
const Input alpha = 0x2a;
|
||||
const Input x0 = 0x07;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
const Output y = 99;
|
||||
const std::array<Input, 5> seq{0, 1, alpha, 200, 255};
|
||||
if (self == role::p2)
|
||||
{
|
||||
cov_reset_roots();
|
||||
auto g = geneval_sequence(x0, x1, seq.begin(), seq.end(), cov_rng(), y);
|
||||
require(g.party0.size() == seq.size(), "geneval_seq size");
|
||||
net.to(role::p0).send_vec(g.party0);
|
||||
net.to(role::p1).send_vec(g.party1);
|
||||
return 0;
|
||||
}
|
||||
auto mine = net.to(role::p2).recv_vec<Output>();
|
||||
require(mine.size() == seq.size(), "geneval_seq recv");
|
||||
for (std::size_t i = 0; i < seq.size(); ++i)
|
||||
{
|
||||
const Output opened = open_subtractive(net, self, mine[i]);
|
||||
if (self == role::p0)
|
||||
require(opened == (seq[i] == alpha ? y : Output{0}), "geneval_seq");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_arith_output(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
using Output = std::uint32_t;
|
||||
const Input alpha = 0x2a;
|
||||
const Input x0 = 0x55;
|
||||
const Input x1 = static_cast<Input>(alpha ^ x0);
|
||||
const Output beta = 0x01020304;
|
||||
const Output y0 = 0x00010002;
|
||||
const Output y1 = static_cast<Output>(beta - y0);
|
||||
if (self == role::p2)
|
||||
{
|
||||
cov_reset_roots();
|
||||
auto g = geneval_point(arith_output, x0, x1, alpha, cov_rng(), y0, y1);
|
||||
require(g.party0.size() == 1u && g.leaf_live, "arith_output live");
|
||||
net.to(role::p0).send(net::msg::delta, g.party0[0]);
|
||||
net.to(role::p1).send(net::msg::delta, g.party1[0]);
|
||||
return 0;
|
||||
}
|
||||
const Output mine = net.to(role::p2).recv<Output>(net::msg::delta);
|
||||
const Output opened = open_subtractive(net, self, mine);
|
||||
if (self == role::p0)
|
||||
require(opened == beta, "arith_output");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- F_CCMP ----------------------------------------------------------------
|
||||
|
||||
int cov_ccmp(role self, trio & net)
|
||||
{
|
||||
if (self == role::p2)
|
||||
return 0;
|
||||
const u64 mine = self == role::p0 ? 4u : 5u;
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
std::uint8_t z0 = 0, z1 = 0, l = 0;
|
||||
detail::ccmp_party_terms(mine, self == role::p0 ? 0 : 1, z0, z1, l);
|
||||
const std::uint8_t peer_z0 = net.exchange_with(peer, z0);
|
||||
const std::uint8_t peer_z1 = net.exchange_with(peer, z1);
|
||||
const std::uint8_t peer_l = net.exchange_with(peer, l);
|
||||
const std::uint8_t opened_z0 = static_cast<std::uint8_t>(z0 ^ peer_z0);
|
||||
const std::uint8_t opened_z1 = static_cast<std::uint8_t>(z1 ^ peer_z1);
|
||||
const std::uint8_t t = static_cast<std::uint8_t>(opened_z0 & opened_z1);
|
||||
const std::uint8_t l1 = self == role::p1 ? l : peer_l;
|
||||
const std::uint8_t got = static_cast<std::uint8_t>(t ^ l1);
|
||||
require(got == local_ccmp(4u, 5u), "ccmp mesh");
|
||||
require(got == 1u, "ccmp 4<5");
|
||||
bool bad = false;
|
||||
try
|
||||
{
|
||||
(void)local_ccmp(4u, 6u);
|
||||
}
|
||||
catch (const std::invalid_argument &)
|
||||
{
|
||||
bad = true;
|
||||
}
|
||||
require(bad, "ccmp rejects");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- exotic payloads / domains ---------------------------------------------
|
||||
|
||||
template <typename Output>
|
||||
int cov_payload_point(role self, trio & net, Output beta, const char * tag)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0x11;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha, beta);
|
||||
send_key(net.to(role::p0), k0);
|
||||
send_key(net.to(role::p1), k1);
|
||||
return 0;
|
||||
}
|
||||
auto check = [&](const auto & key) {
|
||||
const auto on = *eval_point(key, alpha);
|
||||
const auto off = *eval_point(key, static_cast<Input>(alpha ^ 1u));
|
||||
const Output o_on = open_subtractive(net, self, share_bits(on));
|
||||
const Output o_off = open_subtractive(net, self, share_bits(off));
|
||||
if (self == role::p0)
|
||||
{
|
||||
require(o_on == beta, tag);
|
||||
require(o_off == Output{}, tag);
|
||||
}
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(make_dpf(Input{}, beta)))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(make_dpf(Input{}, beta)))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_field128(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, field128{42}, "field128");
|
||||
}
|
||||
|
||||
int cov_gf2(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, dpf::gf2{1}, "gf2");
|
||||
}
|
||||
|
||||
int cov_gf22(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, dpf::gf22{3}, "gf22");
|
||||
}
|
||||
|
||||
int cov_gf24(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, dpf::gf24{0xa}, "gf24");
|
||||
}
|
||||
|
||||
int cov_gf28(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, dpf::gf28{0x1b}, "gf28");
|
||||
}
|
||||
|
||||
int cov_gf216(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, dpf::gf216{0x2d}, "gf216");
|
||||
}
|
||||
|
||||
int cov_gf232(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, dpf::gf232{0x90200001u}, "gf232");
|
||||
}
|
||||
|
||||
int cov_gf264(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, dpf::gf264{0x11}, "gf264");
|
||||
}
|
||||
|
||||
int cov_p256(role self, trio & net)
|
||||
{
|
||||
return cov_payload_point(self, net, p256{1}, "p256");
|
||||
}
|
||||
|
||||
int cov_bitstring(role self, trio & net)
|
||||
{
|
||||
using bs = bitstring<16>;
|
||||
return cov_payload_point(self, net, bs{0xBEEFu}, "bitstring");
|
||||
}
|
||||
|
||||
int cov_fixedpoint(role self, trio & net)
|
||||
{
|
||||
using fp = grotto::fixedpoint<16>;
|
||||
return cov_payload_point(self, net, fp::from_raw(0x00018000), "fixedpoint");
|
||||
}
|
||||
|
||||
int cov_keyword2(role self, trio & net)
|
||||
{
|
||||
using kw = keyword2<cov_kw_pat>;
|
||||
const kw alpha{"abababab"};
|
||||
const u64 beta = 3;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha, beta);
|
||||
send_key(net.to(role::p0), k0);
|
||||
send_key(net.to(role::p1), k1);
|
||||
return 0;
|
||||
}
|
||||
auto check = [&](const auto & key) {
|
||||
const auto on = *eval_point(key, alpha);
|
||||
const auto off = *eval_point(key, kw{"babababa"});
|
||||
const auto o_on = open_subtractive(net, self, share_bits(on));
|
||||
const auto o_off = open_subtractive(net, self, share_bits(off));
|
||||
if (self == role::p0)
|
||||
{
|
||||
require(o_on == beta, "keyword2 on");
|
||||
require(o_off == 0u, "keyword2 off");
|
||||
}
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(make_dpf(kw{}, u64{})))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(make_dpf(kw{}, u64{})))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cov_modint_domain(role self, trio & net)
|
||||
{
|
||||
using in_t = modint<10>;
|
||||
const in_t alpha{3};
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha, at<6>(std::uint8_t{1}), std::uint16_t{2});
|
||||
send_key(net.to(role::p0), k0);
|
||||
send_key(net.to(role::p1), k1);
|
||||
return 0;
|
||||
}
|
||||
auto check = [&](const auto & key) {
|
||||
const auto y0 = *eval_point(out<0, 6>, key, alpha);
|
||||
const auto o0 = open_subtractive(net, self, share_bits(y0));
|
||||
if (self == role::p0)
|
||||
require(o0 == std::uint8_t{1}, "modint at6");
|
||||
const auto y1 = *eval_point(out<1>, key, alpha);
|
||||
const auto o1 = open_subtractive(net, self, share_bits(y1));
|
||||
if (self == role::p0)
|
||||
require(o1 == std::uint16_t{2}, "modint leaf");
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(
|
||||
make_dpf(in_t{}, at<6>(std::uint8_t{}), std::uint16_t{})))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(
|
||||
make_dpf(in_t{}, at<6>(std::uint8_t{}), std::uint16_t{})))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- grotto nmod (public reduction helper over the mesh) -------------------
|
||||
|
||||
int cov_nmod(role self, trio & net)
|
||||
{
|
||||
// 42 / 10 with 1/10 ≈ 0x199999999999999a / 2^64, keep 8 residue bits.
|
||||
const std::int64_t x_raw = 42;
|
||||
const unsigned x_bits = 0;
|
||||
const unsigned __int128 recip = 0x199999999999999aull;
|
||||
const unsigned recip_bits = 64;
|
||||
const unsigned residue_bits = 8;
|
||||
auto got = grotto::nmod(x_raw, x_bits, recip, recip_bits, residue_bits);
|
||||
if (self == role::p2)
|
||||
{
|
||||
net.to(role::p0).send(net::msg::delta, got.quotient);
|
||||
net.to(role::p0).send(net::msg::delta, got.residue);
|
||||
net.to(role::p1).send(net::msg::delta, got.quotient);
|
||||
net.to(role::p1).send(net::msg::delta, got.residue);
|
||||
return 0;
|
||||
}
|
||||
const auto q = net.to(role::p2).recv<std::int64_t>(net::msg::delta);
|
||||
const auto r = net.to(role::p2).recv<std::int64_t>(net::msg::delta);
|
||||
require(q == got.quotient && r == got.residue, "nmod agree");
|
||||
require(q == 4 && r != 0, "nmod 42/10");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- JSON key round-trip over the mesh -------------------------------------
|
||||
|
||||
int cov_json_roundtrip(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 0x2a;
|
||||
const u64 beta = 17;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha, beta);
|
||||
const std::string s0 = json::to_json(k0);
|
||||
const std::string s1 = json::to_json(k1);
|
||||
net.to(role::p0).send_bytes(net::msg::dpf_key,
|
||||
reinterpret_cast<const std::uint8_t *>(s0.data()), s0.size());
|
||||
net.to(role::p1).send_bytes(net::msg::dpf_key,
|
||||
reinterpret_cast<const std::uint8_t *>(s1.data()), s1.size());
|
||||
return 0;
|
||||
}
|
||||
auto body = net.to(role::p2).recv_bytes(net::msg::dpf_key);
|
||||
const std::string s(reinterpret_cast<const char *>(body.data()), body.size());
|
||||
using K0 = std::decay_t<decltype(std::get<0>(make_dpf(Input{}, u64{})))>;
|
||||
using K1 = std::decay_t<decltype(std::get<1>(make_dpf(Input{}, u64{})))>;
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto key = json::from_json<K0>(s);
|
||||
const auto y = *eval_point(key, alpha);
|
||||
const auto open = open_subtractive(net, self, share_bits(y));
|
||||
require(open == beta, "json on");
|
||||
return 0;
|
||||
}
|
||||
auto key = json::from_json<K1>(s);
|
||||
const auto y = *eval_point(key, alpha);
|
||||
(void)open_subtractive(net, self, share_bits(y));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- F_DPF3DS local API (dealer make_dpf3_doerner_shelat) -------------------
|
||||
|
||||
int cov_dpf3_ds_local(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{9};
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k1, k2, k3] = make_dpf3_doerner_shelat(x0, x1, beta);
|
||||
send_key(net.to(role::p0), k1);
|
||||
send_key(net.to(role::p1), k2);
|
||||
const fp61 y = eval_point(k3, alpha);
|
||||
const fp61 got = recent::open_shamir3(net, self, y);
|
||||
require(got == beta, "dpf3_ds on");
|
||||
const fp61 z = eval_point(k3, static_cast<Input>(alpha ^ 1u));
|
||||
const fp61 off = recent::open_shamir3(net, self, z);
|
||||
require(off == fp61{}, "dpf3_ds off");
|
||||
return 0;
|
||||
}
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(
|
||||
make_dpf3_doerner_shelat(Input{}, Input{}, fp61{})))>;
|
||||
auto key = recv_key<K>(net.to(role::p2));
|
||||
(void)recent::open_shamir3(net, self, eval_point(key, alpha));
|
||||
(void)recent::open_shamir3(net, self,
|
||||
eval_point(key, static_cast<Input>(alpha ^ 1u)));
|
||||
return 0;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(
|
||||
make_dpf3_doerner_shelat(Input{}, Input{}, fp61{})))>;
|
||||
auto key = recv_key<K>(net.to(role::p2));
|
||||
(void)recent::open_shamir3(net, self, eval_point(key, alpha));
|
||||
(void)recent::open_shamir3(net, self,
|
||||
eval_point(key, static_cast<Input>(alpha ^ 1u)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- eval_inner_product on a point DPF -------------------------------------
|
||||
|
||||
int cov_eval_inner_product(role self, trio & net)
|
||||
{
|
||||
using Input = std::uint8_t;
|
||||
const Input alpha = 42;
|
||||
const u64 beta = 7;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto [k0, k1] = make_dpf(alpha, beta);
|
||||
send_key(net.to(role::p0), k0);
|
||||
send_key(net.to(role::p1), k1);
|
||||
return 0;
|
||||
}
|
||||
auto check = [&](const auto & key) {
|
||||
std::vector<u64> w(11, 1);
|
||||
const u64 mine = share_bits(
|
||||
eval_inner_product(paired, key, Input{40}, Input{50}, w));
|
||||
// Point leaves are subtractive; the guided-tour reconstruct is additive
|
||||
// on the opened difference after both parties contribute.
|
||||
const u64 opened = open_subtractive(net, self, mine);
|
||||
if (self == role::p0)
|
||||
require(opened == beta * w[2], "eval_inner_product");
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
using K = std::decay_t<decltype(std::get<0>(make_dpf(Input{}, u64{})))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
using K = std::decay_t<decltype(std::get<1>(make_dpf(Input{}, u64{})))>;
|
||||
check(recv_key<K>(net.to(role::p2)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define REG(name, tags, fn) \
|
||||
register_flow(flow{#name, tags, fn, false})
|
||||
|
||||
} // namespace coverage
|
||||
|
||||
void register_coverage_flows()
|
||||
{
|
||||
using namespace coverage;
|
||||
REG(cov_paint_lcp, "coverage paint dcf", cov_paint_lcp);
|
||||
REG(cov_paint_one_hot, "coverage paint dcf", cov_paint_one_hot);
|
||||
REG(cov_paint_break_bit, "coverage paint dcf", cov_paint_break_bit);
|
||||
REG(cov_paint_path, "coverage paint dcf", cov_paint_path);
|
||||
REG(cov_paint_prefix_with_length, "coverage paint dcf",
|
||||
cov_paint_prefix_with_length);
|
||||
REG(cov_idpf_at, "coverage idpf", cov_idpf_at);
|
||||
REG(cov_eq_at, "coverage eq_at", cov_eq_at);
|
||||
REG(cov_idcf, "coverage idcf", cov_idcf);
|
||||
REG(cov_geneval_full, "coverage geneval", cov_geneval_full);
|
||||
REG(cov_geneval_sequence, "coverage geneval", cov_geneval_sequence);
|
||||
REG(cov_arith_output, "coverage arith_output", cov_arith_output);
|
||||
REG(cov_ccmp, "coverage ccmp", cov_ccmp);
|
||||
REG(cov_field128, "coverage payload", cov_field128);
|
||||
REG(cov_gf2, "coverage payload", cov_gf2);
|
||||
REG(cov_gf22, "coverage payload", cov_gf22);
|
||||
REG(cov_gf24, "coverage payload", cov_gf24);
|
||||
REG(cov_gf28, "coverage payload", cov_gf28);
|
||||
REG(cov_gf216, "coverage payload", cov_gf216);
|
||||
REG(cov_gf232, "coverage payload", cov_gf232);
|
||||
REG(cov_gf264, "coverage payload", cov_gf264);
|
||||
REG(cov_p256, "coverage payload", cov_p256);
|
||||
REG(cov_bitstring, "coverage payload", cov_bitstring);
|
||||
REG(cov_fixedpoint, "coverage payload", cov_fixedpoint);
|
||||
REG(cov_keyword2, "coverage keyword2", cov_keyword2);
|
||||
REG(cov_modint_domain, "coverage modint", cov_modint_domain);
|
||||
REG(cov_nmod, "coverage nmod", cov_nmod);
|
||||
REG(cov_json_roundtrip, "coverage json", cov_json_roundtrip);
|
||||
REG(cov_dpf3_ds_local, "coverage dpf3_ds", cov_dpf3_ds_local);
|
||||
REG(cov_eval_inner_product, "coverage eval_inner_product",
|
||||
cov_eval_inner_product);
|
||||
}
|
||||
|
||||
#undef REG
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
1080
party/flows_extreme.cpp
Normal file
1080
party/flows_extreme.cpp
Normal file
File diff suppressed because it is too large
Load diff
642
party/flows_gadget.cpp
Normal file
642
party/flows_gadget.cpp
Normal file
|
|
@ -0,0 +1,642 @@
|
|||
/// @file party/flows_gadget.cpp
|
||||
/// @brief Bench flows for word garbling, stacked Yao, FLUTE, and hidden shuffle.
|
||||
/// @details Amalgamated into run.cpp. Every cross-party byte goes through
|
||||
/// `net::trio` (the same local mesh `party_bench` already dials).
|
||||
/// Two-party gadgets use the p0–p1 link. FLUTE's dealer is p2.
|
||||
/// The hidden shuffle is the three ring passes, and the receiver
|
||||
/// feeds the arrived array into the next party step.
|
||||
|
||||
#include "cases.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/arith_garble.hpp"
|
||||
#include "dpf/flute.hpp"
|
||||
#include "dpf/shuffle.hpp"
|
||||
#include "dpf/yao.hpp"
|
||||
#include "dpf/yao_stack.hpp"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
namespace gadget
|
||||
{
|
||||
|
||||
using net::role;
|
||||
using net::trio;
|
||||
|
||||
void require(bool cond, const char * msg)
|
||||
{
|
||||
if (!cond)
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
void put_lab(std::vector<std::uint16_t> & blob, const arith_garble::lab & lab)
|
||||
{
|
||||
blob.insert(blob.end(), lab.d.begin(), lab.d.end());
|
||||
}
|
||||
|
||||
arith_garble::lab take_lab(const std::uint16_t *& p, const std::uint16_t * end,
|
||||
std::uint16_t mod)
|
||||
{
|
||||
if (static_cast<std::size_t>(end - p) < arith_garble::k_digits)
|
||||
throw std::runtime_error("arith garble: short label");
|
||||
arith_garble::lab lab;
|
||||
lab.mod = mod;
|
||||
for (std::size_t i = 0; i < arith_garble::k_digits; ++i)
|
||||
lab.d[i] = *p++;
|
||||
return lab;
|
||||
}
|
||||
|
||||
std::uint16_t take_u16(const std::uint16_t *& p, const std::uint16_t * end)
|
||||
{
|
||||
if (p == end)
|
||||
throw std::runtime_error("arith garble: short frame");
|
||||
return *p++;
|
||||
}
|
||||
|
||||
/// @brief p0 garbles and ships the evaluator view. p1 evaluates that view.
|
||||
void arith_exchange(trio & net, role self, const arith_garble::circuit & c,
|
||||
const std::uint16_t * semantic)
|
||||
{
|
||||
const auto plain = arith_garble::eval_plain(c, semantic);
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto st = arith_garble::detail::garble(c);
|
||||
auto active = arith_garble::detail::evaluate(c, st, semantic);
|
||||
std::size_t out_i = 0;
|
||||
for (std::uint32_t id : c.outputs())
|
||||
{
|
||||
const std::uint16_t mod = c.nodes()[id].mod;
|
||||
const std::uint16_t got = arith_garble::open_shares(
|
||||
mod, st.zero[id].d[0], active[id].d[0]);
|
||||
require(got == plain[id], "arith garble: garbler open");
|
||||
++out_i;
|
||||
}
|
||||
require(out_i == c.outputs().size(), "arith garble: outputs");
|
||||
|
||||
std::vector<std::uint16_t> blob;
|
||||
const auto & nodes = c.nodes();
|
||||
for (std::uint32_t i = 0; i < nodes.size(); ++i)
|
||||
{
|
||||
const auto & nd = nodes[i];
|
||||
if (nd.code == arith_garble::circuit::op::in)
|
||||
put_lab(blob, active[i]);
|
||||
else if (nd.code == arith_garble::circuit::op::proj)
|
||||
{
|
||||
for (const auto & row : st.proj[i].row)
|
||||
put_lab(blob, row);
|
||||
}
|
||||
else if (nd.code == arith_garble::circuit::op::pass)
|
||||
{
|
||||
put_lab(blob, st.pass[i].payload[0]);
|
||||
put_lab(blob, st.pass[i].payload[1]);
|
||||
blob.push_back(st.pass[i].flag_ct[0]);
|
||||
blob.push_back(st.pass[i].flag_ct[1]);
|
||||
}
|
||||
}
|
||||
for (std::uint32_t id : c.outputs())
|
||||
blob.push_back(st.zero[id].d[0]);
|
||||
net.send_vec_to(role::p1, net::msg::bytes, blob);
|
||||
return;
|
||||
}
|
||||
|
||||
require(self == role::p1, "arith garble: party");
|
||||
auto blob = net.recv_vec_from<std::uint16_t>(role::p0, net::msg::bytes);
|
||||
const std::uint16_t * p = blob.data();
|
||||
const std::uint16_t * end = p + blob.size();
|
||||
arith_garble::detail::garble_state st;
|
||||
const auto & nodes = c.nodes();
|
||||
st.zero.resize(nodes.size());
|
||||
st.delta.assign(static_cast<std::size_t>(arith_garble::k_max_mod) + 1,
|
||||
arith_garble::lab{});
|
||||
for (std::uint16_t m = 1; m <= arith_garble::k_max_mod; ++m)
|
||||
st.delta[m].mod = m;
|
||||
st.proj.resize(nodes.size());
|
||||
st.pass.resize(nodes.size());
|
||||
std::size_t n_in = 0;
|
||||
for (const auto & nd : nodes)
|
||||
if (nd.code == arith_garble::circuit::op::in)
|
||||
++n_in;
|
||||
for (std::uint32_t i = 0; i < nodes.size(); ++i)
|
||||
{
|
||||
const auto & nd = nodes[i];
|
||||
if (nd.code == arith_garble::circuit::op::in)
|
||||
st.zero[i] = take_lab(p, end, nd.mod);
|
||||
else if (nd.code == arith_garble::circuit::op::proj)
|
||||
{
|
||||
const std::uint16_t m = nodes[nd.a].mod;
|
||||
require(m > 0, "arith garble: projection modulus");
|
||||
st.proj[i].row.resize(static_cast<std::size_t>(m - 1));
|
||||
for (std::uint16_t color = 1; color < m; ++color)
|
||||
st.proj[i].row[static_cast<std::size_t>(color - 1)] =
|
||||
take_lab(p, end, nd.mod);
|
||||
}
|
||||
else if (nd.code == arith_garble::circuit::op::pass)
|
||||
{
|
||||
st.pass[i].payload[0] = take_lab(p, end, nd.mod);
|
||||
st.pass[i].payload[1] = take_lab(p, end, nd.mod);
|
||||
st.pass[i].flag_ct[0] = take_u16(p, end);
|
||||
st.pass[i].flag_ct[1] = take_u16(p, end);
|
||||
}
|
||||
}
|
||||
std::vector<std::uint16_t> masks;
|
||||
masks.reserve(c.outputs().size());
|
||||
for (std::size_t i = 0; i < c.outputs().size(); ++i)
|
||||
masks.push_back(take_u16(p, end));
|
||||
require(p == end, "arith garble: trailing bytes");
|
||||
|
||||
std::vector<std::uint16_t> zeros(n_in, 0);
|
||||
auto active = arith_garble::detail::evaluate(c, st, zeros.data());
|
||||
std::size_t out_i = 0;
|
||||
for (std::uint32_t id : c.outputs())
|
||||
{
|
||||
const std::uint16_t mod = nodes[id].mod;
|
||||
const std::uint16_t got = arith_garble::open_shares(
|
||||
mod, masks[out_i], active[id].d[0]);
|
||||
require(got == plain[id], "arith garble: evaluator open");
|
||||
++out_i;
|
||||
}
|
||||
}
|
||||
|
||||
int run_proj(role self, trio & net, std::uint16_t mod)
|
||||
{
|
||||
if (self == role::p2)
|
||||
return 0;
|
||||
arith_garble::circuit c;
|
||||
auto x = c.input(mod);
|
||||
std::vector<std::uint16_t> phi(mod);
|
||||
for (std::uint16_t i = 0; i < mod; ++i)
|
||||
phi[i] = static_cast<std::uint16_t>((i * 3) % mod);
|
||||
c.out(c.project(x, mod, std::move(phi)));
|
||||
const std::uint16_t in = static_cast<std::uint16_t>(mod / 2);
|
||||
arith_exchange(net, self, c, &in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run_mul(role self, trio & net, std::uint16_t p)
|
||||
{
|
||||
if (self == role::p2)
|
||||
return 0;
|
||||
arith_garble::circuit c;
|
||||
auto x = c.input(p);
|
||||
auto y = c.input(p);
|
||||
c.out(c.mul(x, y));
|
||||
const std::uint16_t in[2] = {static_cast<std::uint16_t>(p - 1), 2};
|
||||
arith_exchange(net, self, c, in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run_thresh(role self, trio & net, unsigned b)
|
||||
{
|
||||
if (self == role::p2)
|
||||
return 0;
|
||||
arith_garble::circuit c;
|
||||
const auto mod = static_cast<std::uint16_t>(b + 1);
|
||||
std::vector<arith_garble::wire> bits;
|
||||
bits.reserve(b);
|
||||
for (unsigned i = 0; i < b; ++i)
|
||||
bits.push_back(c.input(mod));
|
||||
c.out(c.threshold(bits, static_cast<std::uint16_t>(b / 2)));
|
||||
std::vector<std::uint16_t> in(b, 0);
|
||||
for (unsigned i = 0; i < b; i += 2)
|
||||
in[i] = 1;
|
||||
arith_exchange(net, self, c, in.data());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run_chain(role self, trio & net)
|
||||
{
|
||||
if (self == role::p2)
|
||||
return 0;
|
||||
arith_garble::circuit c;
|
||||
auto x = c.input(7);
|
||||
auto y = c.input(7);
|
||||
auto z = c.mul(x, y);
|
||||
for (int i = 0; i < 3; ++i)
|
||||
z = c.mul(z, x);
|
||||
c.out(z);
|
||||
const std::uint16_t in[2] = {2, 3};
|
||||
arith_exchange(net, self, c, in);
|
||||
return 0;
|
||||
}
|
||||
|
||||
yao::netlist and_n(unsigned n)
|
||||
{
|
||||
yao::netlist nl;
|
||||
std::vector<yao::bit> in;
|
||||
in.reserve(n);
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
in.push_back(nl.shared_in());
|
||||
auto acc = in[0];
|
||||
for (unsigned i = 1; i < n; ++i)
|
||||
acc = nl.and_(acc, in[i]);
|
||||
nl.out(acc);
|
||||
return nl;
|
||||
}
|
||||
|
||||
yao::netlist xor2()
|
||||
{
|
||||
yao::netlist nl;
|
||||
auto a = nl.shared_in();
|
||||
auto b = nl.shared_in();
|
||||
nl.out(nl.xor_(a, b));
|
||||
return nl;
|
||||
}
|
||||
|
||||
/// @brief Garbled tables and the evaluator's labels on the p0–p1 channel.
|
||||
/// @details Party 1 runs `eval_party1` on the bytes that arrived. The choice
|
||||
/// labels are the OT output for party 1's bits; base OT is its own
|
||||
/// bench (`iknp`), not this garble row.
|
||||
void yao_ship(trio & net, role self, const yao::netlist & nl,
|
||||
const std::uint8_t * p0_bits, const std::uint8_t * p1_bits)
|
||||
{
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
auto & link = net.to(peer);
|
||||
std::size_t nchoice = 0;
|
||||
std::size_t ndirect = 0;
|
||||
for (std::uint32_t i = 0; i < nl.n_in(); ++i)
|
||||
{
|
||||
const auto kind = nl.kind_at(i);
|
||||
if (kind == yao::input::shared || kind == yao::input::priv1)
|
||||
++nchoice;
|
||||
if (kind == yao::input::shared || kind == yao::input::priv0)
|
||||
++ndirect;
|
||||
}
|
||||
const std::size_t ntable = static_cast<std::size_t>(nl.n_and()) * 2u;
|
||||
std::vector<std::uint8_t> share;
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto g = yao::detail::garble_party0(nl, p0_bits);
|
||||
require(g.tables.size() == ntable, "yao: table size");
|
||||
require(g.direct.size() == ndirect, "yao: direct labels");
|
||||
require(g.ot0.size() == nchoice && g.ot1.size() == nchoice, "yao: ot labels");
|
||||
std::vector<yao::block> chosen(nchoice);
|
||||
std::size_t oi = 0;
|
||||
for (std::uint32_t i = 0; i < nl.n_in(); ++i)
|
||||
{
|
||||
const auto kind = nl.kind_at(i);
|
||||
if (kind != yao::input::shared && kind != yao::input::priv1)
|
||||
continue;
|
||||
chosen[oi] = (p1_bits[i] & 1u) ? g.ot1[oi] : g.ot0[oi];
|
||||
++oi;
|
||||
}
|
||||
std::vector<yao::block> blob;
|
||||
blob.reserve(ntable + ndirect + nchoice);
|
||||
blob.insert(blob.end(), g.tables.begin(), g.tables.end());
|
||||
blob.insert(blob.end(), g.direct.begin(), g.direct.end());
|
||||
blob.insert(blob.end(), chosen.begin(), chosen.end());
|
||||
link.send_vec(blob, net::msg::bytes);
|
||||
share = std::move(g.share);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto blob = link.recv_vec<yao::block>(net::msg::bytes);
|
||||
require(blob.size() == ntable + ndirect + nchoice, "yao: payload size");
|
||||
std::vector<yao::block> tables(ntable);
|
||||
std::vector<yao::block> direct(ndirect);
|
||||
std::vector<yao::block> chosen(nchoice);
|
||||
if (ntable != 0)
|
||||
std::memcpy(tables.data(), blob.data(), ntable * sizeof(yao::block));
|
||||
if (ndirect != 0)
|
||||
std::memcpy(direct.data(), blob.data() + ntable,
|
||||
ndirect * sizeof(yao::block));
|
||||
if (nchoice != 0)
|
||||
std::memcpy(chosen.data(), blob.data() + ntable + ndirect,
|
||||
nchoice * sizeof(yao::block));
|
||||
share = yao::detail::eval_party1(nl, tables, direct, chosen);
|
||||
}
|
||||
auto other = net.exchange_vec_with(peer, share, net::msg::delta);
|
||||
require(other.size() == share.size(), "yao: share length");
|
||||
std::vector<std::uint8_t> semantic(nl.n_in());
|
||||
for (std::uint32_t i = 0; i < nl.n_in(); ++i)
|
||||
semantic[i] = static_cast<std::uint8_t>(p0_bits[i] ^ p1_bits[i]);
|
||||
auto plain = yao::eval_plain(nl, semantic.data());
|
||||
require(plain.size() == share.size(), "yao: output length");
|
||||
for (std::size_t i = 0; i < plain.size(); ++i)
|
||||
{
|
||||
const auto opened = static_cast<std::uint8_t>(share[i] ^ other[i]);
|
||||
require(opened == plain[i], "yao: open");
|
||||
}
|
||||
}
|
||||
|
||||
int run_if(role self, trio & net, unsigned heavy, unsigned light)
|
||||
{
|
||||
if (self == role::p2)
|
||||
return 0;
|
||||
auto then_nl = and_n(heavy);
|
||||
std::vector<std::uint8_t> h0(heavy, 1);
|
||||
std::vector<std::uint8_t> h1(heavy, 0);
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto else_nl = and_n(light);
|
||||
std::vector<std::uint8_t> l0(light, 1);
|
||||
std::vector<std::uint8_t> l1(light, 1);
|
||||
auto stacked = yao::eval_if(then_nl, else_nl, 0, 0, h0.data(), h1.data(),
|
||||
l0.data(), l1.data());
|
||||
std::vector<std::uint8_t> semantic(heavy, 1);
|
||||
auto plain = yao::eval_plain(then_nl, semantic.data());
|
||||
require(stacked.share0.size() == plain.size(), "yao stack: outputs");
|
||||
for (std::size_t i = 0; i < plain.size(); ++i)
|
||||
{
|
||||
const auto opened = static_cast<std::uint8_t>(
|
||||
stacked.share0[i] ^ stacked.share1[i]);
|
||||
require(opened == plain[i], "yao stack: if");
|
||||
}
|
||||
require(stacked.stack_blocks > 0, "yao stack: empty");
|
||||
}
|
||||
yao_ship(net, self, then_nl, h0.data(), h1.data());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run_hot(role self, trio & net, unsigned k)
|
||||
{
|
||||
if (self == role::p2)
|
||||
return 0;
|
||||
auto active = and_n(2);
|
||||
const std::uint8_t bits0[2] = {1, 0};
|
||||
const std::uint8_t bits1[2] = {0, 1};
|
||||
if (self == role::p0)
|
||||
{
|
||||
std::vector<yao::netlist> branches;
|
||||
std::vector<std::vector<std::uint8_t>> p0;
|
||||
std::vector<std::vector<std::uint8_t>> p1;
|
||||
branches.reserve(k);
|
||||
for (unsigned i = 0; i < k; ++i)
|
||||
{
|
||||
branches.push_back(i % 2 == 0 ? and_n(2) : xor2());
|
||||
p0.push_back({1, static_cast<std::uint8_t>(i & 1u)});
|
||||
p1.push_back({0, 1});
|
||||
}
|
||||
auto stacked = yao::eval_one_hot(branches, 0, 0, p0, p1);
|
||||
const std::uint8_t semantic[2] = {1, 1};
|
||||
auto plain = yao::eval_plain(active, semantic);
|
||||
require(stacked.share0.size() == plain.size(), "yao stack: one-hot outputs");
|
||||
for (std::size_t i = 0; i < plain.size(); ++i)
|
||||
{
|
||||
const auto opened = static_cast<std::uint8_t>(
|
||||
stacked.share0[i] ^ stacked.share1[i]);
|
||||
require(opened == plain[i], "yao stack: one-hot");
|
||||
}
|
||||
require(stacked.stack_blocks > 0, "yao stack: one-hot empty");
|
||||
}
|
||||
yao_ship(net, self, active, bits0, bits1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> flute_columns(unsigned delta, unsigned n_out)
|
||||
{
|
||||
const unsigned rows = 1u << delta;
|
||||
std::vector<std::uint8_t> columns(static_cast<std::size_t>(n_out) * rows);
|
||||
for (unsigned w = 0; w < n_out; ++w)
|
||||
for (unsigned j = 0; j < rows; ++j)
|
||||
columns[static_cast<std::size_t>(w) * rows + j] =
|
||||
static_cast<std::uint8_t>(((j >> (w % delta)) ^ w) & 1u);
|
||||
return columns;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> flute_bits(unsigned delta)
|
||||
{
|
||||
std::vector<std::uint8_t> bits(delta, 0);
|
||||
bits[0] = 1;
|
||||
if (delta > 2)
|
||||
bits[2] = 1;
|
||||
return bits;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> flute_pack(const flute::detail::setup & s, unsigned party)
|
||||
{
|
||||
std::vector<std::uint8_t> out;
|
||||
out.insert(out.end(), s.m.begin(), s.m.end());
|
||||
out.insert(out.end(), s.share[party].begin(), s.share[party].end());
|
||||
out.insert(out.end(), s.lamz[party].begin(), s.lamz[party].end());
|
||||
return out;
|
||||
}
|
||||
|
||||
int run_flute(role self, trio & net, unsigned delta, unsigned n_out)
|
||||
{
|
||||
const auto columns = flute_columns(delta, n_out);
|
||||
const auto x = flute_bits(delta);
|
||||
const unsigned rows = 1u << delta;
|
||||
const std::uint32_t full = rows - 1u;
|
||||
if (self == role::p2)
|
||||
{
|
||||
auto setup = flute::detail::make_setup(delta, n_out, 2, x.data());
|
||||
auto to0 = flute_pack(setup, 0);
|
||||
auto to1 = flute_pack(setup, 1);
|
||||
net.send_bytes_to(role::p0, net::msg::bytes, to0.data(), to0.size());
|
||||
net.send_bytes_to(role::p1, net::msg::bytes, to1.data(), to1.size());
|
||||
return 0;
|
||||
}
|
||||
const unsigned party = self == role::p0 ? 0u : 1u;
|
||||
auto bytes = net.recv_bytes_from(role::p2, net::msg::bytes);
|
||||
const std::size_t need = static_cast<std::size_t>(delta) + rows + n_out;
|
||||
require(bytes.size() == need, "flute: deal size");
|
||||
flute::detail::setup setup;
|
||||
setup.m.assign(bytes.begin(), bytes.begin() + delta);
|
||||
setup.share.assign(2, {});
|
||||
setup.share[party].assign(bytes.begin() + delta, bytes.begin() + delta + rows);
|
||||
setup.lamz.assign(2, {});
|
||||
setup.lamz[party].assign(bytes.end() - static_cast<std::ptrdiff_t>(n_out),
|
||||
bytes.end());
|
||||
std::vector<std::uint8_t> mine(static_cast<std::size_t>(n_out) * 2u);
|
||||
for (unsigned w = 0; w < n_out; ++w)
|
||||
{
|
||||
const std::uint8_t * column =
|
||||
columns.data() + static_cast<std::size_t>(w) * rows;
|
||||
mine[w] = flute::detail::party_v(setup, party, delta, full, column,
|
||||
setup.lamz[party][w]);
|
||||
mine[n_out + w] = setup.lamz[party][w];
|
||||
}
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
auto theirs = net.exchange_vec_with(peer, mine, net::msg::bytes);
|
||||
auto expect = flute::eval_plain(delta, n_out, columns.data(), x.data());
|
||||
for (unsigned w = 0; w < n_out; ++w)
|
||||
{
|
||||
const std::uint8_t * column =
|
||||
columns.data() + static_cast<std::size_t>(w) * rows;
|
||||
const std::uint8_t t = flute::detail::dot_column(
|
||||
full, delta, setup.m.data(), column);
|
||||
const auto opened = static_cast<std::uint8_t>(
|
||||
mine[w] ^ theirs[w] ^ t ^ mine[n_out + w] ^ theirs[n_out + w]);
|
||||
require(opened == expect[w], "flute: open");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
rss::seed_bundle shuffle_bundle()
|
||||
{
|
||||
rss::seed_bundle bundle{};
|
||||
auto fill = [](rss::seed_block & seed, std::uint8_t tag) {
|
||||
auto * p = reinterpret_cast<std::uint8_t *>(&seed);
|
||||
for (std::size_t i = 0; i < sizeof(seed); ++i)
|
||||
p[i] = static_cast<std::uint8_t>(tag + i * 17u);
|
||||
};
|
||||
fill(bundle.k01, 1);
|
||||
fill(bundle.k12, 2);
|
||||
fill(bundle.k20, 3);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
shuffle::shuffle_party_view<std::uint64_t> shuffle_view(unsigned me, std::size_t n)
|
||||
{
|
||||
std::uint64_t rng = 0xA5A5A5A5A5A5A5A5ull;
|
||||
auto draw = [&] {
|
||||
rng = rng * 6364136223846793005ull + 1u;
|
||||
return rng;
|
||||
};
|
||||
shuffle::shuffle_party_view<std::uint64_t> view;
|
||||
view.own.resize(n);
|
||||
view.next.resize(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const auto a = draw();
|
||||
const auto b = draw();
|
||||
const auto c = static_cast<std::uint64_t>(i) - a - b;
|
||||
if (me == 0)
|
||||
{
|
||||
view.own[i] = a;
|
||||
view.next[i] = b;
|
||||
}
|
||||
else if (me == 1)
|
||||
{
|
||||
view.own[i] = b;
|
||||
view.next[i] = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
view.own[i] = c;
|
||||
view.next[i] = a;
|
||||
}
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
int run_shuffle(role self, trio & net, std::size_t n)
|
||||
{
|
||||
const unsigned me = static_cast<unsigned>(self);
|
||||
const auto bundle = shuffle_bundle();
|
||||
const std::uint64_t index = 1;
|
||||
auto seeds = rss::party_seeds::from_bundle(bundle, me);
|
||||
auto view = shuffle_view(me, n);
|
||||
const unsigned order[3] = {2u, 0u, 1u};
|
||||
for (unsigned left : order)
|
||||
{
|
||||
const unsigned u = shuffle::hidden_u_party(left);
|
||||
const unsigned side = shuffle::hidden_side_party(left);
|
||||
if (me == u)
|
||||
{
|
||||
auto step = shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
me, seeds, view, index, left, nullptr);
|
||||
require(step.out.sends, "shuffle: u sends");
|
||||
require(step.out.to == side, "shuffle: u recipient");
|
||||
net.send_vec_to(static_cast<role>(step.out.to), net::msg::ring_vector,
|
||||
step.out.data);
|
||||
view = std::move(step.view);
|
||||
}
|
||||
else if (me == side)
|
||||
{
|
||||
auto inbound = net.recv_vec_from<std::uint64_t>(
|
||||
static_cast<role>(u), net::msg::ring_vector);
|
||||
auto step = shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
me, seeds, view, index, left, &inbound);
|
||||
require(step.out.sends, "shuffle: side sends");
|
||||
require(step.out.to == left, "shuffle: side recipient");
|
||||
net.send_vec_to(static_cast<role>(step.out.to), net::msg::ring_vector,
|
||||
step.out.data);
|
||||
view = std::move(step.view);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto inbound = net.recv_vec_from<std::uint64_t>(
|
||||
static_cast<role>(side), net::msg::ring_vector);
|
||||
auto step = shuffle::shuffle_hidden_pass<std::uint64_t>(
|
||||
me, seeds, view, index, left, &inbound);
|
||||
require(!step.out.sends, "shuffle: left-out is silent");
|
||||
view = std::move(step.view);
|
||||
}
|
||||
}
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto from1 = net.recv_vec_from<std::uint64_t>(role::p1, net::msg::ring_vector);
|
||||
auto from2 = net.recv_vec_from<std::uint64_t>(role::p2, net::msg::ring_vector);
|
||||
require(from1.size() == n && from2.size() == n, "shuffle: open size");
|
||||
std::vector<std::uint64_t> clear(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
clear[i] = i;
|
||||
auto expect = shuffle::permute(clear,
|
||||
shuffle::permutation_from_seed(bundle.k01, n, index));
|
||||
expect = shuffle::permute(expect,
|
||||
shuffle::permutation_from_seed(bundle.k12, n, index));
|
||||
expect = shuffle::permute(expect,
|
||||
shuffle::permutation_from_seed(bundle.k20, n, index));
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const auto got = view.own[i] + from1[i] + from2[i];
|
||||
require(got == expect[i], "shuffle: open");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
net.send_vec_to(role::p0, net::msg::ring_vector, view.own);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int arith_proj_m5(role self, trio & net) { return run_proj(self, net, 5); }
|
||||
int arith_proj_m17(role self, trio & net) { return run_proj(self, net, 17); }
|
||||
int arith_proj_m64(role self, trio & net) { return run_proj(self, net, 64); }
|
||||
int arith_mul_p5(role self, trio & net) { return run_mul(self, net, 5); }
|
||||
int arith_mul_p7(role self, trio & net) { return run_mul(self, net, 7); }
|
||||
int arith_mul_p11(role self, trio & net) { return run_mul(self, net, 11); }
|
||||
int arith_thresh_b8(role self, trio & net) { return run_thresh(self, net, 8); }
|
||||
int arith_thresh_b16(role self, trio & net) { return run_thresh(self, net, 16); }
|
||||
int arith_chain_mul4(role self, trio & net) { return run_chain(self, net); }
|
||||
int yao_if_4_2(role self, trio & net) { return run_if(self, net, 4, 2); }
|
||||
int yao_if_16_8(role self, trio & net) { return run_if(self, net, 16, 8); }
|
||||
int yao_onehot_k4(role self, trio & net) { return run_hot(self, net, 4); }
|
||||
int yao_onehot_k8(role self, trio & net) { return run_hot(self, net, 8); }
|
||||
int flute_d2(role self, trio & net) { return run_flute(self, net, 2, 1); }
|
||||
int flute_d4(role self, trio & net) { return run_flute(self, net, 4, 1); }
|
||||
int flute_d8(role self, trio & net) { return run_flute(self, net, 8, 1); }
|
||||
int flute_d4_o8(role self, trio & net) { return run_flute(self, net, 4, 8); }
|
||||
int shuffle_n16(role self, trio & net) { return run_shuffle(self, net, 16); }
|
||||
int shuffle_n64(role self, trio & net) { return run_shuffle(self, net, 64); }
|
||||
int shuffle_n256(role self, trio & net) { return run_shuffle(self, net, 256); }
|
||||
|
||||
#define REG(name, tags, fn) \
|
||||
register_flow(flow{#name, tags, fn, true})
|
||||
|
||||
} // namespace gadget
|
||||
|
||||
void register_gadget_flows()
|
||||
{
|
||||
using namespace gadget;
|
||||
REG(arith_proj_m5, "bench arith garble", arith_proj_m5);
|
||||
REG(arith_proj_m17, "bench arith garble", arith_proj_m17);
|
||||
REG(arith_proj_m64, "bench arith garble", arith_proj_m64);
|
||||
REG(arith_mul_p5, "bench arith garble", arith_mul_p5);
|
||||
REG(arith_mul_p7, "bench arith garble", arith_mul_p7);
|
||||
REG(arith_mul_p11, "bench arith garble", arith_mul_p11);
|
||||
REG(arith_thresh_b8, "bench arith garble", arith_thresh_b8);
|
||||
REG(arith_thresh_b16, "bench arith garble", arith_thresh_b16);
|
||||
REG(arith_chain_mul4, "bench arith garble", arith_chain_mul4);
|
||||
REG(yao_if_4_2, "bench yao stack", yao_if_4_2);
|
||||
REG(yao_if_16_8, "bench yao stack", yao_if_16_8);
|
||||
REG(yao_onehot_k4, "bench yao stack", yao_onehot_k4);
|
||||
REG(yao_onehot_k8, "bench yao stack", yao_onehot_k8);
|
||||
REG(flute_d2, "bench flute", flute_d2);
|
||||
REG(flute_d4, "bench flute", flute_d4);
|
||||
REG(flute_d8, "bench flute", flute_d8);
|
||||
REG(flute_d4_o8, "bench flute", flute_d4_o8);
|
||||
REG(shuffle_n16, "bench shuffle rss", shuffle_n16);
|
||||
REG(shuffle_n64, "bench shuffle rss", shuffle_n64);
|
||||
REG(shuffle_n256, "bench shuffle rss", shuffle_n256);
|
||||
}
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
1654
party/flows_recent.cpp
Normal file
1654
party/flows_recent.cpp
Normal file
File diff suppressed because it is too large
Load diff
847
party/iknp_deal.hpp
Normal file
847
party/iknp_deal.hpp
Normal file
|
|
@ -0,0 +1,847 @@
|
|||
/// @file party/iknp_deal.hpp
|
||||
/// @brief Two-party IKNP setup for every tape `dist_ds.hpp` takes from p2.
|
||||
/// @details p0 and p1 sample the same pad correlations the dealer would, write
|
||||
/// them into a local inbox, and then run the existing walk. p2 is not
|
||||
/// connected. Covered tapes: additive carry, point (reveal and
|
||||
/// oblivious hash, wildcard, half-tree, extractable), comparison
|
||||
/// (edge, value-word, blocked suffix, verifiable hash), and interval
|
||||
/// containment.
|
||||
|
||||
#ifndef LIBDPF_PARTY_IKNP_DEAL_HPP__
|
||||
#define LIBDPF_PARTY_IKNP_DEAL_HPP__
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "hedley/hedley.h"
|
||||
|
||||
#include "dist_ds.hpp"
|
||||
#include "dpf/iknp.hpp"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
namespace iknp_deal
|
||||
{
|
||||
|
||||
using net::role;
|
||||
using net::trio;
|
||||
|
||||
struct tape
|
||||
{
|
||||
std::vector<std::uint8_t> bytes;
|
||||
|
||||
template <typename T>
|
||||
void put(net::msg tag, const T & value)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
append(tag, &value, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void put_vec(net::msg tag, const T * data, std::size_t n)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
append(tag, data, n * sizeof(T));
|
||||
}
|
||||
|
||||
void append(net::msg tag, const void * payload, std::size_t n)
|
||||
{
|
||||
if (n > 0xffffffffu)
|
||||
throw std::invalid_argument("iknp tape frame");
|
||||
const auto len = static_cast<std::uint32_t>(n);
|
||||
const auto t = net::to_u16(tag);
|
||||
std::uint8_t hdr[6];
|
||||
std::memcpy(hdr, &len, 4);
|
||||
std::memcpy(hdr + 4, &t, 2);
|
||||
bytes.insert(bytes.end(), hdr, hdr + 6);
|
||||
if (n != 0)
|
||||
{
|
||||
const auto * p = static_cast<const std::uint8_t *>(payload);
|
||||
bytes.insert(bytes.end(), p, p + n);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct cursor
|
||||
{
|
||||
const iknp::material * mat = nullptr;
|
||||
std::size_t block = 0;
|
||||
std::size_t bit = 0;
|
||||
std::size_t b2a = 0;
|
||||
std::size_t cw = 0;
|
||||
std::size_t zero = 0;
|
||||
std::vector<dist::ring_zero_share_msg> zeros;
|
||||
|
||||
dist::and_share_msg take_block()
|
||||
{
|
||||
const auto & s = mat->blocks.at(block++);
|
||||
dist::and_share_msg m;
|
||||
m.a = s.a;
|
||||
m.b = s.b;
|
||||
m.c = s.c;
|
||||
return m;
|
||||
}
|
||||
|
||||
dist::bit_and_pad_msg take_bit()
|
||||
{
|
||||
const auto & s = mat->bits.at(bit++);
|
||||
return dist::bit_and_pad_msg{s.a, s.b, s.c};
|
||||
}
|
||||
|
||||
dist::b2a_pad_msg take_b2a()
|
||||
{
|
||||
const auto & s = mat->b2a.at(b2a++);
|
||||
return dist::b2a_pad_msg{s.r, s.add};
|
||||
}
|
||||
|
||||
dist::cw_pad_msg take_cw()
|
||||
{
|
||||
const auto & s = mat->cws.at(cw++);
|
||||
dist::cw_pad_msg m;
|
||||
m.rand = s.rand;
|
||||
m.gamma = s.gamma;
|
||||
m.bit = s.bit;
|
||||
return m;
|
||||
}
|
||||
|
||||
dist::ring_zero_share_msg take_zero()
|
||||
{
|
||||
return zeros.at(zero++);
|
||||
}
|
||||
|
||||
void done() const
|
||||
{
|
||||
if (mat == nullptr)
|
||||
return;
|
||||
if (block != mat->blocks.size() || bit != mat->bits.size()
|
||||
|| b2a != mat->b2a.size() || cw != mat->cws.size()
|
||||
|| zero != zeros.size())
|
||||
throw std::runtime_error("iknp tape did not consume every pad");
|
||||
}
|
||||
};
|
||||
|
||||
inline int me_of(role self)
|
||||
{
|
||||
if (self == role::p0)
|
||||
return 0;
|
||||
if (self == role::p1)
|
||||
return 1;
|
||||
throw std::invalid_argument("iknp parties are p0 and p1");
|
||||
}
|
||||
|
||||
inline void exchange_zeros(trio & net, int me,
|
||||
const std::vector<std::uint64_t> & masks, cursor & cur)
|
||||
{
|
||||
cur.zeros.resize(masks.size());
|
||||
const role peer = me == 0 ? role::p1 : role::p0;
|
||||
for (std::size_t i = 0; i < masks.size(); ++i)
|
||||
{
|
||||
if (me == 0)
|
||||
{
|
||||
const auto r = dist::random_ring_word(masks[i]);
|
||||
cur.zeros[i].share = r;
|
||||
net.send_to(peer, net::msg::ring_vector, detail::dcf_impl::neg_m(r, masks[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
cur.zeros[i].share = net.recv_from<std::uint64_t>(peer, net::msg::ring_vector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void barrier(trio & net, role self)
|
||||
{
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
const std::uint8_t ready = 1;
|
||||
(void)net.exchange_with(peer, ready, net::msg::delta);
|
||||
}
|
||||
|
||||
inline simde__m128i sample_root(int me)
|
||||
{
|
||||
const auto r = dpf::uniform_sample<simde__m128i>();
|
||||
return me == 0 ? dpf::unset_lo_bit(r) : dpf::set_lo_bit(r);
|
||||
}
|
||||
|
||||
inline dist::level_pad_msg take_level(cursor & cur, int me)
|
||||
{
|
||||
const auto cw = cur.take_cw();
|
||||
const auto first = cur.take_block();
|
||||
const auto second = cur.take_block();
|
||||
dist::level_pad_msg m;
|
||||
m.cw = cw;
|
||||
if (me == 0)
|
||||
{
|
||||
m.mine = first;
|
||||
m.theirs = second;
|
||||
}
|
||||
else
|
||||
{
|
||||
m.mine = second;
|
||||
m.theirs = first;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
struct demand
|
||||
{
|
||||
std::size_t blocks = 0;
|
||||
std::size_t bits = 0;
|
||||
std::size_t b2a = 0;
|
||||
std::size_t cws = 0;
|
||||
std::vector<std::uint64_t> zero_masks;
|
||||
};
|
||||
|
||||
inline void add_point(demand & d, std::size_t depth, std::size_t n_leaf,
|
||||
bool oblivious_hash, bool leaf_b2a)
|
||||
{
|
||||
d.cws += depth;
|
||||
d.blocks += depth * 2 + n_leaf;
|
||||
if (oblivious_hash)
|
||||
d.bits += depth * dist::hash_level_and_count();
|
||||
if (leaf_b2a)
|
||||
d.b2a += n_leaf * 128;
|
||||
}
|
||||
|
||||
inline void add_comparison(demand & d, std::size_t depth, std::size_t prefix,
|
||||
std::size_t cmp_block, std::size_t cmp_q, std::size_t cmp_h,
|
||||
bool verifiable, bool oblivious, bool edge, std::uint64_t mask)
|
||||
{
|
||||
d.cws += depth;
|
||||
d.blocks += depth * 2;
|
||||
if (oblivious && edge && prefix > 1)
|
||||
d.bits += prefix - 1;
|
||||
if (oblivious)
|
||||
{
|
||||
if (cmp_block == 0)
|
||||
{
|
||||
const std::size_t nlev = prefix < depth ? prefix : depth;
|
||||
d.bits += nlev * 128;
|
||||
d.b2a += nlev * 129;
|
||||
}
|
||||
if (cmp_block > 0 && cmp_q > 0 && cmp_h > 0 && cmp_h <= depth)
|
||||
{
|
||||
d.bits += 1;
|
||||
d.b2a += 3;
|
||||
}
|
||||
if (verifiable)
|
||||
d.bits += depth * dist::hash_level_and_count();
|
||||
}
|
||||
d.zero_masks.push_back(mask);
|
||||
}
|
||||
|
||||
template <std::size_t NLeaf>
|
||||
void emit_point(tape & out, cursor & cur, int me, std::size_t depth,
|
||||
bool half, bool oblivious_hash, bool leaf_b2a)
|
||||
{
|
||||
static_assert(NLeaf >= 1, "leaf mux sends at least one pad");
|
||||
if (half)
|
||||
out.put(net::msg::dpf_key, sample_root(me));
|
||||
for (std::size_t level = 0; level < depth; ++level)
|
||||
{
|
||||
out.put(net::msg::beaver_tape, take_level(cur, me));
|
||||
if (oblivious_hash)
|
||||
{
|
||||
const std::size_t n = dist::hash_level_and_count();
|
||||
std::vector<dist::bit_and_pad_msg> hash(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
hash[i] = cur.take_bit();
|
||||
out.put_vec(net::msg::beaver_tape, hash.data(), hash.size());
|
||||
}
|
||||
}
|
||||
dist::leaf_pad_msg<NLeaf> lanes{};
|
||||
for (std::size_t i = 0; i < NLeaf; ++i)
|
||||
lanes.lanes[i] = cur.take_block();
|
||||
out.put(net::msg::beaver_tape, lanes);
|
||||
if (leaf_b2a)
|
||||
{
|
||||
std::vector<dist::b2a_pad_msg> pads(NLeaf * 128);
|
||||
for (auto & p : pads)
|
||||
p = cur.take_b2a();
|
||||
out.put_vec(net::msg::beaver_tape, pads.data(), pads.size());
|
||||
}
|
||||
}
|
||||
|
||||
inline void emit_comparison(tape & out, cursor & cur, int me, std::size_t depth,
|
||||
std::size_t prefix, std::size_t cmp_block, std::size_t cmp_q,
|
||||
std::size_t cmp_h, bool verifiable, bool oblivious, bool edge, bool half)
|
||||
{
|
||||
if (half)
|
||||
out.put(net::msg::dpf_key, sample_root(me));
|
||||
if (oblivious && edge && prefix > 1)
|
||||
{
|
||||
std::vector<dist::bit_and_pad_msg> edge_pads(prefix - 1);
|
||||
for (auto & p : edge_pads)
|
||||
p = cur.take_bit();
|
||||
out.put_vec(net::msg::beaver_tape, edge_pads.data(), edge_pads.size());
|
||||
}
|
||||
for (std::size_t level = 0; level < depth; ++level)
|
||||
{
|
||||
out.put(net::msg::beaver_tape, take_level(cur, me));
|
||||
if (oblivious)
|
||||
{
|
||||
if (cmp_block == 0 && level < prefix)
|
||||
{
|
||||
dist::cmp_level_obliv_msg msg{};
|
||||
for (std::size_t i = 0; i < 128; ++i)
|
||||
{
|
||||
const auto bit = cur.take_bit();
|
||||
const auto b2 = cur.take_b2a();
|
||||
msg.bits[i] = dist::word_bit_for(bit, b2);
|
||||
}
|
||||
msg.ai = cur.take_b2a();
|
||||
out.put(net::msg::beaver_tape, msg);
|
||||
}
|
||||
if (cmp_block > 0 && cmp_q > 0 && level + 1 == cmp_h)
|
||||
{
|
||||
dist::suffix_obliv_msg msg{};
|
||||
msg.and_pad = cur.take_bit();
|
||||
for (int i = 0; i < 3; ++i)
|
||||
msg.b2a[i] = cur.take_b2a();
|
||||
out.put(net::msg::beaver_tape, msg);
|
||||
}
|
||||
if (verifiable)
|
||||
{
|
||||
const std::size_t n = dist::hash_level_and_count();
|
||||
std::vector<dist::bit_and_pad_msg> hash(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
hash[i] = cur.take_bit();
|
||||
out.put_vec(net::msg::beaver_tape, hash.data(), hash.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
out.put(net::msg::beaver_tape, cur.take_zero());
|
||||
}
|
||||
|
||||
template <typename Concrete, typename Leaf>
|
||||
void exchange_wildcard(trio & net, int me, dist::wildcard_leaf_pad_msg<Concrete, Leaf> & pad)
|
||||
{
|
||||
const role peer = me == 0 ? role::p1 : role::p0;
|
||||
Concrete mine_out = dpf::uniform_sample<Concrete>();
|
||||
Leaf mine_vec = dist::random_leaf<Leaf>();
|
||||
Leaf peer_vec = net.exchange_with(peer, mine_vec, net::msg::ring_vector);
|
||||
Leaf cross_for_peer = dpf::multiply_leaf(peer_vec, mine_out);
|
||||
Leaf cross = net.exchange_with(peer, cross_for_peer, net::msg::ring_vector);
|
||||
Leaf zero{};
|
||||
if (me == 0)
|
||||
{
|
||||
zero = dist::random_leaf<Leaf>();
|
||||
const Leaf neg = dpf::subtract_leaf<Concrete>(Leaf{}, zero);
|
||||
net.send_to(peer, net::msg::ring_vector, neg);
|
||||
}
|
||||
else
|
||||
{
|
||||
zero = net.recv_from<Leaf>(peer, net::msg::ring_vector);
|
||||
}
|
||||
pad.output_blind = mine_out;
|
||||
pad.vector_blind = mine_vec;
|
||||
pad.peer_vector_blind = peer_vec;
|
||||
pad.cross = cross;
|
||||
pad.zero_leaf = zero;
|
||||
}
|
||||
|
||||
inline void install(trio & net, role self, tape & out, cursor & cur)
|
||||
{
|
||||
cur.done();
|
||||
barrier(net, self);
|
||||
net.install_inbox(std::move(out.bytes));
|
||||
}
|
||||
|
||||
template <typename InteriorPRG, typename InputT, typename OutputT,
|
||||
bool ObliviousHash, bool Additive>
|
||||
void prepare_point(trio & net, role self)
|
||||
{
|
||||
const int me = me_of(self);
|
||||
using dpf_type = utils::dpf_type_t<InteriorPRG, InteriorPRG, InputT, OutputT,
|
||||
verifiable>;
|
||||
using tree = dpf::tree_traits<InteriorPRG>;
|
||||
constexpr std::size_t depth = dpf_type::depth;
|
||||
constexpr std::size_t lg = dpf_type::lg_outputs_per_leaf;
|
||||
constexpr std::size_t n_leaf = dist::leaf_and_slots(lg);
|
||||
constexpr bool leaf_b2a = lg > 0
|
||||
&& !utils::has_characteristic_two_v<dpf::concrete_type_t<OutputT>>
|
||||
&& (!dpf::is_wildcard_v<OutputT> || ObliviousHash);
|
||||
demand d;
|
||||
if constexpr (Additive)
|
||||
d.bits += utils::bitlength_of_v<InputT> - 1;
|
||||
add_point(d, depth, n_leaf, ObliviousHash, leaf_b2a);
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
auto mat = iknp::sample(net.to(peer), me, d.blocks, d.bits, d.b2a, d.cws);
|
||||
cursor cur;
|
||||
cur.mat = &mat;
|
||||
tape out;
|
||||
if constexpr (Additive)
|
||||
{
|
||||
constexpr std::size_t nbits = utils::bitlength_of_v<InputT>;
|
||||
for (std::size_t i = 0; i + 1 < nbits; ++i)
|
||||
out.put(net::msg::beaver_tape, cur.take_bit());
|
||||
}
|
||||
emit_point<n_leaf>(out, cur, me, depth, tree::is_half_tree, ObliviousHash,
|
||||
leaf_b2a);
|
||||
if constexpr (dpf::is_wildcard_v<OutputT>)
|
||||
{
|
||||
using concrete = dpf::concrete_type_t<OutputT>;
|
||||
using leaf_type = dpf::leaf_node_t<typename dpf_type::exterior_node, concrete>;
|
||||
dist::wildcard_leaf_pad_msg<concrete, leaf_type> pad{};
|
||||
exchange_wildcard<concrete, leaf_type>(net, me, pad);
|
||||
out.put(net::msg::beaver_tape, pad);
|
||||
}
|
||||
install(net, self, out, cur);
|
||||
}
|
||||
|
||||
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
|
||||
bool Oblivious, typename Spec, typename ...Tags>
|
||||
void prepare_comparison(trio & net, role self, const Spec & spec,
|
||||
const Tags & ...tags)
|
||||
{
|
||||
const int me = me_of(self);
|
||||
using pair_type = dist::comparison_pair_t<InteriorPRG, ExteriorPRG, InputT,
|
||||
Spec, Tags...>;
|
||||
using key_type = typename pair_type::first_type::key_type;
|
||||
using tree = dpf::tree_traits<InteriorPRG>;
|
||||
(void)std::initializer_list<int>{((void)tags, 0)...};
|
||||
dcf_runtime_spec runtime{};
|
||||
bool found = false;
|
||||
detail::incr::collect_cmp_one(runtime, found, spec);
|
||||
if (!found)
|
||||
throw std::invalid_argument("dist comparison keygen needs one comparison");
|
||||
if (runtime.prefix == 0)
|
||||
runtime.prefix = utils::bitlength_of_v<InputT>;
|
||||
if constexpr (Oblivious)
|
||||
{
|
||||
if (is_paint_kind(runtime.kind))
|
||||
throw std::invalid_argument(
|
||||
"oblivious comparison does not support paint kinds");
|
||||
}
|
||||
const bool edge = runtime.kind == cmp_kind::leq || runtime.kind == cmp_kind::gt;
|
||||
demand d;
|
||||
add_comparison(d, key_type::depth, runtime.prefix, key_type::cmp_block,
|
||||
key_type::cmp_q, key_type::cmp_h, key_type::is_verifiable, Oblivious,
|
||||
edge, runtime.mask);
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
auto mat = iknp::sample(net.to(peer), me, d.blocks, d.bits, d.b2a, d.cws);
|
||||
cursor cur;
|
||||
cur.mat = &mat;
|
||||
exchange_zeros(net, me, d.zero_masks, cur);
|
||||
tape out;
|
||||
emit_comparison(out, cur, me, key_type::depth, runtime.prefix,
|
||||
key_type::cmp_block, key_type::cmp_q, key_type::cmp_h,
|
||||
key_type::is_verifiable, Oblivious, edge, tree::is_half_tree);
|
||||
install(net, self, out, cur);
|
||||
}
|
||||
|
||||
template <typename InteriorPRG, typename ExteriorPRG, typename InputT,
|
||||
bool Reveal, typename Beta>
|
||||
void prepare_ic(trio & net, role self, const ic_pack<Beta> & spec)
|
||||
{
|
||||
const int me = me_of(self);
|
||||
using input_type = std::decay_t<InputT>;
|
||||
const auto inner = detail::ic_impl::inner_lt(spec);
|
||||
const std::uint64_t gmask = detail::ic_impl::group_mask_of<Beta>();
|
||||
constexpr std::size_t nbits = utils::bitlength_of_v<input_type>;
|
||||
using pair_type = dist::comparison_pair_t<InteriorPRG, ExteriorPRG,
|
||||
input_type, decltype(inner)>;
|
||||
using key_type = typename pair_type::first_type::key_type;
|
||||
using tree = dpf::tree_traits<InteriorPRG>;
|
||||
dcf_runtime_spec runtime{};
|
||||
bool found = false;
|
||||
detail::incr::collect_cmp_one(runtime, found, inner);
|
||||
if (!found)
|
||||
throw std::invalid_argument("dist comparison keygen needs one comparison");
|
||||
if (runtime.prefix == 0)
|
||||
runtime.prefix = nbits;
|
||||
constexpr bool oblivious = !Reveal;
|
||||
if constexpr (oblivious)
|
||||
{
|
||||
if (is_paint_kind(runtime.kind))
|
||||
throw std::invalid_argument(
|
||||
"oblivious comparison does not support paint kinds");
|
||||
}
|
||||
demand d;
|
||||
if constexpr (oblivious)
|
||||
{
|
||||
d.bits += dist::ic_gamma_and_count(nbits);
|
||||
d.bits += dist::ic_correction_and_count(nbits);
|
||||
d.b2a += 4;
|
||||
}
|
||||
const bool edge = runtime.kind == cmp_kind::leq || runtime.kind == cmp_kind::gt;
|
||||
add_comparison(d, key_type::depth, runtime.prefix, key_type::cmp_block,
|
||||
key_type::cmp_q, key_type::cmp_h, key_type::is_verifiable, oblivious,
|
||||
edge, runtime.mask);
|
||||
if constexpr (Reveal)
|
||||
{
|
||||
d.zero_masks.push_back(gmask);
|
||||
d.zero_masks.push_back(gmask);
|
||||
}
|
||||
else
|
||||
{
|
||||
d.zero_masks.push_back(gmask);
|
||||
}
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
auto mat = iknp::sample(net.to(peer), me, d.blocks, d.bits, d.b2a, d.cws);
|
||||
cursor cur;
|
||||
cur.mat = &mat;
|
||||
exchange_zeros(net, me, d.zero_masks, cur);
|
||||
tape out;
|
||||
if constexpr (oblivious)
|
||||
{
|
||||
const std::size_t gamma_n = dist::ic_gamma_and_count(nbits);
|
||||
const std::size_t corr_n = dist::ic_correction_and_count(nbits);
|
||||
std::vector<dist::bit_and_pad_msg> gamma(gamma_n), corr(corr_n);
|
||||
for (auto & p : gamma)
|
||||
p = cur.take_bit();
|
||||
for (auto & p : corr)
|
||||
p = cur.take_bit();
|
||||
std::vector<dist::b2a_pad_msg> b2(4);
|
||||
for (auto & p : b2)
|
||||
p = cur.take_b2a();
|
||||
out.put_vec(net::msg::beaver_tape, gamma.data(), gamma.size());
|
||||
out.put_vec(net::msg::beaver_tape, corr.data(), corr.size());
|
||||
out.put_vec(net::msg::beaver_tape, b2.data(), b2.size());
|
||||
}
|
||||
emit_comparison(out, cur, me, key_type::depth, runtime.prefix,
|
||||
key_type::cmp_block, key_type::cmp_q, key_type::cmp_h,
|
||||
key_type::is_verifiable, oblivious, edge, tree::is_half_tree);
|
||||
out.put(net::msg::beaver_tape, cur.take_zero());
|
||||
if constexpr (Reveal)
|
||||
out.put(net::msg::beaver_tape, cur.take_zero());
|
||||
install(net, self, out, cur);
|
||||
}
|
||||
|
||||
} // namespace iknp_deal
|
||||
|
||||
/// @brief Two-party point keygen. Same walk as `dist_with_point_key`; pads
|
||||
/// come from IKNP instead of p2.
|
||||
/// @details Ideal (semi-honest): p0/p1 input XOR or additive shares of α and
|
||||
/// payload β; each outputs its verifiable DPF key. With
|
||||
/// `RevealPoint`, the tree prefix (and packed wildcard lane when
|
||||
/// applicable) may be opened. Without it, α, peer seeds, peer
|
||||
/// payload shares, and path-opening pad bits stay hidden.
|
||||
/// \complexity Same tree walk as `dist_with_point_key`, plus one IKNP
|
||||
/// `sample`. With tape length `T = Θ(n)` on a reveal point
|
||||
/// (or `T = Θ(n · hash_level_and_count())` when oblivious),
|
||||
/// `sample` does two Chou–Orlandi base sessions of `κ = 128`
|
||||
/// OTs on P-256 and two OT-extension directions of cost
|
||||
/// `Θ(κ T)` bits, then the usual `n` Doerner–Shelat opens.
|
||||
/// Dealer `make_dpf` is still 0 rounds / 0 bytes; Half-Tree
|
||||
/// §5.2 (ePrint 2022/1431) is a different `n+3`-round COT/OLE
|
||||
/// hybrid this path does not implement.
|
||||
/// \rounds Base OT + extension (constant rounds) then the usual per-level
|
||||
/// DS exchanges; no p2.
|
||||
/// \communication IKNP seed OT and extension rows (`Θ(κ T)` bits), then
|
||||
/// the same peer messages as the dealer walk (no dealer
|
||||
/// frames).
|
||||
/// \preprocessing IKNP replaces the dealer tape; there is no offline p2.
|
||||
template <typename InteriorPRG = dpf::prg::aes128,
|
||||
typename ExteriorPRG = InteriorPRG,
|
||||
bool RevealPoint = false,
|
||||
typename InputT,
|
||||
typename OutputT,
|
||||
typename Fn0,
|
||||
typename Fn1>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
auto dist_with_point_key_iknp(
|
||||
trio & net, role self, InputT x0, InputT x1, OutputT beta, Fn0 && on0,
|
||||
Fn1 && on1, bool already_encoded = false, bool additive = false)
|
||||
{
|
||||
using opened_type = point_key_opened<InputT>;
|
||||
using dpf_type = utils::dpf_type_t<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, verifiable>;
|
||||
constexpr bool wild_lane = RevealPoint && dpf::is_wildcard_v<OutputT>
|
||||
&& dpf_type::lg_outputs_per_leaf > 0;
|
||||
if (additive && already_encoded)
|
||||
throw std::invalid_argument(
|
||||
"additive DS input is converted to XOR shares; it is not pre-opened");
|
||||
if (self == role::p2)
|
||||
throw std::invalid_argument("iknp point keygen has no dealer");
|
||||
if (additive)
|
||||
iknp_deal::prepare_point<InteriorPRG, InputT, OutputT, !RevealPoint, true>(
|
||||
net, self);
|
||||
else
|
||||
iknp_deal::prepare_point<InteriorPRG, InputT, OutputT, !RevealPoint, false>(
|
||||
net, self);
|
||||
if (additive)
|
||||
{
|
||||
if (self == role::p0)
|
||||
x0 = dist::additive_to_xor_share<role::p0>(net, x0);
|
||||
else
|
||||
x1 = dist::additive_to_xor_share<role::p1>(net, x1);
|
||||
}
|
||||
if constexpr (RevealPoint)
|
||||
{
|
||||
auto finish = [](auto result) {
|
||||
opened_type out;
|
||||
out.opened_prefix = result.opened_prefix;
|
||||
if constexpr (wild_lane)
|
||||
out.opened_lane = result.opened_lane;
|
||||
return out;
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto result = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, role::p0, false, true>(net, x0, beta, already_encoded);
|
||||
std::forward<Fn0>(on0)(result.dpf_key);
|
||||
return std::optional<opened_type>(finish(std::move(result)));
|
||||
}
|
||||
auto result = dist::point_party<InteriorPRG, ExteriorPRG, InputT,
|
||||
OutputT, role::p1, false, true>(net, x1, beta, already_encoded);
|
||||
std::forward<Fn1>(on1)(result.dpf_key);
|
||||
return std::optional<opened_type>(finish(std::move(result)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self == role::p0)
|
||||
std::forward<Fn0>(on0)(dist::point_party<InteriorPRG, ExteriorPRG,
|
||||
InputT, OutputT, role::p0, false, false>(
|
||||
net, x0, beta, already_encoded));
|
||||
else
|
||||
std::forward<Fn1>(on1)(dist::point_party<InteriorPRG, ExteriorPRG,
|
||||
InputT, OutputT, role::p1, false, false>(
|
||||
net, x1, beta, already_encoded));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Two-party extractable point keygen.
|
||||
template <typename InteriorPRG = dpf::prg::aes128,
|
||||
typename ExteriorPRG = InteriorPRG,
|
||||
typename InputT,
|
||||
typename OutputT,
|
||||
typename Fn0,
|
||||
typename Fn1>
|
||||
void dist_with_extractable_point_key_iknp(trio & net, role self, InputT x0,
|
||||
InputT x1, OutputT beta, Fn0 && on0, Fn1 && on1)
|
||||
{
|
||||
static_assert(!dpf::is_wildcard_v<OutputT>,
|
||||
"extractable socket gen does not reconstruct a wildcard lane");
|
||||
if (self == role::p2)
|
||||
throw std::invalid_argument("iknp point keygen has no dealer");
|
||||
iknp_deal::prepare_point<InteriorPRG, InputT, OutputT, false, false>(
|
||||
net, self);
|
||||
if (self == role::p0)
|
||||
{
|
||||
on0(dist::point_party<InteriorPRG, ExteriorPRG, InputT, OutputT,
|
||||
role::p0, true>(net, x0, beta));
|
||||
}
|
||||
else
|
||||
{
|
||||
on1(dist::point_party<InteriorPRG, ExteriorPRG, InputT, OutputT,
|
||||
role::p1, true>(net, x1, beta));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Two-party comparison keygen.
|
||||
template <typename InteriorPRG = dpf::prg::aes128,
|
||||
typename ExteriorPRG = InteriorPRG,
|
||||
bool Reveal = false,
|
||||
typename InputT,
|
||||
typename Spec,
|
||||
typename Fn0,
|
||||
typename Fn1,
|
||||
typename ...Tags>
|
||||
auto dist_with_cmp_key_iknp(trio & net, role self, InputT x0, InputT x1,
|
||||
const Spec & spec, Fn0 && on0, Fn1 && on1, const Tags & ...tags)
|
||||
-> std::conditional_t<Reveal, std::optional<InputT>, suppressed_point>
|
||||
{
|
||||
if (self == role::p2)
|
||||
throw std::invalid_argument("iknp comparison keygen has no dealer");
|
||||
iknp_deal::prepare_comparison<InteriorPRG, ExteriorPRG, InputT, !Reveal>(
|
||||
net, self, spec, tags...);
|
||||
if constexpr (Reveal)
|
||||
{
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto result = dist::comparison_party<role::p0, InteriorPRG,
|
||||
ExteriorPRG, false, true>(net, x0, spec, tags...);
|
||||
std::forward<Fn0>(on0)(result.dpf_key);
|
||||
return result.opened_point;
|
||||
}
|
||||
auto result = dist::comparison_party<role::p1, InteriorPRG,
|
||||
ExteriorPRG, false, true>(net, x1, spec, tags...);
|
||||
std::forward<Fn1>(on1)(result.dpf_key);
|
||||
return result.opened_point;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self == role::p0)
|
||||
{
|
||||
std::forward<Fn0>(on0)(dist::comparison_party<role::p0, InteriorPRG,
|
||||
ExteriorPRG, false, false>(net, x0, spec, tags...));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::forward<Fn1>(on1)(dist::comparison_party<role::p1, InteriorPRG,
|
||||
ExteriorPRG, false, false>(net, x1, spec, tags...));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Two-party interval keygen.
|
||||
template <typename InteriorPRG = dpf::prg::aes128,
|
||||
typename ExteriorPRG = InteriorPRG,
|
||||
bool Reveal = false,
|
||||
typename InputT,
|
||||
typename Beta,
|
||||
typename Fn0,
|
||||
typename Fn1>
|
||||
auto dist_with_ic_key_iknp(trio & net, role self, InputT r0, InputT r1,
|
||||
const ic_pack<Beta> & spec, Fn0 && on0, Fn1 && on1)
|
||||
-> std::conditional_t<Reveal, std::optional<std::decay_t<InputT>>,
|
||||
suppressed_point>
|
||||
{
|
||||
using input_type = std::decay_t<InputT>;
|
||||
using out_beta = concrete_type_t<Beta>;
|
||||
detail::ic_impl::check_input<input_type>();
|
||||
detail::ic_impl::check_bounds<input_type>(spec);
|
||||
if constexpr (detail::cmp_group_info<Beta>::custom)
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
"dist interval keygen currently supports <=64-bit ring payloads");
|
||||
if constexpr (Reveal)
|
||||
return std::nullopt;
|
||||
else
|
||||
return {};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self == role::p2)
|
||||
throw std::invalid_argument("iknp interval keygen has no dealer");
|
||||
const auto inner_spec = detail::ic_impl::inner_lt(spec);
|
||||
const std::uint64_t gmask = detail::ic_impl::group_mask_of<Beta>();
|
||||
const std::uint64_t nmask = detail::ic_impl::input_mask_of<input_type>();
|
||||
iknp_deal::prepare_ic<InteriorPRG, ExteriorPRG, input_type, Reveal>(
|
||||
net, self, spec);
|
||||
const input_type mine = self == role::p0 ? r0 : r1;
|
||||
std::uint64_t delta = 0;
|
||||
std::uint64_t fval = 0;
|
||||
if constexpr (!is_wildcard_v<Beta>)
|
||||
{
|
||||
delta = detail::dcf_impl::beta_delta_u64(
|
||||
spec.if_true, spec.if_false, gmask);
|
||||
fval = detail::dcf_impl::beta_to_u64_simple(spec.if_false, gmask);
|
||||
}
|
||||
if constexpr (Reveal)
|
||||
{
|
||||
const role peer = self == role::p0 ? role::p1 : role::p0;
|
||||
const input_type theirs =
|
||||
net.exchange_with(peer, mine, net::msg::delta);
|
||||
const input_type r = utils::xor_input_shares(mine, theirs);
|
||||
const std::uint64_t r_bits = detail::ic_impl::bits_of(r);
|
||||
input_type gamma = detail::ic_impl::gamma_of(r);
|
||||
utils::flip_msb_if_signed_integral(gamma);
|
||||
const std::uint64_t cr = detail::ic_impl::correction(
|
||||
r_bits, spec.lo, spec.hi, nmask, gmask);
|
||||
const std::uint64_t absorb =
|
||||
(detail::ic_impl::mul_mask(delta, cr, gmask) + fval) & gmask;
|
||||
const auto recv_side_shares = [&]() {
|
||||
const auto z0 =
|
||||
net.recv_from<dist::ring_zero_share_msg>(role::p2, net::msg::beaver_tape);
|
||||
const auto z1 =
|
||||
net.recv_from<dist::ring_zero_share_msg>(role::p2, net::msg::beaver_tape);
|
||||
const std::uint64_t side = self == role::p1 ? 1ULL : 0ULL;
|
||||
const std::uint64_t dshare = is_wildcard_v<Beta>
|
||||
? 0ULL : (z0.share + side * delta) & gmask;
|
||||
const std::uint64_t cshare = is_wildcard_v<Beta>
|
||||
? 0ULL : (z1.share + side * absorb) & gmask;
|
||||
const std::uint64_t dcoeff = is_wildcard_v<Beta>
|
||||
? (z0.share + side) & gmask : 0ULL;
|
||||
const std::uint64_t ccoeff = is_wildcard_v<Beta>
|
||||
? (z1.share + side * cr) & gmask : 0ULL;
|
||||
return std::array<std::uint64_t, 4>{
|
||||
dshare, cshare, dcoeff, ccoeff};
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto inner = dist::comparison_party<role::p0, InteriorPRG,
|
||||
ExteriorPRG, true>(net, gamma, inner_spec);
|
||||
const auto shares = recv_side_shares();
|
||||
using raw_key = typename decltype(inner)::key_type;
|
||||
auto key = detail::ic_impl::make_side<0, raw_key, input_type,
|
||||
out_beta>(std::move(inner), spec.lo, spec.hi, nmask, gmask,
|
||||
shares[0], shares[1], shares[2], shares[3]);
|
||||
on0(std::move(key));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto inner = dist::comparison_party<role::p1, InteriorPRG,
|
||||
ExteriorPRG, true>(net, gamma, inner_spec);
|
||||
const auto shares = recv_side_shares();
|
||||
using raw_key = typename decltype(inner)::key_type;
|
||||
auto key = detail::ic_impl::make_side<1, raw_key, input_type,
|
||||
out_beta>(std::move(inner), spec.lo, spec.hi, nmask, gmask,
|
||||
shares[0], shares[1], shares[2], shares[3]);
|
||||
on1(std::move(key));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
else
|
||||
{
|
||||
input_type gamma_share{};
|
||||
std::uint64_t cr_share = 0;
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto got = dist::ic_gamma_and_correction<0, input_type>(net,
|
||||
mine, spec.lo, spec.hi, nmask, gmask);
|
||||
gamma_share = got.first;
|
||||
cr_share = got.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto got = dist::ic_gamma_and_correction<1, input_type>(net,
|
||||
mine, spec.lo, spec.hi, nmask, gmask);
|
||||
gamma_share = got.first;
|
||||
cr_share = got.second;
|
||||
}
|
||||
const std::uint64_t absorb_share = is_wildcard_v<Beta>
|
||||
? 0ULL
|
||||
: (detail::ic_impl::mul_mask(delta, cr_share, gmask)
|
||||
+ (self == role::p1 ? fval : 0ULL)) & gmask;
|
||||
const std::uint64_t cr_coeff_share =
|
||||
is_wildcard_v<Beta> ? cr_share : 0ULL;
|
||||
const auto recv_delta = [&]() {
|
||||
const auto z0 =
|
||||
net.recv_from<dist::ring_zero_share_msg>(role::p2, net::msg::beaver_tape);
|
||||
const std::uint64_t side = self == role::p1 ? 1ULL : 0ULL;
|
||||
const std::uint64_t dshare = is_wildcard_v<Beta>
|
||||
? 0ULL : (z0.share + side * delta) & gmask;
|
||||
const std::uint64_t dcoeff = is_wildcard_v<Beta>
|
||||
? (z0.share + side) & gmask : 0ULL;
|
||||
return std::array<std::uint64_t, 2>{dshare, dcoeff};
|
||||
};
|
||||
if (self == role::p0)
|
||||
{
|
||||
auto inner = dist::comparison_party<role::p0, InteriorPRG,
|
||||
ExteriorPRG, false, false>(net, gamma_share, inner_spec);
|
||||
const auto d = recv_delta();
|
||||
using raw_key = typename decltype(inner)::key_type;
|
||||
auto key = detail::ic_impl::make_side<0, raw_key, input_type,
|
||||
out_beta>(std::move(inner), spec.lo, spec.hi, nmask, gmask,
|
||||
d[0], absorb_share, d[1], cr_coeff_share);
|
||||
on0(std::move(key));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto inner = dist::comparison_party<role::p1, InteriorPRG,
|
||||
ExteriorPRG, false, false>(net, gamma_share, inner_spec);
|
||||
const auto d = recv_delta();
|
||||
using raw_key = typename decltype(inner)::key_type;
|
||||
auto key = detail::ic_impl::make_side<1, raw_key, input_type,
|
||||
out_beta>(std::move(inner), spec.lo, spec.hi, nmask, gmask,
|
||||
d[0], absorb_share, d[1], cr_coeff_share);
|
||||
on1(std::move(key));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
|
||||
#endif // LIBDPF_PARTY_IKNP_DEAL_HPP__
|
||||
70
party/key_io.hpp
Normal file
70
party/key_io.hpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/// @file party/key_io.hpp
|
||||
/// @brief Ship a copyable DPF key as a framed byte blob.
|
||||
#ifndef LIBDPF_PARTY_KEY_IO_HPP__
|
||||
#define LIBDPF_PARTY_KEY_IO_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/net/channel.hpp"
|
||||
#include "dpf/net/trio.hpp"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
|
||||
template <typename Key>
|
||||
void send_key(net::channel & c, const Key & key)
|
||||
{
|
||||
Key copy(key);
|
||||
c.send_bytes(net::msg::dpf_key,
|
||||
reinterpret_cast<const std::uint8_t *>(©), sizeof(Key));
|
||||
}
|
||||
|
||||
template <typename Key>
|
||||
Key recv_key(net::channel & c)
|
||||
{
|
||||
auto body = c.recv_bytes(net::msg::dpf_key);
|
||||
if (body.size() != sizeof(Key))
|
||||
throw std::runtime_error("recv_key size mismatch");
|
||||
alignas(Key) unsigned char raw[sizeof(Key)];
|
||||
std::memcpy(raw, body.data(), sizeof(Key));
|
||||
Key * p = std::launder(reinterpret_cast<Key *>(raw));
|
||||
Key out(std::move(*p));
|
||||
p->~Key();
|
||||
return out;
|
||||
}
|
||||
|
||||
/// @brief Send a key blob through the trio hook (or mesh when unset).
|
||||
template <typename Key>
|
||||
void send_key(net::trio & net, net::role peer, const Key & key)
|
||||
{
|
||||
Key copy(key);
|
||||
net.send_bytes_to(peer, net::msg::dpf_key,
|
||||
reinterpret_cast<const std::uint8_t *>(©), sizeof(Key));
|
||||
}
|
||||
|
||||
/// @brief Receive a key blob through the trio hook (or mesh when unset).
|
||||
template <typename Key>
|
||||
Key recv_key(net::trio & net, net::role peer)
|
||||
{
|
||||
auto body = net.recv_bytes_from(peer, net::msg::dpf_key);
|
||||
if (body.size() != sizeof(Key))
|
||||
throw std::runtime_error("recv_key size mismatch");
|
||||
alignas(Key) unsigned char raw[sizeof(Key)];
|
||||
std::memcpy(raw, body.data(), sizeof(Key));
|
||||
Key * p = std::launder(reinterpret_cast<Key *>(raw));
|
||||
Key out(std::move(*p));
|
||||
p->~Key();
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
|
||||
#endif // LIBDPF_PARTY_KEY_IO_HPP__
|
||||
633
party/oblivious_hash.hpp
Normal file
633
party/oblivious_hash.hpp
Normal file
|
|
@ -0,0 +1,633 @@
|
|||
/// @file party/oblivious_hash.hpp
|
||||
/// @brief Correction-seed hash that keeps the path prefix shared.
|
||||
/// @details Matches `detail::vdpf::hash_node` without either party learning
|
||||
/// the prefix. The opened value is only `H(s0) XOR H(s1)`.
|
||||
/// SubBytes uses the Boyar–Peralta AES S-box (ePrint 2011/332;
|
||||
/// Yale CMT SLP with 32 ANDs). Bit×bit ANDs use bit Beaver triples
|
||||
/// and are scheduled by multiplicative depth (six exchanges per
|
||||
/// SubBytes), so one level costs `8×16×10×32 = 40960` bit-ANDs.
|
||||
|
||||
#ifndef LIBDPF_PARTY_OBLIVIOUS_HASH_HPP__
|
||||
#define LIBDPF_PARTY_OBLIVIOUS_HASH_HPP__
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "simde/simde/x86/avx2.h"
|
||||
|
||||
#include "aes_mmo_ref.hpp"
|
||||
#include "aes_sbox_bp.hpp"
|
||||
#include "dpf/doerner_shelat.hpp"
|
||||
#include "dpf/prg_aes.hpp"
|
||||
#include "dpf/verifiable.hpp"
|
||||
#include "dpf/net/round_sink.hpp"
|
||||
#include "dpf/net/sink_exchange.hpp"
|
||||
|
||||
/* aes_bp is file-scope */
|
||||
|
||||
/// @brief AND-triples consumed by one level of `oblivious_cs`.
|
||||
inline constexpr std::size_t hash_level_and_count() noexcept
|
||||
{
|
||||
// Eight AES (two seeds × four MMO lanes) × 16 bytes × 10 SubBytes
|
||||
// × 32 Boyar–Peralta ANDs.
|
||||
return 8u * 16u * 10u * aes_bp::and_count;
|
||||
}
|
||||
|
||||
inline std::vector<bit_and_pad_msg> sample_and_tape(std::size_t n, int party)
|
||||
{
|
||||
::dpf::detail::urandom_pad_rng pads;
|
||||
std::vector<bit_and_pad_msg> out(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const auto full = ::dpf::detail::ds_sample_bit_and(pads);
|
||||
out[i] = bit_pad_for(full, party);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::vector<std::uint8_t> pack_mask_bits(
|
||||
const std::uint8_t * d, const std::uint8_t * e, std::size_t n)
|
||||
{
|
||||
std::vector<std::uint8_t> out((2u * n + 7u) / 8u);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const std::size_t db = 2u * i;
|
||||
const std::size_t eb = db + 1u;
|
||||
if (d[i] & 1u)
|
||||
out[db / 8u] = static_cast<std::uint8_t>(
|
||||
out[db / 8u] | static_cast<std::uint8_t>(1u << (db % 8u)));
|
||||
if (e[i] & 1u)
|
||||
out[eb / 8u] = static_cast<std::uint8_t>(
|
||||
out[eb / 8u] | static_cast<std::uint8_t>(1u << (eb % 8u)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void unpack_mask_bits(const std::vector<std::uint8_t> & packed,
|
||||
std::uint8_t * d, std::uint8_t * e, std::size_t n)
|
||||
{
|
||||
if (packed.size() != (2u * n + 7u) / 8u)
|
||||
throw std::runtime_error("oblivious AND packed size");
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const std::size_t db = 2u * i;
|
||||
const std::size_t eb = db + 1u;
|
||||
d[i] = static_cast<std::uint8_t>(
|
||||
(packed[db / 8u] >> (db % 8u)) & 1u);
|
||||
e[i] = static_cast<std::uint8_t>(
|
||||
(packed[eb / 8u] >> (eb % 8u)) & 1u);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief One exchange of packed `(x⊕α, y⊕β)` masks for `n` bit-ANDs.
|
||||
template <int Me>
|
||||
std::vector<std::uint8_t> batch_bit_and(net::trio & net,
|
||||
const std::uint8_t * x, const std::uint8_t * y, std::size_t n,
|
||||
const bit_and_pad_msg * triples)
|
||||
{
|
||||
const role peer = Me == 0 ? role::p1 : role::p0;
|
||||
std::vector<std::uint8_t> d(n), e(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
d[i] = static_cast<std::uint8_t>(x[i] ^ triples[i].a);
|
||||
e[i] = static_cast<std::uint8_t>(y[i] ^ triples[i].b);
|
||||
}
|
||||
const auto mine = pack_mask_bits(d.data(), e.data(), n);
|
||||
std::vector<std::uint8_t> peer_bytes;
|
||||
if constexpr (Me == 0)
|
||||
{
|
||||
net.send_bytes_to(peer, net::msg::delta, mine.data(), mine.size());
|
||||
peer_bytes = net.recv_bytes_from(peer, net::msg::delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
peer_bytes = net.recv_bytes_from(peer, net::msg::delta);
|
||||
net.send_bytes_to(peer, net::msg::delta, mine.data(), mine.size());
|
||||
}
|
||||
std::vector<std::uint8_t> pd(n), pe(n);
|
||||
unpack_mask_bits(peer_bytes, pd.data(), pe.data(), n);
|
||||
|
||||
// Standard Beaver: z = [c] ⊕ (d∧[b]) ⊕ ([a]∧e) ⊕ (d∧e), and the public
|
||||
// `d∧e` term is added on one party only. Adding it on both cancels under
|
||||
// XOR and the S-box (and every correction seed) diverges from `hash_node`.
|
||||
std::vector<std::uint8_t> shares(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const std::uint8_t od = static_cast<std::uint8_t>(d[i] ^ pd[i]);
|
||||
const std::uint8_t oe = static_cast<std::uint8_t>(e[i] ^ pe[i]);
|
||||
shares[i] = detail::ds_bit_and_party(
|
||||
od, oe, triples[i].a, triples[i].b, triples[i].c, Me == 0);
|
||||
}
|
||||
return shares;
|
||||
}
|
||||
|
||||
/// @brief Packed bit-AND layer on a RoundSink. Slot must fit `4 + packed`.
|
||||
template <int Me>
|
||||
std::vector<std::uint8_t> batch_bit_and_on_sink(dpf::net::RoundSink & sink,
|
||||
std::uint16_t & round, std::size_t index, const std::uint8_t * x,
|
||||
const std::uint8_t * y, std::size_t n, const bit_and_pad_msg * triples)
|
||||
{
|
||||
std::vector<std::uint8_t> d(n), e(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
d[i] = static_cast<std::uint8_t>(x[i] ^ triples[i].a);
|
||||
e[i] = static_cast<std::uint8_t>(y[i] ^ triples[i].b);
|
||||
}
|
||||
const auto mine = pack_mask_bits(d.data(), e.data(), n);
|
||||
if (round >= sink.rounds())
|
||||
throw std::runtime_error("batch_bit_and_on_sink: out of rounds");
|
||||
const std::size_t slot = sink.slot_bytes(round);
|
||||
const std::size_t need = sizeof(std::uint32_t) + mine.size();
|
||||
if (need > slot)
|
||||
throw std::runtime_error("batch_bit_and_on_sink: slot too small");
|
||||
std::vector<std::uint8_t> buf(slot, 0);
|
||||
const std::uint32_t nbytes = static_cast<std::uint32_t>(mine.size());
|
||||
std::memcpy(buf.data(), &nbytes, 4);
|
||||
if (!mine.empty())
|
||||
std::memcpy(buf.data() + 4, mine.data(), mine.size());
|
||||
sink.submit(round, index, buf.data(), buf.size());
|
||||
sink.flush_round(round);
|
||||
sink.poll();
|
||||
if (!sink.peer_ready(round, index))
|
||||
throw std::runtime_error("batch_bit_and_on_sink: peer missing");
|
||||
std::vector<std::uint8_t> peer_buf(slot);
|
||||
sink.read_peer(round, index, peer_buf.data(), slot);
|
||||
std::uint32_t pn = 0;
|
||||
std::memcpy(&pn, peer_buf.data(), 4);
|
||||
if (pn != nbytes)
|
||||
throw std::runtime_error("batch_bit_and_on_sink: peer size");
|
||||
std::vector<std::uint8_t> peer_bytes(pn);
|
||||
if (pn != 0)
|
||||
std::memcpy(peer_bytes.data(), peer_buf.data() + 4, pn);
|
||||
++round;
|
||||
std::vector<std::uint8_t> pd(n), pe(n);
|
||||
unpack_mask_bits(peer_bytes, pd.data(), pe.data(), n);
|
||||
std::vector<std::uint8_t> shares(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
const std::uint8_t od = static_cast<std::uint8_t>(d[i] ^ pd[i]);
|
||||
const std::uint8_t oe = static_cast<std::uint8_t>(e[i] ^ pe[i]);
|
||||
shares[i] = detail::ds_bit_and_party(
|
||||
od, oe, triples[i].a, triples[i].b, triples[i].c, Me == 0);
|
||||
}
|
||||
return shares;
|
||||
}
|
||||
|
||||
/// @brief Shared SubBytes via Boyar–Peralta (32 ANDs per byte, depth-batched).
|
||||
template <int Me>
|
||||
void sub_bytes_shared(net::trio & net, std::uint8_t * state, std::size_t bytes,
|
||||
const bit_and_pad_msg * & tape)
|
||||
{
|
||||
std::vector<std::uint8_t> wire(bytes * aes_bp::wire_count);
|
||||
auto at = [&](std::size_t b, std::size_t w) -> std::uint8_t & {
|
||||
return wire[b * aes_bp::wire_count + w];
|
||||
};
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
at(b, static_cast<std::size_t>(i)) =
|
||||
static_cast<std::uint8_t>((state[b] >> (7 - i)) & 1u);
|
||||
}
|
||||
|
||||
std::array<bool, aes_bp::wire_count> ready{};
|
||||
for (std::size_t i = 0; i < 8; ++i)
|
||||
ready[i] = true;
|
||||
std::array<bool, aes_bp::op_count> done{};
|
||||
std::size_t finished = 0;
|
||||
while (finished < aes_bp::op_count)
|
||||
{
|
||||
bool progress = true;
|
||||
while (progress)
|
||||
{
|
||||
progress = false;
|
||||
for (std::size_t oi = 0; oi < aes_bp::op_count; ++oi)
|
||||
{
|
||||
if (done[oi])
|
||||
continue;
|
||||
const auto kind = aes_bp::ops[oi][0];
|
||||
if (kind == 1)
|
||||
continue;
|
||||
const auto dst = aes_bp::ops[oi][1];
|
||||
const auto a = aes_bp::ops[oi][2];
|
||||
const auto bb = aes_bp::ops[oi][3];
|
||||
if (!ready[a] || !ready[bb])
|
||||
continue;
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
{
|
||||
auto v = static_cast<std::uint8_t>(at(b, a) ^ at(b, bb));
|
||||
if (kind == 2)
|
||||
{
|
||||
if constexpr (Me == 0)
|
||||
v = static_cast<std::uint8_t>(v ^ 1u);
|
||||
}
|
||||
at(b, dst) = v;
|
||||
}
|
||||
ready[dst] = true;
|
||||
done[oi] = true;
|
||||
++finished;
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::size_t> ands;
|
||||
ands.reserve(aes_bp::and_count);
|
||||
for (std::size_t oi = 0; oi < aes_bp::op_count; ++oi)
|
||||
{
|
||||
if (done[oi] || aes_bp::ops[oi][0] != 1)
|
||||
continue;
|
||||
const auto a = aes_bp::ops[oi][2];
|
||||
const auto bb = aes_bp::ops[oi][3];
|
||||
if (ready[a] && ready[bb])
|
||||
ands.push_back(oi);
|
||||
}
|
||||
if (ands.empty())
|
||||
{
|
||||
if (finished != aes_bp::op_count)
|
||||
throw std::runtime_error("oblivious SubBytes stuck");
|
||||
break;
|
||||
}
|
||||
|
||||
const std::size_t n = ands.size() * bytes;
|
||||
std::vector<std::uint8_t> xs(n), ys(n);
|
||||
for (std::size_t ai = 0; ai < ands.size(); ++ai)
|
||||
{
|
||||
const auto a = aes_bp::ops[ands[ai]][2];
|
||||
const auto bb = aes_bp::ops[ands[ai]][3];
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
{
|
||||
xs[ai * bytes + b] = at(b, a);
|
||||
ys[ai * bytes + b] = at(b, bb);
|
||||
}
|
||||
}
|
||||
const auto prod = batch_bit_and<Me>(net, xs.data(), ys.data(), n, tape);
|
||||
tape += n;
|
||||
for (std::size_t ai = 0; ai < ands.size(); ++ai)
|
||||
{
|
||||
const auto dst = aes_bp::ops[ands[ai]][1];
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
at(b, dst) = static_cast<std::uint8_t>(prod[ai * bytes + b] & 1u);
|
||||
ready[dst] = true;
|
||||
done[ands[ai]] = true;
|
||||
++finished;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
{
|
||||
std::uint8_t out = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
out = static_cast<std::uint8_t>(
|
||||
out | (at(b, aes_bp::out_wire[static_cast<std::size_t>(i)])
|
||||
<< (7 - i)));
|
||||
state[b] = out;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief SubBytes with AND layers on a RoundSink (multi-instance friendly).
|
||||
template <int Me>
|
||||
void sub_bytes_shared_on_sink(dpf::net::RoundSink & sink, std::uint16_t & round,
|
||||
std::size_t index, std::uint8_t * state, std::size_t bytes,
|
||||
const bit_and_pad_msg * & tape)
|
||||
{
|
||||
std::vector<std::uint8_t> wire(bytes * aes_bp::wire_count);
|
||||
auto at = [&](std::size_t b, std::size_t w) -> std::uint8_t & {
|
||||
return wire[b * aes_bp::wire_count + w];
|
||||
};
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
at(b, static_cast<std::size_t>(i)) =
|
||||
static_cast<std::uint8_t>((state[b] >> (7 - i)) & 1u);
|
||||
}
|
||||
|
||||
std::array<bool, aes_bp::wire_count> ready{};
|
||||
for (std::size_t i = 0; i < 8; ++i)
|
||||
ready[i] = true;
|
||||
std::array<bool, aes_bp::op_count> done{};
|
||||
std::size_t finished = 0;
|
||||
while (finished < aes_bp::op_count)
|
||||
{
|
||||
bool progress = true;
|
||||
while (progress)
|
||||
{
|
||||
progress = false;
|
||||
for (std::size_t oi = 0; oi < aes_bp::op_count; ++oi)
|
||||
{
|
||||
if (done[oi])
|
||||
continue;
|
||||
const auto kind = aes_bp::ops[oi][0];
|
||||
if (kind == 1)
|
||||
continue;
|
||||
const auto dst = aes_bp::ops[oi][1];
|
||||
const auto a = aes_bp::ops[oi][2];
|
||||
const auto bb = aes_bp::ops[oi][3];
|
||||
if (!ready[a] || !ready[bb])
|
||||
continue;
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
{
|
||||
auto v = static_cast<std::uint8_t>(at(b, a) ^ at(b, bb));
|
||||
if (kind == 2)
|
||||
{
|
||||
if constexpr (Me == 0)
|
||||
v = static_cast<std::uint8_t>(v ^ 1u);
|
||||
}
|
||||
at(b, dst) = v;
|
||||
}
|
||||
ready[dst] = true;
|
||||
done[oi] = true;
|
||||
++finished;
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::size_t> ands;
|
||||
ands.reserve(aes_bp::and_count);
|
||||
for (std::size_t oi = 0; oi < aes_bp::op_count; ++oi)
|
||||
{
|
||||
if (done[oi] || aes_bp::ops[oi][0] != 1)
|
||||
continue;
|
||||
const auto a = aes_bp::ops[oi][2];
|
||||
const auto bb = aes_bp::ops[oi][3];
|
||||
if (ready[a] && ready[bb])
|
||||
ands.push_back(oi);
|
||||
}
|
||||
if (ands.empty())
|
||||
{
|
||||
if (finished != aes_bp::op_count)
|
||||
throw std::runtime_error("oblivious SubBytes stuck");
|
||||
break;
|
||||
}
|
||||
|
||||
const std::size_t n = ands.size() * bytes;
|
||||
std::vector<std::uint8_t> xs(n), ys(n);
|
||||
for (std::size_t ai = 0; ai < ands.size(); ++ai)
|
||||
{
|
||||
const auto a = aes_bp::ops[ands[ai]][2];
|
||||
const auto bb = aes_bp::ops[ands[ai]][3];
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
{
|
||||
xs[ai * bytes + b] = at(b, a);
|
||||
ys[ai * bytes + b] = at(b, bb);
|
||||
}
|
||||
}
|
||||
const auto prod = batch_bit_and_on_sink<Me>(
|
||||
sink, round, index, xs.data(), ys.data(), n, tape);
|
||||
tape += n;
|
||||
for (std::size_t ai = 0; ai < ands.size(); ++ai)
|
||||
{
|
||||
const auto dst = aes_bp::ops[ands[ai]][1];
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
at(b, dst) = static_cast<std::uint8_t>(prod[ai * bytes + b] & 1u);
|
||||
ready[dst] = true;
|
||||
done[ands[ai]] = true;
|
||||
++finished;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t b = 0; b < bytes; ++b)
|
||||
{
|
||||
std::uint8_t out = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
out = static_cast<std::uint8_t>(
|
||||
out | (at(b, aes_bp::out_wire[static_cast<std::size_t>(i)])
|
||||
<< (7 - i)));
|
||||
state[b] = out;
|
||||
}
|
||||
}
|
||||
|
||||
inline void shift_rows_bytes(std::uint8_t s[16])
|
||||
{
|
||||
::dpf::party::aes_ref::shift_rows(s);
|
||||
}
|
||||
|
||||
inline void mix_columns_bytes(std::uint8_t s[16])
|
||||
{
|
||||
::dpf::party::aes_ref::mix_columns(s);
|
||||
}
|
||||
|
||||
inline void xor_bytes(std::uint8_t * dst, const std::uint8_t * src, std::size_t n)
|
||||
{
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
dst[i] = static_cast<std::uint8_t>(dst[i] ^ src[i]);
|
||||
}
|
||||
|
||||
inline std::array<std::uint8_t, 16> m128_bytes(simde__m128i v)
|
||||
{
|
||||
std::array<std::uint8_t, 16> b{};
|
||||
std::memcpy(b.data(), &v, 16);
|
||||
return b;
|
||||
}
|
||||
|
||||
/// @brief Public correction seed for one level. Prefix shares stay local.
|
||||
template <int Me>
|
||||
cs_block oblivious_cs(net::trio & net, std::size_t level,
|
||||
std::uint64_t prefix_share, simde__m128i my_seed,
|
||||
const bit_and_pad_msg * tape)
|
||||
{
|
||||
const auto schedule = prg::aes128_key(simde_mm_setzero_si128());
|
||||
const auto rk = [&](int r) {
|
||||
return m128_bytes(schedule.rd_key[static_cast<std::size_t>(r)]);
|
||||
};
|
||||
|
||||
constexpr std::size_t blocks = 8;
|
||||
std::uint8_t state[blocks][16]{};
|
||||
std::uint8_t feed[blocks][16]{};
|
||||
|
||||
auto place_prefix = [&](std::uint8_t * dst) {
|
||||
for (int i = 0; i < 8; ++i)
|
||||
dst[i] = static_cast<std::uint8_t>(
|
||||
dst[i] ^ static_cast<std::uint8_t>(prefix_share >> (8 * i)));
|
||||
};
|
||||
|
||||
for (int owner = 0; owner < 2; ++owner)
|
||||
{
|
||||
const bool mine = (owner == Me);
|
||||
for (int pos = 0; pos < 4; ++pos)
|
||||
{
|
||||
const std::size_t bi = static_cast<std::size_t>(owner * 4 + pos);
|
||||
if (mine)
|
||||
{
|
||||
auto bytes = m128_bytes(my_seed);
|
||||
const std::uint64_t tag = 0x5600ull | (level & 0xffffu);
|
||||
for (int i = 0; i < 8; ++i)
|
||||
bytes[8 + i] = static_cast<std::uint8_t>(
|
||||
bytes[8 + i]
|
||||
^ static_cast<std::uint8_t>(tag >> (8 * i)));
|
||||
place_prefix(bytes.data());
|
||||
std::memcpy(feed[bi], bytes.data(), 16);
|
||||
std::memcpy(state[bi], bytes.data(), 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::uint8_t bytes[16]{};
|
||||
place_prefix(bytes);
|
||||
std::memcpy(feed[bi], bytes, 16);
|
||||
std::memcpy(state[bi], bytes, 16);
|
||||
}
|
||||
if constexpr (Me == 0)
|
||||
{
|
||||
auto key0 = rk(0);
|
||||
key0[0] = static_cast<std::uint8_t>(
|
||||
key0[0] ^ static_cast<std::uint8_t>(pos));
|
||||
key0[1] = static_cast<std::uint8_t>(
|
||||
key0[1]
|
||||
^ static_cast<std::uint8_t>(static_cast<unsigned>(pos) >> 8));
|
||||
key0[2] = static_cast<std::uint8_t>(
|
||||
key0[2]
|
||||
^ static_cast<std::uint8_t>(
|
||||
static_cast<unsigned>(pos) >> 16));
|
||||
key0[3] = static_cast<std::uint8_t>(
|
||||
key0[3]
|
||||
^ static_cast<std::uint8_t>(
|
||||
static_cast<unsigned>(pos) >> 24));
|
||||
xor_bytes(state[bi], key0.data(), 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto all_bytes = [&]() -> std::uint8_t * { return &state[0][0]; };
|
||||
const bit_and_pad_msg * cursor = tape;
|
||||
for (int round = 1; round <= 10; ++round)
|
||||
{
|
||||
sub_bytes_shared<Me>(net, all_bytes(), blocks * 16, cursor);
|
||||
for (std::size_t b = 0; b < blocks; ++b)
|
||||
shift_rows_bytes(state[b]);
|
||||
if (round < 10)
|
||||
{
|
||||
for (std::size_t b = 0; b < blocks; ++b)
|
||||
mix_columns_bytes(state[b]);
|
||||
}
|
||||
if constexpr (Me == 0)
|
||||
{
|
||||
const auto key = rk(round);
|
||||
for (std::size_t b = 0; b < blocks; ++b)
|
||||
xor_bytes(state[b], key.data(), 16);
|
||||
}
|
||||
}
|
||||
if (cursor != tape + hash_level_and_count())
|
||||
throw std::runtime_error("oblivious hash consumed the wrong AND count");
|
||||
|
||||
for (std::size_t b = 0; b < blocks; ++b)
|
||||
xor_bytes(state[b], feed[b], 16);
|
||||
|
||||
cs_block mine{};
|
||||
for (int pos = 0; pos < 4; ++pos)
|
||||
{
|
||||
std::uint8_t mixed[16]{};
|
||||
xor_bytes(mixed, state[static_cast<std::size_t>(pos)], 16);
|
||||
xor_bytes(mixed, state[static_cast<std::size_t>(4 + pos)], 16);
|
||||
std::memcpy(&mine[static_cast<std::size_t>(pos)], mixed, 16);
|
||||
}
|
||||
return open_cs<Me>(net, mine);
|
||||
}
|
||||
|
||||
/// @brief `oblivious_cs` with AND layers on a RoundSink; final open stays on trio.
|
||||
template <int Me>
|
||||
cs_block oblivious_cs_on_sink(net::trio & net, dpf::net::RoundSink & sink,
|
||||
std::uint16_t & round, std::size_t index, std::size_t level,
|
||||
std::uint64_t prefix_share, simde__m128i my_seed,
|
||||
const bit_and_pad_msg * tape)
|
||||
{
|
||||
const auto schedule = prg::aes128_key(simde_mm_setzero_si128());
|
||||
const auto rk = [&](int r) {
|
||||
return m128_bytes(schedule.rd_key[static_cast<std::size_t>(r)]);
|
||||
};
|
||||
|
||||
constexpr std::size_t blocks = 8;
|
||||
std::uint8_t state[blocks][16]{};
|
||||
std::uint8_t feed[blocks][16]{};
|
||||
|
||||
auto place_prefix = [&](std::uint8_t * dst) {
|
||||
for (int i = 0; i < 8; ++i)
|
||||
dst[i] = static_cast<std::uint8_t>(
|
||||
dst[i] ^ static_cast<std::uint8_t>(prefix_share >> (8 * i)));
|
||||
};
|
||||
|
||||
for (int owner = 0; owner < 2; ++owner)
|
||||
{
|
||||
const bool mine = (owner == Me);
|
||||
for (int pos = 0; pos < 4; ++pos)
|
||||
{
|
||||
const std::size_t bi = static_cast<std::size_t>(owner * 4 + pos);
|
||||
if (mine)
|
||||
{
|
||||
auto bytes = m128_bytes(my_seed);
|
||||
const std::uint64_t tag = 0x5600ull | (level & 0xffffu);
|
||||
for (int i = 0; i < 8; ++i)
|
||||
bytes[8 + i] = static_cast<std::uint8_t>(
|
||||
bytes[8 + i]
|
||||
^ static_cast<std::uint8_t>(tag >> (8 * i)));
|
||||
place_prefix(bytes.data());
|
||||
std::memcpy(feed[bi], bytes.data(), 16);
|
||||
std::memcpy(state[bi], bytes.data(), 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::uint8_t bytes[16]{};
|
||||
place_prefix(bytes);
|
||||
std::memcpy(feed[bi], bytes, 16);
|
||||
std::memcpy(state[bi], bytes, 16);
|
||||
}
|
||||
if constexpr (Me == 0)
|
||||
{
|
||||
auto key0 = rk(0);
|
||||
key0[0] = static_cast<std::uint8_t>(
|
||||
key0[0] ^ static_cast<std::uint8_t>(pos));
|
||||
key0[1] = static_cast<std::uint8_t>(
|
||||
key0[1]
|
||||
^ static_cast<std::uint8_t>(static_cast<unsigned>(pos) >> 8));
|
||||
key0[2] = static_cast<std::uint8_t>(
|
||||
key0[2]
|
||||
^ static_cast<std::uint8_t>(
|
||||
static_cast<unsigned>(pos) >> 16));
|
||||
key0[3] = static_cast<std::uint8_t>(
|
||||
key0[3]
|
||||
^ static_cast<std::uint8_t>(
|
||||
static_cast<unsigned>(pos) >> 24));
|
||||
xor_bytes(state[bi], key0.data(), 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto all_bytes = [&]() -> std::uint8_t * { return &state[0][0]; };
|
||||
const bit_and_pad_msg * cursor = tape;
|
||||
for (int aes_round = 1; aes_round <= 10; ++aes_round)
|
||||
{
|
||||
sub_bytes_shared_on_sink<Me>(
|
||||
sink, round, index, all_bytes(), blocks * 16, cursor);
|
||||
for (std::size_t b = 0; b < blocks; ++b)
|
||||
shift_rows_bytes(state[b]);
|
||||
if (aes_round < 10)
|
||||
{
|
||||
for (std::size_t b = 0; b < blocks; ++b)
|
||||
mix_columns_bytes(state[b]);
|
||||
}
|
||||
if constexpr (Me == 0)
|
||||
{
|
||||
const auto key = rk(aes_round);
|
||||
for (std::size_t b = 0; b < blocks; ++b)
|
||||
xor_bytes(state[b], key.data(), 16);
|
||||
}
|
||||
}
|
||||
if (cursor != tape + hash_level_and_count())
|
||||
throw std::runtime_error("oblivious hash consumed the wrong AND count");
|
||||
|
||||
for (std::size_t b = 0; b < blocks; ++b)
|
||||
xor_bytes(state[b], feed[b], 16);
|
||||
|
||||
cs_block mine{};
|
||||
for (int pos = 0; pos < 4; ++pos)
|
||||
{
|
||||
std::uint8_t mixed[16]{};
|
||||
xor_bytes(mixed, state[static_cast<std::size_t>(pos)], 16);
|
||||
xor_bytes(mixed, state[static_cast<std::size_t>(4 + pos)], 16);
|
||||
std::memcpy(&mine[static_cast<std::size_t>(pos)], mixed, 16);
|
||||
}
|
||||
return open_cs<Me>(net, mine);
|
||||
}
|
||||
|
||||
#endif
|
||||
296
party/oblivious_select.hpp
Normal file
296
party/oblivious_select.hpp
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
/// @file party/oblivious_select.hpp
|
||||
/// @brief Shared-output muxes for the socket Doerner–Shelat walk.
|
||||
/// @details Included from inside `dpf::party::dist`. Products stay shared.
|
||||
/// The only opened values are public correction words.
|
||||
|
||||
#ifndef LIBDPF_PARTY_OBLIVIOUS_SELECT_HPP__
|
||||
#define LIBDPF_PARTY_OBLIVIOUS_SELECT_HPP__
|
||||
|
||||
template <int Me, typename T>
|
||||
std::vector<T> exch_vec(net::trio & net, const std::vector<T> & mine)
|
||||
{
|
||||
const role peer = Me == 0 ? role::p1 : role::p0;
|
||||
if constexpr (Me == 0)
|
||||
{
|
||||
net.to(peer).send_vec(mine, net::msg::delta);
|
||||
return net.to(peer).template recv_vec<T>(net::msg::delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto got = net.to(peer).template recv_vec<T>(net::msg::delta);
|
||||
net.to(peer).send_vec(mine, net::msg::delta);
|
||||
return got;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief This party's XOR share of `bit * block`. The product is not opened.
|
||||
template <typename Exchange>
|
||||
simde__m128i beaver_xor_share_bit(Exchange && exch, bool i_hold_block,
|
||||
std::uint8_t bit_share, simde__m128i block, const and_share_msg & mine)
|
||||
{
|
||||
and_round1_msg msg{};
|
||||
msg.d_bit = static_cast<std::uint8_t>(bit_share ^ mine.a);
|
||||
msg.b_bit = mine.b;
|
||||
msg.a_m = mine.a;
|
||||
msg.e_m = i_hold_block ? detail::ds_xor(block, mine.b) : mine.b;
|
||||
const and_round1_msg peer = exch(msg);
|
||||
const std::uint8_t d = static_cast<std::uint8_t>(msg.d_bit ^ peer.d_bit);
|
||||
const simde__m128i e = i_hold_block
|
||||
? detail::ds_xor(msg.e_m, peer.b_bit)
|
||||
: detail::ds_xor(peer.e_m, mine.b);
|
||||
simde__m128i z = detail::ds_xor(
|
||||
detail::ds_xor(detail::ds_gate(d, mine.b), detail::ds_gate(mine.a, e)),
|
||||
mine.c);
|
||||
if (i_hold_block)
|
||||
z = detail::ds_xor(z, detail::ds_gate(d, e));
|
||||
return z;
|
||||
}
|
||||
|
||||
/// @brief Additive leaf share of a XOR-shared block. Limb width matches `leaf_add`.
|
||||
template <int Me>
|
||||
simde__m128i b2a_leaf(net::trio & net, simde__m128i xor_share,
|
||||
const b2a_pad_msg * pads, std::size_t limb_bytes)
|
||||
{
|
||||
std::uint8_t bytes[16]{};
|
||||
std::memcpy(bytes, &xor_share, 16);
|
||||
std::vector<std::uint8_t> masked(128);
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
const std::uint8_t bit = static_cast<std::uint8_t>(
|
||||
(bytes[i / 8] >> (i % 8)) & 1u);
|
||||
masked[static_cast<std::size_t>(i)] = static_cast<std::uint8_t>(
|
||||
bit ^ pads[i].r);
|
||||
}
|
||||
const auto peer = exch_vec<Me>(net, masked);
|
||||
const std::size_t limb_bits = limb_bytes * 8;
|
||||
const std::size_t nlimbs = 16 / limb_bytes;
|
||||
const std::uint64_t limb_mask = limb_bits == 64
|
||||
? ~std::uint64_t{0}
|
||||
: ((std::uint64_t{1} << limb_bits) - 1);
|
||||
std::uint64_t acc[16]{};
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
const std::uint8_t c = static_cast<std::uint8_t>(
|
||||
masked[static_cast<std::size_t>(i)] ^ peer[static_cast<std::size_t>(i)]);
|
||||
std::uint64_t s = pads[i].add;
|
||||
if (c != 0)
|
||||
s = Me == 0 ? (std::uint64_t{1} - pads[i].add)
|
||||
: (std::uint64_t{0} - pads[i].add);
|
||||
const std::size_t limb = static_cast<std::size_t>(i) / limb_bits;
|
||||
const unsigned shift = static_cast<unsigned>(
|
||||
static_cast<std::size_t>(i) % limb_bits);
|
||||
acc[limb] += s << shift;
|
||||
}
|
||||
std::uint8_t outb[16]{};
|
||||
for (std::size_t k = 0; k < nlimbs; ++k)
|
||||
{
|
||||
const std::uint64_t v = acc[k] & limb_mask;
|
||||
std::memcpy(outb + k * limb_bytes, &v, limb_bytes);
|
||||
}
|
||||
simde__m128i out{};
|
||||
std::memcpy(&out, outb, 16);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <int Me, typename Concrete, typename Leaf>
|
||||
Leaf open_leaf_sum(net::trio & net, Leaf mine)
|
||||
{
|
||||
const role peer = Me == 0 ? role::p1 : role::p0;
|
||||
const Leaf theirs = net.exchange_with(peer, mine, net::msg::ring_vector);
|
||||
return leaf_add<Concrete>(mine, theirs);
|
||||
}
|
||||
|
||||
/// @brief Public leaf CW from a shared naked leaf and this party's mask.
|
||||
template <int Me, typename Concrete, typename Leaf>
|
||||
Leaf cw_from_selected(net::trio & net, Leaf selected, Leaf mask, bool sign)
|
||||
{
|
||||
constexpr bool char2 = utils::has_characteristic_two_v<Concrete>;
|
||||
Leaf share{};
|
||||
if constexpr (char2)
|
||||
{
|
||||
share = leaf_add<Concrete>(selected, mask);
|
||||
}
|
||||
else if (sign)
|
||||
{
|
||||
if constexpr (Me == 0)
|
||||
share = leaf_add<Concrete>(selected, mask);
|
||||
else
|
||||
share = dpf::subtract_leaf<Concrete>(selected, mask);
|
||||
}
|
||||
else if constexpr (Me == 0)
|
||||
{
|
||||
const Leaf neg = dpf::subtract_leaf<Concrete>(Leaf{}, selected);
|
||||
share = dpf::subtract_leaf<Concrete>(neg, mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
share = dpf::subtract_leaf<Concrete>(mask, selected);
|
||||
}
|
||||
return open_leaf_sum<Me, Concrete>(net, share);
|
||||
}
|
||||
|
||||
/// @brief Share of the leaf selected by the low `lg` bits of the target.
|
||||
/// @details Party 0's `make(i)` is the candidate. Party 1's is zero. Each mux
|
||||
/// multiplies the selector by both parties' deltas and keeps the sum
|
||||
/// shared, so neither party sees the lane.
|
||||
template <int Me, typename Concrete, typename Leaf, typename Exchange,
|
||||
typename Make, typename BitAt>
|
||||
Leaf mux_leaf_share(net::trio & net, Exchange && exch, std::size_t lg,
|
||||
Make && make, BitAt && bit_at, const and_share_msg * ands,
|
||||
const b2a_pad_msg * b2a)
|
||||
{
|
||||
static_assert(sizeof(Leaf) == sizeof(simde__m128i),
|
||||
"shared leaf mux covers a single AES block");
|
||||
constexpr bool char2 = utils::has_characteristic_two_v<Concrete>;
|
||||
constexpr std::size_t limb = [] {
|
||||
constexpr std::size_t s = sizeof(Concrete);
|
||||
return (s == 1 || s == 2 || s == 4 || s == 8) ? s : std::size_t{8};
|
||||
}();
|
||||
std::vector<Leaf> cand(std::size_t{1} << lg);
|
||||
for (std::size_t i = 0; i < cand.size(); ++i)
|
||||
cand[i] = make(static_cast<unsigned>(i));
|
||||
std::size_t pad_i = 0;
|
||||
std::size_t b2a_i = 0;
|
||||
for (std::size_t b = 0; b < lg; ++b)
|
||||
{
|
||||
const std::uint8_t bit = bit_at(b);
|
||||
std::vector<Leaf> next(cand.size() / 2);
|
||||
for (std::size_t k = 0; k < next.size(); ++k)
|
||||
{
|
||||
const Leaf lo = cand[2 * k];
|
||||
const Leaf hi = cand[2 * k + 1];
|
||||
const Leaf delta = dpf::subtract_leaf<Concrete>(hi, lo);
|
||||
simde__m128i dblock{};
|
||||
std::memcpy(&dblock, &delta, sizeof(dblock));
|
||||
const simde__m128i zero = simde_mm_setzero_si128();
|
||||
simde__m128i zx{};
|
||||
simde__m128i zy{};
|
||||
if constexpr (Me == 0)
|
||||
{
|
||||
zx = beaver_xor_share_bit(exch, true, bit, dblock, ands[pad_i++]);
|
||||
zy = beaver_xor_share_bit(exch, false, bit, zero, ands[pad_i++]);
|
||||
}
|
||||
else
|
||||
{
|
||||
zx = beaver_xor_share_bit(exch, false, bit, zero, ands[pad_i++]);
|
||||
zy = beaver_xor_share_bit(exch, true, bit, dblock, ands[pad_i++]);
|
||||
}
|
||||
Leaf prod{};
|
||||
if constexpr (char2)
|
||||
{
|
||||
const simde__m128i z = detail::ds_xor(zx, zy);
|
||||
std::memcpy(&prod, &z, sizeof(prod));
|
||||
}
|
||||
else
|
||||
{
|
||||
const simde__m128i ax = b2a_leaf<Me>(net, zx, b2a + b2a_i, limb);
|
||||
b2a_i += 128;
|
||||
const simde__m128i ay = b2a_leaf<Me>(net, zy, b2a + b2a_i, limb);
|
||||
b2a_i += 128;
|
||||
Leaf px{};
|
||||
Leaf py{};
|
||||
std::memcpy(&px, &ax, sizeof(px));
|
||||
std::memcpy(&py, &ay, sizeof(py));
|
||||
prod = leaf_add<Concrete>(px, py);
|
||||
}
|
||||
next[k] = leaf_add<Concrete>(lo, prod);
|
||||
}
|
||||
cand.swap(next);
|
||||
}
|
||||
return cand[0];
|
||||
}
|
||||
|
||||
template <int Me>
|
||||
std::uint8_t and_bit_share(net::trio & net, std::uint8_t x, std::uint8_t y,
|
||||
const bit_and_pad_msg & pad)
|
||||
{
|
||||
const role peer = Me == 0 ? role::p1 : role::p0;
|
||||
const bit_and_mask_msg mine{
|
||||
static_cast<std::uint8_t>(x ^ pad.a),
|
||||
static_cast<std::uint8_t>(y ^ pad.b)};
|
||||
const bit_and_mask_msg got = net.exchange_with(peer, mine, net::msg::delta);
|
||||
const std::uint8_t d = static_cast<std::uint8_t>(mine.x ^ got.x);
|
||||
const std::uint8_t e = static_cast<std::uint8_t>(mine.y ^ got.y);
|
||||
return detail::ds_bit_and_party(d, e, pad.a, pad.b, pad.c, Me == 0);
|
||||
}
|
||||
|
||||
template <int Me>
|
||||
std::uint64_t b2a_bit(net::trio & net, std::uint8_t bit, const b2a_pad_msg & pad)
|
||||
{
|
||||
const role peer = Me == 0 ? role::p1 : role::p0;
|
||||
const std::uint8_t masked = static_cast<std::uint8_t>(bit ^ pad.r);
|
||||
const std::uint8_t other = net.exchange_with(peer, masked, net::msg::delta);
|
||||
const std::uint8_t c = static_cast<std::uint8_t>(masked ^ other);
|
||||
if (c == 0)
|
||||
return pad.add;
|
||||
if constexpr (Me == 0)
|
||||
return std::uint64_t{1} - pad.add;
|
||||
else
|
||||
return std::uint64_t{0} - pad.add;
|
||||
}
|
||||
|
||||
/// @brief Additive share of `bit * word`. `word` is meaningful only for the holder.
|
||||
template <int Me>
|
||||
std::uint64_t mul_known_word(net::trio & net, bool i_hold, std::uint64_t word,
|
||||
std::uint8_t bit, const word_bit_pad * pads)
|
||||
{
|
||||
std::vector<bit_and_mask_msg> mine(64);
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
const std::uint8_t wbit = i_hold
|
||||
? static_cast<std::uint8_t>((word >> i) & 1u) : std::uint8_t{0};
|
||||
mine[static_cast<std::size_t>(i)] = bit_and_mask_msg{
|
||||
static_cast<std::uint8_t>(bit ^ pads[i].a),
|
||||
static_cast<std::uint8_t>(wbit ^ pads[i].b)};
|
||||
}
|
||||
const auto peer = exch_vec<Me>(net, mine);
|
||||
std::vector<std::uint8_t> masked(64);
|
||||
std::uint8_t prod[64]{};
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
const auto & m = mine[static_cast<std::size_t>(i)];
|
||||
const auto & g = peer[static_cast<std::size_t>(i)];
|
||||
const std::uint8_t d = static_cast<std::uint8_t>(m.x ^ g.x);
|
||||
const std::uint8_t e = static_cast<std::uint8_t>(m.y ^ g.y);
|
||||
prod[i] = detail::ds_bit_and_party(
|
||||
d, e, pads[i].a, pads[i].b, pads[i].c, Me == 0);
|
||||
masked[static_cast<std::size_t>(i)] = static_cast<std::uint8_t>(
|
||||
prod[i] ^ pads[i].r);
|
||||
}
|
||||
const auto opened = exch_vec<Me>(net, masked);
|
||||
std::uint64_t acc = 0;
|
||||
for (int i = 0; i < 64; ++i)
|
||||
{
|
||||
const std::uint8_t c = static_cast<std::uint8_t>(
|
||||
masked[static_cast<std::size_t>(i)] ^ opened[static_cast<std::size_t>(i)]);
|
||||
std::uint64_t s = pads[i].add;
|
||||
if (c != 0)
|
||||
s = Me == 0 ? (std::uint64_t{1} - pads[i].add)
|
||||
: (std::uint64_t{0} - pads[i].add);
|
||||
acc += s << i;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
/// @brief XOR-share of the AND of every bit in `bits`. `n == 0` returns 1 on p0.
|
||||
template <int Me>
|
||||
std::uint8_t and_tree(net::trio & net, const std::uint8_t * bits, std::size_t n,
|
||||
const bit_and_pad_msg * pads)
|
||||
{
|
||||
if (n == 0)
|
||||
return Me == 0 ? std::uint8_t{1} : std::uint8_t{0};
|
||||
std::uint8_t acc = bits[0];
|
||||
for (std::size_t i = 1; i < n; ++i)
|
||||
acc = and_bit_share<Me>(net, acc, bits[i], pads[i - 1]);
|
||||
return acc;
|
||||
}
|
||||
|
||||
template <int Me>
|
||||
std::uint8_t open_bit(net::trio & net, std::uint8_t mine)
|
||||
{
|
||||
const role peer = Me == 0 ? role::p1 : role::p0;
|
||||
const std::uint8_t other = net.exchange_with(peer, mine, net::msg::delta);
|
||||
return static_cast<std::uint8_t>(mine ^ other);
|
||||
}
|
||||
|
||||
#endif
|
||||
6
party/p0.cpp
Normal file
6
party/p0.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#include "cases.hpp"
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
return dpf::party::run(dpf::net::role::p0, argc, argv);
|
||||
}
|
||||
6
party/p1.cpp
Normal file
6
party/p1.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#include "cases.hpp"
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
return dpf::party::run(dpf::net::role::p1, argc, argv);
|
||||
}
|
||||
6
party/p2.cpp
Normal file
6
party/p2.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#include "cases.hpp"
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
return dpf::party::run(dpf::net::role::p2, argc, argv);
|
||||
}
|
||||
173
party/party_bench.cpp
Normal file
173
party/party_bench.cpp
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/// @file party/party_bench.cpp
|
||||
/// @brief Time specific (2+1) flows. Examples:
|
||||
/// party_bench --list
|
||||
/// party_bench --tag beaver,bench --repeat 20 --warmup 2
|
||||
/// party_bench --case beaver_dot_n32 --repeat 50 --warmup 5
|
||||
/// party_bench --tag iknp --repeat 1 --warmup 0
|
||||
|
||||
#include "cases.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "spawn.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
dpf::party::register_all_flows();
|
||||
|
||||
std::string tag;
|
||||
std::string case_name;
|
||||
std::uint64_t repeat = 10;
|
||||
std::uint64_t warmup = 2;
|
||||
bool list_only = false;
|
||||
bool metrics = true;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
std::string a = argv[i];
|
||||
auto need = [&](const char * f) {
|
||||
if (i + 1 >= argc)
|
||||
{
|
||||
std::cerr << "missing value for " << f << "\n";
|
||||
std::exit(2);
|
||||
}
|
||||
return std::string(argv[++i]);
|
||||
};
|
||||
if (a == "--list")
|
||||
list_only = true;
|
||||
else if (a == "--tag")
|
||||
tag = need("--tag");
|
||||
else if (a == "--case")
|
||||
case_name = need("--case");
|
||||
else if (a == "--repeat")
|
||||
repeat = std::stoull(need("--repeat"));
|
||||
else if (a == "--warmup")
|
||||
warmup = std::stoull(need("--warmup"));
|
||||
else if (a == "--no-metrics")
|
||||
metrics = false;
|
||||
else if (a == "--help" || a == "-h")
|
||||
{
|
||||
std::cout
|
||||
<< "party_bench --list | --case NAME | --tag TAGS\n"
|
||||
<< " [--repeat N=10] [--warmup W=2] [--no-metrics]\n"
|
||||
<< "With metrics, each role also reports bytes_sent/recv and\n"
|
||||
<< "frames_sent/recv for the flow itself (not the repeat barrier).\n"
|
||||
<< "Tags are AND-matched (comma/space). Common tags:\n"
|
||||
<< " beaver product dot scale stream horner poly mux\n"
|
||||
<< " dpf wildcard geneval ds verifiable dcf grotto extreme bench smoke\n"
|
||||
<< " arith garble yao flute shuffle (word, stack, table, hidden column)\n"
|
||||
<< " iknp (two-party; spawned as p0/p1 only)\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (list_only)
|
||||
{
|
||||
dpf::party::list_flows(std::cout, tag.empty() ? case_name : tag);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<const dpf::party::flow *> flows;
|
||||
if (!case_name.empty())
|
||||
{
|
||||
auto * f = dpf::party::find_flow(case_name);
|
||||
if (!f)
|
||||
{
|
||||
std::cerr << "unknown flow: " << case_name << "\n";
|
||||
return 2;
|
||||
}
|
||||
flows.push_back(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tag.empty())
|
||||
tag = "bench";
|
||||
flows = dpf::party::select_flows(tag);
|
||||
if (flows.empty())
|
||||
{
|
||||
std::cerr << "no flows match tag: " << tag << "\n";
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
dpf::party::spawn_opts opts;
|
||||
opts.repeat = repeat;
|
||||
opts.warmup = warmup;
|
||||
opts.metrics = metrics;
|
||||
|
||||
auto pair_flow = [](const dpf::party::flow * f) {
|
||||
return dpf::party::flow_is_pair(*f);
|
||||
};
|
||||
|
||||
auto flow_of = [](const std::string & line) {
|
||||
const auto p = line.find("flow=");
|
||||
if (p == std::string::npos)
|
||||
return std::string{};
|
||||
const auto e = line.find(' ', p + 5);
|
||||
return line.substr(p + 5, e == std::string::npos ? std::string::npos : e - (p + 5));
|
||||
};
|
||||
auto avg_ns_of = [](const std::string & line) {
|
||||
const auto p = line.find("avg_ns=");
|
||||
if (p == std::string::npos)
|
||||
return 0.0;
|
||||
return std::stod(line.substr(p + 7));
|
||||
};
|
||||
|
||||
std::cout << "flow\twall_ms\tp0\tp1\tp2\trepeat\twarmup\n";
|
||||
int fails = 0;
|
||||
bool mixed = false;
|
||||
const bool first_pair = pair_flow(flows.front());
|
||||
for (const auto * f : flows)
|
||||
mixed = mixed || (pair_flow(f) != first_pair);
|
||||
|
||||
// One mesh for a homogeneous tag. wall_ms on each row is that flow's
|
||||
// slowest role. The process startup is paid once, as suite_wall_ms.
|
||||
if (case_name.empty() && !mixed && flows.size() > 1)
|
||||
{
|
||||
opts.by_tag = true;
|
||||
auto r = first_pair ? dpf::party::spawn_pair_flow(tag, opts)
|
||||
: dpf::party::spawn_trio_flow(tag, opts);
|
||||
for (const auto * f : flows)
|
||||
{
|
||||
double slow = 0;
|
||||
std::vector<std::string> lines;
|
||||
for (const auto & line : r.metrics_lines)
|
||||
{
|
||||
if (flow_of(line) != f->name)
|
||||
continue;
|
||||
lines.push_back(line);
|
||||
slow = std::max(slow, avg_ns_of(line) / 1.0e6);
|
||||
}
|
||||
if (lines.empty())
|
||||
slow = r.wall_ms;
|
||||
std::cout << f->name << '\t' << slow << '\t'
|
||||
<< r.rc[0] << '\t' << r.rc[1] << '\t' << r.rc[2] << '\t'
|
||||
<< repeat << '\t' << warmup << '\n';
|
||||
for (const auto & line : lines)
|
||||
std::cout << " " << line << '\n';
|
||||
}
|
||||
std::cout << "suite_wall_ms\t" << r.wall_ms << '\n';
|
||||
if (r.rc[0] || r.rc[1] || r.rc[2])
|
||||
++fails;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto * f : flows)
|
||||
{
|
||||
auto r = pair_flow(f) ? dpf::party::spawn_pair_flow(f->name, opts)
|
||||
: dpf::party::spawn_trio_flow(f->name, opts);
|
||||
std::cout << f->name << '\t' << r.wall_ms << '\t'
|
||||
<< r.rc[0] << '\t' << r.rc[1] << '\t' << r.rc[2] << '\t'
|
||||
<< repeat << '\t' << warmup << '\n';
|
||||
for (const auto & line : r.metrics_lines)
|
||||
std::cout << " " << line << '\n';
|
||||
if (r.rc[0] || r.rc[1] || r.rc[2])
|
||||
++fails;
|
||||
}
|
||||
}
|
||||
return fails ? 1 : 0;
|
||||
}
|
||||
136
party/registry.cpp
Normal file
136
party/registry.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/// @file party/registry.cpp
|
||||
#include "registry.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
std::vector<flow> & table()
|
||||
{
|
||||
static std::vector<flow> t;
|
||||
return t;
|
||||
}
|
||||
|
||||
std::vector<std::string> split_csv(std::string_view s)
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
std::string cur;
|
||||
for (char c : s)
|
||||
{
|
||||
if (c == ',' || c == ' ')
|
||||
{
|
||||
if (!cur.empty())
|
||||
{
|
||||
out.push_back(cur);
|
||||
cur.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
cur.push_back(c);
|
||||
}
|
||||
if (!cur.empty())
|
||||
out.push_back(cur);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_flow(flow f)
|
||||
{
|
||||
if (f.name == nullptr || f.run == nullptr)
|
||||
throw std::invalid_argument("register_flow: null name or run");
|
||||
for (const auto & e : table())
|
||||
{
|
||||
if (e.name == f.name || std::string_view(e.name) == f.name)
|
||||
throw std::invalid_argument(std::string("duplicate flow: ") + f.name);
|
||||
}
|
||||
table().push_back(f);
|
||||
}
|
||||
|
||||
bool flow_has_tag(const flow & f, std::string_view tag)
|
||||
{
|
||||
if (tag.empty())
|
||||
return true;
|
||||
if (f.tags == nullptr)
|
||||
return false;
|
||||
std::string_view tags(f.tags);
|
||||
std::size_t i = 0;
|
||||
while (i < tags.size())
|
||||
{
|
||||
while (i < tags.size() && tags[i] == ' ')
|
||||
++i;
|
||||
std::size_t j = i;
|
||||
while (j < tags.size() && tags[j] != ' ')
|
||||
++j;
|
||||
if (j > i && tags.substr(i, j - i) == tag)
|
||||
return true;
|
||||
i = j;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flow_is_pair(const flow & f)
|
||||
{
|
||||
const std::string_view name = f.name ? f.name : "";
|
||||
if (name.size() >= 5 && name.substr(0, 5) == "iknp_")
|
||||
return true;
|
||||
return flow_has_tag(f, "iknp");
|
||||
}
|
||||
|
||||
const flow * find_flow(std::string_view name)
|
||||
{
|
||||
for (const auto & f : table())
|
||||
{
|
||||
if (f.name == name)
|
||||
return &f;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const flow *> select_flows(std::string_view filter)
|
||||
{
|
||||
std::vector<const flow *> out;
|
||||
auto want = split_csv(filter);
|
||||
for (const auto & f : table())
|
||||
{
|
||||
bool ok = true;
|
||||
for (const auto & t : want)
|
||||
{
|
||||
if (!flow_has_tag(f, t))
|
||||
{
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok)
|
||||
out.push_back(&f);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void list_flows(std::ostream & os, std::string_view filter)
|
||||
{
|
||||
auto sel = select_flows(filter);
|
||||
for (const flow * f : sel)
|
||||
{
|
||||
os << f->name << '\t' << (f->tags ? f->tags : "")
|
||||
<< (f->bench ? "\tbench" : "\tsmoke") << '\n';
|
||||
}
|
||||
os << "# " << sel.size() << " flows\n";
|
||||
}
|
||||
|
||||
std::size_t flow_count()
|
||||
{
|
||||
return table().size();
|
||||
}
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
51
party/registry.hpp
Normal file
51
party/registry.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/// @file party/registry.hpp
|
||||
/// @brief Named (2+1) party flows with tags for filtering and benchmarking.
|
||||
#ifndef LIBDPF_PARTY_REGISTRY_HPP__
|
||||
#define LIBDPF_PARTY_REGISTRY_HPP__
|
||||
|
||||
#include <cstddef>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "dpf/net/trio.hpp"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
|
||||
using flow_fn = int (*)(net::role self, net::trio & net);
|
||||
|
||||
/// @brief One runnable flow. `tags` is a space-separated tag list.
|
||||
struct flow
|
||||
{
|
||||
const char * name = nullptr;
|
||||
const char * tags = nullptr;
|
||||
flow_fn run = nullptr;
|
||||
/// @brief When false, skip under `--tag bench` (reject / handshake-only).
|
||||
bool bench = true;
|
||||
};
|
||||
|
||||
void register_flow(flow f);
|
||||
void register_all_flows();
|
||||
|
||||
const flow * find_flow(std::string_view name);
|
||||
bool flow_has_tag(const flow & f, std::string_view tag);
|
||||
|
||||
/// @brief Two-party flow: spawn p0 and p1 only. IKNP has no dealer.
|
||||
bool flow_is_pair(const flow & f);
|
||||
|
||||
/// @brief All flows, or those matching every comma-separated tag in `filter`.
|
||||
/// Empty filter returns every registered flow.
|
||||
std::vector<const flow *> select_flows(std::string_view filter);
|
||||
|
||||
void list_flows(std::ostream & os, std::string_view filter = {});
|
||||
|
||||
std::size_t flow_count();
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
|
||||
#endif // LIBDPF_PARTY_REGISTRY_HPP__
|
||||
1518
party/run.cpp
Normal file
1518
party/run.cpp
Normal file
File diff suppressed because it is too large
Load diff
219
party/spawn.hpp
Normal file
219
party/spawn.hpp
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
/// @file party/spawn.hpp
|
||||
/// @brief Spawn p0/p1/p2 for one flow (used by gtest and party_bench).
|
||||
#ifndef LIBDPF_PARTY_SPAWN_HPP__
|
||||
#define LIBDPF_PARTY_SPAWN_HPP__
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#ifndef LIBDPF_PARTY_BIN_DIR
|
||||
#define LIBDPF_PARTY_BIN_DIR "."
|
||||
#endif
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace party
|
||||
{
|
||||
|
||||
inline std::string party_bin(const char * name)
|
||||
{
|
||||
const char * env = std::getenv("LIBDPF_PARTY_BIN_DIR");
|
||||
std::string dir = env ? env : LIBDPF_PARTY_BIN_DIR;
|
||||
return dir + "/" + name;
|
||||
}
|
||||
|
||||
struct spawn_result
|
||||
{
|
||||
int rc[3] = {0, 0, 0};
|
||||
double wall_ms = 0;
|
||||
/// @brief Per-role metric lines (from --metrics), ordered p0/p1/p2.
|
||||
std::vector<std::string> metrics_lines;
|
||||
};
|
||||
|
||||
struct spawn_opts
|
||||
{
|
||||
std::uint64_t repeat = 1;
|
||||
std::uint64_t warmup = 0;
|
||||
bool metrics = false;
|
||||
/// @brief Pass `case_name` as `--tag` so one mesh runs every match.
|
||||
bool by_tag = false;
|
||||
};
|
||||
|
||||
inline spawn_result spawn_trio_flow(const std::string & case_name,
|
||||
const spawn_opts & opts = {})
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
auto dir = fs::temp_directory_path()
|
||||
/ ("libdpf_party_" + std::to_string(::getpid()) + "_" + case_name);
|
||||
fs::create_directories(dir);
|
||||
|
||||
std::string rep = std::to_string(opts.repeat);
|
||||
std::string warm = std::to_string(opts.warmup);
|
||||
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
pid_t kids[3];
|
||||
const char * bins[3] = {"p0", "p1", "p2"};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
kids[i] = fork();
|
||||
if (kids[i] < 0)
|
||||
{
|
||||
fs::remove_all(dir);
|
||||
throw std::runtime_error("fork failed");
|
||||
}
|
||||
if (kids[i] == 0)
|
||||
{
|
||||
auto bin = party_bin(bins[i]);
|
||||
const char * sel = opts.by_tag ? "--tag" : "--case";
|
||||
if (opts.metrics)
|
||||
{
|
||||
execl(bin.c_str(), bin.c_str(),
|
||||
"--dir", dir.string().c_str(),
|
||||
sel, case_name.c_str(),
|
||||
"--repeat", rep.c_str(),
|
||||
"--warmup", warm.c_str(),
|
||||
"--metrics",
|
||||
static_cast<char *>(nullptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
execl(bin.c_str(), bin.c_str(),
|
||||
"--dir", dir.string().c_str(),
|
||||
sel, case_name.c_str(),
|
||||
"--repeat", rep.c_str(),
|
||||
"--warmup", warm.c_str(),
|
||||
static_cast<char *>(nullptr));
|
||||
}
|
||||
_exit(126);
|
||||
}
|
||||
}
|
||||
|
||||
spawn_result out;
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
int status = 0;
|
||||
if (waitpid(kids[i], &status, 0) < 0)
|
||||
out.rc[i] = 125;
|
||||
else
|
||||
out.rc[i] = WIFEXITED(status) ? WEXITSTATUS(status) : 124;
|
||||
}
|
||||
const auto t1 = std::chrono::steady_clock::now();
|
||||
out.wall_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
|
||||
|
||||
if (opts.metrics)
|
||||
{
|
||||
const char * roles[3] = {"p0", "p1", "p2"};
|
||||
for (const char * role : roles)
|
||||
{
|
||||
auto path = dir / (std::string("metrics.") + role);
|
||||
std::ifstream in(path);
|
||||
std::string line;
|
||||
while (std::getline(in, line))
|
||||
{
|
||||
if (!line.empty())
|
||||
out.metrics_lines.push_back(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::remove_all(dir);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// @brief Spawn only p0 and p1 (no dealer). For IKNP / two-party flows.
|
||||
inline spawn_result spawn_pair_flow(const std::string & case_name,
|
||||
const spawn_opts & opts = {})
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
auto dir = fs::temp_directory_path()
|
||||
/ ("libdpf_pair_" + std::to_string(::getpid()) + "_" + case_name);
|
||||
fs::create_directories(dir);
|
||||
|
||||
std::string rep = std::to_string(opts.repeat);
|
||||
std::string warm = std::to_string(opts.warmup);
|
||||
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
pid_t kids[2];
|
||||
const char * bins[2] = {"p0", "p1"};
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
kids[i] = fork();
|
||||
if (kids[i] < 0)
|
||||
{
|
||||
fs::remove_all(dir);
|
||||
throw std::runtime_error("fork failed");
|
||||
}
|
||||
if (kids[i] == 0)
|
||||
{
|
||||
auto bin = party_bin(bins[i]);
|
||||
const char * sel = opts.by_tag ? "--tag" : "--case";
|
||||
if (opts.metrics)
|
||||
{
|
||||
execl(bin.c_str(), bin.c_str(),
|
||||
"--dir", dir.string().c_str(),
|
||||
sel, case_name.c_str(),
|
||||
"--repeat", rep.c_str(),
|
||||
"--warmup", warm.c_str(),
|
||||
"--metrics",
|
||||
"--pair",
|
||||
static_cast<char *>(nullptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
execl(bin.c_str(), bin.c_str(),
|
||||
"--dir", dir.string().c_str(),
|
||||
sel, case_name.c_str(),
|
||||
"--repeat", rep.c_str(),
|
||||
"--warmup", warm.c_str(),
|
||||
"--pair",
|
||||
static_cast<char *>(nullptr));
|
||||
}
|
||||
_exit(126);
|
||||
}
|
||||
}
|
||||
|
||||
spawn_result out;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
int status = 0;
|
||||
if (waitpid(kids[i], &status, 0) < 0)
|
||||
out.rc[i] = 125;
|
||||
else
|
||||
out.rc[i] = WIFEXITED(status) ? WEXITSTATUS(status) : 124;
|
||||
}
|
||||
out.rc[2] = 0;
|
||||
const auto t1 = std::chrono::steady_clock::now();
|
||||
out.wall_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
|
||||
|
||||
if (opts.metrics)
|
||||
{
|
||||
const char * roles[2] = {"p0", "p1"};
|
||||
for (const char * role : roles)
|
||||
{
|
||||
auto path = dir / (std::string("metrics.") + role);
|
||||
std::ifstream in(path);
|
||||
std::string line;
|
||||
while (std::getline(in, line))
|
||||
{
|
||||
if (!line.empty())
|
||||
out.metrics_lines.push_back(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs::remove_all(dir);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace party
|
||||
} // namespace dpf
|
||||
|
||||
#endif // LIBDPF_PARTY_SPAWN_HPP__
|
||||
Loading…
Add table
Add a link
Reference in a new issue