# [ERC7945] トークンの残高やTransfer額を暗号化する機密トランザクション対応トークンの仕組みを理解しよう！

> ERC7945は、機密トランザクション（Confidential Transactions）に対応したファンジブルトークンの標準インターフェースです。

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

## はじめに

初めまして。
『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額を暗号化したまま取引できるインターフェースを提案している「**ERC7945**」についてまとめていきます。

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

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

**ERC20**の機能を踏襲しつつ、残高やTransfer額を暗号文（`bytes`）として扱うことで、オンチェーンのプライバシーを実現する標準インターフェースです。

## 概要

**ERC7945**は、機密トランザクション（Confidential Transactions）に対応したファンジブルトークンの標準インターフェースです。

**ERC20**ではトークンの残高やTransfer額がオンチェーンで平文のまま公開されますが、**ERC7945**ではこれらの値を暗号化された`bytes`型で扱います。
Transfer時にはゼロ知識証明（ZKP）などの証明（`_proof`）を添付し、暗号化された値が正しいことをオンチェーンで検証します。
これにより、残高やTransfer額を第三者に知られることなくトークンの取引ができます。

**ERC20**と同様のTransfer・approve・allowanceの仕組みを備えており、既存のdAppパターンを大きく変えずにプライバシー機能を追加できます。

![erc20-vs-erc7945.png](/images/qiita-86c432ff-be70-436d-bac8-53a267841cf6.webp)

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

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

## 動機

**ERC20**のようなトークン標準はDeFiをはじめとする多くのブロックチェーンアプリケーションを支えてきました。
しかし、残高やTransfer額がオンチェーンで誰でも閲覧できるため、プライバシーの課題がありました。

MoneroやZcashのようなブロックチェーンでは、プロトコルレベルで機密トランザクションが実装されています。
一方、Ethereumではスマートコントラクトを通じてプライバシーを実現するアプローチ（Zetherなど）が研究されていますが、標準的なインターフェースが定まっていませんでした。

標準がないと、ユーザーやアプリケーションがあるトークンコントラクトが機密トランザクションに対応しているかどうかを簡単に判定できません。
**ERC7945**はこの問題を解決するために、コアとなる関数とイベントだけを規定した標準インターフェースを提案しています。

## 仕様

#### `ITokenMetadata`インターフェース

トークンのメタデータを提供するインターフェースです。
**ERC20**と同様の`name`、`symbol`、`decimals`関数を定義しています。
これらはすべてオプションです。

```javascript
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
```

#### `IConfidentialToken`インターフェース

機密トランザクションの中核となるインターフェースです。

##### `confidentialBalanceOf`

指定したアドレスの暗号化された残高を返す関数です。

```javascript
function confidentialBalanceOf(address owner)
    public view returns (bytes memory confidentialBalance)
```

- **引数**
    - `owner`
        残高を取得するアカウントのアドレス。
- **戻り値**
    - 暗号化された残高（`bytes`型）。

返される値は暗号化されているため、復号鍵を持たない第三者には残高の実際の値がわかりません。

##### `confidentialTransfer`

暗号化されたTransfer額とその証明を使ってトークンをTransferする関数です。

```javascript
function confidentialTransfer(
    address _to,
    bytes memory _confidentialTransferValue,
    bytes memory _proof
) public returns (bool success)
```

- **引数**
    - `_to`
        Transfer先のアドレス。
    - `_confidentialTransferValue`
        暗号化されたTransfer額。
    - `_proof`
        Transferの正当性を証明するデータ（ZKP等）。
- **戻り値**
    - 成功なら`true`、失敗なら`false`。
- 呼び出し元は戻り値の`false`を適切に処理する必要があります。
- `_proof`の検証に失敗した場合はリバートします。

:::message
Zetherのような実装では、`_confidentialTransferValue`と残高はElGamal公開鍵で準同型暗号化されます。
`_proof`では以下の3点が検証されます。

- Transfer額が送信者と受信者の両方の公開鍵で正しく暗号化されていること
- Transfer額の平文が非負であること
- 送信者の残高がTransfer額を支払うのに十分であること

