/// @file dpf/bench_cells.hpp /// @brief Bench cells for work that is not a DPF walk. /// @details A secret index stays a key. These cells time what happens after /// the parties already hold shares: a constant-round word gadget, a /// stacked branch of a leaf netlist, a short public table, and a /// hidden reorder of an RSS column. Each cell is a compose plan. The /// harness drives it with `run_parties`, so the payload crosses the /// same transport as a DPF plan (`DPF_TRANSPORT`). /// /// Two-party cells put the garble or the table on party 0 and the /// second evaluation on party 1, with one peer payload between them. /// The shuffle is three ring passes. The left-out party's slot is /// empty; the other two carry that pass's array. #ifndef LIBDPF_INCLUDE_DPF_BENCH_CELLS_HPP__ #define LIBDPF_INCLUDE_DPF_BENCH_CELLS_HPP__ #include #include #include #include #include #include #include #include #include "dpf/arith_garble.hpp" #include "dpf/compose.hpp" #include "dpf/flute.hpp" #include "dpf/shuffle.hpp" #include "dpf/yao.hpp" #include "dpf/yao_stack.hpp" namespace dpf { namespace bench { /// @brief One measured configuration. The low 16 bits ride in the node aux. enum class kind : std::uint32_t { proj5 = 1, proj17, proj64, mul5, mul7, mul11, thresh8, thresh16, chain7, yao_if4, yao_if16, yao_hot4, yao_hot8, flute2, flute4, flute8, flute4x8, shuf16, shuf64, shuf256 }; inline constexpr std::uint32_t aux_pass(kind k, unsigned pass) noexcept { return static_cast(k) | (static_cast(pass) << 16); } inline kind kind_of(std::uint32_t aux) noexcept { return static_cast(aux & 0xffffu); } inline unsigned pass_of(std::uint32_t aux) noexcept { return aux >> 16; } inline bool is_shuffle(kind k) noexcept { return k == kind::shuf16 || k == kind::shuf64 || k == kind::shuf256; } inline std::size_t shuffle_n(kind k) { if (k == kind::shuf16) return 16; if (k == kind::shuf64) return 64; if (k == kind::shuf256) return 256; throw std::invalid_argument("bench: shuffle cell"); } namespace detail { inline dpf::yao::netlist and_n(unsigned n) { dpf::yao::netlist nl; std::vector in; 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; } inline dpf::yao::netlist xor2() { dpf::yao::netlist nl; auto a = nl.shared_in(); auto b = nl.shared_in(); nl.out(nl.xor_(a, b)); return nl; } inline std::uint64_t mix_bytes(const std::uint8_t * p, std::size_t n) { std::uint64_t h = 14695981039346656037ull; for (std::size_t i = 0; i < n; ++i) { h ^= p[i]; h *= 1099511628211ull; } return h; } inline void paint(std::uint8_t * out, std::size_t n, std::uint64_t mix) { for (std::size_t i = 0; i < n; i += 8) { const std::uint64_t w = mix + static_cast(i); const std::size_t k = std::min(8, n - i); std::memcpy(out + i, &w, k); } } inline std::size_t proj_bytes(std::uint16_t mod) { arith_garble::circuit c; auto x = c.input(mod); std::vector id(mod); for (std::uint16_t i = 0; i < mod; ++i) id[i] = i; c.out(c.project(x, mod, std::move(id))); const std::uint16_t in = 1; return std::max( 32, dpf::arith_garble::eval_pair(c, &in).ciphertext_rows * 32u); } inline std::size_t mul_bytes(std::uint16_t p) { 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] = {1, 1}; return std::max( 32, dpf::arith_garble::eval_pair(c, in).ciphertext_rows * 32u); } inline std::size_t thresh_bytes(unsigned b) { arith_garble::circuit c; std::vector bits; const auto mod = static_cast(b + 1); for (unsigned i = 0; i < b; ++i) bits.push_back(c.input(mod)); c.out(c.threshold(bits, static_cast(b))); std::vector in(b, 1); return std::max( 32, dpf::arith_garble::eval_pair(c, in.data()).ciphertext_rows * 32u); } inline std::size_t chain_bytes() { 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}; return std::max( 32, dpf::arith_garble::eval_pair(c, in).ciphertext_rows * 32u); } inline std::uint64_t run_proj(std::uint16_t mod) { arith_garble::circuit c; auto x = c.input(mod); std::vector id(mod); for (std::uint16_t i = 0; i < mod; ++i) id[i] = static_cast((i * 3) % mod); c.out(c.project(x, mod, std::move(id))); const std::uint16_t in = static_cast(mod / 2); auto got = dpf::arith_garble::eval_pair(c, &in); return got.opened.empty() ? 0 : got.opened[0]; } inline std::uint64_t run_mul(std::uint16_t p) { 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(p - 1), 2}; auto got = dpf::arith_garble::eval_pair(c, in); return got.opened.empty() ? 0 : got.opened[0]; } inline std::uint64_t run_thresh(unsigned b) { arith_garble::circuit c; std::vector bits; const auto mod = static_cast(b + 1); for (unsigned i = 0; i < b; ++i) bits.push_back(c.input(mod)); c.out(c.threshold(bits, static_cast(b / 2))); std::vector in(b, 0); for (unsigned i = 0; i < b; i += 2) in[i] = 1; auto got = dpf::arith_garble::eval_pair(c, in.data()); return got.opened.empty() ? 0 : got.opened[0]; } inline std::uint64_t run_chain() { 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}; auto got = dpf::arith_garble::eval_pair(c, in); return got.opened.empty() ? 0 : got.opened[0]; } inline std::size_t yao_if_bytes(unsigned heavy, unsigned light) { std::vector h(heavy, 1), l(light, 1); auto got = dpf::yao::eval_if(and_n(heavy), and_n(light), 0, 0, h.data(), h.data(), l.data(), l.data()); return std::max( 16, (got.stack_blocks + got.extra_blocks) * 16u); } inline std::uint64_t run_if(unsigned heavy, unsigned light) { std::vector h0(heavy, 1), h1(heavy, 0), l0(light, 1), l1(light, 1); auto got = dpf::yao::eval_if(and_n(heavy), and_n(light), 1, 0, h0.data(), h1.data(), l0.data(), l1.data()); return got.share0.empty() ? 0 : static_cast(got.share0[0] ^ got.share1[0]); } inline std::size_t yao_hot_bytes(unsigned k) { std::vector br; std::vector> p0, p1; for (unsigned i = 0; i < k; ++i) { br.push_back(i % 2 == 0 ? and_n(2) : xor2()); p0.push_back({1, 1}); p1.push_back({0, 0}); } auto got = dpf::yao::eval_one_hot(br, 1, 0, p0, p1); return std::max( 16, (got.stack_blocks + got.extra_blocks) * 16u); } inline std::uint64_t run_hot(unsigned k) { std::vector br; std::vector> p0, p1; for (unsigned i = 0; i < k; ++i) { br.push_back(i % 2 == 0 ? and_n(2) : xor2()); p0.push_back({1, static_cast(i & 1u)}); p1.push_back({0, 1}); } auto got = dpf::yao::eval_one_hot(br, 1, 2, p0, p1); return got.share0.empty() ? 0 : static_cast(got.share0[0] ^ got.share1[0]); } inline std::uint64_t run_flute(unsigned delta, unsigned n_out) { const unsigned rows = 1u << delta; std::vector columns(static_cast(n_out) * rows); for (unsigned w = 0; w < n_out; ++w) for (unsigned j = 0; j < rows; ++j) columns[static_cast(w) * rows + j] = static_cast(((j >> (w % delta)) ^ w) & 1u); std::vector bits(delta, 0); bits[0] = 1; if (delta > 2) bits[2] = 1; auto got = dpf::flute::eval_pair(delta, n_out, columns.data(), bits.data()); std::uint64_t mix = 0; for (auto b : got.opened) mix = (mix << 1) | b; return mix; } inline rss::seed_bundle fixed_bundle() { rss::seed_bundle b{}; auto fill = [](rss::seed_block & s, std::uint8_t tag) { auto * p = reinterpret_cast(&s); for (std::size_t i = 0; i < sizeof(s); ++i) p[i] = static_cast(tag + i * 17u); }; fill(b.k01, 1); fill(b.k12, 2); fill(b.k20, 3); return b; } inline void shuffle_outbound(unsigned me, unsigned pass, std::size_t n, std::uint8_t * out, std::size_t out_n) { if (out_n != n * sizeof(std::uint64_t)) throw std::logic_error("bench shuffle slot"); const unsigned order[3] = {2u, 0u, 1u}; const unsigned left = order[pass]; if (me == left) { std::memset(out, 0, out_n); return; } const auto bundle = fixed_bundle(); std::vector column(n); std::iota(column.begin(), column.end(), 0); shuffle::shuffle_party_view held[3]; for (unsigned p = 0; p < 3; ++p) { held[p].own.assign(n, 0); held[p].next.assign(n, 0); } std::uint64_t rng = 0xA5A5A5A5A5A5A5A5ull; auto draw = [&] { rng = rng * 6364136223846793005ull + 1u; return rng; }; for (std::size_t i = 0; i < n; ++i) { const auto a = draw(); const auto b = draw(); const auto c = column[i] - a - b; held[0].own[i] = a; held[0].next[i] = b; held[1].own[i] = b; held[1].next[i] = c; held[2].own[i] = c; held[2].next[i] = a; } for (unsigned step = 0; step <= pass; ++step) { const unsigned L = order[step]; const unsigned u = shuffle::hidden_u_party(L); const unsigned side = shuffle::hidden_side_party(L); auto u_step = shuffle::shuffle_hidden_pass( u, rss::party_seeds::from_bundle(bundle, u), held[u], 0, L, nullptr); auto s_step = shuffle::shuffle_hidden_pass(side, rss::party_seeds::from_bundle(bundle, side), held[side], 0, L, &u_step.out.data); std::vector side_msg = s_step.out.data; auto l_step = shuffle::shuffle_hidden_pass(L, rss::party_seeds::from_bundle(bundle, L), held[L], 0, L, &side_msg); if (step == pass) { const auto & msg = (me == u) ? u_step.out.data : s_step.out.data; std::memcpy(out, msg.data(), out_n); } held[u] = std::move(u_step.view); held[side] = std::move(s_step.view); held[L] = std::move(l_step.view); } } inline std::uint64_t heavy(kind k) { switch (k) { case kind::proj5: return run_proj(5); case kind::proj17: return run_proj(17); case kind::proj64: return run_proj(64); case kind::mul5: return run_mul(5); case kind::mul7: return run_mul(7); case kind::mul11: return run_mul(11); case kind::thresh8: return run_thresh(8); case kind::thresh16: return run_thresh(16); case kind::chain7: return run_chain(); case kind::yao_if4: return run_if(4, 2); case kind::yao_if16: return run_if(16, 8); case kind::yao_hot4: return run_hot(4); case kind::yao_hot8: return run_hot(8); case kind::flute2: return run_flute(2, 1); case kind::flute4: return run_flute(4, 1); case kind::flute8: return run_flute(8, 1); case kind::flute4x8: return run_flute(4, 8); default: return 0; } } inline std::size_t payload_of(kind k) { switch (k) { case kind::proj5: return proj_bytes(5); case kind::proj17: return proj_bytes(17); case kind::proj64: return proj_bytes(64); case kind::mul5: return mul_bytes(5); case kind::mul7: return mul_bytes(7); case kind::mul11: return mul_bytes(11); case kind::thresh8: return thresh_bytes(8); case kind::thresh16: return thresh_bytes(16); case kind::chain7: return chain_bytes(); case kind::yao_if4: return yao_if_bytes(4, 2); case kind::yao_if16: return yao_if_bytes(16, 8); case kind::yao_hot4: return yao_hot_bytes(4); case kind::yao_hot8: return yao_hot_bytes(8); case kind::flute2: case kind::flute4: case kind::flute8: return 8; case kind::flute4x8: return 16; case kind::shuf16: return 16 * sizeof(std::uint64_t); case kind::shuf64: return 64 * sizeof(std::uint64_t); case kind::shuf256: return 256 * sizeof(std::uint64_t); } throw std::invalid_argument("bench: cell"); } inline protocol::plan peer_plan(std::size_t, kind k) { protocol::composer c(0); auto done = c.bench_peer(static_cast(k), payload_of(k)); (void)done; return c.default_plan(); } inline protocol::plan ring_plan(std::size_t, kind k) { protocol::composer c(0); auto done = c.bench_ring(static_cast(k), payload_of(k)); (void)done; return c.default_plan(); } } // namespace detail struct cell { const char * name; int parties; kind id; }; /// @brief The battery. Secret indexes stay on the DPF plans beside these. inline std::vector battery() { return { {"arith_proj_m5", 2, kind::proj5}, {"arith_proj_m17", 2, kind::proj17}, {"arith_proj_m64", 2, kind::proj64}, {"arith_mul_p5", 2, kind::mul5}, {"arith_mul_p7", 2, kind::mul7}, {"arith_mul_p11", 2, kind::mul11}, {"arith_thresh_b8", 2, kind::thresh8}, {"arith_thresh_b16", 2, kind::thresh16}, {"arith_chain_mul4", 2, kind::chain7}, {"yao_if_4_2", 2, kind::yao_if4}, {"yao_if_16_8", 2, kind::yao_if16}, {"yao_onehot_k4", 2, kind::yao_hot4}, {"yao_onehot_k8", 2, kind::yao_hot8}, {"flute_d2", 2, kind::flute2}, {"flute_d4", 2, kind::flute4}, {"flute_d8", 2, kind::flute8}, {"flute_d4_o8", 2, kind::flute4x8}, {"shuffle_n16", 3, kind::shuf16}, {"shuffle_n64", 3, kind::shuf64}, {"shuffle_n256", 3, kind::shuf256}, }; } inline protocol::plan plan_for(std::size_t party, kind id) { if (is_shuffle(id)) return detail::ring_plan(party, id); return detail::peer_plan(party, id); } /// @brief Local half of a cell. Party 0 emits the payload. Party 1 applies it. /// Every shuffle party emits its own ring slot. inline void run_cell(std::uint32_t opcode, std::size_t party, std::uint32_t aux, std::uint8_t * out, std::size_t out_n) { if (out == nullptr && out_n != 0) throw std::invalid_argument("bench cell buffer"); const auto k = kind_of(aux); if (is_shuffle(k)) { if (opcode != protocol::opcodes::bench_emit) return; detail::shuffle_outbound(static_cast(party), pass_of(aux), shuffle_n(k), out, out_n); return; } if (opcode == protocol::opcodes::bench_apply) { if (party != 1) { if (out_n != 0) out[0] = 0; return; } const auto mix = detail::heavy(k); if (out_n >= sizeof(mix)) std::memcpy(out, &mix, sizeof(mix)); return; } if (party != 0) { if (out_n != 0) std::memset(out, 0, out_n); return; } detail::paint(out, out_n, detail::heavy(k)); } } // namespace bench } // namespace dpf #endif