# [ERC5851] Verifiable CredentialをSBTで管理する仕組みを理解しよう！

> ERC5851は、特定のアドレスがある条件（クレーム）を満たしていることを証明し、それをオンチェーンのメタデータを用いて検証できる仕組みを導入する提案です。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、Soulbound Token（SBT）を用いてVerifiable Credentialを管理し、オンチェーンKYC認証の仕組みを提案している**ERC5851**についてまとめていきます！

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

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

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

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

## 概要

**ERC5851**は、特定のアドレスがある条件（クレーム）を満たしていることを証明し、それをオンチェーンのメタデータを用いて検証できる仕組みを導入する提案です。
「**クレーム**」とは、「**年齢が18歳以上**」など、対象者がある特性を有すると主張する内容であり、発行者によって**SBT（Soulbound Token）**を使って証明されます。

SBTを利用することで、発行者がオフチェーンでの身元確認の結果をブロックチェーン上に紐づけることができ、発行者と検証者の間でスマートコントラクトを通じてその情報を活用できるようになります。

## 動機

**ERC5851**の背景には、以下のようなユースケースにおける信頼性と効率性の向上があります。

* 一人一票の投票によるSybil攻撃の防止
* 資格のあるユーザーだけが参加できるイベントの実現
* 政府の金融規制などに準拠するための本人確認（KYC）の効率化

現在のKYC（Know Your Customer）や本人確認のプロセスはオフチェーンで行われ、プロジェクトごとに個別の実装が求められるため、技術的な負担が大きくなっています。
**ERC5851**では、発行者と検証者が標準的な形式でクレームをやり取りできるスマートコントラクトを定義し、KYCプロセスをオンチェーンで再利用可能かつ相互運用可能にすることを目指しています。

この標準が確立されれば、ユーザーは一度の確認で複数のサービスに信頼情報を共有でき、発行者や検証者側も常に最新の規制に準拠した形で動作することが可能になります。
結果として、ユーザーのプライバシーを保ちつつ、よりスムーズで信頼性のあるWeb3エコシステムの構築に寄与します。

## 仕様

### 用語定義

* **Zero-Knowledge Proof (ZKP)**
    * プライバシーを保ったまま、ある主張が正しいことを証明する暗号技術です。

* **Soulbound Token (SBT)**
    * 譲渡不可能なNFTで、ユーザーのアイデンティティ情報を定義するために使われます。

* **SBT Certificate**
    * クレーム（主張）に対応するID署名を保有していることを示すSBTです。

* **Verifiable Credential (VC)**
    * 発行者が提示する一連の主張（例：パスポート情報やウォレット内の資産）です。
    * 改ざん検知可能であり、検証者に対してホルダーが自身の属性を証明できます。

* **Claim**
    * DIDホルダーが満たすべき条件（例：「年齢18歳以上」）のことです。

* **Holder**
    * Claim（主張）を保有し、証明するエンティティ（DIDレジストリやデジタルIDプロバイダ）です。

* **Claimer**
    * 自身のClaimを主張する当事者です。

* **Issuer**
    * Claimを元にVC（Verifiable Credential）を発行するエンティティです。例：政府、企業、団体など。

* **Verifier**
    * VCを検証して真偽や正当性を判断するエンティティです。

### メタデータ標準

**ERC5851**では、以下3つの構造体を使用してクレーム（Claim）の情報を定義します。

#### Metadata構造体

クレーム項目のメタ情報を格納します。

```javascript
struct Metadata {
    string title;
    string _type;
    string description;
}
```

クレーム項目の名前、型、説明を定義する構造体。
各Claimが何を示すものかを表現するためのメタ情報です。
タイトル、データ型、詳細な説明を保持します。

**パラメータ**

* `title`
    * クレーム項目の名称。
* `_type`
    * データの型（例: `uint`, `string`, `bool` など）。
* `description`
    * クレーム内容の追加説明。

#### Values構造体

クレームに関連する値を格納するための形式で、**EIP3475**に準拠しています。

```javascript
struct Values {
    string stringValue;
    uint uintValue;
    address addressValue;
    bool boolValue;
}
```

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

[\[ERC3475\] 債券をトークン化して発行や償還ができる仕組みを理解しよう！](/articles/6b45573b83b926381ac3)

`Claim` で使用される実際の値を定義する構造体。
`Claim` が期待する値（Expectations）を格納するための形式です。
必要な型に応じて1つの値を使います。

**パラメータ**

* `stringValue`
    * 文字列型の値。
* `uintValue`
    * 整数型の値。
* `addressValue`
    * アドレス型の値。
* `boolValue`
    * 真偽値型の値。

#### Claim構造体

検証条件、メタデータ、期待値（Expectation）を含む構造体です。

```javascript
struct Claim {
    Metadata metadata;
    string logic;
    Values expectation;
}
```

クレームの定義、検証条件、期待値を持つ構造体。
ホルダーが保持すべき条件を記述した情報で、Claim検証に用いられます。
論理条件（例: >=18など）と、対応する期待値を含みます。

**パラメータ**

* `metadata`
    * クレーム項目に関するメタ情報。
