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: 0x1a
Introduced: Frontier (EVM genesis)
BYTE extracts a single byte from a 256-bit value at a specified index, using big-endian byte ordering where byte 0 is the most significant byte (leftmost). Returns 0 if the index is out of range (>= 32).
Primary uses: extracting individual bytes from packed data, parsing structured data, endianness conversions.
Specification
Stack Input:
i (top) - byte index (0-31)
x - value to extract from
Stack Output:
x[i] - byte at index i, or 0 if i >= 32
Gas Cost: 3 (GasFastestStep)
Byte Ordering (Big-Endian):
bytes32: [0][1][2]...[29][30][31]
↑ ↑
MSB LSB
byte 0 byte 31
Behavior
BYTE pops two values from the stack:
- i - byte index (0 = MSB, 31 = LSB)
- x - 256-bit value to extract from
Returns the byte at position i, or 0 if i >= 32.
Algorithm:
if i >= 32:
result = 0
else:
result = (x >> (8 * (31 - i))) & 0xFF
Examples
import { byte } from '@tevm/voltaire/evm/bitwise';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// Extract byte 0 (MSB)
const value = 0x123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEFn;
const frame = createFrame({ stack: [0n, value] });
const err = byte(frame);
console.log(frame.stack[0].toString(16)); // '12' (first byte)
// Extract byte 31 (LSB)
const value = 0x123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEFn;
const frame = createFrame({ stack: [31n, value] });
byte(frame);
console.log(frame.stack[0].toString(16)); // 'ef' (last byte)
// Extract byte 15 (middle of 32-byte value)
const value = 0x000000000000000000000000000000FF00000000000000000000000000000000n;
const frame = createFrame({ stack: [15n, value] });
byte(frame);
console.log(frame.stack[0].toString(16)); // 'ff'
Out of Range Index
// Index >= 32 returns 0
const value = 0x123456789ABCDEFn;
const frame = createFrame({ stack: [32n, value] });
byte(frame);
console.log(frame.stack[0]); // 0n
// Extract specific byte from address
const addr = 0x000000000000000000000000dEaDbEeFcAfE1234567890ABCDEf12345678n;
// Address starts at byte 12 (160 bits / 8 = 20 bytes, offset from byte 12)
const frame = createFrame({ stack: [12n, addr] });
byte(frame);
console.log(frame.stack[0].toString(16)); // 'de' (first byte of address)
Iterate Through Bytes
// Extract all 32 bytes
const value = 0x0123456789ABCDEFn; // Only lower bytes set
for (let i = 0; i < 32; i++) {
const frame = createFrame({ stack: [BigInt(i), value] });
byte(frame);
const byteVal = frame.stack[0];
if (byteVal !== 0n) {
console.log(`Byte ${i}: 0x${byteVal.toString(16)}`);
}
}
// Output:
// Byte 24: 0x01
// Byte 25: 0x23
// Byte 26: 0x45
// ...
Gas Cost
Cost: 3 gas (GasFastestStep)
BYTE shares the lowest gas tier with:
- AND (0x16), OR (0x17), XOR (0x18), NOT (0x19)
- SHL (0x1b), SHR (0x1c), SAR (0x1d)
- ADD (0x01), SUB (0x03)
- Comparison operations
Edge Cases
Index Zero (MSB)
// Byte 0 is most significant byte
const value = 0xFF00000000000000000000000000000000000000000000000000000000000000n;
const frame = createFrame({ stack: [0n, value] });
byte(frame);
console.log(frame.stack[0].toString(16)); // 'ff'
Index 31 (LSB)
// Byte 31 is least significant byte
const value = 0x00000000000000000000000000000000000000000000000000000000000000FFn;
const frame = createFrame({ stack: [31n, value] });
byte(frame);
console.log(frame.stack[0].toString(16)); // 'ff'
Index Out of Range
// Any index >= 32 returns 0
const value = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn;
for (const idx of [32n, 100n, 256n, (1n << 255n)]) {
const frame = createFrame({ stack: [idx, value] });
byte(frame);
console.log(frame.stack[0]); // 0n for all
}
Zero Value
// All bytes are 0
const frame = createFrame({ stack: [15n, 0n] });
byte(frame);
console.log(frame.stack[0]); // 0n
Maximum Value
// All bytes are 0xFF
const MAX = (1n << 256n) - 1n;
const frame = createFrame({ stack: [15n, MAX] });
byte(frame);
console.log(frame.stack[0].toString(16)); // 'ff'
Stack Underflow
// Insufficient stack items
const frame = createFrame({ stack: [5n] });
const err = byte(frame);
console.log(err); // { type: "StackUnderflow" }
Out of Gas
// Insufficient gas
const frame = createFrame({ stack: [5n, 0x123n], gasRemaining: 2n });
const err = byte(frame);
console.log(err); // { type: "OutOfGas" }
console.log(frame.gasRemaining); // 0n
Common Usage
Parse Function Selector
// Extract 4-byte function selector from calldata
function getSelector(bytes memory data) pure returns (bytes4) {
require(data.length >= 4, "data too short");
assembly {
let word := mload(add(data, 32)) // Load first 32 bytes
// Extract bytes 0-3 (function selector)
let b0 := byte(0, word)
let b1 := byte(1, word)
let b2 := byte(2, word)
let b3 := byte(3, word)
// Pack into bytes4
mstore(0, or(or(or(shl(24, b0), shl(16, b1)), shl(8, b2)), b3))
return(0, 4)
}
}
// Extract nibble (half-byte) from bytes32
function getNibble(bytes32 data, uint256 nibbleIndex) pure returns (uint8) {
require(nibbleIndex < 64, "index out of range");
uint256 byteIndex = nibbleIndex / 2;
bool isLowerNibble = (nibbleIndex % 2) == 1;
assembly {
let b := byte(byteIndex, data)
let nibble := and(shr(mul(4, iszero(isLowerNibble)), b), 0x0F)
mstore(0, nibble)
return(0, 32)
}
}
Validate Address Encoding
// Check if address is properly zero-padded in uint256
function isValidAddressEncoding(uint256 value) pure returns (bool) {
// Bytes 0-11 must be zero for valid address encoding
assembly {
let valid := 1
for { let i := 0 } lt(i, 12) { i := add(i, 1) } {
if iszero(eq(byte(i, value), 0)) {
valid := 0
break
}
}
mstore(0, valid)
return(0, 32)
}
}
// Extract 5-byte (40-bit) timestamp from packed data
function extractTimestamp(bytes32 packed) pure returns (uint40) {
assembly {
// Timestamp is bytes 0-4
let b0 := byte(0, packed)
let b1 := byte(1, packed)
let b2 := byte(2, packed)
let b3 := byte(3, packed)
let b4 := byte(4, packed)
let timestamp := or(or(or(or(
shl(32, b0),
shl(24, b1)),
shl(16, b2)),
shl(8, b3)),
b4)
mstore(0, timestamp)
return(0, 32)
}
}
Check UTF-8 Encoding
// Check if byte is valid UTF-8 continuation byte (10xxxxxx)
function isUtf8Continuation(bytes32 data, uint256 byteIndex) pure returns (bool) {
assembly {
let b := byte(byteIndex, data)
// Continuation bytes: 0b10xxxxxx (0x80-0xBF)
let isContinuation := and(eq(and(b, 0xC0), 0x80), 1)
mstore(0, isContinuation)
return(0, 32)
}
}
Implementation
/**
* BYTE opcode (0x1a) - Extract byte at index i from value x
*/
export function byte(frame: FrameType): EvmError | null {
// Consume gas (GasFastestStep = 3)
frame.gasRemaining -= 3n;
if (frame.gasRemaining < 0n) {
frame.gasRemaining = 0n;
return { type: "OutOfGas" };
}
// Pop operands
if (frame.stack.length < 2) return { type: "StackUnderflow" };
const i = frame.stack.pop(); // Byte index
const x = frame.stack.pop(); // Value
// Extract byte (big-endian: byte 0 = MSB)
const result = i >= 32n
? 0n
: (x >> (8n * (31n - i))) & 0xFFn;
// 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 { byte } from './byte.js';
describe('BYTE (0x1a)', () => {
it('extracts MSB (byte 0)', () => {
const value = 0xFF00000000000000000000000000000000000000000000000000000000000000n;
const frame = createFrame({ stack: [0n, value] });
expect(byte(frame)).toBeNull();
expect(frame.stack[0]).toBe(0xFFn);
});
it('extracts LSB (byte 31)', () => {
const value = 0x00000000000000000000000000000000000000000000000000000000000000FFn;
const frame = createFrame({ stack: [31n, value] });
expect(byte(frame)).toBeNull();
expect(frame.stack[0]).toBe(0xFFn);
});
it('extracts middle byte', () => {
const value = 0x000000000000000000000000000000AB00000000000000000000000000000000n;
const frame = createFrame({ stack: [15n, value] });
expect(byte(frame)).toBeNull();
expect(frame.stack[0]).toBe(0xABn);
});
it('returns 0 for index >= 32', () => {
const value = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn;
const frame = createFrame({ stack: [32n, value] });
expect(byte(frame)).toBeNull();
expect(frame.stack[0]).toBe(0n);
});
it('returns 0 for large index', () => {
const value = 0x123456n;
const frame = createFrame({ stack: [1000n, value] });
expect(byte(frame)).toBeNull();
expect(frame.stack[0]).toBe(0n);
});
it('extracts from zero value', () => {
const frame = createFrame({ stack: [15n, 0n] });
expect(byte(frame)).toBeNull();
expect(frame.stack[0]).toBe(0n);
});
it('extracts all bytes from MAX value', () => {
const MAX = (1n << 256n) - 1n;
for (let i = 0; i < 32; i++) {
const frame = createFrame({ stack: [BigInt(i), MAX] });
expect(byte(frame)).toBeNull();
expect(frame.stack[0]).toBe(0xFFn);
}
});
it('returns StackUnderflow with insufficient stack', () => {
const frame = createFrame({ stack: [5n] });
expect(byte(frame)).toEqual({ type: 'StackUnderflow' });
});
it('returns OutOfGas when insufficient gas', () => {
const frame = createFrame({ stack: [5n, 0x123n], gasRemaining: 2n });
expect(byte(frame)).toEqual({ type: 'OutOfGas' });
});
});
Edge Cases Tested
- MSB extraction (byte 0)
- LSB extraction (byte 31)
- Middle byte extraction
- Out of range indices (>= 32)
- Large indices (1000+)
- Zero value
- Maximum value (all bytes 0xFF)
- All 32 byte positions
- Stack underflow
- Out of gas
Security
Endianness Confusion
// WRONG: Assuming byte 0 is LSB (little-endian)
function extractLSB(bytes32 data) pure returns (uint8) {
assembly {
let b := byte(0, data) // Actually MSB, not LSB!
mstore(0, b)
return(0, 32)
}
}
// CORRECT: Byte 31 is LSB
function extractLSB(bytes32 data) pure returns (uint8) {
assembly {
let b := byte(31, data) // LSB
mstore(0, b)
return(0, 32)
}
}
Index Validation
// DANGEROUS: No bounds check on user input
function extractByte(bytes32 data, uint256 index) pure returns (uint8) {
assembly {
let b := byte(index, data) // Returns 0 if index >= 32
mstore(0, b)
return(0, 32)
}
}
// BETTER: Explicit validation
function extractByte(bytes32 data, uint256 index) pure returns (uint8) {
require(index < 32, "index out of range");
assembly {
let b := byte(index, data)
mstore(0, b)
return(0, 32)
}
}
Off-by-One Errors
// WRONG: Confusing byte index with bit index
function extractNthBit(bytes32 data, uint256 bitIndex) pure returns (bool) {
// bitIndex = 0-255, but BYTE takes byte index (0-31)
assembly {
let b := byte(bitIndex, data) // WRONG: treats bit index as byte index
mstore(0, and(b, 1))
return(0, 32)
}
}
// CORRECT: Convert bit index to byte index
function extractNthBit(bytes32 data, uint256 bitIndex) pure returns (bool) {
require(bitIndex < 256, "bit index out of range");
uint256 byteIndex = bitIndex / 8;
uint256 bitPosition = bitIndex % 8;
assembly {
let b := byte(byteIndex, data)
let bit := and(shr(sub(7, bitPosition), b), 1)
mstore(0, bit)
return(0, 32)
}
}
Packed Data Alignment
// RISKY: Assuming specific packing without validation
struct Packed {
uint40 timestamp; // Bytes 0-4
uint160 addr; // Bytes 5-24
uint72 value; // Bytes 25-31
}
function extractTimestamp(bytes32 packed) pure returns (uint40) {
// Assumes timestamp is at bytes 0-4
// If packing changes, this breaks silently
assembly {
let t := or(or(or(or(
shl(32, byte(0, packed)),
shl(24, byte(1, packed))),
shl(16, byte(2, packed))),
shl(8, byte(3, packed))),
byte(4, packed))
mstore(0, t)
return(0, 32)
}
}
Benchmarks
BYTE is one of the fastest EVM operations:
Execution time (relative):
- BYTE: 1.0x (baseline, fastest tier)
- SHR/SHL: 1.0x (same tier, can be used as alternative)
- AND: 1.0x (same tier)
- DIV: 2.5x
Gas efficiency:
- 3 gas per byte extraction
- ~333,333 BYTE operations per million gas
Comparison with alternatives:
- BYTE: 3 gas (direct extraction)
- SHR + AND: 6 gas (shift + mask)
- DIV + MOD: 10 gas (arithmetic extraction)
References