This ERC defines an attestation format for ERC-7730 clear signing descriptor files using the Ethereum Attestation Service (EAS). An attestation is a signed claim by a specific party that they have reviewed a descriptor with a specific content hash.
Attestations use a canonical EAS schema (bytes32 descriptorHash) registered on Ethereum mainnet. They may be created onchain (stored in the EAS contract) or offchain (an EIP-712 signed JSON blob distributed alongside descriptors). Verification follows standard EAS rules, plus matching the attested descriptorHash against the descriptor. Revocation uses EAS's native primitives.
Wallets fetch attestations from sources they trust and maintain their own trust model for which attesters to accept.
ERC-7730 defines a JSON format that enables wallets to display human-readable transaction details before a user signs. Wallets fetch descriptor files from registries and use them to format transaction data. A malicious or substituted descriptor can cause a wallet to display misleading signing details.
This ERC addresses three cases:
Existing mechanisms leave gaps:
This ERC defines attestations that can be distributed independently from descriptors, allowing wallets to verify which trusted parties reviewed a descriptor.
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.
The canonical EAS schema for attestations conforming to this ERC is registered on Ethereum mainnet:
| Field | Value |
|---|---|
| Schema | bytes32 descriptorHash |
| Schema UID | 0xe023eef113c1670774801c34b377fdf612dd8a4d2fa92fe382e15bd91fafb5c2 |
| Resolver | 0x0000000000000000000000000000000000000000 |
| Revocable | true |
| EAS Contract | 0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587 |
| Chain | Ethereum mainnet (chainId = 1) |
All attestations conforming to this ERC MUST reference this schema UID. Attestations referencing other schema UIDs are out of scope for this ERC, even if they carry equivalent information.
The schema's resolver address is 0x0000...0000, meaning the schema is permissionless — anyone can attest. Trust filtering happens at the wallet layer (see Multi-Attester Semantics).
The Ethereum Attestation Service (EAS) is not itself specified by an ERC. This section lists every EAS function and data structure this ERC depends on, so that an implementation does not need to consult other documents. The EAS contract at 0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587 on Ethereum mainnet (chainId = 1) is the reference for the items below; its behavior is normative for this ERC.
struct Attestation {
bytes32 uid;
bytes32 schema;
uint64 time;
uint64 expirationTime;
uint64 revocationTime;
bytes32 refUID;
address recipient;
address attester;
bool revocable;
bytes data;
}
struct AttestationRequestData {
address recipient;
uint64 expirationTime;
bool revocable;
bytes32 refUID;
bytes data;
uint256 value;
}
struct AttestationRequest {
bytes32 schema;
AttestationRequestData data;
}
struct RevocationRequestData {
bytes32 uid;
uint256 value;
}
struct RevocationRequest {
bytes32 schema;
RevocationRequestData data;
}
interface IEAS {
function attest(AttestationRequest calldata request) external payable returns (bytes32 uid);
function revoke(RevocationRequest calldata request) external payable;
function revokeOffchain(bytes32 uid) external returns (uint64 timestamp);
function getAttestation(bytes32 uid) external view returns (Attestation memory);
function getRevokeOffchain(address revoker, bytes32 uid) external view returns (uint64 timestamp);
}
The schema UID is derived by the EAS schema registry as keccak256(abi.encodePacked(schemaString, resolver, revocable)). For the canonical schema this is keccak256(abi.encodePacked("bytes32 descriptorHash", address(0), true)), which equals the UID in the table above. A reader can use this to confirm that the UID corresponds to the schema string.
To compute the descriptorHash:
D be the parsed JSON descriptor object.includes references in D per ERC-7730 merge rules, recursively until no includes key remains. Let D' be the resulting fully-resolved descriptor.D' to a byte string using RFC 8785 (JCS — JSON Canonicalization Scheme).descriptorHash is the resulting 32-byte value. The value being attested is always the raw 32 bytes. It is encoded as a 0x-prefixed lowercase hexadecimal string with 66 characters total if string representation is required.Implementations MUST use Keccak-256 as specified in the Ethereum Yellow Paper (i.e., the pre-standardization variant used throughout the Ethereum ecosystem), NOT the NIST SHA3-256 standard.
Resolving includes before hashing ensures descriptorHash covers the descriptor's effective content. Changes to included files change D' and therefore the hash, correctly invalidating prior attestations. If an included file is unavailable at hash-computation time, the hash cannot be computed; verification of attestations that depend on it is inconclusive.
Attestations conforming to this ERC are EAS attestations using the canonical schema. They MAY exist in two forms:
Onchain attestation. Created by calling attest() (or attestByDelegation()) on the EAS contract. Stored as a record in EAS contract state. Identified by a deterministic bytes32 UID.
Offchain attestation. An EIP-712 signed JSON blob following the EAS offchain attestation v2 format, never submitted onchain. Distributed alongside descriptors.
Both forms carry identical information content. The EAS attestation's data field MUST be the 32-byte descriptorHash.
The attester's chain (the canonical EAS chain) is independent of the chain(s) a descriptor targets. Attestations MAY cover descriptors targeting any chain.
The EIP-712 domain for offchain attestations MUST be:
| Field | Value |
|---|---|
name |
"EAS Attestation" |
version |
"0.26" (the string returned by the canonical EAS contract's VERSION() function) |
chainId |
1 |
verifyingContract |
0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587 |
The EIP-712 primary type is Attest, with the following fields in this order:
Attest(uint16 version,bytes32 schema,address recipient,uint64 time,uint64 expirationTime,bool revocable,bytes32 refUID,bytes data,bytes32 salt)
For attestations conforming to this ERC, version MUST be 2, schema MUST be the canonical schema UID, and data MUST be abi.encode(descriptorHash). The attestation uid is the identifier used for revocation of offchain attestations.
To produce an attestation:
descriptorHash as defined in Descriptor Hash Computation.expirationTime (see Lifecycle Guidance).attest() on the EAS contract with schema = <canonical UID>, data = abi.encode(descriptorHash), recipient = 0x0, expirationTime, revocable = true, refUID = 0x0.To verify an attestation against a descriptor:
schema field equals the canonical schema UID. Reject otherwise.data field as bytes32 descriptorHash.descriptorHash from the descriptor being verified per Descriptor Hash Computation. Confirm it equals the decoded value. Reject otherwise.expirationTime. If non-zero and the current time exceeds it, treat the attestation as expired. An expired attestation MUST NOT be treated as a successful verification.domain and message, then perform ECDSA recovery (EOA attesters) or call ERC-1271 isValidSignature (contract attesters) at the current block. Implementations that cache the result of an ERC-1271 check MUST bound how long a cached result is used, because a contract attester can change its own signature-validation state at any time (see Contract Attester State Changes).eas.getRevokeOffchain(attester, uid). A non-zero result means revoked. The attester argument MUST be the address recovered in step 5; only the attester's own revocation invalidates an attestation.eas.getAttestation(uid).revocationTime. A non-zero result means revoked.attester is in the wallet's trusted-attester set.Attesters MAY revoke a previously issued attestation. Revocation is always an onchain transaction to the canonical EAS contract, regardless of whether the original attestation was onchain or offchain:
eas.revokeOffchain(uid) on the canonical EAS contract. EAS records (msg.sender, uid) → block.timestamp.eas.revoke({schema: <canonical UID>, data: {uid, value: 0}}) on the canonical EAS contract. EAS sets revocationTime on the attestation record.For purposes of this ERC, only revocation by the original attester invalidates an attestation (see Verifying an Attestation, step 6).
Attesters SHOULD set expirationTime to a bounded future timestamp to limit exposure if the attester loses operational capability (key loss, organizational change, discontinued service). Setting expirationTime = 0 produces an attestation that never expires and relies entirely on active revocation; wallets receiving such attestations SHOULD weigh this property when deciding trust policy.
Attestations are distributed alongside descriptors. Wallets fetch from sources they trust and maintain their own trust model for which attesters to accept. This ERC does not mandate a filename convention, directory layout, registry topology, or discovery mechanism. For onchain attestations, the canonical EAS contract is the authoritative store.
Multiple attesters MAY independently issue attestations over the same descriptor. Each attestation is verified independently. A failure of one attestation MUST NOT invalidate other attestations or the descriptor itself.
Wallets are responsible for maintaining their own set of trusted attesters and the policy that determines how many attestations (and from which attesters) are required before the descriptor's formatting instructions are presented to the user.
EAS provides a schema registry, deterministic UIDs, native revocation primitives (onchain and offchain), and existing infrastructure for indexing and discovery.
This document dedicates the EAS contract and the canonical schema UID on Ethereum mainnet and does not allow for a deployment-agnostic attestation mechanism.
This is a deliberate decision. An attestation or its revocation is only meaningful if every verifier agrees on where to check. The EAS contract provides that single point of agreement.
The schema is a single field (bytes32 descriptorHash). Attester identity, time, expiration, revocation, and reference relationships (via refUID) are carried by EAS-native fields. Adding those fields to the schema would duplicate EAS metadata and increase attestation size.
The schema is registered with resolver = address(0), meaning any address can attest. Access control at the protocol layer would require a custom resolver contract and ongoing governance. Wallets instead apply their own trusted-attester policy.
This ERC is independent of ERC-7730. Descriptors do not change; attestations are a separate document type.
Example offchain attestation produced by an EOA attester. Values for descriptorHash and signature are illustrative.
{
"sig": {
"version": 2,
"domain": {
"name": "EAS Attestation",
"version": "0.26",
"chainId": "1",
"verifyingContract": "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587"
},
"primaryType": "Attest",
"types": {
"Attest": [
{ "name": "version", "type": "uint16" },
{ "name": "schema", "type": "bytes32" },
{ "name": "recipient", "type": "address" },
{ "name": "time", "type": "uint64" },
{ "name": "expirationTime", "type": "uint64" },
{ "name": "revocable", "type": "bool" },
{ "name": "refUID", "type": "bytes32" },
{ "name": "data", "type": "bytes" },
{ "name": "salt", "type": "bytes32" }
]
},
"message": {
"version": 2,
"schema": "0xe023eef113c1670774801c34b377fdf612dd8a4d2fa92fe382e15bd91fafb5c2",
"recipient": "0x0000000000000000000000000000000000000000",
"time": "1777630591",
"expirationTime": "1785406591",
"revocable": true,
"refUID": "0x0000000000000000000000000000000000000000000000000000000000000000",
"data": "0x0d7fadf23cc3d0bcfd1b5e3aa6b3da2a7f7e8f3f5f8b7eacd8b4f5fcf3e3d8a0",
"salt": "0x6f489a5fc25fdcd2cd647d8d3ce01e2a7353514db4cd2f19829ede0c2347b3dc"
},
"signature": {
"r": "0x9261b566676ac129fbeecbe0bf14af19fe8e5b60f3f644fb9b69bc1db347612e",
"s": "0x2e0633b5bc3da6930eb76cd298d8be7dcd2fdc6372b21f3717a8257fc749d963",
"v": 28
},
"uid": "0x88e12c640bbf4f8286c4d2b4d6c3aac5913870b7a1d3411bed1c56e308be09bd"
},
"signer": "0xBf01daF454dce008d3E2bfD47d5e186F71477253"
}
| Threat | Description | Mitigation |
|---|---|---|
| Descriptor modification | Attacker modifies the descriptor content after it was attested. | Recomputed descriptorHash does not match the value in the attestation; verification fails. |
| Included file substitution | Attacker modifies content referenced via includes, changing the effective descriptor without touching the descriptor file itself. |
descriptorHash is computed over the fully-resolved descriptor; any change to included content changes the hash, invalidating prior attestations. |
| Attestation modification | Attacker rewrites any field of a distributed offchain attestation (for example, to advance expirationTime). |
The EIP-712 signature no longer verifies. |
| Attester impersonation | Attacker forges an attestation claiming a trusted attester's identity. | ECDSA recovery (EOA) or ERC-1271 validation (contract) does not yield the trusted attester. |
| MITM on transport | A network-layer attacker modifies attestation bytes in transit. | The EIP-712 signature no longer verifies. |
| Supply-chain substitution | Attacker compromises a distribution node and serves forged attestations. | Wallets reject attestations from untrusted attesters. |
| Downgrade (no attestation fetched) | Attacker delivers the descriptor without any attestation, hoping the wallet trusts it unattested. | Wallets that maintain a trusted attester list can detect the absence of expected attestations and act accordingly. Trust policy is a wallet concern. |
| Stale attestation | Attester has lost operational capability but old attestations continue to circulate. | expirationTime bounds exposure. Attesters are expected to set a bounded TTL (see Lifecycle Guidance). |
| Replay across descriptors | Attacker copies an attestation signed over one descriptor and presents it alongside a different descriptor. | descriptorHash is encoded in the signed data. A mismatch between the attestation's descriptorHash and the descriptor being verified causes rejection. |
| Hidden revocation | A distribution source omits notice that the attester has revoked. | Wallets query the canonical EAS contract directly for revocation status (see Verifying an Attestation). |
If an attester's private key is stolen, the attacker can issue signatures that verify as that attester. This ERC cannot distinguish those signatures from intentional attestations.
expirationTime bounds exposure. EAS revocation can invalidate known compromised attestations. Wallets or trust registries can also remove the compromised attester from their trusted-attester sets.
Key rotation, compromise announcement, and trust-list propagation are out of scope for this ERC.
Contract attesters are verified via ERC-1271 at the current block. Their endorsement reflects the contract's state at verification time, not at issuance time. A contract attester retains the ability to invalidate its own prior attestations by changing the contract's signature-validation state. Implementations that cache contract-attester verification results must therefore limit how long a cached result is used (see Verifying an Attestation).
A valid attestation proves that a specific account attested to a descriptor's content. It does not prove that the account is trustworthy or that the descriptor is correct. Wallets are responsible for maintaining the set of attesters they trust, the policy that determines how many attestations are required, and any additional checks (freshness, expiration margin, cross-attester agreement) before applying a descriptor's formatting instructions.
This ERC does not protect against:
Attester addresses are publicly visible in attestations and on chain. Issuing an attestation publicly links the attester address to the descriptor hash.
Copyright and related rights waived via CC0.