EVM Precompiled Contracts: What They Are and How They Work

Complete guide to all 10 EVM precompiled contracts at addresses 0x01-0x0a. Covers gas costs, cryptographic operations, zk-SNARK support, and Solidity usage.

By Zig EVM Team |

TL;DR: EVM precompiled contracts are special contracts hardcoded at addresses 0x01 through 0x0a that execute computationally expensive operations — primarily cryptographic primitives — in optimized native code rather than EVM bytecode. They exist because operations like elliptic curve pairing or modular exponentiation would cost millions of gas if implemented in Solidity. There are currently 10 precompiles covering signature recovery, hashing, big-number math, zk-SNARK operations, and KZG verification. This guide covers every precompile, its gas model, real-world usage, and how to call each one from Solidity.

Why Precompiled Contracts Exist

The EVM is a general-purpose computation engine, but it was never designed for heavy cryptographic math. Consider what it takes to verify an ECDSA signature purely in EVM opcodes: you would need 256-bit modular arithmetic, elliptic curve point multiplication, and field inversions — all implemented on a stack machine with 256-bit words. The gas cost would be astronomical, likely exceeding the block gas limit for a single operation.

Precompiled contracts solve this problem by moving expensive computations out of the EVM and into the client’s native code. When the EVM encounters a CALL to a precompile address, it does not execute bytecode. Instead, it routes the call input to a hardcoded function written in Go (geth), Rust (reth), or in our case, Zig. The function executes, returns its output, and deducts a predetermined gas cost.

This design pattern gives Ethereum three critical capabilities:

  1. Signature verification (ecrecover) that every transaction validation depends on
  2. Cross-chain hash compatibility (SHA-256, RIPEMD-160) for Bitcoin SPV proofs and other integrations
  3. Zero-knowledge proof verification (BN256 curve operations, blake2f) that powers zk-rollups

Complete List of All 10 Precompiles

Here is every precompile currently active on Ethereum mainnet:

AddressNameEIPGas CostPurpose
0x01ecrecoverFrontier3,000 (flat)ECDSA signature recovery
0x02SHA-256Frontier60 + 12 per wordSHA-256 hash
0x03RIPEMD-160Frontier600 + 120 per wordRIPEMD-160 hash
0x04identityFrontier15 + 3 per wordData copy (returns input)
0x05modexpByzantium (EIP-198)Dynamic (see below)Modular exponentiation
0x06ecAddByzantium (EIP-196)150BN256 elliptic curve addition
0x07ecMulByzantium (EIP-196)6,000BN256 elliptic curve scalar multiplication
0x08ecPairingByzantium (EIP-197)45,000 + 34,000 per pairBN256 pairing check
0x09blake2fIstanbul (EIP-152)1 per roundBLAKE2b F compression
0x0apointEvaluationDencun (EIP-4844)50,000 (flat)KZG point evaluation

Deep Dive: Each Precompile Explained

0x01 — ecrecover: ECDSA Signature Recovery

The most frequently called precompile on Ethereum. Given a message hash and an ECDSA signature (v, r, s), ecrecover returns the Ethereum address that produced the signature.

Input format (128 bytes):

  • Bytes 0-31: Message hash
  • Bytes 32-63: Recovery identifier v (27 or 28)
  • Bytes 64-95: Signature r value
  • Bytes 96-127: Signature s value

Output: 32 bytes containing the recovered address (left-padded with zeros)

Gas cost: 3,000 (flat, regardless of input)

Real-world usage: Every require(msg.sender == signer) pattern in permit-style functions, ERC-2612 token permits, meta-transactions, and account abstraction signature validation.

// Solidity has a built-in wrapper for ecrecover
address signer = ecrecover(messageHash, v, r, s);
require(signer == expectedAddress, "Invalid signature");

// Under the hood, this compiles to a staticcall to address 0x01

0x02 — SHA-256: SHA-256 Hash

Computes the SHA-256 hash of arbitrary input data. While Ethereum natively uses Keccak-256 (available via the SHA3 opcode), SHA-256 is necessary for interoperability with Bitcoin, TLS certificates, and many off-chain systems.

