ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.82k stars 5.24k forks source link

ENS support for reverse resolution of Ethereum addresses #181

Closed Arachnid closed 6 years ago

Arachnid commented 7 years ago
  EIP: draft
  Title: ENS support for reverse resolution of Ethereum addresses
  Author: Nick Johnson 
  Status: Draft
  Type: Informational
  Created: 2016-12-01

Abstract

This EIP specifies a TLD, registrar, and resolver interface for reverse resolution of Ethereum addresses using ENS. This permits associating a human-readable name with any Ethereum blockchain address. Resolvers can be certain that the reverse record was published by the owner of the Ethereum address in question.

Motivation

While name services are mostly used for forward resolution - going from human-readable identifiers to machine-readable ones - there are many use-cases in which reverse resolution is useful as well:

Specification

Reverse ENS records are stored in the ENS hierarchy in the same fashion as regular records, under a reserved domain, addr.reverse. To generate the ENS name for a given account's reverse records, convert the account to hexadecimal representation in lower-case, and append addr.reverse. For instance, the ENS registry's address at 0x112234455c3a32fd11230c42e7bccd4a84e02010 has any reverse records stored at 112234455c3a32fd11230c42e7bccd4a84e02010.addr.reverse.

Note that this means that contracts wanting to do dynamic reverse resolution of addresses will need to perform hex encoding in the contract.

Registrar

The owner of the addr.reverse domain will be a registrar that permits the caller to take ownership of the reverse record for their own address. It provides the following method:

function claim(address owner) returns (bytes32 node);

When called by account x it will instruct the ENS registry to transfer ownership of the name hex(x) + '.addr.reverse' to the provided address, and return the namehash of the ENS record thus transferred.

Allowing the caller to specify an owner other than themselves for the relevant node facilitates contracts that need accurate reverse ENS entries delegating this to their creators with a minimum of code inside their constructor:

reverseRegistrar.claim(msg.sender)

Resolver interface

A new resolver interface is defined, consisting of the following method:

function name(bytes32 node) constant returns (string);

Resolvers that implement this interface must return a valid ENS name for the requested node, or the empty string if no name is defined for the requested node.

The interface ID of this interface is 0x691f3431.

Future EIPs may specify more record types appropriate to reverse ENS records.

Appendix 1: Registrar implementation

This registrar, written in Solidity, implements the specifications outlined above.

pragma solidity ^0.4.0;

import 'interface.sol';

contract ReverseRegistrar {
    AbstractENS public ens;
    bytes32 public rootNode;

    /**
     * @dev Constructor
     * @param ensAddr The address of the ENS registry.
     * @param node The node hash that this registrar governs.
     */
    function ReverseRegistrar(address ensAddr, bytes32 node) {
        ens = AbstractENS(ensAddr);
        rootNode = node;
    }

    /**
     * @dev Transfers ownership of the reverse ENS record associated with the
     *      calling account.
     * @param owner The address to set as the owner of the reverse record in ENS.
     * @return The ENS node hash of the reverse record.
     */
    function claim(address owner) returns (bytes32 node) {
        var label = sha3HexAddress(msg.sender);
        ens.setSubnodeOwner(rootNode, label, owner);
        return sha3(rootNode, label);
    }

    /**
     * @dev An optimised function to compute the sha3 of the lower-case
     *      hexadecimal representation of an Ethereum address.
     * @param addr The address to hash
     * @return The SHA3 hash of the lower-case hexadecimal encoding of the
     *         input address.
     */
    function sha3HexAddress(address addr) constant returns (bytes32 ret) {
        assembly {
            let lookup := 0x3031323334353637383961626364656600000000000000000000000000000000
            let i := 40
        loop:
            i := sub(i, 1)
            mstore8(i, byte(and(addr, 0xf), lookup))
            addr := div(addr, 0x10)
            i := sub(i, 1)
            mstore8(i, byte(and(addr, 0xf), lookup))
            addr := div(addr, 0x10)
            jumpi(loop, i)
            ret := sha3(0, 40)
        }
    }
}
maurelian commented 7 years ago
  1. How was the eth-addr.reverse domain chosen? Is the intent that future TLD's (say .tld) could also have an associated tld-addr.reverse registrar/resolver?
  2. Is there a precedent for this system on DNS?
  3. "To generate the ENS name for a given account's reverse records, convert the account to hexadecimal representation in lower-case, and append eth-addr.reverse". Are there any alternatives to this name generation approach?
