Semantic Hash Type - Keccak256Hash extends Bytes32 with a keccak256 semantic flag. Same runtime representation (32 bytes), different compile-time meaning for type safety.
Overview
Keccak256Hash is a branded Uint8Array that extends Bytes32 with additional semantic meaning. It represents the output of a keccak256 hash operation through TypeScript branding.
While Bytes32 is a generic 32-byte value, Keccak256Hash explicitly communicates “this came from a keccak256 operation” at compile-time with zero runtime overhead.
import { Keccak256 } from '@tevm/voltaire/crypto/Keccak256';
import * as Bytes32 from '@tevm/voltaire/primitives/Bytes/Bytes32';
// Keccak256Hash output
const hash = Keccak256.hash(data);
// Convert to Bytes32 for generic operations
const bytes = Bytes32.from(hash);
// Both are 32 bytes at runtime
console.log(hash.length); // 32
console.log(bytes.length); // 32
Type Definition
Keccak256Hash extends Bytes32 with a keccak256 semantic symbol:
import type { brand } from '@tevm/voltaire/brand';
import type { BrandedBytes } from '@tevm/voltaire/primitives/Bytes';
type Keccak256Hash = BrandedBytes<32> & {
readonly [Symbol.for("keccak256")]: true;
};
Type structure:
- Base:
Uint8Array (32 bytes)
- Brand:
{ readonly [brand]: "Bytes32" }
- Size:
{ readonly size: 32 }
- Semantic:
{ readonly [Symbol.for("keccak256")]: true }
This layered branding provides:
- Runtime: Plain Uint8Array (zero overhead)
- Compile-time: Type safety preventing mixing hash types
- Semantic: Documents this value came from keccak256
Relationship to Bytes32
Keccak256Hash and Bytes32 are the same size (32 bytes) but convey different semantics:
import { Keccak256 } from '@tevm/voltaire/crypto/Keccak256';
import * as Bytes32 from '@tevm/voltaire/primitives/Bytes/Bytes32';
// Keccak256Hash = semantic "this is a keccak256 hash"
const txHash: Keccak256Hash = Keccak256.hash(txData);
// Bytes32 = generic 32-byte value
const storageSlot: Bytes32Type = Bytes32.from(0);
// Same runtime representation
console.log(txHash instanceof Uint8Array); // true
console.log(storageSlot instanceof Uint8Array); // true
// Different compile-time types
type HashType = typeof txHash; // Keccak256Hash
type BytesType = typeof storageSlot; // Bytes32Type
When to use each:
- Keccak256Hash - Transaction hashes, block hashes, merkle nodes, keccak256 outputs
- Bytes32 - Storage slots, generic 32-byte values, numeric conversions
See Bytes32 documentation for generic 32-byte operations.
Migration from Hash Primitive
Old Hash primitive replaced by Keccak256Hash for clarity:
// OLD (deprecated Hash primitive)
import { Keccak256 } from '@tevm/voltaire/crypto/Keccak256';
const hash = Keccak256.hash(data);
// NEW (Keccak256Hash type)
import { Keccak256 } from '@tevm/voltaire/crypto/Keccak256';
const hash = Keccak256.hash(data);
Migration steps:
- Replace
Keccak256.hash() with Keccak256.hash()
- Replace
HashType type with Keccak256Hash
- All operations remain the same (same 32-byte structure)
The new approach is more explicit about which hash algorithm produced the value.
When to Use Keccak256Hash vs Bytes32
Use Keccak256Hash when:
// Transaction hashes
const txHash = Keccak256.hash(txData);
// Block hashes
const blockHash = Keccak256.hash(blockHeader);
// Merkle tree nodes
const merkleNode = Keccak256.hashMultiple([left, right]);
// Event topics
const topic = Keccak256.topic('Transfer(address,address,uint256)');
// Any value that came from keccak256
const digest = Keccak256.hash(message);
Use Bytes32 when:
import * as Bytes32 from '@tevm/voltaire/primitives/Bytes/Bytes32';
// Storage slots
const slot = Bytes32.from(0);
// Generic 32-byte values
const padding = Bytes32.zero();
// Numeric conversions
const value = Bytes32.from(42n);
// When hash algorithm doesn't matter
const genericHash: Bytes32.Bytes32Type = Bytes32.from(anyHash);
Constructors
All Keccak256Hash values come from Keccak256 module methods:
Keccak256.hash
Direct hashing of bytes:
import { Keccak256 } from '@tevm/voltaire/crypto/Keccak256';
const data = new Uint8Array([1, 2, 3, 4, 5]);
const hash: Keccak256Hash = Keccak256.hash(data);
Keccak256.hashString
Hash UTF-8 string:
const hash: Keccak256Hash = Keccak256.hashString('hello world');
Keccak256.hashHex
Hash hex-encoded string:
const hash: Keccak256Hash = Keccak256.hashHex('0xdeadbeef');
Keccak256.hashMultiple
Hash multiple chunks:
const hash: Keccak256Hash = Keccak256.hashMultiple([chunk1, chunk2, chunk3]);
Keccak256.topic
Event topic (full 32-byte hash):
const topic: Keccak256Hash = Keccak256.topic('Transfer(address,address,uint256)');
See Keccak256 index for all hash methods.
Conversions
Keccak256Hash can be converted using Bytes32 operations:
toHex
Convert to hex string:
import * as Bytes32 from '@tevm/voltaire/primitives/Bytes/Bytes32';
const hash = Keccak256.hash(data);
const hex = Bytes32.toHex(hash);
// "0x..." (64 hex characters)
toBytes
Convert to plain Uint8Array:
const bytes = Bytes32.toUint8Array(hash);
console.log(bytes instanceof Uint8Array); // true
toBigint
Convert to bigint (big-endian):
const value = Bytes32.toBigint(hash);
console.log(typeof value); // "bigint"
toAddress
Extract address (last 20 bytes):
import { Keccak256 } from '@tevm/voltaire/crypto/Keccak256';
// Ethereum address derivation
const pubKeyHash = Keccak256.hash(publicKey);
const address = Bytes32.toAddress(pubKeyHash);
Operations
equals
Check equality:
import * as Bytes32 from '@tevm/voltaire/primitives/Bytes/Bytes32';
const hash1 = Keccak256.hash(data1);
const hash2 = Keccak256.hash(data2);
const same = Bytes32.equals(hash1, hash2);
compare
Compare two hashes:
const cmp = Bytes32.compare(hash1, hash2);
// -1 if hash1 < hash2
// 0 if hash1 === hash2
// 1 if hash1 > hash2
isZero
Check if all zeros (rare for cryptographic hashes):
const isEmpty = Bytes32.isZero(hash);
clone
Create independent copy:
const copy = Bytes32.clone(hash);
See Bytes32 documentation for all operations.
Ethereum Use Cases
Transaction Hashes
import { Keccak256 } from '@tevm/voltaire/crypto/Keccak256';
import * as Transaction from '@tevm/voltaire/primitives/Transaction';
// Hash transaction for signing
const tx = Transaction.from({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e',
value: 1000000000000000000n,
nonce: 5n,
gasLimit: 21000n,
maxFeePerGas: 20000000000n,
maxPriorityFeePerGas: 1000000000n,
});
const txHash: Keccak256Hash = Keccak256.hash(Transaction.toBytes(tx));
Block Hashes
// Block header hash
const blockHeaderData = encodeBlockHeader(block);
const blockHash: Keccak256Hash = Keccak256.hash(blockHeaderData);
Merkle Tree Nodes
// Compute merkle parent node
const leftNode = Keccak256.hash(leftData);
const rightNode = Keccak256.hash(rightData);
// Parent = keccak256(left || right)
const parentNode: Keccak256Hash = Keccak256.hashMultiple([leftNode, rightNode]);
Event Topics
// Event signature hash
const transferTopic: Keccak256Hash = Keccak256.topic('Transfer(address,address,uint256)');
// Used in log filtering
const logs = await provider.getLogs({
topics: [transferTopic],
address: tokenAddress,
});
Address Derivation
import * as Bytes32 from '@tevm/voltaire/primitives/Bytes/Bytes32';
// Ethereum address = last 20 bytes of keccak256(publicKey)
const pubKeyHash: Keccak256Hash = Keccak256.hash(publicKey);
const address = Bytes32.toAddress(pubKeyHash);
Storage Proofs
// Storage slot key
const storageKey = Keccak256.hashMultiple([
address,
Bytes32.from(slot),
]);
// Merkle proof verification
function verifyProof(leaf: Keccak256Hash, proof: Keccak256Hash[], root: Keccak256Hash): boolean {
let current = leaf;
for (const sibling of proof) {
current = Keccak256.hashMultiple([current, sibling]);
}
return Bytes32.equals(current, root);
}