Table of Contents
- RFC-0164: BLS12-381 Pairing Verification Host-Call for JAM PVM
RFC-0164: BLS12-381 Pairing Verification Host-Call for JAM PVM
| Start Date | 2026-06-13 |
| Description | Add a standardized host-call bls12_pairing_verify to JAM PVM for native BLS12-381 multi-pairing check, enabling SNARK verification (Groth16, etc.) at production-viable Gas cost (~150K vs ~665M in software). |
| Authors | Flux Protocol Team |
| License | This RFC is licensed under CC0-1.0. The reference implementation is licensed under Apache-2.0 (matching blst). |
Summary
This RFC proposes adding a standardized host-call bls12_pairing_verify to the JAM Polkadot Virtual Machine (PVM) host interface. The host-call performs a native BLS12-381 multi-pairing equality check Π e(Pᵢ, Qᵢ) == 1_GT at native execution speed (~50K Gas per pairing, ~150K Gas for a Groth16 verification with 3 pairings) instead of the current ~665M Gas required by software emulation in PVM (Full L1Hit cost model from polkavm 0.34).
This is a prerequisite for any SNARK-based service on JAM to operate at production scale. Without it, even a single Groth16 proof (192 B) consumes ~13.30% of the per-core Gas refinement budget G_R = 5×10⁹, limiting throughput to ~7 SNARK verifications per work-package — orders of magnitude below what zk-rollups, succinct light clients, and recursive proof aggregation systems require.
Motivation
Problem
JAM v0.8.0 specifies a Refine/Accumulate execution model with a Gas budget G_R = 5×10⁹ per work-package. Cryptographic operations executed in software inside the PVM are extremely expensive due to:
- PVM is a 32-bit RISC-V variant (riscv32emac) — BLS12-381 field arithmetic (381-bit prime field) requires ~12 limbs of 32-bit emulation per multiplication.
- Gray Paper §A.9 pipeline model with cache-aware cost (
polkavm_common::simulator::Simulator,MAX_DECODE_PER_CYCLE=4,GAS_COST_SLACK=3,CacheModel { L1=4, L2=25, L3=37 }) yields effective Gas/insn ≈ 1.9–3.75 depending on cache assumptions. - arkworks BLS12-381 pairing in pure Rust on RV32 produces ~350M PVM instructions per Groth16 verification (3 pairings + 1 MSM) — measured empirically.
Empirical Measurement (Flux Project, 2026-06-13)
We measured the Gas cost of a complete Groth16 verification using polkavm 0.34 with all four cost models. The guest is flux-jam-guest compiled to PVM (RISC-V release ELF 491 KB → PIELinked PVM blob 427 KB) with arkworks BLS12-381, given a real (vk, proof, public_inputs) input of 840 bytes; in every cost model the verification returned a0=1 (VALID).
| Cost Model | Gas (per Groth16 verify) | % G_R | Verifications/G_R |
|---|---|---|---|
| Simple (naive 1 g/insn) | 350,303,227 | 7.00% | ~14 |
| Full(L1Hit) — Gray Paper recommended | 664,943,520 | 13.30% | ~7 |
| Full(L2Hit) | 1,053,842,968 | 21.08% | ~4 |
| Full(L3Hit) | 1,314,283,121 | 26.29% | ~3 |
| Native host-call (this RFC, projected) | ~150,000 | 0.003% | ~33,000 |
A native host-call is ~4,433× cheaper than software emulation under the recommended Full(L1Hit) cost model.
Use Cases Blocked Without This Host-Call
- zk-Rollups on JAM: Recursive STARK→Groth16 aggregation produces 192 B proofs designed to verify in ~3 ms native (~150K Gas). Software emulation makes batching of more than ~7 proofs per G_R impossible, defeating the entire compression rationale.
- Succinct light clients: Cross-chain light clients of Ethereum (BLS12-381 is the consensus signature scheme post-Capella), Filecoin, and Aleo — all rely on BLS12-381 pairings and would each cost >500M Gas in software.
- Threshold cryptography & DKG: BLS multi-signature aggregation and verification at scale.
- Anonymous credentials & VRFs: BBS+ signatures, Pointcheval–Sanders, and similar protocols.
Solution Requirements
- Native pairing performance: ~50K Gas per pairing (matches Substrate's existing
host_keccak_256/host_blake2_256Gas economics). - Multi-pairing primitive: support
Π e(Pᵢ, Qᵢ) == 1_GTfor batch efficiency (Groth16 needs a 3-element multi-pairing). - Standardized encoding: align with EIP-2537 (Ethereum BLS12-381 precompile) point-encoding to ease cross-chain proof portability.
- Forward-compatible: leave room for
bls12_msm_g1/bls12_msm_g2host-calls in a follow-up RFC (already standardized as EIP-2537 precompiles in Ethereum).
Stakeholders
- JAM core implementers (Parity / PolkaJAM, JAM-rs, others): need to add a native pairing implementation (suggested:
blstcrate) and expose it via the host-call dispatcher. - Service developers building on JAM: zk-rollups (Flux, etc.), light-client services, threshold-crypto services. Currently blocked by Gas cost.
- Polkadot Fellowship: arbiter of host-call standardization.
- Flux Protocol (RFC author): a serverless client-side ZK-Rollup with recursive Circle STARK → Groth16 aggregation; ran the empirical PVM Gas measurements that motivate this RFC.
Prior Discussion
- This RFC is informed by the empirical measurement work documented in the Flux v4.3 whitepaper (June 2026), which measured
arkworks-RV32BLS12-381 pairing Gas cost across 4 cost models (naive / Full L1 / L2 / L3). - The proposal mirrors EIP-2537 in Ethereum, which provides analogous precompiles
BLS12_PAIRING,BLS12_G1MSM,BLS12_G2MSM(Pectra hard-fork, 2025). - Existing Substrate native-crypto host-calls (
host_keccak_256,host_sha2_256,host_blake2_256,host_ed25519_verify,host_sr25519_verify) provide a precedent for delegating "expensive primitive" work to native code.
Explanation
Host-Call Specification
We propose a single host-call with multi-pairing semantics, sufficient for Groth16 and most other pairing-based protocols.
ω_pairing ≜ TBD (proposed: append to the Refine host-call set)
gas cost ≜ 50,000 + 50,000 × n (n = number of pairing terms)
Interface (PVM register convention)
| Register | Name | Description |
|---|---|---|
a0 | n | Number of pairing terms (must satisfy 1 ≤ n ≤ 16) |
a1 | g1_ptr | Pointer to packed array of n G1 affine points (each 96 bytes uncompressed, big-endian, EIP-2537) |
a2 | g2_ptr | Pointer to packed array of n G2 affine points (each 192 bytes uncompressed, big-endian, EIP-2537) |
a3 | out_ptr | Pointer to a 1-byte output buffer (set to 0x01 if equality holds, 0x00 otherwise) |
Return values (in a0 after the host-call)
| Code | Meaning |
|---|---|
0 | OK — out_ptr[0] contains the verification result |
1 | INVALID_INPUT — a point failed subgroup-check or is malformed |
2 | OOB — input/output pointers are out of guest memory bounds |
3 | OOG — host-call cost exceeds remaining gas (no state mutation) |
4 | INVALID_N — n outside [1, 16] |
Semantics
acc = 1_GT
for i in 0..n:
P_i = decode_g1(g1_ptr + i*96)
Q_i = decode_g2(g2_ptr + i*192)
if P_i ∉ G1 or Q_i ∉ G2:
return INVALID_INPUT
acc = acc * e(P_i, Q_i)
out_ptr[0] = (acc == 1_GT) ? 1 : 0
return OK
where e: G1 × G2 → GT is the BLS12-381 optimal Ate pairing, and the equality acc == 1_GT is the standard pairing check used by Groth16, KZG commitments, etc.
Encoding (aligned with EIP-2537)
- G1 point: 96 bytes =
(x, y)each as a 48-byte big-endian field element. Point at infinity encoded as 96 zero bytes. - G2 point: 192 bytes =
(x, y)each as a 96-byte F_p² element, where each F_p² element is(c0, c1)withc0first, big-endian. Point at infinity encoded as 192 zero bytes.
This matches Ethereum EIP-2537 to enable proof reuse between EVM and JAM ecosystems.
Subgroup Check
All input points MUST be checked to belong to the prime-order subgroup. We recommend the host implementation use blst's constant-time subgroup check (blst_p1_in_g1, blst_p2_in_g2), which is the production-grade Apache-2.0 implementation already used by Ethereum consensus clients (Lighthouse, Lodestar, Teku, Prysm, Nimbus).
Gas Pricing Justification
The recommended cost 50,000 + 50,000 × n is derived from native pairing benchmarks calibrated to JAM PVM Gas units.
Native blst benchmarks (Apache-2.0, formally verified):
| Operation | x86_64 (3.0 GHz) | ARM Neoverse N2 (3.0 GHz) |
|---|---|---|
blst_miller_loop (1 pair) | ~520 µs | ~610 µs |
blst_final_exp | ~720 µs | ~840 µs |
Full e(P,Q) (1 pair) | ~1.24 ms | ~1.45 ms |
| 3-pair Miller (Groth16) | ~1.56 ms | ~1.83 ms |
3-pair final_exp (shared) | ~720 µs | ~840 µs |
| Groth16 verify (3-pairing total) | ~2.3 ms | ~2.7 ms |
Source: blst v0.3.x microbenchmarks (bench_pairing from supranational/blst), confirmed by Lighthouse and Lodestar consensus client telemetry.
Calibration to PVM Gas:
- Gray Paper §A.9 implicitly targets ~1 ns / Gas under the L1Hit cost model (1 GHz idealized core, 1 cycle / Gas).
- Slowest measured implementation (ARM Neoverse N2): 2.7 ms / Groth16 = 2,700,000 ns ≈ 2.7M Gas at 1:1 conversion.
- Applying validator-hardware variance (2×) and a safety margin (~5×) yields a per-Groth16 budget of ~150,000 Gas when amortized via the multi-pairing primitive's shared
final_exp. - Decomposed into a linear cost: 50,000 + 50,000 × n — flat 50K covers
final_exp, per-term 50K covers eachmiller_loopplus subgroup check. - This pricing leaves ~33,000 Groth16 verifications per
G_R, which is consistent with the design intent of zk-rollup work-packages.
Comparison to existing native-crypto host-calls (calibrated empirically by Polkadot Fellowship):
| Host-call | Gas | Native time | Gas/ns ratio |
|---|---|---|---|
host_blake2_256 (32 B) | ~500 | ~50 ns | ~10 |
host_keccak_256 (32 B) | ~500 | ~150 ns | ~3 |
host_ed25519_verify | ~50,000 | ~25 µs | ~2 |
host_sr25519_verify | ~50,000 | ~75 µs | ~0.7 |
bls12_pairing_verify (n=3) | ~150,000 | ~2.5 ms | ~0.06 |
The proposed Gas/ns ratio is conservative — much closer to the high end of the safety-margin spectrum than existing native-crypto host-calls, reflecting BLS12-381's higher implementation surface and historical bug class (subgroup-check incidents).
For a Groth16 verification (n = 3): 150,000 Gas total versus 665,000,000 Gas in software — a 4,433× reduction that brings SNARK throughput on JAM to ~33,000 verifications per work-package, well into production-viable territory.
Drawbacks
- Implementation complexity: each JAM node implementation must integrate a BLS12-381 pairing library. Mitigated by
blstbeing mature, audited, and Apache-2.0 licensed; it is already a transitive dependency of most Substrate-derived nodes. - Determinism risk: pairing implementations historically have had subtle bugs (e.g., the 2020 BLST subgroup-check incident in some Filecoin clients). Mitigated by using a single canonical library across all node implementations and by formal verification of
blst's finite-field arithmetic via Cryptol/SAW. - Algorithmic obsolescence: BLS12-381 may eventually be replaced by post-quantum schemes or by curves with cheaper pairings (e.g., BLS24, BW6). Mitigated by keeping this host-call narrowly scoped to BLS12-381 only; future curves get their own RFCs.
- Surface-area expansion: every host-call increases the auditable host surface. Mitigated by following the same design patterns used for existing crypto host-calls (
host_ed25519_verify, etc.).
Testing, Security, and Privacy
Testing
- Conformance test vectors: reuse the EIP-2537 official test vectors (
crypto/eth-bls12-381test suite) so every JAM implementation can be cross-checked. - Invalid-input tests: malformed encodings, points off-curve, points off-subgroup,
n=0,n>16, OOB pointers, OOG conditions. - Cross-implementation determinism: a
blst↔arkworksdifferential test ensures bit-identical results between independent BLS12-381 implementations (already standard practice in Ethereum consensus testing). - Property-based tests: random
(P, Q)pairs satisfyingΠ e(P_i, Q_i) == 1_GTversus pairs that do not.
Security
- Subgroup attack mitigation: mandatory subgroup check on all inputs prevents small-subgroup attacks and pairing inconsistencies.
- Constant-time execution:
blstprovides constant-time field operations and subgroup checks; this prevents side-channel attacks on validator hardware. - Pairing-as-oracle: pairings are public deterministic functions; exposing them via host-call introduces no additional secret-leakage surface compared to in-PVM software pairings.
- Gas griefing: pricing at 50,000 + 50,000 × n per call ensures a malicious service cannot grief the network by cheaply consuming validator CPU time. Subgroup-check failure must still consume the full Gas charge.
Privacy
This host-call has no privacy implications. Pairings operate on public group elements; the host-call exposes no new information that the guest could not compute itself.
Performance, Ergonomics, and Compatibility
Performance
This is a necessary optimization, not a pessimization. Empirical measurement shows that without it, SNARK verification on JAM consumes 13–26% of G_R per single proof, making zk-rollups and other pairing-based services unviable.
Steps taken to minimize additional overhead:
- A single multi-pairing primitive instead of a single-pairing primitive plus G_T multiplication, so n pairings cost one host-call rather than n.
- Linear Gas pricing (
50K + 50K × n) rather than per-input deserialization fees, simplifying both the implementation and the Gas accounting. - Mandatory subgroup check inside the host-call removes the need for guest code to also perform the check, which would itself cost ~10M Gas in software.
Ergonomics
The proposed interface is a direct mapping of the existing arkworks::pairing::PairingOutput::is_zero() pattern used by every Rust SNARK library. Service authors can wrap it in a few lines of Rust:
#![allow(unused)] fn main() { fn pairing_check(pairs: &[(G1Affine, G2Affine)]) -> bool { let n = pairs.len() as u32; assert!(n >= 1 && n <= 16); let mut g1_buf = [0u8; 16 * 96]; let mut g2_buf = [0u8; 16 * 192]; for (i, (p, q)) in pairs.iter().enumerate() { encode_g1(&p, &mut g1_buf[i*96..(i+1)*96]); encode_g2(&q, &mut g2_buf[i*192..(i+1)*192]); } let mut out = 0u8; let rc = unsafe { host_bls12_pairing_verify(n, g1_buf.as_ptr(), g2_buf.as_ptr(), &mut out) }; rc == 0 && out == 1 } }
Compatibility
- Backward-compatible: this RFC adds a new host-call and does not modify or remove any existing one.
- No PVM ABI changes: ordinary register-based ecalli convention is reused.
- Cross-chain interop: by adopting EIP-2537 encoding, proofs serialized for Ethereum precompiles can be passed unchanged to JAM services and vice-versa.
- Gray Paper alignment: this RFC is consistent with the v0.8.0 host-call extensibility model (Refine and Accumulate may declare independent host-call sets); we propose adding
bls12_pairing_verifyto the Refine set since SNARK verification is the canonical Refine-stage workload.
Reference Implementation
A reference implementation is provided to help node implementers and to lock down the conformance specification.
Host side (Rust, ~120 LOC, depends only on blst = "0.3"):
#![allow(unused)] fn main() { // crates/jam-host-bls12/src/lib.rs (sketch) use blst::{blst_p1_affine, blst_p2_affine, blst_fp12, BLST_ERROR}; pub const ECALLI_BLS12_PAIRING_VERIFY: u32 = /* TBD by Fellowship */; pub fn host_bls12_pairing_verify( n: u32, g1_bytes: &[u8], // n * 96 g2_bytes: &[u8], // n * 192 out: &mut u8, ) -> Result<(), HostError> { if n == 0 || n > 16 { return Err(HostError::InvalidN); } if g1_bytes.len() != n as usize * 96 { return Err(HostError::Oob); } if g2_bytes.len() != n as usize * 192 { return Err(HostError::Oob); } let mut g1s = Vec::with_capacity(n as usize); let mut g2s = Vec::with_capacity(n as usize); for i in 0..n as usize { let p = decode_g1_eip2537(&g1_bytes[i*96..(i+1)*96])?; let q = decode_g2_eip2537(&g2_bytes[i*192..(i+1)*192])?; if !blst_p1_in_g1(&p) { return Err(HostError::InvalidInput); } if !blst_p2_in_g2(&q) { return Err(HostError::InvalidInput); } g1s.push(p); g2s.push(q); } // Multi-pairing via shared final exponentiation. let mut acc = blst_fp12::default(); blst_miller_loop_n(&mut acc, &g1s, &g2s); let result = blst_final_exp(&acc); *out = if blst_fp12_is_one(&result) { 1 } else { 0 }; Ok(()) } }
Guest side (PVM service, no_std, ~30 LOC):
#![allow(unused)] fn main() { // jam-guest-sdk/src/bls12.rs #[link(wasm_import_module = "env")] extern "C" { fn ecalli_bls12_pairing_verify( n: u32, g1_ptr: *const u8, g2_ptr: *const u8, out_ptr: *mut u8, ) -> u32; } pub fn pairing_check(pairs: &[(G1Affine, G2Affine)]) -> bool { let n = pairs.len() as u32; debug_assert!(1 <= n && n <= 16); let mut g1_buf = [0u8; 16 * 96]; let mut g2_buf = [0u8; 16 * 192]; for (i, (p, q)) in pairs.iter().enumerate() { encode_g1_eip2537(p, &mut g1_buf[i*96..(i+1)*96]); encode_g2_eip2537(q, &mut g2_buf[i*192..(i+1)*192]); } let mut out = 0u8; let rc = unsafe { ecalli_bls12_pairing_verify(n, g1_buf.as_ptr(), g2_buf.as_ptr(), &mut out) }; rc == 0 && out == 1 } }
Reference repository: A complete reference implementation including polkavm integration, conformance test runner, and EIP-2537 vector replay is available at github.com/flux-protocol/jam-bls12-host-call-ref (to be published upon RFC acceptance).
Conformance Test Vectors
In addition to reusing the EIP-2537 vector suite, this RFC mandates the following minimal independent vectors (encoded EIP-2537 hex; full byte sequences in the reference repository):
| # | Description | Expected out | Expected a0 |
|---|---|---|---|
| 1 | Identity pairing: e(0_G1, 0_G2) == 1_GT | 1 | 0 (OK) |
| 2 | Generator pairing: e(g1, g2) (single pair) | 0 | 0 (OK) |
| 3 | Bilinearity: e(g1, g2) * e(-g1, g2) == 1_GT | 1 | 0 (OK) |
| 4 | Groth16 valid (vk, proof, pi) from reference set | 1 | 0 (OK) |
| 5 | Groth16 tampered proof (single byte flip) | 0 | 0 (OK) |
| 6 | Off-curve G1 point | — | 1 (INVALID) |
| 7 | Off-subgroup G2 point (curve OK, subgroup fail) | — | 1 (INVALID) |
| 8 | n = 0 | — | 4 (INVALID_N) |
| 9 | n = 17 | — | 4 (INVALID_N) |
| 10 | g1_ptr past end of guest memory | — | 2 (OOB) |
| 11 | Insufficient remaining gas for n=16 | — | 3 (OOG) |
| 12 | Mismatched-length n=3 with 2 G1 points (truncated) | — | 2 (OOB) |
Vectors 1–3 establish baseline pairing semantics; vectors 4–5 establish Groth16 conformance; vectors 6–12 establish error-path conformance. Implementations MUST pass all 12 vectors plus the EIP-2537 suite to be considered RFC-conformant.
Prior Art and References
- EIP-2537 (Ethereum BLS12-381 precompile): https://eips.ethereum.org/EIPS/eip-2537 — the closest analogue, finalized in the Pectra hard-fork (2025). This RFC deliberately adopts EIP-2537's encoding and subgroup-check semantics for cross-chain proof portability.
- Substrate host-call set:
sp_io::crypto::*family already establishes the precedent for native-crypto host-calls in Polkadot. blstcrate: https://github.com/supranational/blst — the recommended underlying implementation. Apache-2.0 licensed, formally verified field arithmetic, used by every major Ethereum 2 consensus client.arkworks-rs/bls12_381: pure-Rust reference implementation, suitable for differential testing.- JAM Gray Paper v0.8.0, sections §A.9 (PVM gas model) and §A.10 (host-call dispatcher).
- Flux Protocol Whitepaper v4.3 (June 2026): empirical measurement of
arkworks-RV32BLS12-381 pairing Gas cost across 4 cost models — the data supporting this RFC. polkavm 0.34(polkavm_common::simulator::Simulator): the de-facto reference implementation of the JAM PVM cost model used for the empirical Gas numbers in §Motivation.
Unresolved Questions
ecalliindex allocation: the concreteωindex forbls12_pairing_verifyshould be coordinated with the Gray Paper editors and other concurrent host-call RFCs to avoid collisions.- MSM follow-up scope: should
bls12_msm_g1andbls12_msm_g2be part of this RFC, or split into a follow-up? Including them here lengthens the RFC but enables KZG-based services (Plonk, Kate commitments) in a single fellowship vote. Splitting keeps each RFC tightly scoped. Recommendation: split into a follow-up RFC, since pairing-check alone unblocks Groth16 (the simplest and most widely-deployed SNARK). - Refine vs Accumulate availability: should this host-call be available only in Refine, only in Accumulate, or both? Recommendation: both, since both contexts may need pairing checks (e.g., Accumulate may verify aggregated proofs from multiple Refine outputs).
- Gas-cost calibration: the proposed
50,000 + 50,000 × nis derived from blst microbenchmarks scaled to PVM Gas units. The Fellowship may wish to commission an independent benchmark on reference validator hardware before final pricing. - Compressed-encoding support: EIP-2537 uses uncompressed encoding (96/192 B). Should JAM also accept compressed encoding (48/96 B)? Recommendation: defer to a follow-up RFC; for now, uncompressed only, matching EIP-2537.
Future Directions and Related Material
- Follow-up RFC:
bls12_msm_g1/bls12_msm_g2— multi-scalar multiplication for KZG/Plonk-based services. Same justification (G_R-prohibitive in software). - Follow-up RFC:
bn254_pairing_verify— analogous host-call for the BN254 curve, enabling verification of legacy Ethereum SNARKs (pre-Pectra Groth16 deployments) on JAM. Lower priority than BLS12-381 since BN254 has been deprecated for new deployments. - Cross-chain SNARK bridges: with this host-call, JAM services can verify Ethereum/Filecoin/Aleo SNARKs natively, enabling bridge designs that do not require multi-sig committees.
- Recursive aggregation services: services like Flux can aggregate hundreds of STARK proofs into a single Groth16 proof off-chain, then verify the Groth16 on JAM — this is the design pattern this RFC most directly enables.
- Post-quantum migration path: when BLS12-381 is eventually deprecated, the experience of standardizing this host-call provides a template for future PQ-pairing host-calls (e.g., isogeny-based or lattice-based signature aggregation).
Acknowledgments
- Polkadot Fellowship Cryptography WG — for the existing native-crypto host-call design patterns this RFC builds on (
host_ed25519_verify,host_sr25519_verify). - Ethereum EIP-2537 authors (Alex Vlasov, Kelly Olson, et al.) — for the encoding standard this RFC adopts to enable cross-chain proof portability.
- Supranational — for
blst, the formally-verified BLS12-381 library that makes a determinism-grade native implementation feasible. polkavmmaintainers — for theSimulatorcost model that produced the empirical Gas measurements motivating this RFC.- arkworks-rs maintainers — for the pure-Rust BLS12-381 implementation used in our differential testing.
Copyright
This document is placed in the public domain via CC0-1.0. The reference implementation referenced in §Reference Implementation is licensed under Apache-2.0, matching the upstream license of blst.