Skip to main content

Overview

Address: 0x0000000000000000000000000000000000000005 Introduced: Byzantium (EIP-198) EIP: EIP-198, EIP-2565 The ModExp precompile computes modular exponentiation: (base^exponent) mod modulus. This enables efficient RSA signature verification, Fermat primality testing, and other advanced cryptographic operations in smart contracts. EIP-198 introduced ModExp in Byzantium. EIP-2565 (Berlin) reduced gas costs to make RSA verification practical.

Gas Cost

Complex formula that varies by hardfork: Pre-Berlin: max(200, complexity * iteration_count / GQUADDIVISOR) Berlin+: max(200, complexity * iteration_count / GQUADDIVISOR_v2) Where:
  • complexity = mult_complexity * max(length(base), length(modulus))
  • mult_complexity = (max(length(base), length(modulus)) / 8)^2 if max > 64, else mult_complexity = max(length(base), length(modulus))^2 / 4
  • iteration_count = max(exponent_bitlength - 1, 1) adjusted for exponent head
  • Minimum gas: 200
The exact calculation is in src/crypto/ModExp/calculateGas Examples:
  • Small inputs (1-byte each): ~200 gas
  • 256-byte RSA (2048-bit): ~50,000+ gas
  • 512-byte RSA (4096-bit): ~200,000+ gas

Input Format

Offset | Length | Description
-------|--------|-------------
0      | 32     | base_length (big-endian u256)
32     | 32     | exponent_length (big-endian u256)
64     | 32     | modulus_length (big-endian u256)
96     | base_length | base value (big-endian)
96+base_length | exponent_length | exponent value (big-endian)
96+base_length+exponent_length | modulus_length | modulus value (big-endian)
Minimum input length: 96 bytes (length headers only)

Output Format

Output length equals modulus_length specified in input.
Offset | Length | Description
-------|--------|-------------
0      | modulus_length | (base^exponent mod modulus) as big-endian
Returns empty output if modulus_length = 0.

Usage Example

import { execute, PrecompileAddress } from '@tevm/voltaire/precompiles';
import { Hardfork } from '@tevm/voltaire/primitives/Hardfork';
import * as Hex from '@tevm/voltaire/primitives/Hex';

// Compute 2^3 mod 5 = 8 mod 5 = 3
const input = Hex(
  '0x' +
  '0000000000000000000000000000000000000000000000000000000000000001' + // base_length = 1
  '0000000000000000000000000000000000000000000000000000000000000001' + // exponent_length = 1
  '0000000000000000000000000000000000000000000000000000000000000001' + // modulus_length = 1
  '02' + // base = 2
  '03' + // exponent = 3
  '05'   // modulus = 5
);

const result = execute(
  PrecompileAddress.MODEXP,
  input,
  100000n,
  Hardfork.CANCUN
);

if (result.success) {
  console.log('Result:', result.output[0]); // 3
  console.log('Gas used:', result.gasUsed);
} else {
  console.error('Error:', result.error);
}

Error Conditions

  • Input length < 96 bytes
  • Out of gas (gas cost depends on input sizes)
  • Modulus = 0 (returns error)
  • Integer overflow in length values

Use Cases

  • RSA signature verification: Verify RSA-2048, RSA-4096 signatures on-chain
  • Zero-knowledge proofs: Perform modular arithmetic for zkSNARKs
  • Cryptographic protocols: Diffie-Hellman key exchange, ElGamal encryption
  • Primality testing: Fermat and Miller-Rabin primality tests
  • Number theory: Modular inverses, Chinese remainder theorem

Implementation Details

  • Zig: Uses multi-precision arithmetic from ModExp crypto module
  • TypeScript: BigInt-based modular exponentiation with square-and-multiply
  • Integration: Depends on ModExp.calculateGas for hardfork-specific gas calculation
  • Optimization: Binary exponentiation (square-and-multiply algorithm)

RSA Verification Example

import * as Hex from '@tevm/voltaire/primitives/Hex';

// RSA-2048 verification (256-byte values)
const baseLen = 256;
const expLen = 3;  // Common public exponent: 65537
const modLen = 256;

// Example RSA-2048 input (signature verification)
const base = Hex('0x' + 'ab'.repeat(256)); // Signature (256 bytes)
const exponent = Hex('0x010001'); // Public exponent 65537 (3 bytes)
const modulus = Hex('0x' + 'cd'.repeat(256)); // RSA modulus (256 bytes)

const input = Hex(
  '0x' +
  '0000000000000000000000000000000000000000000000000000000000000100' + // base_length = 256
  '0000000000000000000000000000000000000000000000000000000000000003' + // exponent_length = 3
  '0000000000000000000000000000000000000000000000000000000000000100' + // modulus_length = 256
  Hex.toHex(base).slice(2) +
  Hex.toHex(exponent).slice(2) +
  Hex.toHex(modulus).slice(2)
);

const result = execute(
  PrecompileAddress.MODEXP,
  input,
  1000000n, // RSA needs significant gas
  Hardfork.CANCUN
);

// result.output should equal expected message hash

Gas Cost Reduction (EIP-2565)

Berlin hard fork (EIP-2565) reduced gas costs significantly:
Input SizePre-BerlinBerlinReduction
2048-bit RSA~300,000~50,00083%
4096-bit RSA~1,200,000~200,00083%
This made RSA verification practical for many use cases.

Edge Cases

  • Zero exponent: Returns 1 (any number to power 0 is 1)
  • Modulus = 1: Returns 0 (anything mod 1 is 0)
  • Base > modulus: Automatically reduced mod modulus
  • Truncated input: Missing bytes treated as zero
  • Zero modulus: Returns error (division by zero)

Test Vectors

import * as Hex from '@tevm/voltaire/primitives/Hex';

// Test 1: 2^3 mod 5 = 3
const input1 = Hex(
  '0x' +
  '0000000000000000000000000000000000000000000000000000000000000001' + // base_len = 1
  '0000000000000000000000000000000000000000000000000000000000000001' + // exp_len = 1
  '0000000000000000000000000000000000000000000000000000000000000001' + // mod_len = 1
  '02' + '03' + '05' // base=2, exp=3, mod=5
);
// Expected: 0x03

// Test 2: 3^1 mod 5 = 3
const input2 = Hex(
  '0x' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '03' + '01' + '05' // base=3, exp=1, mod=5
);
// Expected: 0x03

// Test 3: 5^0 mod 7 = 1 (zero exponent)
const input3 = Hex(
  '0x' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '05' + '00' + '07' // base=5, exp=0, mod=7
);
// Expected: 0x01

// Test 4: Zero modulus error
const input4 = Hex(
  '0x' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '0000000000000000000000000000000000000000000000000000000000000001' +
  '02' + '03' + '00' // base=2, exp=3, mod=0
);
// Expected: Error (division by zero)

References

Specifications