Skip to main content
The DNSSEC oracle validates cryptographic proofs of DNS record ownership, enabling secure integration between traditional DNS and ENS.

Overview

The DNSSEC oracle implements DNSSEC (DNS Security Extensions) validation on Ethereum. It verifies chains of signed DNS records against trusted root anchors, ensuring that only legitimate DNS owners can prove ownership. Location: contracts/dnssec-oracle/DNSSECImpl.sol Interface: contracts/dnssec-oracle/DNSSEC.sol

DNSSEC Background

DNSSEC adds cryptographic signatures to DNS records:
  1. Trust Anchors: Root zone public key hashes (DS records)
  2. Chain of Trust: Each zone signs its child zones
  3. RRSIG Records: Contain cryptographic signatures of DNS records
  4. DNSKEY Records: Contain public keys used for verification
  5. DS Records: Contain hashes of child zone keys

Contract Architecture

DNSSEC Interface

The abstract interface defines the core verification function:
abstract contract DNSSEC {
    bytes public anchors;

    struct RRSetWithSignature {
        bytes rrset;
        bytes sig;
    }

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

    function verifyRRSet(
        RRSetWithSignature[] memory input,
        uint256 now
    ) public view virtual returns (bytes memory rrs, uint32 inception);
}

DNSSECImpl Implementation

The concrete implementation extends the interface:
contract DNSSECImpl is DNSSEC, Owned {
    using Buffer for Buffer.buffer;
    using BytesUtils for bytes;
    using RRUtils for *;

    mapping(uint8 => Algorithm) public algorithms;
    mapping(uint8 => Digest) public digests;

    // Implementation details...
}

Core Data Structures

RRSetWithSignature

Represents a signed set of DNS resource records:
struct RRSetWithSignature {
    bytes rrset;  // The RRSet data including RRSIG
    bytes sig;    // The signature data
}

SignedSet

Parsed representation of a signed RRSet:
struct SignedSet {
    uint16 typeCovered;   // DNS record type covered by signature
    uint8 algorithm;      // Signature algorithm
    uint8 labels;         // Number of labels in name
    uint32 ttl;           // Time to live
    uint32 expiration;    // Signature expiration time
    uint32 inception;     // Signature inception time
    uint16 keytag;        // Key tag for key lookup
    bytes signerName;     // Name of the signing zone
    bytes data;           // The signed RR data
    bytes name;           // The record name
}

Main Functions

verifyRRSet (External)

Validates a chain of signed DNS records using the current block timestamp.
function verifyRRSet(
    RRSetWithSignature[] memory input
) external view virtual override returns (bytes memory rrs, uint32 inception);
Parameters:
  • input: Array of signed RRSets forming a chain from root to target
Returns:
  • rrs: The validated RR data from the last RRSet
  • inception: Signature inception time
Process:
  1. Starts with trust anchors as initial proof
  2. Validates each RRSet in sequence
  3. Each RRSet’s data becomes proof for the next
  4. Returns final validated data

verifyRRSet (Public with Timestamp)

Validates a chain at a specific timestamp.
function verifyRRSet(
    RRSetWithSignature[] memory input,
    uint256 now
) public view virtual override returns (bytes memory rrs, uint32 inception);
Parameters:
  • input: Array of signed RRSets
  • now: Unix timestamp to validate signatures against
Usage:
// Verify at current time
(bytes memory data, uint32 inception) = oracle.verifyRRSet(proofs);

// Verify at specific time
(bytes memory data, uint32 inception) = oracle.verifyRRSet(proofs, 1234567890);

Validation Process

Signature Validation

