# [ERC5539] データや証明が有効か無効かを管理する仕組みを理解しよう！

> ERC5539は、「このデータや証明はもう無効です」という情報を、ブロックチェーン上で安全かつ信頼できる形で記録・確認できるようにするインターフェースの提案をしています。

- URL: https://cardene.dev/articles/b2342a60c4c2d820eef5
- 公開日: 2025-03-31
- 更新日: 2025-03-31
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、データや証明が有効か無効かをオンチェーンで管理する仕組みを提案している**ERC5539**についてまとめていきます！

:::message
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-5539

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

**ERC5539**は、「**このデータや証明はもう無効です**」という情報を、ブロックチェーン上で安全かつ信頼できる形で記録・確認できるようにするインターフェースの提案をしています。

## 動機

証明書や資格、権限などが「**もう使えない（取り消された）**」ことを示す仕組みは、中央集権の世界でも分散型の世界でもどちらにも必要です。

しかし、従来のやり方では取り消しリスト（CRL: Certificate Revocation List）をHTTPサーバーで公開するのが一般的です。
これには以下の問題があります。

#### 中央集権的な弱点
今の取り消しリストは、以下のような点で中央集権的です。

- リストは特定のサーバーでホスティングされている。
- そのサーバーのキャッシュやネットワーク構成に依存している。
- 攻撃対象になりやすい（例えばサーバー攻撃やDNS改ざん）。

公開鍵暗号は本来、安全性の高い技術ですが、「取り消し済みかどうか」の確認を中央のサーバーに頼っていると、サーバーが止まったり改ざんされたときに正しく確認できず、安全性が保証できなくなります。

#### 自分で取り消し情報を持ちたくない問題

さらに、多くの証明書の発行者（Issuer）は、自分で取り消しリストを運用するのが面倒なので、CloudflareやAWSといった第三者のインフラに頼ることがあります。

しかし、そうすると結局中央集権的な仕組みに逆戻りしてしまいます。
本来、発行者は「インフラ運用」ではなく、「証明や資格を発行すること」に集中したいはずです。

## 仕様

このEIPでは、**EthereumRevocationRegistry**というコントラクトについて仕様が定義されています。  
このコントラクトの主な目的は、証明の「取り消し状態」を管理するための仕組みを提供することです。

Ethereumアドレスごとに「名前空間（`namespace`）」が与えられ、その中で複数の「取り消しリスト（`revocation list`）」を持つことができます。
さらに、それぞれのリストには「取り消しキー（`revocation key`）」という単位で、実際に取り消す対象が登録されます。

### 用語

| 用語 | 意味 |
|------|------|
| **namespace** | Ethereumアドレスを表す名前空間。リストの管理単位になる。 |
| **revocation list** | 名前空間の中にあるリスト。何が「**取り消されているか**」を記録するための配列。 |
| **revocation key** | 実際に「**取り消されたかどうか**」を判定する対象。各リストの中で管理される。 |
| **owner** | 名前空間とそのリストの管理者。権限の移譲・譲渡もできる。 |
| **delegate** | 一時的に管理権限を任されたアドレス。リスト単位で設定される。 |

- Ethereumアドレスは、自分専用の「名前空間（`namespace`）」を所有できます。
- この名前空間の中には、複数の「**取り消しリスト**」を作ることができます。
- 各取り消しリストは、`bytes32`型の一意なキーで識別されます。
- 取り消しリストの中には、対象ごとに「取り消しキー（`revocation key`）」を登録でき、それが「取り消されているか（`true`） or 有効なままか（`false`）」を記録できます。

#### 権限の管理

##### `owner`

名前空間と、その中の取り消しリストを管理できるのが「`owner`」です。
`owner`は、自分以外のアドレスに管理を一部任せたり、完全に所有権を譲渡することもできます。

##### `delegate`

`delegate`は、一時的にリストの管理権限を任されたアドレスです。
`delegate`に権限を与えるのは、そのリストの`owner`だけです。
必要に応じて、いつでも権限を取り消すことができます。

### 関数

#### 取り消し状態の管理（Revocation Management）

##### `isRevoked`

```js
function isRevoked(address namespace, bytes32 list, bytes32 key) public view returns (bool);
```

