# [ERC7984] 送金額と残高を秘匿化できるConfidential Fungible Tokenの仕組みを理解しよう！

> ERC7984は、トークンの残高や送金額を秘匿化しながらトークンのTransferを行うための標準インターフェースです。通常のERC20トークンでは、誰が誰にいくら送ったかがブロックチェーン上に公開されます。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください！

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://twitter.com/cardene777

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

今回は、送金額と残高を秘匿化した状態でトークンをTransferできるポインタベースのConfidential Fungible Tokenの仕組みを提案している**ERC7984**についてまとめていきます！

:::message
以下にまとめられているものを解説しながらまとめていきます。
:::

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

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

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

## 概要

**ERC7984**は、トークンの残高や送金額を秘匿化しながらトークンのTransferを行うための標準インターフェースです。
通常の**ERC20**トークンでは、誰が誰にいくら送ったかがブロックチェーン上に公開されます。
**ERC7984**ではこの金額情報を「**ポインタ**」と呼ばれる`bytes32`型の値に置き換えることで、金額の詳細を外部から見えなくしています。

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

[\[ERC20\] ERC20を1からわかりやすく解説](/articles/aeefe2d89dd5332c9f43)

以下の図は、従来の**ERC20**と**ERC7984**の金額表現の違いを示しています。

![pointer-comparison.png](/images/qiita-ace00a8d-59b0-45c7-80e9-ed719260da8a.webp)

**ERC20**では金額がそのまま公開されますが、**ERC7984**ではポインタを経由して暗号化データを参照するため、金額が外部から見えません。

ポインタは、オンチェーンまたはオフチェーンに保存された暗号化データへの参照です。
ポインタの解決方法（どうやって実際の金額に変換するか）や演算方法は実装に依存するため、この提案自体はFHE（完全準同型暗号）やゼロ知識証明、セキュアエンクレーブ（信頼された実行環境）など特定の技術に縛られない設計になっています。

## 動機

#### なぜトークンの秘匿化が必要なのか

Ethereumをはじめとするパブリックブロックチェーンでは、すべてのトランザクションが公開されています。
これは透明性という観点では優れていますが、多くのビジネスシーンでは問題になります。

例えば、企業が従業員への給与をオンチェーンで支払う場合、全社員の給与額が誰でも閲覧できてしまいます。
DeFi（分散型金融）のプロトコルでは、大口取引の金額が公開されることでフロントランニング（先回り取引）の標的になるリスクがあります。
機関投資家の決済では、取引戦略が推測される可能性もあります。

#### この提案が解決すること

**ERC7984**は、ポインタベースの金額表現を標準化することで、これらの問題を解決します。
金額を`bytes32`のポインタとして扱い、実際の値は暗号化された状態で別の場所に保存されます。
標準インターフェースを定めることで、プライバシー重視のウォレットやDEX（分散型取引所）など、さまざまなアプリケーションが統一された方法で秘匿トークンを扱えるようになります。

## 仕様

#### ポインタの仕組み

**ERC7984**のすべての金額は、`bytes32`型のポインタで表現されます。
通常の**ERC20**では`uint256`型の数値がそのまま残高や送金額として使われますが、**ERC7984**ではその数値の代わりにポインタを使います。

ポインタは、暗号化されたデータへの「**参照先**」です。
例えば、FHEベースの実装ではポインタが暗号文へのハンドルを指し、ゼロ知識証明ベースの実装ではコミットメント値を指す、といった使い方になります。
ポインタの解決方法や演算ロジックは実装固有のため、この仕様では定義していません。

この設計により、**ERC7984**準拠のトークンは特定の暗号技術に依存せず、将来新しい技術が登場しても対応できます。

#### インターフェース定義

**ERC7984**準拠のトークンは**ERC165**を実装する必要があります。
`supportsInterface`関数はインターフェースID `0x4958f2a4` に対して`true`を返します。

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

[\[ERC165\] コントラクトが特定のインターフェースをサポートしているか確認する仕組みを理解しよう！](/articles/541184977a5cba51b759)

以下が**ERC7984**の完全なインターフェースです。