Gas cost: 60 base + 12 per 32-byte word of input

Real-world usage: Bitcoin relay contracts (verifying Bitcoin block headers), cross-chain bridges, and any protocol that needs SHA-256 compatibility.

0x03 — RIPEMD-160: RIPEMD-160 Hash

Computes the RIPEMD-160 hash. This exists primarily for Bitcoin address derivation, which uses RIPEMD-160(SHA-256(pubkey)).

Gas cost: 600 base + 120 per 32-byte word of input

Real-world usage: Bitcoin SPV proof verification, Bitcoin-Ethereum bridge contracts.

0x04 — identity: Data Copy

The simplest precompile — it returns exactly the data it receives. This may seem useless, but it serves as an efficient way to copy memory in contexts where CALLDATACOPY is not available.

Gas cost: 15 base + 3 per 32-byte word

Real-world usage: Memory copying in inline assembly, testing frameworks, and gas-efficient data forwarding.

0x05 — modexp: Modular Exponentiation

Computes base^exp mod modulus for arbitrarily large integers. This is the workhorse precompile for RSA signature verification and other big-number cryptographic operations.

Input format: ABI-encoded with length-prefixed base, exponent, and modulus values.

Gas cost: Dynamic, based on the sizes of the inputs. The formula accounts for the bit-length of the exponent and the byte-length of the modulus. For a 2048-bit RSA verification, expect roughly 1,360 gas.

// Verifying an RSA signature on-chain using modexp
function verifyRSA(
    bytes memory base,
    bytes memory exponent,
    bytes memory modulus
) internal view returns (bytes memory) {
    bytes memory input = abi.encodePacked(
        uint256(base.length),
        uint256(exponent.length),
        uint256(modulus.length),
        base, exponent, modulus
    );

    (bool success, bytes memory result) = address(0x05).staticcall(input);
    require(success, "modexp failed");
    return result;
}

0x06 and 0x07 — ecAdd and ecMul: BN256 Curve Operations

These precompiles perform point addition and scalar multiplication on the alt_bn128 (BN256) elliptic curve. They are fundamental building blocks for zk-SNARK proof verification.

ecAdd (0x06):

  • Input: Two curve points (128 bytes total, 64 bytes per point)
  • Output: The sum point (64 bytes)
  • Gas cost: 150

ecMul (0x07):

  • Input: A curve point (64 bytes) and a scalar (32 bytes)
  • Output: The product point (64 bytes)
  • Gas cost: 6,000

Real-world usage: Groth16 proof verification (used by Zcash, Tornado Cash, and many zk-rollups). A typical Groth16 verifier calls ecMul multiple times and ecAdd to accumulate the results before a final pairing check.

0x08 — ecPairing: BN256 Pairing Check

The most computationally expensive precompile. It performs a bilinear pairing check on the BN256 curve, which is the final step in verifying a zk-SNARK proof.

Input: A sequence of (G1 point, G2 point) pairs. Each pair is 192 bytes (64 bytes for G1 + 128 bytes for G2).

Output: 32 bytes — 1 if the pairing check passes, 0 otherwise.

Gas cost: 45,000 base + 34,000 per input pair. A typical Groth16 verification with 4 pairs costs 45,000 + 4 * 34,000 = 181,000 gas.

Without this precompile, a single pairing operation would require tens of millions of gas in pure EVM bytecode, making zk-SNARK verification economically impossible.

0x09 — blake2f: BLAKE2b Compression

Implements the F compression function of BLAKE2b. Unlike SHA-256 which computes a complete hash, blake2f exposes just the compression function, giving developers flexibility to build custom hashing schemes.

Input (213 bytes):

  • 4 bytes: Number of rounds
  • 64 bytes: State vector h
  • 128 bytes: Message block
  • 16 bytes: Offset counters
  • 1 byte: Final block flag

Gas cost: 1 gas per round. Typical usage with 12 rounds costs just 12 gas.

Real-world usage: Zcash Equihash verification, interoperability with Substrate-based chains that use BLAKE2b, and custom hash constructions.

0x0a — pointEvaluation: KZG Point Evaluation (EIP-4844)