The validation process follows RFC 4035:
function validateSignedSet(
    RRSetWithSignature memory input,
    bytes memory proof,
    uint256 now
) internal view returns (RRUtils.SignedSet memory rrset);
Validation Steps:
  1. Parse the signed set:
    rrset = input.rrset.readSignedSet();
    
  2. Validate label count:
    if (name.labelCount(0) != rrset.labels) {
        revert InvalidLabelCount(name, rrset.labels);
    }
    
  3. Check signature validity period:
    // Check expiration
    if (!RRUtils.serialNumberGte(rrset.expiration, uint32(now))) {
        revert SignatureExpired(rrset.expiration, uint32(now));
    }
    
    // Check inception
    if (!RRUtils.serialNumberGte(uint32(now), rrset.inception)) {
        revert SignatureNotValidYet(rrset.inception, uint32(now));
    }
    
  4. Verify cryptographic signature:
    verifySignature(name, rrset, input, proof);
    

Serial Number Arithmetic

DNSSEC uses RFC 1982 serial number arithmetic for time comparisons:
function serialNumberGte(uint32 i1, uint32 i2) internal pure returns (bool) {
    unchecked {
        return int32(i1) - int32(i2) >= 0;
    }
}
This handles wraparound of 32-bit timestamps correctly.

Signature Verification

Verification with DNSKEY

Verifies a signature using a known public key:
function verifyWithKnownKey(
    RRUtils.SignedSet memory rrset,
    RRSetWithSignature memory data,
    RRUtils.RRIterator memory proof
) internal view;
Process:
  1. Extracts DNSKEY from proof
  2. Validates key properties (protocol, flags)
  3. Computes key tag
  4. Verifies signature using algorithm contract

Verification with DS

Verifies using a Delegation Signer (hash of key):
function verifyWithDS(
    RRUtils.SignedSet memory rrset,
    RRSetWithSignature memory data,
    RRUtils.RRIterator memory proof
) internal view;
Process:
  1. Verifies RRSet is self-signed with DNSKEY
  2. Computes hash of DNSKEY
  3. Compares hash with DS record from proof

Key Verification

function verifySignatureWithKey(
    RRUtils.DNSKEY memory dnskey,
    bytes memory keyrdata,
    RRUtils.SignedSet memory rrset,
    RRSetWithSignature memory data
) internal view returns (bool);
Checks:
  • Protocol must be 3 (DNSSEC)
  • Algorithm must match signature
  • Key tag must match signature
  • Zone flag (bit 7) must be set
  • Signature must verify using algorithm contract

Algorithm and Digest Management

Algorithm Registry

Maps algorithm IDs to verification contracts:
mapping(uint8 => Algorithm) public algorithms;

function setAlgorithm(uint8 id, Algorithm algo) public owner_only {
    algorithms[id] = algo;
    emit AlgorithmUpdated(id, address(algo));
}
Common Algorithms:
  • 5: RSASHA1
  • 7: RSASHA1-NSEC3-SHA1
  • 8: RSASHA256
  • 13: ECDSAP256SHA256
  • 14: ECDSAP384SHA384

Digest Registry

Maps digest type IDs to verification contracts:
mapping(uint8 => Digest) public digests;

function setDigest(uint8 id, Digest digest) public owner_only {
    digests[id] = digest;
    emit DigestUpdated(id, address(digest));
}
Common Digests:
  • 1: SHA-1
  • 2: SHA-256
  • 4: SHA-384

Trust Anchors

The oracle is initialized with root zone trust anchors:
constructor(bytes memory _anchors) {
    anchors = _anchors;
}

bytes public anchors;
These anchors contain the DS records for the DNS root zone, establishing the root of trust.

DNS Record Types

Key DNS record type constants:
uint16 constant DNSCLASS_IN = 1;      // Internet class
uint16 constant DNSTYPE_DS = 43;      // Delegation Signer
uint16 constant DNSTYPE_DNSKEY = 48;  // DNS Public Key

RRUtils Library

The RRUtils library provides utilities for parsing DNS records:
library RRUtils {
    struct RRIterator {
        bytes data;
        uint256 offset;
        uint16 dnstype;
        uint16 class;
        uint32 ttl;
        uint256 rdataOffset;
        uint256 nextOffset;
    }

    function iterateRRs(
        bytes memory self,
        uint256 offset
    ) internal pure returns (RRIterator memory);

    function next(RRIterator memory iter) internal pure;
    function done(RRIterator memory iter) internal pure returns (bool);
    function name(RRIterator memory iter) internal pure returns (bytes memory);
    function rdata(RRIterator memory iter) internal pure returns (bytes memory);
}