暗号化されたまま残高の加算・減算ができるため、コントラクトは実際の値を知ることなく残高を更新できます。

```mermaid
sequenceDiagram
    participant S as 送信者
    participant C as コントラクト
    participant R as 受信者

    S->>C: confidentialTransfer(to, 暗号化額, 証明)
    C->>C: 証明を検証（ZKP）
    C->>C: 送信者の暗号化残高を減算
    C->>C: 受信者の暗号化残高を加算
    C-->>S: success = true
```
:::

##### `confidentialTransferFrom`

第三者（承認されたアカウント）がトークンをTransferする関数です。
**ERC20**の`transferFrom`に相当します。

```javascript
function confidentialTransferFrom(
    address _from,
    address _to,
    bytes memory _confidentialTransferValue,
    bytes memory _proof
) public returns (bool success)
```

- **引数**
    - `_from`
        Transfer元のアドレス。
    - `_to`
        Transfer先のアドレス。
    - `_confidentialTransferValue`
        暗号化されたTransfer額。
    - `_proof`
        Transferの正当性を証明するデータ。
- **戻り値**
    - 成功なら`true`、失敗なら`false`。
- `_from`が送信者に対して何らかの方法で承認を与えていない場合はリバートします。
- `_proof`の検証に失敗した場合もリバートします。

:::message
`confidentialTransfer`との違いとして、`confidentialTransferFrom`では`_proof`で以下の3点が検証されます。

- Transfer額が`_from`・`_to`・呼び出し元の**3者**の公開鍵で正しく暗号化されていること
- Transfer額の平文が非負であること
- 呼び出し元の**allowance**（残高ではなくapproveされた部分）がTransfer額を支払うのに十分であること

`confidentialTransfer`では送信者と受信者の2者の公開鍵で暗号化され、送信者の残高を確認しますが、`confidentialTransferFrom`では3者の公開鍵が必要で、確認対象がallowance部分になります。
:::

![transfer-vs-transferfrom.png](/images/qiita-bac7298a-74ca-4202-877f-686657b9ab2d.webp)

##### `confidentialApprove`

指定したアカウントに残高の一部からのTransferを許可する関数です。
**ERC20**の`approve`とは大きく異なる動作をします。

```javascript
function confidentialApprove(
    address _spender,
    bytes memory _confidentialValue,
    bytes memory _proof
) public returns (bool success)
```

- **引数**
    - `_spender`
        承認するアカウントのアドレス。
    - `_confidentialValue`
        allowance部分に割り当てる暗号化された値。
    - `_proof`
        分割の正当性を証明するデータ。
- **戻り値**
    - 成功なら`true`、失敗なら`false`。
- `_proof`の検証に失敗した場合はリバートします。
- `_proof`では、`_confidentialValue`が呼び出し元と`_spender`の公開鍵で暗号化されていること、値が非負であること、呼び出し元の残高が十分であることが検証されます。
- この関数を再度呼び出すと、既存のallowance部分を残高に統合してから`_confidentialValue`で新しいallowance部分が上書き設定されます。

![confidential-approve.png](/images/qiita-c02032db-3d0e-4cca-b84f-49977922da9a.webp)

:::message
**ERC20**の`approve`との違いを説明します。

**ERC20**の`approve`では、指定した額までの引き出しを許可するだけです。
しかし**ERC7945**の`confidentialApprove`では、呼び出し元の暗号化残高を「**allowance部分**」と「**残りの部分**」に分割します。

例えば、暗号化された残高が100トークン分に相当する場合に30トークン分をapproveすると、残高が「allowance部分（30）」と「残り部分（70）」に分かれます。

```
approve前の残高 = approve後のallowance部分 + approve後の残り部分
```

`_spender`は`confidentialTransferFrom`を使ってallowance部分からTransferでき、使うたびにallowance部分が減っていきます。
残り部分は呼び出し元の新しい残高として使われます。

再度`confidentialApprove`を呼び出すと、まず既存のallowance部分が残高に統合され、その後`_confidentialValue`で新しいallowance部分が上書き設定されます。
:::