The newest precompile, added in the Dencun upgrade (March 2024). It verifies that a KZG polynomial commitment opens to a specific value at a given point.

Input (192 bytes):

  • 32 bytes: Versioned hash
  • 32 bytes: Evaluation point z
  • 32 bytes: Claimed evaluation y
  • 48 bytes: KZG commitment
  • 48 bytes: KZG proof

Output: Two 32-byte field elements on success (the BLS modulus and the evaluation verification)

Gas cost: 50,000 (flat)

Real-world usage: This precompile is the verification backbone of EIP-4844 blob transactions. Layer 2 rollups (Optimism, Arbitrum, Base) post blob data to Ethereum, and the point evaluation precompile allows contracts to verify specific pieces of that blob data without accessing the full blob. This is what makes proto-danksharding work.

How the EVM Invokes Precompiles

When the EVM processes a CALL, STATICCALL, or DELEGATECALL instruction, it checks whether the target address is in the precompile range (0x01 through 0x0a). If it is, the EVM skips bytecode execution entirely and instead:

  1. Reads the call input data from memory
  2. Passes it to the precompile’s native implementation
  3. Deducts the precompile’s gas cost
  4. Writes the output to the designated memory region
  5. Returns success or failure

This is a critical implementation detail: precompile addresses have no bytecode. If you call EXTCODESIZE on address 0x01, it returns 0. The addresses are “occupied” only in the sense that the execution engine recognizes them specially during CALL processing.

// In a Zig EVM implementation, precompile dispatch looks like this:
fn executeCall(evm: *EVM, target: u160, input: []const u8) ![]u8 {
    if (target >= 0x01 and target <= 0x0a) {
        return executePrecompile(target, input, evm.gas);
    }
    // Otherwise, load and execute the target's bytecode
    return executeCode(evm, target, input);
}

You can experiment with how CALL instructions interact with precompile addresses in the Zig EVM Playground, where you can step through bytecode execution and observe the precompile dispatch.

Calling Precompiles from Solidity

There are two approaches to calling precompiles from Solidity:

Using Built-in Functions

Solidity provides wrappers for the most common precompiles:

// ecrecover (0x01) -- built-in function
address recovered = ecrecover(hash, v, r, s);

// sha256 (0x02) -- built-in function
bytes32 digest = sha256(abi.encodePacked(data));

// ripemd160 (0x03) -- built-in function
bytes20 digest = ripemd160(abi.encodePacked(data));

Using Low-Level Calls

For precompiles without built-in wrappers (modexp, BN256, blake2f, KZG), you use staticcall in assembly:

function bn256Add(
    uint256 x1, uint256 y1,
    uint256 x2, uint256 y2
) internal view returns (uint256 x, uint256 y) {
    uint256[4] memory input = [x1, y1, x2, y2];
    uint256[2] memory output;

    assembly {
        let success := staticcall(
            gas(),        // forward all gas
            0x06,         // ecAdd precompile address
            input,        // input data pointer
            128,          // input size (4 * 32 bytes)
            output,       // output data pointer
            64            // output size (2 * 32 bytes)
        )
        if iszero(success) { revert(0, 0) }
    }

    return (output[0], output[1]);
}

Gas Cost Comparison: Precompile vs Pure EVM

To understand why precompiles matter, consider the gas cost difference:

OperationPrecompile GasEstimated Pure EVM GasSavings Factor
ECDSA Recovery3,000~500,000~167x
SHA-256 (64 bytes)84~10,000~119x
BN256 Scalar Mul6,000~5,000,000~833x
BN256 Pairing (2 pairs)113,000~50,000,000~442x
Modexp (2048-bit)~1,360~2,000,000~1,471x

These are not minor optimizations. Without precompiles, zero-knowledge proof verification on Ethereum would be economically impossible, as a single Groth16 verification would exceed the block gas limit.

Precompiles in EVM Implementations

If you are building an EVM implementation — as we are with the Zig EVM project — precompile support is a critical compatibility requirement. Every EVM-compatible chain must implement the same precompile set to maintain consensus.

