- nick.eth
ENSIP-17: Gasless DNS Resolution
Abstract
This standard describes a mechanism by which users can specify ENS resolvers and resolution data as DNS TXT records, resulting in a system where DNS names are resolvable in ENS with no onchain actions required.
Motivation
ENS has had DNS integration for some time, facilitated by the ability to prove the contents of a DNS TXT record onchain, and thereby claim the corresponding name in the ENS registry. This has several shortcomings, the most notable of which is the high transaction fees associated, as verifying a DNSSEC proof often costs in excess of 1,000,000 gas.
Wildcard resolution (ENSIP-10) and CCIP-Read (EIP-3668) now permit a new approach, by which DNSSEC proofs are fetched and verified at resolution time instead. This permits users to enable ENS resolution of their names simply by setting a TXT record on their DNS name and enabling DNSSEC.
Specification
A new resolver, OffchainDNSResolver
, is deployed and set as the resolver for all DNS TLDs; this resolver will be consulted whenever resolution is attempted for a nonexistent subdomain of these TLDs. The resolver then initiates a CCIP-Read request to a gateway which fetches and returns DNSSEC proofs for TXT records on that name. In the CCIP-Read callback function, the resolver verifies the DNSSEC proofs using an updated version of the DNSSEC oracle, and if they verify correctly, decodes the address of a resolver and optional extra data from the TXT record. The resolution request is then completed by invoking the resolver, optionally passing the extra data contained in the TXT record to enable it to vary its response dynamically.
TXT record format
Compliant TXT records MUST adhere to this format:
ENS1 <resolver-name-or-address> [context]
Where:
ENS1
is a fixed string, identifying this TXT record as a gasless ENS record.resolver-name-or-address
is either the 0x-prefixed hexadecimal address or the ENS name of the resolver to use to resolve queries for this name. If an ENS name is supplied, the name MUST be resolvable without using ENSIP-10 - meaning that the name MUST have a resolver set in the ENS registry, and the resolver MUST implement theaddr
function, which MUST NOT initiate a CCIP-Read request usingOffchainData
.context
is arbitrary additional data that may be passed to the resolver to complete the request (see below).
Resolution Process
Summary
The OffchainDNSResolver
decodes or resolves the address of the resolver to use from resolver-name-or-address
, and calls whichever of IExtendedDNSResolver.resolve
, IExtendedResolver.resolve
or a legacy resolution function is first supported. If either resolve
function is invoked, nested CCIP-Read requests are handled correctly.
Detailed Description
OffchainDNSResolver
implements resolve
by initiating a CCIP-Read request, reverting with OffchainLookup
and specifying the address of a CCIP-Read gateway capable of fetching and returning DNSSEC signed DNS records. The callback function for the CCIP-Read request implements the following logic:
- Use the DNSSEC oracle to verify the returned RRSet. If verification fails, revert.
- For each record in the returned RRSet:
- Check if the RR name matches the name being resolved and the record type is TXT. If either check fails, continue to the next record.
- Check if the first field in the TXT record starts with
ENS1
. If not, continue to the next record. - Decode
resolver-name-or-address
andcontext
from the text record. - If
resolver-name-or-address
contains a.
character:- Compute the namehash of
resolver-name-or-address
. - Call
resolver(bytes32)
on the registry, passing in the namehash. If the registry returns0
, continue to the next TXT record. - Call
addr(bytes32)
on the resolver address returned in step ii. If the resolver returns0
, continue to the next TXT record. - Treat the returned address as the resolver address to use in subsequent steps.
- Compute the namehash of
- If
resolver-name-or-address
does not contain a '.' character, treat it as a hexadecimal address and decode it. Treat the decoded address as the resolver address to use in subsequent steps. - Using ERC165, check if the returned resolver supports
IExtendedDNSResolver
. If it does, callresolve(bytes name, bytes query, bytes context)
, passingcontext
from the TXT record as the last argument. The call toresolve
may use CCIP-Read; if it does, reverts will be handled appropriately. Return the result of this call as the result of the initial resolution request and halt. - Using ERC165, check if the returned resolver supporst
IExtendedResolver
. If it does, callresolve(bytes name, bytes query)
. The call toresolve
may use CCIP-Read; if it does, reverts will be handled appropriately. Return the result of this call as the result of the initial resolution request and halt. - Otherwise, call the resolver with calldata equal to the
query
originally supplied toOffchainDNSResolver.resolve
and return its result as the result of the initial resolution request and halt. This resolution path does NOT support nested CCIP-Read requests.
The IExtendedDNSResolver
interface is defined as follows:
interface IExtendedDNSResolver {
function resolve(
bytes memory name,
bytes memory data,
bytes memory context
) external view returns (bytes memory);
This acts as an extension of IExtendedResolver
defined in ENSIP-10, providing the additional context
argument, containing any supplementary data from the TXT record.
Note that a TXT record may contain multiple text fields; in this implementation only the first field of each TXT record is considered. TXT record fields are limited to a maximum of 255 bytes each.
DNSSEC Gateway API
The DNSSEC Gateway implements the following API over CCIP-Read:
struct RRSetWithSignature {
bytes rrset;
bytes sig;
}
interface IDNSGateway {
function resolve(
bytes memory name,
uint16 qtype
) external returns (RRSetWithSignature[] memory);
}
Where:
name
is a DNS-encoded name to query.qtype
is a DNS QTYPE as defined in RFC1034 - for example,TXT
= 16.
The returned rrset
s are in the format described in section 5.3.2 of RFC4035: The RRDATA section from the RRSIG without the signature data, followed by a series of canonicalised RR records that the signature applies to. One RRSET is returned for each step in the chain of trust, starting with the DNSKEY RRSET for the DNS root .
, and ending with the requested RRSET, if it exists.
A Solidity DNSSEC implementation for decoding and verifying RRSET data is available at https://github.com/ensdomains/ens-contracts/blob/staging/contracts/dnssec-oracle/.
Backwards Compatibility
DNS names imported using the existing functionality will continue to function as before, and new names can still be imported using the DNS import functionality. If an imported name exists, it is used to resolve the request, and any TXT records for gasless DNSSEC are ignored. If desired, the owner of the name can set the resolver to address(0)
to cause resolution to fall back to gasless DNSSEC.
Security Considerations
Gasless DNSSEC relies on a gateway and CCIP-Read to fetch cryptographic proofs of the chain of trust between the DNS root and the desired text record. It uses the same code, and hence has the same trust model, as the DNS import functionality; forging a DNS record would require access to a signing key somewhere in the chain of trust between DNS root and the record in question.
Due to the use of a gateway service to generate responses, there is additional risk of unavailability: the gateway could be out of operation, or could choose to selectively refuse to answer (censor) certain queries.
Copyright
Copyright and related rights waived via CC0.