Arachnid commented 7 years ago

How was the eth-addr.reverse domain chosen?

It seemed like a good idea at the time.

Is the intent that future TLD's (say .tld) could also have an associated tld-addr.reverse registrar/resolver?

No, and I agree this is a bit confusing. The 'eth' in 'eth-addr' refers to the type of address it's a reverse registry for (eg, Ethereum addresses). If we provided reverse lookups for, say, swarm addresses, that would be on 'swarm-addr.reverse', even though there wouldn't be a .swarm TLD. So, there could be a 2ld under .reverse for each type of address ENS resolves to, rather than for each TLD in a forward address.

Is there a precedent for this system on DNS?

Yes, this is more or less how IPv4 addresses (with in-addr.arpa) and IPv6 addresses (with ip6.arpa) are reverse-resolved, only with more hierarchal separation of the IP.

Are there any alternatives to this name generation approach?

Yes, instead of hex encoding the address, we could specify that the reverse record for an address is held at sha3(namehash('eth-addr.reverse'), sha3(address)) - eg, the same approach but without hex encoding first. This would have the advantage of never needing to do onchain hex encoding, but the significant disadvantage that you can't represent the name a reverse record is hosted under as text, any longer.

jpritikin commented 7 years ago

Do you really foresee the need to do reverse lookups on-chain? The use cases mentioned could probably be satisfied with an off-chain database (perhaps stored in swarm) that is regularly updated to match the forward mapping. An on-chain reverse database doubles the storage requirement for ENS.

Arachnid commented 7 years ago

Do you really foresee the need to do reverse lookups on-chain?

No; that's why I didn't think the cost of having to hex encode the data onchain was a compelling one.

The use cases mentioned could probably be satisfied with an off-chain database (perhaps stored in swarm) that is regularly updated to match the forward mapping.

Couldn't the same argument be made about forward lookups? Doing either offchain introduces significant challenges in determining how the database is located, and how it's verified to be correct, however.

jpritikin commented 7 years ago

Couldn't the same argument be made about forward lookups?

No because the auction etc needs to be on-chain. It's useful to be able to transfer ownership of domains in a decentralized way. In contrast, the reverse mapping is merely a reorganization of the forward mapping.

significant challenges in determining how the database is located, and how it's verified to be correct

That's true, but I suggest these challenges are worth confronting to halve the on-chain storage requirement. The reverse database could be located by a well-known contract that stores a swarm hash. How trustworthy does the reverse mapping need to be? If occasional errors could be tolerated then updates could be managed in a decentralized way by using a security deposit.

Arachnid commented 7 years ago

No because the auction etc needs to be on-chain. It's useful to be able to transfer ownership of domains in a decentralized way.

Good point.

That's true, but I suggest these challenges are worth confronting to halve the on-chain storage requirement.

I disagree. I think that this is exactly the sort of small-but-valuable data with clearly defined update rules that blockchains are good at storing.

The reverse database could be located by a well-known contract that stores a swarm hash.

This introduces a dependency on swarm for anyone wanting to do reverse resolution, though. It also turns reverse lookup support from a fairly simple proposition into a significant undertaking all of its own. Reverse resolution is useful now, not if & when we can figure that out.

One really common use-case for reverse lookups is wallets displaying the names associated with contracts and wallet addresses in, eg, transaction histories and address books. If Swarm is required to be able to do these reverse lookups, then a lot of wallet software simply won't support this, because it would impose a significant extra dependency.

KillariDev commented 7 years ago

the reverse lookup is not just simple the same database backward. There can be multiple addresses pointing into different names and other way around.

jpritikin commented 7 years ago