* `logic`
    * 検証に使用する論理演算子（例: `>=`, `==`, `⊂`など）。
* `expectation`
    * 満たすべき値。

| 記号   | 意味                                |
| ---- | --------------------------------- |
| `⊄`  | 指定された値の集合または範囲に属していないことを示します。 |
| `⊂`  | 指定された値の集合のいずれかに属していることを示します。  |
| `<`  | 値が指定された値より小さいことを示します。         |
| `==` | 値が指定された値と完全に一致していることを示します。    |

* 例：18歳以上かを証明するクレーム

```json
{
  "title": "age",
  "type": "uint",
  "description": "age of the person based on the birth date on the legal document",
  "logic": ">=",
  "value": "18"
}
```

### インターフェース

検証者（Verifier）と発行者（Issuer）のコントラクトが提供すべき関数を定義します。

##### Verifierインターフェース

* `ifVerified(address claimer, uint256 SBTID)`:
  　特定のアドレスが、指定されたSBT証明（トークンID）におけるClaim条件を満たしているかを返す関数。

##### Issuerインターフェース

* `standardClaim(uint256 SBTID)`
    * あるSBTトークンに紐づく全Claim構造体を取得します。

* `changeStandardClaim(uint256 SBTID, Claim[] memory _claims)`
    * 特定のSBTIDに対してClaim内容を設定または変更します。

* `certify(address claimer, uint256 SBTID)`
    * 指定アドレスに対して、そのSBT証明を付与します（ZK証明を利用可能）。

* `revoke(address certifying, uint256 SBTID)`
    * 指定されたアドレスからSBT証明を取り消します。

#### イベント

##### `StandardChanged`

```javascript
event StandardChanged(uint256 SBTID, Claim[] _claims);
```

SBTに紐づくクレーム内容が変更された時に発行されるイベント。
新しい `Claim` 条件が定義されたとき、またはSBTが新規作成されたときに通知するために使用されます。

**パラメータ**

* `SBTID`
    * クレームが変更されたSBTのトークンID。
* `_claims`
    * 新しく設定された `Claim` 構造体の配列。

##### `Certified`

```javascript
event Certified(address claimer, uint256 SBTID);
```

SBT証明が指定アドレスに発行された時に発行されるイベント。
新たにユーザーに対してSBTが割り当てられたことを通知します。

**パラメータ**

* `claimer`
    * SBT証明を受け取ったアドレス。
* `SBTID`
    * 発行されたSBTのトークンID。

##### `Revoked`

```javascript
event Revoked(address claimer, uint256 SBTID);
```

SBT証明が失効された時に発行されるイベント。
何らかの理由により、以前発行された証明が無効化されたことを通知します。

**パラメータ**

* `claimer`
    * SBT証明が失効されたアドレス。
* `SBTID`
    * 失効されたSBTのトークンID。

#### Verifier

```js
/// @notice getter function to validate if the address `claimer` is the holder of the claim defined by the tokenId `SBTID`
/// @dev it MUST be defining the conditional operator (logic explained below) to allow the application to convert it into code logic 
/// @dev logic given here MUST be the conditiaonl operator, MUST be one of ("⊄", "⊂", "<", "<=", "==", "!=", ">=", ">")
/// @param claimer is the EOA address that wants to validate the SBT issued to it by the issuer. 
/// @param SBTID is the Id of the SBT that user is the claimer.
/// @return true if the assertion is valid, else false
/**
example ifVerified(0xfoo, 1) => true will mean that 0xfoo is the holder of the SBT identity token defined by tokenId of the given collection. 
*/
function ifVerified(address claimer, uint256 SBTID) external view returns (bool);
```

##### `ifVerified`

```javascript
function ifVerified(address claimer, uint256 SBTID) external view returns (bool);
```

指定アドレスがSBTに紐づくクレーム条件を満たしているかを検証する関数。
`claimer`が指定されたSBTを所持しており、定義された論理条件に合致しているかを返します。

**引数**

* `claimer`
    * 検証対象のアドレス。
* `SBTID`
    * SBT証明のID。

**戻り値**

* `bool`
    * 条件を満たしていれば `true`、そうでなければ `false`。

#### Issuer

