原文

# pERC20: Private Token Standard (Draft) — JiangXb-son (2026-06-15)

著者: Cyimon (@Cyimon) · ステータス: ドラフト · タイプ: 標準トラック (ERC) · EIP: 8287 (PR) · 作成日: 2026-06-09

説明: EVM向けの、デフォルトでプライベートなファンジブルトークン標準。

議論: ethresear.ch #25089 · Ethereum Magicians #28702 · 実装: PERC20Labs/pERC20_


以前公開されたpERC20プロトコル設計 (Ethereum Magicians #28702 / ethresear.ch #25089) を拡張します。この改訂版の主な追加点は、ZIP-32 サブアカウントを介した**ERC-20承認済み支出** — approveallowance、および transferFrom — です。更新された標準は、ERC-20機能的に完全互換 (capability-complete) ですが、バイト互換性はありません (異なるABI、公開残高なし)。

ERC-20pERC20レイヤー
name / symbol / decimals / totalSupplyはい — 同じ公開ビューオンチェーン
balanceOfはい — ホルダーのみのスキャンオフチェーン
transferはい — プライベートな当事者と金額オンチェーン
approve / allowance / transferFrom新規 — ZIP-32 サブアカウントを介したEOAスペンダー向けの承認済み支出。オンチェーン = transfer (コントラクトスペンダーはサポートされません)オフチェーン
mint / burnはい — 一般的な拡張オンチェーン
Transfer / Approval イベント省略 (プライバシー)

以下は最新のpERC20標準: プライベートトークン標準です。


概要

pERC20 は、EVM向けのデフォルトでプライベートなファンジブルトークン標準です。これはERC-20のプライバシーバージョンです。内部的には、Zcashプロトコル仕様のOrchardシールドプールモデルを使用しています。ERC-20の全メソッドインターフェース (method surface) を維持していますが、いくつかのメソッドは公開オンチェーン読み取りではなくプライベート (ホルダーのみ、オフチェーン) であり、transfer / approve / transferFrom はすべてオンチェーンでは同じ transfer 操作として現れます。以下のpERC20インターフェースを参照してください。

動機

イーサリアムの公開台帳は、すべてのERC-20残高、転送、およびアローワンスを永続的に可視化します。支払い、給与、財務、およびオンチェーン金融がL1に移行するにつれて、ユーザーと発行者はプライベートなファンジブルトークンを必要としています — 公開残高に関するプライベートなメッセージングだけでなく。

プライバシーはプロトコル層でますます対処されています。例えば、EIP-8182はプロトコルに組み込まれたシールドプールを定義しています。ユーザーは公開ETHまたは互換性のあるERC-20トークンをデポジットし、共有プール内でプライベートに価値を移動させ、公開形式に戻して引き出します。このモデルは既存の公開資産をシールドしますが、作成時からプライベートなトークンを発行する方法は定義していません。

pERC20 は後者のギャップを埋めます。これは、ネイティブにプライベートなファンジブルトークンのためのアプリケーション層トークン標準です。ミント、保持、転送、および approve / transferFrom を介した支出が、最初からプライベートノートとして行われ、公開の balanceOf フェーズや共有シールドプールへのデポジットは必要ありません。これはERC-20のプライベートな対応物を指定します — 同じメソッドインターフェース、異なる公開性 — そのため、プロトコルレベルのプライバシー (例: EIP-8182) が並行して進化する間も、発行者は今日からプライベート資産を立ち上げることができます。両者は補完的であり、競合するものではありません。EIP-8182は公開資産をプライベート化し、pERC20 はプライベート資産の発行を定義します。

仕様

キーワード MUSTMUST NOTSHOULDMAY はRFC 2119に従って解釈されます。Solidity構文は0.8.20以上です。

基盤となるプロトコル

価値はアカウント残高ではなく、シールドノートに保持されます。ノート形式、ナリファイア、コミットメントツリー、ノート暗号化、およびアクション/バンドル構造は、Zcashプロトコル仕様Orchardシールドプールに従い、ここではGroth16で検証されるアセットごとのEVMコントラクトとして適応されています。以下に繰り返されないフィールドレベルの形式は、参照実装セクションで規範的です。

pERC20インターフェース

このセクションでは、すべてのpERC20インターフェースを一箇所にリストし、それぞれがERC-20標準インターフェースに対応するかどうかを示します。ERC-20: yes = ERC-20標準; extension = 一般的な拡張 (mint/burn); no = pERC20固有。レイヤー: on-chain = コントラクトABI; off-chain = ウォレット/SDK (コントラクトメソッドなし)。

pERC20インターフェースERC-20レイヤー公開性説明
name() / symbol() / decimals()yesオンチェーンpublicERC-20と同一
totalSupply()yesオンチェーンpublic公開カウンター (mint − burn)
balanceOf(addr)yesオフチェーンprivateビューイングキーでOrchardノートをスキャン; ホルダーのみ
transfer(PrivacyCall)yesオンチェーンprivate (当事者 + 金額)Orchardアクションバンドル
approve(spender, N)yesオフチェーンprivate (関係は隠蔽)ZIP-32 サブアカウント; キーを資金提供 + 配信; オンチェーンではtransfer(PrivacyCall)として提出
allowance(owner, spender)yesオフチェーンprivateサブアカウントの残り残高をスキャン
transferFrom(from, to, amount)yesオフチェーンprivate (関係は隠蔽)スペンダーがサブアカウントから to に支払う; お釣りはサブアカウントに戻る。オンチェーンではtransfer(PrivacyCall)として提出
mint(amount, PrivacyCall)extensionオンチェーン金額は公開; 受取人はプライベート発行者のみ; Orchardアクション + totalSupply増加
burn(amount, PrivacyCall)extensionオンチェーン金額は公開; バーナーはプライベートホルダーが自身のノートをバーン; Orchardアクション + totalSupply減少
issuer()noオンチェーンpublicトークン発行者アドレス
cmxFrozenRoot|cmxFrozenRoot] / setFrozenRoot|setFrozenRoot]noオンチェーン公開ルート; 管理者書き込みコンプライアンス凍結ノートルート
cmxRoot|cmxRoot] / isValidAnchor|isValidAnchor] / isSpent|isSpent] / treeSize|treeSize]noオンチェーンpublicOrchardコミットメントツリーの状態

