JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

混幣器與 CoinJoin:斬斷硬幣之間的關聯

鏈上分析師靠把你的硬幣串連在一起取勝。混幣器則反擊:斬斷你投入的硬幣與取出的硬幣之間那條線——透過協作式的 CoinJoin 交易、託管型混幣服務,以及像 Tornado Cash 這樣的零知識池。本篇將精確示範一個匿名集如何藏住一筆提領,以及這些工具為何與制裁法正面相撞。

斬斷關聯,而非把硬幣藏起來

上一篇留給你一幅發人深省的景象:鏈上分析師用共同輸入所有權與找零偵測等啟發式,把你的地址分群,然後在其中某個地址碰到 KYC 交易所的那一刻,把它釘到你的真名上。混幣器是第一招反擊,而我們最好先精確說清楚它攻擊的是什麼。混幣器並不隱藏一枚硬幣的存在,(多半也)不隱藏它的金額。它攻擊的是鏈上分析師手中最有價值的東西:那條把你存入的硬幣,連到你日後取出的硬幣的。斬斷這條線,整個分群就瓦解了。

一個好用的心智模型是擁擠車站裡的寄物處。你交進一張 20 美元鈔票;其他數百人也把自己的 20 美元鈔票交進同一個抽屜;稍後你領回一張 20 美元鈔票——而不是你那張 20 美元。你拿回了等值的錢,但沒有任何旁觀者能把你走出去時那張鈔票,配對到你走進來時那張。所有可能是你那張鈔票來源的那群人,就是你的匿名集(anonymity set),而它的大小是決定你被藏得多好的唯一數字。本篇的每一種設計,骨子裡都只是用不同的機器來打造這一群人。

我們會依「信任最小化」程度由低到高,看三種風味。CoinJoin 完全不用託管方——彼此獨立的使用者協作出一筆共用交易。託管型混幣服務是老派又方便的做法——你信任一位營運者,由他把大家的硬幣匯成一池,再用不相干的硬幣付還給你。而像 Tornado Cash 這樣的零知識池,則用一份智慧合約加上一個零知識證明徹底取代營運者,於是你只需信任程式碼與數學。接著,我們會直面任何巧思都抹不掉的難題:這些工具仍會洩漏的中繼資料,以及它們所招來的制裁。

CoinJoin:多個錢包,一筆交易

CoinJoin 由比特幣開發者 Gregory Maxwell 在 2013 年提出,是本篇最優雅的構想。回想共同輸入所有權啟發式:當一筆交易花掉地址 A 和 B 的輸入時,分析師會假設同一個錢包擁有兩者,因為一般來說,你必須用每一個輸入的私鑰簽名。CoinJoin 把這個例外武器化了。數位彼此獨立的使用者,共同建造一筆交易——各自貢獻自己的輸入、各自只簽自己的那一份。如今「一起簽名」不再等於「同一持有人」,這條啟發式卻仍信心十足地把陌生人併成一個虛構實體。鏈上分析師最可靠的規則,就此被毒化。

魔法成分是等額輸出。如果 Alice 投入 1.013、收到 0.9,而 Bob 投入 1.020、收到 1.1,旁觀者仍能靠金額把他們重新連起來。所以一個好的 CoinJoin 會付給每位參與者一筆完全相同的輸出——比如各自恰好 1.0 BTC——並把任何餘額另外當作找零退回。有了三筆無法區分的 1.0 輸出,把輸入對應到輸出的方式有 3! = 6 種,旁觀者根本分不出哪個輸入注資了哪個輸出。這些等額輸出就是匿名集:一輪裡等額輸出的同伴愈多,這群人就愈大。

