ERC-8176 - Descriptor Attestations for ERC-7730

Created 2026-02-26
Status Draft
Category ERC
Type Standards Track
Authors
Requires

Abstract

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.

Motivation

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.

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.

Canonical EAS Schema

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).

EAS Interface Used

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.

Descriptor Hash Computation

To compute the descriptorHash:

  1. Let D be the parsed JSON descriptor object.
  2. Resolve any includes references in D per ERC-7730 merge rules, recursively until no includes key remains. Let D' be the resulting fully-resolved descriptor.
  3. Serialize D' to a byte string using RFC 8785 (JCS — JSON Canonicalization Scheme).
  4. Compute the Keccak-256 hash of the resulting byte string.
  5. The 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.

Attestation Format

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.

Creating an Attestation

To produce an attestation:

  1. Compute descriptorHash as defined in Descriptor Hash Computation.
  2. Choose a TTL and compute expirationTime (see Lifecycle Guidance).
  3. Encode the attestation:
  4. Onchain: call attest() on the EAS contract with schema = <canonical UID>, data = abi.encode(descriptorHash), recipient = 0x0, expirationTime, revocable = true, refUID = 0x0.
  5. Offchain: sign an EIP-712 typed data payload following the EAS offchain attestation v2 format, then publish the resulting JSON blob.
  6. For contract attesters, the signature MAY be produced by any party authorized by the contract's ERC-1271 logic; the contract is the attester regardless of which key produced the signature bytes.

Verifying an Attestation

To verify an attestation against a descriptor:

  1. Confirm the attestation's schema field equals the canonical schema UID. Reject otherwise.
  2. Decode the attestation's data field as bytes32 descriptorHash.
  3. Compute descriptorHash from the descriptor being verified per Descriptor Hash Computation. Confirm it equals the decoded value. Reject otherwise.
  4. Check 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.
  5. Recover the attester:
  6. Offchain: compute the EIP-712 digest from the attestation's 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).
  7. Onchain: the attester is recorded in the attestation record. Trust the EAS contract's record as authoritative.
  8. Check revocation against the canonical EAS contract's state. Wallets SHOULD query the canonical EAS contract directly, or infrastructure they trust to mirror it, and SHOULD NOT rely on the source that supplied the attestation to report revocation:
  9. Offchain attestations: 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.
  10. Onchain attestations: eas.getAttestation(uid).revocationTime. A non-zero result means revoked.
  11. Apply wallet trust policy: confirm attester is in the wallet's trusted-attester set.

Revocation

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:

For purposes of this ERC, only revocation by the original attester invalidates an attestation (see Verifying an Attestation, step 6).

Lifecycle Guidance

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.

Distribution

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.

Multi-Attester Semantics

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.

Rationale

Why EAS

EAS provides a schema registry, deterministic UIDs, native revocation primitives (onchain and offchain), and existing infrastructure for indexing and discovery.

Specific EAS Deployment Dependency

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.

Why a Minimal Schema

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.

Why a Permissionless Schema

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.

Backwards Compatibility

This ERC is independent of ERC-7730. Descriptors do not change; attestations are a separate document type.

Reference Implementation

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"
}

Security Considerations

Threat Model

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).

Key Compromise

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 Attester State Changes

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).

Wallet Trust Responsibility

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.

Explicit Non-Protections

This ERC does not protect against:

Privacy

Attester addresses are publicly visible in attestations and on chain. Issuing an attestation publicly links the attester address to the descriptor hash.

Copyright

Copyright and related rights waived via CC0.