每筆付款洩漏的三個秘密
想像你買了一杯咖啡,而收據不是塞進你的口袋,而是永遠刻在城鎮廣場的一塊玻璃牌匾上。它寫著你的名字、咖啡店的名字,並印上精確的金額。任何路過的人,無論是今天還是五十年後,都能讀到它。這就是比特幣或以太坊這類透明鏈上的付款的真實樣貌。在本階的第一篇你看到,地址只是可被鏈分析揭穿的化名;第二篇你看到,混幣器與 CoinJoin 能模糊硬幣之間的關聯。隱私幣走得更遠:它們在協議層直接堵住洩漏。
任何付款都會暴露三項不同的事實,而一個私密系統必須把它們全部隱藏:發送者(花掉了哪些幣、由誰花的)、接收者(誰收到錢)以及金額。上一篇的混幣器主要處理發送者與接收者之間的關聯,卻把金額留在明面上——而金額往往足以把軌跡重新縫合起來。要獲得真正的隱私,你需要一種能一次隱藏這三者的設計。
有兩種設計領先群雄,而它們體現了相反的哲學。門羅幣讓隱私強制且永遠開啟:每一筆交易都隱藏這三項事實,因此沒有一群透明交易讓你被反襯出來。Zcash 保留一條透明鏈,但加上一個可選的屏蔽池供你選用,在池中由一個zk-SNARK證明一筆付款有效,卻什麼都不揭露。本篇接下來就拆開這兩台機器。
門羅幣之一——用隱形地址隱藏接收者
先從接收者談起。在比特幣上,如果你公布一個地址、人們在那裡付款給你,每一筆付款都落進同一個可見的桶裡——你的全部收入就是一個叢集。門羅幣用隱形地址解決這點:接收者公布一個長期地址,但由發送者為每一筆付款各自推導出一個全新、無法關聯的一次性公鑰。給你的任何兩筆付款都不共用同一個鏈上地址,而且只有你能認出哪些輸出是你的。
訣竅是用橢圓曲線金鑰建立的共享秘密。你的錢包地址其實是兩把公鑰:一把檢視金鑰 `A = a·G` 和一把消費金鑰 `B = b·G`。發送者挑一個隨機純量 `r`,把 `R = r·G` 隨交易公布,並從共享秘密 `r·A` 的雜湊算出一次性輸出金鑰。因為 `a·R = a·(r·G) = r·(a·G) = r·A`,持有私有檢視金鑰 `a` 的你,能重算出完全相同的秘密並認出這筆付款。其他任何人都做不到。
# Monero one-time (stealth) address — sender pays recipient (A, B) # A = a*G public view key a = private view key (recipient) # B = b*G public spend key b = private spend key (recipient) r = random_scalar() # fresh per output, chosen by the SENDER R = r*G # published with the transaction P = Hs(r*A)*G + B # the ONE-TIME output public key written on chain # Recipient scans every output, recomputing with the private view key a: # P' = Hs(a*R)*G + B since a*R = a*r*G = r*(a*G) = r*A -> P' == P # Only someone who knows a can recognise the payment as theirs. # To spend it, the recipient forms the one-time private key: # x = Hs(a*R) + b and P = x*G holds, so x signs for P # key image I = x * Hp(P) is published to stop double-spends (see next section)
門羅幣之二——隱藏發送者(環)與金額(RingCT)
現在談發送者。一個普通的簽章說的是「這枚特定硬幣的擁有者授權了這次花費」。而環簽章說的是更弱、卻私密得多的話:*「這 N 枚硬幣中有一枚的擁有者授權了它——但我不會告訴你是哪一枚。」*門羅幣把每個輸入組成一個環:真正的硬幣加上誘餌(取自過往交易的誘餌輸出)。自 2020 年起每個環都固定為大小 16(1 真 + 15 假),讓所有交易看起來都一樣。簽署者證明自己屬於環中,卻不揭露位置。
如果你連是哪枚硬幣都看不到,怎麼阻止有人把同一枚硬幣花兩次?每個環簽章都會公布一個金鑰映像 `I = x·Hp(P)`——一個由真實硬幣私鑰唯一且確定性推導出的值。再花同一枚硬幣,就會出現相同的金鑰映像,於是網路拒絕它。金鑰映像在雙花之間把一枚硬幣連到它自己,卻不揭露它來自環中的哪一個成員。
最後是金額。門羅幣用 RingCT(環機密交易)取代可見的數字。每個金額 `b` 都藏在一個 Pedersen 承諾 `C = a·G + b·H` 之中,其中 `a` 是隨機盲化因子,`H` 是第二個生成元,它對 `G` 的離散對數關係未知。承諾具有同態性:它們可以相加。一筆交易只有在「輸入承諾減去輸出承諾再減去手續費等於零」時才有效——這證明沒有憑空鑄造硬幣,而每個個別金額仍然封存。
# Monero RingCT — hide each amount in a Pedersen commitment # G, H : two curve points with UNKNOWN discrete-log relation (H = hash_to_point(G)) # a : blinding factor (random, secret) # b : the amount (secret) C = a*G + b*H # the commitment reveals nothing about b # Homomorphic balance check — a transaction is accepted only if: # sum(C_in) - sum(C_out) - fee*H == 0 # i.e. inputs and outputs (plus fee) cancel: no inflation, yet amounts stay hidden. # But a hidden b could be NEGATIVE -> mint money. So each output also carries a # range proof showing 0 <= b < 2^64 without revealing b. # Monero's Bulletproofs (Oct 2018) shrank these proofs ~80%, cutting typical fees ~95%.
Zcash——一個屏蔽池,一個零知識花費
Zcash 走的是完全不同的路線。它不是把可見硬幣與誘餌混在一起,而是維護單一個加密的屏蔽池。你的錢以票據(note)的形式存在那裡——一個秘密三元組 `(金額, 接收者, 隨機數)`。當一張票據被建立時,只有對它的一個簡短承諾被附加到一棵全域的 Merkle 樹上;票據內容則加密給接收者。鏈上儲存的是承諾,而非票據明文。
要花費時,你公布一個 zk-SNARK——一個微小的證明(幾百位元組),可在毫秒內驗證,卻除了「這次花費有效」之外什麼都不揭露。這個證明一次性地擔保一整捆事實。關鍵在於它只揭露一個新的公開值:一個 作廢符(nullifier),一個由所花票據確定性推導出的標記。公布作廢符讓網路能拒絕同一張票據的第二次花費,卻不揭露它是樹中的哪一張票據。這一步同時藏住了發送者、接收者與金額。
// Zcash shielded SPEND — what the zk-SNARK proves (Sapling, simplified) // // PUBLIC inputs (on chain, everyone sees): // rt : current root of the note-commitment Merkle tree // nf : nullifier of the note being spent // cv : a Pedersen value commitment that hides the amount // // PRIVATE witness (never revealed): // note : (value v, recipient pk, randomness rcm) // path : Merkle path proving cm = NoteCommit(note) sits under root rt // ask : spend-authority key that owns the note // // The circuit checks ALL of, simultaneously: // 1. cm == NoteCommit(v, pk, rcm) // the note is well formed // 2. MerkleVerify(path, cm) == rt // it really exists in the pool // 3. nf == PRF(ask, note_position) // nullifier is the unique right one // 4. cv == v*G + rcv*H // value commitment matches v // 5. ask authorises spending pk // you actually own it // // Reveal only rt, nf, cv. Across a transaction, sum of input cv minus output cv // must commit to zero -> value balances -> no inflation, all amounts hidden.
因為證明不揭露是哪一張票據,你的發送者匿名集就是整個屏蔽池——潛在地包含有史以來建立的每一張屏蔽票據,而非一個大小 16 的環。Zcash 地址有兩種:透明的 t-地址(就像比特幣)與屏蔽的 z-地址。其證明系統迅速成熟:2018 年的 Sapling 升級把緩慢的原始電路換成 Groth16 證明,使生成時間從數十秒與數 GB 記憶體降到手機上約一秒;2022 年的 Orchard(Halo 2)升級走得更遠,這點我們下一節再談。
可信設置、有毒廢料,與一次真實的偽造驚魂
Zcash 最早的 SNARK 需要一個可信設置:一次性的儀式,從秘密隨機值產生公開的證明參數。那些秘密——綽號有毒廢料——必須被銷毀。如果多方儀式中哪怕只有一位參與者偷偷保留自己的份額,確切的危險是:他們可以偽造證明、憑空假造硬幣。但他們無法做到的是破壞任何人的隱私。所以設置出包的最壞情況是悄無聲息的通膨,而非去匿名化。
這並非假想。2018 年,密碼學家 Ariel Gabizon 發現了一個真實的缺陷——不在儀式中,而在 Zcash 最初使用的 *BCTV14* 證明系統裡——它本可允許無限量、無法察覺地偽造屏蔽的 ZEC。這個缺陷由 Sapling 升級悄悄修復,並於 2019 年 2 月公開揭露;團隊未找到它曾被利用的證據,而且重要的是,這個漏洞從未威脅到屏蔽用戶的隱私。這是一個發人深省的教訓:新穎的密碼學帶來新穎、有時看不見的風險。
對可信設置問題有兩種值得了解的回應。第一,Zcash 2022 年的 Orchard 升級採用了 Halo 2,一個完全不需要可信設置的遞迴證明系統——為新的屏蔽價值消除了有毒廢料的風險。第二,門羅幣一開始就不需要可信設置:它的 Pedersen 承諾與範圍證明(現代門羅幣使用 Bulletproofs)只依賴標準的離散對數假設。這是門羅幣一項真正的架構優勢——但代價,如我們所見,是每次花費小得多的匿名集。
兩種哲學,兩種取捨
退一步看,這個選擇其實是強制對上可選的隱私之爭,而它有兩面刃。
- 匿名集。 門羅幣的匿名集受環的大小所限(目前 16)但是均勻的——人人都私密,因此沒有一群透明交易可供篩除。Zcash 的屏蔽集可以大到天文數字,但前提是人們真的去使用它。
- 可選隱私的陷阱。 因為 Zcash 的隱私是選擇加入的,多年來大多數 ZEC 留在透明的 t-地址裡,使實際的屏蔽集遠小於密碼學所允許的——而一筆從 t-地址跳進池中再跳出來的轉帳,可能洩漏它的兩端。強制隱私則完全避免了這種「誰才會費心去屏蔽?」的指紋。
- 可替代性。 強制隱私讓每一枚幣都有同樣空白的歷史,因此沒有任何一枚幣會被「污染」或列入黑名單——這是現金擁有、而透明加密貨幣缺乏的性質。可選隱私意味著透明的幣仍可能被污染,削弱了整條鏈整體的可替代性。
- 法遵與揭露。 Zcash 的選擇加入設計天然地與檢視金鑰相配:你可以把一把能解密你屏蔽活動的金鑰交給稽核員或交易所,選擇性地證明合規而不必公開。門羅幣的永遠開啟模式也提供檢視金鑰,但它全有或全無的立場讓交易所上架更困難。這種法遵張力正是本階最後一篇的全部主題。
- 易用性與成本。 門羅幣交易較大(一個環加上範圍證明)但不需要特殊硬體。Zcash 屏蔽證明驗證起來很小,但歷史上生成起來很重;Sapling 讓在手機上屏蔽變得可行。兩者對用戶的要求都比透明鏈更高。
你現在已經看過隱私如何套用於付款。下一篇會把同樣的 零知識機器推得更遠——進入可程式化的隱私,讓智慧合約的狀態與餘額保持隱藏,而規則仍可被證明地強制執行。你剛在 Zcash 中認識的作廢符概念,正是那整套構造的種子。