---
eip: 8176
title: Descriptor Attestations for ERC-7730
description: Ethereum Attestation Service (EAS) schema and verification rules for attesting to ERC-7730 clear signing descriptors
author: Bartosz Rozwarski (@llbartekll)
discussions-to: https://ethereum-magicians.org/t/erc-8176-integrity-verification-for-erc-7730/27911
status: Draft
type: Standards Track
category: ERC
created: 2026-02-26
requires: 712, 1271, 7730
---

## Abstract

This ERC defines an attestation format for [ERC-7730](./eip-7730.md) 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](./eip-712.md) 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](./eip-7730.md) 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:

- **Registry or repository poisoning.** A contributor submits a descriptor that hides or mislabels important fields.
- **Man-in-the-middle substitution.** A network attacker modifies a descriptor in transit.
- **Supply-chain substitution.** A registry mirror, CDN cache, or pinning gateway serves a forged descriptor.

Existing mechanisms leave gaps:

- **ERC-7730 context binding** verifies that a descriptor targets the correct contract address and chain, but it does not indicate whether the display formatting has been reviewed by a trusted party.
- **Git commit signatures** prove authorship within a repository but do not survive extraction. Once a descriptor file is served via an API or downloaded as a standalone file, the git signature is lost.

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](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

### 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](#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.

```solidity
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](./eip-7730.md) 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](https://www.rfc-editor.org/rfc/rfc8785) (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](https://github.com/ethereum/yellowpaper/blob/9c601d6a58c44928d4f2b837c0350cec9d9259ed/paper.pdf) (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](./eip-712.md) 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](#descriptor-hash-computation).
2. Choose a TTL and compute `expirationTime` (see [Lifecycle Guidance](#lifecycle-guidance)).
3. Encode the attestation:
   - **Onchain:** call `attest()` on the EAS contract with `schema = <canonical UID>`, `data = abi.encode(descriptorHash)`, `recipient = 0x0`, `expirationTime`, `revocable = true`, `refUID = 0x0`.
   - **Offchain:** sign an [EIP-712](./eip-712.md) typed data payload following the EAS offchain attestation v2 format, then publish the resulting JSON blob.
4. For contract attesters, the signature MAY be produced by any party authorized by the contract's [ERC-1271](./eip-1271.md) 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](#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:
   - **Offchain:** compute the EIP-712 digest from the attestation's `domain` and `message`, then perform ECDSA recovery (EOA attesters) or call [ERC-1271](./eip-1271.md) `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](#contract-attester-state-changes)).
   - **Onchain:** the attester is recorded in the attestation record. Trust the EAS contract's record as authoritative.
6. 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:
   - **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.
   - **Onchain attestations:** `eas.getAttestation(uid).revocationTime`. A non-zero result means revoked.
7. 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:

- **Offchain attestations:** call `eas.revokeOffchain(uid)` on the canonical EAS contract. EAS records `(msg.sender, uid) → block.timestamp`.
- **Onchain attestations:** call `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](#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](./eip-7730.md). 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.

```json
{
  "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](./eip-1271.md) 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](#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](#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](./eip-1271.md) 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](#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:

- **Malicious attesters.** If an attester signs an incorrect descriptor, the attestation will still verify. Trust decisions are wallet policy.
- **Attester key compromise** — see [Key Compromise](#key-compromise) above.
- **Semantic attacks on descriptor content.** A descriptor may pass signature verification while mislabeling fields or omitting relevant data. Signature verification does not validate semantic correctness.
- **Supply-chain compromise on the attester side.** If the attester's signing pipeline is compromised, it can produce valid signatures over malicious descriptors. Attesters are responsible for their own operational security.

### 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](../LICENSE.md).
