ENS Logo
Docs

Name Processing

When interacting with the ENS protocol smart-contracts directly it is important to note that names are not stored in their human readable format. In fact there are a few steps a name undergoes before it can be used by a smart-contract.

When building a dApp most of the time you don't have to worry about name processing, as most libraries will handle this for you.

Name Normalization

Normalization is the process of canonicalizing a name before running it through the Namehash algorithm. It is important to always normalize all input, because even one little difference (like a capital vs lowercase character) will cause the namehash to be completely different.

For example, NaMe.EtH normalizes to name.eth. This ensures that the correct Registry node is used, no matter how the user types in the name.

ENS names are validated and normalized using the ENSIP-15 normalization algorithm.

Previously, UTS-46 was used, but that is insufficient for emoji sequences. Correct emoji processing is only possible with UTS-51. The ENSIP-15 normalization algorithm draws from those older Unicode standards, but also adds many other validation rules to prevent common spoofing techniques like inserting zero-width characters, or using confusable (look-alike) characters. See here for additional discussion on this: Homogylphs

A standard implementation of the algorithm is available here: https://github.com/adraffy/ens-normalize.js. This library is also included in ENSjs.

To normalize a name, simply call ens_normalize:

import {ens_normalize} from '@adraffy/ens-normalize'; // or require()
// npm i @adraffy/ens-normalize
// browser: https://cdn.jsdelivr.net/npm/@adraffy/ens-normalize@latest/dist/index.min.mjs (or .cjs)

// *** ALL errors thrown by this library are safe to print ***
// - characters are shown as {HEX} if should_escape()
// - potentially different bidi directions inside "quotes"
// - 200E is used near "quotes" to prevent spillover
// - an "error type" can be extracted by slicing up to the first (:)
// - labels are middle-truncated with ellipsis (…) at 63 cps

// string -> string
// throws on invalid names
// output ready for namehash
let normalized = ens_normalize('RaFFY🚴‍♂️.eTh');
// => "raffy🚴‍♂.eth"

// note: does not enforce .eth registrar 3-character minimum

If the name was not able to be normalized, then that method will throw a descriptive error. A name is valid if it is able to be normalized.

Namehash

In order for us to interface with our nice readable names there needs to be a way we communicate them to smart-contracts. ENS stores names in a uint256 encoded format we call a "namehash". This is done to optimize for gas, performance, and more.

Code Examples

// https://github.com/ensdomains/ensjs-v3

import { namehash } from '@ensdomains/ensjs/utils';

const node = namehash('name.eth');
// https://github.com/ConsenSysMesh/ens-namehash-py

from namehash import namehash

node = namehash('name.eth')
// https://github.com/InstateDev/namehash-rust

fn main() {
  let node = &namehash("name.eth");
  let s = hex::encode(&node);
}

ENSjs https://github.com/ConsenSysMesh/ens-namehash-py https://github.com/InstateDev/namehash-rust

Algorithm

The specification for the namehash algorithm is here: https://eips.ethereum.org/EIPS/eip-137#namehash-algorithm

It's a recursive algorithm that works its way down until you hit the root domain. For ens.eth, the algorithm works like so:

namehash('ens.eth') = keccak256(namehash('eth') + labelhash('ens'))
namehash('eth') = keccak256(namehash('') + labelhash('eth'))
namehash('') = 0x0000000000000000000000000000000000000000000000000000000000000000

That last line is a special case: The namehash for an empty string (representing the root domain) is 32 null bytes.

If you plug everything in above, you'll end up with the final namehash value:

  • namehash('') =
    • 0x0000000000000000000000000000000000000000000000000000000000000000
  • labelhash('eth') =
    • keccak256('eth') =
    • 0x4f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0
  • namehash('eth') =
    • keccak256(namehash('') + labelhash('eth')) =
    • keccak256(0x00000000000000000000000000000000000000000000000000000000000000004f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0) =
    • 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae
  • labelhash('ens') =
    • keccak256('ens') =
    • 0x5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da
  • namehash('ens.eth') =
    • keccak256(namehash('eth') + labelhash('ens')) =
    • keccak256(0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da) =
    • 0x4e34d3a81dc3a20f71bbdf2160492ddaa17ee7e5523757d47153379c13cb46df

Reverse Nodes

The Reverse Node is a node in the Registry that can be claimed for any Ethereum account. The name this node represents is [addr].addr.reverse, where [addr] is the Ethereum public address (lowercase, without the "0x"). These reverse nodes are typically used to set a Primary Name for an account.

To generate the namehash for a reverse node:

  • Take the input address and:
    • Remove the "0x" at the beginning
    • Convert all characters to lowercase
  • Add .addr.reverse to the end
  • Run this result through the namehash algorithm

For example, for address 0x481f50a5BdcCC0bc4322C4dca04301433dED50f0, the name for the reverse node is:

  • 481f50a5bdccc0bc4322c4dca04301433ded50f0.addr.reverse

And the resulting namehash for the reverse node is:

  • 0x58354ffdde6ac279f3a058aafbeeb14059bcb323a248fb338ee41f95fa544c86

Labelhash

The labelhash is just the Keccak-256 output for a particular label.

Labelhashes are used to construct namehashes, and often times a labelhash (rather than the raw label) will be the required input for various contract methods.

// https://www.npmjs.com/package/js-sha3
const labelhash = '0x' + require('js-sha3').keccak_256('name')

DNS Encoding

This is a binary format for domain names, which encodes the length of each label along with the label itself. It is used by some of the ENS contracts, such as when wrapping a subname or DNS name using the Name Wrapper.

To DNS-encode a name, first split the name into labels (delimited by .). Then for each label from left-to-right:

  • One byte to denote the length of the label
  • The UTF-8 encoded bytes for the label
  • If this is the last label, then one final NUL (0x00) byte.

For example, to DNS-encode my.name.eth:

  • 0x02 (length of the label "my")
  • 0x6D79 (UTF-8 encoded bytes of "my")
  • 0x04 (length of the label "name")
  • 0x6E616D65 (UTF-8 encoded bytes of "name")
  • 0x03 (length of the label "eth")
  • 0x657468 (UTF-8 encoded bytes of "eth")
  • 0x00 (end of name marker)

Final result: 0x026d79046e616d650365746800

// https://npmjs.com/package/dns-packet
const dnsEncodedBytes = require('dns-packet').name.encode('name.eth');
const dnsEncodedHexStr = '0x' + require('dns-packet').name.encode('name.eth').toString('hex');
// => 0x046e616d650365746800
Last Modified
3 days ago