イベント

pERC20イベントERC-20レイヤー説明
Transfer(from, to, value)yesオフチェーン (省略)発行されません; 当事者と金額はプライベートです
Approval(owner, spender, value)yesオフチェーン (省略)発行されません; owner ↔ spenderをリンクすることになります
NoteAdded / NoteConfirmedTransferを置き換えオンチェーンノートごとの可視性
Mint / Burnextensionオンチェーン公開金額のみ
Perc20Created / FrozenRootUpdated / BundleExecutednoオンチェーンデプロイメント、コンプライアンス、バンドルメタデータ

オンチェーンでの区別不能性 (On-chain indistinguishability)。 transferapprove の資金提供ステップ、transferFrom、および取り消し・回収はすべて同じオンチェーン呼び出しです: transfer([[glossary/PrivacyCall|PrivacyCall]])。オブザーバーはどのERC-20操作が実行されているかを判別できません。

ネイティブにはサポートされない。 ERC-20approve(contractAddress, amount) — コントラクトが自律的に transferFrom を呼び出す — にはネイティブな同等物がありません。支出にはプライベートキーが必要であり、コントラクトはそれを保持できません。理由 (Rationale) を参照してください。

コントラクトインターフェース

pERC20 は1つのオンチェーンABI (IPERC20; 上記のpERC20インターフェースを参照) を公開します。その表でオフチェーンとマークされたメソッドにはコントラクトのエントリポイントがありません。動作は以下のメソッドセマンティクスで指定されます。

