Skip to main content
The DNSRegistrar contract allows owners of DNS domains to claim the corresponding ENS names by providing DNSSEC proofs of ownership.

Contract Overview

Location: contracts/dnsregistrar/DNSRegistrar.sol Inheritance: IDNSRegistrar, IERC165 The DNSRegistrar bridges traditional DNS with ENS by:
  • Accepting DNSSEC proofs of DNS ownership
  • Validating proofs through the DNSSEC oracle
  • Creating ENS records for proven DNS domains
  • Managing the public suffix list

State Variables

Immutable References

ENS public immutable ens;
DNSSEC public immutable oracle;
address public immutable previousRegistrar;
address public immutable resolver;
  • ens: The ENS registry contract
  • oracle: The DNSSEC oracle for proof validation
  • previousRegistrar: Previous registrar for migration support
  • resolver: Default resolver for claimed domains

Public Suffix List

PublicSuffixList public suffixes;
Defines which top-level domains can be claimed. Can be updated by the ENS root owner.

Inception Tracking

mapping(bytes32 => uint32) public inceptions;
Tracks the most recent signature inception time for each claimed domain to prevent stale proof replay attacks.

Constructor

constructor(
    address _previousRegistrar,
    address _resolver,
    DNSSEC _dnssec,
    PublicSuffixList _suffixes,
    ENS _ens
);
Parameters:
  • _previousRegistrar: Address of the previous registrar for migration
  • _resolver: Default resolver address for new domains
  • _dnssec: DNSSEC oracle contract
  • _suffixes: Public suffix list contract
  • _ens: ENS registry contract

Core Functions

proveAndClaim

Submits DNSSEC proofs and claims a DNS name in ENS.
function proveAndClaim(
    bytes memory name,
    DNSSEC.RRSetWithSignature[] memory input
) public override;
Parameters:
  • name: The DNS name to claim, in DNS wire format
  • input: Chain of signed DNS RRSETs ending with a TXT record proving ownership
Process:
  1. Verifies the DNSSEC proof through the oracle
  2. Validates the parent domain is an enabled public suffix
  3. Checks for a TXT record at _ens.<name> containing the owner address
  4. Sets the ENS owner to the address from the TXT record
Example TXT Record:
_ens.example.com. IN TXT "a=0x1234567890123456789012345678901234567890"

proveAndClaimWithResolver

Claims a DNS name and sets a custom resolver and address in one transaction.
function proveAndClaimWithResolver(
    bytes memory name,
    DNSSEC.RRSetWithSignature[] memory input,
    address resolver,
    address addr
) public override;
Parameters:
  • name: The DNS name to claim
  • input: Chain of signed DNS RRSETs
  • resolver: Custom resolver address to set
  • addr: Address to set in the resolver (optional, use address(0) to skip)
Requirements:
  • msg.sender must match the owner specified in the DNS TXT record
  • If addr is provided, resolver must not be address(0)
Usage:
// Claim with custom resolver and address
registrar.proveAndClaimWithResolver(
    dnsName,
    proofs,
    customResolverAddress,
    myEthereumAddress
);

enableNode

Enables a public suffix node in ENS, making it available for claiming.
function enableNode(bytes memory domain) public returns (bytes32 node);
Parameters:
  • domain: The domain to enable, in DNS wire format
Requirements:
  • Domain must be in the public suffix list
Process:
  1. Validates domain is a recognized public suffix
  2. Recursively creates parent nodes if needed
  3. Sets the registrar as owner of intermediate nodes
  4. Sets the default resolver for the node
Returns: The ENS node hash for the enabled domain

Administrative Functions

setPublicSuffixList

Updates the public suffix list contract.
function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner;
Parameters:
  • _suffixes: New PublicSuffixList contract address
Access: Only callable by the ENS root owner Emits: NewPublicSuffixList(address suffixes)

Internal Functions

_claim

Internal function that handles the core claiming logic.
function _claim(
    bytes memory name,
    DNSSEC.RRSetWithSignature[] memory input
) internal returns (bytes32 parentNode, bytes32 labelHash, address addr);
Process:
  1. Verifies the DNSSEC proof through the oracle
  2. Extracts the first label and parent name
  3. Enables the parent node if needed
  4. Validates proof is not stale using inception time
  5. Extracts owner address from TXT record
  6. Emits Claim event
