はじめに

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

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

今回は、トークンの残高やTransfer額を暗号化したまま取引できるインターフェースを提案している「ERC7945」についてまとめていきます。

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

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

概要

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

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

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

erc20-vs-erc7945.png

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

動機

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の検証に失敗した場合はリバートします。

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

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

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

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の検証に失敗した場合もリバートします。

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

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

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

transfer-vs-transferfrom.png

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

ERC20のapproveとの違いを説明します。

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

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

text
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量が漏洩し、機密性が損なわれます。

引用

最後に

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

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

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