Approaches to Support Setting Primary Names for All Contracts

Problem Statement

Currently, setting primary ENS names for smart contracts is limited to a subset of contracts. To be able to set a primary name, a contract must:

  • Extend Ownable or ERC-173

  • Be capable of delegating reverse address claim permissions to another externally owned account (EOA) via methods like reverseClaimer or reverseSetter (which must be set at deployment and cannot be changed afterward)

  • Able to make arbitrary calls, can set a primary name.

This significantly restricts the number of contracts, especially those already deployed or not meeting the above criteria, that can obtain a primary name.

The Enscribe team has been thinking about how we could expand this capability so that, in principle, every contract can have a primary name set.

High-Level Solution

We propose extending the current permission logic in the Reverse Registrar contract.

Instead of limiting this capability exclusively to contracts that implement Ownable/ERC-173, we suggest also allowing the original deployer of the contract to set the primary name.

This approach achieves two key objectives:

  • Universal Accessibility: Enables every contract address to set a primary name.

  • Security: Ensures only authorized entities (contract owner or verified deployer) can set primary names.

To achieve this, we must reliably verify the contract deployer on-chain or allow any address to securely prove that it deployed the contract.

Potential Implementation Approaches

We have identified a number of potential methods for verifying deployer addresses:

  • Oracle / CCIP-Read / EigenLayer AVS:
    Utilize off-chain services to fetch and verify deployer addresses, with on-chain verification.

  • ZK Proofs:
    Generate cryptographic proofs off-chain that verify deployer information, then verify these proofs on-chain.

  • Deployer Decoding from Contract Address:
    Derive the deployer’s address directly from the contract address by analyzing the nonce (for CREATE) or salt (for CREATE2) used during deployment.

Note: We still need to explore the possible implementation strategies in more depth and would love to initiate a discussion around the proposed high-level solution before we commence work on a PoC.

Changes Requested

With the L2 primary name support discussions in ENSIP-19, which enable setting primary names for L2 chains, we want to propose some changes to the L2ReverseRegistrar contract.

Since the L2ReverseRegistrar implementation has not yet been finalized and the mainnet deployment is still pending with only testing ongoing in L2 testnets, there are a few changes we can make now to enable setting primary names not just for Ownable/ERC-173 contracts but for all existing and new contracts.

Refer to the L2ReverseRegistrar.sol Implementation

Authorized modifier
Source Link

/// @notice Checks if the caller is authorised
/// @param addr The address to check.
modifier authorised(address addr) {
    if (addr != msg.sender && !_ownsContract(addr, msg.sender)) {
        revert Unauthorised();
    }
    _;
}

We want to highlight the _ownsContract() function
Source Link

/// @notice Checks if the provided contractAddr is a contract and is owned by the provided addr.
/// @param contractAddr The address of the contract to check.
/// @param addr The address to check ownership against.
function _ownsContract(
    address contractAddr,
    address addr
) internal view returns (bool) {
    if (contractAddr.code.length == 0) return false;
    try Ownable(contractAddr).owner() returns (address owner) {
        return owner == addr;
    } catch {
        return false;
    }
}

In this we want to add a new check for contract deployer, which would look something like -

function _ownsContract(
    address contractAddr,
    address addr
) internal view returns (bool) {
    if (contractAddr.code.length == 0) return false;

    // Check Ownable ownership first
    try Ownable(contractAddr).owner() returns (address owner) {
        if (owner == addr) {
            return true;
        }
    } catch {
        // Continue to check deployer if Ownable check fails
    }

    // Check if addr is the deployer (mock implementation)
    if (_isAddrContractDeployer(addr, contractAddr)) {
        return true;
    }

    return false;
}

// Dummy/mock function to simulate deployer address verification
function _isAddrContractDeployer(address addr, address contractAddress) internal pure returns (bool) {
    // TODO: Implement actual deployer verification logic
    return false;
}

Summary

To enable every contract - past or future - to set a primary ENS name, we propose expanding the current authorization logic beyond Ownable/ERC-173 checks. By recognizing verified deployers as authorized entities, we unlock support for a broader range of contracts, including those already deployed without ownership functions.