```javascript
interface IERC7984 {
    event ConfidentialTransfer(address indexed from, address indexed to, bytes32 indexed amount);
    event OperatorSet(address indexed holder, address indexed operator, uint48 until);
    event AmountDisclosed(bytes32 indexed handle, uint256 amount);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function contractURI() external view returns (string memory);

    function confidentialTotalSupply() external view returns (bytes32);
    function confidentialBalanceOf(address account) external view returns (bytes32);

    function isOperator(address holder, address spender) external view returns (bool);
    function setOperator(address operator, uint48 until) external;

    function confidentialTransfer(address to, bytes32 amount) external returns (bytes32);
    function confidentialTransfer(address to, bytes32 amount, bytes calldata data) external returns (bytes32);

    function confidentialTransferFrom(address from, address to, bytes32 amount) external returns (bytes32);
    function confidentialTransferFrom(address from, address to, bytes32 amount, bytes calldata data) external returns (bytes32);

    function confidentialTransferAndCall(address to, bytes32 amount, bytes calldata callData) external returns (bytes32);
    function confidentialTransferAndCall(address to, bytes32 amount, bytes calldata data, bytes calldata callData) external returns (bytes32);

    function confidentialTransferFromAndCall(address from, address to, bytes32 amount, bytes calldata callData) external returns (bytes32);
    function confidentialTransferFromAndCall(address from, address to, bytes32 amount, bytes calldata data, bytes calldata callData) external returns (bytes32);
}
```

このインターフェースは大きく4つのカテゴリに分けられます。
メタデータ関数（`name`、`symbol`、`decimals`、`contractURI`）、残高照会関数（`confidentialTotalSupply`、`confidentialBalanceOf`）、オペレーター管理関数（`setOperator`、`isOperator`）、そしてTransfer関数群です。
以下では各カテゴリを詳しく見ていきます。

### メタデータ関数

#### `name`

```javascript
function name() external view returns (string memory);
```

トークンの名前を返す関数です。
例えば `"MyConfidentialToken"` のような文字列を返します。
**ERC20**の`name`関数と同じ役割です。

#### `symbol`

```javascript
function symbol() external view returns (string memory);
```

トークンのシンボル（略称）を返す関数です。
例えば `"MCT"` のような文字列を返します。

#### `decimals`

```javascript
function decimals() external view returns (uint8);
```

トークンの小数点以下の桁数を平文の`uint8`として返す関数です。
例えば `6` を返す場合、1トークンは `1000000` の最小単位で表現されます。
この値はポインタではなく平文で返される点が重要です。
小数点桁数はトークンの単位変換に必要な共通情報であり、秘匿化する必要がないためです。

#### `contractURI`

```javascript
function contractURI() external view returns (string memory);
```

トークンのメタデータURIを返す関数です。
**ERC7572**で定義されたスキーマに従うことが推奨されています。

:::message
**ERC7572**については以下を参照してください。
:::

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

### 残高照会関数

#### `confidentialTotalSupply`

```javascript
function confidentialTotalSupply() external view returns (bytes32);
```

トークンの総供給量をポインタとして返す関数です。
通常の**ERC20**では`totalSupply`が`uint256`の数値を返しますが、**ERC7984**ではポインタを返すため、総供給量も秘匿化されます。

#### `confidentialBalanceOf`

```javascript
function confidentialBalanceOf(address account) external view returns (bytes32);
```

指定したアカウントの残高をポインタとして返す関数です。
返されるのはポインタであるため、この関数を呼んだだけでは実際の残高がいくらなのかはわかりません。
残高を知るには、ポインタを解決するための適切な権限や鍵が必要です。

### オペレーター管理関数

**ERC7984**では、**ERC20**の`approve`/`allowance`（承認/許可量）モデルの代わりに、オペレーターモデルを採用しています。
オペレーターとは、トークン保有者に代わってトークンをTransferする権限を持つアドレスのことです。

#### `setOperator`

```javascript
function setOperator(address operator, uint48 until) external;
```

指定したアドレスをオペレーターとして承認する関数です。
`until`パラメータは、他のパラメータと異なり秘匿化されない平文の`uint48`でオペレーター権限の有効期限（UNIXタイムスタンプ）を指定します。

| パラメータ名 | 型 | 説明 |
|------------|-----|------|
| `operator` | `address` | オペレーターとして承認するアドレス。DeFiプロトコルのコントラクトアドレスなどを指定する。 |
| `until` | `uint48` | オペレーター権限の有効期限（UNIXタイムスタンプ）。この時刻を過ぎると自動的に権限が失効する。 |

この関数が呼ばれると`OperatorSet`イベントが発行されます。
1つのアカウントに対して複数のオペレーターを同時に設定できます。

