/// @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 #include #include #include #include #include #include #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 bytes; template void put(net::msg tag, const T & value) { static_assert(std::is_trivially_copyable_v); append(tag, &value, sizeof(T)); } template void put_vec(net::msg tag, const T * data, std::size_t n) { static_assert(std::is_trivially_copyable_v); 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(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(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 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 & 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(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(); 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 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 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 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 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 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 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 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 void exchange_wildcard(trio & net, int me, dist::wildcard_leaf_pad_msg & pad) { const role peer = me == 0 ? role::p1 : role::p0; Concrete mine_out = dpf::uniform_sample(); Leaf mine_vec = dist::random_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(); const Leaf neg = dpf::subtract_leaf(Leaf{}, zero); net.send_to(peer, net::msg::ring_vector, neg); } else { zero = net.recv_from(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 void prepare_point(trio & net, role self) { const int me = me_of(self); using dpf_type = utils::dpf_type_t; using tree = dpf::tree_traits; 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::is_wildcard_v || ObliviousHash); demand d; if constexpr (Additive) d.bits += utils::bitlength_of_v - 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; for (std::size_t i = 0; i + 1 < nbits; ++i) out.put(net::msg::beaver_tape, cur.take_bit()); } emit_point(out, cur, me, depth, tree::is_half_tree, ObliviousHash, leaf_b2a); if constexpr (dpf::is_wildcard_v) { using concrete = dpf::concrete_type_t; using leaf_type = dpf::leaf_node_t; dist::wildcard_leaf_pad_msg pad{}; exchange_wildcard(net, me, pad); out.put(net::msg::beaver_tape, pad); } install(net, self, out, cur); } template 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; using key_type = typename pair_type::first_type::key_type; using tree = dpf::tree_traits; (void)std::initializer_list{((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; 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 void prepare_ic(trio & net, role self, const ic_pack & spec) { const int me = me_of(self); using input_type = std::decay_t; const auto inner = detail::ic_impl::inner_lt(spec); const std::uint64_t gmask = detail::ic_impl::group_mask_of(); constexpr std::size_t nbits = utils::bitlength_of_v; using pair_type = dist::comparison_pair_t; using key_type = typename pair_type::first_type::key_type; using tree = dpf::tree_traits; 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 gamma(gamma_n), corr(corr_n); for (auto & p : gamma) p = cur.take_bit(); for (auto & p : corr) p = cur.take_bit(); std::vector 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 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; using dpf_type = utils::dpf_type_t; constexpr bool wild_lane = RevealPoint && dpf::is_wildcard_v && 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( net, self); else iknp_deal::prepare_point( net, self); if (additive) { if (self == role::p0) x0 = dist::additive_to_xor_share(net, x0); else x1 = dist::additive_to_xor_share(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(net, x0, beta, already_encoded); std::forward(on0)(result.dpf_key); return std::optional(finish(std::move(result))); } auto result = dist::point_party(net, x1, beta, already_encoded); std::forward(on1)(result.dpf_key); return std::optional(finish(std::move(result))); } else { if (self == role::p0) std::forward(on0)(dist::point_party( net, x0, beta, already_encoded)); else std::forward(on1)(dist::point_party( net, x1, beta, already_encoded)); } } /// @brief Two-party extractable point keygen. template 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, "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( net, self); if (self == role::p0) { on0(dist::point_party(net, x0, beta)); } else { on1(dist::point_party(net, x1, beta)); } } /// @brief Two-party comparison keygen. template 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, suppressed_point> { if (self == role::p2) throw std::invalid_argument("iknp comparison keygen has no dealer"); iknp_deal::prepare_comparison( net, self, spec, tags...); if constexpr (Reveal) { if (self == role::p0) { auto result = dist::comparison_party(net, x0, spec, tags...); std::forward(on0)(result.dpf_key); return result.opened_point; } auto result = dist::comparison_party(net, x1, spec, tags...); std::forward(on1)(result.dpf_key); return result.opened_point; } else { if (self == role::p0) { std::forward(on0)(dist::comparison_party(net, x0, spec, tags...)); } else { std::forward(on1)(dist::comparison_party(net, x1, spec, tags...)); } return {}; } } /// @brief Two-party interval keygen. template auto dist_with_ic_key_iknp(trio & net, role self, InputT r0, InputT r1, const ic_pack & spec, Fn0 && on0, Fn1 && on1) -> std::conditional_t>, suppressed_point> { using input_type = std::decay_t; using out_beta = concrete_type_t; detail::ic_impl::check_input(); detail::ic_impl::check_bounds(spec); if constexpr (detail::cmp_group_info::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(); const std::uint64_t nmask = detail::ic_impl::input_mask_of(); iknp_deal::prepare_ic( 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) { 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(role::p2, net::msg::beaver_tape); const auto z1 = net.recv_from(role::p2, net::msg::beaver_tape); const std::uint64_t side = self == role::p1 ? 1ULL : 0ULL; const std::uint64_t dshare = is_wildcard_v ? 0ULL : (z0.share + side * delta) & gmask; const std::uint64_t cshare = is_wildcard_v ? 0ULL : (z1.share + side * absorb) & gmask; const std::uint64_t dcoeff = is_wildcard_v ? (z0.share + side) & gmask : 0ULL; const std::uint64_t ccoeff = is_wildcard_v ? (z1.share + side * cr) & gmask : 0ULL; return std::array{ dshare, cshare, dcoeff, ccoeff}; }; if (self == role::p0) { auto inner = dist::comparison_party(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(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 ? 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 ? cr_share : 0ULL; const auto recv_delta = [&]() { const auto z0 = net.recv_from(role::p2, net::msg::beaver_tape); const std::uint64_t side = self == role::p1 ? 1ULL : 0ULL; const std::uint64_t dshare = is_wildcard_v ? 0ULL : (z0.share + side * delta) & gmask; const std::uint64_t dcoeff = is_wildcard_v ? (z0.share + side) & gmask : 0ULL; return std::array{dshare, dcoeff}; }; if (self == role::p0) { auto inner = dist::comparison_party(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(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__