Returns:
  • parentNode: ENS node of the parent domain
  • labelHash: Keccak256 hash of the claimed label
  • addr: Owner address from the TXT record

_enableNode

Recursively enables a domain and its parents in ENS.
function _enableNode(
    bytes memory domain,
    uint256 offset
) internal returns (bytes32 node);
Parameters:
  • domain: Domain in DNS wire format
  • offset: Current offset in the domain name
Process:
  • Recursively processes from right to left (TLD to subdomain)
  • Creates ENS records for unclaimed nodes
  • Sets registrar as owner of intermediate nodes
  • Takes over nodes owned by the previous registrar

Events

Claim

Emitted when a DNS name is successfully claimed.
event Claim(
    bytes32 indexed node,
    address indexed owner,
    bytes dnsname,
    uint32 inception
);
Parameters:
  • node: ENS node hash of the claimed name
  • owner: Ethereum address that now owns the name
  • dnsname: DNS name in wire format
  • inception: Signature inception time from the DNSSEC proof

NewPublicSuffixList

Emitted when the public suffix list is updated.
event NewPublicSuffixList(address suffixes);
Parameters:
  • suffixes: Address of the new PublicSuffixList contract

Errors

NoOwnerRecordFound

error NoOwnerRecordFound();
Thrown when no valid TXT record with owner address is found.

PermissionDenied

error PermissionDenied(address caller, address owner);
Thrown when msg.sender doesn’t match the required owner.

PreconditionNotMet

error PreconditionNotMet();
Thrown when a required condition is not satisfied (e.g., trying to set address without resolver).

StaleProof

error StaleProof();
Thrown when the provided DNSSEC proof has an inception time older than the stored inception for that domain.

InvalidPublicSuffix

error InvalidPublicSuffix(bytes name);
Thrown when attempting to claim a domain that’s not under a recognized public suffix.

DNS Wire Format

DNS names must be provided in DNS wire format, which encodes labels with length prefixes: Example: example.com becomes:
\x07example\x03com\x00
  • \x07 = length of “example” (7 bytes)
  • example = the label
  • \x03 = length of “com” (3 bytes)
  • com = the label
  • \x00 = root label (empty)

TXT Record Format

The owner TXT record must be placed at _ens.<yourdomain> and contain:
a=0x<ethereum_address>
Full Example:
_ens.example.com. 3600 IN TXT "a=0x1234567890123456789012345678901234567890"
The DNSClaimChecker library extracts the address from this format:
// From DNSClaimChecker.sol
function getOwnerAddress(
    bytes memory name,
    bytes memory data
) internal pure returns (address, bool) {
    // Prepends "_ens." to the name and searches for matching TXT record
    // Parses "a=0x..." format to extract address
}

Public Suffix List

The PublicSuffixList interface:
interface PublicSuffixList {
    function isPublicSuffix(bytes calldata name) external view returns (bool);
}
This ensures only appropriate domains can be claimed. For example:
  • com is a public suffix (can be used as parent)
  • example.com is NOT a public suffix (cannot be used as parent for other claims)

Integration Example

// 1. Set up contracts
DNSRegistrar registrar = DNSRegistrar(registrarAddress);

// 2. Prepare DNS name in wire format
bytes memory dnsName = hex"076578616d706c6503636f6d00"; // example.com

// 3. Collect DNSSEC proofs (typically done off-chain)
DNSSEC.RRSetWithSignature[] memory proofs = getProofsFromDNS();

// 4. Claim the domain
registrar.proveAndClaim(dnsName, proofs);

// Or claim with custom resolver
registrar.proveAndClaimWithResolver(
    dnsName,
    proofs,
    myResolverAddress,
    myEthereumAddress
);

Security Considerations

Proof Validation

All DNSSEC proofs are validated through the oracle before accepting claims. This ensures:
  • Signatures are cryptographically valid
  • Chain of trust extends to root anchors
  • Signatures are temporally valid

Stale Proof Prevention

The inception tracking prevents replay of old DNSSEC signatures:
if (!RRUtils.serialNumberGte(inception, inceptions[node])) {
    revert StaleProof();
}
inceptions[node] = inception;

Ownership Changes

To change ownership of a DNS-claimed ENS name:
  1. Update the DNS TXT record with the new address
  2. Submit a new DNSSEC proof with proveAndClaim
The inception time must be greater than or equal to the previous claim.

Build docs developers (and LLMs) love