# Steg > Applied research and operations in proof verification, modular verifier design, and Ethereum-anchored trust models :::info **Working Implementation**: This document describes three complementary DNSSEC P-256 demonstration projects deployed on Sepolia, all leveraging the EIP-7951 P-256 precompile. Working implementations are available in the [`dnssec-solutions`](https://github.com/steg-eth/dnssec-solutions) repository. ::: ### 1. Introduction #### 1.1. Document Purpose This document provides a comprehensive technical reference for implementing DNSSEC P-256 (Algorithm 13) as a profile in the Universal Resolver Matrix (URM), serving as a technical specification for DNSSEC onchain resolution. #### 1.2. Demonstration Projects This specification is supported by three demonstration projects in the [`dnssec-solutions`](https://github.com/steg-eth/dnssec-solutions) repository that showcase different approaches to DNSSEC verification: 1. **Gasless DNSSEC Resolution** - CCIP-Read (EIP-3668) implementation with Profile A trust model (pinned zone KSK) 2. **Onchain DNS Import** - ENS-style DNSSEC oracle with Profile B trust model (IANA root DS chain) 3. **DNS Claiming** - ENS registrar contracts for claiming DNS names using DNSSEC proofs All three projects leverage the **EIP-7951 P-256 precompile** for efficient Algorithm 13 signature verification, achieving \~94% gas savings compared to Solidity implementations. #### 1.3. Universal Resolver Matrix Framework The Universal Resolver Matrix (URM) is a systematic framework for mapping resolution pathways across namespaces using four core dimensions: 1. **Trust Model**, 2. **Proof System**, 3. **Rules & Lifecycle**, and 4. **Verification Path**. This document structures DNSSEC P-256 implementation around these dimensions, with additional sections covering: * Deployment Architecture (L1 vs Namechain considerations) * Contract & Namespace Inventory * URM Mapping (Resolver Profiles) * Edge Cases & Client Requirements ### 2. Scope & Goals #### 2.1. DNSSEC Algorithm 13 (P-256) Overview ENS uses **Algorithm 13 (ECDSAP256SHA256)** for DNSSEC verification, enabling trustless DNS-to-ENS resolution by validating P-256 ECDSA signatures onchain. A DNS record set (RRset) is considered verifiable if its RRSIG uses Algorithm 13 and the signature can be cryptographically chained—via DNSKEY and DS records—up to a trust anchor. This validation relies on Ethereum's P-256 precompile (EIP-7951) available on both L1 and Namechain for efficient onchain signature verification. ##### Trust Model Choices: Profile A vs Profile B We provide implementations for both trust models: **Profile A (Pinned Zone KSK)** - Used by **Gasless DNSSEC Resolution**: * **Trust Anchor**: Pinned zone KSK (e.g., `eketc.co` KSK with keyTag=2371) * **Validation**: Verifies domain DNSKEY → TXT record chain only * **Advantages**: Faster verification, immediate deployment, no dependency on root/TLD Algorithm 13 adoption * **Limitation**: Does not verify parent zone chain (e.g., `.co` → root) * **Use Case**: Proof-of-concept, gasless resolution via CCIP-Read **Profile B (IANA Root DS Chain)** - Used by **Onchain DNS Import** and **DNS Claiming**: * **Trust Anchor**: IANA root zone DNSKEY records * **Validation**: Full DS (Delegation Signer) chain from root → TLD → domain * **Advantages**: Complete trust chain validation, ENS-compatible, maximum security * **Requirements**: Root/TLD zones must use Algorithm 13 or hybrid verification approaches * **Use Case**: Full on-chain oracle, ENS registrar integration **Why Both Models:** 1. **DNS root zone uses RSA (Algorithm 8)**, not ECDSA-P256 (Algorithm 13) 2. **Most TLDs use RSA**, not Algorithm 13 (only `.fr` is known to use Algorithm 13) 3. **Profile A** demonstrates immediate Algorithm 13 verification without waiting for infrastructure adoption 4. **Profile B** provides complete ENS-compatible verification once Algorithm 13 adoption increases **Implementation Status:** * **Profile A**: `Gasless DNSSEC Resolution/` - Sepolia testnet, working with `eketc.co` * **Profile B**: `Onchain DNS Import/` - Full oracle implementation with IANA root support #### 2.3. DNS Record Types & Usage DNSSEC P-256 supports verification of all standard DNS record types (TXT, A, AAAA, CNAME, etc.), with primary focus on: * **TXT records** for ENS attribution (`_ens.example.com TXT "ens_name=example.eth"`) * **A/AAAA records** for direct IP address resolution * **CNAME records** for DNS aliasing with DNSSEC chain following #### 2.4. Deployment Architecture: L1 vs Namechain DNSSEC P-256 verification requires both P-256 precompile and SHA-256 hashing. The deployment location significantly impacts gas costs and production viability: ##### 2.4.1. Architecture Overview Three distinct architectures are demonstrated, each with different deployment patterns: **1. Gasless DNSSEC Resolution (Profile A - CCIP-Read)** * **Resolver Contract**: Deploys on L1 or Namechain * **Gateway Server**: Off-chain Node.js server for proof fetching * **Trust Anchor**: Pinned zone KSK stored in resolver contract * **Gas Model**: Users pay no gas; gateway pays for verification **2. Onchain DNS Import (Profile B - Oracle)** * **Oracle Contract**: Deploys on L1 or Namechain (recommended: Namechain) * **Trust Anchors**: IANA root zone DNSKEY records stored in oracle * **Gas Model**: Users submit proofs and pay gas for verification * **Storage**: Verified DNS records stored on-chain permanently **3. DNS Claiming (Profile B - Registrar)** * **Registrar Contract**: Deploys on L1 or Namechain * **Dependency**: Uses Onchain DNS Import's oracle for verification * **Integration**: Claims DNS names on ENS registry * **Gas Model**: Users pay gas for claiming operations :::note **Deployment Recommendation**: For Profile B implementations, **Namechain deployment is strongly recommended** to avoid expensive cross-chain calls. This enables seamless L2-to-L2 verification flows without L1 bridging overhead, achieving 98-99% gas cost reduction. ::: ##### 2.4.2. Sepolia Testnet Deployments **Profile A Implementation (Gasless DNSSEC Resolution):** | Contract | Address | Purpose | | ---------------------- | -------------------------------------------- | --------------------------------------------------------- | | **DnssecP256Verifier** | `0x580F2Db4Da8E6D5c654aa604182D0dFD17D5766B` | Trust anchor storage (pinned KSK) and DNSSEC verification | | **DnssecResolver** | `0x7233d88AF9ee1eC3833F6AF4f733c1C5c0587Da2` | CCIP-Read resolver for DNSSEC-backed names | **Working Example:** * DNS Name: `_ens.eketc.co` (TXT record: `"ens_name=dnssec.eth"`) * ENS Name: `dnssec.eth` (Sepolia) * Trust Anchor: `eketc.co` KSK (keyTag=2371, Algorithm 13) - Profile A pinned anchor * Gateway: `https://gateway.eketc.co/ccip-read` **Profile B Implementation (Onchain DNS Import):** | Contract | Purpose | | ----------------------- | ----------------------------------------------- | | **DNSSECOracle** | Full IANA root DS chain verification oracle | | **Algorithm Contracts** | P-256 (EIP-7951) and RSA signature verification | **Note**: Profile B deployments are demonstrated in the `Onchain DNS Import/` project. See project README for deployment addresses and examples. **EIP-7951 Precompile**: Confirmed available at `0x0100` on Sepolia ##### 2.4.3. Contract Inventory Three distinct contract patterns are demonstrated: **Pattern 1: CCIP-Read Resolver (Profile A - Gasless DNSSEC Resolution)** * **DnssecP256Verifier** - Trust anchor holder (pinned KSK) and cryptographic verifier using P-256 precompile * **DnssecResolver** - CCIP-Read resolver for DNSSEC-backed DNS names with offchain proof fetching * **Gateway Server** - Off-chain Node.js server that fetches DNSSEC proofs and serves via CCIP-Read **Pattern 2: Oracle-Based (Profile B - Onchain DNS Import)** * **DNSSECOracle** - Full DNSSEC oracle with IANA root trust anchors, stores verified DNS records on-chain * **Algorithm Contracts** - P-256 (EIP-7951 precompile) and RSA signature verification implementations * **No Gateway Required** - Users submit proofs directly to oracle contract **Pattern 3: Registrar-Based (Profile B - DNS Claiming)** * **DNSRegistrar** - ENS registrar for claiming DNS names using DNSSEC proofs * **DNSClaimChecker** - Library for parsing `_ens` TXT records to extract owner addresses * **Dependency**: Uses Onchain DNS Import's DNSSECOracle for proof verification **Common Infrastructure:** * **Universal Resolver (L1/L2)** - Existing resolution entry point (optional but recommended) * **EIP-7951 P-256 Precompile** - Shared cryptographic primitive at `0x0100` for all patterns ##### 2.4.4. Roots of Trust & Security The canonical root of trust consists of: * **DNS Root Zone KSK** - Stored in DnssecP256Verifier contract as trust anchors * **DNSSEC Chain of Trust** - Hierarchical validation from root → TLD → SLD → zone * **Ethereum Consensus** - Onchain verification guarantees Security is inherited from DNSSEC's cryptographic signatures and Ethereum consensus. DNS records are valid only if the complete DNSSEC chain validates onchain. ##### 2.4.5. Benefits & Tradeoffs This architecture enables: * **Trustlessness**: Cryptographic proof of DNS record authenticity * **Cost-effectiveness**: Namechain deployment reduces gas costs by 98-99% * **Scalability**: SHA-256 hashing and P-256 verification optimized for onchain execution * **DNSSEC Integration**: DNS zones become first-class ENS resolution sources ##### 2.4.6. Governance Requirements While not strictly required for basic resolver operation, a production-ready DNSSEC resolver should go through ENS DAO governance for integration into core ENS infrastructure. DAO approval is required if you want your resolver to be: * Set as the default resolver for ENS (e.g., for the reverse registrar or for .eth names) * Integrated at the protocol level (e.g., replacing the public resolver, or being referenced in official ENS contracts) **Why Governance Matters:** DAO approval is a matter of governance and trust. If you want your resolver to be widely used, trusted, or set as a default in ENS infrastructure, DAO approval: * Signals community trust and security review * Ensures the resolver is maintained and meets ENS standards * Protects against unauthorized trust anchor modifications during DNS root key rollovers ### 3. Trust Model #### 3.1. Roots of Trust DNSSEC P-256 supports two trust model approaches: **Profile A (Pinned Zone KSK)** - Used by Gasless DNSSEC Resolution: * **Trust Anchor**: Pinned zone-level KSK stored in the verifier contract * **Example**: `eketc.co` KSK (keyTag=2371, Algorithm 13) serves as the trust anchor * **Why stored in contract?** Solves the "bootstrap problem" - establishes a fixed trust anchor without requiring root zone verification * **Advantage**: Immediate deployment without dependency on root/TLD Algorithm 13 adoption **Profile B (IANA Root DS Chain)** - Used by Onchain DNS Import and DNS Claiming: * **Trust Anchor**: IANA root zone Key Signing Key (KSK) stored in the oracle contract * **Chain Validation**: Complete DS chain from root → TLD → SLD → zone * **Why stored in contract?** Solves the "bootstrap problem" - to verify DNSSEC, we need root keys, but fetching them requires verification (infinite regress). Onchain storage establishes a fixed trust anchor. * **Advantage**: Complete trust chain validation, ENS-compatible approach #### 3.2. DNSSEC Chain of Trust Trust flows hierarchically from the DNS root zone down to individual records: 1. **Root Zone** (`.`) - Contains root DNSKEY, signs TLD DS records 2. **TLD Zone** (`com`) - Contains TLD DNSKEY, signs SLD DS records 3. **SLD Zone** (`example.com`) - Contains zone DNSKEY, signs resource records 4. **Resource Records** - Individual DNS records signed by zone DNSKEY Each level validates the next via DS (Delegation Signer) records containing cryptographic digests of child zone DNSKEYs. #### 3.3. Security Guarantees **Security properties:** * **Cryptographic authenticity** - DNS records signed by zone operators with P-256 ECDSA * **Integrity protection** - Records cannot be tampered without breaking signatures * **Non-repudiation** - Zone operators cannot deny signing their records * **Trust minimization** - Security depends only on DNS root KSK and Ethereum consensus **Attacks protected against:** * **DNS spoofing** - Invalid signatures rejected onchain * **Man-in-the-middle** - Signatures prevent tampering * **Gateway manipulation** - Invalid proofs rejected by verifier * **Record tampering** - Any modification breaks signature verification **Cryptographic assumptions:** * **P-256 ECDSA security** - Hard computational problems * **SHA-256 collision resistance** - No collisions computationally feasible * **DNS root KSK integrity** - Root keys authentic and not compromised * **P-256 precompile correctness** - Precompile implements ECDSA correctly * **Ethereum consensus** - L1 consensus guarantees verification correctness ### 4. Proof System #### 4.1. Inputs & Core Definitions **Key inputs for DNSSEC verification:** * **DNS Question** - `qname (bytes), qtype (uint16)` - The original DNS query that needs verification. Specifies exactly what domain (`qname`) and record type (`qtype`) we're proving is authentic. Example: `"_ens.example.com"` with `qtype = 16` (TXT) to verify ENS attribution records. * **ProofBundle** - Complete DNSSEC proof chain delivered via CCIP-Read containing all cryptographic evidence: answer RRsets, RRSIG signatures, DNSKEY records, DS delegation proofs, and validity timestamps. This bundle provides everything needed for onchain verification without requiring the contract to fetch additional data. * **Root Trust Anchors** - Pre-stored DNSKEY records in the verifier contract that serve as the "root of trust" for DNSSEC verification. For ENS Algorithm 13 verification, these are TLD-level anchors (like .fr) rather than root zone keys, since the root currently uses RSA. **Core data structures:** * **RR (Resource Record)** - `name, rrtype, rrclass, ttl, rdata` - Individual DNS records containing the actual data. The `name` field specifies the domain, `rrtype` indicates the record type (1=A, 16=TXT, 28=AAAA), `ttl` specifies cache lifetime, and `rdata` contains the actual record content (e.g., IP address, text data). * **RRSIG (Resource Record Signature)** - `typeCovered, algorithm, labels, originalTTL, expiration, inception, keyTag, signerName, signature` - Cryptographic signature covering a set of DNS records (RRset). For ENS, `algorithm` must be 13, `typeCovered` matches the RR type being verified, and `signature` is a 64-byte P-256 ECDSA signature. The `keyTag` identifies which DNSKEY was used to create this signature. * **DNSKEY** - `flags, protocol, algorithm, publicKey` - Public key records used to verify RRSIG signatures. For Algorithm 13, `algorithm = 13` and `publicKey` contains 64 bytes of P-256 elliptic curve coordinates (32 bytes x + 32 bytes y). Flags distinguish KSK (257) from ZSK (256) keys. * **DS (Delegation Signer)** - `keyTag, algorithm, digestType, digest` - Cryptographic digests that prove delegation from parent to child zones. Parent zones create SHA-256 digests of child DNSKEY records and sign them, creating an unbroken chain of trust. For Algorithm 13, `algorithm = 13` and `digestType = 2` (SHA-256). **Algorithm constants:** * `ALGORITHM_13 = 13` - ECDSAP256SHA256 - The exclusive elliptic curve algorithm used by ENS for DNSSEC verification. Uses P-256 curve with SHA-256 hashing, providing 128-bit security level. Required for all DNSKEY, DS, and RRSIG records in ENS-verified zones. * `DNS_RECORD_TYPE_TXT = 16` - TXT records used for ENS attribution. Contains human-readable text like `"ens_name=example.eth"` that maps DNS domains to ENS names. TXT records are ideal for this purpose as they're flexible, widely supported, and don't conflict with other DNS uses. #### 4.2. Cryptographic Primitives ##### 4.2.1. ECDSA P-256 (Algorithm 13: ECDSAP256SHA256) **Purpose:** Exclusive cryptographic signature algorithm used by ENS for DNSSEC verification. DNS zone operators must sign all DNS resource records using Algorithm 13. **Usage:** * Zone operators generate P-256 key pairs for DNSSEC signing (private key for signing, public key in DNSKEY records) * RRSIG(13) records contain P-256 signatures over canonicalized RRsets using SHA-256 hashing * DNSKEY(13) records contain P-256 public keys (64 bytes: 32-byte x-coordinate + 32-byte y-coordinate) * DS records contain SHA-256 digests of DNSKEY(13) records to prove delegations **Verification:** EIP-7951 P-256 precompile at `0x0100` performs ECDSA signature verification on both L1 and Namechain **Sepolia Deployment Status:** ✅ **CONFIRMED AVAILABLE** * Precompile Address: `0x0100` * Gas Cost per Signature: \~3,000 gas * Full DNSSEC Verification: \~311,588 gas * Testnet Cost: \< $0.01 (at 0.001 gwei) * Mainnet Cost: \~$9-30 (at 30-100 gwei) ##### 4.2.2. SHA-256 **Purpose:** Hash function for ECDSAP256SHA256 algorithm and DS record digests. **Usage:** * RRsets canonicalized then hashed with SHA-256 before signing * DS records contain SHA-256 digests of DNSKEY records * Used in chain of trust validation **Implementation:** Solidity implementation (no native precompile), \~2,000-3,000 gas per hash ##### 4.2.3. DNS Name Canonicalization **Purpose:** Normalize DNS names to ensure consistent hashing and signature verification across different implementations. **Algorithm:** [RFC 4034 Section 6.1](https://www.rfc-editor.org/rfc/rfc4034.html#section-6.1) - DNS name canonicalization: * Convert all labels to lowercase (ASCII A-Z to a-z) * Decompress any compressed name pointers * Remove trailing dots from the root zone * Ensure consistent representation for cryptographic operations ##### 4.2.4. RRset Canonicalization **Purpose:** Sort and normalize resource records within an RRset to ensure deterministic hashing for signature verification. **Algorithm:** [RFC 4034 Section 6.2](https://www.rfc-editor.org/rfc/rfc4034.html#section-6.2) - RRset canonical ordering: * Sort RRsets by RR type, then by RDATA in network (big-endian) byte order * Ensures identical RRsets always hash to the same value * Critical for signature verification consistency across DNS implementations #### 4.3. Proof Types (URM Terms) ##### 4.3.1. dnssec\_proof(algorithm\_13) — DNSSEC Algorithm 13 Signature Chain **When Used:** * DNS zones signed exclusively with Algorithm 13 (ECDSA-P256-SHA256) * DNSKEY records must use algorithm 13 * DS records must reference algorithm 13 keys * RRSIG records must use algorithm 13 signatures * Any RRset (TXT, A, AAAA, etc.) covered by valid RRSIG(13) chaining to DNSKEY(13) with valid DS digest **Proof Mechanism:** * **Cryptographic proof required** - Complete DNSKEY(13) + DS + RRSIG(13) trust chain via CCIP-Read * **Trust Source:** DNS root KSK → TLD → SLD → zone DNSKEY(13) chain validated via DS digests and RRSIG(13) signatures * **Verification:** DnssecP256Verifier validates Algorithm 13 trust chain onchain using P-256 precompile **Example Flow:** ```mermaid sequenceDiagram participant C as Client participant UR as UniversalResolver participant DR as DnssecResolver participant GW as Gateway participant DNS as DNS Infrastructure participant DV as DnssecP256Verifier C->>+UR: resolve("_ens.example.com", textCallData) UR->>+DR: Route to DnssecResolver DR->>DR: Detect DNS name DR-->>-C: revert OffchainLookup(urls, callData, callback, extraData) C->>+GW: HTTP GET/POST callData GW->>GW: Decode DNS question GW->>+DNS: DNSSEC Algorithm 13-aware resolution DNS-->>-GW: Answer RRset + RRSIG(13)s + DNSKEY(13)s + DS chain GW->>GW: Canonicalize RRsets offchain GW-->>-C: Algorithm 13 ProofBundle via CCIP-Read C->>+DR: ccipCallback(proofBundle, extraData) DR->>DR: Decode Algorithm 13 ProofBundle DR->>+DV: verify(proofBundle, question) DV->>DV: Validate DNSKEY(13) + DS + RRSIG(13) trust chain DV-->>-DR: (valid: true, canonicalRRset) DR->>DR: Extract TXT record DR-->>-UR: Return ENS resolution result UR-->>-C: Final resolution ``` #### 4.4. Proof Formats :::info **Research Structure**: This JSON represents a conceptual DNSSEC proof bundle format for educational purposes. Actual ENS DNSSEC implementations may use different data structures optimized for onchain verification. ::: **DNSSEC Algorithm 13 Proof Bundle:** ```json { "question": { "qname": "bytes", "qtype": "uint16" }, "answerRRset": "RR[]", "canonicalRRsetBytes": "bytes", "answerRRSIGs": "RRSIG[]", // Must use algorithm 13 "zoneDNSKEYs": "ZoneDNSKEY[]", // Must specify algorithm 13 "delegationDS": "DelegationDS[]", // Must reference algorithm 13 keys "timestamps": { "queryTime": "uint256", "validFrom": "uint256", "validUntil": "uint256" } } ``` **Validation:** DNSKEY(13) + DS + RRSIG(13) trust chain validation with offchain canonicalization for gas optimization. All signatures must use Algorithm 13, all keys must specify algorithm 13. #### 4.5. Algorithms ##### 4.5.1. DNSSEC Signature Verification (Algorithm 13) * **Input:** canonicalRRsetBytes, RRSIG, DNSKEY * **Process:** Hash canonical bytes → verify P-256 signature → check validity windows * **Output:** bool valid ##### 4.5.2. Chain of Trust Validation * **Input:** ProofBundle, rootAnchors * **Process:** Validate root DNSKEY → DS chain → DNSKEY RRSIGs → answer RRSIG * **Output:** (bool valid, bytes canonicalRRset) #### 4.6. Proof Generation & Validation Flow ##### 4.6.1. Gateway Proof Generation 1. **Decode DNS question** from CCIP-Read callData 2. **Fetch DNSSEC chain** from DNS infrastructure 3. **Canonicalize RRsets** offchain (gas optimization) 4. **Build ProofBundle** with canonical bytes ##### 4.6.2. Onchain Validation (DnssecP256Verifier) 1. **Validate root DNSKEY** matches stored trust anchor 2. **Verify DS chain** (root → TLD → SLD → zone) 3. **Verify DNSKEY RRSIGs** using parent zone keys 4. **Verify answer RRSIG** using zone DNSKEY 5. **Check validity windows** and QNAME/QTYPE matching #### 4.7. Comparison to Other Proof Systems | Aspect | DNSSEC P-256 | EVM State | WebAuthn | | ---------------- | --------------------------- | ------------- | ------------------------ | | **Proof Type** | DNSSEC Signature Chain | Onchain state | Cryptographic signatures | | **Primitive** | P-256 ECDSA + SHA-256 | EVM state | P-256 ECDSA | | **Verification** | P-256 precompile + SHA-256 | Consensus | P-256 precompile | | **Trust Anchor** | DNS Root KSK | Ethereum L1 | FIDO metadata | | **Proof Format** | DNSSEC ProofBundle | Storage reads | WebAuthn assertion | | **Gas Cost** | Medium-High (\~25k-38k gas) | Low | Low | **Key Difference:** DNSSEC provides cryptographic proof of DNS record authenticity, while EVM state proofs prove onchain data. DNSSEC requires complete chain validation from root trust anchors. ### 5. Rules & Lifecycle #### 5.1. High-Level Rules for DNSSEC Records To establish **verified DNS records** for ENS resolution using Algorithm 13: 1. **DNS zone must be DNSSEC-signed exclusively with Algorithm 13 (ECDSA-P256-SHA256)** * Zone operators must configure DNSSEC signing with P-256 keys only * All DNSKEY records in the trust chain must specify algorithm 13 * All DS records must reference algorithm 13 keys * All RRSIG records must use algorithm 13 signatures 2. **Any RRset is verifiable if its RRSIG uses Algorithm 13 and chains to DNSKEY(13) with valid DS digest** * TXT records for ENS attribution: `_ens.example.com TXT "ens_name=example.eth"` * RRset must be covered by valid RRSIG(13) signature * RRSIG(13) must chain to DNSKEY(13) via valid DS digest * Complete DNSKEY(13) + DS + RRSIG(13) trust chain must exist from zone to root 3. **DNSSEC Algorithm 13 proof chain must validate onchain** * CCIP-Read fetches proof bundle containing DNSKEY(13), DS, and RRSIG(13) records * DnssecP256Verifier validates complete Algorithm 13 trust chain onchain * All cryptographic checks must pass using P-256 precompile (EIP-7951) **Lifecycle operations:** * **Register:** DNS zone owner creates DNSSEC-signed records. No onchain registration required. * **Update:** Zone owner updates records or DNSSEC keys. New proof required for resolution. * **Remove:** Zone owner removes records or disables DNSSEC. Resolution fails verification. * **Expiry:** RRSIG signatures expire. Zone operators must re-sign before expiry. #### 5.2. Profile A: Algorithm 13-Specific Rules (Gasless DNSSEC Resolution) * **Algorithm exclusivity:** Only Algorithm 13 (ECDSA-P256-SHA256) supported; all other algorithms rejected * **DNSKEY requirements:** DNSKEY records must specify algorithm 13 and contain P-256 public keys * **RRSIG requirements:** RRSIG records must use algorithm 13 and cover RRsets with valid P-256 signatures * **Signature validity windows:** RRSIG(13) signatures must be valid at verification time (inception ≤ now ≤ expiration) * **Key rollover handling:** Zone operators can rollover DNSKEY(13) following DNSSEC procedures with proper double-signing * **Trust anchor:** Pinned zone KSK serves as trust anchor (no DS chain verification required) * **QNAME/QTYPE matching:** RRSIG(13) must exactly match original DNS question (QNAME and QTYPE) #### 5.3. Profile B: Hybrid Algorithm Rules (Onchain DNS Import / DNS Claiming) * **Algorithm support:** Both Algorithm 8 (RSA/SHA-256) and Algorithm 13 (ECDSA-P256-SHA256) supported * **DNSKEY requirements:** DNSKEY records can specify algorithm 8 or 13; P-256 keys for algorithm 13, RSA keys for algorithm 8 * **DS requirements:** DS records must reference supported algorithm keys and use valid digest algorithms (SHA-256 preferred) * **RRSIG requirements:** RRSIG records can use algorithm 8 or 13; signatures verified accordingly * **Signature validity windows:** RRSIG signatures must be valid at verification time (inception ≤ now ≤ expiration) * **Key rollover handling:** Zone operators can rollover DNSKEYs following DNSSEC procedures with proper double-signing * **Trust chain completeness:** Complete DNSKEY + DS + RRSIG delegation chain required from zone to IANA root trust anchor * **QNAME/QTYPE matching:** RRSIG must exactly match original DNS question (QNAME and QTYPE) #### 5.3. DNS Record Types | Record Type | Usage in ENS | Example | | ----------- | --------------------------- | --------------------------------------------- | | **TXT** | Primary for ENS attribution | `_ens.example.com TXT "ens_name=example.eth"` | | **A** | IPv4 addresses | `example.com A 192.0.2.1` | | **AAAA** | IPv6 addresses | `example.com AAAA 2001:db8::1` | | **CNAME** | DNS aliases | `www.example.com CNAME example.com` | ### 6. Verification Path #### 6.1. Generic DNSSEC Verification Given a DNS name and record type: **1. Query Phase** * Client calls UniversalResolver.resolve(dnsName, callData) * Universal Resolver routes to DnssecResolver * DnssecResolver triggers OffchainLookup with gateway URL **2. Proof Phase** * Client fetches ProofBundle from gateway * Gateway performs DNSSEC-aware resolution * Gateway canonicalizes RRsets offchain * Gateway returns ProofBundle via CCIP-Read **3. Verification Phase (Profile A Implementation)** * Client calls DnssecResolver.ccipCallback(proofBundle, extraData) * Resolver calls DnssecP256Verifier.verify() with Algorithm 13 proof bundle * Verifier validates pinned zone KSK trust anchor (Algorithm 13) * Verifier validates KSK → DNSKEY RRset signature (Algorithm 13) * Verifier validates ZSK → answer RRset signature (Algorithm 13) * Verifier ensures all signatures use Algorithm 13 and all keys specify algorithm 13 * Resolver extracts DNS record (TXT, A, etc.) and resolves ENS name if applicable * Returns final ENS resolution result **Profile A Trust Chain:** ``` Pinned Trust Anchor: eketc.co KSK (keyTag=2371, flags=257) ↓ signs eketc.co DNSKEY RRset (contains KSK + ZSK) ↓ contains authenticated ZSK (keyTag=34505, flags=256) ↓ signs _ens.eketc.co TXT RRset ↓ resolves to dnssec.eth (ENS name) ``` #### 6.1.1. Complete Verification Flow Example **The Verification Flow - How All Components Work Together:** 1. **Start with DNS Question:** `"_ens.example.com TXT"` (qname + qtype = 16) 2. **Get ProofBundle:** Contains RRsets, RRSIGs, DNSKEYs, DS records from gateway 3. **Check RRSIG:** Verify the TXT RRset is signed with Algorithm 13 (algorithm field = 13) 4. **Find DNSKEY:** Use RRSIG keyTag to locate the DNSKEY(13) that signed it 5. **Verify signature:** Use P-256 precompile to check the 64-byte ECDSA signature 6. **Check chain:** Follow DS records up to trusted TLD anchor (like .fr) 7. **Validate timestamps:** Ensure RRSIG inception ≤ now ≤ expiration 8. **Extract data:** Get verified TXT record content for ENS resolution **Example with eketc.co Working Implementation:** * **DNS Question:** `"_ens.eketc.co TXT"` (verifying ENS attribution) * **RRSIG Check:** Algorithm 13 signature covering TXT RRset (signed by ZSK) * **DNSKEY Match:** keyTag=34505 points to eketc.co ZSK with P-256 coordinates * **Trust Chain:** KSK (keyTag=2371) → DNSKEY RRset → ZSK (keyTag=34505) → TXT RRset * **Final Result:** Cryptographically verified `"ens_name=dnssec.eth"` This creates a complete chain of cryptographic trust from individual DNS records through the DNSKEY(13) + DS + RRSIG(13) trust chain up to TLD-level anchors! #### 6.2. Resolver / Gateway Topology **Gateway Responsibilities (Profile A - Gasless DNSSEC Resolution):** * Fetch DNSSEC Algorithm 13 records from DNS infrastructure (DNSKEY(13), RRSIG(13)) * **Canonicalize RRsets offchain** using RFC 4034 Section 6.1 (DNS name canonicalization) and Section 6.2 (RRset canonical ordering) for gas optimization * Build ProofBundle with zone DNSKEY(13) chain (pinned KSK trust anchor) * Ensure all signatures use Algorithm 13 and all keys specify algorithm 13 * Return proof bundles via CCIP-Read **Verifier Responsibilities (Profile A - Gasless DNSSEC Resolution):** * Validate pinned zone KSK matches stored trust anchor (Algorithm 13) * Verify KSK → DNSKEY RRset signature (Algorithm 13) * Verify ZSK → answer RRset signature (Algorithm 13) * Validate RRSIG(13) signatures using P-256 precompile (EIP-7951) * Check Algorithm 13 signature validity windows and QNAME/QTYPE matching * Reject any non-Algorithm 13 signatures or keys **Oracle Responsibilities (Profile B - Onchain DNS Import):** * Validate IANA root zone DNSKEY matches stored trust anchors (Algorithm 8 or 13) * Verify complete DNSKEY + DS + RRSIG delegation chain from root → TLD → SLD → zone * Validate DS digests match DNSKEY records at each level * Validate RRSIG signatures using P-256 precompile (EIP-7951) for Algorithm 13 or RSA verification for Algorithm 8 * Store verified DNS records on-chain permanently * Support both Algorithm 8 (RSA) and Algorithm 13 (P-256) verification **Registrar Responsibilities (Profile B - DNS Claiming):** * Accept DNSSEC proofs for DNS name claiming * Call Onchain DNS Import's DNSSECOracle for proof verification * Parse `_ens.` TXT records to extract owner address * Claim DNS names on ENS registry via setSubnodeOwner() * Set default resolver and address records **Resolver Responsibilities (Profile A - Gasless DNSSEC Resolution):** * Detect DNS names requiring offchain resolution * Trigger CCIP-Read via OffchainLookup * Receive proof bundles and call verifier * Extract DNS records and resolve ENS names internally * Return final resolution results #### 6.3. Minimal Client-Side Pseudocode ```typescript async function verifyDnssecRecord( dnsName: string, recordType: string, universalResolver: Contract ): Promise { try { const result = await universalResolver.resolve(encodeDNSName(dnsName), encodeResolverCall(recordType)); return { valid: true, ensRecord: result }; } catch (error) { if (!isOffchainLookupError(error)) throw error; const lookup: OffchainLookupError = parseOffchainLookupError(error); const proofBundle: ProofBundle = await fetchProofBundle(lookup.urls[0], lookup.callData); const result = await dnssecResolver.ccipCallback(encodeProofBundle(proofBundle), lookup.extraData); return decodeResult(result); } } ``` #### 6.4. Example Implementation Flow **Scenario:** Resolving `_ens.example.com` TXT record using DNSSEC Algorithm 13 to obtain ENS name mapping. **Steps:** 1. Client initiates resolution → UniversalResolver routes to DnssecResolver 2. DnssecResolver triggers OffchainLookup → Client fetches Algorithm 13 proof from gateway 3. Gateway returns ProofBundle with DNSKEY(13) + DS + RRSIG(13) trust chain → Client calls ccipCallback 4. DnssecP256Verifier validates Algorithm 13 trust chain using P-256 precompile → Resolver extracts TXT record 5. Resolver resolves `example.eth` internally → Returns Ethereum address ### 7. Contract & Namespace Inventory #### 7.1. Core Constants & Helpers * `ALGORITHM_13 = 13` - ECDSAP256SHA256 - The exclusive elliptic curve algorithm used by ENS for DNSSEC verification * `P256_PRECOMPILE_ADDRESS = 0x0100` - EIP-7951 precompile address for P-256 ECDSA verification * `DNS_RECORD_TYPE_TXT = 16` - TXT records used for ENS attribution #### 7.2. DNS Namespaces | Zone Level | Example | Notes | | ----------------- | ------------------ | --------------------------------- | | **Root** | `.` | DNS root zone, trust anchor | | **TLD** | `.com` | Top-level domain delegations | | **SLD** | `example.com` | Second-level domain (zone) | | **ENS subdomain** | `_ens.example.com` | DNS subdomain for ENS attribution | #### 7.3. Resolver Types ##### 7.3.1. DnssecResolver (Extended Resolver) Wildcard resolver implementing EIP-3668 (CCIP-Read) for DNSSEC-backed DNS names. Handles offchain lookup triggers, receives proof bundles, calls verifier for validation, and translates DNS records into ENS resolution results. Compatible with ENSIP-10 extended resolver interface patterns. ##### 7.3.2. DnssecP256Verifier (Trust Anchor Holder) Cryptographic verifier for DNSSEC P-256 proofs. Holds DNS root zone KSK trust anchors, validates complete DNSSEC chain of trust from root to answer records, and verifies RRSIG signatures using P-256 precompile. #### 7.4. Implementation Requirements Three distinct implementation patterns are demonstrated, each with different contract requirements: ##### 7.4.1. Pattern 1: CCIP-Read Resolver (Profile A - Gasless DNSSEC Resolution) **Implementation Components:** 1. **UniversalResolver** - Existing ENS infrastructure (no deployment needed) 2. **DnssecP256Verifier** - Holds pinned zone KSK trust anchors, performs P-256 verification 3. **DnssecResolver** - CCIP-Read enabled resolver for offchain proof fetching 4. **Gateway Server** - Offchain Node.js service that fetches DNSSEC proofs and canonicalizes RRsets **Sepolia Deployment:** * **DnssecP256Verifier**: `0x580F2Db4Da8E6D5c654aa604182D0dFD17D5766B` * **DnssecResolver**: `0x7233d88AF9ee1eC3833F6AF4f733c1C5c0587Da2` * **Gateway**: `https://gateway.eketc.co/ccip-read` **Working Example:** * DNS Name: `_ens.eketc.co` → ENS Name: `dnssec.eth` * Trust Anchor: `eketc.co` KSK (keyTag=2371, Algorithm 13) - Profile A **Gas Costs (Sepolia):** * DNSSEC Verification: \~311,588 gas (\< $0.01) * DNS-Verified Read: \~313,613 gas (\< $0.01) * User Gas Cost: **$0** (gateway pays) ##### 7.4.2. Pattern 2: Oracle-Based (Profile B - Onchain DNS Import) **Implementation Components:** 1. **DNSSECOracle** - Full DNSSEC oracle with IANA root trust anchors 2. **Algorithm Contracts** - P-256 (EIP-7951) and RSA verification implementations 3. **No Gateway Required** - Users submit proofs directly to oracle **Deployment:** See `Onchain DNS Import/` project README for deployment addresses **Gas Costs:** * Oracle Verification: \~94% savings for Algorithm 13 domains vs. Solidity * User pays gas for proof submission and storage ##### 7.4.3. Pattern 3: Registrar-Based (Profile B - DNS Claiming) **Implementation Components:** 1. **DNSRegistrar** - ENS registrar for claiming DNS names 2. **DNSClaimChecker** - Library for parsing `_ens` TXT records 3. **Dependency** - Uses Onchain DNS Import's DNSSECOracle for verification **Deployment:** See `dns-claiming/` project README for deployment instructions **Gas Costs:** * Claiming operation: User pays gas for registrar transaction * Verification handled by Onchain DNS Import oracle ##### 7.4.4. Mainnet Deployment Recommendations **L1 Deployment:** * Profile A: \~$9-30 per verification at 30-100 gwei (gateway pays) * Profile B: \~$9-30 per verification at 30-100 gwei (user pays) **Namechain Deployment (Recommended):** * **98-99% cost reduction** compared to L1 * Profile A: \~$0.09-0.30 per verification (gateway pays) * Profile B: \~$0.09-0.30 per verification (user pays) * EIP-7951 precompile available on Namechain * Seamless L2-to-L2 verification flows without L1 bridging ### 8. Gas Benchmarks #### 8.1. Core DNSSEC Operations | Operation | Gas Cost | Sepolia Cost | Mainnet Cost (30 gwei) | Mainnet Cost (100 gwei) | | -------------------------- | --------- | ------------ | ---------------------- | ----------------------- | | **DNSSEC Verification** | \~311,588 | \<$0.01 | \~$9.30 | \~$31.20 | | **DNS-Verified Read** | \~313,613 | \<$0.01 | \~$9.40 | \~$31.40 | | **On-chain Read** | \~3,124 | negligible | \~$0.09 | \~$0.31 | | **Single P-256 Signature** | \~3,000 | \<$0.01 | \~$0.09 | \~$0.30 | #### 8.2. Write Operations | Operation | Gas Cost | Sepolia Cost | Mainnet Cost (30 gwei) | Mainnet Cost (100 gwei) | | ------------------------ | --------- | ------------ | ---------------------- | ----------------------- | | `setText()` | \~50,959 | \<$0.01 | \~$1.50 | \~$5.10 | | `setAddr() (single)` | \~74,767 | \<$0.01 | \~$2.20 | \~$7.50 | | `setAddr() (multi-coin)` | \~75,227 | \<$0.01 | \~$2.30 | \~$7.50 | | `multicall() (2 ops)` | \~109,252 | \<$0.01 | \~$3.30 | \~$10.90 | **Note:** Sepolia testnet uses extremely low gas prices (\~0.001 gwei) making costs negligible for testing. Mainnet costs shown at typical and congested gas prices. #### 8.3. Cost Analysis **EIP-7951 Savings:** * Pure Solidity P-256: \~200,000 gas per signature * EIP-7951 P-256: \~3,000 gas per signature * **Savings:** \~98.5% reduction per signature **Total Verification Cost Breakdown:** * Proof bundle decoding: \~5,000 gas * Trust anchor validation: \~2,000 gas * Chain validation: \~10,000 gas * DNSKEY RRSIG verification: \~150,000 gas * Answer RRSIG verification: \~150,000 gas * Overhead: \~4,588 gas #### 8.4. Working Example Gas Usage **eketc.co → dnssec.eth resolution:** * DNSSEC verification: 311,588 gas * CCIP-Read callback: +2,025 gas * **Total:** 313,613 gas per DNS-verified read ### 9. Working Example: eketc.co → dnssec.eth #### 9.1. Setup Overview This working implementation demonstrates DNSSEC Algorithm 13 verification using Profile A trust model with a pinned zone KSK. **Components:** * DNS Domain: `eketc.co` (DNSSEC signed with Algorithm 13) * DNS Record: `_ens.eketc.co TXT "ens_name=dnssec.eth"` * ENS Name: `dnssec.eth` (Sepolia testnet) * Trust Anchor: `eketc.co` KSK (keyTag=2371, Algorithm 13) * Contracts: Deployed on Sepolia with working end-to-end resolution #### 9.2. DNSSEC Configuration **Zone Keys:** * KSK (Key Signing Key): flags=257, keyTag=2371, Algorithm 13 * ZSK (Zone Signing Key): flags=256, keyTag=34505, Algorithm 13 **DNS Records:** ```dns eketc.co. IN DNSKEY 257 3 13 [KSK public key in base64] eketc.co. IN DNSKEY 256 3 13 [ZSK public key in base64] _ens.eketc.co. IN TXT "ens_name=dnssec.eth" ``` #### 9.3. Trust Chain Verification ``` Trust Anchor: eketc.co KSK (pinned, not verified from root) ↓ signs DNSKEY RRset (authenticated by KSK signature) ↓ contains ZSK (authenticated, can sign zone data) ↓ signs TXT RRset ("ens_name=dnssec.eth") ↓ resolves to dnssec.eth ENS name ``` #### 9.4. Contract Integration **Verifier Contract (`0x580F2Db4Da8E6D5c654aa604182D0dFD17D5766B`):** * Stores pinned trust anchor (KSK public key) * Validates KSK → DNSKEY RRset signature * Validates ZSK → TXT RRset signature * Uses EIP-7951 precompile for P-256 verification **Resolver Contract (`0x7233d88AF9ee1eC3833F6AF4f733c1C5c0587Da2`):** * Implements CCIP-Read for offchain proof fetching * Links `_ens.eketc.co` to `dnssec.eth` * Validates proofs via verifier contract * Returns resolved ENS records #### 9.5. Resolution Flow 1. **Query:** `dnssec.eth` address resolution 2. **Delegation:** ENS registry points to DnssecResolver 3. **OffchainLookup:** Resolver triggers CCIP-Read for `_ens.eketc.co TXT` 4. **Gateway:** Fetches DNSSEC proof bundle from DNS infrastructure 5. **Verification:** Onchain validation using EIP-7951 precompile 6. **Resolution:** Returns verified ENS record #### 9.6. Testing **Command-line testing:** ```bash # Test DNS-verified read (Gasless DNSSEC Resolution) cd "Gasless DNSSEC Resolution" node scripts/test-dns-read.mjs # Test via ENS app # Visit: https://sepolia.app.ens.domains/dnssec.eth ``` **Gas measurements:** * Full DNS-verified resolution: \~313,613 gas * Cost on Sepolia: \<$0.01 (gateway pays) * Cost on mainnet: \~$9-30 (gateway pays) #### 9.7. Profile B Example: Onchain DNS Import Oracle The **Onchain DNS Import** project demonstrates full IANA root DS chain verification with complete oracle functionality. See `Onchain DNS Import/README.md` for: * Oracle deployment instructions * Trust anchor configuration (IANA root zone keys) * DNSSEC proof submission examples * Gas benchmarks showing \~94% savings for Algorithm 13 domains #### 9.8. Profile B Example: DNS Claiming with ENS Registrar The **DNS Claiming** project demonstrates claiming DNS names on ENS using DNSSEC proofs. See `dns-claiming/README.md` for: * DNSRegistrar deployment instructions * Name claiming workflow * Integration with Onchain DNS Import oracle * Limitations (works only for TLDs with ENS registrar permissions) **Note:** DNS Claiming is separated from Onchain DNS Import because it requires ENS registry permissions that may not be available for all TLDs (e.g., you cannot claim `.co` domains on ENS). ### 10. URM Mapping (Resolver Profiles) DNSSEC P-256 defines two resolver profiles for DNSSEC-backed resolution, corresponding to the two trust models demonstrated: | Profile | Scope | Trust Model | Proof System | Rules | Verification Path | | ------------------------- | -------------------------------------- | ---------------------------------------- | -------------------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | | **dnssec-p256-profile-a** | DNS zones with Algorithm 13 signatures | Profile A - pinned zone KSK trust anchor | `dnssec_proof(algorithm_13)` via CCIP-Read | Algorithm 13 exclusivity, signature validity windows | OffchainLookup → Gateway → Onchain KSK→DNSKEY→ZSK→RRset validation → ENS resolution | | **dnssec-p256-profile-b** | DNS zones with Algorithm 13 signatures | Profile B - IANA root DS chain | `dnssec_proof(algorithm_13)` via oracle submission | Algorithm 13 (or hybrid 8+13), full DS chain validation | User submission → Oracle → Root→TLD→SLD→Zone DS chain validation → On-chain storage | **Profile A Key Characteristics:** * **Algorithm**: Exclusive support for DNSSEC Algorithm 13 (ECDSA-P256-SHA256) * **Working Implementation**: `Gasless DNSSEC Resolution/` - `eketc.co` zone with pinned KSK trust anchor * **Security**: Cryptographic proof anchored in zone KSK (Profile A) * **Deployment**: Sepolia testnet with EIP-7951 precompile * **Gas Model**: Users pay no gas; gateway pays for verification **Profile B Key Characteristics:** * **Algorithm**: Support for Algorithm 8 (RSA) and Algorithm 13 (ECDSA-P256-SHA256) * **Working Implementation**: `Onchain DNS Import/` - Full IANA root DS chain oracle * **Security**: Cryptographic proof anchored in IANA root zone (Profile B) * **Deployment**: Sepolia testnet with EIP-7951 precompile * **Gas Model**: Users pay gas for proof submission and on-chain storage * **ENS Compatibility**: Matches ENS's DNSSEC oracle approach ### 11. Edge Cases & Client Requirements #### 11.1. Algorithm 13 Exclusivity Profile A (Gasless DNSSEC Resolution) exclusively supports DNSSEC Algorithm 13. Profile B (Onchain DNS Import) supports both Algorithm 8 (RSA) and Algorithm 13 (P-256) for hybrid verification. RRSIGs with unsupported algorithms are rejected. DNSKEY records must specify supported algorithms, DS records must reference supported algorithm keys. #### 11.2. Signature Expiry RRSIG signatures have inception/expiration timestamps. Expired signatures are rejected with no grace periods. Zone operators must re-sign records before expiry. #### 11.3. DNSSEC Failures Unsigned zones and zones with unsupported algorithms cannot be verified. Verification failures can occur at multiple points—clients must handle failures gracefully and not accept invalid results. #### 11.4. Client Requirements Clients **must**: * Handle OffchainLookup errors correctly and fetch proof bundles from gateways * Call resolver callback with proof bundles without skipping verification * Handle verification failures by treating them as resolution failures * Respect signature validity and not cache proof bundles across different queries Clients **must not**: * Trust gateway responses without onchain verification * Skip verification steps or bypass the verifier contract * Cache proof bundles for different queries * Assume all DNS zones are DNSSEC-signed with Algorithm 13 * Attempt to use non-Algorithm 13 DNSSEC signatures *** ### References #### Standards & Specifications **DNSSEC Standards:** * **[RFC 4034](https://www.rfc-editor.org/rfc/rfc4034)** — Resource Records for the DNS Security Extensions * **[RFC 4035](https://www.rfc-editor.org/rfc/rfc4035)** — Protocol Modifications for the DNS Security Extensions * **[RFC 4648](https://www.rfc-editor.org/rfc/rfc4648)** — The Base16, Base32, and Base64 Data Encodings **Ethereum Improvement Proposals:** * **[EIP-7951](https://eips.ethereum.org/EIPS/eip-7951)** — Precompile for secp256r1 Curve Support (P-256 precompile) * **[EIP-3668](https://eips.ethereum.org/EIPS/eip-3668)** — CCIP-Read: Secure offchain data retrieval **ENS Improvement Proposals:** * **[ENSIP-10](https://docs.ens.domains/ensip/10)** — Wildcard Resolution (Extended Resolver interface) * **[ENSIP-19](https://docs.ens.domains/ensip/19)** — Reverse Resolution (URM template structure) #### Infrastructure & Governance **DNS Infrastructure:** * **[IANA](https://www.iana.org)** — Internet Assigned Numbers Authority (DNS root zone management) * **[DNS Root Zone](https://www.iana.org/domains/root/db)** — Authoritative root of the DNS hierarchy **Ethereum Network:** * **Fusaka Upgrade** — Ethereum network upgrade including P-256 precompile * **Namechain** — ENS Layer 2 solution (EIP-7951 available) *** ### Appendix A: DNSSEC Record Structures #### A.1 DNSKEY(13) Record DNSKEY records contain Algorithm 13 public keys used to verify RRSIG(13) signatures. For Algorithm 13 (ECDSA-P256-SHA256), the public key is a P-256 elliptic curve point. ENS requires all DNSKEY records in the trust chain to specify algorithm 13. **Structure (Solidity/ABI):** ```solidity struct DNSKEY { uint16 flags; // Key flags (bit 7 = ZSK, bit 15 = KSK) uint8 protocol; // Protocol field (always 3 for DNSSEC) uint8 algorithm; // Algorithm identifier (13 for ECDSAP256SHA256) bytes publicKey; // Public key (64 bytes for P-256: x + y coordinates) } ``` #### A.2 DS Record DS records contain cryptographic digests of child zone DNSKEY(13) records, proving delegation from parent to child zone. DS records must reference Algorithm 13 keys and use valid digest algorithms (SHA-256 preferred) to maintain the Algorithm 13 trust chain. **Structure (Solidity/ABI):** ```solidity struct DS { uint16 keyTag; // Key tag of the DNSKEY being referenced uint8 algorithm; // Algorithm of the DNSKEY (13 for P-256) uint8 digestType; // Digest algorithm (2 for SHA-256) bytes digest; // Digest of the DNSKEY (32 bytes for SHA-256) } ``` #### A.3 RRSIG(13) Record RRSIG records contain Algorithm 13 cryptographic signatures covering RRsets, proving authenticity and integrity. All RRSIG records in ENS DNSSEC verification must use algorithm 13 and contain valid P-256 signatures over canonicalized RRsets. **Structure (Solidity/ABI):** ```solidity struct RRSIG { uint16 typeCovered; // RR type covered by this signature uint8 algorithm; // Algorithm used (must be 13 for ECDSAP256SHA256) uint8 labels; // Number of labels in original owner name uint32 originalTTL; // TTL of the RRset when signed uint32 expiration; // Signature expiration timestamp uint32 inception; // Signature inception timestamp uint16 keyTag; // Key tag of DNSKEY used to sign bytes signerName; // Name of signer (zone name in DNS wire format) bytes signature; // Algorithm 13 signature bytes (64 bytes for P-256: r + s) } ``` *** ### Appendix B: Trust Anchor Data #### B.1 Profile A: Pinned Zone KSK Trust Anchors **Trust Model Choice:** Our implementation uses **Profile A (pinned zone KSK trust model)** rather than IANA root trust. This design choice was made because: 1. **DNS root zone uses RSA (Algorithm 8)**, not ECDSA-P256 (Algorithm 13) 2. **Most TLDs use RSA**, not Algorithm 13 (only `.fr` is known to use Algorithm 13) 3. **PoC Focus**: We wanted to demonstrate complete Algorithm 13 verification as a proof-of-concept 4. **Practical Deployment**: Profile A allows immediate deployment without waiting for root/TLD adoption of Algorithm 13 **Contract Storage Format:** ```solidity struct PinnedAnchor { uint256 publicKeyX; // 32-byte x-coordinate of P-256 public key uint256 publicKeyY; // 32-byte y-coordinate of P-256 public key uint16 keyTag; // DNS key tag bytes zoneName; // Zone name in DNS wire format } mapping(bytes => PinnedAnchor) public pinnedAnchors; // zoneName => anchor ``` #### B.2 Working Implementation: eketc.co Zone Trust Anchor **Zone:** `eketc.co` (DNSSEC signed with Algorithm 13) **Trust Anchor:** KSK (Key Signing Key, flags=257, keyTag=2371) **Algorithm:** 13 (ECDSA-P256-SHA256) **Purpose:** Demonstrates complete Algorithm 13 verification chain ##### DNSKEY Records ```bash # Query eketc.co DNSKEY records dig DNSKEY eketc.co @1.1.1.1 # Output showing Algorithm 13 keys: ;; ANSWER SECTION: eketc.co. 3600 IN DNSKEY 257 3 13 [KSK base64 data] eketc.co. 3600 IN DNSKEY 256 3 13 [ZSK base64 data] ``` ##### KSK Details (Trust Anchor) * **Flags**: 257 (Key Signing Key) * **Protocol**: 3 (DNSSEC) * **Algorithm**: 13 (ECDSA-P256-SHA256) * **Key Tag**: 2371 (computed per RFC 4034 Appendix B) * **Public Key**: 64 bytes (P-256 elliptic curve point) **KSK Public Key Coordinates:** * X: `0x99db2cc14cabdc33d6d77da63a2f15f71112584f234e8d1dc428e39e8a4a97e1` * Y: `0xaa271a555dc90701e17e2a4c4b6f120b7c32d44f4ac02bd894cf2d4be7778a19` ##### ZSK Details (Zone Signing) * **Flags**: 256 (Zone Signing Key) * **Protocol**: 3 (DNSSEC) * **Algorithm**: 13 (ECDSA-P256-SHA256) * **Key Tag**: 34505 (computed) * **Purpose**: Signs zone records (TXT, A, etc.) ##### Trust Chain ``` Pinned Trust Anchor: eketc.co KSK (keyTag=2371) ↓ signs (Algorithm 13) eketc.co DNSKEY RRset ↓ contains authenticated eketc.co ZSK (keyTag=34505) ↓ signs (Algorithm 13) _ens.eketc.co TXT RRset ↓ resolves to dnssec.eth ENS name ``` #### B.3 Contract Storage Implementation **Sepolia Deployment:** * **Verifier Contract**: `0x580F2Db4Da8E6D5c654aa604182D0dFD17D5766B` * **Pinned Anchor**: `eketc.co` KSK stored via `setPinnedTrustAnchor()` ```solidity // In DnssecP256Verifier contract function setPinnedTrustAnchor( bytes memory zoneName, uint256 pubX, uint256 pubY, uint16 keyTag ) external onlyOwner { pinnedAnchors[zoneName] = PinnedAnchor({ publicKeyX: pubX, publicKeyY: pubY, keyTag: keyTag, zoneName: zoneName }); } // Usage in deployment script verifier.setPinnedTrustAnchor( hex"05656b65746302636f00", // eketc.co in wire format uint256(0x99db2cc14cabdc33d6d77da63a2f15f71112584f234e8d1dc428e39e8a4a97e1), // pubX uint256(0xaa271a555dc90701e17e2a4c4b6f120b7c32d44f4ac02bd894cf2d4be7778a19), // pubY 2371 // keyTag ); ``` #### B.4 Profile B: IANA Root DS Chain Trust Anchors **Trust Model:** **Profile B (IANA root DS chain trust model)** is used by the **Onchain DNS Import** and **DNS Claiming** projects. This approach provides: 1. **Complete Trust Chain**: Full DS chain validation from IANA root zone to target domain 2. **ENS Compatibility**: Matches ENS's DNSSEC oracle approach for maximum compatibility 3. **Maximum Security**: Complete trust chain validation provides strongest security guarantees 4. **Hybrid Support**: Supports both Algorithm 8 (RSA) and Algorithm 13 (P-256) for root/TLD zones 5. **On-chain Storage**: Stores verified DNS records on-chain permanently **Implementation:** `Onchain DNS Import/contracts/DNSSECOracle.sol` #### B.5 Why Both Profile A and Profile B 1. **Algorithm Adoption**: DNS root and most TLDs use RSA (Algorithm 8), not P-256 (Algorithm 13) 2. **Profile A Benefits**: Immediate deployment, gasless resolution, proof-of-concept demonstration 3. **Profile B Benefits**: Complete trust chain, ENS compatibility, maximum security 4. **Deployment Flexibility**: Different use cases benefit from different trust models 5. **Future-Proof**: Profile B can fully support IANA root trust once Algorithm 13 adoption increases #### B.5 Future: IANA Root Trust Support **Current State:** DNS root zone uses RSA (Algorithm 8) **Future State:** When root zone adopts Algorithm 13, the verifier can be extended to support: * IANA root KSK trust anchors * Full DNSKEY(13) + DS + RRSIG(13) delegation chains * TLD-level Algorithm 13 trust anchors (.fr, etc.) ##### Sepolia Deployment (Working Implementation) **Deployed Contracts:** * **DnssecP256Verifier**: `0x580F2Db4Da8E6D5c654aa604182D0dFD17D5766B` * **DnssecResolver**: `0x7233d88AF9ee1eC3833F6AF4f733c1C5c0587Da2` **Working Example:** * DNS Name: `_ens.eketc.co` → ENS Name: `dnssec.eth` * Trust Anchor: `eketc.co` KSK (keyTag=2371, Algorithm 13) * Gateway: `https://gateway.eketc.co/ccip-read` **Deployment Verification:** ```bash # Test DNS-verified read node scripts/test-dns-read.mjs # Test via ENS app # Visit: https://sepolia.app.ens.domains/dnssec.eth ``` **Gas Costs (Sepolia):** * DNSSEC Verification: \~311,588 gas (\< $0.01) * DNS-Verified Read: \~313,613 gas (\< $0.01) * On-chain Read: \~3,124 gas (negligible) #### B.2 Key Rollover History DNS root zone KSK rollovers occur periodically for security. Contract trust anchors must be updated via governance during rollovers. | Date | Event | Key Tag | Algorithm | Notes | | ---------- | ------------------------- | ------- | -------------------- | ----------------------------- | | 2010-07-15 | Initial root KSK rollover | 19036 | 5 (RSASHA1) | First rollover | | 2017-10-11 | Root KSK rollover | 20326 | 8 (RSASHA256) | Second rollover | | TBD | Future P-256 KSK | TBD | 13 (ECDSAP256SHA256) | When IANA adopts Algorithm 13 | **Important:** DNS root zone currently does not support Algorithm 13. Full P-256 DNSSEC verification requires either hybrid verification approaches or trust anchors at TLD level until root adopts P-256. *** :::warning **Educational Content**: This document presents hypothetical ENSIP-19 integration concepts for research purposes only. ::: ### 1. Introduction #### 1.1. Document Purpose This document provides a comprehensive technical reference for implementing [ENSIP-19](https://docs.ens.domains/ensip/19/) (Multichain Primary Names) as a profile in the Universal Resolver Matrix (URM), expanding its normative specification with detailed implementation guidance for EVM ecosystems. #### 1.2. Universal Resolver Matrix Framework The Universal Resolver Matrix (URM) is a systematic framework for mapping resolution pathways across namespaces using four core dimensions: 1. **Trust Model**, 2. **Proof System**, 3. **Rules & Lifecycle**, and 4. **Verification Path**. This document structures ENSIP-19 implementation around these dimensions, with additional sections covering implementation details: * Contract & Namespace Inventory * URM Mapping (Resolver Profiles) * Edge Cases & Client Requirements ### 2. Scope & Goals #### 2.1. ENSIP-19 Overview ENSIP-19 standardizes reverse and primary name resolution for all coin types in EVM based ecosystems. It extends ENSIP-3's Ethereum-only reverse resolution via a two-phase verification (reverse then forward), enabling Ethereum-wide default names and chain-specific primary names for consistent identity resolution across EVM chains. #### 2.2. Coin Type Resolution Scope ENSIP-19 provides a framework for resolving addresses using any coin type from SLIP-44 (via ENSIP-9), but support levels differ by chain type. EVM chains (mainnet coinType=60, rollups via ENSIP-11) support full bidirectional resolution. Non-EVM chains (Bitcoin, Cosmos, Solana) support forward resolution only. #### 2.3. EVM vs Non-EVM Chains (Bidirectional vs Forward-only) EVM chains enable bidirectional resolution: forward (name → address via `addr(node, coinType)`) and reverse (address → name via primary names). Non-EVM chains support forward resolution only; reverse resolution requires infrastructure (chain-specific registrars, state bridges, verifier contracts) that non-EVM chains currently lack. Each profile prioritizes both forward and reverse resolution. Chain-specific registrars are required (not optional) for rollups to achieve full chain-specific primary name functionality. Without chain-specific registrars, rollups must fall back to default primary names, which reduces the ability to have chain-specific identity semantics. #### 2.4. Non-EVM Resolver Profile & Future Framework Non-EVM chains cannot have primary names via ENSIP-19 without additional infrastructure: chain-specific registrar contracts, state commitment bridges to L1, and verifier contracts. See [Profile 3 (Section 8.3)](#83-profile-3-non-evm--legacy-coin-types-ensip19-non-evm) for current limitations and the Non-EVM Resolver Profile for detailed implementation strategies addressing these gaps. #### 2.5. Deployment Architecture: Hybrid L1 + Namechain ENSIP-19 uses a hybrid deployment architecture balancing trustlessness, cost-effectiveness, and ENSv2 integration. The system requires exactly 7 core contracts deployed across L1 and L2 chains. ##### 2.5.1. Architecture Overview ENS infrastructure migrates to Namechain (ENS L2) as part of ENSv2, with critical infrastructure remaining on L1: | Deployment Location | Component | Purpose | | ---------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Ethereum L1** | ENS Registry | Canonical root of trust for all ENS name registrations and resolver assignments | | **Namechain (ENS L2)** | Default Registrar | Maintains address → name mappings for default primary names across all EVM chains | | **Namechain (ENS L2)** | Default Resolver | Wildcard resolver intercepting `default.reverse` and `[coinTypeAsHex].reverse` lookups | | **Ethereum L1** | Bridge Verifiers | Validates L2 state proofs against L1-committed state roots for rollups | | **Ethereum L1** | Universal Resolver | Routes resolution requests to appropriate namespace-specific resolvers | | **Namechain (ENS L2)** | Chain-Specific Resolvers | Cost-effective resolvers per chain that verify registrar lookups via CCIP-Read; validate pre-settled TEE proofs natively in callbacks, fallback to L1 DA + settled ZK proofs | | **L2 Chains** | Chain-Specific Registrars | Per-chain registrars maintaining address → name mappings for chain-specific primary names | ##### 2.5.2. Contract Inventory (7-Contract Pattern) **Existing Infrastructure (5 Contracts - No Deployment Needed):** 1. **ENS Registry (L1)** - Central registry mapping names to resolvers (`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`) 2. **Default Registrar (Namechain)** - Handles default EVM primary name mappings 3. **Default Resolver (Namechain)** - Resolves default and legacy reverse names 4. **Bridge Verifiers (L1)** - Validates L2 state proofs (deployed by rollup infrastructure teams) 5. **Universal Resolver (L1)** - Resolution entry point (optional but recommended) **New Contracts per Chain (2 Contracts - Must Deploy):** 6. **Chain Registrar (L2)** - Stores address → name mappings on target chain 7. **Chain Resolver (Namechain)** - Handles chain-specific resolution with CCIP-Read validation; validates pre-settled TEE proofs natively in callbacks, fallback to L1 DA + settled ZK proofs ##### 2.5.3. Roots of Trust & Security The canonical root of trust consists of: * **ENS Registry on Ethereum L1**: Primary source of truth for all name registrations and resolver assignments * **Registrar contracts per chain**: Default registrar on Namechain for fallback, chain-specific registrars on L2 chains * **Bridge verifiers on L1**: Validate rollup state proofs against L1-committed state roots Security is inherited from Ethereum consensus and contract immutability. Primary names are valid only if reverse and forward mappings agree. ##### 2.5.4. Benefits & Tradeoffs This hybrid architecture enables: * **Cost-effectiveness**: Chain-specific resolvers and verifiers deployed on Namechain benefit from significantly lower gas costs * **Trustlessness**: Core ENS infrastructure (Registry) remains on L1 for maximum security, while chain-specific verifiers on Namechain validate L2 state proofs * **Scalability**: **Dual-path proof validation enables flexible latency/security tradeoffs:** * **Fast path:** Namechain resolvers validate pre-settled TEE proofs natively in CCIP callbacks * **Secure path:** Fallback to L1 DA + settled ZK proofs via cross-chain calls to bridge verifiers * **ENSv2 integration**: Migration path for existing infrastructure ##### 2.5.5. Governance Requirements **DAO Approval: When Is It Needed?** While not strictly required for basic resolver operation, a production-ready ENSIP-19 resolver should go through ENS DAO governance for integration into core ENS infrastructure. DAO approval is required if you want your resolver to be: * Set as the default resolver for ENS (e.g., for the reverse registrar or for .eth names) * Integrated at the protocol level (e.g., replacing the public resolver, or being referenced in official ENS contracts) **Why Governance Matters:** DAO approval is a matter of governance and trust. If you want your resolver to be widely used, trusted, or set as a default in ENS infrastructure, DAO approval: * Signals community trust and security review * Ensures the resolver is maintained and meets ENS standards * Protects against unauthorized trust anchor modifications during key rollovers **ENSIP-19 Specific Considerations:** For ENSIP-19 implementations, governance approval ensures proper integration with the existing ENS ecosystem and maintains backward compatibility with ENSIP-3 reverse resolution while extending functionality to multichain environments. ### 3. Trust Model #### 3.1. Roots of Trust The canonical root of trust for ENSIP-19 establishes the cryptographic foundation for multichain primary names through this hierarchical trust model: 1. **ENS Registry (L1)** - Immutable source of truth for all name registrations and resolver assignments, secured by Ethereum consensus 2. **Registrar Contracts (per chain)** - Maintain address-to-name mappings with chain-specific semantics and lifecycle management 3. **Bridge Verifiers (L1)** - Cryptographically validate rollup state proofs against L1-committed state roots for cross-chain consistency 4. **Universal Resolver (L1)** - Routes resolution requests to appropriate resolvers while maintaining backward compatibility This architecture enables primary names that work seamlessly across EVM ecosystems while preserving ENS's decentralized security guarantees. #### 3.2. Multichain Ethereum Assumptions ENSIP-19 targets the "multichain Ethereum" environment where EVM-compatible chains are the primary scaling solution for Ethereum. Many addresses are chain-agnostic (EOAs, deterministic deployments), while smart contract accounts and alternative derivation schemes mean an identity may have multiple addresses across chains. #### 3.3. Security Guarantees Security is inherited from Ethereum consensus and the immutability of deployed contracts. New registrars are registry-independent and intentionally minimal: they operate separately from the ENS Registry and only store the reverse mapping (address → name), reducing attack surface through limited functionality. A primary name is valid only if reverse and forward mappings agree: the reverse record (address → name) must match forward resolution (name → address for the relevant coinType). #### 3.4. Trust Assumptions by Chain Class ##### 3.4.1. L1 Ethereum Users trust Ethereum L1 and ENS contracts. The ENS Registry on L1 serves as the canonical root of trust for all name registrations and resolver assignments, with security inherited from Ethereum consensus and contract immutability. ##### 3.4.2. Rollups Posting State to L1 Users trust the rollup's security model and the correctness of its state commitment to L1. Rollups can have chain-specific primary names via their own registrars, with trustless verification enabled through state commitment bridges that post state roots to L1, allowing bridge verifiers to validate L2 state proofs. ##### 3.4.3. Non-rollup EVM Chains (Default Name/Address Semantics) Non-rollup EVM chains (e.g., BSC, Polygon, Avalanche C-Chain) lack state commitment bridges to L1, making trustless verification impossible. They rely entirely on L1 infrastructure: no chain-specific registrars exist, so all addresses resolve to the shared default primary name via `default.reverse`. For forward resolution, if no chain-specific address is set, resolvers fall back to `addr(node, 0x8000_0000)` (default EVM address). Unlike rollups, these chains cannot have chain-specific primary names. ### 4. Proof System #### 4.1. Inputs & Core Definitions ENSIP-19's resolution process takes an address and coin type as inputs, converts them to hex-encoded strings for namespace construction, and uses a mapping function to determine which EVM chain (if any) the coin type represents. The system relies on ENSIP-10's `resolve()` function (with [CCIP-Read](https://eips.ethereum.org/EIPS/eip-3668) support) to perform the actual resolution queries against onchain state. All inputs are derived from ENSIP-9 (coin types) and ENSIP-11 (chain ID mapping), ensuring compatibility with the broader multichain ENS ecosystem. **Core Definitions:** * **`addressBytes`**: Target address as bytes (ENSIP-9 address encoding). * **`[addressAsHex]`**: Prefix-free lowercase hex representation of `addressBytes`. Example: `0x0000ABcD` → `"0000abcd"`. * **`coinType`**: Coin type per ENSIP-9. `coinType = 60` corresponds to Ethereum mainnet (L1 testnets may also use this). EVM `chainId` can be mapped to a `coinType` via ENSIP-11. * **`[coinTypeAsHex]`**: Prefix-free lowercase hex representation of `coinType` (no leading zeros). Equivalent to `BigInt(coinType).toString(16)` in JavaScript. Example: `4095` → `"fff"`. * **`chainFromCoinType(coinType)`**: Mapping function that converts coin type to chain ID: ```text if coinType = 60: return 1 if 0x8000_0000 ≤ coinType ≤ 0xffff_ffff: return coinType ^ 0x8000_0000 else: return 0 ``` Example mappings: | Network | `coinType` | `chainFromCoinType()` | EVM | | -------- | ------------- | --------------------- | --- | | Default | `0x8000_0000` | `0` | ✓ | | Ethereum | `60` | `1` | ✓ | | Chain(2) | `0x8000_0002` | `2` | ✓ | | Bitcoin | `0` | `0` | | * **`resolve(name, data)`**: An ENSIP-10 implementation (CCIP-Read capable). **Example Usage:** For reverse resolution (address → name), given `addressBytes = 0x1234...abcd` and `coinType = 60`: * Construct `reverseName = "1234...abcd.addr.reverse"` * Compute `reverseNode = namehash(reverseName)` * Call `resolve(reverseName, abi.encodeCall(INameResolver.name, (reverseNode)))` * Returns the primary name (e.g., `"alice.eth"`) For forward resolution (name → address), given `name = "alice.eth"` and `coinType = 60`: * Compute `node = namehash("alice.eth")` * Call `resolve("alice.eth", abi.encodeCall(IAddrResolver.addr, (node)))` * Returns the resolved address For forward resolution with non-mainnet coin type (e.g., `coinType = 0x8000000a` for Optimism): * Call `resolve("alice.eth", abi.encodeCall(IAddressResolver.addr, (node, coinType)))` * Returns the chain-specific address or falls back to default address #### 4.2. Cryptographic Primitives ##### 4.2.1. ECDSA (secp256k1) ECDSA (secp256k1) is used for account control, authorizing transactions that set or update primary name mappings. Account owners sign transactions to register or update their primary names. Verification uses the native Ethereum `ecrecover` precompile (no additional cost). This primitive is used for all EVM chains (L1 and L2s). ##### 4.2.2. Namehash Namehash creates deterministic, collision-resistant node identifiers from names using the recursive namehash function (ENSIP-1). It converts reverse names like `"[addressAsHex].[coinTypeAsHex].reverse"` into nodes. Key properties include: one-way function, no collisions, and deterministic behavior across all implementations. ##### 4.2.3. Merkle / Storage Proofs for L2 Merkle and storage proofs prove L2 registrar/resolver state to L1 verifier contracts for rollups. Proof types vary by rollup architecture: optimistic rollups use fraud proofs (challenge-response) or state root commitments; ZK rollups use SNARK/STARK proofs of state transitions; Validium uses similar ZK proofs but with offchain data availability. Bridge verifier contracts on L1 validate proofs against L1 state roots. This primitive applies only to rollups that post state to L1 and have wildcard resolvers. #### 4.3. Proof Types (URM Terms) ##### 4.3.1. onchain\_state(L1) **When Used:** * Default registrar on L1 ("default.reverse") * Ethereum mainnet registrar (implicit, via ENSIP-3 reverse resolution) * Any resolution that reads directly from L1 contracts **Proof Mechanism:** * No explicit proof needed — state is directly accessible via EVM calls * Trust Source: Ethereum L1 consensus * Verification: Standard EVM execution validates state reads * Gas Cost: Standard storage read costs (\~2100 gas per SLOAD) **Example Flow:** ```mermaid flowchart TD Start([Client initiates resolution]) --> BuildName["Build reverseName from addressBytes + coinType"] BuildName --> ComputeNode["Compute reverseNode = namehash(reverseName)"] ComputeNode --> CallUR["Client calls UniversalResolver.resolve(reverseName, nameCallData)"] CallUR --> URRoute{"Universal Resolver routes to resolver"} URRoute -->|Query ENS Registry| ENSReg["ENS Registry looks up resolver for reverseNode"] ENSReg --> GetResolver["Get resolver address from registry"] GetResolver --> CallResolver["Call resolver.resolve(reverseName, nameCallData)"] CallResolver --> DecodeData["Resolver decodes nameCallData to extract function selector + params"] DecodeData --> DetermineRegistrar{"Determine which registrar based on coinType"} DetermineRegistrar -->|coinType = 60| MainnetReg["Use mainnet registrar addr.reverse namespace"] DetermineRegistrar -->|coinType = 0x8000_0000| DefaultReg["Use default registrar default.reverse namespace"] MainnetReg --> ReadStorage1["Read registrar.name(address) from contract storage via SLOAD"] DefaultReg --> ReadStorage2["Read default registrar.name(address) from contract storage via SLOAD"] ReadStorage1 --> CheckResult{"Name exists? name != empty"} ReadStorage2 --> CheckResult CheckResult -->|Yes: name found| ReturnName["Return name ABI-encoded bytes"] CheckResult -->|No: name empty| ReturnEmpty["Return empty bytes array"] ReturnName --> ForwardPhase["Continue to forward verification phase §4"] ReturnEmpty --> StopEmpty["Stop resolution - Display address"] ForwardPhase --> VerifyForward["Verify forward mapping: resolve(name) → address, compare to input addressBytes"] VerifyForward --> Match{"Addresses match?"} Match -->|Yes| Valid["Primary name valid - Return to client"] Match -->|No| Invalid["Revert or return empty - Inconsistent mapping"] StopEmpty --> End1([End: No primary name]) Valid --> End2([End: Primary name resolved]) Invalid --> End3([End: Invalid primary name]) style Start fill:#e1f5ff style End1 fill:#ffe1e1 style End2 fill:#e1ffe1 style End3 fill:#ffe1e1 style DetermineRegistrar fill:#fff4e1 style CheckResult fill:#fff4e1 style Match fill:#fff4e1 ``` ##### 4.3.2. rollup\_storage\_proof(chainId) **When Used:** * Chain-specific registrars on rollups that post state to L1 * Chains with wildcard resolvers registered at `"[coinTypeAsHex].reverse"` * Examples: Optimism, Arbitrum, Base, Scroll, Linea **Proof Mechanism:** * **State proof required** — L2 state must be proven to L1 verifier * **Trust Source:** Rollup bridge security + L1 state root commitments * **Verification:** Dual-path validation enables flexible latency/security tradeoffs: * **Fast path:** Namechain resolvers validate pre-settled TEE proofs natively in CCIP callbacks * **Secure path:** Bridge verifier contract validates full proofs against L1-committed state root * **Gas Cost:** Variable (native TEE validation vs onchain proof verification: \~200k-500k gas) **General Flow:** See the [Trustless Gateway Model](#trustless-gateway-model-required-default) below for the complete resolution flow. **Chain-Specific Example:** See the [ær Rollup Example](#642-resolution-flow-trustless-gateway-model) for a concrete implementation. ```mermaid flowchart TD Start([Client initiates resolution]) --> BuildName["Build reverseName from addressBytes + coinType"] BuildName --> ComputeNode["Compute reverseNode = namehash(reverseName)"] ComputeNode --> CallUR["Client calls UniversalResolver.resolve(reverseName, nameCallData)"] CallUR --> URRoute{"Universal Resolver routes to resolver"} URRoute -->|Query ENS Registry| ENSReg["ENS Registry looks up chain resolver for reverseNode"] ENSReg --> GetResolver["Get chain resolver address from registry"] GetResolver --> CallResolver["Call chain resolver.resolve(reverseName, nameCallData)"] CallResolver --> ResolverCheck{"Resolver checks if name in cache"} ResolverCheck -->|Name cached| ReturnCached["Return cached name"] ResolverCheck -->|Name not cached| TriggerCCIP["Resolver triggers CCIP-Read OffchainLookup"] TriggerCCIP --> OffchainLookup["Resolver reverts with OffchainLookup error containing: urls, callData, callbackFunction, extraData"] OffchainLookup --> ClientReceives["Client receives OffchainLookup error"] ClientReceives --> ClientFetches["Client selects gateway URL and makes HTTP POST request with callData"] ClientFetches --> GatewayReceives["Gateway receives callData: storage slot, address, block number, chain ID"] GatewayReceives --> GatewayRPC["Gateway calls L2 RPC method based on rollup type"] GatewayRPC -->|Optimistic Rollup| OptimisticRPC["Gateway calls eth_getProof(registrarAddress, storageSlots, blockNumber)"] GatewayRPC -->|ZK Rollup| ZKRPC["Gateway calls custom prover endpoint or eth_getProof"] OptimisticRPC --> L2Response1["L2 RPC returns: storageValue, merkleProof[], stateRoot, blockNumber"] ZKRPC --> L2Response2["L2 RPC/Prover returns: SNARK proof bytes, publicInputs, stateRoot, blockNumber"] L2Response1 --> GatewayPack1["Gateway packages Merkle proof structure: stateRoot, storageSlot, storageValue, merkleProof[], l2BlockNumber, chainId"] L2Response2 --> GatewayPack2["Gateway packages SNARK proof structure: proof bytes, publicInputs, l2BlockNumber, chainId"] GatewayPack1 --> GatewayReturns["Gateway returns full proof bundle via CCIP-Read HTTP response"] GatewayPack2 --> GatewayReturns GatewayReturns --> ClientCallback["Client calls resolver.ccipCallback(proofBundle, extraData)"] ClientCallback --> ResolverDecode["Resolver decodes proofBundle and extracts proof data"] ResolverDecode --> ResolverVerify["Resolver calls bridgeVerifier.verifyStorageProof(proof) or bridgeVerifier.verifyZKProof(proof)"] ResolverVerify --> VerifierCheck1{"Verifier validates Merkle proof"} ResolverVerify --> VerifierCheck2{"Verifier validates SNARK proof"} VerifierCheck1 --> CheckStateRoot1{"State root matches L1-committed root?"} VerifierCheck2 --> CheckStateRoot2{"State root matches L1-committed root?"} CheckStateRoot1 --> CheckBlock1{"Block number recent enough? Within challenge period?"} CheckStateRoot2 --> CheckBlock2{"Block number recent enough?"} CheckStateRoot1 -->|No| Revert1["Revert: Invalid state root"] CheckStateRoot2 -->|No| Revert2["Revert: Invalid state root"] CheckBlock1 -->|No| Revert3["Revert: Block too old"] CheckBlock2 -->|No| Revert4["Revert: Block too old"] CheckBlock1 -->|Yes| ValidProof1["Proof valid - Return storageValue (name)"] CheckBlock2 -->|Yes| ValidProof2["Proof valid - Return storageValue (name)"] ValidProof1 --> ResolverCache["Resolver caches name for future queries"] ValidProof2 --> ResolverCache ResolverCache --> CheckName{"Name exists? name != empty"} ReturnCached --> CheckName CheckName -->|Yes: name found| ReturnName["Return name ABI-encoded bytes"] CheckName -->|No: name empty| FallbackDefault["Fallback to default primary name"] FallbackDefault --> ReturnDefault["Return default primary name"] ReturnName --> ForwardPhase["Continue to forward verification phase §4"] ReturnDefault --> ForwardPhase ForwardPhase --> VerifyForward["Verify forward mapping: resolve(name) → address, compare to input addressBytes"] VerifyForward --> Match{"Addresses match?"} Match -->|Yes| Valid["Primary name valid - Return to client"] Match -->|No| Invalid["Revert or return empty - Inconsistent mapping"] Revert1 --> EndError([End: Proof validation failed]) Revert2 --> EndError Revert3 --> EndError Revert4 --> EndError Invalid --> EndError Valid --> EndSuccess([End: Primary name resolved]) style Start fill:#e1f5ff style EndSuccess fill:#e1ffe1 style EndError fill:#ffe1e1 style ResolverCheck fill:#fff4e1 style GatewayRPC fill:#fff4e1 style VerifierCheck1 fill:#fff4e1 style VerifierCheck2 fill:#fff4e1 style CheckStateRoot1 fill:#fff4e1 style CheckStateRoot2 fill:#fff4e1 style CheckBlock1 fill:#fff4e1 style CheckBlock2 fill:#fff4e1 style CheckName fill:#fff4e1 style Match fill:#fff4e1 ``` ##### 4.3.3. none (Informational Only / Non-EVM) **When Used:** * Non-rollup, non-EVM coin types where `chainFromCoinType(coinType) = 0` * Examples: Bitcoin (`coinType = 0`), other non-EVM chains * These do not participate in ENSIP-19's multichain primary name trust model **Proof Mechanism:** * **No proof system** — informational resolution only * **Trust Source:** None (not part of trustless primary name system) * **Verification:** N/A * **Usage:** Coin-type specific `addr(node, coinType)` records only **Example:** * Bitcoin addresses can have `addr(node, 0)` records set on ENS * But they cannot have primary names via ENSIP-19 (no reverse resolution) * Resolution is one-way: name → address, but not address → name **Non-EVM Chain Integration Framework**: The limitations described above (no primary name reverse resolution for non-EVM chains) stem from missing infrastructure components (chain-specific registrar contracts, state commitment bridges to L1, verifier contracts). See the **[Non-EVM Resolution Pattern](/patterns/non-evm-pattern)** for a detailed analysis of required infrastructure, trust models, verification paths, and implementation strategies to enable full bidirectional resolution for non-EVM chains like Bitcoin, Cosmos, and Solana. ##### 4.3.4. Proof Formats ENSIP-19 requires the **trustless gateway model** for all rollup resolution, ensuring security guarantees and maintaining decentralization. **1. Onchain State Proofs (L1)** * **Format:** Direct EVM storage reads via `SLOAD` opcode * **Structure:** No explicit proof format—the state itself is the proof * **Validation:** Ethereum consensus guarantees state correctness * **Gateway Type:** N/A (no gateway needed for L1) * **Example:** Reading `registrar.name(address)` directly from L1 contract storage **2. Rollup Storage Proofs (L2 → L1)** **Trustless Gateway Model (Required Default)** **Gateway Role:** Untrusted data fetcher * Gateway fetches raw L2 state data * Gateway may construct proof structure, but **does not validate** * Onchain verifier contract generates/validates proof **Gateway Infrastructure:** * **Protocol:** CCIP-Read (EIP-3668) is the standard protocol used for offchain data fetching * **How proof construction works:** * Gateway calls L2 RPC methods to fetch proof data: * **For Optimistic rollups:** Calls `eth_getProof(contractAddress, [storageSlot], blockNumber)` on the L2 RPC endpoint * **For ZK rollups:** Calls custom prover endpoints or `eth_getProof` (chain-dependent) to fetch SNARK proof bytes * Gateway receives raw proof data from L2 (Merkle proof paths for Optimistic, SNARK proof bytes for ZK) * Gateway packages this raw data into the proof structure format expected by the bridge verifier contract * Gateway returns the proof bundle via CCIP-Read response HTTP endpoint * **Gateway implementation:** Any HTTP server that implements CCIP-Read (EIP-3668) can serve as a gateway. Multiple competing gateways can exist—the protocol is trustless because validation happens onchain. Gateways only provide availability and latency; they cannot compromise correctness because invalid proofs are rejected by the bridge verifier contract. **Proof Format:** Full cryptographic proof structure **Optimistic Rollups:** ``` { stateRoot: bytes32, // L2 state root committed to L1 storageSlot: bytes32, // Storage slot being proven storageValue: bytes32, // Value at that slot merkleProof: bytes[], // Merkle proof path (full proof) l2BlockNumber: uint256, // L2 block number chainId: uint256 // L2 chain ID } ``` **ZK Rollups:** ``` { proof: bytes, // Full SNARK proof bytes publicInputs: bytes32, // Public inputs (state root, etc.) l2BlockNumber: uint256, // L2 block number chainId: uint256 // L2 chain ID } ``` :::info **Research Structures**: These JSON proof format examples represent conceptual structures for research purposes. Actual ENSIP-19 implementations may use different data formats optimized for onchain verification and gas efficiency. ::: **Validation:** Dual-path verification enables flexible latency/security tradeoffs * **Fast path:** Namechain resolvers validate pre-settled TEE proofs natively in CCIP callbacks (lower latency, lower gas cost) * **Secure path:** Bridge verifier contract on L1 validates full proofs against L1-committed state root (higher security, higher gas cost) * **Security:** Trustless—gateway cannot lie; invalid proofs are rejected by verifier (TEE or onchain) * **Gas Cost:** Variable (native TEE validation vs onchain proof verification) * **Example:** Gateway returns proof bundle; resolver validates TEE proof natively or falls back to `bridgeVerifier.verifyStorageProof(proof)` on L1 #### 4.4. ENSIP-10 Resolution Results & ABI Encoding [ENSIP-10](https://docs.ens.domains/ensip/10) is an optional extension that introduces a universal entry point for advanced and wildcard resolution. The standard resolver interface is defined by [EIP-137](https://eips.ethereum.org/EIPS/eip-137) and related EIPs, which all resolvers must implement for basic functionality. **ENSIP-10 Overview:** * **Purpose:** ENSIP-10 defines the resolve(bytes calldata name, bytes calldata data) function, enabling advanced features like wildcard resolution and unified querying. * **Adoption:** Not all resolvers implement ENSIP-10. Many legacy and basic resolvers only support the standard methods (e.g., addr, text, contenthash). **How ENSIP-10 Works:** * **Input `name`:** The ENS name (or reverse name) as a bytes representation (e.g., "alice.eth" or "b8c2c29ee19d8307cb7255e1cd9cbde883a267d5.addr.reverse"). * **Input `data`:** ABI-encoded function call specifying what you want to resolve. This encodes both the function selector (what function to call) and the parameters (like the node hash). * **Output `return value`:** ABI-encoded return value containing the resolved data (as raw bytes). * **Format:** Return values are ABI-encoded, following Ethereum's Application Binary Interface encoding standard. This ensures consistent, machine-readable data formats across resolver implementations that support ENSIP-10. **The structure can include:** * **Direct contract call results:** (if data is stored onchain) * **CCIP-Read responses:** (if resolver needs offchain data, it triggers EIP-3668 OffchainLookup, client fetches data, then calls callback) **Validation:** * **For onchain data:** Results are verified by re-executing the resolution logic—the EVM executes the resolver contract's code, which reads from contract storage, and the result is verified by Ethereum consensus * **For offchain/L2 data:** Results are validated through dual-path verification: * **Fast path:** Pre-settled TEE proofs validated natively in CCIP callbacks * **Secure path:** Cryptographic proofs (Merkle proofs, SNARK proofs) validated onchain by bridge verifier contracts **Example (Forward Resolution):** ```solidity // Client wants to resolve an address resolve( "alice.eth", abi.encodeCall(IAddrResolver.addr, (namehash("alice.eth"))) ) // Returns: 0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5 (ABI-encoded address bytes) ``` **Example (Reverse Resolution - Primary Name):** ```solidity // Client wants to find primary name for an address resolve( "b8c2c29ee19d8307cb7255e1cd9cbde883a267d5.addr.reverse", abi.encodeCall(INameResolver.name, (namehash("b8c2c29ee19d8307cb7255e1cd9cbde883a267d5.addr.reverse"))) ) // Returns: "alice.eth" (ABI-encoded string) ``` **Why ABI encoding matters:** * **Standardization:** All resolvers return data in the same format, so clients know how to decode results * **Type safety:** The function signature in `data` specifies what type of data to return, and ABI encoding preserves type information * **Efficiency:** Binary encoding is more gas-efficient than string-based formats for onchain operations #### 4.5. Algorithms ##### 4.5.1. Namehash Algorithm * **Input:** DNS-style name string (e.g., `"b8c2c29ee19d8307cb7255e1cd9cbde883a267d5.80000002.reverse"`) * **Process:** Recursive hash of labels from right to left * **Output:** `bytes32` node identifier * **Standard:** ENSIP-1 namehash specification ##### 4.5.2. Standard EVM Execution * **Algorithm:** Standard Ethereum Virtual Machine opcodes * **Purpose:** Executes resolver contract logic * **Operations:** Storage reads (`SLOAD`), contract calls (`CALL`, `STATICCALL`), hash operations (`KECCAK256`) * **Gas Cost:** Standard EVM gas pricing ##### 4.5.3. Rollup State Proof Verification * **Algorithm:** Varies by rollup type: * **Optimistic:** Merkle proof verification + state root comparison * **ZK:** SNARK verification (elliptic curve operations, polynomial commitments) * **Purpose:** Validates L2 state without requiring L2 execution on L1 * **Implementation:** Bridge verifier contracts (e.g., Optimism's `L2OutputOracle`, Arbitrum's `Outbox`) #### 4.6. Proof Generation & Validation Flow ##### 4.6.1. L1 Proof Generation (Default & Mainnet) **Generation:** 1. No explicit proof generation needed 2. State is directly accessible via EVM calls 3. Resolver contracts read from registrar storage **Validation:** 1. EVM executes contract call 2. Storage reads are validated by Ethereum consensus 3. Return value is the proof itself (the state) ##### 4.6.2. L2 Proof Generation (Rollups, Trustless Gateway Model) **Generation (Trustless Gateway Model):** 1. **Client/Gateway** queries L2 RPC for registrar state 2. **Gateway** constructs storage proof structure: * For Optimistic: Fetches Merkle proof of storage slot from L2 * For ZK: Requests SNARK proof from L2 prover or constructs from available data 3. **Gateway** packages proof with state root and block number 4. **Gateway** returns **full proof structure** via CCIP-Read response (does not validate) 5. **Note:** Gateway is untrusted—it may return invalid proofs, but they will be rejected onchain **Validation (Trustless Gateway Model - Default):** 1. **Resolver** receives proof via `ccipCallback` 2. **Resolver** validates proof natively (fast path): * **Namechain resolvers can validate pre-settled TEE proofs directly in CCIP callbacks** * TEE proofs are cryptographic attestations of L2 state that have already been verified and settled * This enables native validation without requiring cross-chain calls to L1 bridge verifiers 3. **Resolver** falls back to L1 DA + settled ZK proofs (secure path): * If TEE proof validation fails or is unavailable * **Resolver calls bridge verifier contract on L1** with full proof via cross-chain call * **Verifier checks onchain:** * Proof is valid (Merkle path verification or SNARK verification) * State root matches L1-committed root * Block number is recent enough (within challenge period for Optimistic) 4. **Verifier** returns validated state value (or reverts if invalid) 5. **Resolver** uses value to complete resolution 6. **Security:** Gateway cannot lie—invalid proofs are rejected by verifier (TEE or onchain) #### 4.7. Comparison to Other Proof Systems (DNSSEC P-256, WebAuthn) | Aspect | ENSIP-19 | DNSSEC P-256 | WebAuthn | | ---------------- | ------------------------------ | ------------------------ | ------------------------ | | **Proof Type** | Onchain state | Cryptographic signatures | Cryptographic signatures | | **Primitive** | EVM state + bridge proofs | ECDSA P-256 | ECDSA P-256 | | **Verification** | Consensus + bridge verifiers | P-256 precompile | P-256 precompile | | **Trust Anchor** | Ethereum L1 | DNS root KSK | FIDO metadata | | **Proof Format** | Storage reads or Merkle/SNARK | DNSKEY/DS/RRSIG chain | WebAuthn assertion | | **Gas Cost** | Low (L1) or Medium (L2 proofs) | Low (with EIP-7951) | Low (with EIP-7951) | **Key Difference:** ENSIP-19 doesn't require cryptographic proofs for L1 because the state itself is the proof. For L2s, proofs are needed to bridge trust from L2 to L1, but these are infrastructure-level proofs (state commitments) rather than per-query cryptographic proofs like DNSSEC. ### 5. Rules & Lifecycle #### 5.1. High-Level Rules for Primary Names To establish a primary name for an address: * Set the address for the name (forward resolution) * Set the reverse record for the address (reverse resolution) * The primary name is valid only if reverse and forward mappings are consistent **Lifecycle operations:** * **Register:** Map address → name via the relevant registrar * **Update:** Change mappings (either forward or reverse) * **Remove:** Unset mappings (e.g., unset resolver or name) * **Fallback:** If chain-specific primary name is missing, use the default primary name #### 5.2. Reverse Resolution Rules Given `(addressBytes, coinType)`: 1. Compute `[addressAsHex]` and `[coinTypeAsHex]` 2. Build `reverseName` based on `coinType`: | `coinType` | `reverseName` | | --------------- | ------------------------------------------ | | `60` | `"[addressAsHex].addr.reverse"` | | `0x8000_0000` | `"[addressAsHex].default.reverse"` | | `*` (otherwise) | `"[addressAsHex].[coinTypeAsHex].reverse"` | 3. Compute `reverseNode = namehash(reverseName)` 4. Call `name = resolve(reverseName, abi.encodeCall(INameResolver.name, (reverseNode)))` 5. If `name` is empty: no primary name exists; stop and display the address 6. If `name` is non-empty: continue to forward verification (see Section 6) #### 5.3. Chain-Specific Primary Names (Rollups) New registrars are deployed per chain. Only chains that post state to L1 (rollups) may have a corresponding ENSIP-10 wildcard resolver at `"[coinTypeAsHex].reverse"`. Each such resolver: * Verifies registrar lookups trustlessly against L1 commitments, when `addressAsHex` is a valid EVM address * If there is no chain-specific name, it returns the default name instead #### 5.4. Default Address & Default Chain Behavior ENSIP-9 required `addr(node)` to match `addr(node, 60)` for backwards compatibility. ENSIP-19 defines a new default for EVM chains: If `chainFromCoinType(coinType) > 0`, then when no address is stored for that `coinType`, `addr(node, coinType)` must equal `addr(node, 0x8000_0000)`. **Interpretation:** * `coinType = 0x8000_0000` represents the default EVM chain * Chain-specific EVM address records should fall back to this default if unset #### 5.5. Implementation Notes & Deprecations ##### 5.5.1. Mainnet addr.reverse Namespace ENSIP-19 specifies that `coinType = 60` (Ethereum mainnet) uses `"[addressAsHex].addr.reverse"` as its reverse namespace (see Section 5.2). This namespace format is part of the ENSIP-19 specification itself, not merely legacy support—it's the required format for mainnet primary names. The `addr.reverse` namespace maintains compatibility with existing ENSIP-3 infrastructure while fitting into ENSIP-19's broader multichain framework. ##### 5.5.2. Reverse-Name Avatars (ENSIP-12) Reverse-name avatars (ENSIP-12) are deprecated: * New reverse resolvers defined by ENSIP-19 do not support `text()` records on reverse nodes * Avatar support should be implemented through forward resolution (`text(node, "avatar")`) rather than reverse resolution ##### 5.5.3. Deprecating "Mainnet as Default" Mainnet as implicit default is deprecated: * Historically, `addr()` was used as an implicit default chain address, and mainnet primary names were implicitly reused on other chains * ENSIP-19 requires clients to remove logic treating mainnet as default, because this can mislead users when Smart Contract Accounts (SCAs) or counterfactual addresses cannot be replicated across chains * Clients must explicitly specify `coinType` for all resolution queries and use the default fallback chain (`0x8000_0000`) rather than assuming mainnet ### 6. Verification Path #### 6.1. Generic Primary Name Verification (Two-Phase Flow) Given `(addressBytes, coinType)`: **1. Reverse Phase** 1. Compute `[addressAsHex]` from `addressBytes` 2. Compute `[coinTypeAsHex]` and `chainFromCoinType(coinType)` 3. Build `reverseName` according to the table in Section 5.2 4. `reverseNode = namehash(reverseName)` 5. Call `name = resolve(reverseName, abi.encodeCall(INameResolver.name, (reverseNode)))` 6. If `name` is empty → **no primary name**; abort and display the address **2. Forward Phase** 1. Compute `node = namehash(name)` 2. Resolve `resolvedAddress = resolve(name, callData)` where the `callData` depends on `coinType` (per ENSIP-19): | `coinType` | `callData` | | ---------: | :-------------------------------------------------------- | | `60` | `abi.encodeCall(IAddrResolver.addr, (node))` | | `*` | `abi.encodeCall(IAddressResolver.addr, (node, coinType))` | **Note:** The resolver-profile implementation logic: * If `chainFromCoinType(coinType) > 0` and chain-specific semantics are required: * Try `addr(node, coinType)` (which uses `IAddressResolver.addr(node, coinType)`) * If unset and chain is an EVM chain → fallback to `addr(node, 0x8000_0000)` (default) * If `chainFromCoinType(coinType) = 0`: * For Ethereum mainnet (`coinType = 60`), use `addr(node, 60)` or `addr(node)` per ENSIP-9/1 compatibility rules (uses `IAddrResolver.addr(node)`) * For non-EVM coins, use `addr(node, coinType)` only 3. Compare the resulting address to `addressBytes` 4. If equal → **`name` is a verified primary name** for `(addressBytes, coinType)` * If not equal → treat as **invalid** (mismatch between reverse and forward) #### 6.2. Resolver / Gateway Topology ENSIP-19 resolution involves multiple components across different layers, each with distinct responsibilities for maintaining security, performance, and decentralization. ##### Core Infrastructure Layer (L1 - Trust Anchor) * **ENS Registry**: Authoritative source for all resolver assignments and namespace delegations * **Bridge Verifier Contracts**: Cryptographically validate L2 state proofs against L1-committed state roots * **Universal Resolver**: Entry point that routes requests to appropriate namespace-specific resolvers ##### Resolution Layer (Namechain/L2 - Cost Optimization) * **Default Resolver (`"reverse"`)**: Handles default and legacy reverse namespaces with wildcard resolution * **Chain-Specific Resolvers (`"[coinTypeAsHex].reverse"`)**: Deployed per rollup chain for cost-effective resolution * **Primary validation**: Pre-settled TEE proofs validated natively in CCIP callbacks (fast path) * **Fallback validation**: L1 DA + settled ZK proofs via cross-chain calls to bridge verifiers (secure path) ##### State Management Layer (Per Chain) * **Default Registrar (`"default.reverse"`)**: Maintains address→name mappings for default EVM chains * **Chain-Specific Registrars**: Per-rollup registrars storing address→name mappings with chain-specific semantics ##### Data Fetching Layer (Offchain - Availability) * **CCIP-Read Gateways**: Untrusted but crucial infrastructure that: * Fetches raw L2 state data from rollup RPC endpoints * Constructs cryptographic proof structures (Merkle/SNARK proofs) * Returns proof bundles via HTTP for onchain verification * **Cannot compromise security** - invalid proofs are rejected by onchain verifiers ##### Trust Model & Division of Responsibilities * **L1 components** ensure cryptographic security and final authority * **Namechain components** optimize costs while maintaining L2 security * **Gateways** provide data availability without requiring trust * **Cross-chain validation** bridges L2 efficiency with L1 security guarantees #### 6.3. Minimal Client-Side Pseudocode A simplified client-side function for "get primary name": ```typescript async function getPrimaryName(addressBytes: Uint8Array, coinType: bigint): Promise { const addressAsHex = toLowerHex(addressBytes); // without 0x, no leading zeros trimmed const coinTypeAsHex = coinType.toString(16); const chainId = chainFromCoinType(coinType); let reverseName: string; if (coinType === 60n) { reverseName = `${addressAsHex}.addr.reverse`; } else if (coinType === 0x8000_0000n) { reverseName = `${addressAsHex}.default.reverse`; } else { reverseName = `${addressAsHex}.${coinTypeAsHex}.reverse`; } const reverseNode = namehash(reverseName); const name = await ensip10ResolveName(reverseName, reverseNode); if (!name) return null; const node = namehash(name); // Forward verification let resolved: Uint8Array | null = null; if (chainId > 0) { resolved = await resolverAddr(node, coinType); if (!resolved) { // Default EVM fallback resolved = await resolverAddr(node, 0x8000_0000n); } } else { // Non-EVM or legacy resolved = await resolverAddr(node, coinType); } if (!resolved) return null; return bytesEqual(resolved, addressBytes) ? name : null; } ``` ### 7. Contract & Namespace Inventory #### 7.1. Core Constants & Helpers * `DEFAULT_EVM_COIN_TYPE = 0x8000_0000` * `ETHEREUM_MAINNET_COIN_TYPE = 60` * `chainFromCoinType(coinType)` as defined in Section 4.1 #### 7.2. Reverse Namespaces & Registrars (Mainnet Table) From ENSIP-19 Annex – Mainnet: | Network | `chainId` | `reverseNamespace` | Registrar Contract (Mainnet) | | -------- | --------: | -------------------- | -------------------------------------------- | | Default | 0 | `"default.reverse"` | `0x283F227c4Bd38ecE252C4Ae7ECE650B0e913f1f9` | | Ethereum | 1 | `"addr.reverse"` | (legacy ENSIP-3 registrar; see Section 5.5) | | Optimism | 10 | `"8000000a.reverse"` | `0x0000000000D8e504002cC26E3Ec46D81971C1664` | | Base | 8453 | `"80002105.reverse"` | `0x0000000000D8e504002cC26E3Ec46D81971C1664` | | Arbitrum | 42161 | `"8000a4b1.reverse"` | `0x0000000000D8e504002cC26E3Ec46D81971C1664` | | Linea | 59144 | `"8000e708.reverse"` | `0x0000000000D8e504002cC26E3Ec46D81971C1664` | | Scroll | 534352 | `"80082750.reverse"` | `0x0000000000D8e504002cC26E3Ec46D81971C1664` | #### 7.3. Resolver Types ##### 7.3.1. Default Resolver ("reverse") ENSIP-10 wildcard resolver that handles legacy and default reverse names. Deployed on **Namechain** (ENS L2) for cost-effective operations. ##### 7.3.2. Per-Chain Resolvers ("\[coinTypeAsHex].reverse") ENSIP-10 wildcard resolvers for rollups that: * Deployed on **Namechain** (ENS L2) for cost-effective operations * Accept CCIP-Read requests for `name()` of `"[addressAsHex].[coinTypeAsHex].reverse"` * **Validate proofs natively using pre-settled TEE proofs in CCIP callbacks** (fast path) * **Fallback to L1 DA + settled ZK proofs** via cross-chain calls to bridge verifiers on L1 (secure path) * Fallback to default primary name if chain-specific name is missing ### 8. URM Mapping (Resolver Profiles) ENSIP-19 defines three resolver profiles for different chain types. Each profile uses the same two-phase verification algorithm but differs in trust model, proof system, and verification path. | Profile | Scope | Trust Model | Proof System | Rules | Verification Path | | ------------------------------------------------ | --------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | | **Profile 1: Default EVM** (`ensip19-default`) | EVM chains with `chainFromCoinType(coinType) > 0`, uses `0x8000_0000` as fallback | L1 default registrar on Namechain | [`onchain_state(L1)`](#432-onchain_state-l1) - direct storage reads | Reverse: `default.reverse`, Forward: `addr(node, coinType)` with default fallback | Direct EVM calls to Namechain registrar, validated by Ethereum consensus | | **Profile 2: Chain-Specific** (`ensip19-rollup`) | EVM rollups with per-chain registrars posting state to L1 | L2 registrar state + L1 rollup commitment via bridge verifiers | [`rollup_storage_proof(chainId)`](#432-rollup_storage_proof-chainid) - Merkle/SNARK proofs | Reverse: `[coinTypeAsHex].reverse`, Forward: `addr(node, coinType)`, fallback to Profile 1 | CCIP-Read → Gateway → Bridge verifier (L1) validates L2 state proofs | | **Profile 3: Non-EVM** (`ensip19-non-evm`) | Non-EVM chains where `chainFromCoinType(coinType) = 0` | ENS registry + resolver for forward resolution only | `onchain_state(L1)` or chain-specific | Forward-only: `addr(node, coinType)`, no reverse resolution | Standard ENS forward resolution, no primary name support | #### 8.1 Gap Analysis for Non-EVM Primary Names **Current State:** Profile 3 covers non-EVM chains (e.g., Bitcoin, Cosmos) but only provides forward address resolution. Primary name reverse resolution is **not supported**. **Missing Infrastructure for Chains Like Cosmos:** To enable primary name reverse resolution for non-EVM chains, the following gaps must be addressed: 1. **State Bridge / Proof System:** * **Gap:** No mechanism to prove non-EVM chain state to Ethereum L1 * **Required:** Bridge infrastructure that commits non-EVM state roots to Ethereum (similar to rollup state commitments) * **Example:** Cosmos → Ethereum state bridge that posts Cosmos state roots to L1 2. **Registrar Contract:** * **Gap:** No registrar contract on non-EVM chains storing reverse mappings (`address → name`) * **Required:** Deploy registrar contract on target chain (Cosmos, Bitcoin, etc.) that stores reverse resolution data * **Challenge:** Non-EVM chains may not support smart contracts (e.g., Bitcoin) 3. **Verification Path:** * **Gap:** No verification path for non-EVM state proofs * **Required:** Bridge verifier contract on L1 that validates non-EVM state proofs against L1-committed state roots * **Challenge:** May require chain-specific cryptographic primitives (e.g., Cosmos signature verification) 4. **Address Format Compatibility:** * **Gap:** Non-EVM addresses (e.g., Cosmos bech32 `cosmos1...`) don't fit EVM address format * **Required:** Address encoding/decoding layer to map non-EVM addresses to ENS-compatible format * **Challenge:** Standardization of address representation across chains 5. **Trust Model:** * **Gap:** No defined trust model for non-EVM chains * **Required:** Define root of trust (e.g., Cosmos validator set, Bitcoin miners) * **Challenge:** Different consensus mechanisms require different trust assumptions **DAO Funding Implications:** Mapping these gaps helps the ENS DAO prioritize funding for infrastructure enabling primary name support on high-value chains, understand requirements before committing resources, and assess feasibility of primary name support vs. maintaining forward-only resolution. **Current Status:** Profile 3 remains forward-only until these infrastructure gaps are addressed. See the Non-EVM Resolver Profile for detailed implementation strategies. ## 9. Edge Cases & Client Requirements #### 9.1. Client Behaviors to Deprecate From ENSIP-19 "Deprecating Mainnet as Default": Clients **must not**: * Treat `addr()` or `addr(node, 60)` as a global default for all chains * Display mainnet primary names as if they automatically apply on other chains This pattern leads to dangerous mis-signalling, especially with smart contract accounts that cannot control the same address on other chains. #### 9.2. Migration Edge Cases ##### 9.2.1. Legacy ENSIP-3 Reverse Records Where a resolver is configured at `"[addressAsHex].addr.reverse"`, a default-aware reverse resolver **must** replace it, or be unset. Legacy ENSIP-3 reverse records do not support default primary name fallback behavior. ##### 9.2.2. Resolvers without Default Address Logic Most existing resolvers don't implement the fallback from `addr(node, coinType)` → `addr(node, 0x8000_0000)`. They must be redeployed or configured with explicit addresses for each EVM coin type to support ENSIP-19 default address semantics. #### 9.3. URM Implementation Notes * URM should treat ENSIP-19 as a **family of profiles** (default, rollup, non-EVM) with: * Shared algorithmic core (two-phase primary name resolution) * Different proof types and namespaces per profile * Resolver implementers should: * Ensure ENSIP-10 gateways handle **name()** for the reverse nodes described in Section 5.2 * Confirm default fallback semantics are correctly implemented for EVM chains *** :::warning **Educational Content**: This document presents hypothetical cross-chain resolution concepts for research purposes only. ::: ## Universal Resolver Matrix: Non-EVM Resolution and Architecture ### 1. Introduction #### 1.1. Document Purpose This document provides a comprehensive breakdown of how ENSIP-19: Multichain Primary Names can be extended to support primary name resolution for non-EVM chains (Cosmos, Solana, etc.) via a new resolver profile in the Universal Resolver Matrix (URM). It examines the current limitations of ENSIP-19 for non-EVM chains and defines the infrastructure requirements, trust models, proof systems, and implementation frameworks needed to enable **bidirectional resolution** (forward + reverse) for non-EVM blockchains. It expands the normative specification in [ENSIP-19](https://docs.ens.domains/ensip/19/) into a single technical reference for implementing non-EVM primary names as a URM profile, with additional sections covering implementation details: * Contract & Namespace Inventory * URM Mapping (Resolver Profiles) * Edge Cases & Client Requirements #### 1.2. Universal Resolver Matrix Framework The Universal Resolver Matrix (URM) is a systematic framework for mapping resolution pathways across namespaces using four core dimensions: 1. **Trust Model**, 2. **Proof System**, 3. **Rules & Lifecycle**, and 4. **Verification Path**. This document structures non-EVM primary name implementation around these dimensions, with additional sections covering implementation details. ### 2. Scope & Goals #### 2.1. ENSIP-19 Overview & Non-EVM Limitations ENSIP-19 standardizes primary name resolution for all coin types in EVM-based ecosystems, but currently provides **forward resolution only** for non-EVM chains (`name → address` via `addr(node, coinType)`). Reverse resolution (`address → name`) is not available for non-EVM chains due to missing infrastructure: chain-specific registrar contracts, state commitment bridges to L1, and verifier contracts. #### 2.2. Non-EVM Chain Resolution Scope This profile extends ENSIP-19 to enable bidirectional resolution for SLIP-44 coin types where `chainFromCoinType(coinType) = 0` (non-EVM chains). It requires additional infrastructure per chain to enable primary name support. #### 2.3. EVM vs Non-EVM Chains (Bidirectional vs Forward-only) EVM chains (mainnet coinType=60, rollups via ENSIP-11) support full bidirectional resolution. Non-EVM chains (Cosmos, Solana, etc.) currently support forward resolution only; reverse resolution requires infrastructure (chain-specific registrars, state bridges, verifier contracts) that this profile defines. #### 2.4. Non-EVM Resolver Profile & Future Framework Non-EVM chains cannot have primary names via current ENSIP-19 without additional infrastructure. This profile defines the required components (bridges, registrars, verifiers) needed for each chain type, creating an implementation roadmap that can be adapted for specific chains (Cosmos, Solana, etc.). #### 2.5. Deployment Architecture: Hybrid L1 + Namechain (Surge) This profile requires a hybrid deployment architecture across Ethereum L1, Namechain (built on Nethermind's Surge framework), and target non-EVM chains: ##### 2.5.1. Architecture Overview ENS infrastructure migrates to Namechain, a based ZK rollup built on Nethermind's Surge framework, enabling fast preconfirmations and TEE-based proofs for low-latency CCIP-Read operations: | Deployment Location | Component | Purpose | | --------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Ethereum L1** | ENS Registry | Canonical root of trust for all ENS name registrations and resolver assignments | | **Namechain (Surge)** | Non-EVM Resolvers | Cost-effective wildcard resolvers per chain with **dual-path proof validation**: native TEE proof validation in CCIP callbacks (seconds) + fallback to L1 DA + settled ZK proofs | | **Namechain (Surge)** | Bridge Verifiers | Validates non-EVM state proofs against L1-committed state roots (cross-chain verified) | | **Ethereum L1** | Universal Resolver | Routes resolution requests to appropriate namespace-specific resolvers | | **Non-EVM Chains** | Chain Registrars | Per-chain registrars maintaining address → name mappings | | **Non-EVM Chains** | State Bridges | Commit non-EVM chain state roots to Ethereum L1 | ##### 2.5.2. Contract Inventory (Per Chain) **Existing Infrastructure (3 Contracts - No Deployment Needed):** 1. **ENS Registry (L1)** - Central registry mapping names to resolvers (`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`) 2. **Bridge Verifiers (Namechain)** - Validates non-EVM state proofs against L1-committed state roots 3. **Universal Resolver (L1)** - Resolution entry point (optional but recommended) **New Contracts per Chain (3 Contracts - Must Deploy):** 4. **Chain Registrar (Non-EVM Chain)** - Stores address → name mappings on target chain 5. **State Bridge (Non-EVM Chain → L1)** - Commits chain state roots to Ethereum L1 6. **Chain Resolver (Namechain)** - Handles chain-specific resolution with CCIP-Read validation ##### 2.5.3. Roots of Trust & Security The canonical root of trust consists of: * **ENS Registry on Ethereum L1**: Primary source of truth for all name registrations and resolver assignments * **Chain registrar contracts**: Store reverse mappings on non-EVM chains * **State bridge infrastructure**: Commits non-EVM chain state roots to Ethereum L1 * **Bridge verifiers on Namechain**: Validate state proofs against L1-committed state roots via cross-chain communication Security is inherited from both non-EVM chain consensus and Ethereum L1 consensus. Primary names are valid only if reverse and forward mappings agree. ##### 2.5.4. Benefits & Tradeoffs This hybrid architecture enables: * **Trustlessness**: Core infrastructure (Registry, verifiers) remains on L1 for maximum security * **Cost-effectiveness**: Chain-specific resolvers on Namechain benefit from lower gas costs * **Scalability**: **Dual-path proof validation enables flexible latency/security tradeoffs:** * **Fast path:** Resolvers validate pre-settled TEE proofs natively in CCIP callbacks (seconds latency) * **Secure path:** Fallback to L1 DA + settled ZK proofs via cross-chain calls to bridge verifiers * **High performance**: CCIP-Read latency reduced from hours to seconds using based preconfirmations and TEE proofs * **ENSv2 integration**: Migration path for existing infrastructure ##### 2.5.5. Governance Requirements **DAO Approval: When Is It Needed?** While not strictly required for basic resolver operation, a production-ready non-EVM resolver should go through ENS DAO governance for integration into core ENS infrastructure. DAO approval is required if you want your resolver to be: * Set as the default resolver for ENS (e.g., for the reverse registrar or for .eth names) * Integrated at the protocol level (e.g., replacing the public resolver, or being referenced in official ENS contracts) **Why Governance Matters:** DAO approval is a matter of governance and trust. If you want your resolver to be widely used, trusted, or set as a default in ENS infrastructure, DAO approval: * Signals community trust and security review * Ensures the resolver is maintained and meets ENS standards * Protects against unauthorized trust anchor modifications and bridge deployments **Non-EVM Specific Considerations:** For non-EVM chains, governance approval is particularly important due to the additional infrastructure complexity (state bridges, cross-chain verifiers) and the higher security implications of integrating fundamentally different blockchain architectures into the ENS ecosystem. *** **Non-EVM Chain Integration** enables: * Primary name reverse resolution (`address → name`) for non-EVM blockchains * Trustless verification of non-EVM chain state on Ethereum L1 * Standardized framework for integrating any SLIP-44 coin type into ENS primary name system * Bridge infrastructure connecting non-EVM chains to Ethereum L1 trust model **Current Limitations:** ENSIP-19 currently only supports: * ✅ Forward resolution: Non-EVM addresses can be stored and resolved via `addr(node, coinType)` * ❌ Reverse resolution: Primary name reverse resolution (`address → name`) is **not available** for non-EVM chains **Why Non-EVM Chains Lack Primary Name Support:** Non-EVM chains lack the required infrastructure needed for trustless reverse resolution: 1. **Chain-specific registrar contracts** — No onchain contracts storing reverse mappings (`address → name`) on the non-EVM chain 2. **State commitment bridges to L1** — No mechanism to commit non-EVM chain state roots to Ethereum L1 (similar to rollup state commitments) 3. **Verifier contracts** — No bridge verifier contracts on Namechain that validate non-EVM state proofs against L1-committed state roots This profile's goals: * Provide a **URM-ready framework** for implementing primary name support for non-EVM chains * Define the **required infrastructure components** (bridges, registrars, verifiers) needed for each chain type * Establish **trust models** and **verification paths** for non-EVM chain integration * Create an **implementation roadmap** that can be adapted for specific chains (Cosmos, Solana, etc.) * Serve as a **research agenda** and **funding guide** for ENS DAO to prioritize chain integrations **Prerequisites & Dependencies:** **Foundation from ENSIP-19:** * ENSIP-9: Multichain Address Resolution (coinType standard based on SLIP-44) * ENSIP-10: Extended Resolver interface (CCIP-Read support) * ENSIP-11: EVM-Compatible Chain Address Resolution (EVM coinType mapping) * Forward resolution infrastructure (`addr(node, coinType)` already works for all SLIP-44 coin types) **Required New Infrastructure (Per Chain):** 1. **State Bridge** — Commits non-EVM chain state roots to Ethereum L1 2. **Chain Registrar Contract** — Stores reverse mappings on the non-EVM chain (if chain supports smart contracts) 3. **Bridge Verifier Contract** — Validates non-EVM state proofs on Namechain 4. **Address Format Standardization** — Encoding/decoding layer for non-EVM address formats 5. **Chain-Specific Cryptographic Primitives** — Signature verification, hash functions, proof formats *** ### 3. Trust Model #### 1.1 Roots of Trust **Canonical root of trust:** The root of trust for non-EVM chain primary names requires a **two-tier trust model**: 1. **Ethereum L1 Consensus** — The ENS Registry and resolver contracts on Ethereum L1 serve as the ultimate trust anchor 2. **Non-EVM Chain Consensus** — The non-EVM chain's consensus mechanism (validators, miners, etc.) provides the trust anchor for chain state 3. **Bridge Trust** — State commitment bridge that connects non-EVM chain state to Ethereum L1 **Why this model?** Similar to rollup trust models (Profile 2 in ENSIP-19), non-EVM chains need a bridge that commits their state roots to L1, creating a trustless verification path. The bridge serves as the "rollup" for non-EVM chains. **Trust chain:** 1. **Non-EVM Chain State** — Registrar contract (or equivalent data structure) stores `address → name` mappings 2. **State Commitment** — Bridge posts non-EVM chain state roots to Ethereum L1 (periodic commitments) 3. **Bridge Verifier on L1** — Verifier contract validates storage proofs against L1-committed state roots 4. **ENS Registry** — Canonical root on L1 maps reverse namespaces to resolvers 5. **Resolver on Namechain** — Resolver uses bridge verifier to validate reverse resolution requests **Key principle:** Trust flows from non-EVM chain consensus → state commitment bridge → Ethereum L1 consensus → ENS resolution. #### 1.2 Security Guarantees **Security properties:** * **Cryptographic authenticity** — State proofs prove that reverse mappings exist in the non-EVM chain's state. Proofs cannot be forged without access to the chain's private keys or state. * **State integrity** — Bridge commitments ensure state roots are correctly committed to L1. Invalid state proofs are rejected by the verifier contract. * **Consensus security** — Security inherits from both the non-EVM chain's consensus mechanism and Ethereum L1 consensus * **Trust minimization** — Security depends only on bridge correctness (state commitment) and chain consensus, not on gateways or oracles **Attacks protected against:** * **State spoofing** — Attackers cannot forge state proofs because proofs must validate against L1-committed state roots * **Bridge manipulation** — Invalid state commitments are rejected by verifier contract * **Replay attacks** — State proofs include block numbers/height, preventing reuse of old proofs * **Cross-chain address confusion** — CoinType ensures addresses are bound to the correct chain **Cryptographic assumptions:** * **Non-EVM chain consensus security** — The non-EVM chain's consensus mechanism is secure (e.g., Cosmos validators, Solana validators) * **Bridge correctness** — The state commitment bridge correctly posts state roots to L1 * **Verifier contract correctness** — The bridge verifier contract correctly validates state proofs * **Namechain consensus** — Namechain consensus guarantees the immutability of verifier contracts and L1 bridge commitments #### 1.3 Trust Assumptions Users trust: * **Ethereum L1 and ENS contracts** — Ethereum L1 consensus is secure, ENS Registry correctly maps namespaces, resolver contracts correctly implement verification logic * **Non-EVM chain consensus** — The non-EVM chain's consensus mechanism (validators, miners, etc.) is secure and correctly maintains chain state * **State commitment bridge** — The bridge correctly commits non-EVM chain state roots to Ethereum L1 and does not post invalid or manipulated state roots * **Bridge verifier contract** — The verifier contract on Namechain correctly validates state proofs and rejects invalid proofs * **Gateway availability** — While gateways are untrusted for correctness, users assume at least one honest gateway is available to fetch state proofs when needed (availability, not correctness) **Operational trust:** * **Bridge operators** — Users trust that bridge operators correctly post state roots (trust can be minimized through cryptographic verification) * **Chain-specific infrastructure** — Users trust that registrar contracts (if deployed) correctly store reverse mappings and are not compromised *** ### 4. Proof System #### 4.1. Inputs & Core Definitions ENSIP-19's resolution process takes an address and coin type as inputs, converts them to hex-encoded strings for namespace construction, and uses a mapping function to determine which non-EVM chain (if any) the coin type represents. The system relies on ENSIP-10's `resolve()` function (with [CCIP-Read](#trustless-gateway-model-required-default) support) to perform the actual resolution queries against onchain state. All inputs are derived from ENSIP-9 (coin types) and address format standardization, ensuring compatibility with the broader multichain ENS ecosystem. **Core Definitions:** * **`addressBytes`**: Target address as bytes (ENSIP-9 address encoding for non-EVM chains) * **`[addressAsHex]`**: Prefix-free lowercase hex representation of `addressBytes`. Example: Cosmos address encoded as bytes → hex representation * **`coinType`**: Coin type per ENSIP-9. Non-EVM chains have `chainFromCoinType(coinType) = 0` * **`[coinTypeAsHex]`**: Prefix-free lowercase hex representation of `coinType` (no leading zeros) * **`resolve(name, data)`**: An ENSIP-10 implementation (CCIP-Read capable) **Example Usage:** For reverse resolution (address → name), given `addressBytes = cosmos1abc123...` and `coinType = 118`: * Construct `reverseName = "encoded_cosmos_address.76.reverse"` * Compute `reverseNode = namehash(reverseName)` * Call `resolve(reverseName, abi.encodeCall(INameResolver.name, (reverseNode)))` * Returns the primary name (e.g., `"alice.eth"`) For forward resolution (name → address), given `name = "alice.eth"` and `coinType = 118`: * Compute `node = namehash("alice.eth")` * Call `resolve("alice.eth", abi.encodeCall(IAddressResolver.addr, (node, coinType)))` * Returns the Cosmos address #### 4.2. Cryptographic Primitives ##### 4.2.1. Chain-Specific Signature Algorithms Non-EVM chains use different signature algorithms than Ethereum's ECDSA secp256k1: **Cosmos:** * **Signature Algorithm**: Ed25519, secp256k1 (chain-dependent) * **Hash Function**: SHA-256, RIPEMD-160 * **Address Format**: Bech32 encoding (e.g., `cosmos1...`, `osmo1...`) * **Merkle Tree**: IAVL tree (key-value Merkle tree) for state proofs **Solana:** * **Signature Algorithm**: Ed25519 * **Hash Function**: SHA-256 * **Address Format**: Base58 encoding (public keys) * **State Structure**: Account-based model ##### 4.2.2. Namehash Namehash creates deterministic, collision-resistant node identifiers from names using the recursive namehash function (ENSIP-1). It converts reverse names like `"[encodedAddress].[coinTypeAsHex].reverse"` into nodes. Key properties include: one-way function, no collisions, and deterministic behavior across all implementations. ##### 4.2.3. Non-EVM State Proofs Merkle and storage proofs prove non-EVM chain registrar state to L1 verifier contracts. Proof types vary by chain architecture: account-based chains use IAVL tree proofs (Cosmos) or Sparse Merkle Tree proofs (Solana). Bridge verifier contracts on Namechain validate proofs against L1-committed state roots. #### 4.3. Proof Types (URM Terms) ##### 4.3.1. `non_evm_state_proof(chainId)` — Non-EVM Chain State Proof **When Used:** * Reverse resolution for non-EVM chains via Namechain resolvers * Validating that `address → name` mapping exists on the non-EVM chain **Proof Mechanism:** * **State proof required** — Non-EVM state must be proven to Namechain verifier * **Trust Source:** Non-EVM chain consensus + L1 state root commitments * **Verification:** **Dual-path validation enables flexible latency/security tradeoffs:** * **Fast path:** Namechain resolvers validate pre-settled TEE proofs natively in CCIP callbacks (seconds latency) * **Secure path:** Fallback to L1 DA + settled ZK proofs via cross-chain calls to Namechain bridge verifiers * **Gas Cost:** Variable (native TEE validation vs cross-chain proof verification: \~200k-500k gas) **General Flow:** See the Trustless Gateway Model below for the complete resolution flow. **Chain-Specific Example:** See the Cosmos Examplefor a concrete implementation. ```mermaid flowchart TD Start([Client initiates resolution]) --> BuildName["Build reverseName from addressBytes + coinType"] BuildName --> ComputeNode["Compute reverseNode = namehash(reverseName)"] ComputeNode --> CallUR["Client calls UniversalResolver.resolve(reverseName, nameCallData)"] CallUR --> URRoute{"Universal Resolver routes to resolver"} URRoute -->|Query ENS Registry| ENSReg["ENS Registry looks up resolver for reverseNode"] ENSReg --> GetResolver["Get resolver address from registry"] GetResolver --> CallResolver["Call chain resolver.resolve(reverseName, nameCallData)"] CallResolver --> ResolverCheck{"Resolver checks if name in cache"} ResolverCheck -->|Name cached| ReturnCached["Return cached name"] ResolverCheck -->|Name not cached| TriggerCCIP["Resolver triggers CCIP-Read OffchainLookup"] TriggerCCIP --> OffchainLookup["Resolver reverts with OffchainLookup error containing: urls, callData, callbackFunction, extraData"] OffchainLookup --> ClientReceives["Client receives OffchainLookup error"] ClientReceives --> ClientFetches["Client selects gateway URL and makes HTTP POST request with callData"] ClientFetches --> GatewayReceives["Gateway receives callData: storage slot, address, block number, chain ID"] GatewayReceives --> GatewayRPC["Gateway calls non-EVM RPC method based on chain type"] GatewayRPC -->|Cosmos| CosmosRPC["Gateway calls cosmos RPC for IAVL proof"] GatewayRPC -->|Solana| SolanaRPC["Gateway calls solana RPC for account proof"] CosmosRPC --> Proof1["IAVL tree proof of registrar storage"] SolanaRPC --> Proof2["Sparse Merkle Tree proof of account state"] Proof1 --> GatewayPack["Gateway packages proof structure: stateRoot, storageSlot, storageValue, merkleProof[], blockHeight, chainId"] Proof2 --> GatewayPack GatewayPack --> GatewayReturns["Gateway returns full proof bundle via CCIP-Read HTTP response"] GatewayReturns --> ClientCallback["Client calls resolver.ccipCallback(proofBundle, extraData)"] ClientCallback --> ResolverDecode["Resolver decodes proofBundle and extracts proof data"] ResolverDecode --> ResolverVerify["Resolver calls bridgeVerifier.verifyStateProof(proof)"] ResolverVerify --> VerifierCheck{"Verifier validates proof"} VerifierCheck --> CheckStateRoot{"State root matches L1-committed root?"} CheckStateRoot --> CheckBlock{"Block height recent enough?"} CheckStateRoot -->|No| Revert1["Revert: Invalid state root"] CheckBlock -->|No| Revert2["Revert: Block too old"] CheckBlock -->|Yes| ValidProof["Proof valid - Return storageValue (name)"] ValidProof --> ResolverCache["Resolver caches name for future queries"] ResolverCache --> CheckName{"Name exists? name != empty"} ReturnCached --> CheckName CheckName -->|Yes: name found| ReturnName["Return name ABI-encoded bytes"] CheckName -->|No: name empty| ReturnEmpty["Return empty bytes array"] ReturnName --> ForwardPhase["Continue to forward verification phase §4"] ReturnEmpty --> ForwardPhase ForwardPhase --> VerifyForward["Verify forward mapping: resolve(name) → address, compare to input addressBytes"] VerifyForward --> Match{"Addresses match?"} Match -->|Yes| Valid["Primary name valid - Return to client"] Match -->|No| Invalid["Revert or return empty - Inconsistent mapping"] Revert1 --> EndError([End: Proof validation failed]) Revert2 --> EndError Invalid --> EndError Valid --> EndSuccess([End: Primary name resolved]) style Start fill:#e1f5ff style EndSuccess fill:#e1ffe1 style EndError fill:#ffe1e1 style ResolverCheck fill:#fff4e1 style GatewayRPC fill:#fff4e1 style VerifierCheck fill:#fff4e1 style CheckStateRoot fill:#fff4e1 style CheckBlock fill:#fff4e1 style CheckName fill:#fff4e1 style Match fill:#fff4e1 ``` ##### 4.3.2. Proof Formats ENSIP-19 requires the **trustless gateway model** for all non-EVM resolution, ensuring security guarantees and maintaining decentralization. **Trustless Gateway Model (Required Default)** **Gateway Role:** Untrusted data fetcher * Gateway fetches raw non-EVM state data * Gateway may construct proof structure, but **does not validate** * Onchain verifier contract generates/validates proof **Gateway Infrastructure:** * **Protocol:** CCIP-Read (EIP-3668) is the standard protocol used for offchain data fetching * **How proof construction works:** * Gateway calls non-EVM chain RPC methods to fetch proof data: * **For Cosmos:** Calls `abci_query` for IAVL tree proof of registrar contract storage * **For Solana:** Calls `getAccountInfo` with proof request for Sparse Merkle Tree proof * Gateway receives raw proof data from non-EVM chain * Gateway packages this raw data into the proof structure format expected by the bridge verifier contract * Gateway returns the proof bundle via CCIP-Read response HTTP endpoint * **Gateway implementation:** Any HTTP server that implements CCIP-Read (EIP-3668) can serve as a gateway. Multiple competing gateways can exist—the protocol is trustless because validation happens onchain. Gateways provide availability and latency; they cannot compromise correctness because invalid proofs are rejected by the bridge verifier contract. **Proof Format:** Full cryptographic proof structure **Cosmos (Account-Based):** ``` { stateRoot: bytes32, // IAVL state root committed to L1 blockHeight: uint64, // Block height of state accountAddress: bytes, // Address of registrar contract storageKey: bytes32, // Storage key for reverse mapping storageValue: bytes, // Reverse mapping value (name) merkleProof: bytes[], // IAVL tree proof path blockHash: bytes32, // Block hash at this height timestamp: uint64 // Block timestamp } ``` **Solana (Account-Based):** ``` { stateRoot: bytes32, // Sparse Merkle Tree root blockHeight: uint64, // Block height accountAddress: bytes, // Registrar account address accountData: bytes, // Account data containing reverse mapping merkleProof: bytes[], // Sparse Merkle Tree proof blockHash: bytes32, // Block hash timestamp: uint64 // Block timestamp } ``` :::info **Research Structures**: These JSON proof format examples represent conceptual structures for research purposes. Actual non-EVM bridge implementations may use different data formats optimized for cross-chain verification and gas efficiency. ::: **Validation:** **Dual-path verification enables flexible latency/security tradeoffs** * **Fast path:** Namechain resolvers validate pre-settled TEE proofs natively in CCIP callbacks (low latency, lower gas cost) * **Secure path:** Bridge verifier contract on Namechain validates full proofs against L1-committed state root (higher security, higher gas cost) * **Security:** Trustless—gateway cannot lie; invalid proofs are rejected by verifier (TEE or onchain) * **Gas Cost:** Variable (native TEE validation vs onchain proof verification) * **Example:** Gateway returns proof bundle; resolver validates TEE proof natively or falls back to `bridgeVerifier.verifyStateProof(proof)` on Namechain **ENSIP-19 Default:** **[Trustless gateway model](#trustless-gateway-model-required-default)** with onchain bridge verifiers is the required implementation approach. This aligns with the URM principle: "Data at the edge, trust at the root." Gateways provide availability and latency, but correctness is verified onchain. The trustless model ensures that any gateway can be used without requiring trust in the gateway operator—invalid proofs are rejected by the bridge verifier contract. #### 4.4. ENSIP-10 Resolution Results & ABI Encoding [ENSIP-10](https://docs.ens.domains/ensip/10) is an optional extension that introduces a universal entry point for advanced and wildcard resolution. The standard resolver interface is defined by [EIP-137](https://eips.ethereum.org/EIPS/eip-137) and related EIPs, which all resolvers must implement for basic functionality. **ENSIP-10 Overview:** * **Purpose:** ENSIP-10 defines the resolve(bytes calldata name, bytes calldata data) function, enabling advanced features like wildcard resolution and unified querying. * **Adoption:** Not all resolvers implement ENSIP-10. Many legacy and basic resolvers only support the standard methods (e.g., addr, text, contenthash). **How ENSIP-10 Works:** * **Input `name`:** The ENS name (or reverse name) as a bytes representation (e.g., "alice.eth" or encoded reverse name). * **Input `data`:** ABI-encoded function call specifying what you want to resolve. This encodes both the function selector (what function to call) and the parameters (like the node hash). * **Output `return value`:** ABI-encoded return value containing the resolved data (as raw bytes). * **Format:** Return values are ABI-encoded, following Ethereum's Application Binary Interface encoding standard. This ensures consistent, machine-readable data formats across resolver implementations that support ENSIP-10. **The structure can include:** * **Direct contract call results:** (if data is stored onchain) * **CCIP-Read responses:** (if resolver needs offchain data, it triggers EIP-3668 OffchainLookup, client fetches data, then calls callback) **Validation:** * **For onchain data:** Results are verified by re-executing the resolution logic—the EVM executes the resolver contract's code, which reads from contract storage, and the result is verified by Ethereum consensus * **For offchain/cross-chain data:** Results are validated through cryptographic proofs (Merkle proofs, IAVL proofs, SPV proofs) validated onchain by bridge verifier contracts **Example (Forward Resolution):** ```solidity // Client wants to resolve an address resolve( "alice.eth", abi.encodeCall(IAddrResolver.addr, (namehash("alice.eth"))) ) // Returns: Cosmos address (ABI-encoded bytes) ``` **Example (Reverse Resolution - Primary Name):** ```solidity // Client wants to find primary name for an address resolve( "encoded_cosmos_address.76.reverse", abi.encodeCall(INameResolver.name, (namehash("encoded_cosmos_address.76.reverse"))) ) // Returns: "alice.eth" (ABI-encoded string) ``` **Why ABI encoding matters:** * **Standardization:** All resolvers return data in the same format, so clients know how to decode results * **Type safety:** The function signature in `data` specifies what type of data to return * **Efficiency:** Binary encoding is more gas-efficient than string-based formats for onchain operations #### 4.5. Algorithms ##### 4.5.1. Namehash Algorithm * **Input:** DNS-style name string (e.g., `"encoded_address.76.reverse"`) * **Process:** Recursive hash of labels from right to left * **Output:** `bytes32` node identifier * **Standard:** ENSIP-1 namehash specification ##### 4.5.2. Chain-Specific State Proof Verification * **Algorithm:** Varies by chain type: * **Cosmos:** IAVL tree proof verification + state root comparison * **Solana:** Sparse Merkle Tree proof verification * **Purpose:** Validates non-EVM chain state without requiring full chain execution on L1 * **Implementation:** Bridge verifier contracts (chain-specific implementations) #### 4.6. Proof Generation & Validation Flow ##### 4.6.1. Proof Generation (Trustless Gateway Model) **Generation (Trustless Gateway Model):** 1. **Client/Gateway** queries non-EVM chain RPC for registrar state 2. **Gateway** constructs state proof structure: * For Cosmos: Fetches IAVL tree proof of registrar storage from chain RPC * For Solana: Fetches Sparse Merkle Tree proof of account state 3. **Gateway** packages proof with state root, block number, and chain metadata 4. **Gateway** returns **full proof structure** via CCIP-Read response (does not validate) 5. **Note:** Gateway is untrusted—it may return invalid proofs, but they will be rejected onchain **Validation (Trustless Gateway Model - Dual Path):** 1. **Resolver** receives proof via `ccipCallback` 2. **Resolver** validates proof natively (fast path): * **Namechain resolvers can validate pre-settled TEE proofs directly in CCIP callbacks** * TEE proofs are cryptographic attestations of L2 state that have already been verified and settled * This enables native validation without requiring cross-chain calls to L1 bridge verifiers 3. **Resolver** falls back to L1 DA + settled ZK proofs (secure path): * If TEE proof validation fails or is unavailable * **Resolver calls bridge verifier contract on Namechain** with full proof via cross-chain call * **Verifier checks onchain:** * Proof is valid (chain-specific proof verification) * State root matches L1-committed root * Block height is recent enough (within validity window) 4. **Verifier** returns validated state value (or reverts if invalid) 5. **Resolver** uses value to complete resolution 6. **Security:** Gateway cannot lie—invalid proofs are rejected by verifier (TEE or onchain) #### 4.7. Comparison to Other Proof Systems (DNSSEC, EVM Rollups) | Aspect | Non-EVM State Proofs | DNSSEC P-256 | EVM Rollup Proofs | | ---------------- | -------------------------------- | ------------------------ | ------------------------------ | | **Proof Type** | Chain-specific Merkle proofs | Cryptographic signatures | Merkle/SNARK proofs | | **Primitive** | Chain-specific (IAVL, SPV, SMT) | ECDSA P-256 | EVM state + bridge proofs | | **Verification** | Bridge verifier contracts | P-256 precompile | Bridge verifier contracts | | **Trust Anchor** | Ethereum L1 + Non-EVM consensus | DNS root KSK | Ethereum L1 + rollup consensus | | **Proof Format** | Chain-specific Merkle structures | DNSKEY/DS/RRSIG chain | Storage/Merkle proofs | | **Gas Cost** | Medium (100k-300k gas) | Low (with EIP-7951) | Medium-High (200k-500k gas) | **Key Difference:** Non-EVM proofs require chain-specific cryptographic primitives and bridge infrastructure, unlike EVM rollups which can reuse Ethereum's ECDSA and state proof infrastructure. *** ### 5. Rules & Lifecycle #### 5.1. High-Level Rules for Primary Names To establish a primary name for an address on a non-EVM chain: * Set the address for the name (forward resolution) * Set the reverse record for the address (reverse resolution) * The primary name is valid only if reverse and forward mappings are consistent **Lifecycle operations:** * **Register:** Map address → name via the relevant registrar * **Update:** Change mappings (either forward or reverse) * **Remove:** Unset mappings (e.g., unset resolver or name) * **Fallback:** If chain-specific primary name is missing, resolution fails (no default fallback for non-EVM) #### 5.2. Reverse Resolution Rules Given `(addressBytes, coinType)` where `chainFromCoinType(coinType) = 0`: 1. Compute `[addressAsHex]` from `addressBytes` (using chain-specific encoding) 2. Compute `[coinTypeAsHex]` and `chainFromCoinType(coinType)` 3. Build `reverseName` based on `coinType`: `"[encodedAddress].[coinTypeAsHex].reverse"` 4. Compute `reverseNode = namehash(reverseName)` 5. Call `name = resolve(reverseName, abi.encodeCall(INameResolver.name, (reverseNode)))` 6. If `name` is empty: no primary name exists; stop and display the address 7. If `name` is non-empty: continue to forward verification (see Section 6) #### 5.3. Address Format Standardization **Problem:** Non-EVM chains use different address formats that don't directly map to ENS's `addressBytes` format. **Solution:** Address encoding/decoding layer: 1. **Encoding (for ENS storage)**: Convert native address → standardized `addressBytes` format 2. **Decoding (for resolution)**: Convert `addressBytes` → native address format for chain queries **Example Encoding Schemes:** | Chain | Native Format | ENS Format | Encoding | | ------ | ---------------------------------------------- | -------------- | -------------------------------- | | Cosmos | `cosmos1abc123...` | Variable bytes | Decode bech32 → bytes | | Solana | `9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM` | 32 bytes | Decode base58 → public key bytes | *** ### 6. Verification Path #### 4.1 Component Responsibilities **Gateway Responsibilities:** 1. **Query non-EVM chain**: Gateway queries non-EVM chain RPC for registrar contract/account state 2. **Fetch state proof**: Gateway requests Merkle proof of storage value from chain's RPC 3. **Package proof bundle**: Gateway packages proof with state root, block height, and chain metadata 4. **Return via CCIP-Read**: Gateway returns proof bundle to client **Untrusted role:** Gateway can lie; invalid proofs are rejected onchain by bridge verifier. *** **Bridge Verifier Contract Responsibilities (on Namechain):** 1. **State root storage**: Stores state root commitments from bridge contract 2. **Proof validation**: Validates Merkle proofs against committed state roots 3. **Chain-specific verification**: Implements chain-specific proof verification logic (IAVL tree, SPV proofs, etc.) 4. **Validity window checks**: Verifies block height/timestamp is within acceptable window 5. **Return validation result**: Returns `(bool valid, bytes value)` to resolver **Trusted role:** Verifier contract is the security boundary; all proof validation happens onchain. *** **Resolver Contract Responsibilities (on L1):** 1. **Receive reverse resolution request**: Resolver receives request for `(address, coinType)` → name 2. **Trigger CCIP-Read** (if needed): Resolver reverts with OffchainLookup to fetch proof from gateway 3. **Validate state proof**: Resolver calls bridge verifier to validate proof 4. **Extract name**: If valid, resolver extracts name from proof 5. **Forward verification**: Resolver verifies forward mapping (`name → address`) matches 6. **Return result**: If both directions match, return name; otherwise revert *** **Key principle:** Gateway provides proof data (untrusted), bridge verifier validates proofs onchain (trustless), resolver orchestrates verification (trustless). #### 6.1. Generic Primary Name Verification (Two-Phase Flow) Given `(addressBytes, coinType)` where `chainFromCoinType(coinType) = 0` (non-EVM chain): **1. Reverse Phase** 1. Compute `[addressAsHex]` from `addressBytes` (using chain-specific encoding) 2. Compute `[coinTypeAsHex]` and verify `chainFromCoinType(coinType) = 0` 3. Build `reverseName` according to chain-specific encoding: `"[encodedAddress].[coinTypeAsHex].reverse"` 4. `reverseNode = namehash(reverseName)` 5. Call `name = resolve(reverseName, abi.encodeCall(INameResolver.name, (reverseNode)))` 6. If `name` is empty → **no primary name**; abort and display the address **2. Forward Phase** 1. Compute `node = namehash(name)` 2. Resolve `resolvedAddress = resolve(name, abi.encodeCall(IAddressResolver.addr, (node, coinType)))` 3. Compare the resulting address to `addressBytes` 4. If equal → **`name` is a verified primary name** for `(addressBytes, coinType)` * If not equal → treat as **invalid** (mismatch between reverse and forward) #### 6.2. Resolver / Gateway Topology The following actors participate in non-EVM primary name resolution: * **ENS Registry (L1)**: Maintains resolver assignments for all namespaces * **ENSIP-10 wildcard resolvers**: Chain-specific resolvers on **Namechain** for `"[coinTypeAsHex].reverse"` namespaces with **dual-path proof validation** * **Bridge verifiers on Namechain**: Validate non-EVM state proofs against L1-committed state roots (secure fallback) * **State bridges**: Commit non-EVM chain state roots to Ethereum L1 * **Chain registrars**: Per-chain contracts/accounts maintaining address → name mappings * **CCIP-Read gateways**: Fetch proof data from non-EVM chains with **preconfirmation support** * **Based preconfirmers**: L1 validators providing preconfirmations for fast TEE proof generation * **Trusted Execution Environments (TEEs)**: Generate pre-settled proofs for native validation #### 6.3. Minimal Client-Side Pseudocode A simplified client-side function for "get primary name": ```typescript async function getPrimaryName(addressBytes: Uint8Array, coinType: bigint): Promise { // Encode address for ENS (chain-specific) const encodedAddress = encodeAddressForENS(addressBytes, coinType); const addressAsHex = toLowerHex(encodedAddress); const coinTypeAsHex = coinType.toString(16); // Build reverse name const reverseName = `${addressAsHex}.${coinTypeAsHex}.reverse`; const reverseNode = namehash(reverseName); // Call resolver (may trigger CCIP-Read) const name = await ensip10ResolveName(reverseName, reverseNode); if (!name) return null; // Forward verification const node = namehash(name); const resolved = await resolverAddr(node, coinType); if (!resolved) return null; return bytesEqual(resolved, encodedAddress) ? name : null; } ``` #### 6.4. Chain-Specific Example: Cosmos Chain ##### 6.4.1. Prerequisites & Contract Inventory **Cosmos-Specific Implementation:** For Cosmos integration: **Existing Infrastructure:** 3 contracts (ENS Registry, Bridge Verifier, Universal Resolver) **Cosmos-Specific Contracts:** 3 new contracts (Cosmos Registrar, Cosmos State Bridge, Cosmos Resolver) ##### 6.4.2. Resolution Flow (Trustless Gateway Model) **Setup Phase:** 1. Deploy **Cosmos Registrar** contract on Cosmos chain 2. Deploy **Cosmos State Bridge** on Cosmos → Ethereum L1 bridge 3. Deploy **Cosmos Resolver** on **Namechain (Surge)**, configured for `"76.reverse"` namespace with **dual-path proof validation** 4. **Submit ENS DAO governance proposal** to register Cosmos Resolver at `namehash("76.reverse")` 5. **ENS DAO approves and executes** registration **Resolution Flow:** Leverages **based preconfirmations and TEE proofs** for low-latency CCIP-Read with Cosmos-specific parameters: * **Reverse Name:** `"encoded_cosmos_address.76.reverse"` (Cosmos coinType = `118` = `0x76`) * **Cosmos Registrar:** Stores `address → name` mappings on Cosmos chain * **Cosmos State Bridge:** Commits Cosmos IAVL state roots to Ethereum L1 * **Cosmos Resolver:** Handles resolution on Namechain with **dual-path validation**: * **Fast path:** Native TEE proof validation in CCIP callbacks (seconds) * **Secure path:** Fallback to L1 DA + settled ZK proofs * **Proof Type:** IAVL tree proof with TEE attestation **Key Cosmos-Specific Characteristics:** * **Cost-effective:** Resolver operations on Namechain (\~$0.01-0.30 per operation) * **High performance:** CCIP-Read latency reduced from hours to seconds using based preconfirmations * **Security:** Inherits Cosmos validator security + Ethereum L1 finality with slashable preconfirmers * **Flexible validation:** Applications can choose latency vs security tradeoffs ##### 6.4.3. Gateway Implementation Requirements **Critical:** Trustless gateways must be built according to the proof system of the chain they're interacting with. **Chain-Specific Proof Systems:** **1. Cosmos (IAVL Tree):** * **Proof Format:** IAVL tree proof of storage slot * **RPC Method:** `abci_query` with proof request * **Proof Structure:** ``` { stateRoot: bytes32, storageSlot: bytes32, storageValue: bytes32, merkleProof: bytes[], blockHeight: uint256 } ``` * **Bridge Verifier:** Validates IAVL proof against L1-committed state root **3. Solana (Sparse Merkle Tree):** * **Proof Format:** Sparse Merkle Tree proof of account state * **RPC Method:** `getAccountInfo` with proof request * **Proof Structure:** ``` { stateRoot: bytes32, accountAddress: bytes, accountData: bytes, merkleProof: bytes[], blockHeight: uint256 } ``` * **Bridge Verifier:** Validates account state proof **Gateway Implementation Considerations:** * **Multi-Chain Support:** A single gateway can support multiple chains, but requires chain-specific logic for each * **Resolver Configuration:** Each chain resolver must be configured with the correct bridge verifier address * **Proof Compatibility:** The gateway must construct proofs in the exact format expected by the chain's bridge verifier #### 4.4 Minimal Pseudocode (Client-Side) ```typescript // Type definitions interface NonEVMStateProof { stateRoot: string; blockHeight: number; accountAddress: string; storageKey: string; storageValue: string; // name merkleProof: string[]; blockHash: string; timestamp: number; chainId: string; } interface ReverseResolutionResult { name: string | null; valid: boolean; } // Reverse resolution for non-EVM chains async function resolveNonEVMPrimaryName( address: string, // Native address format (e.g., Cosmos bech32, Solana base58) coinType: number, // SLIP-44 coin type resolverAddress: string, // NonEVMResolver on L1 chainId: string // Chain identifier (e.g., "cosmos", "solana") ): Promise { // Step 1: Encode address to ENS format const addressBytes = encodeAddressForENS(address, coinType); const addressAsHex = bytesToHex(addressBytes).replace('0x', ''); const coinTypeAsHex = coinType.toString(16); // Step 2: Build reverse name const reverseName = `${addressAsHex}.${coinTypeAsHex}.reverse`; const reverseNode = namehash(reverseName); // Step 3: Call resolver (may trigger CCIP-Read) const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, provider); try { let name: string; try { // Try direct resolution first name = await resolver.name(reverseNode); } catch (error) { if (isOffchainLookupError(error)) { // Handle CCIP-Read for state proof fetching const lookup = parseOffchainLookupError(error); const proof = await fetchStateProofViaCCIP(lookup, address, coinType, chainId); // Call callback with proof name = await resolver.ccipCallback( encodeProofBundle(proof), lookup.extraData ); } else { throw error; } } // Step 4: Verify forward mapping const forwardNode = namehash(name); const resolvedAddress = await resolver.addr(forwardNode, coinType); if (resolvedAddress.toLowerCase() !== addressBytes.toLowerCase()) { return { name: null, valid: false }; } return { name, valid: true }; } catch (error) { console.error('Reverse resolution failed:', error); return { name: null, valid: false }; } } // Fetch state proof via CCIP-Read async function fetchStateProofViaCCIP( lookup: OffchainLookupError, address: string, coinType: number, chainId: string ): Promise { const response = await fetch(lookup.urls[0], { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ callData: lookup.callData, address, coinType, chainId }) }); if (!response.ok) { throw new Error(`Gateway error: ${response.statusText}`); } const data = await response.json(); return decodeProofBundle(data.proofBundle); } // Helper functions function encodeAddressForENS(address: string, coinType: number): Uint8Array { // Chain-specific encoding logic // Cosmos: Decode bech32 → bytes // Solana: Decode base58 → 32 bytes // Implementation depends on coinType } function isOffchainLookupError(error: any): boolean { return error?.errorName === 'OffchainLookup' || error?.message?.includes('OffchainLookup'); } function parseOffchainLookupError(error: any): OffchainLookupError { return { sender: error.args[0], urls: error.args[1], callData: error.args[2], callbackFunction: error.args[3], extraData: error.args[4], }; } function encodeProofBundle(proof: NonEVMStateProof): string { // ABI-encode proof bundle for contract call } function decodeProofBundle(hex: string): NonEVMStateProof { // Decode hex-encoded proof bundle } ``` #### 4.5 Example Implementation Flow **Scenario:** Enabling primary name reverse resolution for Cosmos chain (`coinType = 118`) **Step 1: Deploy Cosmos Registrar Contract** Deploy registrar contract on Cosmos chain: ```solidity // Cosmos Registrar Contract (Cosmos SDK smart contract) contract CosmosRegistrar { mapping(bytes => string) public reverseNames; // address → name function setName(bytes calldata address, string calldata name) external { require(msg.sender == address, "Only address owner can set name"); reverseNames[address] = name; } function name(bytes calldata address) external view returns (string memory) { return reverseNames[address]; } } ``` **Step 2: Deploy Cosmos → Ethereum State Bridge** Deploy bridge contract on Ethereum L1 that commits Cosmos state roots: ```solidity // Cosmos State Bridge Contract (on L1) contract CosmosStateBridge { struct StateRootCommitment { bytes32 stateRoot; uint64 blockHeight; uint64 timestamp; } mapping(uint64 => StateRootCommitment) public commitments; address[] public bridgeOperators; // Multisig or validator set function commitStateRoot( bytes32 stateRoot, uint64 blockHeight, bytes[] calldata signatures ) external { require(validateSignatures(stateRoot, blockHeight, signatures), "Invalid signatures"); commitments[blockHeight] = StateRootCommitment({ stateRoot: stateRoot, blockHeight: blockHeight, timestamp: block.timestamp }); } } ``` **Step 3: Deploy Cosmos Bridge Verifier** Deploy verifier contract on L1 that validates IAVL tree proofs: ```solidity // Cosmos Bridge Verifier (on L1) contract CosmosBridgeVerifier { CosmosStateBridge public bridge; function verifyIAVLProof( bytes32 stateRoot, bytes memory key, bytes memory value, bytes[] memory proof, uint64 blockHeight ) external view returns (bool) { // Verify state root matches committed root bytes32 committedRoot = bridge.commitments(blockHeight).stateRoot; require(stateRoot == committedRoot, "State root mismatch"); // Verify IAVL tree proof (implementation depends on IAVL library) return verifyIAVLTreeProof(stateRoot, key, value, proof); } } ``` **Step 4: Deploy NonEVMResolver** Deploy resolver on L1 that uses bridge verifier: ```solidity // NonEVMResolver (on L1) contract NonEVMResolver { CosmosBridgeVerifier public verifier; function name(bytes32 node) external view returns (string memory) { // Extract address and coinType from reverse node // Fetch state proof via CCIP-Read // Validate proof via verifier // Return name } } ``` **Step 5: User Sets Reverse Mapping** User sets reverse mapping on Cosmos: ``` User → CosmosRegistrar.setName(cosmosAddress, "alice.eth") ``` **Step 6: Bridge Commits State Root** Bridge operator commits Cosmos state root to L1: ``` Bridge Operator → CosmosStateBridge.commitStateRoot(cosmosStateRoot, blockHeight, signatures) ``` **Step 7: Reverse Resolution Flow** 1. Client requests: `resolve("cosmos1abc123....800076.reverse", nameCallData)` 2. Resolver triggers CCIP-Read for state proof 3. Gateway queries Cosmos RPC for IAVL proof of registrar storage 4. Gateway returns proof bundle 5. Resolver validates proof via CosmosBridgeVerifier 6. Verifier checks proof against L1-committed state root 7. If valid → return "alice.eth" 8. Resolver verifies forward: `addr("alice.eth", 118) == cosmosAddress` 9. If match → return "alice.eth"; otherwise revert *** ### 7. Contract & Namespace Inventory #### 7.1. Core Constants & Helpers * `DEFAULT_EVM_COIN_TYPE = 0x8000_0000` * `ETHEREUM_MAINNET_COIN_TYPE = 60` * `chainFromCoinType(coinType)` as defined in Section 4.1 * **Chain Identifiers:** `CHAIN_ID_COSMOS = "cosmos"`, `CHAIN_ID_SOLANA = "solana"` * **Coin Types (SLIP-44):** `COIN_TYPE_BITCOIN = 0`, `COIN_TYPE_COSMOS = 118`, `COIN_TYPE_SOLANA = 501` #### 7.2. Reverse Namespaces & Registrars | Chain | CoinType | `reverseNamespace` | Registrar Contract | | ---------- | -------- | ------------------ | --------------------------- | | **Cosmos** | `118` | `"76.reverse"` | CosmosRegistrar (on Cosmos) | | **Solana** | `501` | `"1f5.reverse"` | SolanaRegistrar (on Solana) | #### 7.3. Resolver Types ##### 7.3.1. Chain-Specific Resolvers (Namechain) ENSIP-10 wildcard resolvers for non-EVM chains that: * Deployed on **Namechain** (ENS L2) for cost-effective operations * Accept CCIP-Read requests for `name()` of `"[encodedAddress].[coinTypeAsHex].reverse"` * **Validate state proofs natively** via bridge verifier calls * Fallback to default primary name if chain-specific name is missing ### 8. URM Mapping (Resolver Profiles) This profile defines the non-EVM resolver profile for the Universal Resolver Matrix. It extends ENSIP-19 to support primary name resolution for non-EVM chains using a trustless gateway model with state proof validation. | Profile | Scope | Trust Model | Proof System | Rules | Verification Path | | ------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | **non-evm-primary-names** | Non-EVM chains (Cosmos, Solana) with `chainFromCoinType(coinType) = 0` | Non-EVM chain consensus + L1 state commitment bridge + ENS Registry | [`non_evm_state_proof(chainId)`](#432-non_evm_state_proof-chainid) - Chain-specific Merkle proofs (IAVL, SMT) | Reverse: `"[encodedAddress].[coinTypeAsHex].reverse"`, Forward: `addr(node, coinType)`, no default fallback | CCIP-Read → Gateway → Bridge verifier validates non-EVM state proofs against L1-committed state roots | #### 8.1. Profile Definition: non-evm-primary-names **Scope:** * Enables primary name reverse resolution (`address → name`) for non-EVM blockchains from SLIP-44 * Extends ENSIP-19 to support bidirectional resolution for Cosmos, Solana, and other non-EVM chains * Requires chain-specific infrastructure: state bridges, registrars, verifiers per chain * Use case: Cross-chain identity resolution, unified naming across all blockchains **Trust Model:** * **Root of trust:** Two-tier model: (1) Non-EVM chain consensus (validators/miners), (2) Ethereum L1 consensus * **Bridge trust:** State commitment bridge connects non-EVM chain state to L1 * **Verification:** State proofs validated onchain via bridge verifier against L1-committed state roots * **Security:** Inherits security from both chain consensuses, bridge correctness, and Ethereum L1 finality **Namespace:** * Reverse namespaces per SLIP-44 coin type: `"[encodedAddress].[coinTypeAsHex].reverse"` * Examples: Cosmos (`coinType = 118`), Solana (`coinType = 501`) * Forward resolution uses standard ENS namespaces (`.eth`, subdomains, etc.) **Proof System:** * `non_evm_state_proof(chainId)` — Non-EVM chain state proof * Format varies by chain: IAVL tree proofs (Cosmos), Sparse Merkle Tree proofs (Solana) * Validated against L1-committed state roots via bridge verifier **Verification Path:** 1. **Client initiates:** `resolve("[encodedAddress].[coinTypeAsHex].reverse", nameCallData)` 2. **ENS Registry routes:** Universal Resolver → ENS Registry → NonEVMResolver (Namechain) 3. **CCIP-Read triggered:** Resolver detects need for non-EVM state, returns CCIP-Read request with based preconfirmation 4. **Gateway fetches proof:** Gateway queries non-EVM chain RPC + requests preconfirmation from L1 validators 5. **Pre-settled proof generation:** Preconfirmer generates TEE proof for preconfirmed block state 6. **Gateway returns proof:** Full proof bundle with TEE proof returned via CCIP-Read response 7. **Resolver validates (fast path):** Resolver validates pre-settled TEE proof **natively in CCIP callback** (seconds latency) 8. **Resolver validates (secure path):** Falls back to `bridgeVerifier.verifyStateProof(proof)` on Namechain if TEE validation fails 9. **Bridge verifier checks:** Validates proof against L1-committed state root (onchain validation) 10. **Result returned:** If valid → name returned; if invalid → revert 11. **Forward verification:** Client resolves `addr(name, coinType)`, compares to `addressBytes` **Trust Anchor:** Ethereum L1 consensus (ENS Registry + slashable preconfirmers) + Namechain consensus (bridge verifier contracts). Non-EVM chain state is proven to L1 via state commitment bridge. **Dual-path validation** enables applications to balance latency vs security: TEE proofs for speed, ZK proofs for maximum security. Final trust is Ethereum L1 consensus + Namechain consensus + non-EVM chain consensus. ### 9. Edge Cases & Client Requirements #### 9.1. Chain-Specific Edge Cases **Smart Contract Chains (Cosmos, Solana):** * Registrar contracts store reverse mappings directly * State proofs use native Merkle trees (IAVL, Sparse Merkle Tree) * Address formats need encoding/decoding for ENS compatibility **Address Format Edge Cases:** * **Encoding errors:** Invalid address encodings should be rejected gracefully * **Length variations:** Different chains have different address lengths requiring standardization #### 9.2. State Root Commitment Edge Cases * **Stale state roots:** State roots must be recent enough (within validity window) * **Missing state roots:** If bridge hasn't committed state root for requested block height, resolution fails * **Bridge downtime:** If bridge is unavailable, reverse resolution cannot proceed (availability dependency) #### 9.3. Client Requirements Clients **must**: * **Handle CCIP-Read errors:** If resolver reverts with OffchainLookup, perform HTTP request to gateway * **Validate address formats:** Verify address format matches expected coinType before resolution * **Verify forward mappings:** Always verify forward mapping matches reverse result (two-phase verification) * **Handle chain-specific encoding:** Encode/decode addresses according to chain-specific formats * **Check state root freshness:** Verify state root is recent enough (within validity window) Clients **must not**: * **Skip forward verification:** Don't trust reverse resolution alone; always verify forward mapping * **Assume address format:** Don't assume address format; decode according to coinType * **Cache stale proofs:** Don't cache state proofs beyond validity window * **Ignore bridge status:** Don't attempt resolution if bridge hasn't committed recent state roots *** :::warning **Educational Content**: This document presents hypothetical WebAuthn integration concepts for research purposes only. ::: ### 1. Introduction #### 1.1. Document Purpose This document provides a comprehensive technical reference for implementing WebAuthn (passkeys) as a profile in the Universal Resolver Matrix (URM). It expands the normative specification in WebAuthn Resolver Profile (ENSIP-XX) into a single technical reference for implementing WebAuthn resolution as resolver profiles in the URM. #### 1.2. Universal Resolver Matrix Framework The Universal Resolver Matrix (URM) is a systematic framework for mapping resolution pathways across namespaces using four core dimensions: 1. **Trust Model**, 2. **Proof System**, 3. **Rules & Lifecycle**, and 4. **Verification Path**. This document structures WebAuthn implementation around these dimensions, with additional sections covering implementation details: * Contract & Namespace Inventory * URM Mapping (Resolver Profiles) * Edge Cases & Client Requirements ### 2. Scope & Goals #### 2.1. WebAuthn Overview WebAuthn enables passkeys as ENS controllers and credential-based authentication for ENS names. It provides a standardized, cryptographically secure way to use WebAuthn ES256 signatures (P-256 ECDSA) for ENS name control and authentication via a two-phase verification process. #### 2.2. Profile Scope WebAuthn resolution defines two resolver profiles: 1. **Profile 1 (Controller)**: Passkeys as ENS controllers — enables WebAuthn credentials to control and manage ENS names (update records, set resolver, etc.) 2. **Profile 2 (Credential Resolution)**: WebAuthn credential resolution — enables resolving/verifying ENS names that have WebAuthn credentials stored in their records #### 2.3. WebAuthn vs Non-WebAuthn Credentials WebAuthn resolution provides trustless onchain verification on Namechain. For Profile 2, an optional hybrid approach is available for cost-sensitive applications: **Primary Approach (Trustless)**: Direct onchain verification on Namechain. **Optional Hybrid Approach (Cost-Efficient)**: Gateway verifies offchain, stores attestations onchain for high-frequency use cases. #### 2.4. WebAuthn Scope & Algorithm Support This document focuses exclusively on WebAuthn ES256 (P-256 ECDSA) credential verification. WebAuthn is uniquely positioned for onchain verification because it uses the P-256 elliptic curve, which has dedicated precompile support (EIP-7951) on both Ethereum L1 and Namechain. Other credential types (RS256, EdDSA, ES384, etc.) cannot be verified onchain without additional precompiles or offchain services, breaking the trustless model that WebAuthn enables. #### 2.5. Deployment Architecture: Full Namechain Since Namechain launches with EIP-7951 (P-256 precompile) support, WebAuthn uses a fully onchain deployment on Namechain (ENS L2). The system requires exactly **[3 core contracts](#25-deployment-architecture-full-namechain)** deployed entirely on Namechain. ##### 2.5.1. Architecture Overview All WebAuthn infrastructure deploys on Namechain (ENS L2) for cost-effective, trustless operations: | Deployment Location | Component | Purpose | | ---------------------- | -------------------------- | ------------------------------------------------------------------- | | **Namechain (ENS L2)** | WebAuthnVerifier | Validates WebAuthn ES256 signatures using EIP-7951 P-256 precompile | | **Namechain (ENS L2)** | WebAuthnControllerResolver | Stores public key bindings, enables passkey control of ENS names | | **Namechain (ENS L2)** | WebAuthnCredentialResolver | Fetches credentials from ENS records, verifies assertions | **Why Full Namechain Architecture:** * **EIP-7951 at launch**: Namechain includes P-256 precompile from day one * **Cost-effectiveness**: All operations at Namechain gas prices * **Trustlessness**: Full onchain verification with L1 state commitment * **Simplicity**: No cross-chain calls, direct onchain operations * **ENSv2 native**: Part of the core ENS L2 infrastructure ##### 2.5.2. Contract Inventory (3-Contract Pattern) **Existing Infrastructure (0 Contracts - No Deployment Needed):** * ENS Registry (L1) * Universal Resolver (L1) **New Contracts (3 Contracts - All on Namechain):** 1. **WebAuthnVerifier (Namechain)** - Validates WebAuthn ES256 signatures using EIP-7951 precompile 2. **WebAuthnControllerResolver (Namechain)** - Stores public key bindings for passkey control 3. **WebAuthnCredentialResolver (Namechain)** - Handles credential resolution with direct onchain verification ##### 2.5.3. Roots of Trust & Security The canonical root of trust consists of: * **ENS Registry on Ethereum L1**: Primary source of truth for all name registrations and resolver assignments * **WebAuthnVerifier on Namechain**: Trustless cryptographic verification using EIP-7951 P-256 precompile * **Resolver contracts on Namechain**: Store public key bindings and handle verification logic Security is inherited from Ethereum consensus, P-256 cryptographic security, and WebAuthn standard security guarantees. All WebAuthn contracts deploy on Namechain (ENS L2) from launch, with state committed to L1 providing the same security as L1 infrastructure. ##### 2.5.4. Benefits & Tradeoffs This full Namechain architecture provides: * **Ultra-low costs**: All operations at Namechain gas prices * **Simplified architecture**: No cross-chain calls, direct onchain verification * **Full trustlessness**: All operations on Namechain (ENS L2, state committed to L1) * **Performance**: Fast verification without cross-chain bridge delays * **ENSv2 native**: Core infrastructure deployed on ENS L2 from launch * **Gateway optional**: With virtually free verification costs, gateway approaches may be unnecessary for most use cases ##### 2.5.5. Governance Requirements While not strictly required for basic resolver operation, a production-ready WebAuthn resolver should go through ENS DAO governance for integration into core ENS infrastructure. DAO approval is required if you want your resolver to be: * Set as the default resolver for ENS (e.g., for the reverse registrar or for .eth names) * Integrated at the protocol level (e.g., replacing the public resolver, or being referenced in official ENS contracts) **Why Governance Matters:** DAO approval is a matter of governance and trust. If you want your resolver to be widely used, trusted, or set as a default in ENS infrastructure, DAO approval: * Signals community trust and security review * Ensures the resolver is maintained and meets ENS standards * Protects against unauthorized trust anchor modifications during key rollovers **WebAuthn Specific Considerations:** For WebAuthn implementations, governance approval is particularly important due to the novel authentication paradigm and hardware security dependencies. DAO approval ensures that WebAuthn resolvers meet community standards for user experience, security, and compatibility with existing ENS infrastructure while pioneering passkey-based name control. ### 3. Trust Model #### 3.1. Roots of Trust The canonical root of trust for WebAuthn resolution differs between profiles but shares the same cryptographic verification infrastructure: **Profile 1 (Controller) - Root of Trust:** * **ENS name owner's authorization** — The ultimate source of trust is the ENS name owner's explicit authorization stored onchain. When a name owner registers a WebAuthn public key via `registerWebAuthnKey()`, this creates an onchain binding: `namehash("alice.eth") → publicKey`. This binding is stored in the `WebAuthnControllerResolver` contract and can only be created or modified by the ENS name owner (verified via `ensRegistry.owner(node) == msg.sender`). **Profile 2 (Credential Resolution) - Root of Trust:** * **ENS record itself** — The root of trust is the ENS text record containing the WebAuthn credential. When a name owner sets `text(node, "webauthn-credential")` = public key, this creates an implicit binding: the credential exists in the ENS record for that name. The name owner controls this record (via standard ENS ownership), so the credential's presence in the record serves as proof of authorization. **Shared Cryptographic Root:** * **P-256 ECDSA signatures** — Both profiles rely on WebAuthn ES256 signatures (ECDSA over P-256 curve) verified onchain via the EIP-7951 P-256 precompile. The cryptographic security of P-256 ECDSA is the foundation for all signature verification. #### 3.2. Security Guarantees Security properties include: * **Cryptographic authenticity** — WebAuthn signatures prove that assertions originated from the device holding the private key corresponding to the registered public key * **Integrity protection** — WebAuthn assertions include authenticatorData and clientDataJSON, which are hashed and signed * **Non-repudiation** — Users cannot deny having signed an assertion * **Trust minimization** — Security depends only on ENS name ownership, onchain authorization, and Ethereum consensus * **Onchain verification** — All cryptographic signature verification happens onchain via the EIP-7951 P-256 precompile #### 3.3. Trust Assumptions by Profile ##### 3.3.1. Profile 1 (Controller) Users trust the `WebAuthnControllerResolver` contract on Namechain correctly stores authorized public keys and only allows authorized passkeys to update ENS records. The resolver verifies name ownership via ENS Registry on L1 and cryptographic signatures via WebAuthnVerifier on Namechain. ##### 3.3.2. Profile 2 (Credential Resolution) **Primary Approach**: Users trust the cryptographic verification happens entirely onchain on Namechain using EIP-7951 precompile. Full trustlessness through Namechain (ENS L2, committed to L1). **Optional Hybrid Approach**: Users trust that gateway attestations stored onchain provide sufficient audit trail, with cryptographic verification of gateway signatures. Gateway signatures are verifiable onchain, allowing detection of misbehavior. ### 4. Proof System #### 4.1. Inputs & Core Definitions WebAuthn resolution takes a WebAuthn assertion as input, containing: * **authenticatorData**: 37+ byte structure containing flags, sign count, RP ID hash * **clientDataJSON**: JSON string containing challenge, origin, type * **signature**: 64 bytes (32-byte r, 32-byte s) ECDSA P-256 signature * **challenge**: Original challenge value for freshness verification * **publicKey**: P-256 public key coordinates (X, Y) for signature verification **Core Definitions:** * **`authenticatorData`**: Contains UP/UV flags, sign count, attested credential data, and extensions * **`clientDataJSON`**: JSON containing `type`, `challenge`, `origin`, and optional fields * **`challenge`**: Random nonce to prevent replay attacks, verified for freshness * **`signature`**: P-256 ECDSA signature over `SHA-256(authenticatorData || SHA-256(clientDataJSON))` * **`publicKeyX/Y`**: P-256 public key coordinates used for signature verification #### 4.2. Cryptographic Primitives ##### 4.2.1. ECDSA P-256 (ES256) WebAuthn uses ECDSA over the P-256 (secp256r1) curve for signature verification. The P-256 precompile (EIP-7951) enables efficient onchain verification (\~3,000 gas). Signatures are computed over the hash: `SHA-256(authenticatorData || SHA-256(clientDataJSON))`. ##### 4.2.2. SHA-256 Secure Hash Algorithm 256-bit, used for hashing authenticatorData and clientDataJSON. Implemented in Solidity for onchain verification (\~2,000-3,000 gas per hash). ##### 4.2.3. Keccak256 Ethereum's native hash function, used for challenge hashing and gateway attestation verification. Native precompile, very cheap (\~30 gas). #### 4.3. Proof Types (URM Terms) ##### 4.3.1. webauthn\_proof(es256) **When Used:** * Profile 1: When updating ENS records using passkey authentication * Profile 2: When authenticating to dApps or proving control of an ENS name **Proof Mechanism:** 1. User generates WebAuthn assertion using passkey 2. Resolver on Namechain calls WebAuthnVerifier directly on Namechain 3. Namechain verifier reconstructs hash: `SHA-256(authenticatorData || SHA-256(clientDataJSON))` 4. Verifies ECDSA P-256 signature using EIP-7951 precompile on Namechain 5. Validates challenge freshness, origin/RP ID 6. Returns verification result directly on Namechain **Example Flow:** ```mermaid flowchart TD Start([Client initiates action]) --> GenChallenge["Step 1: Generate Challenge
Location: dApp/Resolver
challenge = keccak256
(nonce + timestamp + random)"] GenChallenge --> UserSign["Step 2: User Signs with Passkey
Location: Browser/Device
navigator.credentials.get"] UserSign --> Assertion{"WebAuthn Assertion
authenticatorData
clientDataJSON
signature"} Assertion --> ResolverCall["Step 3: Call Resolver
Location: Namechain
WebAuthnControllerResolver.updateRecord()
or WebAuthnCredentialResolver.verifyCredential()"] ResolverCall --> FetchCred{"Fetch Credential"} FetchCred -->|Profile 1| OnchainKey["Read from authorizedKeys[node]
Namechain storage"] FetchCred -->|Profile 2| CCIPRead["CCIP-Read from ENS text record
if offchain"] OnchainKey --> VerifyCall["Step 4: Direct Verification Call
Location: Namechain
resolver → WebAuthnVerifier.verify()"] CCIPRead --> VerifyCall VerifyCall --> HashReconstruct["4.1: Reconstruct Hash
SHA-256(authenticatorData ||
SHA-256(clientDataJSON))
Cost: ~$0.005"] HashReconstruct --> SigVerify["4.2: Verify P-256 Signature
EIP-7951 precompile
Cost: ~$0.005"] SigVerify --> ChallengeCheck["4.3: Validate Challenge
Match clientDataJSON.challenge
Cost: ~$0.001"] ChallengeCheck --> FreshnessCheck["4.4: Check Freshness
Verify timestamp window
Cost: ~$0.001"] FreshnessCheck --> OriginCheck["4.5: Validate Origin
Verify RP ID matches
Cost: ~$0.001"] OriginCheck --> Result{"Verification Result"} Result -->|Valid| ProfileCheck{"Profile Type?"} Result -->|Invalid| Reject["Reject Request
Revert Transaction"] ProfileCheck -->|Profile 1| UpdateENS["Step 5: Execute ENS Update
Location: Namechain Resolver
Update addr/text/contenthash
Cost: ~$0.04-0.15"] ProfileCheck -->|Profile 2| AuthSuccess["Step 5: Authentication Success
Location: Namechain Resolver
Return success to dApp"] UpdateENS --> Success1([Profile 1: ENS Record Updated]) AuthSuccess --> Success2([Profile 2: User Authenticated]) Reject --> FailEnd([Transaction Failed]) Success1 --> SuccessEnd([Operation Complete]) Success2 --> SuccessEnd FailEnd --> EndError([End: Verification Failed]) style Start fill:#e1f5ff style VerifyCall fill:#fff4e1 style SigVerify fill:#ffcccc style Success1 fill:#d4edda style Success2 fill:#d4edda style SuccessEnd fill:#d4edda style Reject fill:#f8d7da style FailEnd fill:#f8d7da style EndError fill:#f8d7da ``` ##### 4.3.2. gateway\_attestation **When Used:** * Profile 2: Optional hybrid gateway + attestations for cost-sensitive applications **Proof Mechanism:** 1. Gateway verifies WebAuthn signature offchain (free) 2. Gateway creates attestation: `(node, challengeHash, timestamp, gatewaySignature)` 3. Gateway stores attestation onchain via resolver 4. Resolver verifies gateway signature using ecrecover 5. dApp reads attestation and verifies freshness **Example Flow:** ```mermaid flowchart TD Start([Client initiates action]) --> GenChallenge["Step 1: Generate Challenge
Location: dApp/Resolver
challenge = keccak256
(nonce + timestamp + random)"] GenChallenge --> UserSign["Step 2: User Signs with Passkey
Location: Browser/Device
navigator.credentials.get"] UserSign --> Assertion{"WebAuthn Assertion
authenticatorData
clientDataJSON
signature"} Assertion --> GatewayReceive["Step 3: Gateway Receives
Location: Gateway Service
Receives assertion + node + challenge"] GatewayReceive --> GatewayFetch["Step 4: Gateway Fetches Credential
Location: Gateway Service
Query ENS for public key
via CCIP-Read or direct query"] GatewayFetch --> ExtractKey["Extract publicKeyX, publicKeyY"] ExtractKey --> GatewayVerify["Step 5: Gateway Verifies Offchain
Location: Gateway Service
SHA-256 hash computation
ECDSA P-256 verification
Challenge + origin validation
Cost: Free (offchain)"] GatewayVerify --> VerifyResult{"Verification Result"} VerifyResult -->|Failed| Reject[Reject Request] VerifyResult -->|Success| CreateAttest["Step 6: Create Attestation
Location: Gateway Service
node + challengeHash + timestamp
Sign with gateway private key
secp256k1 signature"] CreateAttest --> StoreAttest["Step 7: Store Attestation Onchain
Location: Namechain Resolver
Gateway calls storeAttestation()"] StoreAttest --> VerifySig["7.1: Verify Gateway Signature
ecrecover on Namechain
Cost: ~$0.01"] VerifySig --> CheckTime["7.2: Validate Timestamp
Check freshness window
Cost: ~$0.001"] CheckTime --> StoreData["7.3: Store in Contract
SSTORE attestation
Cost: ~$0.06-0.15"] StoreData --> AttestStored["Attestation Stored
Total Cost: ~$0.07-0.17"] AttestStored --> ReadAttest["Step 8: dApp Reads Attestation
Location: Namechain Resolver
Optional immediate verification"] ReadAttest --> ReadStorage["8.1: Read from Storage
SLOAD attestation
Cost: ~$0.001-0.01"] ReadStorage --> CheckFresh["8.2: Verify Freshness
Check timestamp window"] CheckFresh --> AttestValid{"Attestation Valid?"} AttestValid -->|Invalid| ReadFail[Attestation Invalid] AttestValid -->|Valid| ProfileCheck{"Profile Type?"} ProfileCheck -->|Profile 1| UpdateENS["Step 9: Execute ENS Update
Location: Namechain Resolver
Update ENS records"] ProfileCheck -->|Profile 2| AuthSuccess["Step 9: Authentication Success
Location: Namechain Resolver
Grant access to user"] UpdateENS --> Success1([Profile 1: ENS Record Updated]) AuthSuccess --> Success2([Profile 2: User Authenticated]) Reject --> FailEnd([Request Rejected]) ReadFail --> FailEnd Success1 --> SuccessEnd([Operation Complete]) Success2 --> SuccessEnd FailEnd --> EndError([End: Authentication Failed]) EndError --> EndFinal([End]) style Start fill:#e1f5ff style GatewayReceive fill:#e8f4f8 style GatewayVerify fill:#e8f4f8 style CreateAttest fill:#e8f4f8 style StoreAttest fill:#fff4e1 style AttestStored fill:#ffcccc style Success1 fill:#d4edda style Success2 fill:#d4edda style SuccessEnd fill:#d4edda style Reject fill:#f8d7da style ReadFail fill:#f8d7da style FailEnd fill:#f8d7da style EndError fill:#f8d7da ``` #### 4.4. Proof Formats ##### 4.4.1. WebAuthn Assertion ``` { authenticatorData: bytes, // 37+ bytes - includes flags, counter, RP ID hash clientDataJSON: string, // JSON string - includes challenge, origin, type signature: bytes, // 64 bytes - ECDSA P-256 signature (r, s) challenge: bytes32, // SHA-256 hash of challenge publicKeyX: uint256, // P-256 public key X coordinate publicKeyY: uint256 // P-256 public key Y coordinate } ``` ##### 4.4.2. Gateway Attestation (Hybrid Approach) ``` { node: bytes32, // namehash("alice.eth") challengeHash: bytes32, // keccak256(challenge) timestamp: uint256, // block.timestamp when verified gatewaySignature: bytes // Gateway's ECDSA signature over the attestation } ``` :::info **Research Structures**: These JSON proof format examples represent conceptual structures for research purposes. Actual WebAuthn resolver implementations may use different data formats optimized for onchain verification and gas efficiency. ::: #### 4.5. Algorithms ##### 4.5.1. WebAuthn Signature Verification 1. **Input:** authenticatorData, clientDataJSON, signature, publicKeyX, publicKeyY, challenge 2. **Process:** * Reconstruct message hash: `hash = SHA-256(authenticatorData || SHA-256(clientDataJSON))` * Verify ECDSA P-256 signature using precompile * Extract challenge from clientDataJSON and verify freshness * Verify origin/RP ID matches expected value 3. **Output:** bool valid, bytes32 challenge ##### 4.5.2. Challenge Freshness Check 1. **Input:** challengeTimestamp, currentTimestamp 2. **Process:** Check age ≤ CHALLENGE\_FRESHNESS\_WINDOW (5 minutes) 3. **Output:** bool isFresh #### 4.6. Proof Generation & Validation Flow ##### 4.6.1. WebAuthn Proof Generation **Generation:** 1. Client generates challenge: `challenge = keccak256(nonce + timestamp + random)` 2. User signs via WebAuthn API: `navigator.credentials.get()` 3. Browser/device returns assertion: `{authenticatorData, clientDataJSON, signature}` **Validation:** 1. Resolver calls WebAuthnVerifier.verify() directly on Namechain 2. Namechain verifier performs cryptographic validation 3. Returns result directly on Namechain ##### 4.6.2. Gateway Attestation Generation (Hybrid) **Generation:** 1. Gateway receives WebAuthn assertion 2. Gateway verifies signature offchain using cryptographic libraries 3. Gateway creates attestation with secp256k1 signature 4. Gateway stores attestation onchain via resolver **Validation:** 1. Resolver verifies gateway signature using ecrecover 2. Checks attestation timestamp freshness 3. dApp reads attestation for authentication ### 5. Rules & Lifecycle #### 5.1. High-Level Rules for WebAuthn Credentials To establish a verified WebAuthn credential: 1. **Create WebAuthn credential**: User creates a passkey using their device 2. **Register credential onchain**: Name owner registers the public key onchain (Profile 1) or sets in text record (Profile 2) 3. **Credential verified and ready**: Once registered, the credential is available for verification **Lifecycle operations:** * **Register**: Name owner registers passkey (one-time setup, requires wallet/gas) * **Update**: Use passkey to update ENS records (ongoing, may require gas or use meta-transactions) * **Remove**: Revoke passkey authorization * **Expiry**: Challenges expire after 5 minutes to prevent replay attacks #### 5.2. WebAuthn-Specific Rules **Rule 1: Challenge Validation & Freshness** * Challenge must match exactly between clientDataJSON and provided challenge * Challenge must be fresh (within 5-minute window) **Rule 2: Origin/RP ID Verification** * Origin from clientDataJSON must match expected relying party * RP ID hash in authenticatorData must match expected identifier **Rule 3: Credential Binding & Public Key Matching** * Profile 1: Public key must match authorized key in resolver contract * Profile 2: Public key must match credential in ENS text record **Rule 4: Signature Verification** * Signature must be valid ECDSA P-256 signature over the computed hash * Only ES256 algorithm supported (other algorithms require offchain verification) #### 5.3. Credential Types | Credential Type | Usage in ENS | Security Notes | | ------------------- | ------------------------------------------------- | -------------------------------------------- | | **Passkey** | Platform-bound keys stored on user devices | High security, phishing-resistant | | **Security Key** | Cross-platform hardware keys | Maximum security, physical presence required | | **ES256 Algorithm** | Only supported algorithm for onchain verification | Uses P-256 curve, verified via EIP-7951 | ### 6. Verification Path #### 6.1. Component Responsibilities **WebAuthnVerifier (Namechain):** * Validates WebAuthn ES256 signatures using EIP-7951 P-256 precompile * Performs SHA-256 hashing, challenge validation, freshness checks * Returns verification results to calling resolvers **WebAuthnControllerResolver (Namechain):** * Stores authorized public keys for ENS names * Verifies name ownership via ENS Registry * Calls Namechain verifier for signature verification before executing ENS updates **WebAuthnCredentialResolver (Namechain):** * Fetches WebAuthn credentials from ENS text records * Supports direct onchain verification with optional hybrid gateway + attestations * Provides authentication success/failure to dApps **Gateway (Offchain):** * Fetches credentials via CCIP-Read (if needed) * Verifies signatures offchain for optional hybrid approach * Creates and stores attestations onchain #### 6.2. Generic WebAuthn Verification Flow Given a WebAuthn assertion: **Phase 1: Proof Generation** 1. Client generates challenge with timestamp 2. User signs challenge with passkey via WebAuthn API 3. Returns assertion containing authenticatorData, clientDataJSON, signature **Phase 2: Credential Resolution** 1. Resolver fetches public key (Profile 1: from contract storage, Profile 2: from text record) 2. Prepares verification request with assertion data **Phase 3: Verification** 1. **Direct Verification**: Resolver calls Namechain verifier directly 2. **Hybrid Approach**: Gateway verifies offchain, stores attestation onchain 3. Verification succeeds if signature valid and all checks pass #### 6.3. Resolver / Gateway Topology The WebAuthn system consists of: * **ENS Registry (L1)**: Manages resolver assignments * **WebAuthnVerifier (Namechain)**: Cryptographic verification * **WebAuthnControllerResolver (Namechain)**: Profile 1 implementation * **WebAuthnCredentialResolver (Namechain)**: Profile 2 implementation * **CCIP-Read Gateways**: Data fetching and attestation services #### 6.4. Minimal Client-Side Pseudocode ```typescript async function verifyWebAuthnAssertion( node: string, resolverAddress: string, profile: 'controller' | 'credential', action?: { type: 'setText', params: any } ): Promise { // Generate challenge const challenge = keccak256(nonce + timestamp + random); // Get WebAuthn assertion from user const assertion = await navigator.credentials.get({ publicKey: { challenge: challengeBytes, rpId, timeout: 60000 } }); // Extract assertion components const { authenticatorData, clientDataJSON, signature } = assertion.response; // Fetch credential const publicKey = await fetchCredential(node, resolverAddress); // Call resolver verification const resolver = new ethers.Contract(resolverAddress, ABI, signer); const result = await resolver.verifyCredential( node, authenticatorData, clientDataJSON, signature, publicKey.x, publicKey.y, challenge ); return result.valid; } ``` #### 6.5. Example Implementation Flow **Profile 1 (Controller) Setup:** 1. User creates passkey: `navigator.credentials.create()` 2. User registers public key: `registerWebAuthnKey(node, publicKeyX, publicKeyY)` 3. Binding stored onchain: `authorizedKeys[node] = (publicKeyX, publicKeyY)` 4. User can now update ENS records using passkey authentication **Profile 2 (Authentication) Flow:** 1. dApp requests authentication for "alice.eth" 2. Client generates challenge and prompts user for passkey 3. User signs with passkey 4. Resolver verifies signature (direct onchain or optional hybrid) 5. dApp grants access if verification succeeds ### 7. Contract & Namespace Inventory #### 7.1. Core Constants & Helpers * `ALGORITHM_ES256 = "ES256"` * `CURVE_P256 = "P-256"` * `SIGNATURE_LENGTH = 64` * `PUBLIC_KEY_LENGTH = 64` * `CHALLENGE_FRESHNESS_WINDOW = 300` (5 minutes) * `ATTESTATION_FRESHNESS_WINDOW = 300` (5 minutes) * `TEXT_KEY_WEBAUTHN_CREDENTIAL = "webauthn-credential"` #### 7.2. Namespaces WebAuthn works with standard ENS namespaces: * `.eth` primary names * Subdomains at any level * Reverse resolution support #### 7.3. Contract Types **WebAuthnVerifier (Namechain):** * Validates WebAuthn ES256 signatures * Uses EIP-7951 P-256 precompile * Shared by both profiles **WebAuthnControllerResolver (Namechain):** * Stores public key bindings for passkey control * Implements ENS resolver interface with WebAuthn verification **WebAuthnCredentialResolver (Namechain):** * Fetches credentials from ENS records * Supports dual verification approaches #### 7.4. Implementation Requirements (3-Contract Pattern) **Implementation Strategy:** 1. **WebAuthnVerifier** on Namechain (cryptographic verification using EIP-7951 at launch) 2. **WebAuthnControllerResolver** on Namechain (Profile 1 with direct verifier calls) 3. **WebAuthnCredentialResolver** on Namechain (Profile 2 with direct verifier calls) **Deployment Approach:** * All contracts deploy on Namechain from launch * Direct onchain verification calls * Gateway + attestations optional for cost-sensitive applications * Full trustlessness through Namechain (ENS L2, committed to L1) ### 8. URM Mapping (Resolver Profiles) WebAuthn defines two resolver profiles: | Profile | Scope | Trust Model | Proof System | Rules | Verification Path | | ----------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------- | -------------------------------------------------------------- | | **Profile 1: Controller** (`webauthn-controller`) | Passkeys controlling ENS names | ENS owner authorization + onchain binding | `webauthn_proof(es256)` | Onchain binding required, name owner verification | Direct Namechain verification | | **Profile 2: Credential Resolution** (`webauthn-credential-resolution`) | Authentication via ENS credentials | ENS record integrity + cryptographic verification | `webauthn_proof(es256)` or `gateway_attestation` | Credential in text record, optional hybrid paths | Direct Namechain verification or hybrid gateway + attestations | #### 8.1. Profile 1 – WebAuthn Controller (webauthn-controller) Enables passkeys to control ENS names. Requires onchain binding via `registerWebAuthnKey()`. Uses `webauthn_proof(es256)` with cross-chain verification to L1. #### 8.2. Profile 2 – WebAuthn Credential Resolution (webauthn-credential-resolution) Enables authentication using WebAuthn credentials stored in ENS records. Since all verification happens on Namechain, the approach is simplified: **Primary Approach:** * Direct onchain verification (\~$0.01-0.05 per verification) * Full trustlessness through Namechain (ENS L2, committed to L1) * No cross-chain calls required **Optional Hybrid Approach (for cost-sensitive applications):** * Gateway + attestations * Useful when frequent verifications are needed * Maintains onchain audit trail ### 9.4. Architecture Rationale #### 9.5.1. Why Full Namechain Architecture? **EIP-7951 at Launch:** * Namechain launches with P-256 precompile (EIP-7951) support * No need for cross-chain verification architecture * Direct onchain verification from day one **Cost Benefits:** * All operations at Namechain gas prices * \~50x cheaper than L1-only deployment * Enables practical WebAuthn adoption for consumer applications **Trust Benefits:** * Full onchain verification on Namechain (ENS L2) * State committed to L1 provides same security guarantees * No additional trust assumptions beyond Namechain itself #### 9.5.2. Architecture Advantages This full Namechain architecture provides: * **Ultra-low costs**: Virtually free verification using EIP-7951 precompile * **Simplified deployment**: All contracts on Namechain from launch * **Instant verification**: Direct onchain calls without cross-chain delays * **Trustless security**: Full verification on Namechain (ENS L2, state committed to L1) * **Optional gateway support**: Hybrid approaches available for cost-sensitive high-frequency applications * **ENSv2 native**: Core infrastructure leveraging Namechain's P-256 precompile #### 9.5.3. Implementation Strategy **Contract Deployment:** * All 3 contracts deploy on Namechain from launch * Direct verifier calls between contracts * CCIP-Read support for credential fetching (if needed) **Verification Flow:** * Resolvers call WebAuthnVerifier directly on Namechain * No cross-chain bridge interactions * All verification logic executes on Namechain EVM **Optional Gateway Support:** * Gateway + attestations available for applications needing sub-cent costs * Useful for high-frequency authentication scenarios * Maintains onchain audit trail while reducing per-verification costs ### 9. Edge Cases & Client Requirements #### 9.1. Algorithm Support **Only ES256 Supported:** * WebAuthn resolvers only support ES256 (P-256 ECDSA) * Other algorithms require offchain verification * Clients should filter credentials during selection #### 9.2. Challenge Expiry **Freshness Windows:** * Default: 5 minutes for challenges * Extended: 10 minutes for some use cases * Expired challenges are rejected onchain **Client Behavior:** * Generate fresh challenges for each authentication * Handle expiry gracefully by regenerating challenges * Don't submit expired assertions #### 9.3. Credential Revocation **Profile 1 (Controller):** * Revoke via `removeWebAuthnKey()` or register new key * Immediate effect onchain **Profile 2 (Credential Resolution):** * Update text record to remove credential * Old credential invalid immediately #### 9.4. Client Requirements Clients **must**: * Generate fresh challenges with timestamps * Validate challenge format and freshness * Handle OffchainLookup errors for CCIP-Read * Verify assertion format before submission * Respect freshness windows Clients **must not**: * Reuse challenges * Submit invalid signature formats * Skip challenge validation * Trust gateway verification without onchain checks (for full trustless mode) #### 9.5. URM Implementation Notes * WebAuthn profiles build on existing ENS infrastructure * ES256 requirement ensures onchain verifiability * Dual verification paths in Profile 2 enable cost/security tradeoffs * Gateway attestations provide audit trail for hybrid approach ## 4. AuthResolverImpl ### 4.1 Purpose AuthResolverImpl is the **verification orchestration layer** that composes ENSv2 storage and permission primitives into an Authority-tier surface for MARPs. It holds **credential**, **capability**, and **revocation** records under any ENS name and exposes `verifyAction` ([§5](/spec/verify-action)) as the convenience entry point that bundles credential lookup + scheme dispatch + revocation check. AuthResolverImpl does **NOT** introduce new primitives for write delegation, scoped binary storage, or ownership-change cleanup. v2's PermissionedResolver substrate ships those natively. AuthResolverImpl's contribution is the orchestration surface plus the credential / capability / revocation record schemas. ### 4.2 Inheritance (NORMATIVE) A conformant AuthResolverImpl **MUST** extend `PermissionedResolver` directly. The exact `is` clause at `PermissionedResolver.sol:94-113` declares 17 bases, grouped here by role: ``` AuthResolverImpl (per-name UUPS proxy instance — see §4.3) └── PermissionedResolver ├── IPermissionedResolver (self-interface — defines errors/events) ├── HCAContextUpgradeable (smart-account attribution — see §4.6) ├── UUPSUpgradeable (per-instance ROLE_UPGRADE — see §4.7) ├── EnhancedAccessControl (resource-keyed bitmap roles — see §4.5) ├── IERC7996 (EIP-165 / supportsFeature — see §4.4) ├── IMulticallable (batched reads for relying-party UX) ├── IProxyAuthorization (VerifiableFactory upgrade hook — see §4.7) └── 11 record-profile interfaces: IABIResolver, IAddrResolver, IAddressResolver, IContentHashResolver, IDataResolver, IHasAddressResolver, IInterfaceResolver, INameResolver, IPubkeyResolver, ITextResolver, IVersionableResolver ``` The AuthResolverImpl **MUST NOT** re-implement primitives already present on PermissionedResolver. It **MUST** add only: 1. The custom auth profile methods ([§5](/spec/verify-action)). 2. The EIP-165 feature id advertisement (§4.4). 3. The optional `getDeployedFor` view (§4.3). PermissionedResolver itself is **not declared `abstract`** — its setters (`setData`, `setAlias`, `setText`, etc.) are `external` and non-virtual (`PermissionedResolver.sol:94`). A subclass like AuthResolverImpl can ADD methods but **cannot override** inherited setter signatures or modifiers without contract-level changes upstream. The spec's "extend" framing holds for additions only. Resolver-level aliasing (`setAlias`, gated by `ROLE_SET_ALIAS = 1 << 28` root-only — `PermissionedResolverLib.sol:42`) is inherited from PermissionedResolver. AuthResolverImpl **MUST NOT** override or disable it. Full alias semantics for multi-name MARPs are deferred to v1.1. ### 4.3 Deployment model (NORMATIVE) AuthResolverImpl is **not** a single shared contract. Per `VerifiableFactory.sol`, the v2 pattern is one implementation contract + N per-name UUPS proxies. A conformant deployment: * **MUST** deploy `AuthResolverImpl` once per chain as the shared implementation. * **MUST** deploy each per-name AuthResolver as a UUPS proxy via the canonical factory: ```solidity function deployProxy(address implementation, uint256 salt, bytes memory data) external returns (address proxy); ``` (Source: `VerifiableFactory.sol:32` + `IVerifiableFactory.sol:7`.) * **MUST** pass the application-level salt as a `uint256` value derived from `keccak256(abi.encode("AuthResolverV1", ownerAddress, versionId))`, cast to `uint256`, where `ownerAddress` is the name owner that will hold `ROLE_UPGRADE` on the proxy and `versionId` is a `uint256` bumped per Verifier-major release. * **SHOULD** expose `getDeployedFor external view returns (address owner, uint256 versionId, address verifier)` returning the immutable values baked into the proxy's init data. This is the only way a relying party can verify resolver-to-owner binding without an external registry lookup. **`verifyContract` provenance check (NORMATIVE for relying parties that want to verify proxy provenance).** The canonical signature is **two arguments**: ```solidity function verifyContract(address proxy, address expectedImplementation) external view returns (bool); ``` (Source: `VerifiableFactory.sol:58` + `IVerifiableFactory.sol:9`.) The factory internally calls `IUUPSProxy(proxy).getVerifiableProxyData` to read the salt (appended to the clone's runtime bytecode per [§2](/spec/#2-composite-of-standards)), reconstructs the expected CREATE2 address from `(UUPSProxy creation code, factory, outerSalt)`, and **only returns true if both** the address matches AND the proxy currently points at `expectedImplementation`. **Deployer caveat (load-bearing for address prediction).** The outer CREATE2 salt is computed as: ```solidity bytes32 outerSalt = keccak256(abi.encode(msg.sender, salt)); ``` (Source: `VerifiableFactory.sol:33`.) The outer salt mixes in the **deployer's address** (`msg.sender` at `deployProxy` time), not the name owner's address. As a result, **the proxy address depends on who actually broadcasts the deployment transaction.** If a name owner deploys their own AuthResolver, the proxy address is keyed to their EOA; if a third party (a deployment script, a UI's deployer, a relayer) deploys on their behalf, the address is keyed to that third party. Implementers that need deterministic per-owner addresses MUST either (a) require the name owner to deploy directly, (b) use a known canonical deployer address across all deployments, or (c) record the deployer in the proxy's init data and expose it via `getDeployedFor` so relying parties can re-derive the address. **Deployment event for indexers.** The factory emits: ```solidity event ProxyDeployed(address indexed sender, address indexed proxyAddress, uint256 salt, address implementation); ``` (Source: `IVerifiableFactory.sol:5`. Note: `salt` and `implementation` are NOT indexed.) Indexers tracking AuthResolver deployments SHOULD subscribe to this event filtered by `implementation == ` to discover all per-name proxies on a chain. The implementation **MUST NOT** assume the existence of a centralized registry of MARP-deployed proxies. Each Wave-1 MARP deploys their own proxy; relying parties discover the proxy address via Universal Resolver V2's `findResolver(name)` (`UniversalResolverV2.sol`), not via an AuthResolver-specific directory. ### 4.4 EIP-165 / `IERC7996` advertisement (NORMATIVE) A conformant AuthResolverImpl: * **MUST** advertise a feature id (4-byte selector) derived from `keccak256("auth-resolver-v1")` via `IERC7996.supportsFeature(bytes4 featureId)` (signature per ENSIP-22 / ERC-7996, ENS documentation at "ERC-7996"; the v2 PermissionedResolver inherits `IERC7996`). This is the single one-call detection signal v2-aware clients use to confirm AuthResolver capability. * **MUST** advertise the custom auth profile selectors (`verifyAction`, `getFreshSignedState` once spec'd in a subsequent revision) via the inherited `supportsInterface(bytes4)` override. The selectors are the only signal Universal Resolver V2 uses to know whether to route a profile call. * **MAY** advertise additional per-category feature ids in future revisions (e.g., `auth-resolver-credential-v1`) if v1.1 adds independent versioning per record category. Not required in v1. Universal Resolver V2's typed error vocabulary — `ResolverNotFound`, `ResolverNotContract`, `UnsupportedResolverProfile(bytes4 selector)`, `ResolverError`, `ReverseAddressMismatch`, `HttpError` — flows through to AuthResolver callers unchanged. The error definitions live in upstream `AbstractUniversalResolver` (the local `UniversalResolverV2.sol` inherits from it; the upstream is at `lib/ens-contracts/`). `UnsupportedResolverProfile(bytes4)` (selector `0x7b1c461b`) is also declared by `IPermissionedResolver.sol:32` and raised by PermissionedResolver's own `resolve` path (`PermissionedResolver.sol:535,547`) when an inner staticcall returns empty data. Clients distinguishing `UnsupportedResolverProfile(selector)` (resolver doesn't implement that record type) from `ResolverError` (resolver reverted) feed into the `DenyReason` mapping in [§5.1](/spec/verify-action#51-signature-return-type-denyreason-enum). ### 4.5 Record profile and EAC role grants (NORMATIVE) A conformant AuthResolverImpl: * **MUST** store credential, capability, and revocation records under the inherited `IDataResolver.data(node, key) → bytes` profile (`PermissionedResolver.sol` Supported Record Types). It **MUST NOT** store these records as `text` records. Text records are reserved for human-readable metadata (e.g., `auth.credential.label[]` = "Pinata signer for Émile"), not for credential bytes. * **MUST** use the key convention: * `auth.credential[]` — CBOR-encoded `CredentialRecord` ([§6.1](/spec/record-schemas#61-credentialrecord)) * `auth.capability[]` — CBOR-encoded `CapabilityRecord` ([§6.2](/spec/record-schemas#62-capabilityrecord)) * `auth.revocation[]` — CBOR-encoded `RevocationRecord` ([§6.3](/spec/record-schemas#63-revocationrecord)). Empty bytes (or absence) MUST be interpreted as "not revoked." * **MUST** gate writes via the inherited EAC `ROLE_SET_DATA = 1 << 36` (`PermissionedResolverLib.sol:52`). Read access is ungated. The `` segment is a free string identifier chosen by the name owner. Conformant implementations MUST NOT impose a format constraint on `` beyond the inherited PermissionedResolver key-length limits. Counterparties select among multiple published credentials by the credential record's validity window ([§6.1](/spec/record-schemas#61-credentialrecord)) and revocation status. #### 4.5.1 Role-grant mechanic (NORMATIVE) The grant surface on PermissionedResolver wraps `EnhancedAccessControl` (`EnhancedAccessControl.sol`). EAC itself exposes exactly **two** grant entry points (per `IEnhancedAccessControl.sol:68-95`): ```solidity function grantRoles(uint256 resource, uint256 roleBitmap, address account) external returns (bool); // rejects ROOT_RESOURCE function grantRootRoles(uint256 roleBitmap, address account) external returns (bool); // root only ``` `PermissionedResolver` provides **two convenience wrappers** that compute the resource for callers (the spec previously named a third, `authorizeRootRoles` — that does not exist; the root analog is EAC's `grantRootRoles` directly): ```solidity // Name-scoped wrapper — grants ROLE on resource(node, 0); satisfies any per-key check on this node. function authorizeNameRoles(bytes32 node, uint256 roleBitmap, address account, bool grant) external; // Record-scoped wrapper — grants ROLE on resource(node, partHash(key)); tightest scope. function authorizeDataRoles(bytes32 node, bytes calldata key, address account, bool grant) external; ``` **Permission check is an OR-of-3-resources.** When a caller invokes `setData(node, key, value)`, PermissionedResolver's `onlyPartRoles` modifier (`PermissionedResolver.sol:183-193`) accepts if the caller holds the required role on **any one** of: 1. `resource(node, partHash(key))` — record scope (this exact key under this name) 2. `resource(0, partHash(key))` — cross-name key scope (this key under any name — narrow but useful for cross-name operators) 3. `resource(node, 0)` — name scope (any key under this name — superset of record scope) A grant on `resource(0, 0)` (root) is held only by `grantRootRoles` and satisfies all checks. A grant via `authorizeNameRoles(node, ROLE_SET_DATA, op, true)` satisfies pattern (3) above and therefore covers all data keys under that name. **Direct EAC `grantRoles` / `revokeRoles` are DISABLED at the PermissionedResolver layer.** Both always revert (`PermissionedResolver.sol:714-733`). All role management MUST flow through the `authorize*Roles` wrappers above, or through the initial bootstrap (next). **Initial role grant happens at proxy initialization.** `PermissionedResolver.sol:236-242` exposes `initialize(address admin, uint256 roleBitmap)` which calls `_grantRoles(ROOT_RESOURCE, roleBitmap, admin, false)` and runs `__UUPSUpgradeable_init`. AuthResolverImpl deployments via `VerifiableFactory.deployProxy(impl, salt, initData)` pass an `initData` calldata blob that invokes this `initialize` with the owner address and the initial role bitmap (typically `ROLE_UPGRADE | ROLE_UPGRADE_ADMIN | ROLE_SET_DATA | ROLE_SET_DATA_ADMIN | ROLE_SET_ALIAS | ROLE_SET_ALIAS_ADMIN` on `ROOT_RESOURCE` for the deploying name owner). #### 4.5.2 Admin-role layer (NORMATIVE) For every primary role, `PermissionedResolverLib.sol` defines a paired **admin role** at the upper-128-bit half of the role bitmap: | Primary role | Value | Admin role | Value | Required to grant primary | | ---------------- | ---------- | ---------------------- | ----------------------- | ------------------------- | | `ROLE_SET_DATA` | `1 << 36` | `ROLE_SET_DATA_ADMIN` | `ROLE_SET_DATA << 128` | yes | | `ROLE_SET_ALIAS` | `1 << 28` | `ROLE_SET_ALIAS_ADMIN` | `ROLE_SET_ALIAS << 128` | yes | | `ROLE_UPGRADE` | `1 << 124` | `ROLE_UPGRADE_ADMIN` | `ROLE_UPGRADE << 128` | yes | This is a **bit-packed admin pattern, not an OpenZeppelin-style `roleAdmin` mapping**. EAC's `_checkCanGrantRoles` (`EnhancedAccessControl.sol:370-379`) computes the caller's grantable roles by taking their bitmap, right-shifting 128 bits to project the admin-half onto the primary-half, then OR-ing — so an account holding `ROLE_SET_DATA_ADMIN` (and only `ROLE_SET_DATA_ADMIN`) **can grant `ROLE_SET_DATA`** to others on the same resource, even without holding `ROLE_SET_DATA` itself for that resource. **Operational implication.** To delegate write authority over a single credential key from the name owner to an operator EOA, the name owner — initialized with both `ROLE_SET_DATA` and `ROLE_SET_DATA_ADMIN` on `ROOT_RESOURCE` — calls: ```solidity authResolver.authorizeDataRoles(node, "auth.credential[primary]", operatorEOA, true); ``` If the name owner was initialized with `ROLE_SET_DATA` only (no `_ADMIN`), they could publish records themselves but **could not delegate**. If initialized with `ROLE_SET_DATA_ADMIN` only (no primary), they could delegate but could not publish records themselves. Deployments SHOULD grant both on `ROOT_RESOURCE` at `initialize` time unless an explicit two-role-holder separation-of-duties is part of the operator model. #### 4.5.3 Resource keying PermissionedResolver computes the EAC `resource` as `uint256(keccak256(node, part))` where `part = partHash(key)` (`PermissionedResolverLib.sol:66-92`). This makes records **namehash-stable** — they do not participate in the registry's mutable-token-id polymorphism that uses `anyId` for cross-token grants. The lifecycle implications of this are addressed in [§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties). CBOR field layouts for each record type are abstract in [§6](/spec/record-schemas) and will be specified at the byte level in a later v1.0-draft revision. ### 4.6 HCA attribution (NORMATIVE) A conformant AuthResolverImpl inherits `HCAContextUpgradeable` from PermissionedResolver, which rewrites `_msgSender` to return the controlling owner address when the caller is a registered Hidden Contract Account proxy. Exact rewrite mechanic (`HCAEquivalence.sol:37-42`): ```solidity function _msgSenderWithHcaEquivalence internal view returns (address) { if (address(HCA_FACTORY) == address(0)) return msg.sender; address accountOwner = HCA_FACTORY.getAccountOwner(msg.sender); if (accountOwner == address(0)) return msg.sender; return accountOwner; } ``` The HCAFactory is supplied at construction time as an `IHCAFactoryBasic`-typed `immutable` (`HCAEquivalence.sol:28-30`), passed through PermissionedResolver's constructor at `PermissionedResolver.sol:201`. `IHCAFactoryBasic` is a single-method interface (selector `0x442b172c` per `IHCAFactoryBasic.sol`): ```solidity function getAccountOwner(address hca) external view returns (address); ``` **Operational requirement**: * For MARPs using smart-account wallets (Safe, ERC-4337, ZeroDev/Kernel), `ROLE_SET_DATA` (or any other role) **MUST** be granted to the **controlling EOA**, not the smart-account proxy. The EAC role check runs against `_msgSender`, which the HCA layer rewrites — and EAC inherits `HCAContext` directly (`EnhancedAccessControl.sol:51`), so all four EAC permission modifiers (`canGrantRoles`, `canRevokeRoles`, `onlyRoles`, `onlyRootRoles`) are HCA-rewrite-aware. * For MARPs using EOAs directly, HCA is a no-op. `_msgSender == msg.sender`. Standard EAC role grants work without HCA-specific handling. * For MARPs whose smart account is **not** registered with any HCAFactory, the AuthResolverImpl proxy MAY be deployed with `address(0)` as the HCA factory constructor parameter, which short-circuits the rewrite path (`address(0)` factory → `return msg.sender;` immediately). In that mode, smart-account-backed MARPs MUST grant roles to the proxy address directly. **Rhinestone framing — operational, not source-level.** `IHCAFactoryBasic` is intentionally factory-agnostic; no Rhinestone-specific contract address or factory name appears in the v2 source. The "production HCAFactory" is Rhinestone's deployment as an operational choice across Wave-1 MARPs, not a contract-level constraint. Default for v1 is to ship with HCA enabled pointing at Rhinestone's factory; alternative factories are permitted at AuthResolverImpl construction time and require no source change. ### 4.7 Upgrade authority (NORMATIVE) A conformant AuthResolverImpl: * **MUST** use UUPS upgrade plumbing inherited from PermissionedResolver via OpenZeppelin's `UUPSUpgradeable`. * **MUST** gate `_authorizeUpgrade` on the holder of `ROLE_UPGRADE = 1 << 124` on the proxy's `ROOT_RESOURCE` (`PermissionedResolverLib.sol:57`; enforcement at `PermissionedResolver.sol:740-744` via `onlyRootRoles(ROLE_UPGRADE)`). * **MUST** grant `ROLE_UPGRADE` (and typically `ROLE_UPGRADE_ADMIN`, per §4.5.2) to the deploying name owner at proxy initialization via `initialize(admin, roleBitmap)` (§4.5.1). The deploying name owner controls all upgrades to their AuthResolver proxy. * **MUST NOT** assume or require a central multisig, ENS DAO timelock, or other off-proxy upgrade authority. Each MARP's proxy is independently upgradeable by its owner; a bug or compromise in one proxy does not propagate. **`canUpgradeFrom` is permissive by design.** `PermissionedResolver.sol:628-632` — `canUpgradeFrom(address)` unconditionally returns `true`. UUPS upgrade authorization is therefore enforced solely by the **current** implementation's `_authorizeUpgrade`, not by any source-implementation whitelist. An upgrade from `AuthResolverImpl v1` to a successor `AuthResolverImpl v2` requires only that the upgrader holds `ROLE_UPGRADE` on the proxy at the moment `upgradeToAndCall` is invoked — there is no cross-impl gatekeeping or registry of approved upgrade targets. The Verifier is the only centralized governance surface in the v1 system ([§3.1](/spec/verifier#31-purpose-and-invariants) — single shared deployment, immutable scheme dispatch). An optional implementation-version registry — letting Wave-1 MARPs verify the proxy points at an audited implementation — is a possible companion artifact; it is **not** a deliverable of this revision. ### 4.8 Conformance criteria (NORMATIVE) A conformant AuthResolverImpl satisfies every MUST/MUST NOT and SHOULD/MAY in this table. This restates the normative content in §4.2–§4.7; the source sections remain authoritative for any discrepancy. | # | Requirement | Type | Source | | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------ | | A1 | Extend `PermissionedResolver` directly (17-base inheritance chain inherited) | MUST | §4.2 | | A2 | NOT re-implement primitives already present on PermissionedResolver | MUST NOT | §4.2 | | A3 | Add only: (a) custom auth profile methods ([§5](/spec/verify-action)), (b) EIP-165 feature id advertisement (§4.4), (c) optional `getDeployedFor` view (§4.3) | MUST | §4.2 | | A4 | NOT override or disable inherited `setAlias` | MUST NOT | §4.2 | | A5 | Deploy `AuthResolverImpl` once per chain as the shared implementation | MUST | §4.3 | | A6 | Deploy each per-name AuthResolver as a UUPS proxy via `VerifiableFactory.deployProxy(address implementation, uint256 salt, bytes data)` | MUST | §4.3 | | A7 | Pass salt = `uint256(keccak256(abi.encode("AuthResolverV1", ownerAddress, versionId)))` | MUST | §4.3 | | A8 | Use `verifyContract(proxy, expectedImplementation)` (two args) for provenance verification | MUST (when verifying provenance) | §4.3 | | A9 | Expose `getDeployedFor returns (address owner, uint256 versionId, address verifier)` | SHOULD | §4.3 | | A10 | NOT assume the existence of a centralized registry of MARP-deployed proxies | MUST NOT | §4.3 | | A11 | Advertise a 4-byte feature id derived from `keccak256("auth-resolver-v1")` via `IERC7996.supportsFeature(bytes4 featureId)` | MUST | §4.4 | | A12 | Advertise custom auth profile selectors (`verifyAction`, `getFreshSignedState`) via inherited `supportsInterface(bytes4)` | MUST | §4.4 | | A13 | Advertise per-category feature ids (e.g., `auth-resolver-credential-v1`) | MAY (deferred to v1.1) | §4.4 | | A14 | Store credential/capability/revocation records under `IDataResolver.data(node, key) → bytes` profile | MUST | §4.5 | | A15 | NOT store these records as `text` records | MUST NOT | §4.5 | | A16 | Use key convention `auth.credential[]` / `auth.capability[]` / `auth.revocation[]`, CBOR-encoded | MUST | §4.5 | | A17 | Interpret empty bytes or absence at `auth.revocation[]` as "not revoked" | MUST | §4.5 | | A18 | Gate writes via inherited EAC `ROLE_SET_DATA = 1 << 36`; reads ungated | MUST | §4.5 | | A19 | Grant the deploying name owner BOTH `ROLE_X` and `ROLE_X_ADMIN` at `initialize` time for any role they need to both publish and delegate | SHOULD | §4.5.2 | | A20 | NOT impose format constraints on `` beyond inherited PermissionedResolver key-length limits | MUST NOT | §4.5 | | A21 | For smart-account-backed MARPs under HCA: grant `ROLE_SET_DATA` to the **controlling EOA**, not the smart-account proxy address | MUST | §4.6 | | A22 | Deploy with `address(0)` as the HCA factory parameter to disable HCA | MAY | §4.6 | | A23 | Use UUPS upgrade plumbing inherited via OpenZeppelin's `UUPSUpgradeable` | MUST | §4.7 | | A24 | Gate `_authorizeUpgrade` on holder of `ROLE_UPGRADE = 1 << 124` on the proxy's `ROOT_RESOURCE` | MUST | §4.7 | | A25 | Grant `ROLE_UPGRADE` (typically with `ROLE_UPGRADE_ADMIN`) to the deploying name owner at proxy initialization | MUST | §4.7 | | A26 | NOT assume or require a central multisig, ENS DAO timelock, or other off-proxy upgrade authority | MUST NOT | §4.7 | ## 9. Deferred beyond v1.0-draft.02 | Item | Target revision | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | Signed-freshness EIP-712 layout + gateway response framing (incl. ENSIP-22 recursive CCIP-Read + CORS conformance per [§6.4](/spec/record-schemas#64-deferred-getfreshsignedstate)) | Later v1.0-draft revision | | CBOR field-level layouts for CredentialRecord, CapabilityRecord, RevocationRecord | Later v1.0-draft revision | | Threat model expansion (replay, key compromise, gateway trust, governance capture) | Later v1.0-draft revision | | Re-verification of normative claims against finalized ENSv2 mainnet docs once published | Carry-forward; ongoing | | Initial test vectors (per-scheme verify + per-record encode/decode + factory address-prediction + HCA rewrite + `verifyAction` orchestration) | M1 deliverable with reference implementation (vectors land in `/tests/vectors/`) | | Reference implementation pointer (concrete impl-repo address + commit range) | v1.0-final (placeholder in [§6.5](/spec/record-schemas#65-reference-implementation-pointer-org-confirmed-repo--impl-pending)) | | ENSIP-10 wildcard resolver path | v1.1 | | Resolver-level aliasing semantics for multi-name MARPs | v1.1 | | Capability scope structured-enumeration | v1.1 | | ENSIP-25 binding enforcement (optional `verifyAction` precondition) | v1.1 if NCCoE position-paper review drives a re-decision | | Cross-chain credential discovery via EIP-8121 | v2 cycle | | Batched `verifyMany` entry point on the Verifier | v1.1 if pilots request | | AuthResolverImpl version registry artifact | optional companion artifact | > **Working copy:** [Google Doc](https://docs.google.com/document/d/1L5Kj7oxT4dzlkYdYh0Sne2jx76XomT7K04P7Bu9zR-w/edit?usp=sharing). The version on this site is the published cut. ## Prototype Spec: Verifier + AuthResolverImpl **Version:** v1.0-draft.02 · 2026-05-18 **Purpose:** Normative specification of the Authority tier for AI-agent identity on ENS. Defines two contracts: a **Verifier** (signature-verification dispatch with three v1 schemes — WebAuthn-ES256, ECDSA-secp256k1, EIP-1271) and an **AuthResolverImpl** (per-name UUPS proxy on top of ENSv2's PermissionedResolver) that together let any ENS name serve as an authentication anchor for an agent's signed actions. **Status:** NORMATIVE for [§3 (Verifier)](/spec/verifier), [§4 (AuthResolverImpl)](/spec/authresolver), [§5 (`verifyAction` orchestration)](/spec/verify-action), with inline conformance criteria tables at §3.6, §4.8, §5.4. Other sections are non-normative context, deferred surface, or references. Items still open (full signed-freshness wire format, CBOR field-level layouts, expanded threat model, concrete reference-implementation pointer) carry forward to later v1.0-draft revisions and v1.0-final per [§9](/spec/deferred). **Pillar role:** Implements the Authority tier of the MAIP architecture (one of three architectural tiers: Display, Discovery, Authority). **Target chain:** Ethereum mainnet via ENSv2 contracts; testnet deployment first (Sepolia, contingent on canonical VerifiableFactory address publication). **Reference deployment (illustrative, not normative):** `emilemarcelagustin.eth` on Pinata Agents; ERC-8004 #24994 on Base mainnet. *** ## 1. Scope and Non-Goals ### 1.1 In scope (NORMATIVE in this revision) | Surface | Section | Normative content | | -------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------- | | **Verifier** contract | [§3](/spec/verifier) | Three registered schemes; dispatch surface; `IVerifier` interface; statelessness invariants | | **AuthResolverImpl** contract | [§4](/spec/authresolver) | Inheritance, deployment model, EIP-165 advertisement, record profile, HCA attribution, upgrade authority | | **`verifyAction` orchestration** | [§5](/spec/verify-action) | Required ordering, return-type semantics, `DenyReason` enum, name-lifecycle caveats | ### 1.2 Normative conventions This document uses **RFC 2119** keywords: **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, **MAY**. Lowercase uses of these words carry no normative weight. A conformant Verifier or AuthResolverImpl is one that satisfies every **MUST** and **MUST NOT** in [§3](/spec/verifier), [§4](/spec/authresolver), and [§5](/spec/verify-action), plus the SHOULD/MAY rows in the conformance tables at [§3.6](/spec/verifier#36-conformance-criteria-normative), [§4.8](/spec/authresolver#48-conformance-criteria-normative), and [§5.4](/spec/verify-action#54-conformance-criteria-normative) to the extent claimed by the implementation. ### 1.3 Terminology These terms have specific meanings in this spec. Defined here so the normative body ([§3](/spec/verifier)–[§5](/spec/verify-action)) and conformance tables ([§3.6](/spec/verifier#36-conformance-criteria-normative), [§4.8](/spec/authresolver#48-conformance-criteria-normative), [§5.4](/spec/verify-action#54-conformance-criteria-normative)) can use them without re-introducing them. | Term | Definition | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **MARP** | Managed Agent Runtime Platform — an operator that runs AI agents on behalf of end users (e.g., Bankr Agents, Pinata Agents, ZeroDev/Kernel-based platforms). The Authority-tier surface this spec defines is what MARPs publish under an ENS name so counterparties can verify their agents' signed actions. | | **AuthResolver** | The runtime entity (`AuthResolverImpl` per-name UUPS proxy) that holds an ENS name's credential, capability, and revocation records and exposes the `verifyAction` orchestration call. When unqualified, "AuthResolver" refers to a deployed proxy instance; `AuthResolverImpl` refers to the shared implementation contract behind those proxies. | | **AuthResolverImpl** | The single shared implementation contract deployed once per chain. Per-name AuthResolver proxies are UUPS proxies pointing at this implementation, deployed via `VerifiableFactory.deployProxy` ([§4.3](/spec/authresolver#43-deployment-model-normative)). | | **Verifier** | The single shared signature-verification dispatch contract ([§3](/spec/verifier)). Stateless, permissionless, dispatches by `schemeId` to one of three v1 handlers (P-256/WebAuthn, secp256k1/ECDSA, EIP-1271). | | **Credential** | A registered public key + signature scheme + validity window published under an ENS name as `auth.credential[]`. The thing the Verifier verifies signatures against. | | **Capability** | A scope declaration published as `auth.capability[]`. Reserved in v1 (publishable but not consumed by `verifyAction`); active enforcement deferred to v1.1. | | **Revocation** | An explicit revocation flag published as `auth.revocation[]`. Presence (non-empty bytes) = revoked, regardless of decoded content. Absence = not revoked. | | **Name owner** | The address that holds `ROLE_UPGRADE` on a per-name AuthResolver proxy. Typically the address controlling the ENS name itself, though the two can diverge (per [§4.3](/spec/authresolver#43-deployment-model-normative) deployer caveat — proxy address is keyed to the deployer at `deployProxy` time, not the name owner per se). | | **Operator EOA** | A delegated writer to which the name owner grants `ROLE_SET_DATA` (with optional `ROLE_SET_DATA_ADMIN` per [§4.5.2](/spec/authresolver#452-admin-role-layer-normative)) for a specific credential key. The operator can publish or revoke that credential without holding broader name authority. | | **Controlling EOA** | The end-user wallet that owns a smart-account proxy registered with an HCAFactory. Under HCA attribution ([§4.6](/spec/authresolver#46-hca-attribution-normative)), `HCAContextUpgradeable._msgSender` returns this EOA, not the smart-account proxy address. The address that should hold EAC roles for smart-account-backed deployments. | | **HCA proxy** | A smart-account proxy registered with an HCAFactory. When such a proxy calls AuthResolver, the HCA layer rewrites `_msgSender` to the controlling EOA so EAC role checks resolve correctly. | | **HCAFactory** | A registry contract implementing `IHCAFactoryBasic.getAccountOwner(address)` that maps HCA proxies to their controlling EOAs. The production deployment is operated by Rhinestone; the AuthResolverImpl constructor accepts any `IHCAFactoryBasic` implementation (or `address(0)` to disable HCA entirely). | | **Relying party** | A counterparty receiving a signed action from an agent who wants to verify the signature is currently valid under the agent's published credentials. Calls `verifyAction` (or `data` + `Verifier.verify` directly) after resolving the AuthResolver proxy address via Universal Resolver V2. | | **Reference deployment** | A live ENS name + AuthResolver setup cited in this spec for illustrative purposes (e.g., `emilemarcelagustin.eth`, `alpha-go.bankrtest.eth`). Not normative; implementers MUST NOT hard-code reference-deployment addresses. | | **Frozen snapshot** | A spec revision file that has been promoted to v1.0-draft.0N and MUST NOT be edited in place. Corrections ship as v1.0-draft.0(N+1). | ### 1.4 Parties The Authority-tier surface involves these actors. Each row says what the actor controls and how the spec constrains them. | Party | What they control | Where they appear | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Name Owner** | The ENS name, the per-name AuthResolver proxy upgrade authority (`ROLE_UPGRADE` on `ROOT_RESOURCE`), and which operators get delegated write access | [§4.3](/spec/authresolver#43-deployment-model-normative) (deployment), [§4.5.1](/spec/authresolver#451-role-grant-mechanic-normative) (grants), [§4.7](/spec/authresolver#47-upgrade-authority-normative) (upgrade) | | **Operator** | Permission to publish or rotate one or more `auth.credential[]` / `auth.capability[]` / `auth.revocation[]` records under one ENS name, scoped via EAC role grants | [§4.5.1](/spec/authresolver#451-role-grant-mechanic-normative), [§4.5.2](/spec/authresolver#452-admin-role-layer-normative) (admin layer governs whether they can re-delegate) | | **MARP Platform** | The agent runtime that signs actions on behalf of end users. May ALSO be the Name Owner or Operator (when the platform itself owns the ENS name) or may be neither (when end users own their ENS names and grant the platform Operator scope) | [§4.6](/spec/authresolver#46-hca-attribution-normative) (HCA attribution for smart-account-backed MARPs), [§5](/spec/verify-action) (signed-action flow) | | **Smart-Account User** | An end user controlling a smart-account proxy (Safe, ERC-4337, ZeroDev/Kernel) registered with an HCAFactory. Their controlling EOA appears as `_msgSender` for EAC checks via HCA rewriting | [§4.6](/spec/authresolver#46-hca-attribution-normative) | | **Relying Party** | A counterparty (another contract, a backend, a wallet) that verifies signed actions from an agent before honoring them. Bears the [§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties) normative rules (UR-routed discovery, no address caching, ENSIP-15 normalization in tooling) | [§5](/spec/verify-action), [§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties), [§5.4](/spec/verify-action#54-conformance-criteria-normative) | | **HCAFactory** | A contract that authoritatively maps HCA proxies to controlling EOAs. The AuthResolverImpl makes a single `getAccountOwner` call per request via the HCA layer; HCAFactory correctness is out of scope for this spec | [§4.6](/spec/authresolver#46-hca-attribution-normative) | | **Implementation Author** | The party deploying `AuthResolverImpl` (the shared implementation) on each chain. Distinct from the per-name proxy deployer. Should be the project / DAO maintaining the audited implementation registry (deferred per [§4.7](/spec/authresolver#47-upgrade-authority-normative)) | [§4.3](/spec/authresolver#43-deployment-model-normative), [§4.7](/spec/authresolver#47-upgrade-authority-normative) | | **Verifier Maintainer** | The party deploying and operating the singleton Verifier contract (per chain). Scheme set is immutable post-deployment ([§3.4](/spec/verifier#34-dispatch-surface-and-extensibility)); maintenance is limited to chain-level operational concerns (e.g., re-deployment after a major EIP-7951 precompile change) | [§3](/spec/verifier), [§3.1](/spec/verifier#31-purpose-and-invariants), [§3.4](/spec/verifier#34-dispatch-surface-and-extensibility) | The spec does NOT mandate a specific governance structure for the Implementation Author or Verifier Maintainer roles. Each MARP-owned proxy is independently upgradeable by its Name Owner ([§4.7](/spec/authresolver#47-upgrade-authority-normative)); the shared implementation contract has no central upgrade authority. *** ## 2. Composite of Standards The Verifier and AuthResolverImpl compose existing standards rather than introducing new core protocol changes. The AuthResolverImpl is a **verification orchestration layer** on top of v2 primitives, not a source of new Authority-tier primitives. | Standard | Composition role | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **ENSIP-25** | Verifiable agent identity binding (ENS name ↔ ERC-8004 record). **Identity-layer precondition** for AuthResolverImpl use; not enforced inside `verifyAction` (see [§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties)). | | **ENSIP-26** | Agent-context records (attribution: services, endpoints, description). **Attribution-layer composition** alongside AuthResolverImpl — coexists on the same name under a separate namespace (`services[*]` text records vs. `auth.*` data records). Read by relying parties for context alongside `verifyAction`; not enforced inside `verifyAction` (see [§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties)). | | **ENSIP-64** | Typed text records. Used only for human-readable metadata under sibling namespaces (e.g., `auth.credential.label[]`); not for credential bytes (those live in `data` records per [§4.5](/spec/authresolver#45-record-profile-and-eac-role-grants-normative)). | | **EIP-165 / ENSIP-22 (`IERC7996`)** | Resolver capability discovery via `supportsFeature(bytes4 featureId)` (parallel to EIP-165's `supportsInterface(bytes4)`). AuthResolverImpl advertises a custom feature id ([§4.4](/spec/authresolver#44-eip-165--ierc7996-advertisement-normative)). Inherited from PermissionedResolver. | | **EIP-1967** | Standard implementation slot for UUPS proxies (`UUPSProxyLogic.sol:9` — `_IMPLEMENTATION_SLOT = 0x360894...2bbc`). The verifiable salt itself is **not** in a storage slot — it is appended as the **last 32 bytes of the clone proxy's runtime bytecode** (`CloneProxyBytecode.sol:14,28-30`) and read on demand via `extcodecopy` (`UUPSProxyLogic.sol:73-79`). | | **EIP-3668** | CCIP-Read protocol. Reserved for the deferred `getFreshSignedState` path; the basic `data` read path does NOT revert with `OffchainLookup`. | | **EIP-7951** | P-256 precompile. Verifier dispatch handler for the `WebAuthn-ES256` scheme ([§3.2](/spec/verifier#32-registered-signature-schemes-v1)). | | **ERC-8004** | Agent identity registry. Referenced by ENSIP-25 binding; AuthResolverImpl does not call the registry directly. | | **ENSv2 PermissionedResolver** | Parent contract of AuthResolverImpl. Source: `PermissionedResolver.sol` in `ensdomains/contracts-v2`. 17-base inheritance chain (full list in [§4.2](/spec/authresolver#42-inheritance-normative)): substrate (HCA, UUPS, EAC) + capability advertisement (`IERC7996`) + batched reads (`IMulticallable`) + 11 record-profile interfaces (`IDataResolver`, `ITextResolver`, `IAddrResolver`, etc.) + `IProxyAuthorization` (verifiable-factory hook) + `IPermissionedResolver` (self-interface). | | **ENSv2 `EnhancedAccessControl`** | Per-(node, recordKey) write delegation inherited from PermissionedResolver. AuthResolverImpl uses this for credential / capability writes ([§4.5](/spec/authresolver#45-record-profile-and-eac-role-grants-normative)). | | **ENSv2 `HCAContextUpgradeable`** | Smart-account attribution inherited from PermissionedResolver. AuthResolverImpl uses this for HCA-aware role grants ([§4.6](/spec/authresolver#46-hca-attribution-normative)). | | **ENSv2 `VerifiableFactory`** | CREATE2 proxy factory. AuthResolverImpl proxies are deployed via `deployProxy(impl, salt, data)`. | **Three-layer framing.** ENSIP-25, ENSIP-26, and the AuthResolver `auth.*` records form a three-layer agent-identity story: 1. **Identity layer (ENSIP-25)** — "Is this ENS name bound to this agent's ERC-8004 record?" 2. **Attribution layer (ENSIP-26)** — "What is this agent? What services does it offer? Where do I reach it?" 3. **Authentication layer (AuthResolver)** — "Is this signed action verifiable under the agent's published credentials?" A relying party doing a serious verification reads all three. AuthResolverImpl itself implements only layer 3 — `verifyAction` does NOT enforce layers 1 or 2. The composition is the relying party's responsibility, addressed at [§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties). This spec productionizes the agent-authority cell that the [Universal Resolver Matrix](/patterns/overview) identified as highest-leverage (see also [WebAuthn Pattern](/patterns/webauthn-pattern) and the [WebAuthn Specification](/specifications/webauthn-specification)). **ENSIP-10 (wildcard resolution)** appears in the v0.1 composite but is **deliberately omitted** here — single-name model assumed for v1, deferred per [§9](/spec/deferred) if a Wave-1 MARP needs per-agent subnames. ## 6. Record Schemas (non-normative shape; CBOR field layouts deferred) The structs below describe the **conceptual shape** of each record. The CBOR field-level layout (map keys, type tags, ordering) is deferred to a later v1.0-draft revision. A conformant implementation MUST encode/decode these records in CBOR; the specific field encoding is not yet pinned by this revision. ### 6.1 `CredentialRecord` ```solidity struct CredentialRecord { bytes32 schemeId; // §3.2 — keccak256("WebAuthn-ES256") | keccak256("ECDSA-secp256k1") | keccak256("EIP-1271") bytes pubKey; // Scheme-specific public key bytes (see §3.2 per-scheme encodings) uint64 notBefore; // Validity window start (unix seconds) uint64 notAfter; // Validity window end (unix seconds); 0 means no expiry bytes32 capabilityRef; // Optional pointer to a capability record id (zero = no capability binding in v1) } ``` For v1, `capabilityRef` is structural only — `verifyAction` MUST NOT enforce capability-scope checks (per [§5.2](/spec/verify-action#52-required-behavior-normative-ordering) step 7 note). ### 6.2 `CapabilityRecord` ```solidity struct CapabilityRecord { string scope; // Free string scope identifier (per §9 deferral; structured enumeration in v1.1) uint64 expiry; // Capability expiry (unix seconds); 0 means no expiry bytes32 revocationKey; // Lookup key for revocation override (zero = use credential id as revocation key) } ``` Capability records are publishable in v1 but are not consumed by `verifyAction` in v1. They are reserved for the v1.1 policy-enforcement layer. ### 6.3 `RevocationRecord` ```solidity struct RevocationRecord { uint64 revokedAt; // Block timestamp at revocation (unix seconds); 0 if not revoked string reason; // Free string reason metadata (optional, may be empty) } ``` Per [§5.2](/spec/verify-action#52-required-behavior-normative-ordering) step 4, the presence of any bytes at `auth.revocation[]` is sufficient for the deny decision; decoding the `revokedAt` and `reason` fields is OPTIONAL for v1 conformance but RECOMMENDED for diagnostic surfaces. ### 6.4 Deferred `getFreshSignedState` A conformant AuthResolverImpl **MUST** declare the method signature: ```solidity function getFreshSignedState(bytes32 node, string calldata recordKey) external view returns (bytes memory signedState); ``` The method body — including the `OffchainLookup` revert format, gateway URL conventions, the EIP-712 `AuthorityState` typed-data layout, and attestor-key relationship — is **deferred to a later v1.0-draft revision**. Implementations of this revision MAY implement the method as a revert with a documented `NotYetSpecified` custom error. This signature reservation is included now so that the EIP-165 advertisement ([§4.4](/spec/authresolver#44-eip-165--ierc7996-advertisement-normative)) covers the selector and so the subsequent spec change is body-only (no interface migration). **Forward-looking note for the deferred body.** When the method body is spec'd, it MUST conform to ENSIP-22's CCIP-Read resolver requirements (per ENS documentation at "ENSIP-22 / supportsFeature"): * The resolver MUST support **recursive CCIP-Read calls** (the gateway response may itself trigger another `OffchainLookup`). * The gateway MUST serve **CORS headers** so that browser-based relying parties can read freshness responses without proxying. * The EIP-712 `AuthorityState` typed message MUST include enough context (resolver address, chain id, block number) for a relying party to verify the attestor signature was issued for *this* AuthResolver instance, not a sibling. ### 6.5 Reference implementation pointer (org confirmed; repo + impl pending) The reference implementation will live in a **new dedicated repo under the `steg-eth` GitHub organization** (`github.com/steg-eth/`). Repo name within the `steg-eth` org is TBD. The impl is **not yet written**. Delivery is targeted as an M1 deliverable (target date 2026-08-31), conditional on SPP funding. The pointer block below will be filled in when the impl lands: ``` Reference implementation: github.com/steg-eth/ (repo name TBD within steg-eth org) Tag / commit range covering v1.0-draft.02 scope: (not yet delivered) Audit status: unaudited (impl not yet written) ``` **Conformance promise (post-pointer).** The reference implementation will satisfy the conformance criteria specified at [§3.6](/spec/verifier#36-conformance-criteria-normative), [§4.8](/spec/authresolver#48-conformance-criteria-normative), and [§5.4](/spec/verify-action#54-conformance-criteria-normative), and will be the canonical artifact M1 deliverable. This is a v1.0-final blocker per [§9](/spec/deferred); v1.0-draft.02 ships with the org confirmed and the repo + impl pending. ## 10. References ### 10.1 Normative references Conformance with this spec depends on these documents. Implementers MUST consult them for the contracts the spec inherits, the role-keyword semantics, and the encoding/standard formats the spec uses. * **RFC 2119** — Key words for use in RFCs to Indicate Requirement Levels. Governs the MUST/SHOULD/MAY interpretation in [§1.2](/spec/#12-normative-conventions) and throughout [§3](/spec/verifier)-[§5](/spec/verify-action). * **RFC 8949** — Concise Binary Object Representation (CBOR). The encoding for `auth.credential[]`, `auth.capability[]`, and `auth.revocation[]` records ([§4.5](/spec/authresolver#45-record-profile-and-eac-role-grants-normative)). * **EIP-165** — Standard Interface Detection. Underlies `supportsInterface(bytes4)` used at [§4.4](/spec/authresolver#44-eip-165--ierc7996-advertisement-normative) for custom auth profile selector advertisement. * **EIP-1271** — Standard Signature Validation Method for Contracts. The third v1 Verifier scheme ([§3.2](/spec/verifier#32-registered-signature-schemes-v1) row 3); magic value `0x1626ba7e`. * **EIP-1967** — Standard Proxy Storage Slots. The implementation slot used by UUPS proxies deployed via VerifiableFactory ([§2](/spec/#2-composite-of-standards) row 4). * **EIP-3668** — CCIP-Read (`OffchainLookup` revert protocol). Reserved for the deferred `getFreshSignedState` path ([§6.4](/spec/record-schemas#64-deferred-getfreshsignedstate)); the basic `data` reads do NOT revert with `OffchainLookup`. * **EIP-7951** — P-256 verify precompile. The verification primitive for the WebAuthn-ES256 Verifier scheme ([§3.2](/spec/verifier#32-registered-signature-schemes-v1) row 1). * **ENSIP-10** — Wildcard Resolution. Referenced but NOT used in v1 (single-name model; deferred per [§9](/spec/deferred)). * **ENSIP-22** / **ERC-7996** — Resolver Capability Discovery (`IERC7996.supportsFeature(bytes4)`). The single-call detection signal for AuthResolver capability ([§4.4](/spec/authresolver#44-eip-165--ierc7996-advertisement-normative)). * **ENSIP-23** — Universal Resolver V2 error vocabulary (`ResolverNotFound`, `UnsupportedResolverProfile(bytes4)`, etc.) flowing through to AuthResolver callers ([§4.4](/spec/authresolver#44-eip-165--ierc7996-advertisement-normative), [§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties)). * **ENSIP-25** — Verifiable agent identity binding (ENS name ↔ ERC-8004 record). Identity-layer precondition for AuthResolverImpl use, not enforced by AuthResolverImpl ([§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties)). * **ENSIP-26** — Agent-context records (services, endpoints, attribution). Attribution-layer composition alongside AuthResolverImpl; relying parties compose `verifyAction` + `text(node, "services[X]")` reads themselves. Not enforced by AuthResolverImpl ([§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties)). * **ENSIP-64** — Typed text records. Reserved for human-readable metadata sibling namespaces (e.g., `auth.credential.label[]`); not used for credential bytes ([§4.5](/spec/authresolver#45-record-profile-and-eac-role-grants-normative)). * **ERC-8004** — Agent identity registry. Referenced via ENSIP-25 binding; AuthResolverImpl does not call the registry directly ([§2](/spec/#2-composite-of-standards) row 7). ### 10.2 Informative references Background and source-material pointers. Implementers do not need to read these to achieve conformance, but they explain the design context and identify the canonical Solidity source the spec's normative claims are derived from. * **ENSv2 contracts source** — `github.com/ensdomains/contracts-v2` — canonical Solidity for PermissionedResolver, EnhancedAccessControl, HCAContextUpgradeable, HCAEquivalence, UniversalResolverV2. * **VerifiableFactory source** — `github.com/ensdomains/verifiable-factory` — canonical Solidity for the UUPS proxy factory used at [§4.3](/spec/authresolver#43-deployment-model-normative). * **ENS Resolver Capability Discovery** — `docs.ens.domains` ENSIP-22 page and the `IERC7996` reference for the feature-id pattern at [§4.4](/spec/authresolver#44-eip-165--ierc7996-advertisement-normative). ## 8. Security Considerations This section is intentionally narrow in this revision. Full threat-model treatment (replay, key-compromise, gateway-trust, governance-capture) is deferred to a later v1.0-draft revision in parallel with the signed-freshness specification. **Verifier scheme-handler trust.** The EIP-1271 path executes a staticcall to a caller-supplied contract address. Per [§3.2](/spec/verifier#32-registered-signature-schemes-v1) and [§3.5](/spec/verifier#35-security-considerations), the Verifier MUST gas-cap the staticcall and treat any revert as `valid = false`. The Verifier's correctness depends on the EIP-7951 precompile being live and correct on the target chain. **Per-instance upgrade authority.** Per [§4.7](/spec/authresolver#47-upgrade-authority-normative), each AuthResolver proxy is independently upgradeable by its owner. A compromised name owner key can upgrade their proxy to a malicious implementation at the same address. Relying parties that care about implementation integrity SHOULD verify the proxy's implementation slot against a known-audited registry on every critical `verifyAction` call. This revision does not mandate this; the optional implementation-version registry is the deferred mechanism. **Name re-registration.** Per [§5.3](/spec/verify-action#53-caveats-and-lifecycle-rules-normative-for-relying-parties), relying parties MUST NOT cache resolver addresses across sessions. v2's per-name resolver model auto-clears stale records on ownership change *only if* relying parties re-resolve via Universal Resolver on each check. The narrow attack scenarios (deliberate attacker reusing old resolver, misconfigured registration tool defaulting to "preserve previous resolver") are addressed by the relying-party caching rule plus the optional `verifyContract(proxy, expectedImplementation)` provenance SDK call. **HCA attribution misconfiguration.** Per [§4.6](/spec/authresolver#46-hca-attribution-normative), granting `ROLE_SET_DATA` to a smart-account proxy address rather than the controlling EOA results in the role check failing silently for smart-account-backed MARPs operating under HCA. This is a deployment-time correctness concern, not a runtime threat — but Wave-1 MARP integration guides MUST cover it explicitly. See also [§4.5.2](/spec/authresolver#452-admin-role-layer-normative) for the admin-role layer governing role delegation, which is a separate misconfiguration vector (granting `ROLE_SET_DATA` without `ROLE_SET_DATA_ADMIN` means the grantee can publish records but cannot delegate further). **No replay protection at the Verifier layer.** Per [§3.5](/spec/verifier#35-security-considerations), replay binding is the caller's responsibility. The `stateHash` field of `VerificationResult` ([§5.1](/spec/verify-action#51-signature-return-type-denyreason-enum)) provides a deterministic binding that callers can use as a replay nonce, but the AuthResolverImpl does not enforce nonce uniqueness or message freshness beyond the credential's validity window. ## 7. End-to-End Verification Flow ```mermaid sequenceDiagram autonumber participant RP as Relying Party participant UR as Universal Resolver V2 participant AR as AuthResolverImpl
(per-name UUPS proxy) participant V as Verifier participant P as EIP-7951 / ecrecover / EIP-1271 Note over RP: Receives signed action from agent:
{node, credentialId, message, signature} RP->>UR: findResolver(dnsEncode(name)) UR-->>RP: AuthResolver proxy address RP->>AR: verifyAction(node, credentialId, message, signature) AR->>AR: data(node, "auth.credential[id]") → CBOR bytes AR->>AR: CBOR-decode → CredentialRecord AR->>AR: check notBefore ≤ block.timestamp ≤ notAfter AR->>AR: data(node, "auth.revocation[id]") → check empty AR->>V: isSchemeSupported(schemeId) V-->>AR: true AR->>V: verify(schemeId, message, signature, pubKey) V->>P: dispatch by schemeId
(P-256 precompile | ecrecover | staticcall) P-->>V: valid bool V-->>AR: valid bool AR-->>RP: VerificationResult{allowed, reason, resolvedAt, stateHash} Note over RP: Enforce allow/deny on reason ``` The flow generalizes across all three v1 schemes. The dispatch step (P) varies by `schemeId` on the resolved credential; the rest of the orchestration is identical. The Verifier is a single shared contract; the AuthResolver is per-name. ## 3. Verifier ### 3.1 Purpose and invariants The Verifier is a single shared contract responsible for **signature verification and scheme dispatch only**. It has no records, no roles, no policy state, and no per-name configuration. A conformant Verifier: * **MUST** be permissionlessly callable. Any contract or EOA can invoke `verify(...)` without prior registration. * **MUST NOT** hold per-call state. `verify` is `view`; no replay protection lives at this layer. * **MUST NOT** revert on an unsupported `schemeId`. It MUST return `valid = false` instead, so the caller (typically the AuthResolverImpl's `verifyAction`) can map the outcome to a structured `DenyReason` (per [§5.1](/spec/verify-action#51-signature-return-type-denyreason-enum)). * **MUST** be deployed once per chain. The AuthResolverImpl's deployment salt embeds a `versionId` ([§4.3](/spec/authresolver#43-deployment-model-normative)); the Verifier's address is captured by that versioning. ### 3.2 Registered signature schemes (v1) v1 ships exactly **three** registered schemes, chosen to cover the EOA, smart-contract-account, and passkey signing models present across named Wave-1 MARP candidates from day zero: | schemeId | Scheme | Verification primitive | Covers | | ------------------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `keccak256("WebAuthn-ES256")` | ECDSA over P-256 with SHA-256 challenge hashing | EIP-7951 P-256 precompile | Passkey-backed signing (WebAuthn authenticators) | | `keccak256("ECDSA-secp256k1")` | ECDSA over secp256k1 with keccak256 challenge hashing | `ecrecover` | EOA signing, including standard agent-runtime signing endpoints (e.g., Bankr Agents' `/agent/sign`) | | `keccak256("EIP-1271")` | Contract-account signature | staticcall to `pubKey`-encoded contract's `isValidSignature(bytes32, bytes)` returning the EIP-1271 magic value | Smart-contract-account signing (EIP-7702-delegated wallets, ZeroDev/Kernel, generic ERC-4337 accounts) | A conformant Verifier: * **MUST** register all three schemes at deployment. Returning `false` from `isSchemeSupported` for any of the three is a conformance violation. * **MUST** dispatch by `schemeId`. The mapping above is the source of truth for v1. * For `WebAuthn-ES256`, the `message` parameter **MUST** be passed to the EIP-7951 precompile as the pre-hashed challenge per the WebAuthn assertion verification procedure. The Verifier MUST NOT re-hash the message before precompile invocation. * For `ECDSA-secp256k1`, the `pubKey` parameter **MUST** be 64 bytes (uncompressed, no `0x04` prefix); the Verifier MUST recover the address via `ecrecover` and compare against `keccak256(pubKey)[12:]`. EIP-712 typed-data hashing is the caller's responsibility — the Verifier sees the final 32-byte digest in `message`. * For `EIP-1271`, the `pubKey` parameter **MUST** be a 20-byte contract address (left-padded to `bytes` if the caller passes a longer encoding; the Verifier MUST take the rightmost 20 bytes). The Verifier MUST staticcall `isValidSignature(bytes32 hash, bytes signature)` and return `true` if and only if the returned 4 bytes equal `0x1626ba7e`. ### 3.3 `IVerifier` interface (NORMATIVE) ```solidity interface IVerifier { /// @notice Verify a signature using a registered signature scheme. /// @param schemeId keccak256 identifier (see §3.2). /// @param message Scheme-specific message bytes. For WebAuthn-ES256 this is the /// pre-hashed challenge; for ECDSA-secp256k1 this is a 32-byte digest; /// for EIP-1271 this is a 32-byte hash passed to the contract. /// @param signature Scheme-specific signature blob. /// @param pubKey Scheme-specific public key (uncompressed P-256 point for WebAuthn-ES256; /// 64-byte uncompressed secp256k1 key; 20-byte address for EIP-1271). /// @return valid True iff the signature verifies under the scheme. function verify(bytes32 schemeId, bytes calldata message, bytes calldata signature, bytes calldata pubKey) external view returns (bool valid); /// @notice Whether the Verifier dispatches the given scheme. function isSchemeSupported(bytes32 schemeId) external view returns (bool); } ``` ### 3.4 Dispatch surface and extensibility The `schemeId` parameter is the extensibility surface. Future cycles **MAY** register additional schemes (BLS12-381 aggregation, post-quantum candidates) by deploying a successor Verifier and bumping the `versionId` embedded in the AuthResolverImpl deployment salt ([§4.3](/spec/authresolver#43-deployment-model-normative)). Adding a scheme is a **new Verifier deployment**, not an upgrade of the v1 Verifier — v1 is immutable at the dispatch layer. A conformant Verifier **MUST NOT** expose a setter that adds, removes, or modifies scheme handlers post-deployment. Scheme registration is fixed at construction. ### 3.5 Security considerations * **No batch entry point.** v1 does not expose a batched `verifyMany`. Relying parties needing to verify N signatures call `verify` N times. Deferred to v1.1 if pilot integrations request it. * **No replay protection.** Replay binding is the caller's responsibility (typically via `stateHash` in `VerificationResult` per [§5.1](/spec/verify-action#51-signature-return-type-denyreason-enum), or via EIP-712 typed-data nonces in the application-layer message). * **Scheme handler trust.** The EIP-1271 path executes a staticcall to a contract address the caller controls (via `pubKey`). The Verifier MUST treat the call as untrusted (gas-bounded, revert-safe). Implementations SHOULD cap the staticcall gas to a documented limit (e.g., 100,000 gas) and treat any revert as `valid = false`. * **Precompile availability.** The EIP-7951 precompile MUST be live on the target chain at the AuthResolverImpl deployment block. Per ENS documentation (search "EIP-7951"), the precompile is available on Ethereum mainnet **after the Fusaka hardfork**. Per-L2 availability is not uniformly enumerated; deployment targets MUST confirm precompile presence on each chain before the AuthResolverImpl proxy is deployed there. ### 3.6 Conformance criteria (NORMATIVE) A conformant Verifier satisfies every MUST/MUST NOT and SHOULD/MAY in this table. This restates the normative content in §3.1–§3.5; the source sections remain authoritative for any discrepancy. | # | Requirement | Type | Source | | --- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------- | | V1 | Permissionlessly callable; no per-call state | MUST | §3.1 | | V2 | `verify` is `view`; no replay protection at this layer | MUST | §3.1, §3.5 | | V3 | Return `false` (not revert) for unsupported `schemeId` | MUST | §3.1 | | V4 | Deployed once per chain | MUST | §3.1 | | V5 | Register all three v1 schemes (`WebAuthn-ES256`, `ECDSA-secp256k1`, `EIP-1271`) at construction | MUST | §3.2 | | V6 | Dispatch by `schemeId` per the §3.2 table | MUST | §3.2 | | V7 | For `WebAuthn-ES256`: pass `message` as the pre-hashed challenge to the EIP-7951 precompile | MUST | §3.2 | | V8 | For `WebAuthn-ES256`: NOT re-hash the message before precompile invocation | MUST NOT | §3.2 | | V9 | For `ECDSA-secp256k1`: `pubKey` is 64 bytes uncompressed (no `0x04` prefix); recover via `ecrecover` and compare against `keccak256(pubKey)[12:]` | MUST | §3.2 | | V10 | For `EIP-1271`: take rightmost 20 bytes of `pubKey` as the contract address | MUST | §3.2 | | V11 | For `EIP-1271`: staticcall `isValidSignature(bytes32, bytes)`; return `true` iff returned 4 bytes equal `0x1626ba7e` | MUST | §3.2 | | V12 | NOT expose any setter that adds, removes, or modifies scheme handlers post-deployment | MUST NOT | §3.4 | | V13 | EIP-7951 precompile MUST be live on target chain at deployment block (per Fusaka hardfork status for mainnet; per-L2 confirmation required) | MUST | §3.5 | | V14 | Treat EIP-1271 staticcall as untrusted (gas-bounded, revert-safe) | MUST | §3.5 | | V15 | Cap EIP-1271 staticcall gas to a documented limit (e.g., 100K) and treat any revert as `valid = false` | SHOULD | §3.5 | | V16 | Expose a batched `verifyMany` entry point | MAY (deferred to v1.1) | §3.5 | ## 5. `verifyAction` Orchestration (NORMATIVE) ### 5.1 Signature, return type, `DenyReason` enum A conformant AuthResolverImpl **MUST** expose `verifyAction` with the following signature: ```solidity function verifyAction(bytes32 node, string calldata credentialId, bytes calldata message, bytes calldata signature) external view returns (VerificationResult memory); struct VerificationResult { bool allowed; DenyReason reason; uint64 resolvedAt; // block.timestamp at resolution bytes32 stateHash; // hash binding the result to the inputs that informed it } enum DenyReason { None, // allowed = true; reason is None Unverified, // signature did not verify, or credential record missing/empty Stale, // current block.timestamp outside credential's notBefore/notAfter window Revoked, // revocation record present and non-empty for the credential id Mismatch, // schemeId in credential record not supported by the Verifier PolicyDenied, // reserved — capability scope policy check failed (deferred to v1.1) EndpointUnproven // reserved — endpoint-binding check failed (deferred to v1.1) } ``` `stateHash` **MUST** be deterministic over the inputs that informed the decision. The reference computation is: ``` stateHash = keccak256(abi.encode(node, credentialId, keccak256(message), keccak256(signature), credentialRecordBytes, // raw CBOR bytes returned by data(node, "auth.credential[]") revocationRecordBytes, // raw CBOR bytes returned by data(node, "auth.revocation[]") block.number)) ``` Relying parties MAY use `stateHash` as a replay-binding nonce for the application-layer message; the AuthResolverImpl does NOT enforce replay protection at this layer. ### 5.2 Required behavior (NORMATIVE ordering) A conformant implementation **MUST** execute the following checks in order. On the first check that fails, it MUST return immediately with `allowed = false` and the corresponding `DenyReason`; it MUST NOT continue evaluating subsequent checks. 1. **Credential lookup.** Read `auth.credential[]` via the inherited `data(node, key)`. If the returned bytes are empty, return `{allowed: false, reason: Unverified}`. 2. **Decode.** Decode the CBOR bytes into a `CredentialRecord` ([§6.1](/spec/record-schemas#61-credentialrecord)). Decoding failure MUST return `{allowed: false, reason: Unverified}`. 3. **Validity window.** If `block.timestamp < notBefore` OR (`notAfter != 0` AND `block.timestamp > notAfter`), return `{allowed: false, reason: Stale}`. A `notAfter` of `0` means "no expiry." 4. **Revocation check.** Read `auth.revocation[]`. If the returned bytes are non-empty, return `{allowed: false, reason: Revoked}`. (Decoding the revocation record's reason/timestamp fields is OPTIONAL for the deny decision; the presence of any bytes is sufficient.) 5. **Scheme support.** Call `IVerifier(verifier).isSchemeSupported(credentialRecord.schemeId)`. If false, return `{allowed: false, reason: Mismatch}`. 6. **Signature verification.** Call `IVerifier(verifier).verify(credentialRecord.schemeId, message, signature, credentialRecord.pubKey)`. If false, return `{allowed: false, reason: Unverified}`. 7. **Success.** Return `{allowed: true, reason: None, resolvedAt: uint64(block.timestamp), stateHash: }`. Capability-scope policy enforcement (step that would set `reason: PolicyDenied`) and endpoint-binding checks (`reason: EndpointUnproven`) are reserved in the enum but **MUST NOT** be performed in v1. Implementations that perform additional checks beyond steps 1–7 are non-conformant for this revision. ### 5.3 Caveats and lifecycle rules (NORMATIVE for relying parties) **Direct invocation, not UR-routed.** `verifyAction` is a custom convenience method, not a standard ENS resolver profile. Universal Resolver V2 does not route `verifyAction` calls — a UR-routed call attempting `verifyAction` would surface as `UnsupportedResolverProfile(selector)` (the typed UR error catalogued in [§4.4](/spec/authresolver#44-eip-165--ierc7996-advertisement-normative)). Relying parties **MUST** first resolve `name → resolver address` via `UniversalResolverV2.findResolver(name)` and then call `verifyAction` directly on the resolver contract address. Equivalently, callers MAY use `resolveWithResolver(resolver, name, data, gateways)` which bypasses `findResolver` but still runs through UR's CCIP-Read infrastructure. **SDK normalization.** Any SDK or CLI tooling that constructs a namehash from a user-typed ENS name **MUST** normalize the input via ENSIP-15 (`@adraffy/ens-normalize` or the equivalent in the host library) before hashing — this is the universal ENS normalization correctness rule (ENSIP-15). AuthResolverImpl itself receives an already-resolved `bytes32 node` and does not normalize; the obligation is on the caller's side. **Name lifecycle and credential lifecycle.** AuthResolver records are keyed by namehash (`keccak256(node, partHash(key))` per [§4.5.3](/spec/authresolver#453-resource-keying)), which is invariant across registrations of the same name. v2 provides **two complementary clearing mechanisms** on ownership change: 1. **Fresh-proxy deployment (registry-side).** New owner deploys a fresh AuthResolver proxy via VerifiableFactory. Per [§4.3](/spec/authresolver#43-deployment-model-normative), the proxy address depends on `msg.sender` at deploy time, so a new owner (or a new deployer) yields a different CREATE2 address. New owner passes the fresh proxy as the resolver parameter to `register`, or replaces the subregistry wholesale. Universal Resolver V2 now routes the name to the new proxy. The **old proxy still holds its old records onchain** — clearing is an artifact of the registry no longer pointing to it, not a state change on the old proxy. 2. **`clearRecords(node)` (resolver-side, single tx).** `PermissionedResolver.sol:136,250-255` exposes `clearRecords(bytes32 node)` which bumps an internal per-node version counter `_versions[node]` and orphans all prior records for that node in a single transaction. This is the right mechanism when the *same* AuthResolver proxy is retained across an ownership change (e.g., transferring control of an existing setup) and the new owner wants a clean slate without redeploying. `clearRecords` is gated by `ROLE_SET_DATA` (same as `setData`). Both mechanisms exist; an implementer chooses based on whether the AuthResolver proxy itself is being replaced (mechanism 1) or retained (mechanism 2). Relying parties **MUST NOT** cache resolver addresses across sessions. They **MUST** re-resolve via Universal Resolver V2 on every authentication check. Caching the proxy address from a previous session bypasses mechanism 1's registry-side protection and admits the stale-credential threat. Per-record stale-state detection requires reading `recordVersions(node)` (the version counter mechanism 2 bumps) and rejecting cached record values that don't carry the current version. For names re-registered to a new owner who deliberately reuses the previous owner's resolver address as the `resolver` parameter to `register` (rare; requires intent + knowledge of the old address) and does NOT call `clearRecords`, the old credentials remain live until explicitly cleared. The relying-party caching rule above is the primary mitigation; an optional `verifyContract(proxy, expectedImplementation)` provenance check (see [§4.3](/spec/authresolver#43-deployment-model-normative)) is the belt-and-suspenders mitigation. **Aliasing.** If the AuthResolver name has been aliased to another name via `setAlias` (inherited from PermissionedResolver), the alias rewrite happens only inside UR's `resolve` path. Direct calls to `verifyAction` using the aliased name's namehash return the *unaliased* records. Callers using `verifyAction` directly on an aliased name **MUST** pre-resolve the alias via UR before calling, or accept that the unaliased records are what they see. Full alias semantics for multi-name MARPs are deferred to v1.1. **ENSIP-25 binding (identity layer).** AuthResolverImpl assumes — but does not enforce — that the ENS name has a valid ENSIP-25 binding to an ERC-8004 identity. Relying parties that need atomic binding+authentication verification **SHOULD** compose the two reads themselves (e.g., via `IMulticallable`) in a single batched call. Adding ENSIP-25 enforcement inside `verifyAction` is deferred to v1.1 pending NCCoE position-paper discussion. **ENSIP-26 attribution (attribution layer).** AuthResolverImpl assumes — but does not enforce — that ENSIP-26 agent-context records (`services[name]`, agent description, endpoints) may exist under the same ENS name in a parallel namespace. Relying parties needing endpoint-binding context (e.g., "did this signed action come from a service endpoint the agent actually published?") **SHOULD** compose `verifyAction(node, credentialId, msg, sig)` with `text(node, "services[X]")` reads in a single batched `IMulticallable` call. The `DenyReason.EndpointUnproven` value (§5.1) is reserved for future versions where AuthResolverImpl might enforce this binding inside `verifyAction`; v1 implementations MUST NOT return `EndpointUnproven` (per §5.2 and §5.4 row F6). Active enforcement of the attribution-layer composition is deferred to v1.1. ### 5.4 Conformance criteria (NORMATIVE) A conformant `verifyAction` implementation (on the AuthResolverImpl side) and a conformant relying-party integration satisfy the requirements in this table. The implementation-side rows are MUST/MUST NOT for the AuthResolverImpl; the relying-party-side rows are MUST/MUST NOT/SHOULD for SDKs and integrators consuming the spec. | # | Requirement | Type | Source | Applies to | | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------- | ----------------- | | F1 | Expose `verifyAction(bytes32 node, string credentialId, bytes message, bytes signature) external view returns (VerificationResult memory)` | MUST | §5.1 | AuthResolverImpl | | F2 | Return `VerificationResult { bool allowed; DenyReason reason; uint64 resolvedAt; bytes32 stateHash; }` | MUST | §5.1 | AuthResolverImpl | | F3 | Compute `stateHash` deterministically over the inputs that informed the decision (reference computation per §5.1) | MUST | §5.1 | AuthResolverImpl | | F4 | Execute the §5.2 7-step ordering: credential lookup → CBOR decode → validity window → revocation check → scheme support → signature verification → success | MUST | §5.2 | AuthResolverImpl | | F5 | On first failed step, return immediately with `allowed = false` and the corresponding `DenyReason`; NOT continue evaluating subsequent steps | MUST | §5.2 | AuthResolverImpl | | F6 | NOT perform additional checks beyond steps 1–7 (capability-scope `PolicyDenied` and endpoint-binding `EndpointUnproven` are reserved for v1.1) | MUST NOT | §5.2 | AuthResolverImpl | | F7 | First resolve `name → resolver address` via `UniversalResolverV2.findResolver(name)`, then call `verifyAction` directly on the resolver contract address | MUST | §5.3 | Relying party | | F8 | Normalize user-typed ENS names via ENSIP-15 (`@adraffy/ens-normalize`) before constructing namehash | MUST | §5.3 | SDK / CLI tooling | | F9 | NOT cache resolver addresses across sessions | MUST NOT | §5.3 | Relying party | | F10 | Re-resolve via Universal Resolver V2 on every authentication check | MUST | §5.3 | Relying party | | F11 | On aliased names: pre-resolve the alias via UR before calling `verifyAction` directly (or accept that unaliased records are returned) | MUST | §5.3 | Relying party | | F12 | When atomic binding+authentication verification is required: compose ENSIP-25 binding read and `verifyAction` in a single batched call (e.g., via `IMulticallable`) | SHOULD | §5.3 | Relying party | | F13 | When implementation integrity matters: verify the proxy's impl slot against a known-audited registry on every critical `verifyAction` call | SHOULD | [§8](/spec/security) | Relying party | ## DNSSEC Resolution Pattern **DNSSEC Onchain Resolution** enables trustless mapping from DNS domains to ENS names through cryptographic proof validation on Ethereum. This breakthrough allows DNS zones to become first-class participants in the decentralized identity ecosystem, bridging traditional DNS infrastructure with blockchain-based name resolution. ### Core Concept DNSSEC (Domain Name System Security Extensions) provides cryptographic signatures for DNS records, ensuring their authenticity and integrity. By validating these signatures onchain, we create a trustless bridge between the DNS ecosystem and Ethereum Name Service (ENS). ```mermaid graph TD A[DNS Domain] --> B[DNSSEC Signed Records] B --> C[CCIP-Read Gateway] C --> D[Onchain Verification] D --> E[ENS Name Resolution] F[DNS Root Trust] -.-> D G[Ethereum Consensus] -.-> D ``` ### Why DNSSEC Onchain Matters #### The Problem Traditional DNS lacks cryptographic guarantees by default. In contrast, ENS requires cryptographic proof of ownership for both .eth names (via Ethereum transactions) and DNS-based names (via DNSSEC and TXT records). This minimizes trust dependencies on centralized registrars or manual verification processes. #### The Solution DNSSEC onchain resolution uses DNSSEC’s cryptographic infrastructure to provide verifiable, onchain proof that a DNS domain is controlled by the claimant, enabling secure import of DNS names into ENS. ### Trust Model Architecture #### Hierarchical Chain of Trust DNSSEC establishes a hierarchical chain of trust, starting from the DNS root zone and extending down to individual DNS records: 1. **DNS Root Zone**: Contains the root Key Signing Keys (KSK), which sign the keys of top-level domains (TLDs). 2. **Top-Level Domains (TLDs)**: Use their keys to sign delegations to second-level domains (SLDs). 3. **Second-Level Domains (SLDs)**: Sign their own zone records, including subdomains and resource records. 4. **Individual Records**: Each resource record is cryptographically signed, allowing verification of authenticity all the way up to the root. #### ENS-Specific Implementation ENS uses Algorithm 13 (ECDSAP256SHA256) for DNSSEC verification, which requires: * P-256 (secp256r1) elliptic curve signatures * SHA-256 hashing * Onchain verification using a precompile for secp256r1 signatures (as specified in EIP-7951), available at address 0x100 on supported EVM chains ### Proof System Mechanics #### Core Components * **Resource Records (RR)** - Individual DNS records containing domain data * **RRSIG Records** - Cryptographic signatures covering RR sets * **DNSKEY Records** - Public keys used for signature verification * **DS Records** - Delegation signers proving parent-child zone relationships #### Proof Bundle Structure When resolving a DNS name, the system constructs a complete cryptographic proof containing: ```json { "question": { "qname": "bytes", "qtype": "uint16" }, "answerRRsets": "RR[]", "canonicalRRsetBytes": "bytes", "RRSIGSignatures": "RRSIG[]", "DNSKEYRecords": "DNSKEY[]", "DSDelegationProofs": "DS[]", "timestamps": { "queryTime": "uint256", "validFrom": "uint256", "validUntil": "uint256" } } ``` **Note:** This JSON structure is for educational purposes only and represents a conceptual view of the DNSSEC proof bundle components as described in the Universal Resolver Matrix. Actual contract interfaces may use different data structures. ### Verification Process #### Two-Phase Resolution **Phase 1: Reverse Resolution** * Query DNS for TXT records proving ENS name ownership * Example: `_ens.example.com TXT "ens_name=alice.eth"` **Phase 2: Forward Verification** * Resolve the claimed ENS name to verify the reverse mapping * Ensures bidirectional consistency between DNS and ENS #### Onchain Validation Steps 1. **Canonicalize** DNS records according to RFC standards 2. **Verify RRSIG signatures** using DNSKEY records 3. **Validate delegation chain** through DS record proofs 4. **Check temporal validity** of signatures 5. **Extract ENS attribution** from verified TXT records ### Deployment Architecture #### L1 vs Namechain Considerations **Ethereum L1 Deployment:** * Maximum security through L1 consensus * Higher gas costs (\~$5-15 per verification) * Direct trust in Ethereum's validator set **ENS Namechain (L2) Deployment:** * Cost-effective resolution (\~$0.01-0.30 per operation) * Combined security of L2 rollup + DNSSEC cryptography * Optimal for production DNSSEC integration ### Integration Patterns #### TXT Record Attribution DNS zones prove ENS name ownership through specially formatted TXT records: ``` _ens.example.com TXT "ens_name=alice.eth" _ens.subdomain.example.com TXT "ens_name=bob.eth" ``` #### Wildcard Resolution Supports dynamic subdomain resolution for large DNS zones without pre-registering every subdomain. #### Cross-Chain Applications Once verified onchain, DNS-attributed ENS names work seamlessly across all EVM-compatible chains through ENSIP-19 multichain resolution. ### Security Properties #### Cryptographic Guarantees * **Authenticity**: DNS records are cryptographically signed by zone operators * **Integrity**: Any tampering breaks signature verification * **Non-repudiation**: Zone operators cannot deny signing their records * **Trust minimization**: Security depends only on DNS root keys + Ethereum consensus #### Protected Attack Vectors * DNS spoofing and cache poisoning * Man-in-the-middle attacks * Gateway manipulation attempts * Record tampering and injection ### Implementation Considerations #### Contract Architecture The DNSSEC resolver system uses a two-contract pattern: 1. **DnssecP256Verifier** - Holds trust anchors and performs cryptographic validation 2. **DnssecResolver** - Handles CCIP-Read integration and resolution routing #### Gateway Infrastructure Trustless gateways fetch DNSSEC proofs from authoritative servers and format them for onchain verification. While gateways provide availability, they cannot compromise correctness - invalid proofs are always rejected. ### Relationship to Universal Resolver Matrix DNSSEC Onchain Resolution serves as a critical **Trust Model** within the URM framework: * **Trust Model**: DNSSEC cryptographic signatures + DNS root authority * **Proof System**: Algorithm 13 (ECDSAP256SHA256) with P-256 signatures * **Rules & Lifecycle**: DNSSEC key management and signature validity periods * **Verification Path**: CCIP-Read assisted resolution with onchain validation This integration enables DNS zones to participate as first-class namespaces in the broader cross-chain identity ecosystem. ### Technical Specification For complete implementation details, refer to the comprehensive technical specification: **[DNSSEC Resolution Specification](/specifications/dnssec-specification)** The specification covers contract interfaces, proof formats, deployment architectures, and edge cases in detail. ## EVM Resolution Pattern **EVM Resolution** enables consistent primary name resolution across all Ethereum Virtual Machine (EVM) compatible blockchains through ENSIP-19. This pattern standardizes how users can set and resolve primary names across multiple EVM chains, creating a unified identity experience. ### Core Concept ENSIP-19 extends ENS functionality beyond Ethereum mainnet to support primary names across the entire EVM ecosystem. Users can set primary names that work consistently across different EVM chains, enabling seamless cross-chain identity resolution. ```mermaid graph TD A[User Sets Primary Name] --> B[ENS Registry - Mainnet] B --> C[Resolver Contract - Chain-Specific] C --> D[Cross-Chain Resolution] E[Ethereum Mainnet] -.-> B F[EVM Chain X] -.-> C G[EVM Chain Y] -.-> C ``` ### Why EVM Resolution Matters #### The Problem Without ENSIP-19, each EVM chain would need its own primary name system, creating fragmentation and requiring users to manage separate identities on each chain. #### The Solution ENSIP-19 provides a standardized framework for primary names that works across all EVM chains, with chain-specific resolvers that reference the main ENS registry. ### Trust Model Architecture #### Hierarchical Resolution Primary names follow a two-phase resolution process: 1. **Reverse Resolution**: Address → Name lookup 2. **Forward Verification**: Name → Address verification #### Chain-Specific Implementation Each EVM chain can have its own resolver contracts while referencing the authoritative ENS registry on mainnet for primary name ownership. ### Resolution Process #### Primary Name Setting Users set primary names through resolver contracts on specific chains: * **Mainnet Primary Names**: Set via `0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e` * **Chain-Specific Names**: Set via chain-specific resolver contracts #### Cross-Chain Resolution When resolving on any EVM chain: 1. Query the chain-specific resolver 2. If no chain-specific name exists, fall back to mainnet primary name 3. Return the appropriate name for that context ### Integration Patterns #### Wallet Integration Wallets can display primary names by: 1. Detecting the current chain 2. Querying the appropriate resolver contract 3. Displaying the chain-specific or fallback primary name #### dApp Integration Applications can resolve primary names using standard ENS resolution libraries with chain-aware parameters. ### Relationship to Universal Resolver Matrix EVM Resolution serves as the **ENSIP-19 Profile** within the URM framework: * **Trust Model**: Ethereum consensus + ENS registry authority * **Proof System**: ENSIP-19 resolution algorithm with fallback logic * **Rules & Lifecycle**: Primary name registration and management across chains * **Verification Path**: Two-phase resolution with chain-specific fallbacks This integration enables seamless primary name resolution across the entire EVM ecosystem while maintaining backward compatibility with existing ENS infrastructure. ### Technical Specification For complete implementation details, refer to the comprehensive technical specification: **[EVM Resolution Specification](/specifications/evm-specification)** The specification covers ENSIP-19 implementation, resolver contracts, and cross-chain compatibility in detail. ## Non-EVM Resolution Pattern **Non-EVM Resolution** extends ENSIP-19 to enable primary name resolution for non-Ethereum Virtual Machine (non-EVM) blockchains including Cosmos, Solana, and other ecosystems. This pattern enables bidirectional identity resolution (address ↔ name) across fundamentally different blockchain architectures. ### Core Concept While ENSIP-19 provides forward resolution (name → address) for non-EVM chains, it lacks reverse resolution (address → name) capabilities. Non-EVM Resolution fills this gap by establishing resolver infrastructure that enables complete bidirectional identity mapping across heterogeneous blockchain networks. ```mermaid graph TD A[Non-EVM Chain] --> B[State Bridge] B --> C[ENS Namechain] C --> D[Cross-Chain Resolver] D --> E[Bidirectional Resolution] F[Cosmos SDK] -.-> A G[Solana Runtime] -.-> A H[ENS L2] -.-> C ``` ### Why Non-EVM Resolution Matters #### The Problem Non-EVM blockchains have fundamentally different architectures, consensus mechanisms, and state models than EVM chains. ENSIP-19's EVM-centric design doesn't account for these differences, leaving non-EVM chains with incomplete identity resolution. #### The Solution Non-EVM Resolution establishes a universal bridge architecture that can handle any blockchain's state model while maintaining ENS compatibility. ### Trust Model Architecture #### Heterogeneous State Models Different blockchains require different proof systems: * **Cosmos SDK Chains**: IAVL tree proofs with Tendermint consensus * **Solana**: Sparse Merkle Tree proofs with proof-of-history * **Substrate Chains**: Custom state proofs with GRANDPA finality #### Bridge-Based Verification Trust flows through verified state bridges that commit non-EVM state roots to Ethereum, enabling onchain verification without trusting external oracles. ### Resolution Process #### State Commitment Flow 1. **Non-EVM Registrar**: Records address → name mappings on native chain 2. **State Bridge**: Commits state roots to Ethereum L1 3. **ENS Resolver**: Verifies proofs against committed roots 4. **Bidirectional Resolution**: Enables both forward and reverse lookups #### Proof System Flexibility The pattern supports multiple proof formats to accommodate different blockchain architectures while maintaining unified ENS interface compatibility. ### Integration Patterns #### Universal Bridge Interface Resolvers implement a standardized interface that can verify proofs from any supported blockchain, abstracting away the underlying proof system complexity. #### Chain-Specific Adaptations Each blockchain integration includes: * Native registrar contracts * Bridge verifier contracts * Chain-specific proof formats * Gas-optimized resolution paths ### Security Properties #### Cryptographic Verification * **State Proofs**: Each blockchain's native proof system * **Bridge Security**: Inherits security from both source and destination chains * **ENS Compatibility**: Maintains full compatibility with existing ENS infrastructure #### Attack Resistance * **Bridge Manipulation**: Invalid proofs rejected by verifier contracts * **State Tampering**: Protected by source chain consensus * **Resolution Spoofing**: Bidirectional verification prevents inconsistencies ### Relationship to Universal Resolver Matrix Non-EVM Resolution serves as the **Heterogeneous Chain Profile** within the URM framework: * **Trust Model**: Multi-chain bridge security + native consensus * **Proof System**: Blockchain-specific cryptographic proofs * **Rules & Lifecycle**: Cross-chain name registration and management * **Verification Path**: Bridge-assisted resolution with fallback mechanisms This integration enables the Universal Resolver to handle any blockchain architecture while maintaining a consistent ENS-based identity interface. ### Technical Specification For complete implementation details, refer to the comprehensive technical specification: **[Non-EVM Resolution Specification](/specifications/non-evm-specification)** The specification covers bridge architectures, proof systems, and cross-chain resolver implementations in detail. :::warning **Research Documentation**: This site presents hypothetical implementations and research concepts for the Universal Resolver Matrix framework. Content represents educational explorations and may not reflect current or planned features of Ethereum Name Service (ENS) or related protocols. ::: ### The Universal Resolver Matrix The **Universal Resolver Matrix (URM)** is a comprehensive framework for implementing universal name resolution across diverse namespaces and ecosystems. It provides a systematic approach to mapping resolution pathways using four core dimensions: * **Trust Model**, * **Proof System**, * **Rules & Lifecycle**, and * **Verification Path**. Built on Ethereum Name Service (ENS) infrastructure, the URM creates a unified identity resolution system that bridges traditional DNS, blockchain ecosystems, and modern authentication methods into a single, coherent framework. ### Universal Resolution Framework The URM implements a consistent **resolution choreography** across all namespaces through a unified four-dimensional framework: ```mermaid sequenceDiagram participant Client participant UniversalResolver participant NamespaceResolver participant Gateway participant Namespace participant Verifier Client->>+UniversalResolver: resolve(name, callData) UniversalResolver->>+NamespaceResolver: Route to appropriate resolver alt Direct Onchain Resolution NamespaceResolver->>NamespaceResolver: Read from contract storage NamespaceResolver-->>UniversalResolver: Return resolved data else Offchain Data Required NamespaceResolver->>Client: OffchainLookup(urls, callData, callback, extraData) Client->>+Gateway: HTTP GET/POST callData Gateway->>+Namespace: Query namespace-specific data Namespace-->>Gateway: Return raw data/proof Gateway->>Gateway: Construct namespace proof bundle Gateway-->>Client: Return proof bundle via CCIP-Read Client->>+NamespaceResolver: ccipCallback(proofBundle, extraData) end NamespaceResolver->>+Verifier: verify(proofBundle, query) Verifier->>Verifier: Validate using namespace trust model Verifier-->>NamespaceResolver: (valid: true, canonicalData) NamespaceResolver-->>UniversalResolver: Return verified result UniversalResolver-->>Client: Final resolution result ``` :::note **Diagram Legend**: The conditional "alt" block shows two resolution strategies — direct onchain resolution for available data vs. CCIP-Read assisted resolution for cross-chain/namespace data fetching. ::: ### Core Resolution Profiles #### [DNSSEC Onchain Resolution](/patterns/dnssec-pattern) Trustless DNS→ENS resolution via DNSSEC P-256 signatures validated onchain. #### [EVM Resolution](/patterns/evm-pattern) ENSIP-19 multichain primary names across EVM ecosystems. #### [Non-EVM Resolution](/patterns/non-evm-pattern) Extends ENSIP-19 to non-EVM blockchains (Cosmos, Solana, etc.) #### [WebAuthn Resolution](/patterns/webauthn-pattern) Passkey-based ENS name control and authentication. The **agent-authority cell** the URM identified as highest-leverage — productionized in the [Prototype Specification](/spec/). ### Framework Architecture #### Four Core Dimensions The URM structures each resolution profile around four fundamental dimensions: 1. **Trust Model** — Establishes the cryptographic and organizational trust foundations 2. **Proof System** — Defines how proofs are generated, validated, and verified 3. **Rules & Lifecycle** — Governs the creation, management, and expiration of names 4. **Verification Path** — Maps the complete resolution pathway from query to result #### Technical Specifications Each profile provides comprehensive technical references including: * Contract & namespace inventory * Deployment architecture considerations * Edge cases and client requirements * Implementation details and best practices ### Technical Resources #### [ENSIP Specifications](https://docs.ens.domains/ensip/) Official ENS Improvement Proposals and technical standards. #### [EIP-7951: P-256 Precompile](https://eips.ethereum.org/EIPS/eip-7951) Ethereum precompile for P-256 elliptic curve operations. #### [ENSIP-19: Multichain Names](https://docs.ens.domains/ensip/19) Primary name resolution across multiple blockchains. #### [DNSSEC Algorithm 13](https://www.rfc-editor.org/rfc/rfc6605.html) P-256 ECDSA signatures for DNSSEC validation. ## WebAuthn Resolution Pattern **WebAuthn Resolution** integrates Web Authentication (WebAuthn) passkeys with Ethereum Name Service (ENS) to enable hardware-backed identity management. This pattern allows users to control ENS names and authenticate transactions using biometric or hardware security keys instead of traditional private keys. ### Core Concept WebAuthn enables secure, phishing-resistant authentication through hardware-backed credentials. By integrating WebAuthn with ENS, users can control name ownership and execute transactions using passkeys, creating a more secure and user-friendly identity management system. ```mermaid graph TD A[User Device] --> B[WebAuthn API] B --> C[Hardware Key] C --> D[Passkey Signature] D --> E[ENS Resolver] E --> F[Name Control/Auth] G[Biometric] -.-> C H[FIDO2] -.-> C I[Transaction Signing] -.-> F ``` ### Why WebAuthn Resolution Matters #### The Problem Traditional crypto wallets rely on seed phrases and private keys that are vulnerable to phishing, malware, and user error. ENS name management requires the same security model as token transfers. #### The Solution WebAuthn Resolution enables ENS name control through hardware-backed passkeys, providing phishing-resistant authentication and eliminating the need for traditional key management. ### Trust Model Architecture #### Hardware-Backed Security WebAuthn credentials are stored in dedicated hardware security modules: * **Platform Authenticators**: Built into devices (Touch ID, Face ID, Windows Hello) * **Roaming Authenticators**: External security keys (YubiKey, Titan Security Key) * **Cryptographic Isolation**: Private keys never leave the hardware #### ENS Integration Passkeys are associated with ENS names through resolver contracts that verify WebAuthn signatures onchain using the EIP-7951 P-256 precompile. ### Resolution Process #### Credential Registration 1. **Generate Passkey**: User creates WebAuthn credential via browser API 2. **Register with ENS**: Public key stored in resolver contract 3. **Authorize Control**: Passkey authorized for name management operations #### Authentication Flow 1. **Challenge Generation**: dApp/resolver creates authentication challenge 2. **User Verification**: Passkey signs challenge via WebAuthn API 3. **Onchain Verification**: Resolver verifies signature using stored public key 4. **Access Granted**: User can control ENS name or authenticate transactions ### Integration Patterns #### Dual Profile Architecture **Profile 1: Controller** * Passkeys control ENS name ownership * Users can update records using hardware authentication * Primary name management without traditional keys **Profile 2: Authentication** * Passkeys used for transaction authentication * dApps can request WebAuthn verification for user actions * Optional hybrid approach with gateway-assisted verification #### Resolver Implementation Two resolver types support different use cases: * **WebAuthnControllerResolver**: Full name control via passkeys * **WebAuthnCredentialResolver**: Authentication-only operations ### Security Properties #### Cryptographic Guarantees * **P-256 ECDSA**: Hardware-backed elliptic curve signatures * **SHA-256 Hashing**: Standard cryptographic hashing * **Challenge-Response**: Prevents replay attacks * **Origin Verification**: Prevents cross-origin attacks #### Phishing Protection * **Relying Party Verification**: Signatures bound to specific origins * **Challenge Freshness**: Time-limited authentication challenges * **Hardware Isolation**: Private keys protected by hardware security ### Implementation Architecture #### Onchain Verification All cryptographic verification happens onchain using: * **EIP-7951**: P-256 precompile for signature verification * **ENS Namechain**: Cost-effective verification operations * **Resolver Contracts**: Store authorized public keys and verify signatures #### Optional Gateway Support For applications requiring sub-cent verification costs: * **Offchain Verification**: Gateway verifies signatures before storing attestations * **Cost Optimization**: Reduces per-verification gas costs * **Audit Trail**: Maintains onchain verification record ### Relationship to Universal Resolver Matrix WebAuthn Resolution serves as the **Hardware Authentication Profile** within the URM framework: * **Trust Model**: Hardware security + WebAuthn protocol guarantees * **Proof System**: P-256 ECDSA signatures with challenge-response authentication * **Rules & Lifecycle**: Passkey registration, authorization, and revocation * **Verification Path**: Direct onchain verification with optional gateway assistance This integration enables secure, user-friendly ENS name management through hardware-backed authentication while maintaining full compatibility with existing ENS infrastructure. ### Technical Specification For complete implementation details, refer to the comprehensive technical specification: **[WebAuthn Resolution Specification](/specifications/webauthn-specification)** The specification covers WebAuthn integration, resolver contracts, and authentication flows in detail.