Home Docs Blog Contact

Forward & Reverse Resolution - Initial Proposal

March 23rd, 2022

Forward resolution is the process of taking a name (e.g. coconaut.avax) and a desired type of value (e.g. C-Chain address) and returning the related value. Reverse resolution is the process of taking a value (e.g. C-Chain address) and returning a name (e.g. coconaut.avax). This document will cover our proposed architecture for implementing this functionality.


Forward Resolution

Forward Resolution will make use of two contracts: 

  • The ResolverRegistry, responsible for identifying which contract should be used to resolve a given name. Resolvers can be set for domains at any level (for example, sub.coconaut.avax can have a different resolver than coconaut.avax).
  • A Resolver, which implements the ResolverInterface (described below). Avvy Domains will provide default resolvers, and users can also implement custom resolvers to extend functionality.

A user looking to perform a Forward Resolution would use the following process:

  1. Query the ResolverRegistry to determine the address of the Resolver to be used.
  2. Query the Resolver for the desired value.

A user looking to set up Forward Resolution for a name they control would use the following process:

  1. Set the desired value on the Resolver of their choice.
  2. Set up the ResolverRegistry to use the Resolver of their choice.


Lookup Example

The following diagram demonstrates potential communication for a Forward Resolution.


Finding the Correct Resolver

If a client desires to resolve a record on alpha.beta.charlie.avax, the client cannot know which level of the domain will have an entry in the ResolverRegistry. Clients should iterate up levels of subdomains until they discover an entry in the ResolverRegistry.


Dataset IDs

How should Resolvers index their data? We perform lookups based on names (for example, avvydomains.avax, or subdomain.avvydomains.avax), so naturally one would think the indicies should be those names. One drawback of this method is that we reveal the names themselves. In some cases, it may be advantageous to keep a name hidden.

We propose using unique Dataset IDs, which are disjoint from the names themselves. This provides flexibility for Resolvers in managing ownership of datasets, authentication of changes, etc.


Data Types

When performing a Forward Resolution, a user will present a name (e.g. coconaut.avax) as well as the data type they wish to receive. We will refer to this data type as the key.

We consider two scenarios for use of the application. The first relates to standardized value types which the community will work together to agree upon. Some examples of potential standardized value types include C-Chain Addresses, X-Chain Addresses, DNS records such as CNAME and A records, generic communication information such as email addresses, etc. The second use-case is as an abstract data-storage solution, where users may develop their own use-cases.

We propose the following differentiation between keys:

  • Standard Keys are numbered and have reserved functionality. These Standard Values would be similar to the well-known port range in IP. An example would be to have X-Chain Addresses identified by the Standard Value 1, and C-Chain Addresses to be identified by the Standard Value 2. Each Standard Value may be accompanied by detailed information for clients consuming the data, such as a data schema and an intended use.
  • Custom Keys are identified by a string, and have no dedicated functionality. Users of the application may develop their own standards, or use the application for custom use-cases.

Resolvers would be required to implement two methods relating to resolution: one for resolving Standard Keys, and one for resolving Custom Keys.


Interface for Forward Resolution

We propose two methods for Forward Resolution: 

  • resolveStandard(uint hash, uint key) external returns (string memory data)
  • resolve(uint hash, string memory key) external returns (string memory data)

(where resolveStandard is for Standard Keys, and resolve is for Custom Keys).


Hashing for Forward Resolution

We propose that the hash used for retrieving data from resolvers be a one-way hash of (i) the namehash of the desired subdomain; and (ii) the DatasetID. This one-way hash should not cause hash collisions with other subdomains.

Why is this useful? First, it allows us to keep the hash of sub.avvydomains.avax private, if we want, while still allowing us to perform a lookup. Second, it allows Resolvers to keep Datasets separate (e.g. allowing sub.avvydomains.avax to resolve different values in Dataset 1337 and in Dataset 8888). This is useful for Privacy Considerations and Data Clearing.


Privacy Considerations

What privacy desires might users have? Consider a user who wants the utility of forward resolution, but has a number of wallets that they want to keep relatively private. 

We have a few information targets which we want to keep in mind:

Associating a domain name with a wallet

Associating a domain name with the wallet which owns the domain name can leak information. For example, if my name is johndaviddoe.avax and I hold that name in my wallet, observers may be able to associate my wallet address with my name. We use the concept of Enhanced Privacy, to mitigate this risk.

Associating a wallet with data that is set on the Resolver

