Skip to main content

Precompiles Reference

EVM precompiled contracts in src/evm/precompiles/. Each has a fixed address and provides optimized operations.

Overview

import { execute, isPrecompile } from "@voltaire/evm/precompiles";

// Check if address is precompile
isPrecompile(address, hardfork);

// Execute precompile
const { output, gasUsed } = execute(address, input, gasLimit, hardfork);

Precompile Addresses

AddressNameHardforkPurpose
0x01ECRECOVERFrontierSignature recovery
0x02SHA256FrontierSHA-256 hash
0x03RIPEMD160FrontierRIPEMD-160 hash
0x04IDENTITYFrontierData copy
0x05MODEXPByzantiumModular exponentiation
0x06BN254_ADDByzantiumBN254 G1 addition
0x07BN254_MULByzantiumBN254 G1 multiplication
0x08BN254_PAIRINGByzantiumBN254 pairing check
0x09BLAKE2FIstanbulBlake2 compression
0x0APOINT_EVALUATIONCancunKZG point evaluation
0x0BBLS12_G1_ADDPragueBLS12-381 G1 addition
0x0CBLS12_G1_MULPragueBLS12-381 G1 multiplication
0x0DBLS12_G1_MSMPragueBLS12-381 G1 multi-scalar mult
0x0EBLS12_G2_ADDPragueBLS12-381 G2 addition
0x0FBLS12_G2_MULPragueBLS12-381 G2 multiplication
0x10BLS12_G2_MSMPragueBLS12-381 G2 multi-scalar mult
0x11BLS12_PAIRINGPragueBLS12-381 pairing check
0x12BLS12_MAP_FP_TO_G1PragueMap field element to G1
0x13BLS12_MAP_FP2_TO_G2PragueMap field element to G2

Frontier Precompiles (0x01-0x04)

ECRECOVER (0x01)

Recovers signer address from ECDSA signature. Input (128 bytes):
  • [0:32] - Message hash
  • [32:64] - v (recovery id, 27 or 28)
  • [64:96] - r (signature component)
  • [96:128] - s (signature component)
Output: 32 bytes (address right-padded) Gas: 3000
const input = concat(messageHash, v, r, s);
const { output } = execute(0x01, input, 3000n);
const address = output.slice(12);  // Last 20 bytes
Files: ecrecover.zig, ecrecover.test.ts, ecrecover.bench.ts

SHA256 (0x02)

Computes SHA-256 hash. Input: Arbitrary bytes Output: 32 bytes (hash) Gas: 60 + 12 * ceil(len / 64)
const { output } = execute(0x02, data, 1000n);
// output is SHA-256 hash
Files: sha256.zig, sha256.test.ts, sha256.bench.ts

RIPEMD160 (0x03)

Computes RIPEMD-160 hash. Input: Arbitrary bytes Output: 32 bytes (20-byte hash left-padded with zeros) Gas: 600 + 120 * ceil(len / 64)
const { output } = execute(0x03, data, 1000n);
const hash = output.slice(12);  // Last 20 bytes
Files: ripemd160.zig, ripemd160.test.ts, ripemd160.bench.ts

IDENTITY (0x04)

Returns input unchanged. Used for memory copying in contracts. Input: Arbitrary bytes Output: Same as input Gas: 15 + 3 * ceil(len / 32)
const { output } = execute(0x04, data, 100n);
// output === data
Files: identity.zig, identity.test.ts, identity.bench.ts

Byzantium Precompiles (0x05-0x08)

MODEXP (0x05)

Modular exponentiation: base^exp mod mod. Input:
  • [0:32] - base length (Bsize)
  • [32:64] - exponent length (Esize)
  • [64:96] - modulus length (Msize)
  • [96:96+Bsize] - base
  • [96+Bsize:96+Bsize+Esize] - exponent
  • [96+Bsize+Esize:96+Bsize+Esize+Msize] - modulus
Output: Msize bytes (result) Gas: Complex formula based on input sizes (see EIP-2565)
const input = concat(
  padLeft(baseLen, 32),
  padLeft(expLen, 32),
  padLeft(modLen, 32),
  base,
  exp,
  mod
);
const { output } = execute(0x05, input, gasLimit);
Files: modexp.zig, modexp.test.ts, modexp.bench.ts

BN254_ADD (0x06)

Adds two points on BN254 G1 curve. Input (128 bytes):
  • [0:32] - x1
  • [32:64] - y1
  • [64:96] - x2
  • [96:128] - y2
Output: 64 bytes (x3, y3) Gas: 150 (post-Istanbul)
const input = concat(p1.x, p1.y, p2.x, p2.y);
const { output } = execute(0x06, input, 150n);
const result = { x: output.slice(0, 32), y: output.slice(32, 64) };
Files: bn254_add.zig, bn254_precompiles.test.ts

BN254_MUL (0x07)

Scalar multiplication on BN254 G1 curve. Input (96 bytes):
  • [0:32] - x
  • [32:64] - y
  • [64:96] - scalar
Output: 64 bytes (x_result, y_result) Gas: 6000 (post-Istanbul) Files: bn254_mul.zig

BN254_PAIRING (0x08)

Pairing check on BN254 curve. Input: k * 192 bytes (k pairs of G1, G2 points)
  • Each pair: G1 (64 bytes) + G2 (128 bytes)
