EIP-1108 Vulnerability Patterns

December 17, 2025

EIP-1108 (Istanbul Hard Fork, 2019) reduced the gas costs for the alt_bn128 elliptic curve precompiles on Ethereum Mainnet. These precompiles are essential for BLS signature verification and zkSNARK proof verification.

This article summarizes 1 EIP-1108 vulnerability pattern with real audit cases to help you understand the risks of assuming Ethereum Mainnet gas costs apply universally across all EVM-compatible chains.

EIP-1108 Overview

┌─────────────────────────────────────────────────────────────────────────┐
│                    EIP-1108: PRECOMPILE GAS REDUCTION                   │
└─────────────────────────────────────────────────────────────────────────┘

Affected Precompiles:
┌────────────────┬──────────────────┬─────────────────┬─────────────────┐
│   Precompile   │     Address      │  Before EIP-1108│  After EIP-1108 │
├────────────────┼──────────────────┼─────────────────┼─────────────────┤
│ ECADD          │      0x06        │    500 gas      │    150 gas      │
│ ECMUL          │      0x07        │  40,000 gas     │  6,000 gas      │
│ ECPAIRING      │      0x08        │ 100k + 80k×k    │ 45k + 34k×k     │
└────────────────┴──────────────────┴─────────────────┴─────────────────┘
                                                 ↑
                                     ~70% reduction for ECMUL!

┌─────────────────────────────────────────────────────────────────────────┐
│                    ECPAIRING GAS FORMULA                                │
└─────────────────────────────────────────────────────────────────────────┘

Formula: base_cost + per_pair_cost × k   (k = number of point pairs)

Before EIP-1108:  100,000 + 80,000 × k
After EIP-1108:    45,000 + 34,000 × k

Example (k=2, typical for BLS verification):
┌────────────────────┬────────────────────┐
│   Before EIP-1108  │   After EIP-1108   │
├────────────────────┼────────────────────┤
│ 100,000 + 160,000  │  45,000 + 68,000   │
│    = 260,000 gas   │    = 113,000 gas   │
└────────────────────┴────────────────────┘
                              ↑
                    56% cheaper!

┌─────────────────────────────────────────────────────────────────────────┐
│                    WHY DOES THIS MATTER?                                │
└─────────────────────────────────────────────────────────────────────────┘

These precompiles enable:
  • BLS signature aggregation (multi-sig verification in 1 call)
  • zkSNARK proof verification (privacy protocols like Tornado Cash)
  • zkRollup batch verification (L2 scaling solutions)

Lower gas costs → More practical to use on-chain

Vulnerability Patterns

1. Precompile Gas Cost Assumption Violation

Prerequisite Knowledge:

EIP-1108 defines the gas cost for the alt_bn128 pairing precompile as 34000 * k + 45000, where k is the number of pairs. For k=2, this results in 113,000 gas. However, this EIP is specific to Ethereum Mainnet and may not be followed by other chains. For example, ZKSync’s V28 upgrade (ZIP-11) changed the gas cost to 80000 * k, resulting in 160,000 gas for k=2. The vulnerability stems from assuming the EIP-1108 gas cost is universal across all EVM-compatible chains.

Root Cause:

The code hardcodes the gas limit for the EcPairing precompile call based on the EIP-1108 specification (120,000 gas). This assumes that all deployed chains will follow this gas cost model. The vulnerability occurs because chains like ZKSync have implemented different gas costs for their precompiles, causing the hardcoded limit to be insufficient and the precompile call to always fail.

Attack Vector:

An attacker doesn’t need to actively exploit this; the vulnerability manifests as a denial of service. When the contract is deployed on a chain where the EcPairing precompile requires more gas than the hardcoded 120,000 limit (like ZKSync which requires 160,000), any attempt to verify a BLS signature using the verify function will fail. This breaks the core functionality of the contract, making BlsBn254 signature verification unavailable on that chain.