interface IPERC20 {
    struct PrivacyCall  { bytes actions; uint256[3] bindingSig; }
    struct BundleAction {
        bytes32    cmx;
        bytes      encCiphertext;
        bytes      outCiphertext;
        bytes32    epk;
        bytes32    nfOld;          // nullifier of the consumed (or dummy) input note
        bytes32    anchor;         // historical root of the consumed (or dummy) input note
        bytes      proof;
        uint256[8] pubFields;
        uint256[3] spendAuthSig;
    }
 
    // ERC-20-aligned public views
    function name()        external view returns (string memory);
    function symbol()      external view returns (string memory);
    function decimals()    external view returns (uint8);
    function totalSupply() external view returns (uint256);
    function issuer()      external view returns (address);
 
    // Value-changing operations (private parties; see Method Semantics)
    function transfer(PrivacyCall calldata call) external returns (bool success);
    function mint(uint256 amount, PrivacyCall calldata call) external;
    function burn(uint256 amount, PrivacyCall calldata call) external;
 
    // Compliance
    function cmxFrozenRoot() external view returns (uint256);
    function setFrozenRoot(uint256 newRoot) external; // onlyAdmin
 
    // Note state machine (Orchard commitment tree)
    function cmxRoot()                 external view returns (bytes32);
    function isValidAnchor(bytes32 root) external view returns (bool);
    function isSpent(bytes32 nf)         external view returns (bool);
    function treeSize()                  external view returns (uint256);
 
    event Mint(address indexed issuer, uint256 amount);
    event Burn(uint256 amount);
    event FrozenRootUpdated(uint256 oldRoot, uint256 newRoot);
    event Perc20Created(
        address indexed pool, address indexed issuer,
        string name, string symbol, uint8 decimals
    );
    event NoteAdded(
        bytes32 indexed cmx, bytes encCiphertext, bytes outCiphertext,
        bytes32 epk, bytes32 nfOld, bytes32 cvNetX
    );
    event NoteConfirmed(bytes32 indexed cmx, bytes32 newRoot, uint256 position);
    event BundleExecuted(uint256 valueBalance, uint256 amount, bytes32 recipientMeta);
}

適合性:

  • 成功した transfertrueMUST返します。
  • コアのバンドル実行パスは公開呼び出し可能でMUST NOTありません (供給不変条件; 以下を参照)。
  • 実装はSolidityを複数のコントラクトに分割してもMAY構いません (例: IPERC20 + 検証者ベース) が、観測可能なABIとイベントは上記の統一されたインターフェースとMUST一致します。
  • cmxRoot() は最新のコミットメントツリーのルートです; isValidAnchor(root)root が過去にアクティブであった場合にのみtrueを返します; isSpent(nf)ナリファイアセットを公開します; treeSize() は挿入されたコミットメントの数です。

呼び出し形式

すべての値を変更する操作は、1つ以上のOrchardアクション (Zcashプロトコル仕様) をエンコードするPrivacyCallを提出します:

  • actions = abi.encode(BundleAction[])
  • bindingSig = 価値保存を証明するシュノアバインディング署名 [Rx, Ry, s]

BundleActionは、EVM検証用に適応されたOrchardアクションです: 1つの出力ノートコミットメント (cmx) と、その (実またはダミーの) 入力に対する証明マテリアル。pubFieldsは以下の順序でMUST並べられます (Orchardアクションの主要入力):

インデックスフィールド役割
[0]anchor消費された入力のマークルルート
[1]cv_net_x正味価値コミットメントX (バインディング署名)
[2]cv_net_y正味価値コミットメントY (バインディング署名)
[3]nf_old消費された入力のナリファイア
[4]rk_xランダム化されたspend-authキーX
[5]rk_yランダム化されたspend-authキーY
[6]cmx出力ノートコミットメント
[7]rt_frozenコンプライアンス凍結ルートバインディング