We’re suggesting a change to the L2ReverseRegistrar contract to include deployer checks within _ownsContract, using a pluggable _isAddrContractDeployer method. This prepares the ENS ecosystem for more inclusive and flexible primary name assignments for contracts across L2s, including Namechain.

4 Likes

I believe setNameForAddrWithSignature() covers the safe CREATE case, where the deployment originates from an account that can generate signatures (EoA, SCA).

I don’t think it is safe in general for contracts to be claimed by their deployer.


Wait, no, I agree we need a setNameForDeploymentWithSignature().

1 Like

Technically doing this is straightforward via the last mechanism you mention: you can prove the deployer of a contract by generating its address from the deployer address and nonce (or other parameters, in the case of CREATE2).

However, I don’t believe it’s safe in general to allow the deployer to set the primary name on a contract. Often, dedicated or shared deployment accounts are used, and ownership of the contracts is then transferred elsewhere post-deployment - if there’s any ownership at all. Not infrequently, these deployment accounts get compromised, in which case anyone who obtains the leaked credentials would now also be in a position to set an arbitrary primary name for all the contracts the account deployed.

5 Likes

What about an extendable claim system for reverse registrars so we could add methods post-deployment for claiming?

  • programmatic like Ownable
    • AccessControl
    • ERC1967 Admin
  • ad-hoc
    • Merkle proof of inclusion for a set of DAO-Approved addresses

Basically add Controllable to L2ReverseRegistrar and include controllers in authorised()

2 Likes

Exploring New Ideas to Address the Concerns Raised by the Community

By @nick.eth

However, I don’t believe it’s safe in general to allow the deployer to set the primary name on a contract. Often, dedicated or shared deployment accounts are used, and ownership of the contracts is then transferred elsewhere post-deployment - if there’s any ownership at all. Not infrequently, these deployment accounts get compromised, in which case anyone who obtains the leaked credentials would now also be in a position to set an arbitrary primary name for all the contracts the account deployed.


Proposed Solution

To address the concerns raised by the Nick, we’re introducing a tiered permission system for setting a contract’s primary ENS name:

Level 1 - Contract is Ownable/ERC-173

  • If the contract implements Ownable or ERC-173, only the owner() should be allowed to claim the reverseNode.
  • In this case, do not proceed to check the deployer.

Level 2 - Contract Deployed via ReverseClaimable Mechanism

  • If the reverseNode has already been claimed by another account, and the sender is not that account, then reject the attempt to claim the reverse record by the deployer or set a primary name.
  • Do not check further conditions.

Level 3 - Fallback for Non-Ownable Contracts

  • If the contract is neither Ownable/ERC-173 nor has its reverseNode claimed during deployment,
  • Then allow the sender to set the primary name only if they are the original deployer of the contract.

By @raffy

I don’t think it is safe in general for contracts to be claimed by their deployer.

If we can eliminate the risk where the reverse resolution of a contract address doesn’t match its forward resolution, we effectively remove the primary incentive for a scammer to manipulate the contract’s primary name.

I’ll discuss how to mitigate this risk further at the end of the proposed solution.

Currently, only a small fraction of contracts are eligible to set primary names.
If we can increase that eligibility to 80–90% of contracts (for now eliminating contracts deployed thorugh factory contracts), it would be a significant win for both the ENS and broader Ethereum communities.


By @gregskril

To explore further:

  • AA deployers
  • Burner/compromised key deployments

Burner/Compromised Key Deployments

While this is a broader Ethereum issue, if a private key is compromised, there’s no truly safe recovery path, such contracts shouldn’t be considered safe to interact with in the first place.
However, if we can eliminate the risk where reverse resolution does not match forward resolution, then the motivation for a scammer to set or manipulate a primary name disappears.

Account Abstraction (AA) Deployers

Since these contract deployments occur through fixed EntryPoint contracts, we can reliably filter them.
We can enforce a rule that contracts deployed via EntryPoint are not allowed to have their primary name set via the EntryPoint address, thereby preventing misuse.


Revised Solution After Considering the Concerns

We need to understand the motivation of a hacker or scammer for changing the primary name of a contract, whether by gaining access to the deployer’s private key or exploiting permissions.

