Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7831: Multi-Chain Addressing

Textual representation of an address and a chain.

Authors Sam Wilson (@SamWilsn) <sam@binarycake.ca>
Created 2024-08-30
Discussion Link https://ethereum-magicians.org/t/erc-7831-multi-chain-addressing/21942
Requires EIP-55, EIP-137, EIP-155, EIP-165, EIP-2304

Abstract

This proposal introduces a chain-specific address format that allows specifying both an account and the chain on which that account intends to transact. These chain-specific addresses take the form of (example.eth:optimism), 6A10161835a36302BfD39bDA9B44f5734442234e:ethereum:11155111, and so on. The target chain is resolved using a registry stored on ENS.

Motivation

The Ethereum ecosystem is becoming steadily more fragmented. This means a 20 byte address by itself is not enough information to fully specify an account. This can be problematic if funds are sent to an unreachable address on the incorrect chain.

Instead of using chain identifiers, which are not human readable, the address could be extended with a human-readable chain name, which can then be resolved to a chain identifier. The mapping from chain names to identifiers has, since EIP-155, been maintained off chain using a centralized list. This solution has two main shortcomings:

  • It does not scale with the growing number of L2s.
  • The list maintainer is a trusted centralized entity.

This ERC proposes the use of ENS to map chain names to identifiers, while still allowing maximum flexibility by changing the root chain.

Why not ENS with ERC-2304?

While ERC-2304 allows registrants to specify per-chain addresses, it does not provide a default chain to receive assets on (nor should it.) The choice of receiving chain depends too much on off-chain factors to require a transaction to change.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Grammar snippets in this proposal are given in Augmented Backus-Naur form (ABNF) as defined in RFC 5234 and RFC 7405.

Definitions

The following terms are used throughout this proposal:

  • agent - software/tool responsible for resolving a chain-specific address to its exact account and chain.
  • bridge - contract that connects the root chain to the target chain (eg. to transfer tokens, proxy function calls.)
  • root chain - blockchain containing bridge and name resolver contracts.
  • target chain - blockchain where the identified account intends to transact; can be any chain with a bridge on the root chain.

Syntax

At a high level, a chain-specific address is made of three components separated by colons (:) ordered from most general on the right to most specific on the left:

  • a local-part, that identifies the account on the target chain;
  • a chain-part, that identifies the target chain; and
  • optionally, a root-part that identifies the root chain.

These components may be enclosed by parentheses (( and )) to resolve parsing ambiguities.

More formally, valid chain-specific addresses MUST adhere to the following grammar:

address         = OPEN bare-address CLOSE /
                  bare-address

bare-address    = local-part SEP chain-part [SEP root-part]

OPEN            = '('
CLOSE           = ')'
SEP             = ':'

Local Part

The local-part is the most specific section of a chain-specific address. It identifies the account on the target chain. It can be either a hexadecimal string (hex-address) or an ENS-like name (ens-like).

Valid local-part fragments MUST match the following grammar:

local-part      = hex-address /
                  ens-like
Hexadecimal Address

When local-part is a hexadecimal string, it MUST include checksum letter casing (see Checksum), and it MAY omit leading zeros. It MUST NOT include a leading 0x prefix.

Note that local-part may encode an address longer or shorter than 20 bytes (40 hexadecimal digits.) Implementations MUST support local-part lengths of 1 hexadecimal digit up to 40 digits. Implementations SHOULD support arbitrarily sized (within some reasonable limit) local-part components.

Formally, hex-address MUST match the following grammar:

hex-address     = 1*HEXDIG
ENS-Like Names

To disambiguate an ENS name from a hexadecimal string—and unlike standard ENS names—names used in the local-part of a chain-specific address MUST contain at least one dot (.). If present, a dot placed at the rightmost position (eg. eth. or example.eth.) SHALL be removed before resolving the name. Chain-specific addresses SHOULD NOT contain a dot in the rightmost position unless no other dot is present.

The following grammar is illustrative only. See ERC-137 for the definition of an ENS name.

; Rough approximation of ENS names, with the additional requirement that it
; contain at least one "."
ens-like        = 1*NOTSEP DOT *(1*NOTSEP [DOT])

NOTSEP          = %x01-39 / %x3b-ff

