Skip to main content
Demonstrates BN254 elliptic curve pairing operations inside the zkVM, a fundamental building block for advanced cryptographic schemes.

What You’ll Learn

  • BN254 curve pairing operations
  • Working with G1 and G2 groups
  • Batch pairing verification
  • Cryptographic building blocks for signature schemes

Overview

The BN254 (also known as BN128 or alt_bn128) curve is widely used in blockchain systems, particularly Ethereum. This example demonstrates core pairing functionality that underlies:
  • zkSNARK verification (Groth16, PLONK)
  • BLS signatures
  • Signature aggregation
  • Verifiable random functions (VRFs)
This is a toy example for educational purposes. Do not use directly in production cryptographic systems.

Mathematical Background

Pairing Operation

A pairing is a bilinear map:
e: G1 × G2 → GT
With properties:
  • Bilinearity: e(aP, bQ) = e(P, Q)^(ab)
  • Non-degeneracy: e(g1, g2) ≠ 1

What This Example Proves

Given base points g1 ∈ G1 and g2 ∈ G2 and random scalars a and b, prove:
pair(a × g1, b × g2) * pair(g1, -ab × g2) = 1
This equality holds due to bilinearity:
e(a·g1, b·g2) · e(g1, -ab·g2)
= e(g1, g2)^(ab) · e(g1, g2)^(-ab)
= e(g1, g2)^(ab - ab)
= e(g1, g2)^0
= 1

How It Works

1
Guest Program
2
The guest performs batch pairing and verifies the result is the identity:
3
use risc0_zkvm::guest::env;
use substrate_bn::{Fr, G1, G2, Gt, pairing_batch};

fn main() {
    let inp: bn254_core::Inputs = env::read();

    // Decompress points
    let g1 = G1::from_compressed(&inp.g1_compressed)
        .expect("Point on G1 expected");
    let g2 = G2::from_compressed(&inp.g2_compressed)
        .expect("Point on G2 expected");
    
    // Parse scalar factors
    let a_factor = Fr::from_slice(&inp.a)
        .expect("Scalar factor expected");
    let b_factor = Fr::from_slice(&inp.b)
        .expect("Scalar factor expected");

    // Compute batch pairing:
    // e(a·g1, b·g2) · e(g1, -ab·g2)
    let mut pairs = Vec::new();
    pairs.push((g1 * a_factor, g2 * b_factor));
    pairs.push((g1, g2 * (-a_factor * b_factor)));
    let result = pairing_batch(&pairs);

    // Check if result is identity
    env::commit(&(result == Gt::one()));
}
4
Host Program
5
The host prepares test data and verifies the proof:
6
use bn254_methods::{BN254_VERIFY_ELF, BN254_VERIFY_ID};
use risc0_zkvm::{ExecutorEnv, default_prover};

fn main() {
    // g1 and g2 are generators from EIP-197
    let g1_compressed = hex::decode(
        "020000000000000000000000000000000000000000000000000000000000000001"
    ).expect("valid hex");
    
    let g2_compressed = hex::decode(
        "0A04D4BF3239F77CEE7B47C7245E9281B3E9C1182D6381A87BBF81F9F2A6254B\
         731DF569CDA95E060BEE91BA69B3F2D103658A7AEA6B10E5BDC761E5715E7EE4BB"
    ).expect("valid hex");
    
    // Random scalars chosen for demonstration
    let a = hex::decode(
        "9c0d02eaaf8e7e7ad09595ef6e3b896f8915124ba5bef9287f0997557580caeb"
    ).expect("valid hex");
    let b = hex::decode(
        "db6764642f7bb1f415d93fcd5aace586161ec2e4305f0d6fb57dbabf1d141a5b"
    ).expect("valid hex");

    let input = bn254_core::Inputs {
        g1_compressed,
        g2_compressed,
        a,
        b,
    };

    let env = ExecutorEnv::builder()
        .write(&input)
        .unwrap()
        .build()
        .unwrap();

    let receipt = default_prover()
        .prove(env, BN254_VERIFY_ELF)
        .unwrap()
        .receipt;

    receipt.verify(BN254_VERIFY_ID).unwrap();

    let is_one: bool = receipt.journal.decode()
        .expect("Journal should contain a single bool");
    
    println!("Pairing batch should give one; did it? {}", is_one);
}