pubFields[i]< FrMUSTなければなりません。ここで Fr は検証者が使用するSNARK曲線のスカラー体モジュラスです (参照実装ではBN254)。そうでない場合はリバートします (PubFieldOutOfRange)。実装は、8つのフィールドを ActionPubHash (ポセイドンスポンジ) を介して1つのGroth16公開シグナルにハッシュ化し、回路の PubHashAction() と一致させるMUST必要があります。

calldataフィールドへのバインディング。 証明の公開入力はアクションのトップレベルフィールドとMUST一致します。実装は、以下のいずれかが失敗した場合にMUSTリバートします:

チェックリバート
pubFields[0] == anchor and isValidAnchor(anchor)BadAnchor
pubFields[3] == nfOldMUSTリバート (参照実装: NullifierSpent)
pubFields[6] == cmx and cmx != 0InvalidProof / ZeroCommitment
pubFields[7] == cmxFrozenRoot()BadFrozenRoot
spendAuthSig verifies under pubFields[4], pubFields[5] over (nfOld, cmx, epk, encCiphertext, outCiphertext)BadSpendAuthSig

pubFields ↔ calldataの等価性チェックがないと、有効な証明が異なる nfOld または cmx でリプレイされ、ナリファイアセットをバイパスしたり、証明されていないコミットメントを挿入したりする可能性があります。

encCiphertextは580バイト (MUST Orchardノートプレーンテキスト + 受取人キー下のAEADタグ)。outCiphertextは80バイト (SHOULD 送信者自己回復のためのOVK下)。ノート暗号化レイアウトを変更する実装は、このERCの別のバリアントを公開MUSTする必要があります。

バンドルレベルのbindingSigは、状態変更の前に、すべてのアクションナリファイア、コミットメント、および操作のvalueBalanceに対して検証されるMUST必要があります (エンコーディングについてはメソッドセマンティクスを参照)。

ノート暗号化とキー導出は、Zcashプロトコル仕様のOrchardノート形式に従います。正確なエンコーディングは参照実装リポジトリにあります。

メソッドセマンティクス

name / symbol / decimals / totalSupply

ERC-20と同一: 公開オンチェーンビュー。

transfer(PrivacyCall) → bool

入力Orchardノートを消費し、価値保存アクションバンドル (valueBalance == 0) で出力ノートを作成します。送信者、受取人、および金額はプライベートのままMUSTです。成功時に true を返します; Transfer(from,to,value) ではなく、NoteAdded / NoteConfirmed を発行します。

mint(amount, PrivacyCall) / burn(amount, PrivacyCall)

transfer と同じOrchardアクション検証パスで、公開 totalSupply 会計が行われます:

  • mint: totalSupply += amount (onlyIssuer); amount は公開、受取人はプライベート。Minttransfer と同じアンカー / ナリファイア / spend-auth パスをMUST使用します (出力のみのブランチはありません)。回路は消費された入力を v = 0 に制約し、アクションが純流入を表すようにMUSTします。コントラクトはノートの値を直接読み取りません。
  • burn: totalSupply -= amount; 任意のホルダーは自身のノートをバーンMAYできます; amount は公開、バーナーはプライベート。
操作valueBalanceエンコーディング
transfer0
burnbit255 = 0, 下位ビット = amount
mintbit255 = 1, 下位ビット = amount

balanceOf (オフチェーン、プライベート)

ホルダーのみが、NoteAddedイベントをスキャンし、ビューイングキーOrchardノートを試行復号 (trial-decrypt) し、消費されたナリファイアを除外することで、自身の残高を計算できます。オンチェーンの balanceOf はなく、第三者の残高をクエリする方法もありません。

approve / allowance / transferFrom (オフチェーンセマンティクス; オンチェーン = transfer)

