What is Namehash?
Namehash is the algorithm used by ENS to convert human-readable domain names (like alice.eth) into unique, fixed-size identifiers (bytes32 hashes) that are used throughout the ENS smart contracts.
ENS smart contracts never work with strings directly. All operations use namehash values, which are deterministic 256-bit hashes.
Why Namehash?
Namehash provides several critical properties:
Fixed Size - All names, regardless of length, produce a 32-byte hash
Deterministic - The same name always produces the same hash
Hierarchical - Parent/child relationships are preserved
Efficient - Operations on hashes are cheaper than string operations
Privacy - Subdomain hashes don’t reveal parent domain names
The Algorithm
Namehash is a recursive algorithm defined in EIP-137 .
Definition
namehash([]) = 0x0000000000000000000000000000000000000000000000000000000000000000
namehash([label, ...]) = keccak256(namehash(...), keccak256(label))
Step-by-Step Process
Start with the empty root : 0x0000...0000 (32 zero bytes)
For each label (from right to left):
Hash the label with keccak256
Concatenate the parent hash with the label hash
Hash the result with keccak256
Return the final hash
Visual Breakdown
Examples
Example 1: Computing namehash(‘eth’)
const { keccak256 , toUtf8Bytes , concat , hexlify } = require ( 'ethers' );
// Step 1: Start with root (empty)
const root = '0x0000000000000000000000000000000000000000000000000000000000000000' ;
// Step 2: Hash the label 'eth'
const labelHash = keccak256 ( toUtf8Bytes ( 'eth' ));
// labelHash = 0x4f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0
// Step 3: Concatenate root + labelHash and hash the result
const ethNode = keccak256 ( concat ([ root , labelHash ]));
// ethNode = 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae
The .eth TLD always resolves to 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae
Example 2: Computing namehash(‘alice.eth’)
// Start with the hash we computed for 'eth'
const ethNode = '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' ;
// Hash the label 'alice'
const aliceLabelHash = keccak256 ( toUtf8Bytes ( 'alice' ));
// aliceLabelHash = 0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec
// Concatenate ethNode + aliceLabelHash and hash
const aliceEthNode = keccak256 ( concat ([ ethNode , aliceLabelHash ]));
// aliceEthNode = 0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec
Example 3: Computing namehash(‘pay.alice.eth’)
// Start with 'alice.eth' hash from above
const aliceEthNode = '0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec' ;
// Hash the label 'pay'
const payLabelHash = keccak256 ( toUtf8Bytes ( 'pay' ));
// Concatenate and hash
const payAliceEthNode = keccak256 ( concat ([ aliceEthNode , payLabelHash ]));
Implementation in Solidity
You can see namehash in action in the ENS registry:
function setSubnodeOwner (
bytes32 node ,
bytes32 label ,
address owner
) public virtual override authorised ( node ) returns ( bytes32 ) {
bytes32 subnode = keccak256 ( abi . encodePacked (node, label));
_setOwner (subnode, owner);
emit NewOwner (node, label, owner);
return subnode;
}
This function computes the namehash of a subdomain by:
Taking the parent node hash
Taking the label hash (already hashed)
Computing keccak256(node || label) using abi.encodePacked
The label parameter is already the keccak256 hash of the label string, not the string itself. To create pay.alice.eth, you would pass:
node: namehash of alice.eth
label: keccak256(“pay”)
Working with Labels vs Nodes
Labels
A label is:
A single component of a domain name (e.g., “alice” in “alice.eth”)
Stored as keccak256(label_string) when passed to contracts
Used in setSubnodeOwner to create subdomains
// To create subdomain 'pay.alice.eth':
bytes32 labelHash = keccak256 ( abi . encodePacked ( "pay" ));
bytes32 aliceEthNode = namehash ( "alice.eth" );
registry. setSubnodeOwner (aliceEthNode, labelHash, owner);
Nodes
A node is:
The complete namehash of a full domain name
Used for all registry queries and operations
A 32-byte hash representing the entire name hierarchy
// Query operations use the full node hash
bytes32 node = namehash ( "alice.eth" );
address owner = registry. owner (node);
address resolver = registry. resolver (node);
Normalization
Before computing namehash, names should be normalized according to UTS-46 :
Unicode allows multiple ways to represent the same character. Normalization ensures:
Alice.eth and alice.eth produce the same hash
Confusable characters (lookalikes) are handled consistently
Names are in a canonical form
Convert to lowercase
Apply Unicode normalization (NFC)
Disallow certain characters (emoji, control characters)
Map lookalike characters to canonical forms
const { normalize } = require ( '@ensdomains/eth-ens-namehash' );
normalize ( 'Alice.eth' ) // → 'alice.eth'
normalize ( 'ALICE.eth' ) // → 'alice.eth'
normalize ( 'alice.eth' ) // → 'alice.eth'
Always normalize names before computing namehash. Most ENS libraries do this automatically, but if you’re implementing namehash yourself, normalization is critical.
Complete Implementation Example
Here’s a complete JavaScript implementation:
const { keccak256 , toUtf8Bytes , concat } = require ( 'ethers' );
const { normalize } = require ( '@ensdomains/eth-ens-namehash' );
function namehash ( name ) {
// Normalize the name first
const normalizedName = normalize ( name );
// Start with the root hash (all zeros)
let node = '0x0000000000000000000000000000000000000000000000000000000000000000' ;
// If empty name, return root
if ( normalizedName === '' ) {
return node ;
}
// Split name into labels and process from right to left
const labels = normalizedName . split ( '.' );
for ( let i = labels . length - 1 ; i >= 0 ; i -- ) {
const labelHash = keccak256 ( toUtf8Bytes ( labels [ i ]));
node = keccak256 ( concat ([ node , labelHash ]));
}
return node ;
}
// Usage
console . log ( namehash ( '' )) // 0x0000...0000
console . log ( namehash ( 'eth' )) // 0x93cdeb708b75...
console . log ( namehash ( 'alice.eth' )) // 0x787192fc5378...
Using ENS Libraries
In practice, you should use established ENS libraries rather than implementing namehash yourself:
ethers.js (v6)
@ensdomains/eth-ens-namehash
web3.py
import { namehash } from 'ethers' ;
const hash = namehash ( 'alice.eth' );
// 0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec
Testing Namehash
You can verify your namehash implementation against known values:
Name Namehash “ (empty) 0x0000000000000000000000000000000000000000000000000000000000000000eth0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4aefoo.eth0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84falice.eth0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec
These test vectors are useful for verifying your namehash implementation is correct.
Common Pitfalls
Forgetting to normalize : Always normalize names before hashing. Alice.eth and alice.eth should produce the same hash.
Wrong direction : Process labels from right to left (TLD first), not left to right.
String vs Hash confusion : Registry functions expect node hashes (bytes32), not strings. Always namehash first.
Label hashing : When calling setSubnodeOwner, the label parameter should be keccak256(labelString), not the raw string.
Privacy Implications
Namehash has interesting privacy properties:
// Given only the hash of 'pay.alice.eth', you cannot determine:
// 1. The parent domain ('alice.eth')
// 2. The subdomain label ('pay')
// 3. The full name
const hash = '0x...' // namehash of 'pay.alice.eth'
// There's no way to reverse this to get the original name
Namehash is one-way. You cannot derive the original name from the hash. This provides some privacy for subdomain structures, though names can often be discovered through events or off-chain data.
Next Steps
Architecture Overview Understand how namehash fits into the overall ENS architecture
Registry Contracts See how to use namehash values with the ENS registry