/// @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 std::vector exch_vec(net::trio & net, const std::vector & 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(net::msg::delta); } else { auto got = net.to(peer).template recv_vec(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 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(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(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 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 masked(128); for (int i = 0; i < 128; ++i) { const std::uint8_t bit = static_cast( (bytes[i / 8] >> (i % 8)) & 1u); masked[static_cast(i)] = static_cast( bit ^ pads[i].r); } const auto peer = exch_vec(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( masked[static_cast(i)] ^ peer[static_cast(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(i) / limb_bits; const unsigned shift = static_cast( static_cast(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 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(mine, theirs); } /// @brief Public leaf CW from a shared naked leaf and this party's mask. template Leaf cw_from_selected(net::trio & net, Leaf selected, Leaf mask, bool sign) { constexpr bool char2 = utils::has_characteristic_two_v; Leaf share{}; if constexpr (char2) { share = leaf_add(selected, mask); } else if (sign) { if constexpr (Me == 0) share = leaf_add(selected, mask); else share = dpf::subtract_leaf(selected, mask); } else if constexpr (Me == 0) { const Leaf neg = dpf::subtract_leaf(Leaf{}, selected); share = dpf::subtract_leaf(neg, mask); } else { share = dpf::subtract_leaf(mask, selected); } return open_leaf_sum(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 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; 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 cand(std::size_t{1} << lg); for (std::size_t i = 0; i < cand.size(); ++i) cand[i] = make(static_cast(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 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(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(net, zx, b2a + b2a_i, limb); b2a_i += 128; const simde__m128i ay = b2a_leaf(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(px, py); } next[k] = leaf_add(lo, prod); } cand.swap(next); } return cand[0]; } template 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(x ^ pad.a), static_cast(y ^ pad.b)}; const bit_and_mask_msg got = net.exchange_with(peer, mine, net::msg::delta); const std::uint8_t d = static_cast(mine.x ^ got.x); const std::uint8_t e = static_cast(mine.y ^ got.y); return detail::ds_bit_and_party(d, e, pad.a, pad.b, pad.c, Me == 0); } template 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(bit ^ pad.r); const std::uint8_t other = net.exchange_with(peer, masked, net::msg::delta); const std::uint8_t c = static_cast(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 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 mine(64); for (int i = 0; i < 64; ++i) { const std::uint8_t wbit = i_hold ? static_cast((word >> i) & 1u) : std::uint8_t{0}; mine[static_cast(i)] = bit_and_mask_msg{ static_cast(bit ^ pads[i].a), static_cast(wbit ^ pads[i].b)}; } const auto peer = exch_vec(net, mine); std::vector masked(64); std::uint8_t prod[64]{}; for (int i = 0; i < 64; ++i) { const auto & m = mine[static_cast(i)]; const auto & g = peer[static_cast(i)]; const std::uint8_t d = static_cast(m.x ^ g.x); const std::uint8_t e = static_cast(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(i)] = static_cast( prod[i] ^ pads[i].r); } const auto opened = exch_vec(net, masked); std::uint64_t acc = 0; for (int i = 0; i < 64; ++i) { const std::uint8_t c = static_cast( masked[static_cast(i)] ^ opened[static_cast(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 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(net, acc, bits[i], pads[i - 1]); return acc; } template 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(mine ^ other); } #endif