Privacy Pool 關聯性證明數學推導完整指南:從零知識到大規模應用的深度解析

本文深入解析 Privacy Pool 的關聯性證明(Association Proof)機制,從 Pedersen 承諾、Schnorr 協議到 Kate 承諾的完整數學推導。涵蓋匿名集的組織方式、非成員證明的有序 Merkle 樹技術、Aztec 網路的 zk-zk Rollup 架構、以及隱私合規框架的實際部署案例。提供完整的 Noir 電路代碼範例和密碼學推導,幫助讀者理解如何用零知識證明在隱私和監管合規之間取得平衡。

Privacy Pool 關聯性證明數學推導完整指南:從零知識到大規模應用的深度解析

說到 Privacy Pool 這個項目,我真的又愛又恨。愛是因為它的設計真的很優雅,用密碼學優雅地解決了一個看起來無解的隱私問題;恨是因為它的數學推導實在太硬核了,我當初啃這塊的時候差點把頭髮拔光。

不過等你真正搞懂之後,會發現這套機制的核心思想其實很直觀:我能證明「這筆提款來自某一組存款之一」,卻不需要透露「究竟是哪一筆」。這個看似矛盾的能力,正是零知識證明最迷人的應用場景之一。

為什麼需要關聯性證明?

先說個背景:早期的隱私方案(如 Tornado Cash)有個致命缺陷。假設你往 Tornado Cash 存了 1 ETH,過了一段時間後從一個「乾淨」地址提款。監控機構只要追蹤鏈上資料,就能發現這筆存款和這筆提款之間的「時間差」很小——於是他們就能推斷這兩筆交易是關聯的。

更糟糕的是,如果有大戶或交易所的存款地址被識別出來,那跟這些地址有過交互的所有存款都會被「污染」。隱私貨幣變成了「guilty by association」,彷彿跟犯罪分子用過同一個 Mixer 就一定是壞人。

Privacy Pool 的出現就是為了解決這個問題。它不僅保護隱私,還提供了一個可選擇的「合規邊界」:你可以證明自己不是某個「壞人集合」的成員,卻不需要暴露自己的身份。

承諾電路的數學基礎

關聯性證明(Association Proof)的核心是 Commitment Scheme。概念很簡單:把你不想公開的資訊「藏」進一個承諾裡,之後再證明關於這個承諾的某些屬性。

承諾的基本屬性

一個安全的承諾方案必須滿足三個條件:

1. 隱藏性(Hiding)

承諾不會透露底層訊息的任何資訊。

2. 綁定性(Binding)

承諾者無法在打開時給出與原始訊息不同的值。

3. 隨機性(Randomness)

每次承諾都要加入隨機的 nonce,防止暴力破解。

數學上,一個 Pedersen 承諾長這樣:

Commitment = g^m · h^r (mod p)

其中:
- m: 承諾的訊息(存款金額)
- r: 隨機的盲目因子(blinding factor)
- g, h: 公開的生成元(generator)
- p: 橢圓曲線的階

為什麼用兩個生成元?因為這樣就能「分別承諾」兩個獨立的訊息,卻又把它們「加在一起」變成一個承諾。這個性質後面會用到。

Pedersen 承諾的推導

讓我們從頭推一遍,確保每個步驟都清楚:

步驟 1:離散對數假設

Pedersen 承諾的安全性基於離散對數問題(Discrete Logarithm Problem, DLP)的困難性。簡單來說:

已知:G(生成元)和 Q = k·G(點乘法)
難題:求 k(對數)在計算上不可行

這就像知道一個雜湊值,要找到原像一樣——對傳統電腦來說只能暴力搜索。

步驟 2:加法同態性

Pedersen 承諾有個漂亮的性質:線性可加

Commit(m1, r1) · Commit(m2, r2)
= (g^m1 · h^r1) · (g^m2 · h^r2)
= g^(m1+m2) · h^(r1+r2)
= Commit(m1 + m2, r1 + r2)

