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: 0x49
Introduced: Cancun (EIP-4844)
BLOBHASH retrieves a versioned blob hash from the current transaction’s blob list by index. This enables proto-danksharding support, allowing contracts to verify blob commitments for Layer 2 data availability.
Specification
Stack Input:
Stack Output:
versioned_hash (or 0 if out of bounds)
Gas Cost: 3 (GasFastestStep)
Operation:
if index < len(tx.blob_versioned_hashes):
stack.push(tx.blob_versioned_hashes[index])
else:
stack.push(0)
Hardfork: Available from Cancun onwards (EIP-4844)
Behavior
BLOBHASH retrieves a versioned hash from the transaction’s blob array:
Transaction blobs: [blob0, blob1, blob2]
Versioned hashes: [hash0, hash1, hash2]
BLOBHASH(0) → hash0 (32-byte versioned hash)
BLOBHASH(1) → hash1
BLOBHASH(2) → hash2
BLOBHASH(3) → 0 (out of bounds)
Versioned hashes are commitment hashes with a version byte prefix:
Format: 0x01 || sha256(kzg_commitment)[1:]
Version: 0x01 (KZG commitments)
Length: 32 bytes
Examples
Basic Usage
import { blobhash } from '@tevm/voltaire/evm/block';
import { createFrame } from '@tevm/voltaire/evm/Frame';
const blobHash0 = Bytes32();
blobHash0[0] = 0x01; // Version byte
// ... rest of hash
const frame = createFrame({
stack: [0n], // Query index 0
hardfork: 'CANCUN',
evm: {
blob_versioned_hashes: [blobHash0]
}
});
const err = blobhash(frame);
console.log(frame.stack); // [hash as u256]
console.log(frame.gasRemaining); // Original - 3
Pre-Cancun Error
// Before Cancun hardfork
const preCancunFrame = createFrame({
stack: [0n],
hardfork: 'SHANGHAI'
});
const err = blobhash(preCancunFrame);
console.log(err); // { type: "InvalidOpcode" }
Out of Bounds Access
// Query index beyond available blobs
const frame = createFrame({
stack: [5n], // Index 5
hardfork: 'CANCUN',
evm: {
blob_versioned_hashes: [blob0, blob1] // Only 2 blobs
}
});
blobhash(frame);
console.log(frame.stack); // [0n] - Out of bounds returns 0
Multiple Blob Access
// Access multiple blobs sequentially
const frame = createFrame({
hardfork: 'CANCUN',
evm: {
blob_versioned_hashes: [hash0, hash1, hash2]
}
});
// Get first blob hash
frame.stack.push(0n);
blobhash(frame);
const firstHash = frame.stack.pop();
// Get second blob hash
frame.stack.push(1n);
blobhash(frame);
const secondHash = frame.stack.pop();
console.log(`Hash 0: ${firstHash}, Hash 1: ${secondHash}`);
Index Overflow Handling
// Index larger than usize can represent
const frame = createFrame({
stack: [(1n << 256n) - 1n], // Maximum u256
hardfork: 'CANCUN',
evm: {
blob_versioned_hashes: [hash0]
}
});
blobhash(frame);
console.log(frame.stack); // [0n] - Safely returns 0
Gas Cost
Cost: 3 gas (GasFastestStep)
BLOBHASH is very cheap, matching the cost of basic arithmetic operations.
Comparison:
BLOBHASH: 3 gas
BLOBBASEFEE: 2 gas
ADD, SUB: 3 gas
BLOCKHASH: 20 gas
The low cost enables efficient blob verification without significant overhead.
Common Usage
Blob Commitment Verification
contract BlobVerifier {
event BlobCommitment(bytes32 versionedHash);
function verifyBlob(uint256 index, bytes32 expectedHash) external {
bytes32 actualHash = blobhash(index);
require(actualHash != bytes32(0), "Blob index out of bounds");
require(actualHash == expectedHash, "Blob hash mismatch");
emit BlobCommitment(actualHash);
}
}
L2 Data Availability
contract L2DataCommitment {
mapping(uint256 => bytes32) public batchCommitments;
uint256 public batchCounter;
function submitBatch() external {
// L2 sequencer submits batch commitment
bytes32 commitment = blobhash(0);
require(commitment != bytes32(0), "No blob data");
batchCommitments[batchCounter] = commitment;
batchCounter++;
emit BatchSubmitted(batchCounter - 1, commitment);
}
event BatchSubmitted(uint256 indexed batchId, bytes32 commitment);
}
Multi-Blob Processing
contract MultiBlobProcessor {
uint256 public constant MAX_BLOBS_PER_TX = 6; // EIP-4844 limit
function processBlobTransaction() external returns (bytes32[] memory) {
bytes32[] memory hashes = new bytes32[](MAX_BLOBS_PER_TX);
uint256 count = 0;
for (uint256 i = 0; i < MAX_BLOBS_PER_TX; i++) {
bytes32 hash = blobhash(i);
if (hash == bytes32(0)) break; // No more blobs
hashes[count] = hash;
count++;
}
// Resize array to actual count
assembly {
mstore(hashes, count)
}
return hashes;
}
}
Rollup Batch Commitment
contract RollupBatchCommitment {
struct Batch {
uint256 blockNumber;
bytes32 blobHash;
bytes32 stateRoot;
uint256 timestamp;
}
Batch[] public batches;
function commitBatch(bytes32 stateRoot) external {
bytes32 blobHash = blobhash(0);
require(blobHash != bytes32(0), "No blob data");
batches.push(Batch({
blockNumber: block.number,
blobHash: blobHash,
stateRoot: stateRoot,
timestamp: block.timestamp
}));
}
function verifyBatch(
uint256 batchId,
bytes32 expectedBlob
) external view returns (bool) {
return batches[batchId].blobHash == expectedBlob;
}
}
Blob Data Anchoring
contract BlobAnchor {
mapping(bytes32 => bool) public anchoredBlobs;
function anchorBlob(uint256 index) external {
bytes32 hash = blobhash(index);
require(hash != bytes32(0), "Invalid blob index");
require(!anchoredBlobs[hash], "Already anchored");
anchoredBlobs[hash] = true;
emit BlobAnchored(hash, block.number);
}
event BlobAnchored(bytes32 indexed hash, uint256 blockNumber);
}
Security Considerations
Blob Availability Window
Blobs are only available for a limited time (~18 days on Ethereum):
contract BlobExpiry {
struct BlobReference {
bytes32 hash;
uint256 expiryBlock;
}
uint256 constant BLOB_RETENTION_BLOCKS = 4096 * 32; // ~18 days
function storeBlob(uint256 index) external {
bytes32 hash = blobhash(index);
require(hash != bytes32(0), "No blob");
// Blob data expires after retention period
uint256 expiry = block.number + BLOB_RETENTION_BLOCKS;
// Store hash, but blob data won't be retrievable after expiry
}
}
Index Validation
Always check for zero return (out of bounds):
contract SafeBlobAccess {
function safeGetBlob(uint256 index) external view returns (bytes32) {
bytes32 hash = blobhash(index);
require(hash != bytes32(0), "Blob not found");
return hash;
}
// UNSAFE: Doesn't check zero
function unsafeGetBlob(uint256 index) external view returns (bytes32) {
return blobhash(index); // Could be 0!
}
}
Commitment vs Data
BLOBHASH returns commitment hash, not actual blob data:
contract BlobMisunderstanding {
// WRONG: Cannot access blob data on-chain
function getBlobData(uint256 index) external view returns (bytes memory) {
bytes32 hash = blobhash(index);
// hash is just a commitment, not the data itself!
// Actual blob data is NOT available to EVM
}
// CORRECT: Store commitment for off-chain verification
function storeCommitment(uint256 index) external returns (bytes32) {
bytes32 commitment = blobhash(index);
// Off-chain: fetch blob from beacon node
// On-chain: verify commitment matches
return commitment;
}
}
Transaction Context
BLOBHASH only works in blob transactions:
contract ContextAware {
function checkBlob() external view returns (bool) {
bytes32 hash = blobhash(0);
// In non-blob transaction: returns 0
// In blob transaction: returns hash
return hash != bytes32(0);
}
}
EIP-4844 Context
Type 3 Transaction (Blob Transaction):
├─ max_fee_per_gas
├─ max_priority_fee_per_gas
├─ max_fee_per_blob_gas
├─ blob_versioned_hashes[] ← BLOBHASH accesses this
└─ blobs[] (not in transaction hash)
Versioned Hash (32 bytes):
├─ Byte 0: 0x01 (version - KZG commitment)
└─ Bytes 1-31: sha256(kzg_commitment)[1:32]
Maximum Blobs per Transaction
// EIP-4844 limits
uint256 constant MAX_BLOBS_PER_BLOCK = 6;
uint256 constant TARGET_BLOBS_PER_BLOCK = 3;
uint256 constant BLOB_SIZE = 128 * 1024; // 128 KB per blob
Implementation
/**
* BLOBHASH opcode (0x49) - Get versioned blob hash
* Available: Cancun+ (EIP-4844)
*/
export function blobhash(frame: FrameType): EvmError | null {
// Check hardfork availability
if (frame.evm.hardfork.isBefore('CANCUN')) {
return { type: "InvalidOpcode" };
}
// Consume gas (GasFastestStep = 3)
frame.gasRemaining -= 3n;
if (frame.gasRemaining < 0n) {
frame.gasRemaining = 0n;
return { type: "OutOfGas" };
}
// Pop index
if (frame.stack.length < 1) return { type: "StackUnderflow" };
const index = frame.stack.pop();
// Get blob hash at index, or 0 if out of bounds
let hashValue = 0n;
if (index < BigInt(frame.evm.blob_versioned_hashes.length)) {
const indexNum = Number(index);
const blobHash = frame.evm.blob_versioned_hashes[indexNum];
// Convert 32-byte hash to u256
for (const byte of blobHash) {
hashValue = (hashValue << 8n) | BigInt(byte);
}
}
// else: out of bounds, hashValue remains 0
// Push result
if (frame.stack.length >= 1024) return { type: "StackOverflow" };
frame.stack.push(hashValue);
frame.pc += 1;
return null;
}
Edge Cases
Pre-Cancun Execution
// Before Cancun: InvalidOpcode
const frame = createFrame({
stack: [0n],
hardfork: 'SHANGHAI'
});
const err = blobhash(frame);
console.log(err); // { type: "InvalidOpcode" }
No Blobs in Transaction
// Non-blob transaction (empty array)
const frame = createFrame({
stack: [0n],
hardfork: 'CANCUN',
evm: {
blob_versioned_hashes: []
}
});
blobhash(frame);
console.log(frame.stack); // [0n]
Maximum Blob Index
// Access last blob in max-blob transaction
const frame = createFrame({
stack: [5n], // Index 5 (6th blob, 0-indexed)
hardfork: 'CANCUN',
evm: {
blob_versioned_hashes: new Array(6).fill(mockHash)
}
});
blobhash(frame);
console.log(frame.stack); // [hash as u256]
Index Overflow
// Index too large for usize
const frame = createFrame({
stack: [BigInt(Number.MAX_SAFE_INTEGER) + 1000n],
hardfork: 'CANCUN',
evm: { blob_versioned_hashes: [hash0] }
});
blobhash(frame);
console.log(frame.stack); // [0n] - Safely handled
Benchmarks
Performance:
- Index bounds check: O(1)
- Array access: O(1)
- Hash to u256 conversion: O(32)
Gas efficiency:
- 3 gas per query
- ~333,333 queries per million gas
References