Legitimate Scenario (Genuine Contract Deployer)

  • Contract: 0x123abc…
  • ENS Name: genuine.original.eth
  • Reverse Resolution: 0x123abc….addr.reverse → genuine.original.eth
  • Forward Resolution: genuine.original.eth → 0x123abc…

Attack Scenario (Scammer/Hacker)

  • Contract: 0x123abc…
  • ENS Name: fake.scammer.eth
  • Reverse Resolution: 0x123abc….addr.reverse → fake.scammer.eth
  • Forward Resolution: fake.scammer.eth → 0x456def…

This creates a misleading link where reverse and forward resolutions don’t align, enabling scam potential.


If we can eliminate this mismatch, ensuring that reverse and forward resolution always align then we remove the scammer’s motivation to change the primary name in the first place.

Rephrasing in more technical terms - If we disallow setting any name as a contract’s primary name unless it forward-resolves to that contract address, and also prevent any name from changing its forward resolution unless it is set as the reverse resolution of a contract, we can effectively eliminate the scammer’s motivation to manipulate a contract’s primary name.

Take the previous example again:

Genuine Contract Deployer

The legitimate deployer claims the reverse node and sets the primary name for the contract:

  • Contract: 0x123abc…
  • ENS Name: genuine.original.eth
  • Reverse Resolution: 0x123abc….addr.reverse → genuine.original.eth
  • Forward Resolution: genuine.original.eth → 0x123abc…

Scammer/Hacker Scenario

A scammer gains the ability to change the primary name of the contract and sets it to their own ENS name:

  • Contract: 0x123abc…
  • ENS Name: fake.scammer.eth

They then:

  • Set the forward resolution of fake.scammer.eth to 0x123abc…
  • Set the reverse resolution of 0x123abc….addr.reverse to fake.scammer.eth

Resulting in:

  • Reverse Resolution: 0x123abc….addr.reverse → fake.scammer.eth
  • Forward Resolution: fake.scammer.eth → 0x123abc…

Now, under the proposed constraints, the scammer cannot change the forward resolution of fake.scammer.eth to any other address.

This effectively neutralizes the scam vector, as the ENS name becomes locked to the contract, unless a new name first forward-resolves to the contract and then replaces the old primary name through reverse resolution. This removes the incentive to exploit primary name assignments.


Implementation Plan

To achieve this, we need to create a new ContractResolver and make some changes to the ReverseRegistrar.

Changes in ReverseRegistrar

  • Only allow a custom ContractResolver to be used as the resolver address when setting names via claimForAddr and claimWithResolver.
  • Update the authorised modifier to include the deployer of the contract can also claim reverseNode.

ContractResolver — Updates to NameResolver Logic

  • Check that the name forward-resolves to the contract address using the addr() function from AddrResolver.

ContractResolver — Updates to AddrResolver Logic

  • Ensure the name/node is also set as a reverse record by checking the name() function from NameResolver.

The Enscribe team is keen and already working to address issues with smart contract naming. We believe that with the Namechain release and support for L2 primary names, if we can implement changes to allow all contracts to have a primary name, it would be a win for both us and the ENS community.

2 Likes

@nischal.eth This is super cool. A lot of effort has clearly gone into this. :clap:
Appreciate you and @conor taking the lead on this.

Would love to discuss further on Ecosystem this week and see what opinions other have.

One thing that jumps out to me is this bit:

What if the forward resolution goes off chain?

Am I misreading this, or is this not a circular constraint? Can’t set A before B but also can’t set B before A?

1 Like

Since we are proposing custom ContractResolver - this is going to be an onchain resolver with addr() function which returns actual address (or resolve() which returns the result) not the OffchainLookup error as it is in OffchainResolver.

There is a typo my bad, it should read like this -

If we disallow setting any name as a contract’s primary name unless it forward-resolves to that contract address, and also prevent any name from changing its forward resolution once it is already set as the reverse resolution to that contract address, we can effectively eliminate the scammer’s motivation to manipulate a contract’s primary name.

This might initially seem like a circular dependency, but it’s not.

Consider this scenario -