承認済み支出 (approve / allowance / transferFrom) はZIP-32階層型アカウントに基づいて構築されています。各EOAスペンダーは、独自のスペンディングキービューイングキーを持つ専用のサブアカウント (account_S) を受け取ります。これは、所有者のメインアカウントや他のすべてのスペンダーから暗号的に隔離されています。ZIP-32はキー導出を定義し、このERCERC-20approve / transferFrom を以下のようにマッピングします:

  1. approve(spender, N) — 所有者は未使用のZIP-32サブアカウントを導出し、transfer([[glossary/PrivacyCall|PrivacyCall]]) を介して N を資金提供し、そのサブアカウントのスペンディングキーをEOAスペンダーに (オフチェーンで暗号化して) 配信します。オンチェーン: 1回の transfer
  2. allowance(owner, spender) — そのサブアカウントの残り残高を、サブアカウントのビューイングキーでスキャンします。オンチェーンマッピングはありません。
  3. transferFrom(owner, to, amount) — スペンダーはサブアカウントから to に支払うために支出します; お釣りはサブアカウントに戻ります。オンチェーン: 1回の transfer
  4. approve(spender, 0) / 取り消し — 所有者は transfer を介してサブアカウントを回収します。

アローワンスの上限は、オンチェーンカウンターではなく、サブアカウントの実際のノート残高によって強制されます。ウォレットは、どのビューイングキーがノートを復号したかによって「自身の」資産と「アローワンス」資産を区別します — オンチェーンマーカーはありません。

実行要件

オンチェーン状態マシンはOrchardシールドプール (Zcashプロトコル仕様) に従います。実装はMUST以下を行います:

  • ナリファイアセットを維持します; 同じ nf は2回消費されてはMUST NOTなりません。
  • 追記専用のコミットメントツリーを維持します; 履歴ルートは isValidAnchor を介してクエリ可能です。
  • Groth16証明、spend-authおよびbindingSig、およびすべてのpubFieldsバインディングチェック ( pubFields[7] のみではない) を検証します。
  • 重複またはゼロのコミットメントを拒否します; 空のアクション配列を拒否します; 呼び出しごとのアクション数を上限設定します (maxActions、有限で設定可能な正の境界)。
  • 値の変更を mint / burn / transfer を介してのみ公開します (公開バンドルエントリポイントはありません)。
  • デプロイ時に一度だけPerc20Createdを発行します (ファクトリデプロイメントがRECOMMENDEDですが、必須ではありません)。

コンプライアンス。 cmxFrozenRoot|cmxFrozenRoot] はオフチェーンのブラックリストSMTのルートです; 回路は消費されたノートの非メンバーシップを証明します。setFrozenRootadmin のみです; 初期ルート 0 は空のブラックリストを示します。実装は、更新後短い猶予期間中に直前のルートを受け入れるMAYことができ、処理中の証明が立ち往生しないようにします。

理由 (Rationale)

  • Orchard ZK-UTXOモデル。 ノート、ナリファイア、およびコミットメントツリーはZcashプロトコル仕様に従います; このERCEVM上でのプライベートトークンインターフェースとアセットごとのデプロイメントを定義します。
  • プライベートERC-20であり、異なる資産ではない。 同じメソッドインターフェース; プライバシーは公開性 (公開ビュー vs プライベートクエリ vs 区別不能な転送) を変更しますが、ユーザーの意図は変更しません。
  • すべての移動に対して1つのオンチェーン操作。 transfer / approve / transferFrom を統合することで、ERC-20が不可避的に漏洩する承認メタデータが削除されます。
  • ZIP-32サブアカウントを介した承認済み支出。 各EOAスペンダーは、オンチェーンの allowance マッピングの代わりに、隔離された階層型アカウントを取得します; メソッドセマンティクスを参照してください。
  • approve(contract) はなし。 コントラクトはプライベートキーを持っていません; スペンディングキーをオンチェーンに置くと、誰にでも公開されてしまいます。プログラム可能なプライベート支出 (述語承認ノート、MPCカストディ) は将来の作業であり、このERCの一部ではありません。
  • ウォレットの動作はオンチェーンABIの範囲外。 サブアカウントのレイアウト、暗号化されたキー配信、およびノートスキャンはウォレット/SDKの責任です; 参照実装を参照してください。