Output: 32 bytes (1 if pairing check passes, 0 otherwise) Gas: 34000 * k + 45000 (post-Istanbul)
// Verify e(A1, B1) * e(A2, B2) * ... = 1
const input = concat(...pairs.flatMap(p => [p.g1, p.g2]));
const { output } = execute(0x08, input, gasLimit);
const valid = output[31] === 1;
Files: bn254_pairing.zig

Istanbul Precompile (0x09)

BLAKE2F (0x09)

Blake2b compression function F. Input (213 bytes):
  • [0:4] - rounds (big-endian uint32)
  • [4:68] - h (state, 8 uint64s)
  • [68:196] - m (message block, 16 uint64s)
  • [196:212] - t (offset counter, 2 uint64s)
  • [212:213] - f (final block flag, 0 or 1)
Output: 64 bytes (new state) Gas: rounds
const input = concat(rounds, h, m, t, f);
const { output } = execute(0x09, input, roundsValue);
Files: blake2f.zig, blake2f.test.ts, blake2f.bench.ts

Cancun Precompile (0x0A)

POINT_EVALUATION (0x0A)

KZG point evaluation for EIP-4844 blob verification. Input (192 bytes):
  • [0:32] - versioned hash
  • [32:64] - z (evaluation point)
  • [64:96] - y (claimed evaluation)
  • [96:144] - commitment (48 bytes)
  • [144:192] - proof (48 bytes)
Output: 64 bytes
  • [0:32] - FIELD_ELEMENTS_PER_BLOB (4096)
  • [32:64] - BLS_MODULUS
Gas: 50000
const input = concat(versionedHash, z, y, commitment, proof);
const { output } = execute(0x0A, input, 50000n);
// Success if no error thrown
Files: point_evaluation.zig, precompiles.kzg.test.ts

Prague Precompiles (0x0B-0x13)

BLS12-381 curve operations for beacon chain compatibility.

BLS12_G1_ADD (0x0B)

Add two G1 points. Input: 256 bytes (two 128-byte G1 points) Output: 128 bytes (G1 point) Gas: 500

BLS12_G1_MUL (0x0C)

Scalar multiply G1 point. Input: 160 bytes (128-byte G1 point + 32-byte scalar) Output: 128 bytes (G1 point) Gas: 12000

BLS12_G1_MSM (0x0D)

Multi-scalar multiplication on G1. Input: k * 160 bytes (k pairs of point + scalar) Output: 128 bytes (G1 point) Gas: Discount formula based on k

BLS12_G2_ADD (0x0E)

Add two G2 points. Input: 512 bytes (two 256-byte G2 points) Output: 256 bytes (G2 point) Gas: 800

BLS12_G2_MUL (0x0F)

Scalar multiply G2 point. Input: 288 bytes (256-byte G2 point + 32-byte scalar) Output: 256 bytes (G2 point) Gas: 45000

BLS12_G2_MSM (0x10)

Multi-scalar multiplication on G2. Input: k * 288 bytes Output: 256 bytes (G2 point) Gas: Discount formula based on k

BLS12_PAIRING (0x11)

Pairing check. Input: k * 384 bytes (k pairs of G1 + G2) Output: 32 bytes (1 or 0) Gas: 43000 * k + 65000

BLS12_MAP_FP_TO_G1 (0x12)

Map field element to G1 point. Input: 64 bytes (field element) Output: 128 bytes (G1 point) Gas: 5500

BLS12_MAP_FP2_TO_G2 (0x13)

Map Fp2 element to G2 point. Input: 128 bytes (Fp2 element) Output: 256 bytes (G2 point) Gas: 75000 Files:
  • bls12_g1_add.zig, bls12_g1_mul.zig, bls12_g1_msm.zig
  • bls12_g2_add.zig, bls12_g2_mul. zig, bls12_g2_msm.zig
  • bls12_pairing.zig
  • bls12_map_fp_to_g1.zig, bls12_map_fp2_to_g2.zig
  • precompiles.bls12.test.ts

Common Utilities

root.zig exports

// Address constants
pub const ECRECOVER_ADDRESS: Address = 0x01;
pub const SHA256_ADDRESS: Address = 0x02;
// ... etc

// Check if address is precompile
pub fn isPrecompile(address: Address, hardfork: Hardfork) bool;

// Execute precompile
pub fn execute(address: Address, input: []const u8, gas: u64, hardfork: Hardfork) !ExecuteResult;

common.zig

Shared utilities:
  • Input validation
  • Gas calculation helpers
  • Point encoding/decoding
  • Error types

utils.zig

  • Big integer operations
  • Field element arithmetic
  • Padding utilities

Gas Costs by Hardfork

PrecompileFrontierByzantiumIstanbulBerlin
ECRECOVER3000300030003000
SHA25660+12/word60+12/word60+12/word60+12/word
RIPEMD160600+120/word600+120/word600+120/word600+120/word
IDENTITY15+3/word15+3/word15+3/word15+3/word
MODEXP-ComplexEIP-2565EIP-2565
BN254_ADD-500150150
BN254_MUL-4000060006000
BN254_PAIRING-80k+base34k*k+45k34k*k+45k
BLAKE2F--roundsrounds

Error Handling

Precompiles return errors for:
  • Invalid input length
  • Invalid curve points (not on curve)
  • Invalid field elements (>= modulus)
  • Insufficient gas
  • Unsupported hardfork
pub const PrecompileError = error{
    InvalidInputLength,
    InvalidPoint,
    InvalidFieldElement,
    OutOfGas,
    NotSupported,
};