diff --git a/app/local/config/navigation/protocol.ts b/app/local/config/navigation/protocol.ts index 16e5fb097..0716323a2 100644 --- a/app/local/config/navigation/protocol.ts +++ b/app/local/config/navigation/protocol.ts @@ -690,6 +690,10 @@ export const navigation: SectionData[] = [ title: '16 - Offchain Metadata', href: '/ensip/16', }, + { + title: '17 - Gasless DNS Resolution', + href: '/ensip/17', + }, { title: '18 - Profile Text Records', href: '/ensip/18', diff --git a/docs/ensip/17.mdx b/docs/ensip/17.mdx new file mode 100644 index 000000000..b7a1961d6 --- /dev/null +++ b/docs/ensip/17.mdx @@ -0,0 +1,120 @@ +{/** @type {import('@/lib/mdxPageProps').MdxMetaProps} */} +export const meta = { + description: '', + contributors: [ + 'arachnid', + ], + ensip: { + status: 'draft', + created: '2024-02-09', + } +}; + +# 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: + +```ts +ENS1 [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: + +```go +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: + +```go +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](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/docs/ensip/index.mdx b/docs/ensip/index.mdx index 8c70ed109..34ca305db 100644 --- a/docs/ensip/index.mdx +++ b/docs/ensip/index.mdx @@ -34,6 +34,7 @@ Improvement Proposals have included anything from new contract features, to text | [14 - On-chain Source Parameter](/ensip/14) | ✍️ Draft | | [15 - Normalization Standard](/ensip/15) | ✍️ Draft | | [16 - Offchain Metadata](/ensip/16) | ✍️ Draft | +| [17 - Gasless DNS Resolution](/ensip/17) | ✍️ Draft | | [18 - Profile Text Records](/ensip/18) | ✍️ Draft | {/* ## Propose an ENSIP