後方互換性

pERC20ERC-20機能的に完全互換 (capability-complete) ですが、バイト互換性はありません。公開 balanceOf、オンチェーン allowanceapprove/transferFrom ABI、Transfer/Approval イベントはありません。既存のERC-20インデクサーや構成可能なコントラクトは、プライバシー対応のウォレット/SDKなしではこれを駆動できません。

公開ERC-20ツインへのオプションの bridgeOut は、出口でプライバシーを終了MAYする可能性がありますが、この提案では必須ではありません。

テストケース

参照実装リポジトリには以下が含まれます:

  • Foundryユニットテスト (test/PERC20Test.t.sol): コンストラクタガード、mint/burn/transfer会計、供給不変条件。
  • エンドツーエンドテスト (test/PERC20E2E.t.sol, e2e/): デプロイされた PERC20 に対する実際のGroth16証明。mint、transfer、burn、および approve / transferFrom フローをカバーします。

参照実装

コード

参照実装: PERC20Labs/pERC20_.

  • 規範的なアセットコントラクト: contracts/ptoken/PERC20.sol (IPERC20)。
  • 暗号化およびウォレット形式 (キー導出、perc1アドレス、ノート暗号化、ナリファイアapproveパッケージング): 同じリポジトリ内の参照ライブラリおよびSDK。

関連する標準およびプロトコル

セキュリティに関する考慮事項

  • 二重支払い保護: ナリファイアセット + 正しい nf 導出。各 pubFields[i]< FrMUSTなければなりません (そうでない場合、nf + Fr は異なる isSpent キーで証明を再利用します); pubFields[0][3][6] はそれぞれ anchornfOldcmxMUST等しくなければなりません (そうでない場合、有効な証明が異なるcalldataにバインドされる可能性があります)。
  • 供給不変条件: 値の変更は mint/burn/transfer を介してのみ行われます; コア実行パスは公開呼び出し可能でMUST NOTありません。
  • 価値保存: 状態変更の前にbindingSigが検証されます。
  • リプレイ保護: sighashは chainId、コントラクトアドレス、およびすべての nf/cmx をバインドします。
  • サブアカウントスペンディングキー: approve のために配信されるキーは、暗号文としてのみ現れるMUST必要があります; コントラクトストレージには決して現れません。
  • コンプライアンス権限: setFrozenRootは高信頼の管理者ロールです; マルチシグ/タイムロックをSHOULD使用します。

プライバシーに関する考慮事項

  • 操作はリレイヤーを介して提出され、提出者のEOAを隠すSHOULD必要があります。
  • mint/burnamounttotalSupply は公開です; 転送金額と approve の関係はプライベートです。
  • approve/transferFromtransfer とオンチェーンで区別不能です (同じ transfer([[glossary/PrivacyCall|PrivacyCall]]) セレクター); mint/burn は公開金額を持つ別々の関数です。
  • 試行復号 (trial-decryption) は受領の信頼境界です: NoteAddedイベントだけでは支払いを証明しません; 回路は encCiphertextcmx と一致することを検証しません。
  • setFrozenRootは、管理者がオフチェーンのブラックリストを介して識別されたノートを凍結することを可能にします; これは完全なトラストレス性とのコンプライアンス上の明示的なトレードオフです。

著作権

著作権および関連する権利はCC0により放棄されます。

この文書を引用する際は、Cyimon, “ERC-8287: Private Token Standard,” Ethereum Improvement Proposals, June 2026. としてください。

1投稿 - 1参加者

トピック全文を読む