這意味著你可以把兩個承諾「加」在一起,得到的承諾對應於兩個訊息的和,卻不知道具體是哪兩個。這是後面實現批量驗證的關鍵。

步驟 3:零知識證明的結合

現在把承諾跟零知識證明結合起來。目標是:證明「我知道某個訊息 m 和對應的隨機數 r,使得 C = Commit(m, r)」,卻不透露 m 和 r 本身

這就是著名的 Schnorr 協議:

Prover(證明者):
1. 選擇隨機數 t
2. 計算 T = g^t(承諾的隨機版本)
3. 發送 {T} 給 Verifier

Verifier(驗證者):
4. 發送隨機挑戰 c

Prover:
5. 計算回應 s = t + c·r(mod p)
6. 發送 {s} 給 Verifier

Verifier:
7. 驗證 g^s = T · C^c
   即 g^(t+c·r) = g^t · (g^m · h^r)^c
   即 g^(t+c·r) = g^t · g^(m·c) · h^(r·c)
   即 g^(t+c·r) = g^(t+m·c) · h^(r·c)

直覺解釋:Verifier 發送一個隨機挑戰 c,Prover 必須「提前預測」或「巧妙構造」回應 s,使得等式成立。因為 t 是隨機選擇的(不知道 c),Prover 不可能構造假的承諾——除非真的知道 r。

匿名集的數學結構

關聯性證明的核心創新是把「匿名集」(Anonymity Set)的概念形式化。

什麼是匿名集?

匿名集就是「所有可能解釋你行為的替代方案集合」。在 Privacy Pool 的情境下:

理論上,匿名集越大,隱私保護越強。如果匿名集只有 2 筆存款,觀察者有 50% 機會猜對;如果有一萬筆存款,機會就降到萬分之一。

承諾列表的組織

每個 Privacy Pool 池(如 1 ETH 池、10 ETH 池)維護一個承諾列表:

# 每個存款產生一個承諾
C_i = Commit(m_i, r_i)
# 其中 m_i = 存款金額,r_i = 隨機盲目因子

# 承諾列表被組織成 Merkle 樹
# 根 R = MerkleRoot([C_0, C_1, ..., C_n])

# 存款時:
def deposit(amount: int, blinding_factor: int) -> bytes32:
    commitment = Pedersen.commit(amount, blinding_factor)
    merkle_tree.insert(commitment)
    return commitment  # 這是 note,用於之後提款

# 提款時:
def withdraw(commitment: bytes32, recipient: address, 
              nullifier: bytes32, proof: ZKProof):
    # 證明 commitment 在 Merkle 樹中
    assert verify_merkle_proof(commitment, merkle_root)
    
    # 防止雙花:nullifier 必須未被使用
    assert not nullifier_used(nullifier)
    
    # 零知識證明:證明我知道 commitment 對應的秘密
    assert verify_zk_proof(proof, public_inputs)
    
    # 標記 nullifier 為已使用
    mark_nullifier_used(nullifier)
    
    # 轉帳
    transfer(recipient, amount)

關聯性證明的推導

現在進入重頭戲:如何證明「我的提款來自某個合法集合,但不是某個非法集合」?

定義兩個集合

你要證明的是:C_withdrawal ∈ S_legit \ S_illicit

翻譯成人話:我的提款屬於合法集合,但不在黑名單上。

數學上怎麼做?

這要用到「集合成員證明」的技術。核心思想是:

證明分兩部分:

1. 集合成員證明(Set Membership Proof)
   證明 C ∈ S,其中 S = S_legit ∪ S_illicit
   
   方法:Merkle 包含證明
   - 計算從 C 到根的路徑
   - 證明路徑上的所有姐妹節點
   - 驗證根雜湊匹配

2. 非成員證明(Non-Membership Proof)
   證明 C ∉ S_illicit
   
   方法:利用 Merkle 樹的排序性質
   - S_illicit 是有序集合
   - 找到 C 在有序列表中的位置
   - 證明 C 在相鄰兩個元素之間

