はじめに

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

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

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

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

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

概要

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

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

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

pointer-comparison.png

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

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

動機

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

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

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

この提案が解決すること

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

仕様

ポインタの仕組み

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

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

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

インターフェース定義

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

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

以下が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で定義されたスキーマに従うことが推奨されています。

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

残高照会関数

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処理の完了後に受信先のコントラクトへコールバックを実行します。 以下のフローで処理が進みます。

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

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

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

dataパラメータの役割

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

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

セキュリティ

暗号メカニズムへの依存

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

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

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

引用

最後に

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

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

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