Chain Part

The chain-part identifies the target chain. It MUST be a valid ENS name as defined in ERC-137.

The following grammar is illustrative only. See ERC-137 for the definition of an ENS name.

chain-part      = ens-name

; Rough approximation of ENS names, with no additional requirements
ens-name        = 1*NOTSEP

Root Part

The root-part identifies the root chain against which other names are resolved. When present, the root-part SHALL be the EIP-155 chainid of the root chain in decimal format. root-part SHOULD NOT be present when chainid == 1, and MUST be present when chainid != 1.

root-part = 1*DIGIT

Resolution

Resolving a chain-specific address begins on the right, and moves leftward.

Root Chain

If root-part is not present, assume it is 1. Set the root’s chainid to the value of root-part. The agent MUST be able to resolve ENS names against this chain. This likely means it has RPC access and a known ENS Resolver address, but any method of resolving addresses is sufficient.

Note that this makes example.eth:optimism distinct from example.eth:optimism:10. In the former case, both example.eth and optimism are resolved using ENS deployed on mainnet. In the second case, the two names would be resolved against an ENS deployed on the Optimism chain—an unusual situation.

The assignment of chain identifiers is defined in EIP-155.

Target Chain

Next, construct an ENS name for the target chain by concatenating the value of chain-part with .tbd.eth (such that example.eth:foobar would have a target chain of foobar.tbd.eth.) Resolve the target chain’s address (i.e. with ERC-137’s addr) against the ENS deployment on the root chain. The contract at this address is the “bridge contract.”

The agent has to verify that the bridge contract supports the chain-part in the address. First, the agent MUST call ERC-165’s supportsInterface on the bridge contract using ChainMetadata’s interface identifier (see Bridge Interface) and the agent SHALL fail resolution if it returns false. Next, the agent MUST call the bridge contract’s acceptsName function with the same namehash (see ERC-137) used in the above call to addr. The agent SHALL fail resolution if acceptsName returns false.

A bridge contract SHALL provide functionality/metadata enabling the agent to interact with the target chain. Further, it SHALL support the ERC-165 mechanism for interface discovery, and MAY support other methods to accomplish the same. Bridge contracts MUST implement ChainMetadata (see Bridge Interface.) Further specifics of bridging are left for future proposals.

Local Address

Hexadecimal

Verify the checksum (see Checksum.)

The local address is the hexadecimal encoding of the binary representation of the target chain’s native address. For example, for the native Ethereum address 0x6A10161835a36302BfD39bDA9B44f5734442234e, the local address would be 6A10161835a36302BfD39bDA9B44f5734442234e.

ENS-Like

If the local address ends in a dot (.), remove it. Resolve the address using ERC-2304’s addr against the ENS deployment on the root chain with a coinType derived from the chainid of the target chain (retrieved from the bridge contract.)

Bridge Interface

Bridge contracts MUST implement the following interface:

interface ChainMetadata {
    function chainId() external view returns (uint64);
    function coinType() external view returns (uint256);
    function acceptsName(bytes32 keccak) external view returns (bool);
}

When queried using ERC-165’s supportsInterface, bridge contracts MUST return true for 0x00000000.

Checksum

Hexadecimal strings are cased according to a slightly modified ERC-55 algorithm. The algorithm is modified by wrapping nibble_index to fit within the keccak hash.