**ERC20**の`approve`との大きな違いは、**ERC20**では「いくらまで使えるか」という金額ベースの許可ですが、**ERC7984**のオペレーターは「いつまで使えるか」という時間ベースの許可である点です。
金額がポインタで秘匿化されているため、金額ベースの許可量を管理するのが技術的に困難です。
そこで、承認されたオペレーターは期限内であれば任意の金額をTransferできる設計になっています。

#### `isOperator`

```javascript
function isOperator(address holder, address spender) external view returns (bool);
```

指定したアドレスが現在有効なオペレーターかどうかを返す関数です。

| パラメータ名 | 型 | 説明 |
|------------|-----|------|
| `holder` | `address` | トークン保有者のアドレス。 |
| `spender` | `address` | オペレーターかどうかを確認するアドレス。 |

- **戻り値**

`bool` `spender`が`holder`のオペレーターとして現在承認されている場合は`true`、そうでない場合は`false`を返します。

### Transfer関数

**ERC7984**のTransfer関数は、基本的なTransfer、第三者によるTransfer（`From`付き）、コールバック付きTransfer（`AndCall`付き）の3種類があり、それぞれに`data`パラメータの有無で2つのオーバーロードがあります。

#### `confidentialTransfer`

```javascript
function confidentialTransfer(address to, bytes32 amount) external returns (bytes32);
function confidentialTransfer(address to, bytes32 amount, bytes calldata data) external returns (bytes32);
```

呼び出し元のアカウントから指定したアドレスにトークンをTransferする関数です。
`amount`はポインタで指定するため、トランザクションを外部から見ても実際の送金額はわかりません。

| パラメータ名 | 型 | 説明 |
|------------|-----|------|
| `to` | `address` | 送金先アドレス。 |
| `amount` | `bytes32` | 送金額を表すポインタ。実際の金額は暗号化された状態で参照される。 |
| `data` | `bytes` | 暗号学的な証明やアクセス許可など、実装固有の追加情報。FHEの場合は暗号化パラメータ、ゼロ知識証明の場合は証明データなどを含める。 |

- **戻り値**

`bytes32` 実際にTransferされた金額を表すポインタ。

呼び出し元の残高が不足している場合、関数はリバートする可能性があります。
Transferが成功すると`ConfidentialTransfer`イベントが発行されます。

#### `confidentialTransferFrom`

```javascript
function confidentialTransferFrom(address from, address to, bytes32 amount) external returns (bytes32);
function confidentialTransferFrom(address from, address to, bytes32 amount, bytes calldata data) external returns (bytes32);
```

オペレーターが保有者に代わってトークンをTransferする関数です。
DeFiプロトコルなど第三者のコントラクトがユーザーのトークンを操作する時に使います。

| パラメータ名 | 型 | 説明 |
|------------|-----|------|
| `from` | `address` | 送金元アドレス（トークン保有者）。 |
| `to` | `address` | 送金先アドレス。 |
| `amount` | `bytes32` | 送金額を表すポインタ。 |
| `data` | `bytes` | 実装固有の追加情報（暗号学的証明など）。 |

- **戻り値**

`bytes32` 実際にTransferされた金額を表すポインタ。

呼び出し元が`from`のオペレーターでない場合、関数はリバートします。

#### `confidentialTransferAndCall`

```javascript
function confidentialTransferAndCall(address to, bytes32 amount, bytes calldata callData) external returns (bytes32);
function confidentialTransferAndCall(address to, bytes32 amount, bytes calldata data, bytes calldata callData) external returns (bytes32);
```

トークンをTransferした後、受信先のコントラクトにコールバックを実行する関数です。
Transferとコントラクトへの通知を1つのトランザクションで行えるため、DEXへの入金やレンディングプロトコルへの担保提供などのフローを効率化できます。

| パラメータ名 | 型 | 説明 |
|------------|-----|------|
| `to` | `address` | 送金先アドレス（コントラクト）。 |
| `amount` | `bytes32` | 送金額を表すポインタ。 |
| `data` | `bytes` | 実装固有の追加情報（暗号学的証明など）。 |
| `callData` | `bytes` | コールバック時に受信コントラクトに渡すデータ。 |

- **戻り値**

`bytes32` 実際にTransferされた金額を表すポインタ。

コールバックの詳細は後述のコールバック機構セクションで説明します。

#### `confidentialTransferFromAndCall`

```javascript
function confidentialTransferFromAndCall(address from, address to, bytes32 amount, bytes calldata callData) external returns (bytes32);
function confidentialTransferFromAndCall(address from, address to, bytes32 amount, bytes calldata data, bytes calldata callData) external returns (bytes32);
```