Let's say you have two private C-Chain wallets and you do not want to create observable links between these wallets. You register utilityname.avax from wallet #1, to keep track of the names. You wish to have wallet1.utilityname.avax resolve to wallet #1 and wallet2.utilityname.avax resolve to wallet #2. 

We propose the following method for keeping these subdomains private:

  1. You must deploy a custom Resolver. We can perhaps add some nice UI for deploying a custom resolver, but there must be a custom contract deployed. 
  2. In ResolverRegistry, you set utilityname.avax to point to your custom Resolver. You set your Dataset ID to 1337 (this is chosen arbitrarily, but it is important that your custom Resolver responds to the Dataset ID appropriately during lookups).
  3. Your data values get encrypted using private key encryption before being set on the Resolver. You use your subdomain hashes (e.g. the hashes of wallet1.utilityname.avax, wallet2.utilityname.avax) as your encryption key.
  4. Because Resolvers index data using a combination of the subdomain + the Dataset ID, the subdomain remains private information, keeping the encryption key secret.
  5. You (or others) can perform lookups by simply remembering the subdomain, but blockchain observers cannot determine the values, nor the keys, that you have used to set the data. Values get decrypted offline using the same information that was used to look up the data.

This method is brainstormed and may have flaws. If you have comments, please join our Discord or Telegram to share.

This is likely a rare use-case, but we believe it to be important. To achieve this level of privacy, users will sacrifice the ability to enumerate subdomains.

Why must we use a custom resolver? Each subdomain has a unique hash. On shared contracts, we must authenticate the user setting the hash. To authenticate the user, the user sends the hash, as well as the preimage for the subdomain. Using this information, we can calculate the hash of the subdomain, but also authenticate the user as the owner of the domain. This method unfortunately discloses the subdomain, which is also our encryption key in this method. By using a custom resolver, we do not need to authenticate the user. The user simply needs to configure the ResolverRegistry such that resolution requests for their domain are directed to their custom contract.


Data Clearing

Let's consider the following scenario: User A is the current registrant of toast.avax, and sets a record burnt.toast.avax. User B later acquires toast.avax, but isn't aware of burnt.toast.avax, which continues to resolve. There are a number of scenarios where this could be a large problem, and so it becomes important for User B to be able to clear the data on toast.avax after acquiring the name.

Clearing data requires the following steps:

  1. The user must check the ResolverRegistry to see which Resolvers are being used for toast.avax as well as all of it's subdomains
  2. The user must then either remove the ResolverRegistry references to hanging Resolvers, or clear the data in those Resolvers

To accomplish this, we require that the ResolverRegistry offer the ability to enumerate registry entries for toast.avax and all of it's subdomains. A user can then discover which Resolvers are in use, and make the appropriate changes.

We must also consider the scenario where a domain has transferred to a new owner, and that new owner has not yet investigated the entries which are set on the domain. We should allow clients of the system to identify this situation.

To facilitate these requirements, the following must be true:

  • ResolverRegistry should emit an event when the Resolver is set for a given name.
  • ResolverRegistry should allow users to enumerate registry entries for a given name (including all subdomains).
  • ResolverRegistry should, if possible, provide keep a note of whether the Resolvers for a domain have changed after the domain has changed owners.
  • ResolverRegistry should ideally make it easy to clear all entries for a given name.


Reverse Resolution

In this process, we wish to map a value to a name. When setting up reverse resolution, we must show that the owner of the name is also the owner of the value (since the value can only map to a single name). We must authenticate ownership, otherwise we can have malicious or unintentional errors. 

The process of authenticating ownership will differ dependent on record type. In the case of reversing a c-chain address to a name, solidity will take care of the authentication for us (we can simply check tx.origin or msg.sender). With other record types, the process becomes more difficult (Validator NodeIDs, X-Chain addresses, etc).

Many record types will require custom contracts for reverse resolution. We hope to abstract the process to achieve general goals, primarily, the goal of being able to reset data when names are transferred between owners.

We propose using the ResolverRegistry to identify the ReverseResolvers for given record types. Each Standard Key can have a ReverseResolver. For Custom Keys, developers seeking custom functionality can launch their own ReverseResolvers, however they would not be able to add them to the ResolverRegistry.


Data Clearing

Clearing data is simpler in the case of Reverse Resolution. We are able to enumerate the set of Standard Keys, to discover the set of ReverseResolvers. Each ReverseResolver should offer either a clearData(name) method which attempts to scrub all records relating to a name, or a method of enumerating records for a given name (so they can be modified or removed).