(source)

Table of Contents

RFC-0164: BLS12-381 Pairing Verification Host-Call for JAM PVM

Start Date2026-06-13
DescriptionAdd 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).
AuthorsFlux Protocol Team
LicenseThis 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:

  1. 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.
  2. 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.
  3. 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 ModelGas (per Groth16 verify)% G_RVerifications/G_R
Simple (naive 1 g/insn)350,303,2277.00%~14
Full(L1Hit) — Gray Paper recommended664,943,52013.30%~7
Full(L2Hit)1,053,842,96821.08%~4
Full(L3Hit)1,314,283,12126.29%~3
Native host-call (this RFC, projected)~150,0000.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

  1. 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.
  2. 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.
  3. Threshold cryptography & DKG: BLS multi-signature aggregation and verification at scale.
  4. 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_256 Gas economics).
  • Multi-pairing primitive: support Π e(Pᵢ, Qᵢ) == 1_GT for 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_g2 host-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: blst crate) 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-RV32 BLS12-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)

RegisterNameDescription
a0nNumber of pairing terms (must satisfy 1 ≤ n ≤ 16)
a1g1_ptrPointer to packed array of n G1 affine points (each 96 bytes uncompressed, big-endian, EIP-2537)
a2g2_ptrPointer to packed array of n G2 affine points (each 192 bytes uncompressed, big-endian, EIP-2537)
a3out_ptrPointer to a 1-byte output buffer (set to 0x01 if equality holds, 0x00 otherwise)

Return values (in a0 after the host-call)

CodeMeaning
0OK — out_ptr[0] contains the verification result
1INVALID_INPUT — a point failed subgroup-check or is malformed
2OOB — input/output pointers are out of guest memory bounds
3OOG — host-call cost exceeds remaining gas (no state mutation)
4INVALID_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) with c0 first, 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):

Operationx86_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 each miller_loop plus 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-callGasNative timeGas/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

  1. Implementation complexity: each JAM node implementation must integrate a BLS12-381 pairing library. Mitigated by blst being mature, audited, and Apache-2.0 licensed; it is already a transitive dependency of most Substrate-derived nodes.
  2. 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.
  3. 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.
  4. 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-381 test 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 blstarkworks differential 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_GT versus pairs that do not.

Security

  • Subgroup attack mitigation: mandatory subgroup check on all inputs prevents small-subgroup attacks and pairing inconsistencies.
  • Constant-time execution: blst provides 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_verify to 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):

#DescriptionExpected outExpected a0
1Identity pairing: e(0_G1, 0_G2) == 1_GT10 (OK)
2Generator pairing: e(g1, g2) (single pair)00 (OK)
3Bilinearity: e(g1, g2) * e(-g1, g2) == 1_GT10 (OK)
4Groth16 valid (vk, proof, pi) from reference set10 (OK)
5Groth16 tampered proof (single byte flip)00 (OK)
6Off-curve G1 point1 (INVALID)
7Off-subgroup G2 point (curve OK, subgroup fail)1 (INVALID)
8n = 04 (INVALID_N)
9n = 174 (INVALID_N)
10g1_ptr past end of guest memory2 (OOB)
11Insufficient remaining gas for n=163 (OOG)
12Mismatched-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.
  • blst crate: 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-RV32 BLS12-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

  1. ecalli index allocation: the concrete ω index for bls12_pairing_verify should be coordinated with the Gray Paper editors and other concurrent host-call RFCs to avoid collisions.
  2. MSM follow-up scope: should bls12_msm_g1 and bls12_msm_g2 be 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).
  3. 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).
  4. Gas-cost calibration: the proposed 50,000 + 50,000 × n is 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.
  5. 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.
  • 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.
  • polkavm maintainers — for the Simulator cost 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.

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.