Record Grotto half-ulp tables and comparison geneval, and factor shared beaver terms before the quotient.
Horner and window evaluation need those tables in the tree. Comparison geneval opens the same value words as a Doerner–Shelat key. A factor common to every polynomial term is multiplied first so that preprocessing stays smaller. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3f10e05176
commit
875f09fec1
14 changed files with 42668 additions and 184 deletions
|
|
@ -14,9 +14,12 @@
|
|||
/// 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. `sgn * (x*y + pow(x, 2))`
|
||||
/// is the sign-corrected form and reuses those powers. A product that
|
||||
/// uses an output of an earlier polynomial is a later round.
|
||||
/// inside `x² y`, or in a second polynomial. 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,
|
||||
/// is applied after the quotient when that uses fewer preprocessing
|
||||
/// values. A lone secret summand is added from its value share.
|
||||
///
|
||||
/// Doerner–Shelat's per-level AND is a `bit_mul` of a fresh bit and
|
||||
/// a fresh block. A wildcard leaf is a `scale` of one scalar by each
|
||||
|
|
@ -720,9 +723,23 @@ public:
|
|||
}
|
||||
else if (g.kind == gate_kind::poly)
|
||||
{
|
||||
for (const auto & term : g.terms)
|
||||
for (auto id : term.factors)
|
||||
for (const auto & step : g.steps)
|
||||
{
|
||||
if (step.value_wire >= 0)
|
||||
{
|
||||
const auto id = static_cast<std::uint32_t>(step.value_wire);
|
||||
if (!wires_[id].value_ready)
|
||||
throw std::logic_error("beaver wire is not ready to open");
|
||||
continue;
|
||||
}
|
||||
for (auto [id, exp] : step.delta)
|
||||
{
|
||||
(void)exp;
|
||||
ensure_delta(id);
|
||||
}
|
||||
if (step.mask_wire >= 0)
|
||||
ensure_delta(static_cast<std::uint32_t>(step.mask_wire));
|
||||
}
|
||||
val = eval_poly(g);
|
||||
}
|
||||
else
|
||||
|
|
@ -858,6 +875,7 @@ private:
|
|||
Ring scale{};
|
||||
int bundle = -1;
|
||||
int mask_wire = -1;
|
||||
int value_wire = -1;
|
||||
bool public_only = false;
|
||||
};
|
||||
|
||||
|
|
@ -1176,67 +1194,108 @@ private:
|
|||
for (std::uint32_t id = 0; id < wires_.size(); ++id)
|
||||
already[id] = needs_blind(id) ? 1 : 0;
|
||||
auto saved = bundles_;
|
||||
std::vector<std::vector<poly_step>> compiled;
|
||||
compiled.reserve(pieces.size());
|
||||
for (const auto & piece : pieces)
|
||||
(void)compile_poly(piece);
|
||||
std::size_t added = bundles_.size() - saved.size();
|
||||
bundles_ = std::move(saved);
|
||||
compiled.push_back(compile_poly(piece));
|
||||
const auto bundle_base = saved.size();
|
||||
std::size_t added = bundles_.size() - bundle_base;
|
||||
std::map<std::uint32_t, char> blinds;
|
||||
for (const auto & piece : pieces)
|
||||
auto note = [&](std::uint32_t id) {
|
||||
if (id < already.size() && already[id] != 0)
|
||||
return;
|
||||
blinds[id] = 1;
|
||||
};
|
||||
for (std::size_t i = bundle_base; i < bundles_.size(); ++i)
|
||||
{
|
||||
for (const auto & term : piece)
|
||||
for (const auto & part : bundles_[i].parts)
|
||||
{
|
||||
for (auto id : term.factors)
|
||||
for (auto [wid, exp] : part.lam)
|
||||
{
|
||||
if (id >= already.size() || already[id] == 0)
|
||||
blinds[id] = 1;
|
||||
(void)exp;
|
||||
note(wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto & steps : compiled)
|
||||
{
|
||||
for (const auto & step : steps)
|
||||
{
|
||||
if (step.mask_wire >= 0)
|
||||
note(static_cast<std::uint32_t>(step.mask_wire));
|
||||
for (auto [wid, exp] : step.delta)
|
||||
{
|
||||
(void)exp;
|
||||
note(wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
bundles_ = std::move(saved);
|
||||
return added + blinds.size();
|
||||
}
|
||||
|
||||
static std::vector<poly_term> drop_one(std::vector<poly_term> terms, std::uint32_t wire_id)
|
||||
{
|
||||
for (auto & term : terms)
|
||||
{
|
||||
auto it = std::find(term.factors.begin(), term.factors.end(), wire_id);
|
||||
if (it != term.factors.end())
|
||||
term.factors.erase(it);
|
||||
}
|
||||
return terms;
|
||||
}
|
||||
|
||||
static bool wire_in_every(const std::vector<poly_term> & terms, std::uint32_t wire_id)
|
||||
{
|
||||
for (const auto & term : terms)
|
||||
{
|
||||
if (factor_exp(term, wire_id) == 0)
|
||||
return false;
|
||||
}
|
||||
return !terms.empty();
|
||||
}
|
||||
|
||||
wire schedule_terms(std::vector<poly_term> terms)
|
||||
{
|
||||
if (terms.size() < 2)
|
||||
return emit_terms(std::move(terms));
|
||||
|
||||
std::vector<std::vector<poly_term>> best{terms};
|
||||
std::size_t best_cost = estimate_pieces(best);
|
||||
|
||||
std::map<std::uint32_t, char> seen;
|
||||
for (const auto & term : terms)
|
||||
for (auto id : term.factors)
|
||||
seen[id] = 1;
|
||||
for (auto [wire_id, _] : seen)
|
||||
{
|
||||
bool common = true;
|
||||
for (const auto & term : terms)
|
||||
{
|
||||
if (factor_exp(term, wire_id) == 0)
|
||||
common = false;
|
||||
}
|
||||
if (!common)
|
||||
continue;
|
||||
std::vector<poly_term> quot = terms;
|
||||
for (auto & term : quot)
|
||||
{
|
||||
auto it = std::find(term.factors.begin(), term.factors.end(), wire_id);
|
||||
if (it != term.factors.end())
|
||||
term.factors.erase(it);
|
||||
}
|
||||
const std::uint32_t mid = static_cast<std::uint32_t>(wires_.size());
|
||||
poly_term mul;
|
||||
mul.coeff = traits::one();
|
||||
mul.factors = {mid, wire_id};
|
||||
std::vector<std::vector<poly_term>> seq{std::move(quot), {std::move(mul)}};
|
||||
std::size_t cost = estimate_pieces(seq);
|
||||
auto consider = [&](std::vector<std::vector<poly_term>> seq) {
|
||||
const std::size_t cost = estimate_pieces(seq);
|
||||
if (cost < best_cost)
|
||||
{
|
||||
best = std::move(seq);
|
||||
best_cost = cost;
|
||||
}
|
||||
};
|
||||
|
||||
std::map<std::uint32_t, char> seen;
|
||||
for (const auto & term : terms)
|
||||
for (auto id : term.factors)
|
||||
seen[id] = 1;
|
||||
|
||||
std::vector<std::uint32_t> common;
|
||||
for (auto [wire_id, present] : seen)
|
||||
{
|
||||
(void)present;
|
||||
if (wire_in_every(terms, wire_id))
|
||||
common.push_back(wire_id);
|
||||
}
|
||||
const auto fresh = static_cast<std::uint32_t>(wires_.size());
|
||||
for (auto wire_id : common)
|
||||
{
|
||||
poly_term mul;
|
||||
mul.coeff = traits::one();
|
||||
mul.factors = {fresh, wire_id};
|
||||
consider({drop_one(terms, wire_id), {std::move(mul)}});
|
||||
}
|
||||
|
||||
std::map<std::vector<std::uint8_t>, std::vector<std::uint32_t>> clusters;
|
||||
for (auto [wire_id, _] : seen)
|
||||
for (auto [wire_id, present] : seen)
|
||||
{
|
||||
(void)present;
|
||||
std::vector<std::uint8_t> shape;
|
||||
shape.reserve(terms.size());
|
||||
bool any = false;
|
||||
|
|
@ -1247,13 +1306,13 @@ private:
|
|||
any = any || e != 0;
|
||||
}
|
||||
if (any)
|
||||
clusters[shape].push_back(wire_id);
|
||||
clusters[std::move(shape)].push_back(wire_id);
|
||||
}
|
||||
for (auto & [shape, group] : clusters)
|
||||
{
|
||||
(void)shape;
|
||||
if (group.size() < 2)
|
||||
continue;
|
||||
const std::uint32_t mid = static_cast<std::uint32_t>(wires_.size());
|
||||
poly_term prod;
|
||||
prod.coeff = traits::one();
|
||||
prod.factors = group;
|
||||
|
|
@ -1270,26 +1329,28 @@ private:
|
|||
next.factors.push_back(f);
|
||||
}
|
||||
for (std::uint8_t i = 0; i < e; ++i)
|
||||
next.factors.push_back(mid);
|
||||
next.factors.push_back(fresh);
|
||||
rewritten.push_back(std::move(next));
|
||||
}
|
||||
std::vector<std::vector<poly_term>> seq{{std::move(prod)}, std::move(rewritten)};
|
||||
std::size_t cost = estimate_pieces(seq);
|
||||
if (cost < best_cost)
|
||||
consider({{prod}, rewritten});
|
||||
|
||||
std::map<std::uint32_t, char> rewritten_seen;
|
||||
for (const auto & term : rewritten)
|
||||
for (auto id : term.factors)
|
||||
rewritten_seen[id] = 1;
|
||||
const auto later = fresh + 1;
|
||||
for (auto [wire_id, present] : rewritten_seen)
|
||||
{
|
||||
best = std::move(seq);
|
||||
best_cost = cost;
|
||||
(void)present;
|
||||
if (!wire_in_every(rewritten, wire_id))
|
||||
continue;
|
||||
poly_term mul;
|
||||
mul.coeff = traits::one();
|
||||
mul.factors = {later, wire_id};
|
||||
consider({{prod}, drop_one(rewritten, wire_id), {std::move(mul)}});
|
||||
}
|
||||
}
|
||||
|
||||
if (best.size() != 1 && terms.size() == 1 && terms[0].factors.size() <= 4)
|
||||
{
|
||||
std::fprintf(stderr, "split factors=%zu pieces=%zu cost=%zu flat_factors=",
|
||||
terms[0].factors.size(), best.size(), best_cost);
|
||||
for (auto f : terms[0].factors)
|
||||
std::fprintf(stderr, "%u ", f);
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
wire last{};
|
||||
for (auto & piece : best)
|
||||
last = emit_terms(std::move(piece));
|
||||
|
|
@ -1352,10 +1413,7 @@ private:
|
|||
if (b.parts[0].lam == key)
|
||||
return b.share;
|
||||
}
|
||||
throw std::logic_error(
|
||||
"beaver monomial was not prepared (key " + std::to_string(key.size())
|
||||
+ " bundles " + std::to_string(bundles_.size())
|
||||
+ " gates " + std::to_string(gates_.size()) + ")");
|
||||
throw std::logic_error("beaver monomial was not prepared");
|
||||
}
|
||||
|
||||
unsigned exponent_of(const exp_list & key, std::uint32_t id) const
|
||||
|
|
@ -1512,8 +1570,17 @@ private:
|
|||
std::map<exp_list, Ring> lams;
|
||||
};
|
||||
std::map<exp_list, bucket> buckets;
|
||||
std::vector<poly_step> steps;
|
||||
for (const auto & term : terms)
|
||||
{
|
||||
if (term.factors.size() == 1)
|
||||
{
|
||||
poly_step step;
|
||||
step.scale = term.coeff;
|
||||
step.value_wire = static_cast<int>(term.factors[0]);
|
||||
steps.push_back(std::move(step));
|
||||
continue;
|
||||
}
|
||||
auto groups = group_exponents(term.factors);
|
||||
(void)expansion_size(groups);
|
||||
for_each_term(groups, [&](const exp_list & key) {
|
||||
|
|
@ -1544,7 +1611,6 @@ private:
|
|||
});
|
||||
}
|
||||
|
||||
std::vector<poly_step> steps;
|
||||
for (auto & [delta, slot] : buckets)
|
||||
{
|
||||
if (!(slot.pub == traits::zero()))
|
||||
|
|
@ -1627,6 +1693,13 @@ private:
|
|||
Ring s1 = traits::zero();
|
||||
for (const auto & step : g.steps)
|
||||
{
|
||||
if (step.value_wire >= 0)
|
||||
{
|
||||
const auto & val = wires_[static_cast<std::size_t>(step.value_wire)].value;
|
||||
s0 = traits::add(s0, traits::mul(step.scale, val.p0));
|
||||
s1 = traits::add(s1, traits::mul(step.scale, val.p1));
|
||||
continue;
|
||||
}
|
||||
Ring pub = pow_delta(step.delta);
|
||||
if (step.public_only)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue