Pre-context.
Unruggable have been working with the team at Wonderland, Josh Rudolf from the Ethereum Foundation, and the wider Interop working group on fleshing out the specifications and implementations for interoperable addressing standards.
Over the past year @Premm.eth, @raffy, and myself have had numerous conversations about standardising a method for the resolution of arbitrary binary data. Previously it was a nice-to-have, but the requirements of ERC-7828: Interoperable Names using ENS (more to follow) have made it a necessity.
This specification is simple yet incredibly flexible. It allows for the resolution of anything using an ENS name.
I’ve posted the body of the specification below, and have opened a Pull Request into the main ENSIP repository here.
We would be appreciative of any feedback from the ENS community, and are, of course, happy to answer any questions.
ENSIP-24: Arbitrary Data Resolution
Abstract
This ENSIP proposes a new resolver profile for resolving arbitrary bytes data.
Motivation
ENS has seen significant adoption across the blockchain ecosystem, but the current resolver profiles are too restrictive for many emerging applications.
There is a clear need for a richer record type that can store unstructured binary data. This would provide a flexible, gas-efficient mechanism for associating any data with an ENS name. This would enable direct support for resolving:
- Decentralized identifiers (DIDs)
- Hashed data commitments for off-chain data verification
- Interoperable addresses
- Context data
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.
Overview
This ENSIP introduces a new interface for the resolution of arbitrary bytes data:
interface IDataResolver {
function data(
bytes32 node,
string calldata key
) external view returns (bytes memory);
}
The ERC-165 identifier for this interface is 0xecbfada3.
The key argument is a string type for simplicity and clarity.
Resolvers implementing this ENSIP MUST emit the following event when data is changed:
event DataChanged(
bytes32 node,
string key,
bytes32 indexed keyHash,
bytes32 indexed dataHash
);
The keyHash and dataHash values are the keccak256 hashes of the key and the data being set.
Resolvers MAY implement the following interface
interface ISupportedDataKeys {
function supportedDataKeys(bytes32 node) external view returns (string[] memory);
}
The ERC-165 identifier for this interface is 0x29fb1892.
If implemented this function MUST return an array of string keys which the resolver MAY store data for.
Rationale
Retrofitting the addr(bytes32 node, uint coinType) function defined in ENSIP-9 as a getter for resolving arbitrary bytes was considered, but whilst cool, its usage is hacky and not semantically clear to developers.
We considered simply storing bytes data as a string within text records but this is inefficient, expensive, and impractical.
The DataChanged event emits both the key and the keyHash, the keccak256 hash of the key. For flexibility, the keyHash is explicitly calculated rather than using indexed strings.
The DataChanged event emits a keccak256 hash of the stored data to allow data integrity checks whilst avoiding chain bloat.
The optional ISupportedDataKeys interface allows for data key discovery. The intended usage of this interface is to enable data discovery for small, known, and bounded sets of keys without the need for external dependencies.
Example
Below is an illustrative snippet that shows how to set and retrieve arbitrary data:
pragma solidity ^0.8.25;
interface IDataResolver {
event DataChanged(
bytes32 node,
string key,
bytes32 indexed keyHash,
bytes32 indexed dataHash
);
function data(
bytes32 node,
string calldata key
) external view returns (bytes memory);
}
contract Resolver is IDataResolver {
mapping(bytes32 node => mapping(string key => bytes data)) private dataStore;
function data(bytes32 node, string calldata key) external view returns (bytes memory) {
return dataStore[node][key];
}
// setData function can be used to set the data (not shown)
}
Set and retrieve arbitrary data:
// Pseudo javascript example
// Store arbitrary data
const tx = await resolver.setData(node, "agent-context", "0x0001ABCD...");
await tx.wait();
// Retrieve arbitrary data
const result = await resolver.data(node, "agent-context");
Backwards Compatibility
This proposal introduces a new resolver profile and does not affect existing ENS functionality. It introduces no breaking changes.
Security Considerations
None.
Copyright
Copyright and related rights waived via CC0.