libdpf/party/iknp_deal.hpp
Ryan Henry 0d22946a0e 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>
2026-09-28 05:59:19 -06:00

847 lines
31 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// @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__