Python implementation of the modified ERC-55 algorithm ```python from Crypto.Hash import keccak # from pycryptodome def checksum_encode(addr): hex_addr = addr.hex() checksummed_buffer = "" # Treat the hex address as ascii/utf-8 for keccak256 hashing k = keccak.new(digest_bits=256) k.update(hex_addr.encode("utf-8")) hashed_address = k.hexdigest() # Iterate over each character in the hex address for nibble_index, character in enumerate(hex_addr): if character in "0123456789": # We can't upper-case the decimal digits checksummed_buffer += character elif character in "abcdef": # Check if the corresponding hex digit (nibble) in the hash is 8 or higher nibble_index_wrapped = nibble_index % len(hashed_address) hashed_address_nibble = int(hashed_address[nibble_index_wrapped], 16) if hashed_address_nibble > 7: checksummed_buffer += character.upper() else: checksummed_buffer += character else: raise Exception( f"Unrecognized hex character {character!r} at position {nibble_index}" ) return "0x" + checksummed_buffer def test(addr_str: str): padded_addr_str = addr_str.removeprefix("0x") if len(padded_addr_str) % 2 == 1: # Pad to an even number of nibbles. padded_addr_str = "0" + padded_addr_str addr_bytes = bytes.fromhex(padded_addr_str) checksum_encoded = checksum_encode(addr_bytes) if checksum_encoded != addr_str: print(f"{checksum_encoded} != expected {addr_str}") test("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") test("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359") test("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB") test("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb") test("0x004f67dAbb603AAA58eD52641CCafF09C559704A") test("0x4F67dABB603aAa58Ed52641cCAff09C559704A") test( "0x" "5aaeB6053f3e94c9B9a09f33669435E7" "ef1BEaED5AaEB6053f3e94C9B9a09F33" "669435e7ef1bEAed5aaEb6053f3e94C9" "b9a09f33669435E7ef1beAED5aaEB605" "3f3e94c9b9a09F33669435e7ef1beAED" ) ```

For example, these strings are correctly cased:

  • 004f67dAbb603AAA58eD52641CCafF09C559704A
  • 04f67dAbb603AAA58eD52641CCafF09C559704A
  • 4F67dABB603aAa58Ed52641cCAff09C559704A

Rationale

Component Order

The components are ordered from most specific to most general because…

Separator Choice

The colon (:) is a reasonable choice for separator because it is not an allowed character in ENS names, it is familiar (eg. IPv6), and isn’t as overloaded as the @ symbol.

Alternative: @

The @ symbol is a common choice for addresses, and finds use in email and several federated communication protocols. The English reading (foo-AT-example-DOT-com) is natural and implies a hierarchy between the left and the right components.

Unfortunately, because the @ symbol is so widely used, including it in a chain-specific address would make all those protocol identifiers more confusing (or even invalid.) For example, foo@foo.eth@ethereum is not a valid email address.

Alternative: /

Target Chain as Subdomain

While it would be technically possible to resolve chain-part against a root ENS name (eg. ethereum.eth instead of ethereum.tbd.eth), using a subdomain allows the pre-registration of well-known chain names for an initial distribution of names before switching to open registration.

Without such a pre-registration, an attacker could register well-known names before the legitimate project.

After the pre-registration period, open registration is acceptable because new chains can register their names before announcing publicly.

Backwards Compatibility

It is always possible to determine whether a particular string is a chain-specific address, a plain address, or a plain ENS name. Because of this property, there is little opportunity for backwards incompatibility: chain-specific addresses are not valid legacy addresses or ENS names, so tools without support will simply reject them.

Test Cases

ENS Configuration

Mainnet (1)

Name Coin Type Record
ethereum.tbd.eth - a bridge contract to mainnet (1)
rollup1.tbd.eth - a bridge contract to rollup1 (1608)
example.eth 2147483649 0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa
example.eth 2147485256 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB

Sepolia (11155111)

Name Coin Type Record
ethereum.tbd.eth - a bridge contract to sepolia (11155111)
example.eth

Inputs & Expected Outputs

Valid

Input Target Chain Local Address
(example.eth:ethereum) mainnet (1) 0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa
example.eth:rollup1 rollup1 (1608) 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB
example.eth.:ethereum mainnet (1) 0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa
0:ethereum mainnet (1) 0x0000000000000000000000000000000000000000

Invalid

Input Failure Reason
(0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa:ethereum) Invalid hexadecimal
(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:ethereum) Invalid checksum
(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:ethereum) Invalid checksum
(eth:ethereum) Invalid hexadecimal
(:ethereum) Missing local-part

Reference Implementation

Security Considerations

Unicode & Typosquatting Attacks

An attacker could register ENS names that resemble well-known chain names. For example, etherium and ehtereum are reasonably close to ethereum. While many unicode homoglyphs are caught by ENS libraries, agents should still be aware of the risk they pose.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Sam Wilson (@SamWilsn) <sam@binarycake.ca>, "ERC-7831: Multi-Chain Addressing [DRAFT]," Ethereum Improvement Proposals, no. 7831, August 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7831.