特定の「`namespace`」、「`revocation list`」、「`revocation key\」を指定して、そのキーが取り消されているかどうか（`true`/`false`）を確認する関数。

##### `changeStatus`  

```js
function changeStatus(bool revoked, address namespace, bytes32 revocationList, bytes32 revocationKey) public;
```

対象のキーに対して、取り消し状態を変更する関数。
引数 `revoked` を `true` にすれば取り消し、`false` にすれば復元になる。

##### `changeStatusSigned`

```js
function changeStatusSigned(bool revoked, address namespace, bytes32 revocationList, bytes32 revocationKey, address signer, bytes calldata signature) public;
```

署名付きで `changeStatus` を実行する関数。
**Meta Transaction**（ガス代を第三者が肩代わりする処理）に対応しています。

##### `changeStatusDelegated`

```js
function changeStatusDelegated(bool revoked, address namespace, bytes32 revocationList, bytes32 revocationKey) public;
```

委任されたアドレス（`delegate`）がキーの取り消し状態を変更できる関数。
`delegate`がいれば、`owner`でなくても操作できます。

##### `changeStatusDelegatedSigned` 

```js
function changeStatusDelegatedSigned(bool revoked, address namespace, bytes32 revocationList, bytes32 revocationKey, address signer, bytes calldata signature) public;
```

委任されたアドレスが署名つきで状態を変更できる関数。
こちらもMeta Transactionに対応しています。

##### `changeStatusesInList` 

```js
function changeStatusesInList(bool[] memory revoked, address namespace, bytes32 revocationList, bytes32[] memory revocationKeys) public;
```

複数のキーに対して、まとめて取り消し状態を変更できる関数。
大量処理したいときに使います。

以下の署名付き・`delegate`版もあります。

- `changeStatusesInListSigned(...)`
- `changeStatusesInListDelegated(...)`
- `changeStatusesInListDelegatedSigned(...)`

#### リストそのものの取り消し（Revocation List Management）

##### `listIsRevoked`  

```js
function listIsRevoked(address namespace, bytes32 revocationList) view public returns (bool);
```

あるリスト全体が「取り消された」状態かどうかを確認する関数。
リスト自体が取り消されていると、中のキーもすべて取り消し扱いになります。

##### `changeListStatus`  

```js
function changeListStatus(bool revoked, address namespace, bytes32 revocationList) public;
```

リスト全体の状態を取り消し or 復元させる関数。
個別のキーではなく、リスト単位です。

署名付きの `changeListStatusSigned(...)` もあります。

#### リストの所有者管理（Owner Management）

##### `changeListOwner`  

```js
function changeListOwner(address newOwner, address namespace, bytes32 revocationList) public;
```

リストの所有者を別のアドレスに**譲渡**する関数。
譲渡後の管理は新しい所有者が行います。
この関数も `changeListOwnerSigned(...)` によって署名ベースで譲渡可能です。

#### 委任者の管理（Delegation Management）

##### `addListDelegate`  

```js
function addListDelegate(address delegate, address namespace, bytes32 revocationList) public;
```

対象のリストに、委任者（delegate）を追加する関数。
追加されたアドレスはそのリストの取り消し操作が可能になります。

Meta Tx 対応版として、 `addListDelegateSigned(...)`という関数を実装することも可能です。

##### `removeListDelegate`  

```js
function removeListDelegate(address delegate, address owner, bytes32 revocationList) public;
```

リストから委任者を削除する関数。

署名つきでの削除が行える、`removeListDelegateSigned(...)`という関数を実装できます。

### イベント

#### `RevocationStatusChanged`

取り消し状態が変更されたときに発行されるイベント。  
以下の関数でステートの変更が成功したときに発行されます。

- `changeStatus`
- `changeStatusSigned`
- `changeStatusDelegated`
- `changeStatusDelegatedSigned`
- `changeStatusesInList`
- `changeStatusesInListSigned`
- `changeStatusesInListDelegated`
- `changeStatusesInListDelegatedSigned`

```js
event RevocationStatusChanged(
    address indexed namespace,
    bytes32 indexed revocationList,
    bytes32 indexed revocationKey,
    bool revoked
);
```

- `namespace`
    - 対象の所有者アドレス。
- `revocationList`
    - 操作対象のリストID。
- `revocationKey`
    - 取り消し状態を変えたキー。
- `revoked`
    - `true`なら取り消し済み、`false`なら有効に戻されたことを意味する。

#### `RevocationListOwnerChanged`

取り消しリストの所有者が変更されたときに発行されるイベント。
以下の関数が正常に実行されたときに発行されます。

- `changeListOwner`
- `changeListOwnerSigned`

```js
event RevocationListOwnerChanged(
    address indexed namespace,
    bytes32 indexed revocationList,
    address indexed newOwner
);
```

- `namespace`
    - 元の所有者のアドレス。
- `revocationList`
    - 対象のリストID。
- `newOwner`
    - 新しい`owner`のアドレス。

#### `RevocationListDelegateAdded`

取り消しリストに委任者（`delegate`）が追加されたときに発行されるイベント。
以下の関数が実行されたときに発行されます。

- `addListDelegate`
- `addListDelegateSigned`

```js
event RevocationListDelegateAdded(
    address indexed namespace,
    bytes32 indexed revocationList,
    address indexed delegate
);
```

- `namespace`
    - 委任元（`owner`）のアドレス。
- `revocationList`
    - 委任が適用されたリストID。
- `delegate`
    - 委任されたアドレス。

#### `RevocationListDelegateRemoved`

取り消しリストから委任者が削除されたときに発行されるイベント。
以下の関数の実行が成功した時に発行されます。

- `removeListDelegate`
- `removeListDelegateSigned`

```js
event RevocationListDelegateRemoved(
    address indexed namespace,
    bytes32 indexed revocationList,
    address indexed delegate
);
```

- `namespace`
    - 操作を行ったオーナーのアドレス。
- `revocationList`
    - 対象のリストID。
- `delegate`
    - 削除された委任者のアドレス。

#### `RevocationListStatusChanged`

リスト全体が「**取り消された**」または「**復元した**」場合に発行されるイベント。
以下の関数の実行が成功した時に発行されます。

- `changeListStatus`
- `changeListStatusSigned`

```js
event RevocationListStatusChanged(
    address indexed namespace,
    bytes32 indexed revocationlist,
    bool revoked
);
```

- `namespace`
    - 対象のオーナーアドレス。
- `revocationlist`
    - 対象のリストID。
- `revoked`
    - `true`ならリスト全体が取り消された、`false`なら取り消しが解除される。

### Meta Transactions

Meta Transaction（メタトランザクション）は、「**ユーザーが自分でトランザクションをブロックチェーンに送らず、誰かに代理で送ってもらう**」仕組みです。  
**ERC5539**では、メタトランザクションを使って取り消しの操作などを誰かに代わりに実行してもらえるようにする方法も提案されています。

#### トランザクション署名者と送信者

- **transaction signer（署名者）**  

コントラクトに送るためのデータ（例えば、取り消し状態の変更など）に署名を行い、トランザクションそのものは送信しません。

- **transaction sender（送信者）**  

署名データを受け取り、自分のアドレスでコントラクトにその署名付きデータを送り、トランザクションをブロックチェーンに投げます。

これにより、「**ユーザーAが署名だけして、ユーザーBが代わりにトランザクションを送る**」というような柔軟な運用が可能になります。  
例えば、ユーザーにガス代を払わせたくないDappsなどでよく使われる仕組みです。

#### 署名付きデータは一度きり

署名付きのデータ（`payload`）は一度しか使えないようにしないと、リプレイ攻撃（同じ署名を何度も使われてしまう）のリスクがあります。  
そのため、**ERC5539**では以下の2つを推奨しています。

- **EIP712形式での署名（Signed Hash）**
- **nonce（ナンス）の利用**

##### EIP-712による署名（Signed Hash）

**EIP712**とは、「**何に署名しているかを人間にもわかりやすく表示できる署名形式**」です。  
これを使うことで、署名者は「**これは '取り消しキーを `true` にする操作' に署名してる**」などのように処理を確認しながら署名できます。

また、**EIP712**形式は署名の内容に構造があるためリプレイ攻撃にも強くなります。

:::message
**EIP712**については以下の記事を参考にしてください。
:::

[\[EIP712\] 型付き構造化データのハッシュ化と署名の仕組みを理解しよう！](/articles/f54bc85cb66de5a54257)

##### nonce（ナンス）の仕組み

**ERC5539**では、各トランザクション署名者に対して専用の`nonce`（使い捨て番号）を用意することを推奨しています。

処理の流れは以下のようになっています。

1. 署名者がメタトランザクションに署名する。
2. 送信者がそのデータをコントラクトに送る。
3. コントラクトが署名と`nonce`をチェック。
4. 成功したら、その署名者の`nonce`を1つ増やす。

これによって、同じ署名を再利用することができなくなり安全性が保たれます。

## 補足

### なぜ`namespace`（名前空間）を使うのか？

`namespace`は、各Ethereumアドレスごとに自動的に用意される専用スペースです。  
これにより、アドレスがコントラクトを使う前でも、あらかじめ自分の領域が確保されている状態になります。

#### メリット

- **事前の登録不要**  

各アドレスは自動的に自分専用の`namespace`を持つため、コントラクト上で「**領域を確保する**」と宣言する必要がありません。

- **明確な管理権限**  

初期状態では、自分のアドレスに紐づいた`namespace`の中のリソースは自分だけが操作可能です。

つまり、`namespace`は「**このアドレスの取り消し情報はこの場所にある**」とはっきり示せる仕組みであり、ユーザーごとのデータ分離を実現できます。

### なぜ`namespace`の所有者は常に最初のアドレスなのか？

例えば、ある`namespace`内にある「**取り消しリスト**」の所有権がAからBに移ったとします。  
このとき、`namespace`自体をBに変えてしまうと、過去にAの`namespace`を参照していた外部システムやデータとの整合性が壊れる可能性があります。

Aが「**この証明書は取り消した**」とリストに登録した時に、他のサービスが「**Aの`namespace`のこのキーはrevokedだ**」と記録していた場合に、もし`namespace`がBに移ってしまったら、その参照元は正しい情報にアクセスできません。

つまり、`namespace`は過去の情報との一貫性を保つための固定の参照点です。  
リストの「所有者（`owner`）」が変わっても、`namespace`そのものは元のアドレスに固定されていることで、過去のオフチェーンデータや外部システムとの整合性を保てるようになっています。

## セキュリティ

### メタトランザクションに関するリスク

メタトランザクションでは、ユーザーが署名したデータを他人が代わりに送信するという仕組みを使います。  
便利な一方で、署名されたデータが他のチェーンや別バージョンのコントラクトで再利用される（リプレイ攻撃）というリスクがあります。

#### 具体例

- ユーザーがEthereum Mainnet用に署名したデータが、誰かにPolygonなど別チェーンで使われてしまう
- 同じロジックの別コントラクトに使い回される

こうしたチェーンやコントラクト間の意図しない再利用を防ぐために、**ERC5539**では**EIP712**形式の署名を採用しています。

**EIP712**を使うことで、以下のことが可能になります。

- 署名の中に「**どのコントラクト向けの署名か**」「**どのチェーン向けか**」という情報を含められる。
- ユーザーが「**何に署名しているのか**」を明確に確認できる。
- リプレイ攻撃を効果的に防げる。

### 権限管理に関する考慮点

**ERC5539**では、各取り消しリストに対して、操作できるアドレスを制限する仕組みが用意されています。  
勝手に取り消し状態を変えられたり、委任者を追加されたりしないように、ロールごとに明確な制御をしています。

- `owner`だけがそのリストの管理者として、状態変更・委任者の追加・削除などができる。
- 誰がどのリストに対して書き込み権限を持っているかが、コントラクト内でわかる。
- `delegater`（委任者）も、`owner`の許可がなければ設定できず、いつでも取り消せる。

これにより、以下のことが可能になります。

- 不正アクセスによる取り消し情報の改ざんを防げる。
- 透明性があり、追跡可能な状態が保てる。
- 責任の所在が明確になる。

## 引用

Philipp Bolte (@strumswell), Lauritz Leifermann (@lleifermann), Dennis von der Bey (@DennisVonDerBey), "ERC-5539: Revocation List Registry [DRAFT]," Ethereum Improvement Proposals, no. 5539, August 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5539.

## 最後に

今回は「データや証明が有効か無効かをオンチェーンで管理する仕組みを提案している**ERC5539**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