第一部分比較直觀,Merkle 樹大家都熟。第二部分稍微複雜一點,讓我詳細推導。

非成員證明的數學推導

非成員證明的關鍵是利用「有序 Merkle 樹」的特性。假設 S_illicit 是有序的,那麼對於任意承諾 C:

C 不是 S_illicit 的成員 ⟺ 
存在相鄰元素 L < C < R(L 是 C 左邊的元素,R 是 C 右邊的元素)

步驟 1:構造排序

把所有承諾按大小排序(這裡的「大小」是承諾作為數值的比較):

S_sorted = sort([C_0, C_1, C_2, ..., C_n])

排序的好處:相鄰承諾之間的間隙是「有序的」。如果 C 不在列表中,那它必然落在某兩個相鄰元素之間。

步驟 2:找到相鄰元素

這一步需要一個「承諾到索引」的映射(這是鏈下計算的):

def find_adjacent_commitments(C, sorted_list):
    # 二分搜索找位置
    left, right = 0, len(sorted_list)
    
    while left < right:
        mid = (left + right) // 2
        
        if sorted_list[mid] < C:
            left = mid + 1
        else:
            right = mid
    
    # left 就是 C 應該插入的位置
    # 相鄰元素是:
    left_neighbor = sorted_list[left - 1] if left > 0 else None
    right_neighbor = sorted_list[left] if left < len(sorted_list) else None
    
    return left_neighbor, right_neighbor

步驟 3:驗證區間

現在證明 C 在 L 和 R 之間:

def verify_range(C, L, R):
    assert L < C < R  # C 在 L 和 R 之間
    assert R < next(L)  # R 是 L 的後繼
    # 翻譯:沒有任何 S_illicit 的元素在 C 和 R 之間

問題來了:驗證者怎麼知道 L 和 R 是「真的」相鄰元素,而不是任意挑選的兩個數?

答案:Merkle 證明。你需要同時證明 L 和 R 都在 Merkle 樹中,而且它們真的是「相鄰」的。

步驟 4:多路徑證明

這是整個方案最優雅的部分。你可以構造一個「範圍證明」,同時包含:

1. L 的 Merkle 證明(證明 L 在承諾列表中)
2. R 的 Merkle 證明(證明 R 在承諾列表中)
3. C 的 Merkle 證明的一部分(證明 C 的存在性,但不暴露索引)

實際實現時,為了效率,通常會預先計算並存儲一些「批次證明」,而不是每次提款都重新計算整個 Merkle 路徑。

Aztec 的實際整合方案

說完理論,聊聊 Aztec 網路是怎麼把這套機制落地的。Aztec 是個超有意思的項目——它是第一個在以太坊上實現 zk-zk Rollup 的隱私網路,也就是說它有兩層零知識證明嵌套。

Aztec 的承諾架構

Aztec 用的是一個叫做「-notes」的系統,類似於 Privacy Pool 的 commitment,但有些關鍵差異:

Commitment vs Note:

Privacy Pool:
- Commitment = 存款承諾(公開寫入合約)
- Nullifier = 花的承諾的哈希(防止雙花)
- 驗證:鏈上合約驗證 Merkle 證明

Aztec:
- Note = 加密的 UTXO
- Note Commitment = 承諾的承諾(兩層嵌套)
- Nullifier = note 的哈希(也是防止雙花)
- 驗證:zkRollup 批次驗證(在 Layer 2 完成)

這種雙層嵌套的設計讓 Aztec 能支援更複雜的隱私場景——比如私密的 DeFi 操作、跨應用的資產轉移,甚至是有選擇性披露的證明。

Aztec 的隱私代幣標準

Aztec 定義了一個叫做 AZT20 的隱私代幣標準。跟前幾年流行的 ERC-20 相比,AZT20 的關鍵創新是:

// AZT20 的核心介面(簡化)
interface IAZT20 {
    // 存款:把公開代幣轉成隱私密鑰
    function deposit(
        address asset,      // 原始代幣地址
        uint256 amount,      // 數量
        bytes32 recipientKey // 接收者的隱私位址
    ) external payable;
    
    // 提款:把隱私密鑰轉成公開代幣
    function withdraw(
        bytes32 noteHash,   // 要提款的 note
        address recipient,   // 接收地址
        uint256 amount,     // 金額(可選揭露)
        bytes calldata proof // 零知識證明
    ) external;
    
    // 私密轉帳:只在 Aztec 內部進行
    function transfer(
        bytes32[] inputNotes,   // 花的 note
        bytes32[] outputNotes,  // 新建的 note
        bytes calldata proof
    ) external;
}

有趣的是,withdraw 函數有個 amount 參數可以選擇性揭露。如果你想低調,可以把 amount 設為零,這樣鏈上只會看到「有人提走了一筆款項」,但不知道多少錢。

Aztec 的合規框架

Aztec 的設計哲學是「協議中立」——它不內建任何黑名單機制,但提供了工具讓第三方去實現合規需求。

實務上常見的合規方案:

方案一:監管者揭密鑰

監管者持有一個特殊密鑰 K_supervisor

存款時:
- 用戶產生 note N
- 計算 reveal_hash = hash(N, K_supervisor)
- 將 reveal_hash 寫入一個「揭露預言機」合約

執法時:
- 監管者使用 K_supervisor 向揭露預言機證明身份
- 預言機返回 reveal_hash 對應的 note N
- 監管者就能追蹤這筆存款的所有流向

這個方案的優點是:不影響正常用戶的隱私,只有被監管的用戶才會被追蹤。

方案二:白名單存款池

創建兩個池:
- Public Pool:任何人都可以存款
- KYC Pool:只有通過 KYC 的地址可以存款

存款到 KYC Pool 時,需要附帶一個 ZK 證明:
「存款地址是一個通過 KYC 的白名單地址」

這樣就形成了一個「乾淨」的匿名集。

數學推導的實作細節

讓我深入一些具體的密碼學推導,這些在實際実装時很重要。

Kate 承諾與多項式

Aztec 使用的是 Kate 承諾(Kate commitments),而不是 Pedersen 承諾。Kate 承諾的優點是能支援「多項式」級別的承諾。

Kate 承諾的構造:

Setup:
- 選擇秘密值 τ(trust setup,CRS)
- 計算 [τ⁰]G₁, [τ¹]G₁, ..., [τᵈ]G₁  (G₁ 上的 powers of tau)
- 計算 [τ⁰]G₂, [τ¹]G₂, ..., [τ¹]G₂  (G₂ 上的 powers of tau)

承諾多項式 f(X) = Σ fᵢ Xⁱ:
Commit(f) = Σ fᵢ · [τⁱ]G₁ = [f(τ)]G₁

打開(證明 f(a) = v):
1. 計算商 q(X) = (f(X) - v) / (X - a)
2. 承諾 q:Proof = [q(τ)]G₁
3. 驗證者檢查:e(Proof, [τ]G₂) = e(Commit(f) - [v]G₁, [1]G₂)

為什麼叫「Kate」?這是發明者 Kate、Zaverucha 和 Goldberg 名字的縮寫。這個方案在 2010 年提出,現在被大量用在 zkRollup 項目中。

電路設計:約束系統

零知識證明的底層是一個「約束系統」(Constraint System)。你要證明的計算,會被轉換成一系列數學約束。

以「承諾在 Merkle 樹中」為例,約束系統包括:

// 約束 1:承諾是正確計算的
C = Pedersen.commit(value, randomness)

// 約束 2:承諾的路徑是正確的
path = merkle_path(hash(C), root)
for i in range(depth):
    if path_direction[i] == LEFT:
        assert hash(path_node[i], sibling) == parent
    else:
        assert hash(sibling, path_node[i]) == parent

