Specifies a TLD, registrar, and resolver interface for reverse resolution of Ethereum addresses using Domains.
Abstract
This DomainsP specifies a TLD, registrar, and resolver interface for reverse resolution of Ethereum addresses using Domains. 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:
Applications that allow users to monitor accounts benefit from showing the name of an account instead of its address, even if it was originally added by address.
Attaching metadata such as descriptive information to an address allows retrieving this information regardless of how the address was originally discovered.
Anyone can configure a name to resolve to an address, regardless of ownership of that address. Reverse records allow the owner of an address to claim a name as authoritative for that address.
Specification
Reverse Domains records are stored in the Domains hierarchy in the same fashion as regular records, under a reserved domain, addr.reverse. To generate the Domains name for a given account's reverse records, convert the account to hexadecimal representation in lower-case, and append addr.reverse.
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 methods:
function claim(address owner) returns (bytes32 node)
When called by account x , instructs the Domains registry to transfer ownership of the name hex(x) + '.addr.reverse' to the provided address, and return the namehash of the Domains record thus transferred.
Allowing the caller to specify an owner other than themselves for the relevant node facilitates contracts that need accurate reverse Domains entries delegating this to their creators with a minimum of code inside their constructor:
reverseRegistrar.claim(msg.sender)
function claimWithResolver(address owner, address resolver) returns (bytes32 node)
When called by account x, instructs the Domains registry to set the resolver of the name hex(x) + '.addr.reverse' to the specified resolver, then transfer ownership of the name to the provided address, and return the namehash of the Domains record thus transferred. This method facilitates setting up a custom resolver and owner in fewer transactions than would be required if calling claim.
function setName(string name) returns (bytes32 node)
When called by account x, sets the resolver for the name hex(x) + '.addr.reverse' to a default resolver, and sets the name record on that name to the specified name. This method facilitates setting up simple reverse records for users in a single transaction.
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 Domains 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 DomainsIPs may specify more record types appropriate to reverse DNS records.
Appendix 1: Registrar implementation
This registrar, written in Solidity, implements the specifications outlined above.
pragma solidity >=0.8.4;
import "./TDNS.sol";
import "./IReverseRegistrar.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../root/Controllable.sol";
abstract contract NameResolver {
function setName(bytes32 node, string memory name) public virtual;
}
bytes32 constant lookup = 0x3031323334353637383961626364656600000000000000000000000000000000;
bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;
// namehash('addr.reverse')
contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar {
TDNS public immutable tdns;
NameResolver public defaultResolver;
event ReverseClaimed(address indexed addr, bytes32 indexed node);
/**
* @dev Constructor
* @param tdnsAddr The address of the TDNS registry.
*/
constructor(TDNS tdnsAddr) {
tdns = tdnsAddr;
// Assign ownership of the reverse record to our deployer
ReverseRegistrar oldRegistrar = ReverseRegistrar(
tdnsAddr.owner(ADDR_REVERSE_NODE)
);
if (address(oldRegistrar) != address(0x0)) {
oldRegistrar.claim(msg.sender);
}
}
modifier authorised(address addr) {
require(
addr == msg.sender ||
controllers[msg.sender] ||
tdns.isApprovedForAll(addr, msg.sender) ||
ownsContract(addr),
"ReverseRegistrar: Caller is not a controller or authorised by address or the address itself"
);
_;
}
function setDefaultResolver(address resolver) public override onlyOwner {
require(
address(resolver) != address(0),
"ReverseRegistrar: Resolver address must not be 0"
);
defaultResolver = NameResolver(resolver);
}
/**
* @dev Transfers ownership of the reverse TDNS record associated with the
* calling account.
* @param owner The address to set as the owner of the reverse record in TDNS.
* @return The TDNS node hash of the reverse record.
*/
function claim(address owner) public override returns (bytes32) {
return claimForAddr(msg.sender, owner, address(defaultResolver));
}
/**
* @dev Transfers ownership of the reverse TDNS record associated with the
* calling account.
* @param addr The reverse record to set
* @param owner The address to set as the owner of the reverse record in TDNS.
* @return The TDNS node hash of the reverse record.
*/
function claimForAddr(
address addr,
address owner,
address resolver
) public override authorised(addr) returns (bytes32) {
bytes32 labelHash = sha3HexAddress(addr);
bytes32 reverseNode = keccak256(
abi.encodePacked(ADDR_REVERSE_NODE, labelHash)
);
emit ReverseClaimed(addr, reverseNode);
tdns.setSubnodeRecord(ADDR_REVERSE_NODE, labelHash, owner, resolver, 0);
return reverseNode;
}
/**
* @dev Transfers ownership of the reverse TDNS record associated with the
* calling account.
* @param owner The address to set as the owner of the reverse record in TDNS.
* @param resolver The address of the resolver to set; 0 to leave unchanged.
* @return The TDNS node hash of the reverse record.
*/
function claimWithResolver(address owner, address resolver)
public
override
returns (bytes32)
{
return claimForAddr(msg.sender, owner, resolver);
}
/**
* @dev Sets the `name()` record for the reverse TDNS record associated with
* the calling account. First updates the resolver to the default reverse
* resolver if necessary.
* @param name The name to set for this address.
* @return The TDNS node hash of the reverse record.
*/
function setName(string memory name) public override returns (bytes32) {
return
setNameForAddr(
msg.sender,
msg.sender,
address(defaultResolver),
name
);
}
/**
* @dev Sets the `name()` record for the reverse TDNS record associated with
* the account provided. First updates the resolver to the default reverse
* resolver if necessary.
* Only callable by controllers and authorised users
* @param addr The reverse record to set
* @param owner The owner of the reverse node
* @param name The name to set for this address.
* @return The TDNS node hash of the reverse record.
*/
function setNameForAddr(
address addr,
address owner,
address resolver,
string memory name
) public override returns (bytes32) {
bytes32 node = claimForAddr(addr, owner, resolver);
NameResolver(resolver).setName(node, name);
return node;
}
/**
* @dev Returns the node hash for a given account's reverse records.
* @param addr The address to hash
* @return The TDNS node hash.
*/
function node(address addr) public pure override returns (bytes32) {
return
keccak256(
abi.encodePacked(ADDR_REVERSE_NODE, sha3HexAddress(addr))
);
}
/**
* @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 ret The SHA3 hash of the lower-case hexadecimal encoding of the
* input address.
*/
function sha3HexAddress(address addr) private pure returns (bytes32 ret) {
assembly {
for {
let i := 40
} gt(i, 0) {
} {
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)
}
ret := keccak256(0, 40)
}
}
function ownsContract(address addr) internal view returns (bool) {
try Ownable(addr).owner() returns (address owner) {
return owner == msg.sender;
} catch {
return false;
}
}
}