Document the new DPF surfaces in one command set, and test the field, half-tree, and multipoint edges.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 23:18:10 -06:00
parent 0d8a5a8131
commit 0dff6df8ed
250 changed files with 12199 additions and 1981 deletions

View file

@ -14,7 +14,9 @@
/// A polynomial is a sum of monomials in several wires.
/// `2 + 3*x + 4*y + 5*x*y + 6*pow(x, 2) + pow(x, 2)*y + x*y*z`
/// is one round. `λ_x²` is stored once whether it appears as `x²`,
/// inside `x² y`, or in a second polynomial. Wires that occur with the
/// inside `x² y`, or in a second polynomial. An inner product is that
/// sum: `dot({x0,x1}, {y0,y1})` is `x0*y0 + x1*y1`, and the pair
/// products share one preprocessing value. Wires that occur with the
/// same exponents in every term, as in `a3*(x*z)^3 + a2*(x*z)^2 + a1*(x*z) + a0`,
/// are multiplied first and the univariate polynomial is a later round.
/// A factor shared by every term, such as a sign or a piecewise scale,
@ -70,8 +72,9 @@ namespace dpf
namespace beavers
{
/// Ring operations used to build and consume triples.
/// Specialize for a ring whose multiplicative identity is not `Ring{1}`.
/// @brief Ring operations used to build and consume triples.
/// @details Specialize for a ring whose multiplicative identity is not `Ring{1}`.
/// @tparam Ring payload ring
template <typename Ring>
struct ring_traits
{
@ -90,7 +93,8 @@ struct ring_traits
static Ring sample() { return dpf::uniform_sample<Ring>(); }
};
/// Bitwise AND uses the all-ones word as its multiplicative identity.
/// @brief Bitwise AND uses the all-ones word as its multiplicative identity.
/// @tparam T value type
template <typename T>
struct ring_traits<dpf::xor_wrapper<T>>
{
@ -121,7 +125,8 @@ struct default_sampler
Ring operator()() const { return ring_traits<Ring>::sample(); }
};
/// Additive (2,2) split. `open()` is `p0 + p1`.
/// @brief Additive (2,2) split. `open()` is `p0 + p1`.
/// @tparam Ring payload ring
template <typename Ring>
struct split
{
@ -157,9 +162,10 @@ struct split
template <typename Ring>
class session;
/// A value in a session. Copying a wire copies its id; it does not copy the
/// @brief A value in a session. Copying a wire copies its id; it does not copy the
/// blind. The ring argument is on the type so `a * x * x` can build an
/// expression without naming the session.
/// @tparam Ring payload ring
template <typename Ring>
class wire
{
@ -192,15 +198,16 @@ private:
std::uint32_t id_ = 0;
};
/// Unevaluated sum of monomials. `*` distributes over `+`. A public
/// @brief Unevaluated sum of monomials. `*` distributes over `+`. A public
/// coefficient scales a term. Nothing is sampled until `session::operator()`.
/// @tparam Ring payload ring
template <typename Ring>
struct expr
{
struct term
{
Ring coeff{};
/// Positive exponents, sorted by wire id.
/// @brief Positive exponents, sorted by wire id.
std::vector<std::pair<std::uint32_t, std::uint8_t>> powers;
};
@ -226,10 +233,20 @@ expr<Ring> wire_expr(wire<Ring> w);
template <typename Ring>
expr<Ring> horner_expr(wire<Ring> x, std::initializer_list<Ring> coeffs);
/// One PRG lane per blind role, plus the share-mask stream for that role.
/// `blind(role, index)` and `share(role, index, value)` do not depend on
namespace detail
{
template <typename Ring, typename ContX, typename ContY>
expr<Ring> dot_expr(const ContX & xs, const ContY & ys);
} // namespace detail
/// @brief One PRG lane per blind role, plus the share-mask stream for that role.
/// @details `blind(role, index)` and `share(role, index, value)` do not depend on
/// call order. Walking `index` forward stays inside a refilled window.
/// `PRG` defaults to `dpf::prg::aes128`.
/// @tparam Ring payload ring
/// @tparam PRG pseudorandom generator
template <typename Ring, typename PRG = dpf::prg::aes128>
class oracle
{
@ -241,7 +258,7 @@ public:
using seed_type = typename PRG::block_type;
using traits = ring_traits<Ring>;
/// Monomial roles sit above wire ids. Dot-cross roles sit above those.
/// @brief Monomial roles sit above wire ids. Dot-cross roles sit above those.
static constexpr std::uint32_t mono_role_base = 0x40000000u;
static constexpr std::uint32_t dot_role_base = 0x80000000u;
@ -260,7 +277,7 @@ public:
return dot_role_base + gate;
}
/// Fused within-polynomial λ combinations (Appendix E groupings).
/// @brief Fused within-polynomial λ combinations (Appendix E groupings).
static constexpr std::uint32_t bundle_role_base = 0xC0000000u;
HEDLEY_NO_THROW
@ -290,7 +307,11 @@ public:
return lanes_.mask_at(role, index);
}
/// Additive split of `value`. The mask is the role's mask stream at `index`.
/// @brief Additive split of `value`. The mask is the role's mask stream at `index`.
/// @param role the `role`
/// @param index the index
/// @param value the value to convert or store
/// @return Additive split of `value`
split<Ring> share(std::uint32_t role, std::uint64_t index, const Ring & value) const
{
Ring p0 = mask(role, index);
@ -311,20 +332,22 @@ private:
dpf::randomness::lane_table<Ring, PRG> lanes_;
};
/// Shares produced for one copy index of a recorded formula.
/// @brief Shares produced for one copy index of a recorded formula.
/// @tparam Ring payload ring
template <typename Ring>
struct prg_material
{
std::vector<split<Ring>> lambda;
std::vector<split<Ring>> monomial;
/// Fused λ-combinations for polynomial gates, in bundle index order.
/// @brief Fused λ-combinations for polynomial gates, in bundle index order.
std::vector<split<Ring>> bundles;
/// Parallel to the session's gates. Empty split when the gate is not a dot.
/// @brief Parallel to the session's gates. Empty split when the gate is not a dot.
std::vector<split<Ring>> dot_cross;
};
/// Dealer session: record formulae, `sample` blinds and monomials, `bind`
/// @brief Dealer session: record formulae, `sample` blinds and monomials, `bind`
/// input secrets, `evaluate` every round.
/// @tparam Ring payload ring
template <typename Ring>
class session
{
@ -338,7 +361,7 @@ public:
using exp_list = std::vector<std::pair<std::uint32_t, std::uint8_t>>;
using wire = ::dpf::beavers::wire<Ring>;
/// One factor of a monomial query: `{{x, 2}, {a, 1}}`.
/// @brief One factor of a monomial query: `{{x, 2}, {a, 1}}`.
struct power
{
wire base{};
@ -351,29 +374,34 @@ public:
session(session &&) = delete;
session & operator=(session &&) = delete;
/// Arithmetic input. Its blind is sampled once and then reused.
/// @brief Arithmetic input. Its blind is sampled once and then reused.
/// @return Arithmetic input
HEDLEY_WARN_UNUSED_RESULT
wire input()
{
return emplace_wire(0, true, false);
}
/// Sample this wire's blind even if no recorded formula opens it.
/// One-shot triples use this for the product wire, so it can be reused.
/// @brief Sample this wire's blind even if no recorded formula opens it.
/// @details One-shot triples use this for the product wire, so it can be reused.
/// @param w the `w`
void pin(wire w)
{
wires_[check(w)].pinned = true;
}
/// 0/1 wire in this ring. `bind` accepts only `zero()` or `one()`
/// @brief 0/1 wire in this ring. `bind` accepts only `zero()` or `one()`
/// (`1` for integer rings, the all-ones word for `xor_wrapper`).
/// @return 0/1 wire in this ring
HEDLEY_WARN_UNUSED_RESULT
wire bit()
{
return emplace_wire(0, true, true);
}
/// One-round product. Repeated wires share a blind.
/// @brief One-round product. Repeated wires share a blind.
/// @param factors the `factors`
/// @return One-round product
HEDLEY_WARN_UNUSED_RESULT
wire product(std::initializer_list<wire> factors)
{
@ -396,7 +424,10 @@ public:
return commit_product({check(a), check(b), check(c)});
}
/// Record a sum of monomials. Like terms share one blind product.
/// @brief Record a sum of monomials. Like terms share one blind product.
/// @param e the `e`
/// @return Record a sum of monomials
/// @throws std::invalid_argument if `beaver expression is from a different session`
HEDLEY_WARN_UNUSED_RESULT
wire operator()(const expr<Ring> & e)
{
@ -405,15 +436,23 @@ public:
return commit_poly(e);
}
/// `c[0] + c[1] x + c[2] x^2 + ...` in one round.
/// @brief `c[0] + c[1] x + c[2] x^2 + ...` in one round.
/// @param x the `x`
/// @param coeffs the public coefficients
/// @return `c[0] + c[1] x + c[2] x^2 + ...` in one round
HEDLEY_WARN_UNUSED_RESULT
wire horner(wire x, std::initializer_list<Ring> coeffs)
{
return (*this)(horner_expr(x, coeffs));
}
/// Sign-corrected Horner: `sign * (c[0] + c[1] x + ...)`, still one round
/// @brief Sign-corrected Horner: `sign * (c[0] + c[1] x + ...)`, still one round
/// when `sign` and `x` are inputs.
/// @param sign the sign bit or sign value
/// @param x the `x`
/// @param coeffs the public coefficients
/// @return Sign-corrected Horner: `sign * (c[0] + c[1] x + ...)`, still one round when `sign`
/// and `x` are inputs
HEDLEY_WARN_UNUSED_RESULT
wire horner(wire sign, wire x, std::initializer_list<Ring> coeffs)
{
@ -426,7 +465,10 @@ public:
return product(x, x);
}
/// One-round `a * x * x` (one blind for `x`).
/// @brief One-round `a * x * x` (one blind for `x`).
/// @param a the `a`
/// @param x the `x`
/// @return One-round `a * x * x` (one blind for `x`)
HEDLEY_WARN_UNUSED_RESULT
wire mul_square(wire a, wire x)
{
@ -437,31 +479,21 @@ public:
HEDLEY_WARN_UNUSED_RESULT
wire dot(const ContX & xs, const ContY & ys)
{
std::vector<std::uint32_t> x;
std::vector<std::uint32_t> y;
for (const auto & w : xs)
x.push_back(check(w));
for (const auto & w : ys)
y.push_back(check(w));
return commit_dot(std::move(x), std::move(y));
return finish_dot(detail::dot_expr<Ring>(xs, ys));
}
HEDLEY_WARN_UNUSED_RESULT
wire dot(std::initializer_list<wire> xs, std::initializer_list<wire> ys)
{
std::vector<std::uint32_t> x;
std::vector<std::uint32_t> y;
x.reserve(xs.size());
y.reserve(ys.size());
for (auto w : xs)
x.push_back(check(w));
for (auto w : ys)
y.push_back(check(w));
return commit_dot(std::move(x), std::move(y));
return finish_dot(detail::dot_expr<Ring>(xs, ys));
}
/// `z_i = scalar * lanes[i]`, one output wire per lane. The scalar blind
/// @brief `z_i = scalar * lanes[i]`, one output wire per lane. The scalar blind
/// is shared. Each lane gets its own `λ_s λ_i` share.
/// @tparam Cont cont
/// @param scalar the `scalar`
/// @param lanes the lane values
/// @return `z_i = scalar * lanes[i]`, one output wire per lane
template <typename Cont>
HEDLEY_WARN_UNUSED_RESULT
std::vector<wire> scale(wire scalar, const Cont & lanes)
@ -482,7 +514,11 @@ public:
return out;
}
/// `bit * scalar`. `bit` must come from `bit()`.
/// @brief `bit * scalar`. `bit` must come from `bit()`.
/// @param selector the `selector`
/// @param scalar the `scalar`
/// @return `bit * scalar`
/// @throws std::invalid_argument if `bit_mul selector must come from bit()`
HEDLEY_WARN_UNUSED_RESULT
wire bit_mul(wire selector, wire scalar)
{
@ -492,7 +528,11 @@ public:
return commit_product({id, check(scalar)});
}
/// One-round `selector ? when1 : when0`, i.e. `when0 + selector * (when1 - when0)`.
/// @brief One-round `selector ? when1 : when0`, i.e. `when0 + selector * (when1 - when0)`.
/// @param selector the `selector`
/// @param when1 the `when1`
/// @param when0 the `when0`
/// @return One-round `selector ? when1 : when0`, i.e
HEDLEY_WARN_UNUSED_RESULT
wire mux(wire selector, wire when1, wire when0)
{
@ -515,8 +555,11 @@ public:
return out;
}
/// Sample every missing wire blind and every missing monomial.
/// Blinds already sampled are left alone.
/// @brief Sample every missing wire blind and every missing monomial.
/// @details Blinds already sampled are left alone.
/// @tparam Sample sample
/// @param sampler the randomness sampler
/// @throws std::logic_error if `beaver blind is missing`
template <typename Sample>
void sample(Sample && sampler)
{
@ -572,9 +615,12 @@ public:
sample(default_sampler<Ring>{});
}
/// Install missing blinds and product shares from copy `index` of `src`.
/// Already-sampled wires keep their λ. New product shares are built from
/// @brief Install missing blinds and product shares from copy `index` of `src`.
/// @details Already-sampled wires keep their λ. New product shares are built from
/// those stored blinds, then split with the oracle's share lane.
/// @tparam PRG pseudorandom generator
/// @param src the source
/// @param index the index
template <typename PRG>
void sample_from(const oracle<Ring, PRG> & src, std::uint64_t index = 0)
{
@ -614,9 +660,13 @@ public:
}
}
/// Every wire, monomial, and dot cross of this formula at copy `index`.
/// Does not change the session. Copies are independent lanes samples, so
/// @brief Every wire, monomial, and dot cross of this formula at copy `index`.
/// @details Does not change the session. Copies are independent lanes samples, so
/// `material_at(src, 5)` does not depend on having asked for 0..4.
/// @tparam PRG pseudorandom generator
/// @param src the source
/// @param index the index
/// @return Every wire, monomial, and dot cross of this formula at copy `index`
template <typename PRG>
prg_material<Ring> material_at(const oracle<Ring, PRG> & src, std::uint64_t index) const
{
@ -665,7 +715,13 @@ public:
return out;
}
/// Split `secret` into fresh additive shares and bind them to an input.
/// @brief Split `secret` into fresh additive shares and bind them to an input.
/// @tparam Sample sample
/// @param w the `w`
/// @param secret the secret value
/// @param sampler the randomness sampler
/// @throws std::invalid_argument if `only input wires can be bound`
/// @throws std::logic_error if `input wire is already bound`
template <typename Sample>
void bind(wire w, Ring secret, Sample && sampler)
{
@ -686,7 +742,12 @@ public:
bind(w, secret, default_sampler<Ring>{});
}
/// Bind shares the caller already holds. Their sum is the secret.
/// @brief Bind shares the caller already holds. Their sum is the secret.
/// @param w the `w`
/// @param p0 the `p0`
/// @param p1 the `p1`
/// @throws std::invalid_argument if `only input wires can be bound`
/// @throws std::logic_error if `input wire is already bound`
void bind_shares(wire w, Ring p0, Ring p1)
{
auto id = check(w);
@ -700,9 +761,10 @@ public:
wires_[id].value_ready = true;
}
/// Open every ready round. Inputs used by round-1 gates are opened
/// @brief Open every ready round. Inputs used by round-1 gates are opened
/// together; a gate output is a later round's input and keeps the blind
/// chosen in `sample`.
/// @throws std::logic_error if `beaver wire is not ready to open`
void evaluate()
{
int max_round = 0;
@ -774,7 +836,10 @@ public:
return wires_[id].lambda;
}
/// Share of `Π λ_i^{e_i}`. A lone `λ_w` is the wire blind itself.
/// @brief Share of `Π λ_i^{e_i}`. A lone `λ_w` is the wire blind itself.
/// @param spec the specification
/// @return Share of `Π λ_i^{e_i}`
/// @throws std::invalid_argument if `beaver power is zero`
split<Ring> monomial(const std::vector<power> & spec) const
{
std::vector<std::pair<std::uint32_t, unsigned>> raw;
@ -807,7 +872,10 @@ public:
return traits::add(v.p0, v.p1);
}
/// Public ABY2.0 mask δ = x + λ, after `evaluate` has opened the wire.
/// @brief Public ABY2.0 mask δ = x + λ, after `evaluate` has opened the wire.
/// @param w the `w`
/// @return Public ABY2.0 mask δ = x + λ, after `evaluate` has opened the wire
/// @throws std::logic_error if `beaver wire has not been opened`
Ring delta(wire w) const
{
auto id = check(w);
@ -819,12 +887,35 @@ public:
split<Ring> dot_cross(wire w) const
{
auto id = check(w);
int g = wires_[id].gate;
if (g < 0 || gates_[static_cast<std::size_t>(g)].kind != gate_kind::dot)
if (!wires_[id].dot_output)
throw std::invalid_argument("wire is not a dot output");
if (!gates_[static_cast<std::size_t>(g)].cross_ready)
int g = wires_[id].gate;
if (g < 0)
throw std::invalid_argument("wire is not a dot output");
const auto & gate = gates_[static_cast<std::size_t>(g)];
if (gate.kind == gate_kind::dot)
{
if (!gate.cross_ready)
throw std::logic_error("call sample() before reading a dot cross term");
return gate.cross;
}
Ring s0 = traits::zero();
Ring s1 = traits::zero();
bool found = false;
for (const auto & step : gate.steps)
{
if (step.bundle < 0 || !step.delta.empty())
continue;
const auto & bundle = bundles_[static_cast<std::size_t>(step.bundle)];
if (!bundle.ready)
throw std::logic_error("call sample() before reading a dot cross term");
s0 = traits::add(s0, traits::mul(step.scale, bundle.share.p0));
s1 = traits::add(s1, traits::mul(step.scale, bundle.share.p1));
found = true;
}
if (!found)
throw std::logic_error("call sample() before reading a dot cross term");
return gates_[static_cast<std::size_t>(g)].cross;
return split<Ring>{s0, s1};
}
int round_of(wire w) const
@ -835,16 +926,19 @@ public:
HEDLEY_NO_THROW
std::size_t wire_count() const noexcept { return wires_.size(); }
/// Product shares beyond the per-wire blinds: subset monomials from
/// @brief Product shares beyond the per-wire blinds: subset monomials from
/// `product` gates, plus one fused bundle per public-δ class in a
/// polynomial (Appendix E). A lone mask is not counted.
/// @return Product shares beyond the per-wire blinds: subset monomials from `product` gates,
/// plus one fused bundle per public-δ class in a polynomial (Appendix E)
HEDLEY_NO_THROW
std::size_t monomial_count() const noexcept
{
return monos_.size() + bundles_.size();
}
/// Wire blinds that the recorded formulae actually open, plus product shares.
/// @brief Wire blinds that the recorded formulae actually open, plus product shares.
/// @return Wire blinds that the recorded formulae actually open, plus product shares
std::size_t preprocessing_count() const
{
std::size_t n = monomial_count();
@ -865,7 +959,7 @@ private:
std::vector<std::uint32_t> factors;
};
/// One λ-monomial in a fused preprocessing share.
/// @brief One λ-monomial in a fused preprocessing share.
struct bundle_part
{
Ring coeff{};
@ -879,7 +973,7 @@ private:
bool ready = false;
};
/// Online: `public(δ) * scale * share`, where share is a wire mask,
/// @brief Online: `public(δ) * scale * share`, where share is a wire mask,
/// a raw monomial, or a fused sum of monomials.
struct poly_step
{
@ -896,6 +990,7 @@ private:
bool is_input = false;
bool is_bit = false;
bool pinned = false;
bool dot_output = false;
bool lambda_ready = false;
bool value_ready = false;
bool delta_ready = false;
@ -1369,25 +1464,10 @@ private:
return last;
}
wire commit_dot(std::vector<std::uint32_t> xs, std::vector<std::uint32_t> ys)
wire finish_dot(const expr<Ring> & e)
{
if (xs.empty() || xs.size() != ys.size())
throw std::invalid_argument(
"beaver dot operands must have the same non-zero length");
int round = 1;
for (std::size_t i = 0; i < xs.size(); ++i)
{
round = std::max(round, wires_[xs[i]].ready_round + 1);
round = std::max(round, wires_[ys[i]].ready_round + 1);
}
auto out = emplace_wire(round, false, false);
gate g;
g.kind = gate_kind::dot;
g.out = out.id_;
g.lhs = std::move(xs);
g.rhs = std::move(ys);
gates_.push_back(std::move(g));
wires_[out.id_].gate = static_cast<int>(gates_.size() - 1);
auto out = (*this)(e);
wires_[out.id_].dot_output = true;
return out;
}
@ -1572,8 +1652,10 @@ private:
return false;
}
/// Group λ-monomials that share a public δ monomial into one share.
/// A bucket that is only `c · λ_i` reuses the wire blind.
/// @brief Group λ-monomials that share a public δ monomial into one share.
/// @details A bucket that is only `c · λ_i` reuses the wire blind.
/// @param terms the polynomial terms
/// @return Group λ-monomials that share a public δ monomial into one share
std::vector<poly_step> compile_poly(const std::vector<poly_term> & terms)
{
struct bucket
@ -1665,9 +1747,20 @@ private:
steps.push_back(std::move(step));
continue;
}
Ring scale = parts[0].coeff;
for (const auto & part : parts)
{
if (!(part.coeff == scale))
scale = traits::one();
}
if (!(scale == traits::one()))
{
for (auto & part : parts)
part.coeff = traits::one();
}
poly_step step;
step.delta = delta;
step.scale = traits::one();
step.scale = scale;
step.bundle = require_bundle(std::move(parts));
steps.push_back(std::move(step));
}
@ -1852,8 +1945,40 @@ Ring coeff_of(Coeff value)
return Ring{value};
}
template <typename Ring, typename ContX, typename ContY>
expr<Ring> dot_expr(const ContX & xs, const ContY & ys)
{
std::vector<wire<Ring>> x;
std::vector<wire<Ring>> y;
for (const auto & w : xs)
x.push_back(w);
for (const auto & w : ys)
y.push_back(w);
if (x.empty() || x.size() != y.size())
throw std::invalid_argument(
"beaver dot operands must have the same non-zero length");
expr<Ring> acc = wire_expr(x[0]) * wire_expr(y[0]);
for (std::size_t i = 1; i < x.size(); ++i)
acc = add_exprs(std::move(acc), wire_expr(x[i]) * wire_expr(y[i]));
return acc;
}
} // namespace detail
template <typename Ring>
HEDLEY_WARN_UNUSED_RESULT
expr<Ring> dot(std::initializer_list<wire<Ring>> xs, std::initializer_list<wire<Ring>> ys)
{
return detail::dot_expr<Ring>(xs, ys);
}
template <typename Ring>
HEDLEY_WARN_UNUSED_RESULT
expr<Ring> dot(const std::vector<wire<Ring>> & xs, const std::vector<wire<Ring>> & ys)
{
return detail::dot_expr<Ring>(xs, ys);
}
template <typename Ring>
HEDLEY_WARN_UNUSED_RESULT
expr<Ring> wire_expr(wire<Ring> w)
@ -1895,7 +2020,13 @@ expr<Ring> horner_expr(wire<Ring> x, std::initializer_list<Ring> coeffs)
return e;
}
/// `coeff * v0 * v1 * ...`, with repeated wires counting as a power.
/// @brief `coeff * v0 * v1 * ...`, with repeated wires counting as a power.
/// @tparam Ring payload ring
/// @tparam Wires wires
/// @param coeff the public coefficient
/// @param first the first element of the range
/// @param rest the remaining arguments
/// @return `coeff * v0 * v1 * ...`, with repeated wires counting as a power
template <typename Ring, typename... Wires>
HEDLEY_WARN_UNUSED_RESULT
expr<Ring> monomial(Ring coeff, wire<Ring> first, Wires... rest)
@ -2128,8 +2259,10 @@ expr<Ring> operator+(expr<Ring> e, Coeff coeff)
// `out` is the ABY2.0 blind of the product wire, for a later round.
// ---------------------------------------------------------------------------
/// `subset[mask - 1]` is `Π λ_i` over the bits set in `mask` (bit i selects
/// @brief `subset[mask - 1]` is `Π λ_i` over the bits set in `mask` (bit i selects
/// `in[i]`). Singleton masks are the wire blinds themselves.
/// @tparam Arity arity
/// @tparam Ring payload ring
template <std::size_t Arity, typename Ring>
struct fresh_beaver
{
@ -2259,9 +2392,14 @@ struct mux_beaver
split<Ring> out{};
};
/// One fresh Beaver pair from copy `index` of an oracle.
/// Roles match a session that records `input, input, product`: wires 0 and 1,
/// @brief One fresh Beaver pair from copy `index` of an oracle.
/// @details Roles match a session that records `input, input, product`: wires 0 and 1,
/// the product wire, and monomial 0.
/// @tparam Ring payload ring
/// @tparam PRG pseudorandom generator
/// @param src the source
/// @param index the index
/// @return One fresh Beaver pair from copy `index` of an oracle
template <typename Ring, typename PRG = dpf::prg::aes128>
HEDLEY_WARN_UNUSED_RESULT
beaver2<Ring> beaver2_at(const oracle<Ring, PRG> & src, std::uint64_t index)
@ -2278,7 +2416,14 @@ beaver2<Ring> beaver2_at(const oracle<Ring, PRG> & src, std::uint64_t index)
src.share(oracle<Ring>::wire_role(2), index, out)};
}
/// `n` copies starting at `begin`. Each role is one contiguous lane read.
/// @brief `n` copies starting at `begin`. Each role is one contiguous lane read.
/// @tparam Ring payload ring
/// @tparam PRG pseudorandom generator
/// @param src the source
/// @param begin the iterator to the first query
/// @param out the output buffer
/// @param n the `n`
/// @throws std::invalid_argument if `beaver2 output is null`
template <typename Ring, typename PRG = dpf::prg::aes128>
void fill_beaver2(const oracle<Ring, PRG> & src, std::uint64_t begin,
beaver2<Ring> * out, std::size_t n)