G1 MSM syscall for advanced ZK verifiers #536
Replies: 2 comments 3 replies
|
Thanks for the write-up. Instead of adding a new altbn128 MSM syscall, I think it makes more sense to augment the existing This will create some inconsistency in the list of syscalls, meaning the basic altbn128 curve ops are handled by designated syscalls while MSM is handled by a generic syscall like Given this, I think it makes sense to follow the conventions already in place for curve25519 in
Regarding G2, there are definitely use cases like Groth16 proof aggregation. But since G2 operations are significantly more expensive, I think it makes sense to restrict this proposal to just G1. If there is sufficient demand later, G2 can be added via a separate designated SIMD. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
PSE-Halo2 (BN254 / KZG / SHPLONK) verifier on Solana BPF, deployed on devnet. I haven't found prior art on Solana that does this natively (without wrapping down to Groth16 first); happy to be corrected.
groth16-solana,sp1-solana, andrisc0-solanaall use the wrap-to-Groth16 pattern.Wanted to write this up as a discussion before turning it into a formal SIMD because there are a few design questions I'd rather hash out first. Repo: https://github.com/nzengi/Solana-Plonk
What's already on devnet
The shuffle-circuit verify-tx
5DSF3xKZ…dpZlands at 1,372,980 CU. The tx setsset_compute_unit_limit(1_400_000)(Solana's default per-tx ceiling) and doesn't request a raise; the verifier just fits. Mollusk vs on-chain delta is 0.15 %.Tampered-proof rejects are also on-chain. Bit-flipping a Fr eval byte gives
26Tt9UqC…WCs9→Custom 0x200: the verifier ran the full pipeline (read_proof, lagrange, evaluate_gates, build_queries, shplonk, pairing) and the pairing equation evaluated to non-identity at the end. Only 661 CU more than the valid run, so the cryptographic check actually fired rather than bailing out early. Bit-flipping a G1 commit byte givesCustom 0x201the alt_bn128 syscall caught the off-curve point.The concrete problem
The shuffle path fits, but range-check (Plookup) and the larger circuits still abort at the 1.4 M cap. Per-stage profile under Mollusk:
shplonk::verify_openingis 38–61 % of the cost depending on circuit shape. Its inner loop isΣᵢ scalarsᵢ · pointsᵢover BN254 G1, done today as a sequence ofalt_bn128_g1_multiplication_becalls plus the G1 additions, plus O(n²) Lagrange interpolations for the rotation-set reductions. That's exactly what a batched MSM syscall would replace.Why pure-BPF Pippenger doesn't work
Numbers from a Mollusk bench grid (
programs/g1-msm-bench/) on a fresh build, all three modes through the same harness:Naive scalar-mul-and-add in pure BPF is past the per-tx CU cap from n = 4 (which is why
alt_bn128_g1_multiplication_beexists in the first place). The reference Pippenger (crates/g1-msm-ref/) exhausts the 256 KB heap budget at n ≥ 16 the bucket-array allocations + intermediateG1Projectivestorage push pastrequest_heap_frame(256_000). So if you want batched G1 MSM on Solana, it has to be a syscall.The proposal
A new syscall next to the existing
alt_bn128_g1_*family from SIMD-0284 and the proposed G2 family from SIMD-0302 (in review):Grouped layout (scalars then points) maps directly to arkworks
VariableBaseMSM::msm, no internal reorder. Per-point validation does the same field check + curve equation asalt_bn128_g1_multiplication_be; subgroup check is skipped, consistent with the existing G1 syscalls (BN254 G1 has cofactor 1).Cost model (rough, agave-side bench will refine):
4_000 + n × 2_400CU.alt_bn128_g1_multiplication_beis 3,840 CU/call. Native Pippenger window-NAF benchmarks (arkworks, halo2curves) typically save 30–40 % over sequential scalar-mul at n ≥ 8. 2,400 ≈ 3,840 × 0.62, the optimistic end of that range.g1_add's 334 CU.This beats sequential
n × 3,840 + (n − 1) × 334for n ≥ 4. At n = 2 it's a slight wrong-direction crossover (~0.97×), so callers with n ≤ 3 should keep the per-point syscall. Real numbers want an agave PR with the actual implementation benched against the existing alt_bn128 fuzz/test harness inprograms/bpf_loader/; the cost model above should be treated as a starting point.For the verifier this projects to roughly −20 % total CU and −32 % on the SHPLONK slice. Doesn't get the larger circuits (StandardPlonk, range-check) under 1.4 M alone those would still need a 2-tx split or a follow-up SIMD on the Fr-arithmetic side. But this is the highest-impact single piece, and anything doing verifier-side commitment combination on BN254 benefits: PLONK variants, folding schemes (Nova-style), batched Groth16 aggregation.
What's in the repo
crates/g1-msm-ref/no_std Pippenger window-NAF reference, 11/11 tests cross-checked against arkworks naive scalar-mul-and-add at n ∈ {0, 1, 2, 4, 8, 16, 32, 64}.programs/g1-msm-bench/Mollusk harness that produced the table above; three modes (sequential syscall, Pippenger BPF, naive BPF).docs/cu_profile.mdthe verifier with per-stage CU profile, prior-art comparison, and the on-chain evidence list.docs/simd-proposals/simd-XXXX-alt-bn128-g1-msm.mdfull SIMD draft following SIMD-0302's structure.A few things I'm not sure about
Input layout. I went grouped (scalars then points) because it matches arkworks and
solana-bn254ergonomics and avoids an internal reorder copy. EIP-2537 went interleaved for BLS12-381. Don't see a reason to flip but happy to.Max n. Proposing n ≤ 1024 to bound input-deserialization DoS surface (the syscall would reject larger inputs with
InvalidInputData). Could go tighter, or drop the cap and rely on tx size + CU limits.Canonical scalar handling. Current
alt_bn128_g1_multiplication_bereduces non-canonical scalars mod p. I matched that for consistency, but verifiers (this Halo2 thing, groth16-solana) often want strict-canonical rejection for input hygiene. Worth a_strict_bevariant, or pick one mode?Cost model methodology. The
4_000 + n × 2_400numbers above are rough derived from native Pippenger amortization analysis, not measured. agave folks will want to bench the actual implementation before locking the model in. If anyone has existing Pippenger numbers in the codebase or wants to suggest a derivation methodology, that would help.G2 MSM follow-up. Does an
alt_bn128_g2_msmmake sense in parallel, or is the G2 use-case rare enough that pairing-based aggregation already covers it?If anyone from Anza or Firedancer has a strong opinion on layout or cost model, early feedback would save a review round. Otherwise happy to keep iterating here before filing a fresh SIMD PR.
All reactions