DNSKEY Structure

struct DNSKEY {
    uint16 flags;
    uint8 protocol;
    uint8 algorithm;
    bytes publicKey;
}

function readDNSKEY(
    bytes memory data,
    uint256 offset,
    uint256 length
) internal pure returns (DNSKEY memory);

DS Structure

struct DS {
    uint16 keytag;
    uint8 algorithm;
    uint8 digestType;
    bytes digest;
}

function readDS(
    bytes memory data,
    uint256 offset,
    uint256 length
) internal pure returns (DS memory);

Key Tag Computation

Computes the DNSSEC key tag for a DNSKEY:
function computeKeytag(bytes memory data) internal pure returns (uint16);
The key tag is a 16-bit checksum used to quickly identify which key signed a record. The implementation uses optimized EVM operations to efficiently compute the checksum.

Errors

InvalidLabelCount

error InvalidLabelCount(bytes name, uint256 labelsExpected);
Thrown when the label count doesn’t match the signature.

SignatureNotValidYet

error SignatureNotValidYet(uint32 inception, uint32 now);
Thrown when current time is before signature inception.

SignatureExpired

error SignatureExpired(uint32 expiration, uint32 now);
Thrown when current time is after signature expiration.

InvalidClass

error InvalidClass(uint16 class);
Thrown when DNS record class is not IN (Internet).

SignatureTypeMismatch

error SignatureTypeMismatch(uint16 rrsetType, uint16 sigType);
Thrown when signature covers wrong record type.

InvalidProofType

error InvalidProofType(uint16 proofType);
Thrown when proof is neither DNSKEY nor DS.

NoMatchingProof

error NoMatchingProof(bytes signerName);
Thrown when no valid proof validates the signature.

Events

AlgorithmUpdated

event AlgorithmUpdated(uint8 id, address addr);
Emitted when an algorithm verifier is added or updated.

DigestUpdated

event DigestUpdated(uint8 id, address addr);
Emitted when a digest verifier is added or updated.

Usage Example

// Initialize oracle with root anchors
bytes memory rootAnchors = getRootTrustAnchors();
DNSSECImpl oracle = new DNSSECImpl(rootAnchors);

// Set up algorithm verifiers
oracle.setAlgorithm(8, rsaSha256Verifier);
oracle.setAlgorithm(13, ecdsaP256Verifier);

// Set up digest verifiers
oracle.setDigest(2, sha256Digest);

// Verify a chain of DNS records
DNSSEC.RRSetWithSignature[] memory proofs = collectDNSSECProofs();
(bytes memory validatedData, uint32 inception) = oracle.verifyRRSet(proofs);

// Use validated data
processDNSRecords(validatedData);

Security Considerations

Trust Anchor Security

The security of the entire system depends on the trust anchors:
  • Must be from authentic root zone
  • Should be updated when root keys rotate
  • Compromise of root keys compromises entire system

Algorithm Security

Only use secure, well-tested algorithms:
  • Older algorithms like RSASHA1 (5) may be weak
  • Prefer modern algorithms like ECDSAP256SHA256 (13)

Time Validation

Signature validity periods must be checked:
  • Prevents replay of expired signatures
  • Ensures signatures haven’t been created with future timestamps
  • Uses serial number arithmetic to handle wraparound

Gas Considerations

DNSSEC verification is computationally expensive:
  • Long proof chains use more gas
  • RSA verification is more expensive than ECDSA
  • Consider gas limits when submitting proofs

Limitations

The implementation has some intentional limitations per RFC 4034/4035:
  1. No NSEC/NSEC3 support: Only positive proofs are accepted
  2. No wildcard support: Wildcard proofs will not validate
  3. No TTL enforcement: TTLs are ignored as data isn’t stored
  4. No canonicalization check: Names should be canonical but aren’t verified

Build docs developers (and LLMs) love