// 約束 3:path_node[0] == C(路徑的起點是承諾本身)
assert path_node[0] == C

// 約束 4:最終的根匹配
assert path_node[depth] == root

每一個約束都是一個「二次約束」(Quadratic Constraint),這是 Plonk 或 R1CS 等常見 ZK 電路框架的基本單位。

Noir 語言的實現

Aztec 團隊開發的 Noir 語言讓這些約束的表達變得優雅:

// Merkle Membership 電路(Noir 代碼)
use dep::std;

// 承諾承諾的結構
struct CommitmentNote {
    value: Field,        // 存款金額
    randomness: Field,    // 盲目因子
    commitment: Field,    // 承諾值
}

// Merkle 路徑驗證
fn verify_merkle_path(
    leaf: Field,                    // 葉節點(承諾)
    path: [Field; DEPTH],           // 路徑上的所有節點
    directions: [bool; DEPTH],       // 每一步的方向
    root: Field                      // Merkle 根
) -> Field {
    let mut current = leaf;
    
    for i in 0..DEPTH {
        let (left, right) = if directions[i] {
            (path[i], current)
        } else {
            (current, path[i])
        };
        current = std::hash::pedersen_hash([left, right]);
    }
    
    assert(current == root);
    root
}

// 主電路
fn main(
    // 公開輸入
    root: Field,
    recipient: Field,
    nullifier_hash: Field,
    
    // 私密輸入(只有證明者知道)
    value: Field,
    randomness: Field,
    secret: Field,
    merkle_path: [Field; DEPTH],
    merkle_directions: [bool; DEPTH]
) -> pub Field {
    // 步驟 1:重新計算承諾
    let note_hash = std::hash::pedersen_hash([
        value,
        randomness,
        secret
    ]);
    
    // 步驟 2:驗證 Merkle 路徑
    let computed_root = verify_merkle_path(
        note_hash,
        merkle_path,
        merkle_directions,
        root
    );
    
    // 步驟 3:驗證 nullifier
    let computed_nullifier = std::hash::pedersen_hash([
        note_hash,
        secret
    ]);
    assert(computed_nullifier == nullifier_hash);
    
    // 步驟 4:輸出(這裡可以加入關聯性證明)
    // ...
    
    root
}

這段代碼表達的意思很清楚:驗證「我知道一個承諾,它在 Merkle 樹中,並且我可以計算它的 nullifier」。但這些「私密輸入」對任何人都是隱藏的。

關聯性證明的實際應用案例

說了這麼多數學,來點實際的。關聯性證明在以下場景特別有用:

場景 1:DeFi 隱私借貸

假設你想在 Aave 上借錢,但又不想暴露自己的資產組合。傳統方案行不通——Aave 需要知道你有多少抵押品才能決定借多少。

使用 Privacy Pool + 關聯性證明:

1. 把 ETH 存入 Privacy Pool 的「已認證存款池」
2. 從池中提款到一個臨時地址
3. 拿這個臨時地址的 ETH 去 Aave 做抵押
4. 借出穩定幣
5. 償還 Aave,把穩定幣提回 Privacy Pool
6. 從 Privacy Pool 提款到乾淨地址

監控機構看到的:
- 你的錢最終來自「已認證存款池」
- 「已認證存款池」包含很多 KYC 用戶的存款
- 他們只能推斷「這筆錢來自某個 KYC 用戶」
- 他們無法確定是哪一個

你自己能證明的:
- 你的提款不來自「黑名單池」
- 你的存款有 KYC 用戶的背書
- 但你不需要透露是哪個 KYC 用戶

場景 2:隱私 NFT 購買

NFT 購買有個隱私問題:你不想讓別人知道你買了某個昂貴的 NFT(會成為被盜目標),也不想讓對手知道你願意出多少錢。

使用 Aztec:

1. 在 Aztec 網路上存入 ETH
2. 通過私密交換把 ETH 換成 NFT 對應的代幣
3. 創建購買意願的零知識證明
4. NFT 賣家只知道「有人要買,價格符合預期」
5. 成交後 NFT 直接轉入你的 Aztec 隱私密鑰
6. 想揭露的時候可以選擇性揭露(用於展現收藏等)

場景 3:機構級別的 AML 合規

這是 Privacy Pool 最商業化的應用場景。傳統 AML(反洗錢)要求金融機構追蹤每一筆資金的來源。Privacy Pool 讓機構可以在保護客戶隱私的同時,仍然滿足監管要求:

銀行業務流程:

1. 客戶存款到銀行
2. 銀行把存款「映射」到 Privacy Pool 的白名單池
3. 客戶進行任意次私密交易
4. 客戶提款時:
   a. 向銀行提交「關聯性證明」
   b. 證明存款來自白名單池
   c. 證明存款不在黑名單池
5. 銀行驗證通過後,完成 AML 審查
6. 客戶可以把錢提到任何地方

監管機構看到的:
- 銀行的 KYC 流程是完整的
- 每筆提款都有「合規證明」
- 銀行無法看到客戶的內部交易細節

銀行看到的:
- 客戶的交易有 Privacy Pool 背書
- 客戶不在任何觀察名單上
- 但銀行也不知道客戶跟誰交易過

這種「三角合規」的設計,讓 Privacy Pool 能在隱私和監管之間找到平衡點。

安全性分析

這套機制真的安全嗎?讓我從幾個角度分析:

密碼學安全性

Pedersen 承諾的安全性:基於離散對數假設(DL),在量子電腦出現之前是安全的。但要注意的是,如果 trust setup 被破壞(有人知道了「陷阱門」),攻擊者可以構造假的承諾。不過現代的 MASP(Multi-Asset Shielded Pool)使用 powers of tau 的多方計算儀式,能把這個風險降到「需要所有參與者都作弊」的程度。

Kate 承諾的安全性:基於 q-SDH 假設(subgroup decision assumption)和配對加密的安全性。

經濟安全性

即使密碼學是安全的,經濟激勵機制也可能出問題:

潛在攻擊向量:

1. 匿名集污染攻擊
   - 攻擊者存入大量資金
   - 這些資金進入「乾淨池」
   - 稀釋了真正乾淨用戶的比例
   - 解決:需要經濟機制防止 Sybil 攻擊

2. 強制揭露威脅
   - 監管機構要求隱私服務提供商「後門」
   - 解決:協議設計上不允許後門,否則會被用戶發現

3. 預言機操縱
   - 關聯性證明依賴「黑名單」的準確性
   - 如果黑名單被污染,會冤枉好用戶
   - 解決:需要社群治理和上訴機制

實務安全建議

如果要部署基於關聯性證明的系統:

安全檢查清單:

□ 使用多方計算(MPC)trust setup
□ 定期更新電路代碼,修補漏洞
□ 實施 rate limiting,防止大規模關聯攻擊
□ 監控匿名集大小,過小時提醒用戶
□ 保持金鑰管理的最佳實踐
□ 定期第三方安全審計
□ 準備緊急熔斷機制

結語

Privacy Pool 和關聯性證明代表了一個重要的技術方向:如何在保護隱私的同時,又不犧牲監管合規的可能性。這個問題沒有完美的答案,但密碼學給了我們一個有力的工具箱。

我個人相信,未來的金融隱私會走向這種「可選擇性合規」的模式。不是一刀切的「完全隱私」或「完全透明」,而是讓用戶和機構能夠在這兩極之間找到適合自己的平衡點。

當然,這條路還很長。密碼學的突破、監管框架的演進、用户教育的普及——每一個環節都是挑戰。但看著 Aztec、Privacy Pool 這些項目從概念走向實際部署,我對這個未來越來越有信心了。

COMMIT: Add Privacy Pool Association Proof mathematical derivation with Aztec integration guide

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!