##### `confidentialAllowance`

`_spender`が`_owner`からTransfer可能なallowance部分を返すview関数です。

```javascript
function confidentialAllowance(address _owner, address _spender)
    public view returns (bytes memory _confidentialValue)
```

- **引数**
    - `_owner`
        トークン所有者のアドレス。
    - `_spender`
        承認されたアカウントのアドレス。
- **戻り値**
    - 暗号化されたallowance値。

#### イベント

##### `ConfidentialTransfer`

トークンがTransferされた時に発火するイベントです。

```javascript
event ConfidentialTransfer(
    address indexed _spender,
    address indexed _from,
    address indexed _to,
    bytes _confidentialTransferValue
)
```

`confidentialTransferFrom`経由のTransferでは`_spender`に呼び出し元のアドレスを設定します。
それ以外のTransferでは`_spender`は`0x0`に設定することが推奨されます。
トークンのMint時は`_from`を`0x0`に、Burn時は`_to`を`0x0`に設定した`ConfidentialTransfer`イベントを発火させることが推奨されます。

##### `ConfidentialApproval`

`confidentialApprove`が成功するたびに必ず発火するイベントです。

```javascript
event ConfidentialApproval(
    address indexed _owner,
    address indexed _spender,
    bytes _currentAllowancePart,
    bytes _allowancePart
)
```

## 補足

#### `confidentialTotalSupply`がオプションな理由

**ERC20**には`totalSupply()`がありますが、**ERC7945**では`confidentialTotalSupply()`はオプションです。

トークンのMintやBurnが行われた時に、すべてのユーザーが`totalSupply()`を復号できると、前後の値を比較することでMintやBurnされた実際の量がわかってしまいます。
これは機密性を損なうため、`confidentialTotalSupply()`を採用する場合は復号できる関係者を監査人などの限られたグループに絞る設計が望ましいです。

#### 監査対応

金融機関や規制当局による監査が必要な場合でも、`confidentialTransfer`の関数シグネチャを変更せずに対応できます。

例えばZetherのような実装では、Transfer額を呼び出し元・受信者だけでなく監査人グループの公開鍵でも冗長に暗号化することで、関係者がTransfer額を知ることが可能になります。
`confidentialTotalSupply()`も同様の方法で監査人に復号を許可できます。

#### Fat Token

**ERC7945**は**ERC20**と同時に実装することもできます。
このような「**Fat Token**」では、アカウントが平文の**ERC20**残高と暗号化された機密残高の2種類を保持します。
**ERC20**の平文残高を機密残高に変換したり、その逆に機密残高を平文残高に戻したりする機能をオプションで提供できます。
**ERC20**インターフェースとの併用により、既存のDeFiプロトコルとの互換性を保ちながら機密性を実現できます。

#### 後方互換性

**ERC7945**は後方互換性の問題はありません。
新しいインターフェースとして追加されるため、既存のコントラクトに影響を与えません。

## セキュリティ

完全な機密性を実現するためには、トークンのMintやBurnで平文の値パラメータを使用してはなりません。
`confidentialTransfer`と同じ暗号化戦略に従ってMintやBurnを実装することが推奨されます。
平文のパラメータでMintやBurnを行うと、第三者にMint量やBurn量が漏洩し、機密性が損なわれます。

## 引用

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

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

https://eprint.iacr.org/2019/191.pdf

## 最後に

今回は「**ERC7945（Confidential Transactions Supported Token）**」についてまとめてきました。

**ERC20**の使い慣れたTransfer・approve・allowanceの構造を維持しながら、残高やTransfer額を暗号化する標準インターフェースです。
ZKPや準同型暗号を活用することで、コントラクトが実際の値を知らなくても残高の更新や証明の検証が可能になります。
監査対応やFat Token（**ERC20**との両立）など、実用的なユースケースへの拡張も考慮されています。

他でも色々記事を書いているのでぜひよろしければ読んでいってください！

https://amzn.asia/d/gxvJ0Pw

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

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://twitter.com/cardene777

https://cardene.substack.com/
