Skip to main content

Try it Live

Run BLS12-381 examples in the interactive playground
Future Plans: This page is planned and under active development. Examples are placeholders and will be replaced with accurate, tested content.

Pairing Operations

The pairing operation is the mathematical foundation that makes BLS signatures possible. It’s a bilinear map that enables signature aggregation and efficient batch verification.

Pairing Definition

Optimal Ate Pairing: e: G1 × G2 → GT Maps two elliptic curve points to an element in a multiplicative group GT (subgroup of Fp12).

Mathematical Properties

Bilinearity:
e(aP, bQ) = e(P, Q)^(ab)  for all a,b ∈ Fr, P ∈ G1, Q ∈ G2
Non-degeneracy:
e(G1_generator, G2_generator) ≠ 1
Efficiency: Computable in polynomial time (~1-2ms)

Algorithm Overview

Miller Loop

Core of pairing computation. Evaluates line functions along curve doubling/addition:
Miller Loop Constant (BLS12-381):
t = 0xd201000000010000 (curve parameter)

Iterations: 64 (bit length of t)
Steps:
  1. Initialize f = 1, T = Q
  2. For each bit of t (from MSB):
    • Double: f ← f² · l_T,T(P), T ← 2T
    • If bit is 1: f ← f · l_T,Q(P), T ← T + Q
  3. Return f

Final Exponentiation

Raises Miller loop result to specific power to ensure result is in prime-order subgroup:
exponent = (p^12 - 1) / r
where p = field modulus, r = curve order
Optimization: Split into easy part and hard part
  • Easy: (p^6 - 1)(p^2 + 1)
  • Hard: Cyclotomic exponentiation

Usage

Single Pairing

import { bls12_381 } from '@tevm/voltaire/crypto';

async function computePairing(
  g1Point: Uint8Array,  // 128 bytes
  g2Point: Uint8Array   // 256 bytes
): Promise<Uint8Array> {
  const input = new Uint8Array(384);
  input.set(g1Point, 0);
  input.set(g2Point, 128);

  const output = Bytes32();
  await bls12_381.pairing(input, output);

  // Note: Precompile returns pairing CHECK (result == 1)
  // For raw pairing value, would need different API
  return output;
}

Multi-Pairing (Product Check)

BLS12-381 precompile computes:
e(P1, Q1) · e(P2, Q2) · ... · e(Pn, Qn) == 1
async function multiPairingCheck(
  pairs: Array<{g1: Uint8Array, g2: Uint8Array}>
): Promise<boolean> {
  const n = pairs.length;
  const input = new Uint8Array(384 * n);

  for (let i = 0; i < n; i++) {
    const offset = 384 * i;
    input.set(pairs[i].g1, offset);
    input.set(pairs[i].g2, offset + 128);
  }

  const output = Bytes32();
  await bls12_381.pairing(input, output);

  return output[31] === 0x01;
}

BLS Signature Verification

Pairing enables signature verification: Verification Equation:
e(signature, G2_generator) = e(H(message), publicKey)
Rearranged for single pairing check:
e(signature, G2_gen) · e(-H(message), pubkey) = 1
async function verifySignature(
  signature: Uint8Array,   // G1
  publicKey: Uint8Array,   // G2
  message: Uint8Array
): Promise<boolean> {
  const messagePoint = await hashToG1(message);
  const negMessage = negateG1(messagePoint);

  return multiPairingCheck([
    { g1: signature, g2: G2_GENERATOR },
    { g1: negMessage, g2: publicKey }
  ]);
}

Optimization Techniques

Precomputation

For fixed G2 points, precompute line functions:
// Validator pubkeys are fixed - precompute once
const precomputedPubKey = precomputeG2Lines(validatorPubKey);

// Reuse in multiple verifications
await verifyWithPrecomputed(signature, precomputedPubKey, message);

Batch Verification

Verify n signatures with n+1 pairings instead of 2n:
Product(e(sig_i, G2)) = Product(e(H(msg_i), pubkey_i))
Cost: ~2ms + 23ms × n vs ~2ms × 2n

Miller Loop Reuse

When G2 points are identical, Miller loop needs computation only once.

Field Arithmetic

Fp12 Tower Extension

Fp → Fp2 → Fp6 → Fp12

Fp2 = Fp[u] / (u² + 1)
Fp6 = Fp2[v] / (v³ - (1 + u))
Fp12 = Fp6[w] / (w² - v)

Frobenius Endomorphism

Fast exponentiation in extension fields:
φ(x) = x^p  (Frobenius map)

For x ∈ Fp12: φ(x) computable via coordinate transformation
Used in: Final exponentiation optimization

Security Considerations

Subgroup Checks

Critical: Verify points are in prime-order subgroups
  • G1 subgroup: order r (255-bit)
  • G2 subgroup: order r (cofactor h2 = large)
  • GT subgroup: order r
Attack: Invalid curve attacks if subgroup not checked
// BLST automatically validates:
// - Point on curve
// - Point in correct subgroup
// - Field element validity

// Will throw error if invalid
await bls12_381.pairing(input, output);

Pairing Inversion

Infeasible: Computing Q from e(P, Q) given P and result No known attack faster than ~2^128 operations.

Performance

Native (BLST):
  • Single pairing: ~1.2 ms
  • Miller loop: ~0.8 ms
  • Final exponentiation: ~0.4 ms
  • Multi-pairing (n pairs): ~1.2ms + 0.9ms × n
Comparison:
  • BN254 pairing: ~0.6 ms (less secure)
  • BLS12-377: ~2 ms (more secure)
  • BLS24-315: ~5 ms (quantum-resistant candidate)

Implementation

Source: src/crypto/crypto.zig Uses BLST library (C) via FFI:
  • Optimized Miller loop
  • Assembly-accelerated field arithmetic
  • Constant-time operations

References