Privacy Pool 關聯性證明技術實作:從密碼學原理到 Solidity 程式碼
本文深入分析 Privacy Pool 的 Association Proof(關聯性證明)機制,從密碼學原理到實際的 ZK 電路設計(Circom),再到 Solidity 智慧合約的完整部署程式碼。我們將展示如何用零知識證明技術,在保護用戶隱私的同時提供合規所需的關聯集合證明,實現隱私與監管的平衡。
Privacy Pool 關聯性證明技術實作:從密碼學原理到 Solidity 程式碼
前言
上次寫隱私池的文章,收到不少私訊問我:「那個 association proof 到底是怎麼回事?有沒有實際能跑的程式碼?」
好問題。網上關於 Privacy Pool 的文章,十篇有九篇只會畫個示意圖、講個大概原理,真正動手的沒幾個。今天這篇,我決定把袖子捲起來,直接帶你看密碼學公式、ZK 電路設計、到最後 Solidity 的實際部署。
我假設你已經知道什麼是零知識證明、什麼是 Merkle Tree,不然這篇可能會有點吃力。如果你是新手,先去翻我之前寫的「零知識證明入門」再回來。
什麼是 Association Proof?
先說個場景
你是一個基金的經理人,想把一大筆 ETH 從冷錢包轉到交易所。但是區塊鏈是公開的——如果你直接轉,外界馬上就知道你「要出貨了」。
傳統隱私池的做法是:把 ETH 存進隱私池,攪一攪,再提出來。這樣外面的人只知道「有人存了錢、有人提了錢」,但不知道是不是你。
問題來了:監管機構這時候跳出來說「我要知道你沒有洗錢」。你怎麼證明?
Association Proof 就是用來解決這個問題的。你可以證明:「我提出來的這筆錢,來源是某個合法的存款集合(比如某幾間交易所的提款),但我不需要透露具體是哪一筆。」
這個概念是 2022 年由 Vitalik 和幾位研究者提出來的,後來成為 Privacy Pool 設計的標準範式。
密碼學原理:ZK-SNARK 電路設計
承諾與廢棄值
每筆存款進隱私池的時候,系統會做兩件事:
- 生成一個承諾(Commitment):把存款金額和一個秘密隨機數丟進 hash 函數,產生一個「指紋」存進 Merkle Tree
- 記錄一個廢棄值(Nullifier):把同一個秘密隨機數做 hash,未來提款的時候用來證明這筆存款已經用過了
// 存款時
const secret = randomBytes(31); // 31 bytes 的隨機數
const nullifier = pedersenHash(secret); // 廢棄值 = hash(secret)
const commitment = keccak256(secret, amount); // 承諾 = hash(secret, amount)
// 承諾存入 Merkle Tree,廢棄值存進廢棄集合
提款證明要驗證什麼?
當你要從隱�私池提款的時候,你需要向合約證明(但不透露):
- 你知道某個存在於 Merkle Tree 中的承諾背後的秘密
- 這個承諾對應的廢棄值沒有被用過
- 這個承諾屬於某個「合法的關聯集合」
第三點就是 Association Proof 的核心。
電路設計:用 Circom 寫 ZK 電路
電路的輸入
// Withdraw.circom
pragma circom 2.0.0;
template Main() {
// 公開輸入
signal input root; // Merkle Tree 的根
signal input nullifierHash; // 廢棄值的 hash(公開)
signal input recipient; // 接收者地址
signal input associationRoot; // 關聯集合的 Merkle 根
// 私人輸入(只有 prover 知道)
signal input nullifier; // 真正的廢棄值
signal input secret; // 存款的秘密
signal input amount; // 存款金額
signal input pathElements[< = 20]; // Merkle proof 路徑
signal input pathIndices[< = 20]; // Merkle proof 索引
signal input associationPathElements[< = 20]; // 關聯集合的路徑
signal input associationPathIndices[< = 20]; // 關聯集合的索引
// 步驟一:驗證承諾的有效性
component commitmentHasher = Poseidon(3);
commitmentHasher.inputs[0] <== nullifier;
commitmentHasher.inputs[1] <== amount;
commitmentHasher.inputs[2] <== 0; // padding
// 步驟二:驗證廢棄值匹配
component nullifierHasher = Poseidon(1);
nullifierHasher.inputs[0] <== nullifier;
nullifierHasher.in === nullifierHash; // 公開的 nullifierHash 必須匹配
// 步驟三:驗證 Merkle 證明
component merkleVerifier = MerkleTreeVerifier(20);
merkleVerifier.leaf <== commitmentHasher.out;
merkleVerifier.root <== root;
merkleVerifier.pathElements <== pathElements;
merkleVerifier.pathIndices <== pathIndices;
merkleVerifier.out === 1;
// 步驟四:驗證關聯集合成員資格
// 我們需要證明:commitment 是某個合規存款集合的成員
// 這個合規集合可以由交易所/銀行預先構建
component associationVerifier = MerkleTreeVerifier(20);
associationVerifier.leaf <== commitmentHasher.out;
associationVerifier.root <== associationRoot;
associationVerifier.pathElements <== associationPathElements;
associationVerifier.pathIndices <== associationPathIndices;
associationVerifier.out === 1;
}
component main {public [root, nullifierHash, recipient, associationRoot]} = Main();
Merkle Tree 驗證器
// MerkleTreeVerifier.circom
pragma circom 2.0.0;
template MerkleTreeVerifier(levels) {
signal input leaf;
signal input root;
signal input pathElements[levels];
signal input pathIndices[levels];
signal output out;
signal hash[levels + 1];
hash[0] <== leaf;
for (var i = 0; i < levels; i++) {
// 根據路徑索引決定左右順序
// pathIndices[i] = 0 表示葉子在左側
// pathIndices[i] = 1 表示葉子在右側
component hashFunc = Poseidon(2);
// 左側 = 當前 hash,右側 = 路徑元素
hashFunc.inputs[0] <== pathIndices[i] == 0 ? hash[i] : pathElements[i];
hashFunc.inputs[1] <== pathIndices[i] == 0 ? pathElements[i] : hash[i];
hash[i + 1] <== hashFunc.out;
}
// 最後的 hash 必須等於根
hash[levels] === root;
out <== 1;
}
Solidity 智慧合約實作
合約架構
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@aztec/contracts/interfaces/IVerifier.sol";
contract PrivacyPoolWithAssociation is Ownable {
// ============================================
// 資料結構
// ============================================
// Merkle Tree 相關
uint256 public constant TREE_DEPTH = 20;
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// 存款記錄
struct Deposit {
bytes32 commitment;
uint32 timestamp;
bool isValid;
}
// ============================================
// 狀態變數
// ============================================
// Merkle Tree 的根
bytes32 public currentRoot;
// 記錄已使用的廢棄值(防止雙花)
mapping(bytes32 => bool) public nullifierUsed;
// 記錄所有有效的根(允許歷史根也能提款)
mapping(bytes32 => bool) public validRoots;
// 關聯集合的根(合規存款池)
mapping(bytes32 => bool) public validAssociationRoots;
// 存款記錄
mapping(bytes32 => Deposit) public deposits;
// ZK 驗證器介面
IVerifier public verifier;
// ============================================
// 事件
// ============================================
event DepositMade(
address indexed depositor,
bytes32 indexed commitment,
uint256 leafIndex,
uint32 timestamp
);
event WithdrawalMade(
address indexed recipient,
bytes32 indexed nullifierHash,
bool associationProofProvided,
uint32 timestamp
);
// ============================================
// 初始化
// ============================================
constructor(address _verifier) {
verifier = IVerifier(_verifier);
currentRoot = bytes32(0);
validRoots[currentRoot] = true;
}
// ============================================
// 存款功能
// ============================================
/**
* @notice 存款函數
* @param _commitment 存款承諾 = hash(secret + amount)
*
* 存款時,用戶將 ETH 存入合約,並提供一個承諾。
* 承諾會作為葉節點加入 Merkle Tree。
*/
function deposit(bytes32 _commitment) external payable {
require(msg.value > 0, "Must send ETH");
require(!deposits[_commitment].isValid, "Commitment already exists");
// 將承諾添加到 Merkle Tree
uint256 leafIndex = _insertIntoTree(_commitment);
// 記錄存款
deposits[_commitment] = Deposit({
commitment: _commitment,
timestamp: uint32(block.timestamp),
isValid: true
});
emit DepositMade(
msg.sender,
_commitment,
leafIndex,
uint32(block.timestamp)
);
}
// ============================================
// 普通提款(不提供關聯證明)
// ============================================
/**
* @notice 普通提款(沒有關聯證明)
* @param _proof ZK-SNARK 證明
* @param _root Merkle Tree 根
* @param _nullifierHash 廢棄值的 hash
* @param _recipient 接收者地址
* @param _relayer 中繼者地址(幫忙墊 Gas 的第三方)
* @param _fee 支付給中繼者的費用
*/
function withdraw(
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address payable _recipient,
address payable _relayer,
uint256 _fee
) external {
// 驗證 ZK 證明
_verifyProof(_proof, _root, _nullifierHash, _recipient, _relayer, _fee, bytes32(0));
// 檢查廢棄值未使用
require(!nullifierUsed[_nullifierHash], "Nullifier already used");
nullifierUsed[_nullifierHash] = true;
// 轉帳(扣除中繼費用)
uint256 amount = msg.value;
require(amount > _fee, "Insufficient amount for fee");
_recipient.transfer(amount - _fee);
if (_fee > 0) {
_relayer.transfer(_fee);
}
emit WithdrawalMade(_recipient, _nullifierHash, false, uint32(block.timestamp));
}
// ============================================
// 關聯證明提款(提供合規存款池證明)
// ============================================
/**
* @notice 帶有關聯證明的提款
* @param _proof ZK-SNARK 證明(包含關聯集合證明)
* @param _root Merkle Tree 根
* @param _nullifierHash 廢棄值的 hash
* @param _recipient 接收者地址
* @param _relayer 中繼者地址
* @param _fee 支付給中繼者的費用
* @param _associationRoot 關聯集合的 Merkle 根
*/
function withdrawWithAssociationProof(
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address payable _recipient,
address payable _relayer,
uint256 _fee,
bytes32 _associationRoot
) external {
// 驗證關聯集合根是否有效
require(
validAssociationRoots[_associationRoot],
"Invalid association root"
);
// 驗證 ZK 證明
_verifyProof(_proof, _root, _nullifierHash, _recipient, _relayer, _fee, _associationRoot);
// 檢查廢棄值未使用
require(!nullifierUsed[_nullifierHash], "Nullifier already used");
nullifierUsed[_nullifierHash] = true;
// 轉帳
uint256 amount = msg.value;
require(amount > _fee, "Insufficient amount for fee");
_recipient.transfer(amount - _fee);
if (_fee > 0) {
_relayer.transfer(_fee);
}
emit WithdrawalMade(_recipient, _nullifierHash, true, uint32(block.timestamp));
}
// ============================================
// 內部函數
// ============================================
function _verifyProof(
bytes calldata _proof,
bytes32 _root,
bytes32 _nullifierHash,
address _recipient,
address _relayer,
uint256 _fee,
bytes32 _associationRoot
) internal view {
// 驗證根是否有效(允許從歷史狀態提款)
require(validRoots[_root], "Invalid Merkle root");
// 準備驗證輸入
uint256[8] memory inputs = [
uint256(_root),
uint256(_nullifierHash),
uint256(uint160(_recipient)),
uint256(uint160(_relayer)),
_fee,
uint256(_associationRoot),
0, // padding
0 // padding
];
// 調用 ZK 驗證器
require(
verifier.verify(_proof, inputs),
"Invalid ZK proof"
);
}
uint256 private _nextLeafIndex;
function _insertIntoTree(bytes32 _commitment) internal returns (uint256) {
uint256 index = _nextLeafIndex;
_nextLeafIndex++;
// 簡化版本:實際需要完整的 Merkle Tree 實現
// 這裡我們只是更新根(實際產品需要完整實現)
currentRoot = keccak256(abi.encodePacked(currentRoot, _commitment));
validRoots[currentRoot] = true;
return index;
}
// ============================================
// 管理員函數
// ============================================
/**
* @notice 添加合規的關聯集合根
* @dev 這個根代表一組「被認可的存款來源」
* 例如:某幾間交易所的存款可以被視為合規
*/
function addAssociationRoot(bytes32 _root) external onlyOwner {
validAssociationRoots[_root] = true;
}
/**
* @notice 移除合規的關聯集合根
*/
function removeAssociationRoot(bytes32 _root) external onlyOwner {
validAssociationRoots[_root] = false;
}
/**
* @notice 緊急暫停
*/
bool public paused;
function pause() external onlyOwner {
paused = true;
}
function unpause() external onlyOwner {
paused = false;
}
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
}
關聯集合的構建方法
什麼是關聯集合?
關聯集合(Association Set)就是一堆「被認可為合法的存款」的集合。你可以把它想像成一個「白名單」。
舉例來說:
- 假設 Coinbase 和 Binance 的所有存款被視為「合規存款」
- 監管機構信任這兩間交易所的 KYC/AML 程序
- 那麼這兩間交易所的存款承諾就可以構成一個關聯集合
用戶提款時,可以選擇證明自己的存款來自這個合規集合,但又不需要透露具體是哪一筆。
怎麼構建關聯集合?
// 假設我們有一組合規的存款承諾
const compliantCommitments = [
commitment1, // 來自 Coinbase 的存款
commitment2, // 來自 Coinbase 的存款
commitment3, // 來自 Binance 的存款
// ...
];
// 將這些承諾插入 Merkle Tree
const associationTree = new MerkleTree(TREE_DEPTH);
compliantCommitments.forEach(commitment => {
associationTree.insert(commitment);
});
// 計算並發布根
const associationRoot = associationTree.getRoot();
// 將根提交到 Privacy Pool 合約
await privacyPool.addAssociationRoot(associationRoot);
實務考量
安全性注意事項
- 廢棄值必須妥善保管:廢棄值(secret)一旦洩漏,他人可以偽造你的提款證明
- 隨機數必須夠強:使用夠長的隨機數(至少 31 bytes),並且使用密碼學安全的隨機數生成器
- ZKP 電路必須經過審計:ZK 電路的漏洞可能導致資金被盜
隱私與合規的平衡
Association Proof 提供了一種優雅的平衡方式:
- 完全隱私模式:不提 Association Proof,外面只知道有人存有人提,不知道是誰
- 合規模式:提供 Association Proof,可以向監管機構證明資金來自合法來源
這兩種模式可以在同一個系統中共存,用戶根據需要選擇。
Gas 成本考量
ZK 驗證在鏈上的成本相當高。以目前以太坊主網為例:
- 單次 ZK 驗證:約 500,000 - 1,000,000 gas
- 這意味著每筆隱私提款的成本可能在 $50-200(取決於 ETH 價格)
所以實務上,很多人會選擇在 Layer 2(如zkSync、StarkNet)上部署 Privacy Pool,以大幅降低成本。
結論
Association Proof 是 Privacy Pool 設計中一個很聰明的概念。它不是在「隱私」和「合規」之間二選一,而是提供了一個連續的光譜——你可以選擇不同程度的隱私,同時滿足監管要求。
我個人認為這種設計會是未來合規隱私技術的主流方向。完全封閉的隱私系統遲早會被監管機構盯上,而完全透明的系統又喪失了區塊鏈最大的價值。Association Proof 在兩者之間找到了一個不錯的平衡點。
如果你對這個主題有興趣,建議你去讀讀 Vitalik 的原始文章 "Privacy Pools as a means to balance privacy and regulatory compliance",網上可以找到。
有任何問題,歡迎留言討論。
參考資料
- Vitalik Buterin, "Privacy Pools as a means to balance privacy and regulatory compliance", 2022
- Aztec Network Documentation: https://docs.aztec.network/
- Circom Documentation: https://docs.circom.io/
- SnarkJS Library: https://github.com/iden3/snarkjs
- Semaphore: https://semaphore.appliedzkp.org/
本網站內容僅供教育與資訊目的,不構成任何技術建議或投資建議。在部署任何隱私相關合約前,請進行完整的安全審計並諮詢專業人士意見。
資料截止日期:2026年3月
相關文章
- Privacy Pools 智能合約實現深度分析:從源碼架構到鏈上隱私驗證 — Privacy Pools 是以太坊隱私領域的重要創新,由 Vitalik Buterin 與研究團隊提出,其核心理念是透過零知識證明實現「選擇性披露」。本文深入分析 Privacy Pools 的智能合約源碼架構、零知識電路設計(Circom 電路代碼)、Merkle 樹管理器實現、以及如何在實際區塊鏈環境中驗證其隱私效果。同時提供完整的 Solidity 合約程式碼、安全審計要點,以及 Privacy Pools 與 Aztec 網路的實際交易隱私效果量化比較。截至 2026 年第一季度,Privacy Pools 協議已處理超過 47,000 ETH 的隱私交易。
- Privacy Pool ZK-Proof 驗證合約完整實作指南:從電路設計到 Solidity 部署 — 本文深入探討 Privacy Pool 系統中零知識證明(ZKP)驗證合約的完整實作流程。我們從密碼學基礎出發,詳細解釋 Groth16 和 PLONK 兩種主流零知識證明系統的原理,提供完整的 Circom 電路代碼範例,並展示如何將這些電路部署到以太坊區塊鏈上進行驗證。涵蓋 Merkle 樹驗證電路、承諾方案實現、完整隱私池合約代碼、以及可信設置教學。
- Privacy Pools 實際部署案例與 ZKML 技術整合完整指南:2025-2026 生產環境實踐 — 本文深入分析 Privacy Pools 的技術架構、生產環境部署案例,以及 ZKML(零知識機器學習)與隱私池的整合實踐。涵蓋 Privacy Pools 核心原理、合約實作、Circom 電路設計、匿名集管理、以及 ZKML 風險評分模型的完整實作程式碼。我們提供風險隔離隱私池的實際架構案例,說明如何結合 ZKML 實現智慧化存取控制,並探討 FATF Travel Rule 合規框架的整合方式。
- 隱私池聯盟成員證明深度技術實作:zkSNARK 電路設計與合規框架完整指南 — 本文深入探討隱私池中聯盟成員證明的密碼學原理、zkSNARK 電路設計、具體實現方式,以及在實際合規場景中的應用。我們提供完整的 Circom 電路代碼、Solidity 智能合約示例,以及針對不同合規框架的實施策略,涵蓋 AML/KYC 合規集成、等級驗證與監管報告等核心主題。
- Privacy Pool 實際交易流程與代碼實作:亞洲合規框架下的完整操作指南 — 本文專注於 Privacy Pool 的實際交易流程與代碼實作,提供從存款到提款的完整技術指南。使用 Noir 和 Circom 編寫的零知識電路代碼示例,以及完整的 Solidity 智能合約和 TypeScript 用戶端代碼。同時分析日本、台灣、韓國、新加坡等亞洲主要市場的監管合規要求,並提供多層次匿名集合的合規集合設計方案。
延伸閱讀與來源
- zkSNARKs 論文 Gro16 ZK-SNARK 論文
- ZK-STARKs 論文 STARK 論文,透明化零知識證明
- Aztec Network ZK Rollup 隱私協議
- Railgun System 跨鏈隱私協議
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!