```js
/// @notice getter function to fetch the on-chain identification logic for the given identity holder.
/// @dev it MUST not be defined for address(0). 
/// @param SBTID is the Id of the SBT that the user is the claimer.
/// @return the struct array of all the descriptions of condition metadata that is defined by the administrator for the given KYC provider.
/**
ex: standardClaim(1) --> {
{ "title":"age",
    "type": "uint",
    "description": "age of the person based on the birth date on the legal document",
    },
   "logic": ">=",
"value":"18"  
}
Defines the condition encoded for the identity index 1, defining the identity condition that holder must be equal or more than 18 years old.
**/

function standardClaim(uint256 SBTID) external view returns (Claim[] memory);

/// @notice function for setting the claim requirement logic (defined by Claims metadata) details for the given identity token defined by SBTID.
/// @dev it should only be called by the admin address.
/// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims.
/// @param `claims` is the struct array of all the descriptions of condition metadata that is defined by the administrator. check metadata section for more information.
/**
example: changeStandardClaim(1, { "title":"age",
        "type": "uint",
        "description": "age of the person based on the birth date on the legal document",
        },
    "logic": ">=",
    "value":"18"  
}); 
will correspond to the functionality that admin needs to adjust the standard claim for the identification SBT with tokenId = 1, based on the conditions described in the Claims array struct details.
**/

function changeStandardClaim(uint256 SBTID, Claim[] memory _claims) external returns (bool);

/// @notice function which uses the ZKProof protocol to validate the identity based on the given 
/// @dev it should only be called by the admin address.
/// @param SBTID is the Id of the SBT-based identity certificate for which admin wants to define the Claims.
/// @param claimer is the address that needs to be proven as the owner of the SBT defined by the tokenID.
/**
example: certify(0xA....., 10) means that admin assigns the DID badge with id 10 to the address defined by the `0xA....` wallet.
*/
function certify(address claimer, uint256 SBTID) external returns (bool);

/// @notice function which uses the ZKProof protocol to validate the identity based on the given 
/// @dev it should only be called by the admin address.
/// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims.
/// @param claimer is the address that needs to be proven as the owner of the SBT defined by the tokenID.
/* eg: revoke(0xfoo,1): means that KYC admin revokes the SBT certificate number 1 for the address '0xfoo'. */
function revoke(address certifying, uint256 SBTID) external returns (bool);
```

##### `standardClaim`

```javascript
function standardClaim(uint256 SBTID) external view returns (Claim[] memory);
```

指定されたSBTIDに対して定義されているクレーム条件を取得する関数。
SBTに紐づくClaimの一覧を返す関数で、どのような条件で認証されるかを確認できます。

**引数**

* `SBTID`
    * 条件を取得したいSBTのID。

**戻り値**

* `Claim[]`
    * 設定されているClaim構造体の配列。

##### `changeStandardClaim`

```javascript
function changeStandardClaim(uint256 SBTID, Claim[] memory _claims) external returns (bool);
```

指定されたSBTに対して、新しいクレーム条件を設定する関数。
管理者によって実行され、SBTIDに紐づくClaim条件を新たに定義または変更します。

**引数**

* `SBTID`
    * 条件を変更したいSBTのID。
* `_claims`
    * 新しく設定するClaim構造体の配列。

**戻り値**

* `bool`
    * 設定に成功したかどうか。

#### `certify`

```javascript
function certify(address claimer, uint256 SBTID) external returns (bool);
```

指定アドレスにSBT証明を付与する関数。
管理者が、ZK証明などを通じて対象アドレスを検証し、その結果としてSBTを発行します。

**引数**

* `claimer`
    * SBTを付与する対象アドレス。
* `SBTID`
    * 発行するSBTのID。

**戻り値**

* `bool`
    * 発行成功時に `true`。

##### `revoke`

```javascript
function revoke(address certifying, uint256 SBTID) external returns (bool);
```

指定アドレスに紐づくSBT証明を取り消す関数。
管理者が、ユーザーのSBT証明を無効化するために使用します。

**引数**

* `certifying`
    * SBT証明を失効させたいアドレス。
* `SBTID`
    * 対象のSBTトークンID。

**戻り値**

* `bool`
    * 失効成功時に `true`。

## 互換性

**ERC5851**は、すでに発行されているSBTのメタデータ構造（IDやクレーム要件の詳細）を保持している限りにおいて互換性があります。

つまり、以前にSBTを発行されたユーザーは、既存のメタデータが変更されなければ、そのまま証明書を利用し続けることができます。

ただし注意点として、たとえばDeFiプロバイダが特定の関数に対してSBT所有者のみが呼び出せるように修飾子（`modifier`）を使っていた場合、管理者がクレーム構造の削除や検証ロジックの変更を行うと、既存ホルダーの証明が無効化される可能性があります。
このため、仕様変更は既存ユーザーへの影響を十分に考慮したうえで行う必要があります。

## 参考実装

**ERC5851**は、以下の2つのインターフェースに分かれた参考実装を提供しています。

* **EIP5851 Verifier**
  クレームを検証する側の実装で、特定の関数に「**SBT証明書の所持者のみ実行可能**」とするための修飾子（`modifier`）を提供します。
  修飾子内部でIssuerコントラクトを呼び出し、アドレスが正当なクレームを保持しているかを検証します。

* **EIP5851 Issuer**
  クレームを発行する側の実装で、KYCコントローラーなどがユーザーに対してSBT形式のアイデンティティ証明書を発行することを可能にします。
  インターフェース全体のフル実装であり、クレームの定義、検証、証明の取り消しなどの機能を含みます。

このようにVerifierとIssuerが分離されていることで、柔軟かつ拡張性の高いID検証システムの構築が可能になります。

## 引用

Yu Liu (@yuliu-debond), Junyi Zhong (@Jooeys), "ERC-5851: On-Chain Verifiable Credentials [DRAFT]," Ethereum Improvement Proposals, no. 5851, October 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5851.

## 最後に

今回は「Soulbound Token（SBT）を用いてVerifiable Credentialを管理し、オンチェーンKYC認証の仕組みを提案している**ERC5851**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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