Running the Example

cargo run --release
Output:
Pairing batch should give one; did it? true

What Gets Proven?

The receipt proves:
  1. Correct Pairing: Batch pairing was computed correctly
  2. Identity Result: The pairing result equals the identity element
  3. Bilinearity: The cryptographic relationship holds

BN254 Curve Details

Groups

  • G1: Points on E(Fp) where E: y² = x³ + 3
  • G2: Points on E'(Fp²) where E': y² = x³ + 3/(9+u)
  • GT: Elements in Fp¹²

Parameters

  • Field size: 254 bits
  • Embedding degree: 12
  • Security level: ~100 bits (estimated)
BN254 security has been downgraded from 128 bits due to recent advances. Consider BLS12-381 for new applications.

EIP-197 Compatibility

This example uses generators from EIP-197, making it compatible with Ethereum’s precompiled contracts:
// Ethereum precompiles
address constant PAIRING = address(0x08);

Use Cases

zkSNARK Verification

Verify Groth16 proofs using pairing checks:
e(A, B) = e(α, β) · e(L, γ) · e(C, δ)
See the Groth16 verifier example.

BLS Signatures

Verify aggregate signatures:
e(H(m), σ) = e(g2, pk)
Where:
  • H(m): Hash-to-curve of message
  • σ: Signature in G1
  • pk: Public key in G2

Signature Aggregation

Combine multiple signatures into one:
σ_agg = σ_1 + σ_2 + ... + σ_n
Verify with batch pairing:
e(H(m1), σ_1) · e(H(m2), σ_2) · ... = e(g2, pk1) · e(g2, pk2) · ...

Verifiable Encryption

Prove ciphertext validity:
e(c1, pk) = e(g1, c2)

Substrate BN Library

This example uses the substrate_bn crate:
use substrate_bn::{Fr, G1, G2, Gt, pairing_batch};

// Scalar field
let scalar = Fr::from_slice(&bytes)?;

// Group operations
let point = g1 * scalar;  // Scalar multiplication
let sum = p1 + p2;        // Point addition

// Pairing
let result = pairing_batch(&[
    (g1_point1, g2_point1),
    (g1_point2, g2_point2),
]);

Point Compression

Points are compressed for efficient transmission:
// Compress
let compressed = g1_point.compress();

// Decompress
let g1_point = G1::from_compressed(&compressed)?;
Compressed sizes:
  • G1: 32 bytes
  • G2: 64 bytes

Performance

OperationCycles (approx)
G1 scalar mul~500K
G2 scalar mul~1M
Single pairing~10M
Batch pairing (2)~15M
Batch pairing is more efficient than individual pairings due to shared computations.

Cryptographic Applications

This building block enables:
  1. Zero-Knowledge Proofs: Groth16, PLONK verification
  2. Aggregate Signatures: BLS signature schemes
  3. Threshold Cryptography: Distributed key generation
  4. Identity-Based Encryption: Boneh-Franklin IBE
  5. Verifiable Delay Functions: Chia VDF

Security Considerations

This is educational code. Production cryptographic systems require:
  • Careful parameter selection
  • Constant-time implementations
  • Thorough security audits
  • Up-to-date security analysis

Known Issues

  • BN254 security degraded to ~100 bits
  • Vulnerable to number field sieve attacks
  • Consider BLS12-381 for new systems

Best Practices

  • Validate all points are on the curve
  • Check points are in the correct subgroup
  • Use constant-time operations for secrets
  • Never reuse randomness

Comparison to Other Curves

CurveSecurityG1 SizeG2 SizeSpeedEthereum
BN254~100 bit32 B64 BFastNative
BLS12-381~128 bit48 B96 BMediumPlanned
BLS12-377~128 bit48 B96 BMediumNo

Next Steps

Build docs developers (and LLMs) love