作者: 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 認可的支出——包括approve 、 allowance和transferFrom 。更新後的標準在功能上與ERC-20完全兼容,但在字節上不兼容(ABI 不同,且不支持公開餘額)。
| ERC-20 | pERC20 | 層 |
|---|---|---|
name / symbol / decimals / totalSupply | 是的——公眾觀點相同 | 鏈上 |
balanceOf | 是的——僅限持卡人掃描 | 鏈下 |
transfer | 是的——私人聚會及金額 | 鏈上 |
approve / allowance / transferFrom | 新增——已批准EOA支出者通過ZIP-32子賬戶進行支出;鏈上= transfer (不支持合約支出者) | 鏈下 |
mint / burn | 是的——常用擴展 | 鏈上 |
Transfer / Approval事件 | 省略(隱私) | — |
以下是最新的 pERC20 標準:私有代幣標準。
抽象的
pERC20是 EVM 的默認私有同質化代幣標準,是ERC-20的隱私版本。其底層採用Zcash協議規範中的 Orchard 屏蔽池模型。它保留了 ERC-20 的所有方法,但部分方法是私有的(僅限持有者訪問,鏈下),而非公開的鏈上讀取,並且transfer / approve / transferFrom在鏈上顯示為相同的transfer操作。請參閱下方的pERC20 接口。
動機
以太坊的公共賬本使所有ERC-20餘額、轉賬和授權永久可見。隨著支付、工資、國庫和鏈上金融遷移到 L1 層,用戶和發行方需要的是私有的同質化代幣,而不僅僅是圍繞公共餘額的私密信息交流。
隱私問題越來越多地在協議層得到解決。例如, EIP-8182定義了一個協議層面的保護池:用戶可以存入公開的ETH或兼容的 ERC-20 代幣,在共享池內進行私密轉移,然後再提取回公開形式。該模型保護了現有的公共資產;但它並未定義如何發行從創建之初就具有私密性的代幣。
pERC20填補了後一個空白。它是一個應用層代幣標準,用於原生私有同質化代幣:從一開始就以私有票據的形式通過approve / transferFrom進行鑄造、持有、轉移和使用,沒有公開的balanceOf階段,也不存入共享的屏蔽池。它定義了ERC-20的私有對應物——相同的方法界面,不同的開放程度——因此發行方可以立即發行私有資產,同時協議級隱私(例如 EIP-8182)也在同步發展。兩者是互補的,而非競爭的:EIP-8182 將公共資產私有化; pERC20定義了私有資產的發行。
規格
關鍵詞MUST 、 MUST NOT 、 SHOULD 、 MAY的解釋遵循 RFC 2119。Solidity 語法版本為0.8.20或更高版本。
底層協議
價值以屏蔽票據的形式存在,而非賬戶餘額。票據格式、作廢符、承諾樹、票據加密以及操作/捆綁結構均遵循Zcash協議規範中的Orchard 屏蔽池,並在此處改編為基於資產的 EVM 合約,且已通過 Groth16 驗證。下文未重複的字段級格式在參考實現部分中具有規範性。
pERC20接口
本節將所有 pERC20 接口集中列出,並標記每個接口是否對應於ERC-20標準接口。ERC -20 : yes = ERC-20 標準; extension = 通用擴展(鑄造/銷燬); no = pERC20 特有。層級: on-chain = 合約 ABI; off-chain = 錢包/SDK(無合約方法)。
| pERC20接口 | ERC-20 | 層 | 開放性 | 描述 |
|---|---|---|---|---|
name() / symbol() / decimals() | 是的 | 鏈上 | 民眾 | 與 ERC-20 相同 |
totalSupply() | 是的 | 鏈上 | 民眾 | 公共計數器( mint - burn ) |
balanceOf(addr) | 是的 | 鏈下 | 私人的 | 使用查看密鑰掃描Orchard紙幣;僅限持卡人使用 |
transfer(PrivacyCall) | 是的 | 鏈上 | 私人(參與方 + 金額) | 果園行動包 |
approve(spender, N) | 是的 | 鏈下 | 私密(關係已隱藏) | ZIP-32子賬戶;資金 + 交付密鑰;鏈上提交為transfer(PrivacyCall) |
allowance(owner, spender) | 是的 | 鏈下 | 私人的 | 掃描子賬戶剩餘餘額 |
transferFrom(from, to, amount) | 是的 | 鏈下 | 私密(關係已隱藏) | 支出者在子賬戶中支出;鏈上提交為transfer(PrivacyCall) |
mint(amount, PrivacyCall) | 擴大 | 鏈上 | 金額公開;收款人私密 | 僅限發行方;果園行動 + totalSupply量增加 |
burn(amount, PrivacyCall) | 擴大 | 鏈上 | 公開金額;私人燃燒器 | 持票人燒燬自己的票據;果園行動 + totalSupply減少 |
issuer() | 不 | 鏈上 | 民眾 | 代幣發行者地址 |
cmxFrozenRoot() / setFrozenRoot() | 不 | 鏈上 | 公共根目錄;管理員寫入 | 合規凍結記錄根 |
cmxRoot() / isValidAnchor() / isSpent() / treeSize() | 不 | 鏈上 | 民眾 | 果園承諾樹狀態 |
活動
| pERC20事件 | ERC-20 | 層 | 描述 |
|---|---|---|---|
Transfer(from, to, value) | 是的 | 鏈下(省略) | 未公開;參與方和金額均屬私人信息。 |
Approval(owner, spender, value) | 是的 | 鏈下(省略) | 未發行;將所有者與消費者關聯 |
NoteAdded / NoteConfirmed | 取代Transfer | 鏈上 | 單張票據可觀察性 |
Mint / Burn | 擴大 | 鏈上 | 公開金額 |
Perc20Created / FrozenRootUpdated / BundleExecuted | 不 | 鏈上 | 部署、合規性、捆綁元數據 |
鏈上操作不可區分。 transfer 、 approve 、 transferFrom和撤銷操作的資金步驟在鏈上都是同一個調用: transfer(PrivacyCall) 。觀察者無法分辨正在執行的是哪個 ERC-20 操作。
原生不支持。ERC -20 的 ` approve(contractAddress, amount) `(合約自主調用transferFrom )沒有原生等效項:支出需要私鑰,而合約無法持有私鑰。參見原理。
合同接口
pERC20公開了一個鏈上 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 notebytes32 anchor; // historical root of the consumed (or dummy) input notebytes proof;uint256[8] pubFields;uint256[3] spendAuthSig;}// ERC-20-aligned public viewsfunction 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;// Compliancefunction 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);}符合性:
-
transfer成功必須返回true。 - 核心包執行路徑絕對不能公開調用(供應不變式;見下文)。
- 實現可以將 Solidity 拆分為多個合約(例如
IPERC20+ 驗證器基礎),但可觀察的 ABI 和事件必須與上述統一接口匹配。 -
cmxRoot()是最新的承諾樹根;isValidAnchor(root)如果root曾經是活躍的則返回 true;isSpent(nf)公開空值集;treeSize()是插入的承諾數。
呼叫格式
每次更改值的操作都會提交一個PrivacyCall編碼一個或多個Orchard 操作( Zcash協議規範):
-
actions=abi.encode(BundleAction[]). -
bindingSig= Schnorr 綁定簽名[Rx, Ry, s]證明值守恆。
每個BundleAction都是pubFields適用於 EVM 驗證的 Orchard 操作:一份輸出票據承諾 ( cmx ) 以及其(真實或虛擬)輸入的證明材料。pubFields 必須按以下順序排列(Orchard 操作的主要輸入):
| 指數 | 場地 | 角色 |
|---|---|---|
[0] | anchor | 消耗輸入的默克爾根 |
[1] | cv_net_x | 淨值承諾 X(具有約束力的簽名) |
[2] | cv_net_y | 淨值承諾 Y(具有約束力的簽名) |
[3] | nf_old | 消耗輸入的無效化 |
[4] | rk_x | 隨機消費授權密鑰 X |
[5] | rk_y | 隨機消費授權密鑰 Y |
[6] | cmx | 輸出說明承諾 |
[7] | rt_frozen | 合規性凍結根綁定 |
每個pubFields[i]必須< Fr ,其中Fr是驗證器(參考實現中為 BN254)使用的 SNARK 曲線的標量場模;否則,將返回錯誤( PubFieldOutOfRange )。實現必須通過ActionPubHash (Poseidon sponge)將這八個字段哈希到一個 Groth16 公共信號中,使其與電路的PubHashAction()相匹配。
綁定到調用數據字段。證明公共輸入必須與操作的頂級字段匹配;如果出現以下任何情況,實現必須回滾:
| 查看 | 恢復 |
|---|---|
pubFields[0] == anchor且isValidAnchor(anchor) | BadAnchor |
pubFields[3] == nfOld | 必須還原(參考實現: NullifierSpent ) |
pubFields[6] == cmx且cmx != 0 | InvalidProof / ZeroCommitment |
pubFields[7] == cmxFrozenRoot() | BadFrozenRoot |
spendAuthSig驗證pubFields[4]和pubFields[5]是否在(nfOld, cmx, epk, encCiphertext, outCiphertext)範圍內。 | BadSpendAuthSig |
如果沒有pubFields ↔ calldata 相等性檢查,有效的證明可以用不同的nfOld或cmx重放,繞過空值集或插入未經證明的承諾。
encCiphertext必須為 580 字節(Orchard 筆記outCiphertext + 收件人密鑰下的 AEAD 標籤)。outCiphertext 應為 80 字節(OVK 下的發送方自恢復)。更改筆記加密佈局的實現必須發佈此 ERC 的單獨變體。
在進行任何狀態變更之前,必須對所有操作無效符、承諾和操作的valueBalance驗證捆綁包級別的bindingSig (有關編碼,請參閱方法語義)。
注意加密和密鑰派生遵循Zcash協議規範中的 Orchard 筆記格式;確切的編碼在參考實現庫中。
方法語義
name / symbol / decimals / totalSupply
與 ERC-20 相同:公開的鏈上視圖。
transfer(PrivacyCall) → bool
使用Orchard票據作為輸入,並創建一個價值守恆的操作包( valueBalance == 0 )。發送者、接收者和金額必須保持私密。成功時返回true ;發出NoteAdded / NoteConfirmed ,而不是Transfer(from,to,value) 。
mint(amount, PrivacyCall) / burn(amount, PrivacyCall)
與transfer相同的Orchard操作驗證路徑,並公開totalSupply統計:
-
mint:totalSupply += amount(onlyIssuer);amount公開,接收方私有。鑄幣必須使用與transfer相同的錨點/無效符/支出授權路徑(無僅輸出分支)。電路必須將消耗的輸入限制為v = 0,以便該操作代表淨流入;合約不直接讀取票據價值。 -
burn:totalSupply -= amount;任何持有人均可銷燬自己的票據;amount公開,銷燬者私密。
| 手術 | valueBalance編碼 |
|---|---|
transfer | 0 |
burn | bit255 = 0,低位 = 金額 |
mint | bit255 = 1,低位 = 金額 |
balanceOf (鏈下,私有)
只有持有者才能通過掃描NoteAdded事件、使用查看密鑰試解密Orchard筆記並排除已花費的無效符來計算餘額。鏈上沒有balanceOf ,也無法查詢第三方的餘額。
approve / allowance / transferFrom (鏈下語義;鏈上 = transfer )
已批准的支出( approve / allowance / transferFrom )基於ZIP-32分層賬戶:每個EOA 支出者都會獲得一個專用的子賬戶( account_S ),該子賬戶擁有自己的支出密鑰和查看密鑰,並與所有者的主賬戶以及所有其他支出者進行加密隔離。ZIP -32定義了密鑰派生;此 ERC 將 ERC-20 的approve / transferFrom映射到 ZIP-32,具體如下:
-
approve(spender, N)— 所有者創建一個未使用的 ZIP-32 子賬戶,通過transfer(PrivacyCall)向其充值N,並將該子賬戶的支出密鑰交付給 EOA 支出者(鏈下加密)。鏈上操作:一次transfer。 -
allowance(owner, spender)— 該子賬戶的剩餘餘額,使用子賬戶查看密鑰掃描。無鏈上映射。 -
transferFrom(owner, to, amount)— 支出者從子賬戶支出to目標賬戶;找零返回到子賬戶。鏈上:一次transfer。 -
approve(spender, 0)/ revoke — 所有者通過transfer將子賬戶退回。
限額由子賬戶的實際票據餘額決定,而非鏈上計數器。錢包區分“自有”資產和“限額”資產的方式,是依據解密票據的查看密鑰,而非鏈上標記。
執行要求
鏈上狀態機遵循 Orchard 屏蔽池( Zcash協議規範)。實現必須:
- 維護一個無效化符集;同一個無效
nf不能使用兩次。 - 維護一個僅追加的承諾樹;可通過
isValidAnchor查詢歷史根節點。 - 驗證 Groth16 證明、支出認證和綁定簽名,以及所有 pubFields 綁定檢查(不僅僅是
pubFields[7])。 - 拒絕重複或零承諾;拒絕空操作數組;限制每次調用的操作數(
maxActions,一個有限的可配置正界限)。 - 僅通過
mint/burn/transfer公開價值變更(沒有公共捆綁包入口點)。 - 在部署時發出
Perc20Created一次(建議進行工廠部署,但並非必須)。
合規性。cmxFrozenRoot cmxFrozenRoot()是鏈下黑名單 SMT 的根;該電路用於證明已花費的筆記不屬於該黑名單setFrozenRoot僅限admin使用;初始根0表示黑名單為空。實現可以在更新後的短暫寬限期內接受緊鄰的前一個根,以避免正在進行的證明被擱置。
理由
- Orchard ZK-UTXO 模型。註釋、無效化符和承諾樹遵循Zcash協議規範;此 ERC 定義了私有代幣接口和在 EVM 上的每個資產的部署。
- 私有 ERC-20 代幣,並非不同的資產。方法表面相同;隱私改變的是開放性(公開查看 vs 私密查詢 vs 不可區分的傳輸),而不是用戶意圖。
- 所有資金轉移只需一次鏈上操作。合併
transfer/approve/transferFrom會移除 ERC-20 不可避免的授權元數據洩露。 - 通過ZIP-32子賬戶批准支出。每個 EOA 支出者都會獲得一個獨立的層級賬戶,而不是鏈上
allowance映射;參見方法語義。 - 無需
approve(contract)。合約沒有私鑰;將支出密鑰放在鏈上會使其暴露給所有人。可編程的私有支出(謂詞授權的票據、MPC託管)是未來的工作,不屬於本次ERC的範圍。 - 錢包行為不在鏈上ABI的範圍內。子賬戶佈局、加密密鑰交付和票據掃描屬於錢包/SDK的職責範圍;請參閱參考實現。
向後兼容性
pERC20具備ERC-20 的所有功能,但與字節不兼容:沒有公開的balanceOf屬性,沒有鏈上allowance ,沒有approve / transferFrom ABI,也沒有Transfer / Approval事件。現有的 ERC-20 索引器和可組合合約在沒有隱私保護錢包/SDK 的情況下無法驅動 pERC20。
可選的bridgeOut到公共 ERC-20 孿生服務器可能會在出口處終止隱私;本提案不要求這樣做。
測試用例
參考實現庫包含:
- 鑄造單元測試(
test/PERC20Test.t.sol):構造函數保護、鑄造/銷燬/轉移會計、供應不變量。 - 端到端測試(
test/PERC20E2E.t.sol,e2e/):針對已部署的PERC20的真實 Groth16 證明,涵蓋鑄造、轉移、銷燬和approve/transferFrom流程。
參考實現
代碼
參考實現: PERC20Labs/pERC20_ 。
- 規範性資產合約:
contracts/ptoken/PERC20.sol(IPERC20)。 - 加密和錢包格式(密鑰派生、
perc1地址、筆記加密、無效化器、approve打包):參考庫和 SDK 在同一個存儲庫中。
相關標準和協議
- EIP-20 :代幣標準
pERC20映射到的公共可互換代幣接口。 - Zcash協議規範:Orchard 屏蔽池 — 注意承諾、無效化器、筆記加密和操作結構,此處針對 EVM Groth16 驗證進行了調整。
- ZIP-32 :受保護的分層確定性錢包——用於在
approve/transferFrom中按消費者子賬戶的分層賬戶派生。
安全考量
- 雙花保護:空值設置 + 正確的
nf推導。每個pubFields[i]必須< Fr(否則nf + Fr會重用具有不同isSpent鍵的證明);pubFields[0]、[3]、[6]必須分別等於anchor、nfOld、cmx(否則有效的證明可以綁定到不同的 calldata)。 - 供應不變性:價值僅通過
mint/burn/transfer改變;核心執行路徑不得公開調用。 - 價值守恆:在狀態突變之前驗證結合特徵。
- 重放保護:sighash 綁定
chainId、合約地址和所有nf/cmx。 - 子賬戶支出密鑰:用於
approve密鑰必須僅以密文形式出現;絕不能存儲在合約中。 - 合規權限:
setFrozenRoot是一個高信任度的管理員角色;應該使用多重簽名/時間鎖。
隱私考量
- 操作應該通過中繼器提交,以隱藏提交者的EOA。
-
mint/burnamount和totalSupply是公開的;轉賬金額和approve關係是私密的。 -
approve/transferFrom在鏈上與transfer無法區分(相同的transfer(PrivacyCall)選擇器);mint/burn是具有公開數量的獨立函數。 - 試解密是收款的信任邊界:僅憑
NoteAdded事件無法證明付款;電路不會驗證encCiphertext是否與cmx匹配。 -
setFrozenRoot允許管理員通過鏈下黑名單凍結已識別的筆記;這是對完全不信任的一種明確的合規性權衡。
版權
通過CC0放棄版權及相關權利。
請引用此文檔:Cyimon,“ERC-8287:私有代幣標準”,以太坊改進提案,2026 年 6 月。