Source:

  • [sherlock/2025-06-symbiotic-relay] BlsBn254 is not available in certain chains due to hardcoded gas limit (issue-422.md)
    • middleware-sdk/src/contracts/libraries/sigs/SigBlsBn254.sol

      Business Context: Symbiotic Relay Multi-Chain BLS Signature Verification

      ┌─────────────────────────────────────────────────────────────────────────┐
      │                    WHAT IS SYMBIOTIC?                                   │
      └─────────────────────────────────────────────────────────────────────────┘
      
      Symbiotic is a restaking protocol (similar to EigenLayer) that allows
      users to re-stake already staked assets to earn additional yield.
      
      ┌─────────────────────────────────────────────────────────────────────────┐
      │                         RESTAKING CONCEPT                               │
      └─────────────────────────────────────────────────────────────────────────┘
      
      Traditional Staking:
        ETH → Stake to Lido → Get stETH → Earn ~4% yield
      
      Restaking:
        stETH → Re-stake to Symbiotic → Provide security for other services
                                      → Earn ADDITIONAL yield
              ↑
        Your stETH is doing two jobs at once!
      
      ┌─────────────────────────────────────────────────────────────────────────┐
      │                    WHY BLS SIGNATURES?                                  │
      └─────────────────────────────────────────────────────────────────────────┘
      
      Symbiotic validators need to reach consensus on cross-chain messages.
      With 100 validators, how to verify efficiently?
      
      Traditional ECDSA:                    BLS Aggregated Signature:
      ┌────────────────────────────┐       ┌────────────────────────────┐
      │ 100 validators             │       │ 100 validators             │
      │ → 100 signatures           │       │ → 1 aggregated signature   │
      │ → 100 ecrecover calls      │       │ → 1 pairing check          │
      │ → ~300,000 gas             │       │ → ~113,000 gas             │
      └────────────────────────────┘       └────────────────────────────┘
                                                      ↑
                                             62% gas savings!
      
      ┌─────────────────────────────────────────────────────────────────────────┐
      │                    CROSS-CHAIN MESSAGE VERIFICATION FLOW                │
      └─────────────────────────────────────────────────────────────────────────┘
      
      Step 1: Event on Chain A
      ┌────────────────────────────────────────────────────────────────────────┐
      │  Chain A (Source)                                                      │
      │                                                                        │
      │  User calls: bridge.deposit(100 ETH, destChain=B, recipient=Bob)       │
      │                      │                                                 │
      │                      ▼                                                 │
      │  Event: Deposit(user, 100 ETH, chainB, Bob)                            │
      └────────────────────────────────────────────────────────────────────────┘
                             │
                             ▼
      Step 2: Validators sign off-chain
      ┌────────────────────────────────────────────────────────────────────────┐
      │  Symbiotic Validators (Off-chain)                                      │
      │                                                                        │
      │  ┌──────────┐  ┌──────────┐  ┌──────────┐                             │
      │  │Operator 1│  │Operator 2│  │Operator 3│  ...                        │
      │  └────┬─────┘  └────┬─────┘  └────┬─────┘                             │
      │       │             │             │                                    │
      │       │  Sign with BLS private key                                     │
      │       ▼             ▼             ▼                                    │
      │     sig1          sig2          sig3                                   │
      │       │             │             │                                    │
      │       └─────────────┼─────────────┘                                    │
      │                     │                                                  │
      │                     ▼                                                  │
      │              ┌──────────────┐                                          │
      │              │   Aggregator │  Combine all signatures                  │
      │              │              │                                          │
      │              │  aggSig =    │                                          │
      │              │  sig1+sig2+..│                                          │
      │              └──────┬───────┘                                          │
      └─────────────────────│──────────────────────────────────────────────────┘
                            │
                            ▼
      Step 3: Submit to destination chain
      ┌────────────────────────────────────────────────────────────────────────┐
      │  Chain B (Destination)                                                 │
      │                                                                        │
      │  Relayer calls:                                                        │
      │  relay.executeMessage(                                                 │
      │      message: {user, 100 ETH, Bob},                                    │
      │      aggregatedSignature: aggSig,                                      │
      │      signersBitmap: 0b111...111  // which validators signed            │
      │  )                                                                     │
      │                      │                                                 │
      │                      ▼                                                 │
      │  ┌─────────────────────────────────────────────────────────────────┐  │
      │  │  BLS Signature Verification (calls EcPairing precompile 0x08)   │  │
      │  │                                                                 │  │
      │  │  1. Aggregate public keys based on bitmap                       │  │
      │  │     aggPubKey = pubKey1 + pubKey2 + ...                         │  │
      │  │                                                                 │  │
      │  │  2. Verify pairing equation                                     │  │
      │  │     e(aggSig, G2) == e(H(msg), aggPubKey)                       │  │
      │  │            ↑                                                    │  │
      │  │     This step needs EcPairing precompile!                       │  │
      │  │     Ethereum: 113,000 gas | ZKSync: 160,000 gas                 │  │
      │  └─────────────────────────────────────────────────────────────────┘  │
      │                      │                                                 │
      │                      ▼                                                 │
      │            Verification passed?                                        │
      │           /                    \                                       │
      │         YES                    NO                                      │
      │          │                      │                                      │
      │          ▼                      ▼                                      │
      │   Execute cross-chain op    Reject tx                                  │
      │   Send 100 ETH to Bob                                                  │
      └────────────────────────────────────────────────────────────────────────┘
      
      ┌─────────────────────────────────────────────────────────────────────────┐
      │                    GAS COST DIFFERENCE ACROSS CHAINS                    │
      └─────────────────────────────────────────────────────────────────────────┘
      
      EcPairing Gas Formula: base_cost + per_pair_cost × k (k = number of pairs)
      
      ┌────────────────┬─────────────────────┬──────────────────┐
      │     Chain      │      Formula        │   k=2 Result     │
      ├────────────────┼─────────────────────┼──────────────────┤
      │ Ethereum (EIP-1108) │ 45000 + 34000×k │   113,000 gas   │
      │ ZKSync (ZIP-11)     │ 80000×k         │   160,000 gas   │
      └────────────────┴─────────────────────┴──────────────────┘
                                                      ↑
                                            41% MORE expensive!
      
      ┌─────────────────────────────────────────────────────────────────────────┐
      │                         THE PROBLEM                                     │
      └─────────────────────────────────────────────────────────────────────────┘
      
      Code hardcodes gas limit based on Ethereum Mainnet:
      
      uint256 constant PAIRING_CHECK_GAS_LIMIT = 120_000;  // Enough for Ethereum
                                                           // NOT enough for ZKSync!
      
      On Ethereum:                          On ZKSync:
      ┌────────────────────┐                ┌────────────────────┐
      │ Provided: 120,000  │                │ Provided: 120,000  │
      │ Required: 113,000  │                │ Required: 160,000  │
      │ Result: ✓ SUCCESS  │                │ Result: ✗ OOG FAIL │
      └────────────────────┘                └────────────────────┘
      
      ┌─────────────────────────────────────────────────────────────────────────┐
      │                         IMPACT                                          │
      └─────────────────────────────────────────────────────────────────────────┘
      
      When deployed on ZKSync:
      
      ┌──────────────┐     Submit BLS sig    ┌──────────────┐
      │   Relayer    │ ────────────────────► │   Contract   │
      └──────────────┘                       └──────┬───────┘
                                                    │
                                                    ▼
                                       ┌───────────────────────┐
                                       │ safePairing(          │
                                       │   ...,                │
                                       │   120_000  ← TOO LOW! │
                                       │ )                     │
                                       └───────────┬───────────┘
                                                   │
                                                   ▼
                                       ┌───────────────────────┐
                                       │ Precompile needs      │
                                       │ 160,000 gas           │
                                       │                       │
                                       │ OUT OF GAS!           │
                                       │ success = false       │
                                       └───────────────────────┘
                                                   │
                                                   ▼
                                ┌─────────────────────────────────┐
                                │  BLS verification ALWAYS fails  │
                                │  Protocol unusable on ZKSync    │
                                └─────────────────────────────────┘
      
      ┌─────────────────────────────────────────────────────────────────────────┐
      │                         FIX                                             │
      └─────────────────────────────────────────────────────────────────────────┘
      
      Option 1: Make gas limit configurable per chain
      ┌────────────────────────────────────────────────┐
      │ mapping(uint256 => uint256) chainGasLimits;    │
      │                                                │
      │ function setGasLimit(uint256 chainId,          │
      │                      uint256 limit) onlyOwner  │
      └────────────────────────────────────────────────┘
      
      Option 2: Use sufficient gas for all chains
      ┌────────────────────────────────────────────────┐
      │ uint256 constant PAIRING_GAS = 200_000;        │
      │ // Covers both Ethereum and ZKSync             │
      └────────────────────────────────────────────────┘
      
      Option 3: Use gasleft() and let caller provide enough
      ┌────────────────────────────────────────────────┐
      │ (bool success,) = precompile.call{gas: gasleft()}(data);│
      └────────────────────────────────────────────────┘

      Vulnerable Code:

      uint256 internal constant PAIRING_CHECK_GAS_LIMIT = 120_000; // VULNERABILITY: Hardcoded gas limit doesn't account for chain-specific precompile costs
      
          (bool success, bool result) = BN254.safePairing( // VULNERABILITY: Using hardcoded gas limit that's too low for chains like ZKSync
              signatureG1.plus(keyG1.scalar_mul(alpha)),
              BN254.negGeneratorG2(),
              messageG1.plus(BN254.generatorG1().scalar_mul(alpha)),
              keyG2,
              PAIRING_CHECK_GAS_LIMIT
          );

EIP-1108 Audit Checklist

When auditing contracts that use elliptic curve precompiles, ensure you check these items:

# Check Item Related Pattern
Are gas limits for precompile calls hardcoded? Hardcoded values based on Ethereum Mainnet may not work on other chains Pattern 1
Is the contract intended for multi-chain deployment? Different chains have different precompile gas costs Pattern 1
Is there a fallback or configurability for gas limits? Consider using gasleft() or configurable parameters Pattern 1