Skip to main content
TypeScript-first: Below are Zig equivalents using @import("crypto").keccak256 and @import("primitives") helpers.

Keccak256

Keccak256 is a cryptographic one-way hash function based on the sponge construction that produces a fixed 32-byte digest from arbitrary-length input.

Overview

Mainnet-critical algorithm - Used in Ethereum execution layer for transaction hashing, address derivation (last 20 bytes of hash), function selectors (first 4 bytes), event topics, and Merkle Patricia tree state roots. In Zig, use @import("crypto").keccak256 from the Voltaire crypto module. It returns a 32-byte array. Keccak256 is fundamental to Ethereum’s security model:
  • Address derivation: Computing Ethereum addresses from public keys (last 20 bytes of Keccak256(publicKey))
  • Function selectors: First 4 bytes of Keccak256(signature) identify contract methods
  • Event topics: Keccak256(eventSignature) creates indexed event identifiers
  • Merkle Patricia trees: Hashing transaction and state trie nodes
  • Contract addresses: CREATE and CREATE2 address calculation
Ethereum uses the original Keccak-256 algorithm (pre-NIST), NOT the finalized SHA-3 standard. They differ in padding scheme: SHA-3 uses 0x06 padding while Keccak uses 0x01. Do not use SHA-3 libraries for Ethereum - they will produce incorrect results. Use Keccak-256 specifically.

Implementation Options

Tevm provides two Keccak256 implementations:

1. Default (WASM)

Compiled from Zig’s stdlib Keccak256 - both faster and smaller than pure JavaScript alternatives.
const crypto = @import("crypto");
const primitives = @import("primitives");

// Hash bytes
const data = try primitives.Hex.fromHex(allocator, "0x0102030405");
defer allocator.free(data);
const hash = crypto.keccak256.hash(data); // [32]u8

// Hash string
const str_hash = crypto.keccak256.hash("hello"); // [32]u8

// Hash hex string
const h = try primitives.Hex.hexToBytesFixed(2, "0x1234");
const hex_hash = crypto.keccak256.hash(&h);
When to use:
  • Default choice for most applications
  • Better performance than pure JavaScript
  • Smaller bundle size than pure JavaScript alternatives

2. Pure TypeScript

Uses @noble/hashes - zero WASM dependency, maximum compatibility.
import { Keccak256HashTs } from '@tevm/voltaire/crypto/keccak256.ts';

const hash: Keccak256Hash = Keccak256HashTs.from(data);
// Pure TypeScript
// No WASM dependency
When to use:
  • Need to avoid WASM dependency
  • Debugging or development scenarios
  • Specific compatibility requirements

Implementation Selection

The APIs are identical across implementations - you can swap them without changing your code:
import { Keccak256Hash } from '@tevm/voltaire/crypto/Keccak256';
import { Keccak256HashTs } from '@tevm/voltaire/crypto/keccak256.ts';
import { Keccak256HashWasm } from '@tevm/voltaire/crypto/keccak256.wasm';

// All have identical API
const hash1: Keccak256Hash = Keccak256Hash.from(data);      // Default (WASM)
const hash2: Keccak256Hash = Keccak256HashTs.from(data);    // TypeScript
const hash3: Keccak256Hash = Keccak256HashWasm.from(data);  // Explicit WASM

Quick Start

View executable examples: hash-bytes.ts | hash-string.ts | hash-hex.ts
import { Keccak256Hash } from '@tevm/voltaire/crypto/keccak256';
import { Hex } from '@tevm/voltaire/primitives/Hex';

// Hash bytes
const data = Hex.toBytes('0x0102030405');
const hash: Keccak256Hash = Keccak256Hash.from(data);
// Uint8Array(32) [...]

// Hash string (UTF-8 encoded)
const stringHash: Keccak256Hash = Keccak256Hash.fromString('hello');
// Uint8Array(32) [...]

// Hash hex string
const hexHash: Keccak256Hash = Keccak256Hash.fromHex('0x1234abcd');
// Uint8Array(32) [...]

API Reference

All Keccak256 implementations share the same API. Return type is Keccak256Hash (32-byte Uint8Array with type branding).

Keccak256Hash.from(data: Uint8Array): Keccak256Hash

Hash arbitrary bytes with Keccak-256. Parameters:
  • data: Input data to hash (Uint8Array)
Returns: 32-byte hash (Keccak256Hash extends Uint8Array) Example:
import { Hex } from '@tevm/voltaire/primitives/Hex';

const hash: Keccak256Hash = Keccak256Hash.from(Hex.toBytes('0x010203'));
console.log(hash.length); // 32
Legacy method Keccak256.hash(data) still works but Keccak256Hash.from(data) is preferred.

Keccak256Hash.fromString(str: string): Keccak256Hash

Hash UTF-8 string with Keccak-256. String is UTF-8 encoded before hashing using TextEncoder. Parameters:
  • str: String to hash
Returns: 32-byte hash (Keccak256Hash) Example:
const hash: Keccak256Hash = Keccak256Hash.fromString('hello');
// Equivalent to: Keccak256Hash.from(new TextEncoder().encode('hello'))
Legacy method Keccak256.hashString(str) still works but Keccak256Hash.fromString(str) is preferred.

Keccak256Hash.fromHex(hex: string): Keccak256Hash

Hash hex-encoded string with Keccak-256. Hex string is decoded to bytes before hashing. Supports both “0x”-prefixed and unprefixed hex. Parameters:
  • hex: Hex string to hash
Returns: 32-byte hash (Keccak256Hash) Example:
const hash: Keccak256Hash = Keccak256Hash.fromHex('0x1234');
Legacy method Keccak256.hashHex(hex) still works but Keccak256Hash.fromHex(hex) is preferred.

Keccak256Hash.fromMultiple(chunks: readonly Uint8Array[]): Keccak256Hash

Hash multiple data chunks in sequence. Equivalent to hashing the concatenation of all chunks, but can be more efficient for pre-chunked data. Parameters:
  • chunks: Array of data chunks to hash
Returns: 32-byte hash (Keccak256Hash) Example:
import { Hex } from '@tevm/voltaire/primitives/Hex';

const hash: Keccak256Hash = Keccak256Hash.fromMultiple([
  Hex.toBytes('0x0102'),
  Hex.toBytes('0x0304'),
  Hex.toBytes('0x0506')
]);
Legacy method Keccak256.hashMultiple(chunks) still works but Keccak256Hash.fromMultiple(chunks) is preferred.

Keccak256Hash.selector(signature: string): Uint8Array

Compute function selector (first 4 bytes of Keccak-256 hash). Used in Ethereum to identify contract functions in transaction calldata. The function selector is the first 4 bytes of the Keccak-256 hash of the function signature. Parameters:
  • signature: Function signature string (e.g., “transfer(address,uint256)”)
Returns: 4-byte selector Example:
const selector = Keccak256Hash.selector('transfer(address,uint256)');
// Uint8Array(4) [0xa9, 0x05, 0x9c, 0xbb]

// Used in transaction calldata:
// 0xa9059cbb + ABI-encoded parameters

Keccak256Hash.fromTopic(signature: string): Keccak256Hash

Compute event topic (32-byte Keccak-256 hash). Used for Ethereum event signatures in logs. Topics are the full 32-byte hash of the event signature. Parameters:
  • signature: Event signature string (e.g., “Transfer(address,address,uint256)”)
Returns: 32-byte topic (Keccak256Hash) Example:
const topic: Keccak256Hash = Keccak256Hash.fromTopic('Transfer(address,address,uint256)');
// Full 32-byte hash used in logs.topics[0]
Legacy method Keccak256.topic(signature) still works but Keccak256Hash.fromTopic(signature) is preferred.

Keccak256Hash.contractAddress(sender: Uint8Array, nonce: bigint): Uint8Array

Compute contract address from deployer and nonce (CREATE). Uses CREATE formula: address = keccak256(rlp([sender, nonce]))[12:] Parameters:
  • sender: Deployer address (20 bytes)
  • nonce: Transaction nonce
Returns: Contract address (20 bytes) Throws: Error if sender is not 20 bytes Example:
import { Address } from '@tevm/voltaire/primitives/Address';

const deployer = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e');
const nonce = 5n;
const contractAddr = Keccak256Hash.contractAddress(deployer, nonce);

Keccak256Hash.create2Address(deployer: Uint8Array, salt: Uint8Array | bigint, initCode: Uint8Array): Uint8Array

Compute contract address using CREATE2. Uses CREATE2 formula: address = keccak256(0xff ++ sender ++ salt ++ keccak256(initCode))[12:] This allows deterministic contract addresses independent of nonce. Parameters:
  • deployer: Deployer address (20 bytes)
  • salt: 32-byte salt (or bigint converted to 32 bytes)
  • initCode: Contract initialization bytecode
Returns: Contract address (20 bytes) Example:
import { Address } from '@tevm/voltaire/primitives/Address';
import { Bytes32 } from '@tevm/voltaire/primitives/Bytes32';
import { Bytecode } from '@tevm/voltaire/primitives/Bytecode';

const deployer = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e');
const salt = Bytes32('0x0000000000000000000000000000000000000000000000000000000000000001');
const initCode = Bytecode('0x60806040523480156100105760006000fd5b50610015565b60c3806100236000396000f3fe');
const create2Addr = Keccak256Hash.create2Address(deployer, salt, initCode);

Constants

Keccak256Hash.DIGEST_SIZE  // 32 - Output size in bytes
Keccak256Hash.RATE         // 136 - Rate in bytes (1088 bits)
Keccak256Hash.STATE_SIZE   // 25 - State size (25 u64 words)

Type Alias

All Keccak256 functions return Keccak256Hash, a branded Uint8Array type:
import type { Keccak256Hash } from '@tevm/voltaire/crypto/Keccak256';

// Keccak256Hash is a Uint8Array with type branding for compile-time safety
type Keccak256Hash = Uint8Array & { readonly __tag: "Keccak256Hash" };

// All hash functions return this type
const hash: Keccak256Hash = Keccak256Hash.from(data);
const topic: Keccak256Hash = Keccak256Hash.fromTopic("Transfer(address,address,uint256)");

// Zero runtime overhead - just TypeScript compile-time checking
console.log(hash instanceof Uint8Array); // true
See Keccak256Hash for full type documentation.

Test Vectors

Known Keccak256 test vectors for validation:
// Empty string
Keccak256Hash.fromString("")
// 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470

// "abc"
Keccak256Hash.fromString("abc")
// 0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45

// "hello"
Keccak256Hash.fromString("hello")
// 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8

// "The quick brown fox jumps over the lazy dog"
Keccak256Hash.fromString("The quick brown fox jumps over the lazy dog")
// 0x4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15

// transfer(address,uint256) selector
Keccak256Hash.selector("transfer(address,uint256)")
// Uint8Array(4) [0xa9, 0x05, 0x9c, 0xbb]

Security Considerations

Collision Resistance

Keccak256 provides strong collision resistance with 128-bit security. Finding two inputs that produce the same hash is computationally infeasible.

Preimage Resistance

Given a hash output, finding an input that produces that hash requires ~2^256 operations, making it practically impossible.

Second Preimage Resistance

Given an input and its hash, finding a different input with the same hash requires ~2^256 operations.

Ethereum-Specific Notes

  • Deterministic: Same input always produces same output
  • One-way: Hash output cannot be reversed to recover input
  • Avalanche effect: Small input changes cause large output changes
  • Constant-time: Implementation avoids timing side-channels

Implementation Details

WASM (Default)

Compiled from Zig’s stdlib Keccak-256 via unified loader (ReleaseSmall):
import { Keccak256HashWasm } from '@tevm/voltaire/crypto/keccak256.wasm';

await Keccak256HashWasm.init(); // Load primitives.wasm
const hash: Keccak256Hash = Keccak256HashWasm.from(data);
Characteristics:
  • Part of main primitives.wasm bundle
  • Includes all Tevm primitives and crypto
  • Requires async init() before use
  • Both faster and smaller than pure JavaScript alternatives
Source: src/crypto/keccak256.wasm.ts + wasm/primitives.wasm

Pure TypeScript

Direct wrapper around @noble/hashes battle-tested implementation:
import { keccak_256 } from "@noble/hashes/sha3.js";

export function from(data: Uint8Array): Keccak256Hash {
  return keccak_256(data) as Keccak256Hash;
}
Characteristics:
  • Zero WASM dependencies
  • Constant-time implementation
  • Runs everywhere (Node.js, browsers, Deno, Bun, edge workers)
  • Thoroughly audited and tested
Source: src/crypto/Keccak256/hash.js
  • Keccak256Hash - 32-byte hash type used by Keccak256
  • Address - Address derivation uses Keccak256
  • SHA256 - Alternative hash function
  • RIPEMD160 - Used in Bitcoin address derivation
  • Blake2 - High-performance alternative hash