Implementation considerations include:

  • Exact specification compliance: Precompile outputs must be byte-identical across all clients. A single bit difference causes a consensus failure.
  • Gas metering accuracy: The gas formulas for dynamic-cost precompiles (modexp, ecPairing) must match exactly.
  • Edge case handling: What happens when ecrecover receives an invalid signature? When modexp gets a zero modulus? Each precompile has specific error behaviors.
  • Performance: Precompiles are called frequently. The BN256 operations in particular benefit from optimized assembly implementations.

In the Zig EVM, we leverage Zig’s comptime capabilities and explicit memory management to implement precompiles with minimal overhead. You can explore the implementation and test precompile behavior in the Zig EVM Playground.

Upcoming Precompile Proposals

The Ethereum community continues to propose new precompiles through the EIP process:

  • EIP-2537 (BLS12-381): Adds precompiles for the BLS12-381 curve, which is used by Ethereum’s beacon chain. This would enable efficient BLS signature verification in smart contracts.
  • EIP-7212 (secp256r1): Adds a precompile for the P-256 curve used by WebAuthn and Apple/Android secure enclaves. This would simplify account abstraction wallets that verify hardware-backed signatures.
  • EIP-7545 (Verkle proof verification): Would support Ethereum’s transition to Verkle trees.

Each new precompile follows the same pattern: an operation that is too expensive in pure EVM bytecode gets promoted to native code with a carefully calibrated gas cost.

Key Takeaways

  • EVM precompiled contracts are native implementations of expensive operations at fixed addresses 0x01 through 0x0a, bypassing EVM bytecode execution entirely.
  • There are currently 10 precompiles covering signature recovery, hashing (SHA-256, RIPEMD-160, BLAKE2b), big-number math (modexp), elliptic curve operations (BN256 add/mul/pairing), and KZG verification.
  • Precompiles reduce gas costs by 100x to 1,400x compared to equivalent pure-EVM implementations, making operations like zk-SNARK verification economically feasible.
  • Calling precompiles uses the same CALL/STATICCALL mechanism as calling any contract — the EVM detects the target address and routes to native code.
  • The KZG point evaluation precompile (0x0a) is the newest addition, enabling proto-danksharding and cheaper Layer 2 data availability.
  • Every EVM-compatible chain must implement all precompiles identically to maintain consensus — a key consideration for EVM implementations like the Zig EVM.

Frequently Asked Questions

What are EVM precompiled contracts?
EVM precompiled contracts are special contracts at fixed addresses (0x01 through 0x0a) that implement computationally expensive operations -- primarily cryptographic functions -- directly in the client's native code rather than in EVM bytecode. They exist because implementing these operations in Solidity would be prohibitively expensive in gas.
How do you call a precompiled contract in Solidity?
You call a precompiled contract using a low-level staticcall to its fixed address. For example, to call ecrecover at address 0x01, you would use assembly { success := staticcall(gas(), 0x01, inputPtr, inputLen, outputPtr, outputLen) } or use Solidity's built-in ecrecover() function which wraps this call.
How many precompiled contracts exist in the EVM?
As of the Dencun upgrade (March 2024), there are 10 precompiled contracts at addresses 0x01 through 0x0a. These include ecrecover, SHA-256, RIPEMD-160, identity, modexp, ecAdd, ecMul, ecPairing, blake2f, and the KZG point evaluation precompile added by EIP-4844.
Why are precompiled contracts needed instead of implementing operations in Solidity?
Cryptographic operations like elliptic curve pairing or modular exponentiation would require millions of gas if implemented in EVM opcodes. Precompiles execute these operations in optimized native code, reducing gas costs by orders of magnitude and making operations like signature verification and zero-knowledge proofs economically feasible.
What is the KZG point evaluation precompile used for?
The KZG point evaluation precompile at address 0x0a was introduced in EIP-4844 (Dencun upgrade) to verify that a KZG commitment opens to a claimed value at a specific point. It is essential for verifying blob data in Ethereum's proto-danksharding, enabling Layer 2 rollups to post data more cheaply.

Try Zig EVM

Explore the interactive playground or dive into the source code.