the reverse lookup is not just simple the same database backward. There can be multiple addresses pointing into different names and other way around.

Oh yeah. I forgot about that. Good point.

Smithgift commented 7 years ago

Anyone can set a name to point to an address, but is not the reverse true as well? An address can claim some arbitrary name to be its authoritative address, which allows it to masquerade as another entity. This will doubtlessly confuse users, to say nothing of mindless contracts.

I believe that the reverse lookup system must verify that address A owns name B before allowing the reverse lookup to be claimed. Further, there should be a way for any random user to force address A to stop claiming to be the reverse of name B if B is no longer owned by A, for the same reason.

Arachnid commented 7 years ago

Anyone can set a name to point to an address, but is not the reverse true as well? An address can claim some arbitrary name to be its authoritative address, which allows it to masquerade as another entity.

Yes. Resolvers for which this matters (forward and reverse records make different assertions, and not everyone needs both to be true) should perform both resolutions and check they match.

I believe that the reverse lookup system must verify that address A owns name B before allowing the reverse lookup to be claimed. Further, there should be a way for any random user to force address A to stop claiming to be the reverse of name B if B is no longer owned by A, for the same reason.

This is doable, but it complicates the system a lot: when a registrar hands ownership of a name (in this case a reverse record) over, it doesn't know what resolver is going to be set for it, or what that resolver will claim the corresponding name is. The only way I can see this working is if the registrar retained ownership of the name and deployed its own resolver, which only supported the name() interface. This would really hurt expandibility of the system.

I could well be missing another way to enforce this - so suggestions are welcome.

Smithgift commented 7 years ago

Yes. Resolvers for which this matters (forward and reverse records make different assertions, and not everyone needs both to be true) should perform both resolutions and check they match.

This will be fine if whatever official library for ENS automatically checks this, but I'm concerned that developers will carelessly ignore this. I suppose if the standard is "THOU SHALT CHECK THE ERROR CODE OF THINE CALL LEST THOU FOOLISHLY BELIEVE A name() IS ACCURATE!" then it's not all that different from any other of the potential-but-avoidable landmines in Ethereumland.

I suppose for contracts, one could provide an on-chain system that caches both lookups, and then a contract that cares must use that specific resolver. Unfortunately, that also is a kind of landmine. "Hello, new developer, here's how to use the ENS within solidity! ...now here's how you actually need to do it if you're concerned about the name being accurate."

On the contrary, however, there are probably legitimate uses for giving an "incorrect" name to an address. (i.e. you're truly claiming to be a certain entity, but it isn't convenient for you to actually set up the names just right.)

I could well be missing another way to enforce this - so suggestions are welcome.

I was thinking the registrar could drop a claim if it was proven that the name() was incorrect, but there's nothing stopping someone from just claiming it again.

Suppose the name()-only resolver had, as its fallback DELEGATECALL <actual resolver>. That is, it would always call the official name(), and for any other method it would call whatever the owner's contract said. This would be very complex, unfortunately, and it would be even more complex to avoid the real contract from using its DELEGATECALLed powers to mess with the storage (and therefore, the name.) You'd have to write the true name to the code itself, or call yet another contract to get the real name.

I suppose the situation overall is not that weirder than being able to lie about who a name "really" belongs to. It's just that UX-wise, the average user's been trained to trust names. If the official message gets out that "Hey, google.eth actually really belongs to Google, don't trust g00gle.eth," which is then followed by a contract which is "named" google.eth asking them to send 100 ETH... well...

But again, you can already perform some weird phising scam with a false name-to-address assertion. If the libraries interacting with ENS are smart enough, this will be much less of an issue.

Arachnid commented 7 years ago

Update: I've implemented the registrar with unit-tests in the ENS repo. In response to a suggestion from @alexvandesande, I've renamed eth-addr.reverse to addr.reverse for consistency (the pattern being that the first part of the name matches the name of the function it's a reverse for).

Arachnid commented 7 years ago

I've opened a PR for this: https://github.com/ethereum/EIPs/pull/630