// A 3-party equal-output CoinJoin (Bitcoin), as a block explorer shows it.
// Alice, Bob and Carol each put in one input and each take one 1.0-BTC
// output. The 1.0 outputs are IDENTICAL, so no one can match a given
// input to a given output.
{
  "txid": "c01n...j01n",
  "vin": [
    { "addr": "bc1q...ALICE", "value": 1.013 },   // signed only by Alice
    { "addr": "bc1q...BOB",   "value": 1.020 },   // signed only by Bob
    { "addr": "bc1q...CAROL", "value": 1.008 }    // signed only by Carol
  ],
  "vout": [
    { "addr": "bc1q...OUT1", "value": 1.000 },   // whose? unknowable
    { "addr": "bc1q...OUT2", "value": 1.000 },
    { "addr": "bc1q...OUT3", "value": 1.000 },
    { "addr": "bc1q...CHG_A", "value": 0.012 },  // each gets change back
    { "addr": "bc1q...CHG_B", "value": 0.019 },  // to a SEPARATE output
    { "addr": "bc1q...CHG_C", "value": 0.007 }
  ]
  // fee = (1.013+1.020+1.008) - (3*1.000 + 0.012+0.019+0.007) = 0.003
}

// common-input-ownership now FAILS: three different wallets signed, not one.
// And the three equal 1.0 outputs are interchangeable: 3! = 6 possible maps.
一筆交易、三位互不相干的簽署者、三筆相同的輸出——啟發式把陌生人錯標成同一個持有人。

關鍵在於 CoinJoin 是非託管的:沒有任何人會持有別人的硬幣。這筆交易只有在每個人都簽名時才有效,而每個人都只在確認自己那筆等額輸出存在且無誤後才簽。因此一個惡意的協調者最多只能拒絕繼續——絕無法偷走任何東西。協調者(或像 JoinMarket 這樣的去中心化配對協定)只是幫陌生人彼此找到對方、並組出未簽名的範本;安全性來自簽名,而非來自信任這位媒人。Wasabi(採用 WabiSabi 協定)與 Samourai 的 Whirlpool,是最知名的消費者級實作。

為什麼一輪不夠?一筆只有三位同伴的 CoinJoin,匿名集僅約 3。真正的隱私來自多輪、多同伴地反覆重新混合,使每一枚硬幣的可能來源集合跨輪疊加放大。但每一輪都要付手續費、都會洩漏時間點,而找零輸出正是弱點:如果你日後在同一筆交易中,把剛混好的 1.0 輸出連同它自己的找零一起花掉,共同輸入啟發式就會把它們重新併在一起,足以拆穿整場混合。好的錢包之所以把找零隔離到不同帳戶,正是為了阻止這件事。

託管型混幣服務:方便,卻是一場信任的賭注

最古老、最簡單的混幣器是託管型混幣服務加密貨幣翻滾器)。你把硬幣送到營運者的地址;他們把你的硬幣丟進一個由眾多使用者匯入的大池,等上一段時間,再把同樣的金額減去手續費付還給你——但用的是不相干的硬幣,送到一個你掌控的全新地址。在鏈上,你的存入與你的提領沒有可追蹤的關聯,因為營運者的池子像一道簾幕橫在兩者之間。

陷阱在於全然的託管信任,而它有三面刃。(1) 盜竊。 在存入與付還之間,營運者實實在在握著你的硬幣,他大可直接把它們留下——這就是經典的捲款跑路(exit scam)。(2) 紀錄。 他們能精確記下哪一筆存入對應哪一筆付還;若他們保留這份私帳,你的隱私距離一張傳票、一次入侵,或一名心懷不滿的內部人員,都只剩一步之遙,便可全面崩盤。(3) 污染。 池中可能含有犯罪所得,於是你可能收到被污點分析標記的「髒」幣,並在你試圖把它存進交易所的那一刻被凍結。你信任的是一個陌生人的誠實、他的營運安全,以及一個你無法檢視的池子的乾淨程度。

歷史在這裡並不寬厚。Helix 與 Bitcoin Fog 的營運者遭到起訴並因洗錢定罪,調查人員在數起案件中證明,這些服務確實保留了紀錄,反而幫忙把使用者去匿名化——與顧客付錢買的東西恰恰相反。ChipMixer 則在 2023 年被歐洲刑警組織查封。教訓是結構性的:託管型混幣器用一個單一信任點單一故障點,取代了鏈的透明性——而那正是區塊鏈當初被發明出來要消除的東西。

零知識池:證明成員身分,卻什麼都不洩漏

最巧妙的混幣器設計是零知識隱私池,因 以太坊上的 Tornado Cash 而聲名大噪。它像 CoinJoin 一樣非託管,但不是一筆共用交易,而是用兩個分離、無法關聯的步驟——現在存入、很久以後再提領——並由一個零知識證明把兩者橋接起來。這個構造重複使用了 Merkle 樹承諾作廢符這套機制,你會在緊接著的下一篇談 Zcash 時再次遇到它們。

