ENS Logo

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 the addr function, which MUST NOT initiate a CCIP-Read request using OffchainData.
  • 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:

  1. Use the DNSSEC oracle to verify the returned RRSet. If verification fails, revert.
  2. For each record in the returned RRSet:
    1. 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.
    2. Check if the first field in the TXT record starts with ENS1 . If not, continue to the next record.
    3. Decode resolver-name-or-address and context from the text record.
    4. If resolver-name-or-address contains a . character:
      1. Compute the namehash of resolver-name-or-address.
      2. Call resolver(bytes32) on the registry, passing in the namehash. If the registry returns 0, continue to the next TXT record.
      3. Call addr(bytes32) on the resolver address returned in step ii. If the resolver returns 0, continue to the next TXT record.
      4. Treat the returned address as the resolver address to use in subsequent steps.
    5. 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.
    6. Using ERC165, check if the returned resolver supports IExtendedDNSResolver. If it does, call resolve(bytes name, bytes query, bytes context), passing context from the TXT record as the last argument. The call to resolve 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.
    7. Using ERC165, check if the returned resolver supporst IExtendedResolver. If it does, call resolve(bytes name, bytes query). The call to resolve 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.
    8. Otherwise, call the resolver with calldata equal to the query originally supplied to OffchainDNSResolver.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 rrsets 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 and related rights waived via CC0.

Status
✍️ Draft
Created
2/9/2024
Contributors
Last Modified
8 months ago