オペレーターが保有者に代わってトークンをTransferし、さらに受信コントラクトにコールバックを実行する関数です。
`confidentialTransferFrom`と`confidentialTransferAndCall`を組み合わせた機能です。

| パラメータ名 | 型 | 説明 |
|------------|-----|------|
| `from` | `address` | 送金元アドレス（トークン保有者）。 |
| `to` | `address` | 送金先アドレス（コントラクト）。 |
| `amount` | `bytes32` | 送金額を表すポインタ。 |
| `data` | `bytes` | 実装固有の追加情報（暗号学的証明など）。 |
| `callData` | `bytes` | コールバック時に受信コントラクトに渡すデータ。 |

- **戻り値**

`bytes32` 実際にTransferされた金額を表すポインタ。

呼び出し元が`from`のオペレーターでない場合、関数はリバートします。

### イベント

#### `ConfidentialTransfer`

```javascript
event ConfidentialTransfer(address indexed from, address indexed to, bytes32 indexed amount);
```

秘匿トークンがTransferされた時に発行されるイベントです。
ゼロ値のTransferを含むすべてのTransferで発行されます。

| パラメータ名 | 型 | indexed | 説明 |
|------------|-----|---------|------|
| `from` | `address` | yes | 送金元アドレス。トークン新規発行の場合は`0x0`を設定することが推奨されている。 |
| `to` | `address` | yes | 送金先アドレス。 |
| `amount` | `bytes32` | yes | Transfer額を表すポインタ。ログには記録されるが、ポインタを解決する手段がなければ実際の金額はわからない。 |

`from`が`0x0`の場合はトークンの新規発行を意味します。
`to`が`0x0`の場合は、**ERC20**の慣習に基づきトークンのBurnを意味することが一般的です。

#### `OperatorSet`

```javascript
event OperatorSet(address indexed holder, address indexed operator, uint48 until);
```

`setOperator`関数が正常に呼び出された時に発行されるイベントです。

| パラメータ名 | 型 | indexed | 説明 |
|------------|-----|---------|------|
| `holder` | `address` | yes | トークン保有者のアドレス。 |
| `operator` | `address` | yes | オペレーターとして設定されたアドレス。 |
| `until` | `uint48` | no | オペレーター権限の有効期限（UNIXタイムスタンプ）。 |

#### `AmountDisclosed`

```javascript
event AmountDisclosed(bytes32 indexed handle, uint256 amount);
```

ポインタで表された金額が公開された時に発行されるイベントです。
このイベントの発行は任意であり、実装固有のメカニズムを通じてポインタの値が開示された場合に使われます。

| パラメータ名 | 型 | indexed | 説明 |
|------------|-----|---------|------|
| `handle` | `bytes32` | yes | 開示されたポインタ。 |
| `amount` | `uint256` | no | ポインタが指していた実際の金額。 |

例えば、監査目的で特定のトランザクションの金額を開示する必要がある場合などに、このイベントが使われます。

### コールバック機構

`AndCall`が付いたTransfer関数は、Transfer処理の完了後に受信先のコントラクトへコールバックを実行します。
以下のフローで処理が進みます。

```mermaid
sequenceDiagram
    participant Sender as 送信者
    participant Token as トークンコントラクト
    participant Receiver as 受信コントラクト

    Sender->>Token: confidentialTransferAndCall
    Token->>Token: Transfer処理を実行
    Token->>Receiver: onConfidentialTransferReceived
    alt 成功（true）
        Receiver-->>Token: trueのポインタを返す
        Token-->>Sender: Transfer額のポインタを返す
    else 失敗（false）
        Receiver-->>Token: falseのポインタを返す
        Token->>Token: トークンを送信者に戻す
        Token-->>Sender: 戻り値を返す
    else リバート
        Receiver-->>Token: revert
        Token-->>Sender: revert
    end
```

コールバックの詳細は以下の通りです。

- 受信先がEOA（コントラクトではないアドレス）の場合、コールバックは実行されずそのまま成功します。
- 受信先がコントラクトの場合、`onConfidentialTransferReceived`関数が呼び出されます。
- コールバックがリバートした場合、Transfer全体がリバートします。
- コールバックが`false`のポインタを返した場合、トークンを元の保有者に戻そうとします。

#### `onConfidentialTransferReceived`

```javascript
function onConfidentialTransferReceived(
    address operator,
    address from,
    bytes32 amount,
    bytes calldata data
) external returns (bytes32 success);
```

コールバック付きTransferを受け取るコントラクトが実装する関数です。