存入。 池子採用固定面額——Tornado Cash 分別設有 0.1、1、10、100 ETH 的池——因此存進同一個池的每一筆存款都長得一模一樣。要存入時,你的錢包產生兩個秘密隨機數:一個秘密值(secret)與一個作廢符(nullifier),並只把它們的雜湊公布出去:一個承諾。合約把這個承諾當成一片葉子附加到 Merkle 樹上,並託管恰好一個面額的 ETH。鏈上只會知道發生了一筆固定大小的存款;承諾本身不洩漏任何可關聯到「是誰、或將來哪一筆提領」的資訊。

提領。 稍後——最好是很久以後,從一個沒有其他歷史的全新地址——你提領時提交一個 zk-SNARK,它一次證明三件事:你知道某個 `(秘密值, 作廢符)` 配對,其承諾是當前 Merkle 根之下的某一片葉子;而那個作廢符雜湊出一個你公布的標記。這個證明從不說明是哪一片葉子。合約驗證證明、檢查所揭露的作廢符雜湊先前從未被使用過(藉此擋下同一筆存款的任何第二次提領),把它記錄下來,並把一個面額釋放給你指定的任意接收者。

# A zero-knowledge privacy pool (Tornado-Cash-style), denomination = 1 ETH.
#
# DEPOSIT  (public on chain):
#   secret, nullifier = random(), random()    # kept PRIVATE by the depositor
#   commitment = hash(nullifier, secret)      # the only thing posted
#   tree.insert(commitment)                    # appended as a Merkle leaf
#   contract escrows exactly 1 ETH
#
# WITHDRAW  (public on chain) -- to a FRESH recipient address:
#   PUBLIC inputs:  root           # current Merkle root over all commitments
#                   nullifierHash  # = hash(nullifier)
#                   recipient      # any address you choose
#   PRIVATE witness: secret, nullifier, merklePath
#
#   the zk-SNARK proves, revealing nothing else:
#     1. commitment == hash(nullifier, secret)            # well-formed note
#     2. MerkleVerify(merklePath, commitment) == root     # it IS in the pool
#     3. nullifierHash == hash(nullifier)                 # the right one-time tag
#
#   contract: verify(proof) AND nullifierHash is UNUSED
#             -> mark nullifierHash spent, send 1 ETH to recipient
#
# The proof never reveals WHICH leaf, so the withdrawal could match ANY
# deposit in the tree. That set of deposits is your anonymity set.
存入一個承諾;用一個成員身分證明加上一次性的作廢符提領——沒有營運者,沒有關聯。

有兩個實務細節讓它行得通。第一,中繼者(relayer)。一個全新的提領地址沒有 ETH 可付 gas,而從你自己的錢包替它注資,會立刻把你重新連回那筆提領。於是一位第三方中繼者代你廣播提領交易,並從提領金額中扣取它的費用,讓接收地址保持潔淨無瑕。第二,作廢符是讓一個公開、匿名的池能在永遠不知道你是誰的情況下,仍防止雙重花費的訣竅:它是由你的存款確定性推導出的一次性標記,恰好只能用一次。把它揭露出來你就能提領;而合約只要見過某個作廢符雜湊,便一律拒絕它再次出現。

把匿名集講具體

上述一切,買到的恰恰只有一樣東西:一個匿名集——旁觀者必須將之視為你提領之可能來源的那組存款。集合愈大,你被藏得愈好,而且我們可以給它一個數字。若有 `k` 筆無法區分的存款,每一筆都可能是你提領的資金來源,那麼旁觀者單次最佳猜測的正確機率是 `1/k`。集合為 2 幾乎毫無價值;集合為 10,000 則確實強健。讓我們把一筆提領,走過一個真實大小的池子。

  1. 起點。 一個 1-ETH 的零知識池已經持有 5,000 筆尚未被提領的存款。你存入 1 ETH;這棵樹現在多了第 5,001 個承諾,逐位元組地與其餘的毫無分別。
  2. 等待。 在接下來兩週裡,又有數百人存入與提領,攪動著這個池子。關鍵是,這段期間你完全不去動你的存款——耐心,正是用來壯大這群人的東西。
  3. 提領。 你提交你的 zk-SNARK,1 ETH 透過一位中繼者落到一個全新地址。合約發出一筆提領事件,但證明隱藏了它消耗的是那數千個承諾中的哪一個。
  4. 旁觀者的難題。 你的 1 ETH 可能來自那一刻仍未被提領的任何一筆存款——比方說約 5,200 筆。你的匿名集約為 5,200,因此對來源的天真猜測只有約 0.02% 的機率猜中。這份「幾乎不可能猜中」,正是一個大型、繁忙的池子替你買到的隱私。

