Skip to content

server: verify the round's openings as random linear combinations - #15

Draft
noot wants to merge 4 commits into
noot/fix-profile-harnessfrom
noot/perf-server-batch-verify
Draft

noot wants to merge 4 commits into
noot/fix-profile-harnessfrom
noot/perf-server-batch-verify

Conversation

@noot

@noot noot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Every equation in the fresh opening check is linear in the opening (BDLOP relation, digit decomposition, leaf hash, Merkle path), so the server now checks 9 random weighted sums of all openings in the inbox instead of each one individually. Per client it still runs only the cheap shape and norm gate. A bad opening survives one combination with probability at most 2^-15, so 9 combinations leave 2^-135, past the 128-bit target; if the batch fails, the server falls back to per-client verify_fresh to name the culprit.

Server round time, single thread, 16 servers, mse payload:

clients before after
100 46.1 ms 15.1 ms
300 137.9 ms 37.0 ms

Needs a security read as well as a perf one: the combination count is BATCH_COMBOS in server.rs, weights come from a ChaCha generator seeded from the OS. The weight range [1, 2^15) is BATCH_WEIGHT_BOUND in cs.rs, with a compile-time assertion that it sits below both q_cs and q_hvc, which the soundness argument needs.

Stacked on #18 (the profile harness fix).

Commits

  • server: verify the round's openings as random linear combinations
  • cs: accumulate the batch slot by slot and vectorise the norm gate
  • cs: avx2 kernels for the batch accumulation and the norm gate (auto-vectorisation only gives SSE2 on baseline x86-64)
  • cs: name the batch weight bound, assert it against both moduli, check nine combinations (review items 2 and 3; item 4 waits on Openings carry the share, which the verifier can compute itself #22)

Testing

cargo test passes, including a new test that tampers each part of one member and checks the batch rejects it, and one that checks the AVX2 kernels against the scalar versions.

noot added 3 commits November 5, 2026 11:58
every equation of the fresh opening check is linear in the opening and
its root, so the server now checks eight random weighted sums of the whole
canonical set instead of running the ring arithmetic per client; position,
shape and norm bounds stay per client. a bad opening passes a combination
with probability at most 2^-15, so eight leave 2^-120. a failing batch
falls back to the per-client check, which still names the culprit.
the fold over openings read each opening once per combination and gave
every rayon split its own multi-megabyte zeroed accumulator to merge,
which made the 16-thread server round slower than before. each
polynomial slot is now accumulated on its own across all openings and
combinations, so an opening is read once and nothing is merged. the
infinity-norm checks are written as full reductions so they vectorise.
the auto-vectoriser only reaches sse2 on the baseline target, so the
weighted accumulation and the infinity-norm checks now dispatch to avx2
at runtime like the other kernels in this module.
@noot
noot force-pushed the noot/perf-server-batch-verify branch from f7dc604 to 1643783 Compare November 5, 2026 18:59
@noot
noot changed the base branch from main to noot/fix-profile-harness November 5, 2026 21:43
@nilsfleischhacker

Copy link
Copy Markdown
Collaborator

Thanks, this looks good at least as far as I can verify. I did not check the actual code in detail, but Claude tells me it matches the intent. Someone else should verify that. Some comments from the paper side:

  1. Soundness. Batch verification via random linear combinations is sound, and the paper now covers it in Appendix C.3. The per-opening check consists of norm bounds, which this PR keeps per client, and equations that are linear in the opening and the commitment (mod p for the leaf, mod q_hvc for the tree). If some opening fails, each combination passes with probability at most 1/|W|, provided the weights are drawn after the openings are fixed and W ⊆ [min(q, q′)]. Then distinct weights are distinct modulo both primes, so at most one weight cancels the defect. Since the weights are sampled from OS entropy once the inbox is complete, the first condition holds.

  2. The fixed 2^15. W = [1, 2^15 − 1] only satisfies W ⊆ [min(q, q′)] as long as both moduli are at least 2^15 − 1. That holds for the current q_hvc = 40961 and p = 139301, and for the parameters in align parameters with the paper's generator (IBLT shape, prime moduli, commitment) #8. With a smaller modulus, though, the bound silently degrades, e.g., to roughly 2^-13.4 per combination for q = 12289. It would be safer to derive the weight range (or at least assert it) from the moduli, instead of hard-coding it.

  3. Number of combinations. We target 128-bit security everywhere else. With |W| = 2^15 − 1, eight combinations give (2^15 − 1)^-8 ≈ 2^-120. Reaching 2^-128 needs ⌈128 / log₂|W|⌉ = 9 combinations, so I think BATCH_COMBOS should be 9.

  4. The share v. The batch check also accumulates and checks the share s (v in the paper). As described in Openings carry the share, which the verifier can compute itself #22, the share should not be sent at all, since the verifier can compute it as ĉ² − Br. Once Openings carry the share, which the verifier can compute itself #22 is addressed, this PR needs the same adjustment: drop s from the accumulator and from the leaf equation.

… nine combinations

the weight range [1, 2^15) is only sound while it sits below both q_cs
and q_hvc, so a compile-time assertion now pins that instead of the
bound living as a literal. nine combinations give 2^-135, past the
128-bit target that eight fell short of.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants