Skip to main content

Try it Live

Run Keccak256 examples in the interactive playground
Future Plans: This page is planned and under active development. Examples are placeholders and will be replaced with accurate, tested content.

Usage Patterns

Best practices and common patterns for using Keccak256 in Ethereum applications.

Implementation Selection

Default Strategy

Start with pure TypeScript, optimize when needed:
// 1. Start with default (universal compatibility)
import { Keccak256 } from '@tevm/voltaire/Keccak256';

const hash = Keccak256.hash(data);

// 2. Profile - is hashing a bottleneck?
// 3. If yes, switch to WASM or native

// 3a. Browser/Edge - use standalone WASM (3KB)
import { Keccak256Standalone } from '@tevm/voltaire/crypto/keccak256.standalone';
await Keccak256Standalone.init();
const hash = Keccak256Standalone.hash(data);

// 3b. Node.js/Bun - use native (fastest)
import { Keccak256 } from '@tevm/voltaire/native/crypto/keccak256';
const hash = Keccak256.hash(data);

Environment-Based Selection

Automatic selection based on runtime:
async function getKeccak256Impl() {
  // Try native first (Node.js/Bun)
  if (typeof process !== 'undefined' && process.versions?.node) {
    try {
      const { Keccak256 } = await import('@tevm/voltaire/Keccak256/native');
      return Keccak256;
    } catch {
      // Native not available, fall through
    }
  }

  // Try WASM (browser/edge)
  if (typeof WebAssembly !== 'undefined') {
    const { Keccak256Standalone } = await import('@tevm/voltaire/Keccak256');
    await Keccak256Standalone.init();
    return Keccak256Standalone;
  }

  // Fallback to pure JS
  const { Keccak256 } = await import('@tevm/voltaire/Keccak256');
  return Keccak256;
}

// Use selected implementation
const Keccak256 = await getKeccak256Impl();
const hash = Keccak256.hash(data);

Bundle Size Optimization

Minimize bundle size for client deployments:
// ❌ Large bundle - imports full primitives WASM (200KB)
import { Keccak256Wasm } from '@tevm/voltaire/Keccak256/wasm';

// ✅ Small bundle - standalone WASM (3KB)
import { Keccak256Standalone } from '@tevm/voltaire/crypto/keccak256.standalone';

// ✅ Conditional import (3KB WASM only when needed)
const Keccak256 = await import('@tevm/voltaire/Keccak256')
  .then(m => m.Keccak256Standalone);
await Keccak256.init();

Type Safety

Working with Keccak256Hash

Type-safe hash handling:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Hex } from '@tevm/voltaire/Hex';

// Hash returns branded type
const hash: Keccak256Hash = Keccak256.hash(data);

// Use as Uint8Array
hash[0];          // First byte
hash.length;      // Always 32
hash.slice(0, 4); // First 4 bytes (selector)
hash.slice(12);   // Last 20 bytes (address)

// Convert to other types
const hexHash: string = Hex.fromBytes(hash);
const bigintHash: bigint = BigInt('0x' + hexHash.slice(2));

// Pass to functions expecting Uint8Array
function processHash(h: Uint8Array) { }
processHash(hash); // ✅ Works - branded type extends Uint8Array

Type Narrowing

Ensure correct input types:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Address } from '@tevm/voltaire/Address';

// ❌ Type error - string not Uint8Array
const hash = Keccak256.hash('0x1234'); // Error

// ✅ Convert first
const hash = Keccak256.hashHex('0x1234'); // OK

// ✅ Use typed primitives
const address = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e');
const addressHash = Keccak256.hash(address); // OK - Address extends Uint8Array

Performance Optimization

Batch Processing

Process multiple hashes efficiently:
import { Keccak256 } from '@tevm/voltaire/Keccak256';

// ❌ Inefficient - repeated function calls
const hashes = data.map(d => Keccak256.hash(d));

// ✅ More efficient - batch with typed array
function batchHash(inputs: readonly Uint8Array[]): Uint8Array[] {
  const result = new Array(inputs.length);
  for (let i = 0; i < inputs.length; i++) {
    result[i] = Keccak256.hash(inputs[i]);
  }
  return result;
}

const hashes = batchHash(dataArray);

Caching Selectors

Cache frequently used selectors:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Hex } from '@tevm/voltaire/Hex';

// Create selector cache
const SelectorCache = {
  transfer: Keccak256.selector('transfer(address,uint256)'),
  approve: Keccak256.selector('approve(address,uint256)'),
  balanceOf: Keccak256.selector('balanceOf(address)'),
} as const;

// Use cached selectors
function buildTransferCalldata(to: Uint8Array, amount: bigint): Uint8Array {
  const selector = SelectorCache.transfer; // No recomputation
  const params = encodeParams(['address', 'uint256'], [to, amount]);
  return concat([selector, params]);
}

Reusing Hash Results

Avoid redundant hashing:
import { Keccak256 } from '@tevm/voltaire/Keccak256';

// ❌ Inefficient - hashes initCode twice
const create2Addr1 = Keccak256.create2Address(
  deployer,
  salt1,
  Keccak256.hash(initCode)
);
const create2Addr2 = Keccak256.create2Address(
  deployer,
  salt2,
  Keccak256.hash(initCode)
);

// ✅ Efficient - hash once, reuse
const initCodeHash = Keccak256.hash(initCode);
const create2Addr1 = Keccak256.create2Address(deployer, salt1, initCodeHash);
const create2Addr2 = Keccak256.create2Address(deployer, salt2, initCodeHash);

Error Handling

Input Validation

Validate inputs before hashing:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Address } from '@tevm/voltaire/Address';

function computeContractAddress(
  sender: Uint8Array,
  nonce: bigint
): Uint8Array {
  // Validate sender length
  if (sender.length !== 20) {
    throw new Error(`Invalid sender length: ${sender.length}, expected 20`);
  }

  // Validate nonce range
  if (nonce < 0n) {
    throw new Error(`Invalid nonce: ${nonce}, must be >= 0`);
  }

  return Keccak256.contractAddress(sender, nonce);
}

Catching Errors

Handle CREATE2 validation errors:
import { Keccak256 } from '@tevm/voltaire/Keccak256';

function safeCreate2Address(
  deployer: Uint8Array,
  salt: Uint8Array,
  initCodeHash: Uint8Array
): Uint8Array | null {
  try {
    return Keccak256.create2Address(deployer, salt, initCodeHash);
  } catch (error) {
    if (error instanceof InvalidLengthError) {
      console.error('Invalid input length:', error.message);
      return null;
    }
    throw error; // Unexpected error
  }
}

Ethereum Integration

Transaction Hashing

Hash transaction data for signing:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Rlp } from '@tevm/voltaire/Rlp';

// Legacy transaction hash
function hashLegacyTransaction(tx: {
  nonce: bigint;
  gasPrice: bigint;
  gasLimit: bigint;
  to: Uint8Array;
  value: bigint;
  data: Uint8Array;
}): Uint8Array {
  const encoded = Rlp.encode([
    tx.nonce,
    tx.gasPrice,
    tx.gasLimit,
    tx.to,
    tx.value,
    tx.data
  ]);
  return Keccak256.hash(encoded);
}

// EIP-1559 transaction hash
function hashEip1559Transaction(tx: {
  chainId: bigint;
  nonce: bigint;
  maxPriorityFeePerGas: bigint;
  maxFeePerGas: bigint;
  gasLimit: bigint;
  to: Uint8Array;
  value: bigint;
  data: Uint8Array;
  accessList: unknown[];
}): Uint8Array {
  const encoded = Rlp.encode([
    tx.chainId,
    tx.nonce,
    tx.maxPriorityFeePerGas,
    tx.maxFeePerGas,
    tx.gasLimit,
    tx.to,
    tx.value,
    tx.data,
    tx.accessList
  ]);
  // EIP-2718: prepend transaction type (0x02)
  const typed = new Uint8Array([0x02, ...encoded]);
  return Keccak256.hash(typed);
}

Address Derivation

Derive Ethereum address from public key:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Secp256k1 } from '@tevm/voltaire/Secp256k1';

function publicKeyToAddress(publicKey: Uint8Array): Uint8Array {
  // Public key must be uncompressed (65 bytes starting with 0x04)
  if (publicKey.length !== 65 || publicKey[0] !== 0x04) {
    throw new Error('Public key must be uncompressed (65 bytes, starts with 0x04)');
  }

  // Hash public key (excluding 0x04 prefix)
  const pubKeyWithoutPrefix = publicKey.slice(1);
  const hash = Keccak256.hash(pubKeyWithoutPrefix);

  // Take last 20 bytes as address
  return hash.slice(12);
}

// Derive from private key
const privateKey = new Uint8Array(32);
crypto.getRandomValues(privateKey);

const publicKey = Secp256k1.derivePublicKey(privateKey, false); // uncompressed
const address = publicKeyToAddress(publicKey);

Contract Interaction

Build contract calls with selectors:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Abi } from '@tevm/voltaire/Abi';

function encodeContractCall(
  signature: string,
  types: string[],
  values: unknown[]
): Uint8Array {
  // Get selector
  const selector = Keccak256.selector(signature);

  // Encode parameters
  const params = Abi.encodeParams(types, values);

  // Combine selector + params
  const calldata = new Uint8Array(selector.length + params.length);
  calldata.set(selector, 0);
  calldata.set(params, selector.length);

  return calldata;
}

// Build transfer call
const transferCall = encodeContractCall(
  'transfer(address,uint256)',
  ['address', 'uint256'],
  [recipientAddress, transferAmount]
);

// Send transaction
await provider.sendTransaction({
  to: tokenAddress,
  data: transferCall
});

Event Filtering

Filter logs by event signature:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Hex } from '@tevm/voltaire/Hex';

async function getTransferLogs(
  tokenAddress: string,
  fromBlock: number,
  toBlock: number
): Promise<Log[]> {
  // Compute Transfer event topic
  const transferTopic = Hex.fromBytes(
    Keccak256.topic('Transfer(address,address,uint256)')
  );

  // Query logs
  return await provider.getLogs({
    address: tokenAddress,
    topics: [transferTopic],
    fromBlock,
    toBlock
  });
}

// Get all transfers
const logs = await getTransferLogs(
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
  0,
  'latest'
);

// Process logs
for (const log of logs) {
  const from = '0x' + log.topics[1].slice(26); // Remove padding
  const to = '0x' + log.topics[2].slice(26);
  console.log(`Transfer from ${from} to ${to}`);
}

Security Best Practices

Signature Normalization

Always normalize function/event signatures:
import { Keccak256 } from '@tevm/voltaire/Keccak256';

// ❌ Dangerous - user input not normalized
function getSelectorUnsafe(userInput: string): Uint8Array {
  return Keccak256.selector(userInput);
}

// ✅ Safe - normalize signature
function getSelectorSafe(signature: string): Uint8Array {
  // Remove whitespace
  const normalized = signature.replace(/\s/g, '');

  // Validate format: name(type1,type2,...)
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*\([a-zA-Z0-9_,\[\]]*\)$/.test(normalized)) {
    throw new Error(`Invalid signature format: ${signature}`);
  }

  return Keccak256.selector(normalized);
}

Input Sanitization

Validate inputs before hashing:
import { Keccak256 } from '@tevm/voltaire/Keccak256';

function hashUserData(data: unknown): Uint8Array {
  // ❌ Unsafe - no validation
  // return Keccak256.hash(data as Uint8Array);

  // ✅ Safe - validate type
  if (!(data instanceof Uint8Array)) {
    throw new TypeError('Data must be Uint8Array');
  }

  // ✅ Safe - validate length
  if (data.length === 0) {
    throw new Error('Data cannot be empty');
  }

  return Keccak256.hash(data);
}

Constant-Time Comparisons

Compare hashes in constant time:
import { Keccak256 } from '@tevm/voltaire/Keccak256';

// ❌ Timing attack vulnerable
function verifyHashUnsafe(data: Uint8Array, expectedHash: Uint8Array): boolean {
  const hash = Keccak256.hash(data);
  return hash.toString() === expectedHash.toString(); // Early exit on mismatch
}

// ✅ Constant-time comparison
function verifyHashSafe(data: Uint8Array, expectedHash: Uint8Array): boolean {
  const hash = Keccak256.hash(data);

  if (hash.length !== expectedHash.length) {
    return false;
  }

  let result = 0;
  for (let i = 0; i < hash.length; i++) {
    result |= hash[i] ^ expectedHash[i];
  }

  return result === 0;
}

Testing

Test Vectors

Validate implementation with known vectors:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { Hex } from '@tevm/voltaire/Hex';
import { describe, it, expect } from 'vitest';

describe('Keccak256', () => {
  it('produces correct hash for empty string', () => {
    const hash = Keccak256.hashString('');
    expect(Hex.fromBytes(hash)).toBe(
      '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
    );
  });

  it('produces correct hash for "abc"', () => {
    const hash = Keccak256.hashString('abc');
    expect(Hex.fromBytes(hash)).toBe(
      '0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45'
    );
  });

  it('produces correct transfer selector', () => {
    const selector = Keccak256.selector('transfer(address,uint256)');
    expect(Hex.fromBytes(selector)).toBe('0xa9059cbb');
  });
});

Property Testing

Test hash properties:
import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { describe, it, expect } from 'vitest';

describe('Keccak256 properties', () => {
  it('always produces 32-byte output', () => {
    const inputs = [
      new Uint8Array(0),
      new Uint8Array(1),
      new Uint8Array(100),
      new Uint8Array(10000)
    ];

    for (const input of inputs) {
      const hash = Keccak256.hash(input);
      expect(hash.length).toBe(32);
    }
  });

  it('is deterministic', () => {
    const data = new Uint8Array([1, 2, 3, 4, 5]);
    const hash1 = Keccak256.hash(data);
    const hash2 = Keccak256.hash(data);
    expect(hash1).toEqual(hash2);
  });

  it('produces different hashes for different inputs', () => {
    const data1 = new Uint8Array([1, 2, 3]);
    const data2 = new Uint8Array([1, 2, 4]);
    const hash1 = Keccak256.hash(data1);
    const hash2 = Keccak256.hash(data2);
    expect(hash1).not.toEqual(hash2);
  });
});

Common Pitfalls

SHA-3 vs Keccak Confusion

import { Keccak256 } from '@tevm/voltaire/Keccak256';
import { sha3_256 } from '@noble/hashes/sha3';

// ❌ Wrong - SHA-3 is NOT Keccak-256
const wrongHash = sha3_256(data); // Different padding

// ✅ Correct - Use Keccak-256
const correctHash = Keccak256.hash(data);

// Ethereum uses original Keccak, not finalized SHA-3

Signature Format Errors

import { Keccak256 } from '@tevm/voltaire/Keccak256';

// ❌ Wrong - space in signature
const wrong1 = Keccak256.selector('transfer(address, uint256)');

// ❌ Wrong - parameter names included
const wrong2 = Keccak256.selector('transfer(address to, uint256 amount)');

// ❌ Wrong - non-canonical type
const wrong3 = Keccak256.selector('transfer(address,uint)');

// ✅ Correct - canonical signature
const correct = Keccak256.selector('transfer(address,uint256)');

Address Derivation Errors

import { Keccak256 } from '@tevm/voltaire/Keccak256';

// ❌ Wrong - hashing compressed public key
const compressedPubKey = new Uint8Array(33); // 33 bytes
const wrongAddr = Keccak256.hash(compressedPubKey).slice(12);

// ❌ Wrong - including 0x04 prefix
const uncompressedPubKey = new Uint8Array(65);
const wrongAddr2 = Keccak256.hash(uncompressedPubKey).slice(12);

// ✅ Correct - hash uncompressed key without 0x04 prefix
const correctAddr = Keccak256.hash(uncompressedPubKey.slice(1)).slice(12);