| パラメータ名 | 型 | 説明 |
|------------|-----|------|
| `operator` | `address` | Transferを開始したアドレス（オペレーターまたは送信者自身）。 |
| `from` | `address` | トークンの元の保有者のアドレス。 |
| `amount` | `bytes32` | Transferされた金額を表すポインタ。 |
| `data` | `bytes` | Transfer関数の`callData`パラメータで渡されたデータ。 |

- **戻り値**

`bytes32` 成功の場合は`true`のポインタ、失敗の場合は`false`のポインタまたはリバートします。

コールバックが失敗した場合、トークンコントラクトは受信コントラクトから送信者へトークンを返却しようとします。
ただし、受信コントラクトがコールバック処理中にトークンを消費していた場合、返却に失敗する可能性があります。

## 補足

#### 技術非依存の設計

**ERC7984**の最大の特徴は、金額の表現に`bytes32`ポインタを使うことで、特定の暗号技術に依存しない設計を実現している点です。

| 暗号技術 | ポインタの指す先 | 特徴 |
|---------|---------------|------|
| FHE（完全準同型暗号） | 暗号文へのハンドル | 暗号化したまま演算できる。 |
| ゼロ知識証明 | コミットメント値 | 値を公開せずに条件を証明できる。 |
| セキュアエンクレーブ | 保護領域内のデータ参照 | ハードウェアレベルで秘匿化される。 |

どの技術を採用しても、外部から見えるインターフェースは同じ`bytes32`のポインタです。
そのため、ウォレットやDEXなどのアプリケーションは、裏側の暗号技術を意識せずに秘匿トークンを扱えます。

#### ERC20のapprove/allowanceとの比較

**ERC20**では、第三者にトークンのTransferを許可する時に`approve`関数で「いくらまで使ってよいか」を金額で指定します。
しかし**ERC7984**では金額がポインタで秘匿化されているため、金額ベースの許可量を管理することが困難です。

そこで**ERC7984**は、時間ベースのオペレーターモデルを採用しています。
以下の図は、2つのモデルの違いを示しています。

![operator-comparison.png](/images/qiita-57d8e4ed-5ae6-4eda-b2f0-67086fd763af.webp)

| 項目 | **ERC20**（approve/allowance） | **ERC7984**（オペレーター） |
|------|--------------------------|---------------------|
| 許可の単位 | 金額（何トークンまで） | 時間（いつまで） |
| 許可の範囲 | 指定金額まで | 期限内は無制限 |
| 許可の管理 | Transferごとに残量が減る | 期限が来ると自動失効 |
| 複数の許可先 | 各アドレスに個別の許可量 | 各アドレスに個別の期限 |

オペレーターモデルの利点は、暗号化されたポインタの残量を追跡する必要がなくなることです。
外部システムの負荷を軽減しつつ、期限到来による自動失効でセキュリティも確保できます。

#### `data`パラメータの役割

Transfer関数に含まれる`bytes calldata data`パラメータは、実装固有の情報を渡すために使われます。
例えば、FHEベースの実装では暗号化に関するパラメータ、ゼロ知識証明ベースの実装では正当性を示す証明データ、アクセス許可情報などを含めることができます。

このパラメータはインターフェースとしては単なるバイト列ですが、実装ごとに異なる意味を持ちます。
`data`のないオーバーロードも用意されているため、追加情報が不要な実装では省略できます。

## セキュリティ

#### 暗号メカニズムへの依存

**ERC7984**のセキュリティは、基盤となるポインタベースの暗号メカニズムに大きく依存します。
ポインタが指すデータの暗号化強度が不十分であれば、金額が漏洩するリスクがあります。
実装時には、サイドチャネル攻撃（処理時間やメモリアクセスパターンから情報を推測する攻撃手法）への対策と、オフチェーン操作における適切な鍵管理が求められます。

#### コールバックに伴うリスク

`AndCall`系のTransfer関数では、受信コントラクトにコールバックを実行します。
コールバックを悪用したリエントランシー攻撃（コールバック中に再度Transfer関数を呼び出す攻撃）やガスグリーフィング（大量のガスを消費させて処理を妨害する攻撃）のリスクがあります。
コールバックを利用する時は、リエントランシーガードの実装やガスリミットの設定を検討する必要があります。

## 引用

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

https://ethereum-magicians.org/t/erc-7984-confidential-fungible-token-interface/24735

## 最後に

今回は「**ERC7984**によるConfidential Fungible Tokenの仕組み」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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