Skip to main 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