At the beginning, no forward resolution or reverse resolution has been set.

  • Contract: 0x123abc…
  • ENS Name: genuine.original.eth
  1. Forward resolution is set first : genuine.original.eth → 0x123abc…
  2. Reverse resolution is then set, but only if the name already forward resolves to this contract, which it does: 0x123abc….addr.reverse → genuine.original.eth

Now user can’t change forward resolution of genuine.original.eth to any other address as it is being used in reverse resolution.

genuine.original.eth forward and reverse resolve to the same contract address.

If a user wants to update the primary name of the contract:

  • New ENS Name created: new.original.eth
  1. Sets Forward Resolution ( 1. allowed because this name isn’t yet used in any reverse resolution): new.original.eth → 0x123abc…
  2. Sets Reverse Resolution 2. since the name now forward-resolves correctly: 0x123abc….addr.reverse → new.original.eth

Now, the new primary name has been updated to new.original.eth, and once again, both forward and reverse resolutions align correctly.

I hope this clears it up. Thanks for the thoughtful question!

This still makes the assumption that the deployer is the authorized user here - but that will not always be the case.

A contract that was deployed by an account which was subsequently compromised is perfectly safe so long as the account retains no credentials on the contract any longer.

If the forward and reverse resolutions do not match, the name has no valid reverse resolution, so there’s no vulnerability here. This is part of the reverse resolution protocol, and no changes such as the ones you described are needed to enact it.

In the scenario you describe, however, the attacker could simply set the forward resolution to the original contract, thus gaining the privilege of setting its canonical name to whatever they like.

I might be wrong here, but from what I understand after reviewing the ReverseRegistrar and PublicResolver contracts, it seems possible to change the forward resolution of a name even if that name is already set as the reverse resolution for an address.

Also, the ENS documentation includes a warning that supports the concern I raised about potential spoofing risks:
Primary Names | ENS Docs.

Sorry, I didn’t fully understand that. Could you please elaborate or share an example? Let me know if I missed something in the scenarios or examples I mentioned earlier.

Yes, but the reverse resolution process requires verifying a reverse record by performing a forward resolution itself. If the forward resolution does not match, the address is treated the same as if it has no record set in the first place. You linked to the relevant section of the docs yourself.

An attacker cannot* use control of a reverse record to point it at a name that then resolves to something else. But they can use it to name the contract whatever they want, potentially contrary to the wishes of the actual owner (if any).

*Not easily, anyway - a clever resolver implementation could be configured to return different results depending on how and when it is called, potentially giving a correct result at the time reverse resolution is performed, but later resolving to a different address.

2 Likes

Yes, that’s a valid point and one we’ve considered in our approach. While an attacker could technically change the primary name and spend mainnet gas doing so, the key question is: what’s the incentive to do that if the name cannot be used for any exploit or misleading purpose?

I see, however, I believe with careful consideration and enough rigor, we could design a ContractResolver that prevents this kind of behavior, by ensuring that once a name is set as the reverse resolution for a contract, its forward resolution cannot be arbitrarily changed. This constraint would safeguard against misleading resolution scenarios like later resolving to a different address.

1 Like

You could - but now the resolution process would have to check that a name is held by a specific, immutable resolver, and that its ownership is burned. This would both complicate resolution and reduce flexibility for the future. The current solution, where checking the forward correspondence is part of the reverse resolution process, works fine without introducing these additional constraints.

Chiming in on this — I agree that the current solution works, however, it requires smart contract developers to consciously incorporate a mixin/implement Ownable to be sure they can set a primary name.

Surely we want to be able to set ENS primary names for all types of addresses? Ideally it shouldn’t be on smart contract devs to have to consciously add additional code to take full advantage of ENS names.

Without a universal approach that works for all contracts, I worry that we’ll be limited to contracts having forward-resolving names only, becoming the standard.

Whilst far better than hex addresses, not having the possibility of universal reverse resolution for all contracts, seems like a shortcoming, unless reverse resolution is not intended to be ubiquitous for contract addresses long term.

I’m aware that time is not on our side with ENSIP-19 being finalised, but I would love to try and figure out a way forward to provide primary names for all contracts. :pray:

Of course we want to. The question is whether it can be done securely.

1 Like