現在來到誠實的部分——這個集合如何悄悄縮小。(1) 金額。 只有同面額的存款才算候選;存入一個古怪的金額,或以奇怪的方式拆分,你就把自己從人群中切了出來。(2) 時間點。 存入與提領只隔幾秒,分析師就能直接把它們配對;你真正的集合只是「在你提領的時刻合理存在的存款」,而非這個池子歷來的總和。(3) 連通分量攻擊。 如果你從地址 X 存入、提領到地址 Y,之後又把 Y 的資金送回 X——或送到一個已與 X 關聯的交易所——你就把兩端都交到了分析師手上,把自己的匿名集塌縮成 1(4) 指紋。 重複使用的中繼者、與眾不同的 gas 設定,以及不尋常的整數金額,全都會洩漏。現實中混幣器多數的去匿名化,肇因於這些使用者的疏失,而非密碼學被攻破。

制裁、法庭,與尋找一條中間道路

一個斬斷鏈上關聯的工具,對保護敏感捐款的吹哨者、對只是不想讓薪水或淨資產公開的普通人,以及對洗白贓款的竊賊,同樣有用。監管者聚焦於最後一種。2022 年 8 月 8 日,美國財政部外國資產控制辦公室(OFAC)把 Tornado Cash 加進其 SDN 制裁清單——這是史上頭一遭,被制裁的是自主運行的軟體,而非某個人或某家公司。OFAC 表示該協定曾被用來洗錢逾 70 億美元,其中包含為北韓 Lazarus 集團洗的數億美元。一夕之間,與這些合約互動對美國人而言在法律上變得棘手,前端介面、中繼者,乃至程式碼倉庫都隨之消失。

法律上的餘震既嚴重又仍在發展。在荷蘭,開發者 Alexey Pertsev 於 2024 年 5 月因洗錢罪定罪,判處五年以上徒刑。在美國,司法部於 2023 年起訴了共同創辦人 Roman StormRoman Semenov,Storm 的審判在 2025 年進行。這些案件底下,躺著一個整個產業都在關注、尚未解決的問題:撰寫並部署中立、不可竄改的隱私軟體,本身是否就是犯罪——抑或它是受保護的工具與言論,只是某些使用者後來加以濫用?

制裁本身則被部分撤回。在 Van Loon 訴財政部案(2024 年 11 月)中,美國第五巡迴上訴法院裁定,Tornado Cash 的不可竄改智慧合約並非任何人能擁有或控制的「財產」,因此 OFAC 制裁它們已逾越其法定權限。2025 年 3 月,OFAC 正式將 Tornado Cash 移出清單。然而,針對特定個人的刑事案件仍在繼續——刻意在程式碼(法院認定其無法被單純當作財產來制裁)與特定人士的行為(被控明知故犯地經營洗錢服務)之間,畫下一條界線。

這場碰撞中,誕生了一個對中間道路的嚴肅嘗試:隱私池(Privacy Pools),出自 2023 年一篇由 Vitalik Buterin 等人共同撰寫的論文。其構想是選擇性、自願性的合規。當你提領時,你的零知識證明額外證明:你的存款屬於一個由可證明為正當之存款所組成的關聯集(association set)——等價地說,屬於一份已公布的、已知非法存款的集合——而這一切都不揭露究竟哪一筆存款才是你的。誠實的使用者能以密碼學方式,把自己與犯罪資金切割開來,同時讓自身隱私完好如初;池子不再是全有或全無。這份清白證明的精神,與你將在本階最後一篇談隱私對上法遵時遇到的檢視金鑰與選擇性揭露,如出一轍。