Documentation Index Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Try it Live Run BN254 examples in the interactive playground
BN254 (BN128)
Pairing-friendly elliptic curve implementation for zkSNARK verification and Ethereum’s Alt-BN128 precompiles (0x06-0x08).
Overview
BN254 (also known as BN128 or Alt-BN128) is a Barreto-Naehrig pairing-friendly elliptic curve widely used in zero-knowledge proof systems. It provides efficient pairing operations essential for zkSNARK verification, privacy-preserving protocols, and cryptographic applications requiring bilinear pairings.
Ethereum Use Cases:
zkSNARKs : Zero-knowledge proof verification (Zcash, Tornado Cash, zkSync)
EIP-196 : ECADD precompile (0x06) - G1 point addition
EIP-196 : ECMUL precompile (0x07) - G1 scalar multiplication
EIP-197 : ECPAIRING precompile (0x08) - Optimal ate pairing check
Privacy protocols : Confidential transactions, private voting systems
Quick Start
import * as BN254 from '@tevm/voltaire/crypto/bn254' ;
// G1 operations (base field)
const g1Gen = BN254 . G1 . generator ();
const g1Doubled = BN254 . G1 . add ( g1Gen , g1Gen );
const g1Scaled = BN254 . G1 . mul ( g1Gen , 5 n );
// G2 operations (extension field)
const g2Gen = BN254 . G2 . generator ();
const g2Scaled = BN254 . G2 . mul ( g2Gen , 3 n );
// Pairing check (zkSNARK verification)
const isValid = BN254 . Pairing . pairingCheck ([
[ g1Scaled , g2Gen ],
[ g1Gen , g2Scaled ]
]);
Elliptic Curve Pairing Basics
Pairing-based cryptography uses a special bilinear map e: G1 × G2 → GT that enables:
Bilinearity : e(aP, bQ) = e(P, Q)^(ab) - scalar multiplication distributes
Non-degeneracy : e(G1, G2) ≠ 1 - generator pairing produces non-trivial result
Computability : Pairing computable in polynomial time (optimal ate pairing)
Applications:
Identity-based encryption : Public keys derived from identities
Short signatures : BLS signatures with signature aggregation
zkSNARKs : Succinct non-interactive zero-knowledge proofs
Broadcast encryption : Efficient one-to-many encryption
API Reference
Field Elements
BN254 operates over two finite fields:
Base Field (Fp)
import * as Fp from '@tevm/voltaire/crypto/bn254/Fp' ;
// Field modulus (254 bits)
const p = Fp . MOD ; // 21888242871839275222246405745257275088696311157297823662689037894645226208583n
// Field arithmetic
const a = 123 n ;
const b = 456 n ;
const sum = Fp . add ( a , b );
const prod = Fp . mul ( a , b );
const inv = Fp . inv ( a );
Scalar Field (Fr)
import * as Fr from '@tevm/voltaire/crypto/bn254/Fr' ;
// Scalar field modulus (curve order)
const r = Fr . MOD ; // 21888242871839275222246405745257275088548364400416034343698204186575808495617n
// Scalar arithmetic
const s1 = Fr . mod ( 1234567890 n );
const s2 = Fr . mod ( 9876543210 n );
const product = Fr . mul ( s1 , s2 );
Extension Field (Fp2)
import * as Fp2 from '@tevm/voltaire/crypto/bn254/Fp2' ;
// Quadratic extension Fp2 = Fp[u]/(u^2 + 1)
const elem = Fp2 . create ( 123 n , 456 n ); // 123 + 456u
const squared = Fp2 . square ( elem );
const conjugate = Fp2 . conjugate ( elem ); // 123 - 456u
Group Elements
G1 Points (Base Field)
import * as G1 from '@tevm/voltaire/crypto/bn254/G1' ;
// Generator point
const g = G1 . generator (); // (1, 2)
// Point operations
const doubled = G1 . double ( g );
const sum = G1 . add ( g , doubled );
const scaled = G1 . mul ( g , 42 n );
const negated = G1 . negate ( g );
// Point validation
const isOnCurve = G1 . isOnCurve ( g );
const isZero = G1 . isZero ( G1 . infinity ());
// Serialization (EIP-196 format)
const serialized = BN254 . serializeG1 ( g ); // 64 bytes: x || y
const deserialized = BN254 . deserializeG1 ( serialized );
Curve equation : y^2 = x^3 + 3 over Fp
G2 Points (Extension Field)
import * as G2 from '@tevm/voltaire/crypto/bn254/G2' ;
// Generator point (Fp2 coordinates)
const g2 = G2 . generator ();
// Point operations
const doubled = G2 . double ( g2 );
const sum = G2 . add ( g2 , doubled );
const scaled = G2 . mul ( g2 , 7 n );
const negated = G2 . negate ( g2 );
// Subgroup check
const inSubgroup = G2 . isInSubgroup ( g2 );
// Serialization (EIP-197 format)
const serialized = BN254 . serializeG2 ( g2 ); // 128 bytes: x_c0 || x_c1 || y_c0 || y_c1
const deserialized = BN254 . deserializeG2 ( serialized );
Curve equation : y^2 = x^3 + 3/(9+u) over Fp2
Pairing Operations
Optimal Ate Pairing
import * as Pairing from '@tevm/voltaire/crypto/bn254/Pairing' ;
// Single pairing computation
const g1 = G1 . generator ();
const g2 = G2 . generator ();
const result = Pairing . pair ( g1 , g2 ); // Element in GT (Fp12)
// Check if pairing result equals 1
const isOne = Pairing . pairingResult . isOne ( result );
Pairing Check (zkSNARK Verification)
// Verify pairing equation: e(P1, Q1) * e(P2, Q2) * ... = 1
const pairs = [
[ g1Point1 , g2Point1 ],
[ g1Point2 , g2Point2 ],
[ g1Point3 , g2Point3 ]
];
const isValid = Pairing . pairingCheck ( pairs );
Common pattern (Groth16 zkSNARK verification):
// Verify proof: e(-A, B) * e(alpha, beta) * e(C, delta) = 1
const isValidProof = Pairing . pairingCheck ([
[ negatedA , proofB ],
[ vkAlpha , vkBeta ],
[ proofC , vkDelta ]
]);
Serialization
// EIP-196 format: x (32 bytes) || y (32 bytes)
const g1 = G1 . generator ();
const bytes = BN254 . serializeG1 ( g1 );
// bytes = [x_31, x_30, ..., x_0, y_31, y_30, ..., y_0]
// Point at infinity: (0, 0)
const infinity = G1 . infinity ();
const infinityBytes = BN254 . serializeG1 ( infinity );
// infinityBytes = [0x00 * 64]
// EIP-197 format: x_c0 (32) || x_c1 (32) || y_c0 (32) || y_c1 (32)
const g2 = G2 . generator ();
const bytes = BN254 . serializeG2 ( g2 );
// Fp2 element x = x_c0 + x_c1*u
// Fp2 element y = y_c0 + y_c1*u
Use Cases
zkSNARK Verification
// Verify Groth16 proof
function verifyGroth16Proof (
proof : { A : G1Point , B : G2Point , C : G1Point },
vk : { alpha : G1Point , beta : G2Point , gamma : G2Point , delta : G2Point },
publicInputs : bigint []
) : boolean {
// Compute linear combination of public inputs
const vkX = computePublicInputLinearCombination ( vk , publicInputs );
// Pairing check: e(-A, B) * e(alpha, beta) * e(vkX, gamma) * e(C, delta) = 1
return BN254 . Pairing . pairingCheck ([
[ BN254 . G1 . negate ( proof . A ), proof . B ],
[ vk . alpha , vk . beta ],
[ vkX , vk . gamma ],
[ proof . C , vk . delta ]
]);
}
EIP-196/197 Precompile Calls
// Direct precompile usage (via Zig/Rust)
import { bn254Add , bn254Mul , bn254Pairing } from '@tevm/voltaire/crypto/bn254' ;
// ECADD (0x06): Add two G1 points
const input1 = new Uint8Array ( 128 ); // p1_x || p1_y || p2_x || p2_y
const output1 = Bytes64 ();
bn254Add ( input1 , output1 ); // result_x || result_y
// ECMUL (0x07): Scalar multiply G1 point
const input2 = new Uint8Array ( 96 ); // p_x || p_y || scalar
const output2 = Bytes64 ();
bn254Mul ( input2 , output2 );
// ECPAIRING (0x08): Pairing check
const input3 = new Uint8Array ( 192 * n ); // n pairs of (g1_point || g2_point)
const isValid = bn254Pairing ( input3 ); // boolean
Implementation Details
Rust Implementation (Production - Arkworks)
Library : arkworks (ark-bn254, ark-ec, ark-ff)
FFI : src/crypto/bn254_arkworks.zig
Status : Audited, production-ready
Performance : 3-5x faster than Zig implementation
Use : Recommended for production deployments
Why arkworks?
Battle-tested in Ethereum ecosystem
Constant-time operations (side-channel resistant)
Extensive security audits
Optimized assembly for critical paths
TypeScript Implementation (Reference)
Location : src/crypto/bn254/ (.js files)
Purpose : Pure TS reference, browser compatibility
Features :
Fp, Fp2 field arithmetic
G1, G2 point operations
Pairing computation
Serialization utilities
WASM Builds
Zig fallback : WASM builds use Zig implementation (arkworks unavailable in WASM). WASM performance is ~50% of native arkworks, but fully functional.
Security Considerations
Production Deployments :
Use arkworks (Rust) implementation for native builds
Audited, constant-time operations
Resistant to timing side-channels
Development/Testing :
Zig implementation suitable for testing
Pure implementation aids understanding
No known vulnerabilities, but unaudited
zkSNARK Security :
Verify trusted setup authenticity
Validate proof inputs (prevent malleability)
Check subgroup membership for G2 points
Ensure scalar values in valid range [1, r-1]
Point Validation :
import { Bn254InvalidPointError , Bn254SubgroupCheckError } from '@tevm/voltaire/crypto/bn254' ;
// Always validate deserialized points
const g1 = BN254 . deserializeG1 ( bytes );
if ( ! G1 . isOnCurve ( g1 )) {
throw new Bn254InvalidPointError ( "Invalid G1 point" , {
context: { curve: "G1" },
docsPath: "/crypto/bn254#point-validation"
});
}
const g2 = BN254 . deserializeG2 ( bytes );
if ( ! G2 . isOnCurve ( g2 )) {
throw new Bn254InvalidPointError ( "Invalid G2 point" , {
context: { curve: "G2" },
docsPath: "/crypto/bn254#point-validation"
});
}
if ( ! G2 . isInSubgroup ( g2 )) {
throw new Bn254SubgroupCheckError ( "G2 point not in subgroup" , {
context: { curve: "G2" },
docsPath: "/crypto/bn254#subgroup-check"
});
}
Native (Arkworks Rust) :
ECADD: ~0.02ms
ECMUL: ~0.15ms
Pairing: ~1.5ms
Pairing check (2 pairs): ~2.5ms
WASM (Zig) :
ECADD: ~0.05ms
ECMUL: ~0.3ms
Pairing: ~3ms
Pairing check (2 pairs): ~5ms
Constants
import { FP_MOD , FR_MOD , B_G1 , G1_GENERATOR_X , G1_GENERATOR_Y } from '@tevm/voltaire/crypto/bn254' ;
// Field modulus (254 bits)
FP_MOD // 21888242871839275222246405745257275088696311157297823662689037894645226208583n
// Curve order (254 bits)
FR_MOD // 21888242871839275222246405745257275088548364400416034343698204186575808495617n
// G1 curve parameter: y^2 = x^3 + 3
B_G1 // 3n
// G1 generator point
G1_GENERATOR_X // 1n
G1_GENERATOR_Y // 2n
References