Skip to content

Commit

Permalink
Merge pull request #78 from ensdomains/feature/stateless-dnssec
Browse files Browse the repository at this point in the history
Made DNSSEC oracle pure
  • Loading branch information
Arachnid committed Sep 19, 2022
2 parents 1b6b4b5 + 70217fb commit 5842106
Show file tree
Hide file tree
Showing 22 changed files with 766 additions and 2,271 deletions.
31 changes: 8 additions & 23 deletions contracts/dnsregistrar/DNSClaimChecker.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../dnssec-oracle/DNSSEC.sol";
Expand All @@ -13,41 +14,25 @@ library DNSClaimChecker {
uint16 constant CLASS_INET = 1;
uint16 constant TYPE_TXT = 16;

function getOwnerAddress(
DNSSEC oracle,
bytes memory name,
bytes memory proof
) internal view returns (address, bool) {
function getOwnerAddress(bytes memory name, bytes memory data)
internal
pure
returns (address, bool)
{
// Add "_ens." to the front of the name.
Buffer.buffer memory buf;
buf.init(name.length + 5);
buf.append("\x04_ens");
buf.append(name);
bytes20 hash;
uint32 expiration;
// Check the provided TXT record has been validated by the oracle
(, expiration, hash) = oracle.rrdata(TYPE_TXT, buf.buf);
if (hash == bytes20(0) && proof.length == 0)
return (address(0x0), false);

require(hash == bytes20(keccak256(proof)));

for (
RRUtils.RRIterator memory iter = proof.iterateRRs(0);
RRUtils.RRIterator memory iter = data.iterateRRs(0);
!iter.done();
iter.next()
) {
require(
RRUtils.serialNumberGte(
expiration + iter.ttl,
uint32(block.timestamp)
),
"DNS record is stale; refresh or delete it before proceeding."
);

bool found;
address addr;
(addr, found) = parseRR(proof, iter.rdataOffset);
(addr, found) = parseRR(data, iter.rdataOffset);
if (found) {
return (addr, true);
}
Expand Down
121 changes: 53 additions & 68 deletions contracts/dnsregistrar/DNSRegistrar.sol
Original file line number Diff line number Diff line change
@@ -1,47 +1,51 @@
//SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@ensdomains/buffer/contracts/Buffer.sol";
import "../dnssec-oracle/BytesUtils.sol";
import "../dnssec-oracle/DNSSEC.sol";
import "../dnssec-oracle/RRUtils.sol";
import "../registry/ENSRegistry.sol";
import "../root/Root.sol";
import "../resolvers/profiles/AddrResolver.sol";
import "./DNSClaimChecker.sol";
import "./PublicSuffixList.sol";
import "../resolvers/profiles/AddrResolver.sol";

interface IDNSRegistrar {
function claim(bytes memory name, bytes memory proof) external;

function proveAndClaim(
bytes memory name,
DNSSEC.RRSetWithSignature[] memory input,
bytes memory proof
) external;

function proveAndClaimWithResolver(
bytes memory name,
DNSSEC.RRSetWithSignature[] memory input,
bytes memory proof,
address resolver,
address addr
) external;
}
import "./IDNSRegistrar.sol";

/**
* @dev An ENS registrar that allows the owner of a DNS name to claim the
* corresponding name in ENS.
*/
contract DNSRegistrar is IDNSRegistrar {
// TODO: Record inception time of any claimed name, so old proofs can't be used to revert changes to a name.
contract DNSRegistrar is IDNSRegistrar, IERC165 {
using BytesUtils for bytes;
using Buffer for Buffer.buffer;
using RRUtils for *;

DNSSEC public oracle;
ENS public ens;
ENS public immutable ens;
DNSSEC public immutable oracle;
PublicSuffixList public suffixes;
// A mapping of the most recent signatures seen for each claimed domain.
mapping(bytes32 => uint32) public inceptions;

bytes4 private constant INTERFACE_META_ID =
bytes4(keccak256("supportsInterface(bytes4)"));
error NoOwnerRecordFound();
error StaleProof();

event Claim(bytes32 indexed node, address indexed owner, bytes dnsname);
struct OwnerRecord {
bytes name;
address owner;
address resolver;
uint64 ttl;
}

event Claim(
bytes32 indexed node,
address indexed owner,
bytes dnsname,
uint32 inception
);
event NewOracle(address oracle);
event NewPublicSuffixList(address suffixes);

Expand All @@ -67,60 +71,36 @@ contract DNSRegistrar is IDNSRegistrar {
_;
}

function setOracle(DNSSEC _dnssec) public onlyOwner {
oracle = _dnssec;
emit NewOracle(address(oracle));
}

function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner {
suffixes = _suffixes;
emit NewPublicSuffixList(address(suffixes));
}

/**
* @dev Claims a name by proving ownership of its DNS equivalent.
* @param name The name to claim, in DNS wire format.
* @param proof A DNS RRSet proving ownership of the name. Must be verified
* in the DNSSEC oracle before calling. This RRSET must contain a TXT
* record for '_ens.' + name, with the value 'a=0x...'. Ownership of
* the name will be transferred to the address specified in the TXT
* record.
*/
function claim(bytes memory name, bytes memory proof) public override {
(bytes32 rootNode, bytes32 labelHash, address addr) = _claim(
name,
proof
);
ens.setSubnodeOwner(rootNode, labelHash, addr);
}

/**
* @dev Submits proofs to the DNSSEC oracle, then claims a name using those proofs.
* @param name The name to claim, in DNS wire format.
* @param input The data to be passed to the Oracle's `submitProofs` function. The last
* proof must be the TXT record required by the registrar.
* @param proof The proof record for the first element in input.
* @param input A chain of signed DNS RRSETs ending with a text record.
*/
function proveAndClaim(
bytes memory name,
DNSSEC.RRSetWithSignature[] memory input,
bytes memory proof
DNSSEC.RRSetWithSignature[] memory input
) public override {
proof = oracle.submitRRSets(input, proof);
claim(name, proof);
(bytes32 rootNode, bytes32 labelHash, address addr) = _claim(
name,
input
);
ens.setSubnodeOwner(rootNode, labelHash, addr);
}

function proveAndClaimWithResolver(
bytes memory name,
DNSSEC.RRSetWithSignature[] memory input,
bytes memory proof,
address resolver,
address addr
) public override {
proof = oracle.submitRRSets(input, proof);
(bytes32 rootNode, bytes32 labelHash, address owner) = _claim(
name,
proof
input
);
require(
msg.sender == owner,
Expand Down Expand Up @@ -152,21 +132,24 @@ contract DNSRegistrar is IDNSRegistrar {
function supportsInterface(bytes4 interfaceID)
external
pure
override
returns (bool)
{
return
interfaceID == INTERFACE_META_ID ||
interfaceID == type(IERC165).interfaceId ||
interfaceID == type(IDNSRegistrar).interfaceId;
}

function _claim(bytes memory name, bytes memory proof)
function _claim(bytes memory name, DNSSEC.RRSetWithSignature[] memory input)
internal
returns (
bytes32 rootNode,
bytes32 parentNode,
bytes32 labelHash,
address addr
)
{
(bytes memory data, uint32 inception) = oracle.verifyRRSet(input);

// Get the first label
uint256 labelLen = name.readUint8(0);
labelHash = name.keccak(1, labelLen);
Expand All @@ -182,15 +165,17 @@ contract DNSRegistrar is IDNSRegistrar {
);

// Make sure the parent name is enabled
rootNode = enableNode(parentName, 0);
parentNode = enableNode(parentName, 0);

bytes32 node = keccak256(abi.encodePacked(parentNode, labelHash));
if (!RRUtils.serialNumberGte(inception, inceptions[node])) {
revert StaleProof();
}
inceptions[node] = inception;

(addr, ) = DNSClaimChecker.getOwnerAddress(oracle, name, proof);
(addr, ) = DNSClaimChecker.getOwnerAddress(name, data);

emit Claim(
keccak256(abi.encodePacked(rootNode, labelHash)),
addr,
name
);
emit Claim(node, addr, name, inception);
}

function enableNode(bytes memory domain, uint256 offset)
Expand Down
18 changes: 18 additions & 0 deletions contracts/dnsregistrar/IDNSRegistrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../dnssec-oracle/DNSSEC.sol";

interface IDNSRegistrar {
function proveAndClaim(
bytes memory name,
DNSSEC.RRSetWithSignature[] memory input
) external;

function proveAndClaimWithResolver(
bytes memory name,
DNSSEC.RRSetWithSignature[] memory input,
address resolver,
address addr
) external;
}
45 changes: 45 additions & 0 deletions contracts/dnsregistrar/RecordParser.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

import "../dnssec-oracle/BytesUtils.sol";

library RecordParser {
using BytesUtils for bytes;

/**
* @dev Parses a key-value record into a key and value.
* @param input The input string
* @param offset The offset to start reading at
*/
function readKeyValue(
bytes memory input,
uint256 offset,
uint256 len
)
internal
pure
returns (
bytes memory key,
bytes memory value,
uint256 nextOffset
)
{
uint256 separator = input.find(offset, len, "=");
if (separator == type(uint256).max) {
return ("", "", type(uint256).max);
}

uint256 terminator = input.find(
separator,
len + offset - separator,
" "
);
if (terminator == type(uint256).max) {
terminator = input.length;
}

key = input.substring(offset, separator - offset);
value = input.substring(separator + 1, terminator - separator - 1);
nextOffset = terminator + 1;
}
}
22 changes: 22 additions & 0 deletions contracts/dnssec-oracle/BytesUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -377,4 +377,26 @@ library BytesUtils {

return bytes32(ret << (256 - bitlen));
}

/**
* @dev Finds the first occurrence of the byte `needle` in `self`.
* @param self The string to search
* @param off The offset to start searching at
* @param len The number of bytes to search
* @param needle The byte to search for
* @return The offset of `needle` in `self`, or 2**256-1 if it was not found.
*/
function find(
bytes memory self,
uint256 off,
uint256 len,
bytes1 needle
) internal pure returns (uint256) {
for (uint256 idx = off; idx < off + len; idx++) {
if (self[idx] == needle) {
return idx;
}
}
return type(uint256).max;
}
}
38 changes: 7 additions & 31 deletions contracts/dnssec-oracle/DNSSEC.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
pragma experimental ABIEncoderV2;

Expand All @@ -11,41 +12,16 @@ abstract contract DNSSEC {

event AlgorithmUpdated(uint8 id, address addr);
event DigestUpdated(uint8 id, address addr);
event NSEC3DigestUpdated(uint8 id, address addr);
event RRSetUpdated(bytes name, bytes rrset);

function submitRRSets(RRSetWithSignature[] memory input, bytes memory proof)
public
function verifyRRSet(RRSetWithSignature[] memory input)
external
view
virtual
returns (bytes memory);
returns (bytes memory rrs, uint32 inception);

function submitRRSet(RRSetWithSignature memory input, bytes memory proof)
function verifyRRSet(RRSetWithSignature[] memory input, uint256 now)
public
virtual
returns (bytes memory);

function deleteRRSet(
uint16 deleteType,
bytes memory deleteName,
RRSetWithSignature memory nsec,
bytes memory proof
) public virtual;

function deleteRRSetNSEC3(
uint16 deleteType,
bytes memory deleteName,
RRSetWithSignature memory closestEncloser,
RRSetWithSignature memory nextClosest,
bytes memory dnskey
) public virtual;

function rrdata(uint16 dnstype, bytes calldata name)
external
view
virtual
returns (
uint32,
uint32,
bytes20
);
returns (bytes memory rrs, uint32 inception);
}
Loading

0 comments on commit 5842106

Please sign in to comment.