[Draft ERC] VOSA-RWA: Compliance-Gated Privacy Token for Real World Assets
VOSA-RWA 為現實世界資產定義了一項新標準,透過整合零知識證明技術,在不將敏感個人資料儲存於鏈上的情況下,同時確保交易隱私與監管合規性。
欄位
值
狀態
草案 (Draft)
類型
標準軌跡 (Standards Track)
類別
ERC
依賴項目
pERC-20 (私有 ERC-20, 草案), EIP-712, ERC-5564
摘要 (Abstract)
VOSA-RWA 定義了一種用於真實世界資產(RWA)發行合規門控隱私代幣的模式。它擴展了 pERC-20 (私有 ERC-20),增加了一個通用合規模組 —— 每一項改變狀態的操作(轉帳、鑄造、銷毀)都需要一個經鏈上驗證的零知識證明(ZK proof),證明該操作滿足鏈下合規要求(KYC、AML、制裁、白名單)。鏈上不存儲任何合規數據或個人識別資訊(PII)。
核心見解:合規與隱私並不衝突。VOSA-RWA 同時實現了兩者 —— 餘額和轉帳金額透過 Poseidon 承諾(commitments)和 Groth16 證明進行隱藏,而每項操作都由鏈上驗證的 ZK 合規證明進行門控。
動機 (Motivation)
RWA 合規問題
代幣化真實世界資產(證券、債券、房地產、大宗商品)面臨著根本性的矛盾:
需求
標準 ERC-20
隱私代幣 (Tornado)
VOSA-RWA
餘額隱私
❌
✅
✅
轉帳金額隱私
❌
✅
✅
KYC/AML 執行
✅ (允許列表)
❌
✅ (ZK 證明)
鏈上無 PII
不適用
不適用
✅
審計追蹤
✅ (事件)
❌
✅
監管合規
✅
❌
✅
現有方案被迫做出選擇:
-
ERC-3643 (T-REX):鏈上身份註冊 + 基於聲明的合規。合規性得到執行,但地址級別的持有量和合規狀態是公開可見的。
-
隱私代幣:餘額隱私,但沒有合規機制。與證券法規不相容。
VOSA-RWA 結合了兩者:透過 ZK 證明對每項操作執行合規性,同時保持餘額隱藏。
為什麼選擇 ZK 合規而非鏈上允許列表?
方法
鏈上數據
隱私性
靈活性
鏈上允許列表 (ERC-3643)
完整身份註冊表
❌ 公開誰持有什麼
每個用戶需鏈上更新
ZK 合規 (VOSA-RWA)
僅:允許的金鑰雜湊
✅ 無 PII,無允許列表
新增合規服務 = 增加金鑰雜湊
使用 ZK 合規:
-
增加新的合規提供者 = 調用
setAllowedKeyHash(hash, true)。無需更改電路,無需升級合約。 -
撤銷提供者 = 調用
setAllowedKeyHash(hash, false)。立即生效。 -
合規服務可以執行任何政策(KYC、AML、制裁、合格投資者檢查),而合約無需知道或關心檢查了什麼。
規範 (Specification)
概述
鏈下 鏈上
┌─────────────────────┐ 證明 ┌─────────────────────────┐
│ 合規服務 │────────────▶│ ComplianceModule │
│ KYC/AML/制裁 │ │ verifyComplianceProof()│
│ 白名單/黑名單 │ └──────────┬──────────────┘
└─────────────────────┘ │ ok
▼
┌─────────────────────────┐
│ RWAERC20 │
│ transferWithCompliance()│
│ mintWithCompliance() │
│ burnWithCompliance() │
└─────────────────────────┘
RWAERC20 擴展了 PrivateERC20 (pERC-20) 並覆寫所有直接操作使其回退(revert)。僅能使用合規門控的入口點:
contract RWAERC20 is PrivateERC20 {
IComplianceModule private _compliance;
mapping(bytes32 => bool) private _usedContexts;
// 這些會回退 —— RWA 要求所有操作都必須合規
function transfer(...) → revert UseTransferWithCompliance()
function mint(...) → revert UseMintWithCompliance()
function burn(...) → revert UseBurnWithCompliance()
function consolidate(...) → revert ConsolidateNotSupported()
function createPolicy(...) → revert CreatePolicyNotSupported()
function createPermit(...) → revert CreatePermitNotSupported()
// 僅這些可用
function transferWithCompliance(proof, context, keyHash, ...) external;
function mintWithCompliance(proof, context, keyHash, ...) external;
function burnWithCompliance(proof, context, keyHash, ...) external;
}
合規流程
- 用戶構建操作(例如:轉帳的輸入/輸出)。
- 用戶向鏈下服務請求合規證明:
→ 服務檢查 KYC/AML/制裁/白名單。
→ 服務生成 ZK 證明:Poseidon(secret) == keyHash,並綁定到上下文(context)。
→ 返回(proof, context, keyHash)。 - 用戶調用
transferWithCompliance(proof, context, keyHash, ...轉帳參數...)。 - 合約:
a. 計算boundContext = keccak256(abi.encode(address(this), context))。
b. 檢查boundContext是否已被使用(重放保護)。
c. 將boundContext標記為已使用。
d. 調用compliance.verifyComplianceProof(proof, boundContext, keyHash)。
e. 如果成功,透過_executeTransfer()執行轉帳。
ComplianceModule 介面
合規模組是一個通用且可重複使用的合約 —— 並非 RWA 專用。任何隱私代幣都可以整合它。
interface IComplianceModule {
event ComplianceProofVerified(
address indexed caller, bytes32 context, bytes32 verifierId, bytes32 keyHash
);
/// @notice 使用預設驗證器驗證合規證明
function verifyComplianceProof(
bytes calldata proof, bytes32 context, bytes32 keyHash
) external returns (bool);
/// @notice 使用特定驗證器驗證合規證明(用於電路版本控制)
function verifyComplianceProof(
bytes32 verifierId, bytes calldata proof, bytes32 context, bytes32 keyHash
) external returns (bool);
}
ComplianceModule 實現
contract ComplianceModule is IComplianceModule {
IGroth16Verifier public immutable defaultVerifier;
mapping(bytes32 => bool) private _allowedKeyHashes;
mapping(bytes32 => address) private _verifiers; // 用於電路版本控制
function verifyComplianceProof(bytes calldata proof, bytes32 context, bytes32 keyHash)
external returns (bool)
{
if (!_allowedKeyHashes[keyHash]) revert KeyHashNotAllowed();
uint256[] memory publicInputs = new uint256[](2);
publicInputs[0] = uint256(context);
publicInputs[1] = uint256(keyHash);
if (!defaultVerifier.verify(proof, publicInputs)) revert VerificationFailed();
emit ComplianceProofVerified(msg.sender, context, bytes32(0), keyHash);
return true;
}
function setAllowedKeyHash(bytes32 keyHash, bool allowed) external onlyOwner;
function setVerifier(bytes32 verifierId, address verifier) external onlyOwner;
function removeVerifier(bytes32 verifierId) external onlyOwner;
function transferOwnership(address newOwner) external onlyOwner;
}
RWAERC20 介面
構造函數 (Constructor)
constructor(
string memory name_,
string memory symbol_,
uint8 decimals_,
address transferVerifier_, // 轉帳 ZK 驗證器 (來自 pERC-20)
address amountVerifier_, // 鑄造/銷毀 ZK 驗證器 (來自 pERC-20)
address poseidon_, // Poseidon 雜湊合約
address complianceModule_ // ComplianceModule 地址
)
合規門控操作
/// @notice 帶有合規證明的轉帳
function transferWithCompliance(
bytes calldata proof, // 合規 ZK 證明
bytes32 context, // 操作上下文(在鏈上綁定到 address(this))
bytes32 keyHash, // 合規服務金鑰雜湊
address[] calldata inputs, // 輸入 VOSA 地址
bytes32[] calldata inputCommitments,
address[] calldata outputs, // 輸出 VOSA 地址
bytes32[] calldata outputCommitments,
uint256[] calldata outputTimestamps,
bytes[] calldata ephemeralPubKeys,
bytes[] calldata signatures, // EIP-712 ECDSA 簽名
bytes calldata zkProof, // 轉帳 ZK 證明(金額守恆)
uint256 deadline,
int256[] calldata policyChangeIndices,
bytes calldata memo
) external nonReentrant whenNotPaused returns (bool);
/// @notice 帶有合規證明的鑄造(僅限允許的鑄造者)
function mintWithCompliance(
bytes calldata proof,
bytes32 context,
bytes32 keyHash,
uint256 amount,
address recipient,
bytes32 commitment,
uint256 outputTimestamp,
bytes calldata ephemeralPubKey,
bytes calldata zkProof,
bytes calldata memo
) external onlyAllowedMinter nonReentrant whenNotPaused returns (bool);
/// @notice 帶有合規證明的銷毀
function burnWithCompliance(
bytes calldata proof,
bytes32 context,
bytes32 keyHash,
address input,
bytes32 inputCommitment,
uint256 amount,
address changeAddress,
bytes32 changeCommitment,
uint256 changeTimestamp,
bytes calldata changeEphemeralPubKey,
bytes calldata signature,
bytes calldata zkProof,
uint256 deadline
) external nonReentrant whenNotPaused returns (bool);
讀取函數 (View Functions)
function complianceModule() external view returns (address);
function allowedMinter() external view returns (address);
function isContextUsed(bytes32 context) external view returns (bool);
管理函數 (Admin Functions)
function setComplianceModule(address complianceModule_) external onlyOwner;
function setAllowedMinter(address allowedMinter_) external onlyOwner;
事件 (Events)
// 合規門控操作事件
event ComplianceGatedTransfer(bytes32 indexed context, bytes32 keyHash);
event ComplianceGatedMint(bytes32 indexed context, bytes32 keyHash);
event ComplianceGatedBurn(bytes32 indexed context, bytes32 keyHash);
// 治理事件
event AllowedMinterChanged(address indexed previousMinter, address indexed newMinter);
event ComplianceModuleChanged(address indexed previousModule, address indexed newModule);
// ComplianceModule 事件(每次驗證成功時觸發)
event ComplianceProofVerified(address indexed caller, bytes32 context, bytes32 verifierId, bytes32 keyHash);
證明電路 (Attestation Circuit)
合規證明使用一個極簡的基於 Poseidon 的證明電路:
公開輸入:context, keyHash
私密輸入:secret
約束:Poseidon(secret) == keyHash
合規服務持有 secret 並在鏈上註冊 keyHash = Poseidon(secret)。為了證明一項操作,它生成一個帶有公開 (context, keyHash) 和私密 secret 的 Groth16 證明。該證明在不洩露秘密的情況下證明了對秘密的知曉。
屬性
值
約束數量
~213
證明時間 (snarkjs)
~922 ms
證明時間 (rapidsnark)
~44 ms
公開訊號
2 (context, keyHash)
曲線
BN254
雜湊算法
Poseidon (1 個輸入)
跨合約重放保護
RWAERC20 在鏈上執行上下文綁定:
function _boundContext(bytes32 context) internal view returns (bytes32) {
return keccak256(abi.encode(address(this), context));
}
合規服務在生成證明時必須計算相同的 boundContext。這可以防止為合約 A 發行的合規證明被用於合約 B,即使它們共享同一個 ComplianceModule。
雙重證明架構
每項合規門控操作都需要兩個獨立的 ZK 證明:
-
合規證明(證明電路):證明合規服務已批准此特定操作。
-
交易證明(pERC-20 電路):證明金額守恆 / 承諾正確性。
這兩者是獨立驗證的 —— 合規模組檢查證明 #1,基礎 PrivateERC20 邏輯檢查證明 #2。任何一個證明都不會向另一個洩露任何資訊。
原理闡述 (Rationale)
為什麼繼承 PrivateERC20 而非包裝?
繼承可以直接訪問內部的 _executeMint / _executeBurn / _executeTransfer,避免了外部自我調用及其問題(破壞 nonReentrant、額外的 Gas、簽名不匹配)。覆寫模式很簡潔:封鎖直接調用,透過合規門控,然後委託給父合約的內部邏輯。
為什麼不在鏈上存儲合規狀態?
鏈上允許列表(如 ERC-3643)會暴露誰被允許持有代幣。這會洩露有關 KYC 狀態、認證、司法管轄區的資訊。使用 ZK 合規:
-
鏈上合約僅存儲
keyHash值(合規服務的不透明識別符)。 -
特定用戶是否通過 KYC 永遠不會記錄在鏈上。
-
合規服務可以執行任何政策,而無需更改智能合約。
為什麼要分離合規模組?
ComplianceModule 是一個獨立合約,未嵌入在 RWAERC20 中。優點:
-
可重複使用:多個 RWA 代幣可以共享一個 ComplianceModule。
-
可升級:
setComplianceModule()允許在不重新部署代幣的情況下進行更換。 -
多提供者:多個合規服務(每個都有自己的
keyHash)可以共存。
為什麼封鎖 consolidate / createPolicy / createPermit?
為了嚴格的 RWA 合規,每一次價值移動都必須有合規證明。Consolidate(合併 UTXO)在沒有合規檢查的情況下改變了 VOSA 結構。Policy/Permit 委託可能允許未經授權的方轉移受監管資產。這些在 RWA 變體中被封鎖;基礎 PrivateERC20 則保留它們用於非監管場景。
效能 (Performance)
Gas 消耗
在 Hardhat 本地網絡測量,Solidity 0.8.24 開啟 viaIR + 優化器 (runs=1),真實 Poseidon (BN254),模擬 Groth16 驗證器。每個真實 Groth16 驗證需額外增加約 20 萬 Gas。
操作
Gas
mintWithCompliance
189,999
transferWithCompliance
164,973
burnWithCompliance
236,787
合規證明生成
證明器
見證人 (Witness)
證明 (Prove)
總計
snarkjs (WASM)
~91 ms
~922 ms
~1.0 s
rapidsnark (C++)
~192 ms
~44 ms
~236 ms
註:合規門控轉帳的總證明時間是合規證明(~236 ms)和轉帳證明(使用 rapidsnark 約 ~70 ms)之和,總計約 ~300 ms。
安全考量 (Security Considerations)
防止合規繞過
從 PrivateERC20 繼承的所有六個操作(transfer, mint, burn, consolidate, createPolicy, createPermit)都被覆寫,並返回描述性錯誤的回退。不存在任何可以在沒有有效合規證明的情況下改變狀態的代碼路徑。
CEI 模式 (檢查-效果-交互)
_requireCompliance 在進行外部調用 verifyComplianceProof 之前將上下文標記為已使用。這可以防止惡意或可升級的合規模組重入合約並重複使用同一個證明。
重入保護
所有三個合規門控入口點都帶有 nonReentrant。結合 _requireCompliance 中的 CEI 模式,不存在重入窗口。
調用者限制
-
mintWithCompliance:僅限於允許的鑄造者或所有者(透過onlyAllowedMinter修飾符)。 -
transferWithCompliance和burnWithCompliance:任何人都可以調用,但需要有效的合規證明以及來自 VOSA 所有者的有效 EIP-712 ECDSA 簽名。不檢查調用者 (msg.sender) —— 這是設計使然,以支持中繼器/元交易(meta-transaction)模式。
簽名覆蓋範圍
繼承自 pERC-20:EIP-712 簽名覆蓋了 policyChangeIndices 和 ephemeralPubKeys,防止搶先交易(frontrunning)替換。
銷毀授權
繼承自 pERC-20:銷毀始終需要所有者的簽名,而非委託人。Policy/permit 的支出者無法贖回 RWA 資產。
上下文重放保護
-
同合約重放:透過
_usedContexts[boundContext] = true防止。 -
跨合約重放:透過
boundContext = keccak256(abi.encode(address(this), context))防止。 -
跨鏈重放:透過 EIP-712
DOMAIN_SEPARATOR(包含chainId)防止。
合規模組信任
合規模組是一個受信任的外部合約。如果被攻破,攻擊者可能會批准未經授權的操作。緩解措施:
-
所有者應為多簽錢包(Gnosis Safe)。
-
setComplianceModule會觸發ComplianceModuleChanged以供監控。 -
setVerifier會驗證code.length > 0。 -
transferOwnership拒絕address(0)。
向後相容性 (Backwards Compatibility)
VOSA-RWA 是一個新的合約標準,沒有向後相容性要求。它沒有實現 ERC-20 介面。ComplianceModule 是一個獨立合約,可供任何需要合規門控的代幣使用。
參考實現 (Reference Implementation)
VOSA/
├── vosa-rwa/
│ └── contracts/
│ ├── src/
│ │ ├── RWAERC20.sol # 主合約 (擴展 PrivateERC20)
│ │ ├── poseidon/
│ │ │ └── RealPoseidon.sol # BN254 Poseidon (PoseidonT3/T4/T5)
│ │ └── mocks/
│ │ ├── MockComplianceModule.sol
│ │ ├── MockGroth16Verifier.sol
│ │ └── MockPoseidon.sol
│ ├── test/
│ │ ├── RWAERC20.test.ts # 單元測試 (30 項通過)
│ │ ├── RWAERC20.integration.test.ts # 完整流程:鑄造 → 轉帳 → 銷毀
│ │ ├── Compliance.and.audit.test.ts # ComplianceModule 整合
│ │ └── RWA.product.integration.test.ts # RWA 產品場景
│ └── scripts/
│ ├── deploy.ts
│ ├── deploy-with-real.ts # 真實 Poseidon + ComplianceModule
│ ├── gas-benchmark.ts
│ └── proof-time-benchmark.ts # snarkjs + rapidsnark
├── erc20-native/ # PrivateERC20 基礎 (pERC-20)
│ └── contracts/src/PrivateERC20.sol
├── compliance/ # 獨立合規模組
│ ├── contracts/
│ │ ├── IComplianceModule.sol
│ │ ├── ComplianceModule.sol
│ │ └── IGroth16Verifier.sol
│ ├── circuits/
│ │ ├── attestation.circom # Poseidon(secret) == keyHash (~213 約束)
│ │ └── scripts/
│ │ ├── compile.sh
│ │ └── setup.sh
│ └── service/ # 鏈下合規服務參考
│ ├── src/
│ │ ├── checks.ts # KYC/AML/制裁/白名單
│ │ ├── attestation.ts # 金鑰管理
│ │ ├── proof.ts # 證明生成
│ │ └── context.ts # 上下文構建
│ └── data/
│ ├── kyc.json, aml.json, whitelist.json, blacklist.json
討論問題
-
通用合規 vs RWA 專用:合規模組應該被標準化為一個獨立的 ERC(可供任何代幣使用),還是與 RWA 代幣標準捆綁在一起?
-
合規證明過期:目前的設計沒有內置證明的存活時間(TTL)—— 過期依賴於業務交易的截止日期(deadline)。證明電路是否應該包含時間戳?
-
凍結資產:VOSA 的一次性地址模型使得鏈上地址凍結變得不切實際(用戶可以在凍結前轉移)。該設計依賴於合規服務拒絕為被凍結用戶簽發證明。這對於監管要求是否足夠?
-
多司法管轄區:不同的司法管轄區有不同的合規要求。標準是否應該支持每項操作的驗證器選擇(目前已可透過
verifyComplianceProof(verifierId, ...)實現),還是這屬於應用層的範疇? -
與 ERC-3643 的互操作性:VOSA-RWA 代幣是否可以與現有的 T-REX 基礎設施互操作(使用 ZK 合規模組作為身份驗證橋樑)?
版權
透過 CC0 放棄版權及相關權利。
1 則貼文 - 1 位參與者
[閱讀完整主題](https://ethereum-magicians.org/t/draft-erc-vosa-rwa-compliance-gated-privacy-token-for-real-world-assets/27908)