ByAUJay
Which Open-Source Libraries Support BLS Aggregation for zk Proofs So I Don’t Leak Inputs During Verification?
A buyer’s guide to proof aggregation with BLS-friendly tooling, how to keep public inputs off-chain (or minimized), and concrete libraries you can ship with today.
Summary: If you need to batch-verify many zk-SNARKs without revealing each proof’s public inputs, you have three practical patterns: Groth16 aggregation (SnarkPack), recursive/accumulation circuits (Halo2/snark-verifier, zkVMs), or a BLS-signature “attest-and-wrap” design. Below, we list the exact open-source libraries, what they support, and how to wire them without leaking inputs.
First, let’s agree on terms
- BLS in this post refers to two related things:
- A pairing-friendly curve family used in many zk systems (e.g., BLS12‑381).
- Boneh–Lynn–Shacham (BLS) signature schemes that support non-interactive aggregation.
- Proof aggregation here means reducing verification of many SNARKs to one smaller verification. There are two main ways you’ll see it implemented:
- Aggregating the algebra of a specific SNARK (e.g., Groth16) to yield an “aggregated Groth16 proof” (SnarkPack).
- Recursively verifying proofs inside another proof (Halo2 KZG accumulation, zkVM recursion), often culminating in a single on-chain-verifiable proof. (research.protocol.ai)
Why input privacy is tricky: vanilla Groth16 verification consumes the public inputs explicitly (an MSM into the verifying key). If you verify N proofs on-chain, you typically post N sets of public inputs. Aggregation or recursion lets you compress this, and with the right design, avoid revealing per-proof inputs. (rareskills.io)
Where input leakage happens—and the two ways to avoid it
-
Leakage surfaces
- On-chain verifiers that read each proof’s public inputs.
- Batched verifiers that require the explicit input vector to recompute MSMs.
- Incorrect field/modulo handling that allows “aliasing” exploits for public inputs. (rareskills.io)
-
Two patterns to “not leak inputs”
- Commit-and-compress the public inputs:
- Provers or an aggregator commit to the list of inputs (e.g., Merkle/KZG).
- Verifier checks only a small commitment and a constant-size proof that the compressed value matches all inputs under a random challenge. With SnarkPack, this shows up as sending a small number of evaluations (e.g., z1, z2 = Σ a_i r^i) plus a proof they’re consistent, instead of all inputs. (ethresear.ch)
- Don’t check inputs on-chain at all; verify the whole batch inside a recursive proof:
- A Halo2 or zkVM circuit verifies many SNARKs internally; the chain verifies one final proof and only sees a compact digest of the batch. (docs.rs)
- Commit-and-compress the public inputs:
The short list: libraries you can use today
Below we group libraries by what they aggregate and how they help you keep inputs private.
A) Groth16 aggregation (SnarkPack)
- What it is: A construction that aggregates n Groth16 proofs into a logarithmic-size proof, with logarithmic verification time; does not require a new trusted setup, reuses Groth16 SRS. Production-used in Filecoin. (research.protocol.ai)
- Use when: Your proofs are Groth16 for the same circuit; you want the smallest aggregated object and ultra-fast verification off-chain/on L2; you can adopt the input-compression trick (so the chain doesn’t see all inputs).
- Open-source implementations:
- filecoin-project/bellperson aggregation module (Rust). The Groth16 aggregator lives under src/groth16/aggregate. (github.com)
- Arkworks-based snarkpack (Rust) maintained by contributors (PoC + benchmarks). (github.com)
- Performance you can rely on: 8,192 Groth16 proofs aggregated in ~8.7s on a 32-core CPU, verification in ~33ms; aggregated proof size <40 KiB (representative Filecoin miner workloads of ~350 public inputs). (research.protocol.ai)
- Keeping inputs private with SnarkPack
- Instead of posting all N input vectors, post constant many “random linear combination” evaluations z = Σ a_i r^i and a (KZG-style) proof they are correct relative to a commitment to the inputs; the verifier learns the batch digest, not each input. This technique is in the open literature discussion of SnarkPack’s handling of public inputs. (ethresear.ch)
- Curves / crypto notes: SnarkPack is about Groth16 (can be instantiated on BN254 or BLS12‑381 depending on your stack). Filecoin uses BLS12‑381 with bellperson/blstrs; Arkworks supports both ecosystems. Verify what your target chain supports on-chain (EVM precompiles natively favor BN254). (github.com)
Practical example: a minimal off-chain aggregator API
- Inputs: n Groth16 proofs π_i for the same circuit, public inputs x_i, verifying key VK.
- Output: π_agg, compressed input proof (z-values + proof), and a commitment to all x_i.
- On-chain: verify π_agg once; check the compact commitment proof; store only a batch root/hashes.
- You avoided: revealing all x_i on-chain while retaining soundness via the compact input proof. (research.protocol.ai)
B) Recursive/accumulation circuits that end in one proof
Use a circuit to verify many SNARKs, then emit a single proof; the chain verifies that final proof only. This naturally hides per-proof inputs—only the batch digest appears publicly.
- Halo2 + snark-verifier (PSE)
- What it is: A production-grade set of gadgets and SDK that implement KZG commitments and a KZG Accumulation Scheme (KzgAs) for Halo2, plus an aggregation circuit and EVM verifier tooling. (docs.rs)
- Why it helps: You can aggregate many Halo2/KZG proofs (or embed a Groth16 verifier inside Halo2 with the right gadgets), then verify one succinct proof on-chain. Inputs are handled/hashed inside the aggregation circuit; only a digest appears as public input. (docs.rs)
- How it works technically: KzgAs turns multiple pairing checks into a single accumulator (lhs/rhs points) under Fiat–Shamir challenges; the aggregation circuit exposes only a fixed-size public digest and an accumulator. The SDK exposes AggregationCircuit and helpers to build keys and aggregate proofs. (docs.rs)
- EVM pipeline: Use snark-verifier’s Solidity generator (or audited EVM verifier from the same ecosystem). There is also halo2-solidity-verifier, though it’s marked not-yet-audited—prefer snark-verifier where possible. (github.com)
- zkVM route (Succinct SP1)
- What it is: A zkVM that can verify heavy programs (including other proof verifiers) and then wrap the result into a Groth16/PLONK-sized proof for EVM chains. SP1 verifications on EVM routinely land around 275–300k gas per final proof verification, with mainnet-deployed gateways. (succinct.xyz)
- Why it helps: Instead of writing an aggregation circuit by hand, write normal Rust: verify N proofs inside SP1 and emit 1 proof. The public input can be just a batch commitment. The chain never sees individual inputs. (blog.succinct.xyz)
- Operationally: SP1 offers precompiles inside the zkVM for BN254/BLS12-381 primitives and publishes canonical verifier gateways you can reuse. (succinct.xyz)
Note: GPU provers and acceleration exist for both strategies (Halo2 and zkVMs). If your bottleneck is prover time, consider GPU backends (Snarkify/cuSnark, Ingonyama ICICLE) in your stack. (docs.snarkify.io)
C) BLS signature aggregation as an “attest-and-wrap” layer
Sometimes your product goal is “prove validity with zk,” but your on-chain interface only needs to attest that many off-chain verifications happened, tied to opaque inputs. A robust pattern is:
- Off-chain: For each user proof, compute a digest d_i of its public inputs (Poseidon/Keccak) and have a BLS key sign d_i. Aggregate all signatures to σ*. Optionally, recursively verify each zk proof off-chain and output one recursive zk proof binding σ* to the batch.
- On-chain: Verify the single aggregated BLS signature σ* against a compact root or digest and, if added, verify the one recursive zk proof. The chain never sees per-proof inputs.
- Libraries you’d use:
- supranational/blst (C/asm) for BLS12‑381 signatures and aggregate verification; it’s audited and widely deployed. (github.com)
- herumi/bls (C++ on mcl) if you want an alternative with multi-verify helpers and ETH2-compatible modes. (github.com)
- If you’re in the ERC‑4337 world, look at the “signature aggregator” pattern (ERC‑7766), which standardizes an on-chain contract interface to validate aggregated signatures. You can adapt the same aggregator pattern to attesting to zk-proof digests. (eips.ethereum.org)
This pattern gives you maximum control over what’s public: the chain only learns batch-level attestations and a small zk proof, never the individual inputs.
Curve and platform considerations (you’ll need to decide this up front)
- EVM L1/L2: Native precompiles exist for BN254 pairings; BLS12‑381 pairings are costlier unless you bring your own precompile or verify off-chain. Many stacks therefore use BN254 for on-chain verification even if off-chain systems run BLS12‑381. Plan accordingly. (github.com)
- BLS12‑381 for cryptographic comfort and ecosystem: If you need audited and high-performance primitives, gnark-crypto exposes BLS12‑381 and other curves; zkcrypto/bls12_381 exists on Rust. Both are suitable building blocks if you need to implement a custom aggregator or in-circuit pairing checks. (github.com)
- In-circuit pairings: If you must verify BLS signatures or pairings inside a circuit (for recursion/attestation), realize the constraint cost is non-trivial. gnark notes and community write-ups cover optimized “emulated pairing” gadgets for BN254 and BLS12‑381; plan for millions of constraints if you do many pairings in-circuit. (hackmd.io)
- Non‑EVM chains: For Solana (BN254 syscalls), see groth16-solana for ~<200k compute units per verification—useful if you port an aggregated Groth16 flow to Solana. (lib.rs)
Practical wiring recipes
- SnarkPack with input privacy (no per-proof inputs on-chain)
- Off-chain aggregator:
- Take {π_i, x_i} over Groth16, same circuit.
- Build commitments to the sequence of x_i; compute challenges r; compute z-values (Σ a_i r^i) and prove they’re correct relative to the commitments.
- Run SnarkPack to produce π_agg.
- On-chain or L2 verifier:
- Verify π_agg once and verify the small commitment proof of the z-values.
- Store only the commitment root. (research.protocol.ai)
- Halo2 KZG accumulation (snark-verifier SDK)
- Design an AggregationCircuit with snark-verifier’s SDK; parameterize it to accept a variable batch size within constraints.
- During aggregation, use KzgAs to fold many pairing checks into a single accumulator; emit a succinct public digest for the batch.
- Generate an EVM verifier using the ecosystem tooling. No per-proof inputs are published—only a batch digest. (docs.rs)
- zkVM-based “wrap and ship” (Succinct SP1)
- Write a Rust program that verifies N Groth16/Halo2 proofs and checks the batch’s input commitments internally.
- Prove it in SP1, then verify the resulting single SP1 proof on EVM for ~275–300k gas; publish only the batch digest as public input to the verifier. (succinct.xyz)
- BLS signature attest-and-wrap
- For each proof, compute d_i = H(public_inputs_i).
- Sign d_i with BLS; aggregate to σ* with blst.
- Option A: Stop here if you only need attestations; Option B: add a (Halo2/zkVM) recursive proof that checks: “for every signed d_i, there exists a valid SNARK with those inputs.”
- Verify σ* (and the single recursive SNARK) on-chain; reveal only the batch digest. (github.com)
Library-by-library details to help you choose
-
SnarkPack (Rust)
- What you get: real Groth16 aggregation with logarithmic verifier, reuse of Groth16 SRS, production benchmarks on 8k+ proofs. (research.protocol.ai)
- Input privacy: use compressed-input proof (z-evaluations) to avoid listing all inputs; if you deploy to EVM, post only commitment roots and tiny extra data. (ethresear.ch)
- Repos: bellperson aggregate module; arkworks-based snarkpack. (github.com)
-
snark-verifier + snark-verifier-sdk (Halo2, KZG accumulation)
- What you get: audited-grade circuit gadgets and SDK to aggregate proofs; Solidity verifier generator; KZGAS modules including succinct verifying keys/accumulators. (docs.rs)
- Input privacy: the aggregation circuit exposes only a batch digest as public input. On-chain learns nothing about per-proof inputs. (docs.rs)
-
Succinct SP1 (zkVM)
- What you get: write Rust, verify any proof systems as programs, and ship one small on-chain proof (~275–300k gas verification; canonical verifier gateways maintained by vendor). (succinct.xyz)
- Input privacy: enforce commitments/relations inside your program; the chain sees only the final program’s public IO (your batch digest). (blog.succinct.xyz)
-
blst and herumi/bls (BLS signatures)
- What you get: audited, high-performance BLS12‑381 signature aggregation; “fast aggregate verify”; used by Ethereum clients. Great to layer on top of zk systems when you want off-chain attestations with one on-chain check. (github.com)
- 4337 context: pattern formalized as an “aggregator” contract interface in ERC‑7766; easy to adapt to attest your zk batch digest instead of per-transaction signatures. (eips.ethereum.org)
-
gnark and gnark-crypto (Go)
- What you get: production zk library (Groth16/PLONK) across BN254, BLS12‑381, etc.; audited crypto; strong performance; pairing/KZG primitives if you need to implement custom gadgets or in-circuit pairings. (docs.gnark.consensys.net)
- In-circuit pairings: community notes show constraint counts and techniques for emulating BN254/BLS12‑381 pairings inside circuits—a prerequisite for “verify BLS signatures in-circuit” designs. (hackmd.io)
-
Other ecosystem pieces you may use
- zkcrypto/bellman, arkworks/groth16 for Groth16; Solana’s groth16-solana if you target Solana. These aren’t “aggregators” by themselves, but form the substrate for your aggregation or recursion stack. (github.com)
Best emerging practices (what teams are converging on)
- Pick your on-chain proof format based on precompiles:
- If verifying directly on EVM L1, BN254 (altbn128) still dominates for gas reasons; aggregate or recurse into a BN254-friendly proof at the end (even if you use BLS12‑381 off-chain). (github.com)
- If you aggregate Groth16 with SnarkPack, don’t ship raw input vectors:
- Use the “challenge-and-evaluate” compression trick (polynomial evaluation at r) so the verifier sees O(1) field elements plus a proof, not O(n) inputs. (ethresear.ch)
- If time-to-market matters, prefer recursion/zkVMs over hand-rolling an aggregator:
- The Halo2/snark-verifier SDK gives you audited building blocks; zkVMs like SP1 let your team stay in normal Rust and still land a 1-proof on-chain flow (~275–300k gas). (docs.rs)
- Harden your input handling:
- Guard against “input aliasing” (uint256 vs field mod q) in verifiers. Libraries and contracts should consistently reduce mod q and reject out-of-range values where appropriate. (galxe.com)
- Audit trail and reproducibility:
- Favor audited primitives (blst, gnark-crypto) and published gateways (SP1). Publish hashes of keys/SRS and pin versions in CI. (github.com)
Decision guide: which path fits your product?
- I just need smaller, faster verification for many Groth16 proofs and minimal engineering:
- Use SnarkPack (bellperson or arkworks). Add input-compression proof to avoid revealing per-proof inputs. Good for batch attestations and systems already on Groth16. (research.protocol.ai)
- I want a general, reusable aggregator I can evolve, and EVM verification:
- Build an aggregation circuit with snark-verifier (Halo2/KZG). You control exactly what’s public (usually just a batch digest). (docs.rs)
- I want to keep business logic in Rust and avoid bespoke circuits:
- Verify proofs inside SP1 and publish a single proof on-chain (~275–300k gas). Layer in BLS signature aggregation for off-chain attestations as needed. (succinct.xyz)
- I only need attested membership (no per-proof visibility):
- Aggregate BLS signatures over commitments to each proof’s inputs; optionally add one recursive proof that enforces “every signature corresponds to a valid proof.” This yields the smallest on-chain surface area. (github.com)
Brief, in-depth details: example snippets
- snark-verifier SDK flow (Halo2, KZGAS)
// Pseudocode sketch use snark_verifier_sdk::halo2::aggregation::{ AggregationCircuit, aggregate_snarks, AggregationConfigParams }; use snark_verifier_sdk::{gen_pk, Snark}; let params = AggregationConfigParams {/* sizing, lookup bits, etc. */}; let snarks: Vec<Snark> = load_my_snarks(); // Groth16/Halo2 proofs wrapped for SDK let agg = aggregate_snarks(params.clone(), snarks); let (pk, vk) = gen_pk(&agg); // keygen on aggregation circuit let proof = prove(&agg, &pk); // one proof verifying all the others // publish proof + batch_digest; no per-proof inputs revealed
This uses KzgAs under the hood, folding multiple pairing checks into a constant-size accumulator with a succinct verifying key. (docs.rs)
- BLS attest-and-wrap
for each proof i: d_i = Poseidon(public_inputs_i) // or Keccak, per your stack sig_i = BLS.Sign(sk, d_i) sigma = BLS.Aggregate(sig_1,...,sig_n) on-chain: BLS.Verify(AGG_PUBKEYS, {d_1,...,d_n?} or a batch root, sigma) optional: verify one recursive SNARK that checks all (proof_i, d_i) pairs
Use blst for aggregation/verification; if you can’t pass all d_i, commit (Merkle/KZG) and verify only a root + membership proofs. (github.com)
- SnarkPack input compression (conceptual)
Commit to inputs {x_i} with a vector commitment Derive challenge r from transcript Send z = Σ x_i r^i and a proof that z matches the committed inputs at r Verify aggregated Groth16 + small input proof; don’t reveal all {x_i}
This is precisely the idea discussed for handling public inputs efficiently with SnarkPack-like flows. (ethresear.ch)
What about curve gadgets and in-circuit pairings?
If your design needs “verify BLS signatures or pairings inside a circuit,” plan capacity: millions of constraints for many pairings are normal. gnark community write-ups document optimization techniques for emulated pairings on BN254 and BLS12‑381; circom has proof-of-concept pairing circuits too. Use these judiciously (or prefer zkVM recursion if you need lots of pairings). (hackmd.io)
Gotchas to avoid
- Input aliasing in verifiers (especially Solidity BN254): always mod-reduce and range-check; do not accept multiple integer representatives of the same field element. (galxe.com)
- Curve mismatch: aggregating off-chain on BLS12‑381 but verifying on-chain with BN254 means you need a wrap/recursion step (Halo2/zkVM) into BN254 for cost efficiency. (github.com)
- DIY cryptography: if you’re assembling a custom aggregator, stick to audited primitives (blst, gnark-crypto) and known constructions (SnarkPack, KZGAS). (github.com)
TL;DR recommendations you can act on this quarter
- Already on Groth16 and want instant batching: adopt SnarkPack; ship aggregated proofs plus a compact input-compression proof. (research.protocol.ai)
- Need flexible, EVM-first aggregation without input leakage: build on Halo2 with snark-verifier’s aggregation circuit and KZGAS; publish only batch digests. (docs.rs)
- Want fastest dev path and portability across chains: recurse in SP1 and verify a single proof (~275–300k gas) on mainnet; optionally combine with a BLS attestation layer. (succinct.xyz)
If you’d like, 7Block Labs can prototype all three for your workload (same circuit, same data) and deliver a side-by-side cost/latency comparison and a recommended migration plan.
References and further reading:
- SnarkPack paper + Filecoin implementation and benchmarks. (research.protocol.ai)
- snark-verifier KZG Accumulation Scheme and Halo2 aggregation SDK docs. (docs.rs)
- SP1 on-chain verification gas and verifier gateways. (succinct.xyz)
- BLS signature libraries (blst, herumi/bls); ERC‑7766 aggregator. (github.com)
- Groth16 public-input handling and input aliasing pitfalls. (rareskills.io)
- gnark-crypto / pairing gadgets and in-circuit pairing notes. (github.com)
Like what you're reading? Let's build together.
Get a free 30‑minute consultation with our engineering team.

