This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.
Overview
Opcode: 0x08
Introduced: Frontier (EVM genesis)
ADDMOD performs modular addition (a + b) % N where all operands are 256-bit unsigned integers. Unlike standard ADD followed by MOD, ADDMOD computes the result using wider arithmetic to prevent intermediate overflow, making it essential for cryptographic operations.
Division by zero (N = 0) returns 0 rather than throwing an exception.
Specification
Stack Input:
Stack Output:
Gas Cost: 8 (GasMidStep)
Operation:
if N == 0:
result = 0
else:
result = (a + b) % N
Behavior
ADDMOD pops three values from the stack (a, b, N), computes (a + b) mod N, and pushes the result back:
- Normal case: Result is
(a + b) % N
- N = 0: Returns 0 (EVM convention)
- No intermediate overflow: Uses 512-bit arithmetic internally
The key advantage over ADD then MOD is that ADDMOD avoids intermediate overflow when a + b >= 2^256.
Examples
Basic Modular Addition
import { addmod } from '@tevm/voltaire/evm/arithmetic';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// (5 + 10) % 3 = 15 % 3 = 0
const frame = createFrame({ stack: [5n, 10n, 3n] });
const err = addmod(frame);
console.log(frame.stack); // [0n]
console.log(frame.gasRemaining); // Original - 8
Overflow-Safe Addition
// MAX + MAX would overflow in ADD, but ADDMOD handles it
const MAX_U256 = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX_U256, MAX_U256, 100n] });
const err = addmod(frame);
// (MAX + MAX) % 100 = (2^256 - 2) % 100
const expected = ((MAX_U256 + MAX_U256) % 100n);
console.log(frame.stack); // [expected]
Zero Modulus
// Division by zero returns 0
const frame = createFrame({ stack: [5n, 10n, 0n] });
const err = addmod(frame);
console.log(frame.stack); // [0n]
Modulus of 1
// Any number mod 1 is 0
const frame = createFrame({ stack: [999n, 888n, 1n] });
const err = addmod(frame);
console.log(frame.stack); // [0n]
Large Modulus
// Result when sum < modulus
const frame = createFrame({ stack: [5n, 10n, 1000n] });
const err = addmod(frame);
console.log(frame.stack); // [15n] (no reduction needed)
Gas Cost
Cost: 8 gas (GasMidStep)
ADDMOD costs more than basic ADD due to wider arithmetic requirements:
Comparison:
- ADD/SUB: 3 gas
- MUL/DIV/MOD: 5 gas
- ADDMOD/MULMOD: 8 gas
- EXP: 10 + 50 per byte
Despite higher cost, ADDMOD is more efficient than separate ADD + MOD operations when dealing with potential overflow.
Edge Cases
Maximum Values
const MAX = (1n << 256n) - 1n;
// MAX + MAX mod 7
const frame = createFrame({ stack: [MAX, MAX, 7n] });
addmod(frame);
const expected = (MAX + MAX) % 7n;
console.log(frame.stack); // [expected]
Identity Elements
// a + 0 = a (mod N)
const frame1 = createFrame({ stack: [42n, 0n, 17n] });
addmod(frame1);
console.log(frame1.stack); // [42n % 17n = 8n]
// 0 + 0 = 0 (mod N)
const frame2 = createFrame({ stack: [0n, 0n, 17n] });
addmod(frame2);
console.log(frame2.stack); // [0n]
Stack Underflow
// Not enough stack items
const frame = createFrame({ stack: [5n, 10n] });
const err = addmod(frame);
console.log(err); // { type: "StackUnderflow" }
Out of Gas
// Insufficient gas
const frame = createFrame({ stack: [5n, 10n, 3n], gasRemaining: 7n });
const err = addmod(frame);
console.log(err); // { type: "OutOfGas" }
console.log(frame.gasRemaining); // 0n
Common Usage
Elliptic Curve Point Addition
// secp256k1 field arithmetic (p = 2^256 - 2^32 - 977)
assembly {
let p := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
// Add two field elements
let x1 := mload(0x00)
let x2 := mload(0x20)
let sum := addmod(x1, x2, p)
}
Modular Ring Operations
// Ring arithmetic mod N
function addInRing(uint256 a, uint256 b, uint256 N)
pure returns (uint256)
{
assembly {
mstore(0x00, addmod(a, b, N))
return(0x00, 0x20)
}
}
Hash Computations
// Polynomial rolling hash
assembly {
let hash := 0
let base := 31
let mod := 1000000007
// hash = (hash * base + char) % mod
hash := addmod(mulmod(hash, base, mod), char, mod)
}
Schnorr/BLS Signature Math
// s = (k + e*x) mod n (Schnorr signature)
assembly {
let n := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
let k := mload(0x00)
let e_x := mulmod(e, x, n)
let s := addmod(k, e_x, n)
}
Implementation
/**
* ADDMOD opcode (0x08) - Addition modulo N
*/
export function addmod(frame: FrameType): EvmError | null {
// Consume gas (GasMidStep = 8)
frame.gasRemaining -= 8n;
if (frame.gasRemaining < 0n) {
frame.gasRemaining = 0n;
return { type: "OutOfGas" };
}
// Pop operands: a, b, N
if (frame.stack.length < 3) return { type: "StackUnderflow" };
const a = frame.stack.pop();
const b = frame.stack.pop();
const n = frame.stack.pop();
// Compute result
let result: bigint;
if (n === 0n) {
result = 0n;
} else {
// BigInt handles arbitrary precision - no overflow
result = (a + b) % n;
}
// Push result
if (frame.stack.length >= 1024) return { type: "StackOverflow" };
frame.stack.push(result);
// Increment PC
frame.pc += 1;
return null;
}
Testing
Test Coverage
import { describe, it, expect } from 'vitest';
import { addmod } from './0x08_ADDMOD.js';
describe('ADDMOD (0x08)', () => {
it('computes (a + b) % N', () => {
const frame = createFrame([5n, 10n, 3n]);
expect(addmod(frame)).toBeNull();
expect(frame.stack).toEqual([0n]); // 15 % 3 = 0
});
it('returns 0 when N is 0', () => {
const frame = createFrame([5n, 10n, 0n]);
expect(addmod(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('handles large values without overflow', () => {
const MAX = (1n << 256n) - 1n;
const frame = createFrame([MAX, MAX, 7n]);
expect(addmod(frame)).toBeNull();
expect(frame.stack).toEqual([(MAX + MAX) % 7n]);
});
it('returns StackUnderflow with insufficient stack', () => {
const frame = createFrame([5n, 10n]);
expect(addmod(frame)).toEqual({ type: 'StackUnderflow' });
});
it('returns OutOfGas when insufficient gas', () => {
const frame = createFrame([5n, 10n, 3n], 7n);
expect(addmod(frame)).toEqual({ type: 'OutOfGas' });
});
});
Edge Cases Tested
- Basic modular addition (15 % 3 = 0)
- Zero modulus (returns 0)
- Modulus of 1 (always returns 0)
- Large values (MAX + MAX)
- Overflow-safe computation
- Identity elements (a + 0, 0 + 0)
- Stack underflow (< 3 items)
- Out of gas (< 8 gas)
Security
Cryptographic Importance
ADDMOD is critical for implementing cryptographic operations that require modular arithmetic:
Elliptic Curve Operations:
// secp256k1 field addition
uint256 constant FIELD_P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
function fieldAdd(uint256 a, uint256 b) pure returns (uint256) {
return addmod(a, b, FIELD_P);
}
BLS12-381 Group Operations:
// BLS12-381 field modulus
uint256 constant BLS_P = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab;
function blsFieldAdd(uint256 a, uint256 b) pure returns (uint256) {
return addmod(a, b, BLS_P);
}
Timing Safety
ADDMOD operations complete in constant time regardless of operand values, preventing timing side-channel attacks in cryptographic implementations.
Overflow Protection
Unlike ADD then MOD, ADDMOD prevents intermediate overflow:
Vulnerable pattern:
// Can overflow if a + b >= 2^256
uint256 sum = a + b;
uint256 result = sum % N; // Wrong result if overflow occurred
Safe pattern:
// Always correct
uint256 result = addmod(a, b, N);
References
- ADD - Basic addition with wrapping
- MULMOD - Modular multiplication
- MOD - Unsigned modulo operation