從洞穴謎題到一門產業
在本階的第一篇裡,你用一座洞穴和一次擲硬幣證明了自己知道某個祕密,那感覺像是個小把戲。但同樣那三個性質——完備性、健全性與零知識——如今已撐起了數十億美元的價值。請記住這幅畫面:一份大約 200 位元組的零知識證明,就能讓以太坊相信另一條鏈正確執行了一百萬筆它從未看過的交易,而這道驗證無論那段運算多龐大,都只花幾十萬 gas。這已經不是謎題了,這是基礎設施。
零知識在正式環境裡所做的一切,可歸為三大家族,而看清每一族裡是證明的「哪個性質」在出力,很有幫助。擴容:證明一段運算正確完成,好讓別人不必重算一遍——這仰賴簡潔性(一份小而能快速驗證的證明),驅動了 zkEVM 與 zk-Rollup。隱私:證明某個陳述為真,卻隱藏讓它為真的那些輸入——這仰賴零知識性質,驅動了屏蔽支付與私密合約。身分與成員資格:證明關於你自己的某個事實——你屬於某個集合、你已年滿 18 歲——卻不透露你是哪一個人。前三篇裡那套相同的電路與證明系統,服務了這全部三者。
zkEVM:證明世界電腦確實算對了
回想一下擴容那一階:zk-Rollup 在鏈下執行交易,接著張貼一份簡潔證明,表明新的狀態根確實由舊的狀態根正確推導而來,而以太坊作為結算層能廉價地驗證它。zkEVM 就是讓這套機制能套用到任意智慧合約上的那個電路:它證明EVM 本身——每一個操作碼、每一次儲存讀寫——都完全照協定所規定的方式執行了。
為什麼這很難?因為一份證明需要把整段運算表達成有限體上的算術約束,而 EVM 從來就不是為此設計的。最致命的是雜湊:以太坊到處用到的 Keccak-256 是一套逐位元的演算法,而位元運算用體上算術來表達極其痛苦——單單一次 Keccak 就可能要花上以萬計的約束,而一個區塊要做上千次。針對 Merkle-Patricia trie 的儲存證明同樣殘酷。zkEVM 工程裡有極大一部分,就只是在想辦法讓這些基本元件能以可忍受的代價被證明。
Vitalik Buterin 著名的 Type 1–4 分類法把這個取捨整理得很清楚。*Type 1* 與以太坊完全等價——它原封不動地證明主網區塊,相容性是黃金標準,但證明起來最慢也最貴(Taiko 朝這裡努力)。*Type 2* 與 EVM 等價(行為相同,內部略有差異)。*Type 3* 與 EVM 幾乎等價——少數預編譯或操作碼有所不同,以減輕證明負擔(早期的 Scroll 與 Polygon zkEVM 屬於此類)。*Type 4* 則把高階的 Solidity 編譯成一台對 ZK 友善的虛擬機,而不去比對 EVM 位元組碼——證明最快,但既有的位元組碼與工具未必能直接沿用(zkSync Era,以及透過 Cairo 虛擬機的 Starknet)。原則是:號碼越低 = 相容性越高,但證明越慢、越貴。
// --- Off-chain: the rollup's prover ---
batch = orderTransactions(mempool)
preRoot = currentStateRoot
postRoot = executeEVM(batch, preRoot) // actually run every opcode
proof = prove(
circuit = "correct EVM execution",
public = { preRoot, postRoot, dataHash(batch) }, // what L1 will check
witness = { batch, every intermediate stack / memory / storage state }
)
postToL1(preRoot, postRoot, dataHash(batch), proof)
// --- On-chain: the L1 verifier contract (cheap, ~constant cost) ---
function submitBatch(pre, post, dataHash, proof) {
require(pre == stateRoot, "wrong starting state");
require(verifier.verify(proof, [pre, post, dataHash]), "invalid proof");
stateRoot = post; // finalized at once — no 7-day challenge window
}讓「證明整台 EVM」這件事終於變得可行的關鍵,是 遞迴證明:把工作切成小塊、為每一塊產生證明,再產生一份外層陳述去證明「這些內層證明全都通過驗證」。摺疊(folding)與聚合方案(Plonky2、Nova 式摺疊)能把上千份證明壓縮成一份要上鏈的小證明,並讓許多機器平行地產生證明。但誠實的取捨依然存在:產生證明既昂貴又吃硬體——每一批要花上幾分鐘的 GPU 或叢集運算——而在 L1 上的驗證則便宜且大致為常數成本。zk-Rollup 是用一台沉重的證明者,換來快速的最終性與無須信任的提款。
證明你是其中一員,卻不說出你是誰
現在切換到零知識性質。私密身分的主力模式是集合成員資格。建一棵 Merkle 樹——一個密碼學累加器——每位成員一片葉子,每片葉子是對該成員某個祕密的承諾值。對外只公布單一的 Merkle 根。要行動時,你用零知識證明:「我知道某個祕密,它的承諾值是這個根之下的某一片葉子」——卻不透露是哪一片。驗證者只知道你是合法成員,除此之外一無所知。這正是密碼學那一階的 Merkle 樹,如今被當成隱私的基本元件來用。
但匿名帶來一個問題:什麼能阻止你行動兩次?答案是 作廢符(nullifier)。它是一個由你的祕密(加上一個主題識別碼)決定性地推導出來的值,你每次行動時都把它公開揭露。它無法被連結回你的身分——沒人能分辨是哪位成員產生了它——但它對每個(成員,主題)組合是唯一的,因此合約只要拒絕一個已經看過的作廢符即可。這個單一構想,正是 Tornado Cash 提款與 Semaphore 協定如何在保有匿名的同時防止雙重花費與重複表態的祕訣。
// Proves: "I am a registered member, and here is my one-time tag for this topic." // Private witness : secret, pathElements[], pathIndices[] // Public statement : root, externalNullifier, signalHash // Public output : nullifierHash commitment = Poseidon(secret) // my leaf; Poseidon = ZK-friendly hash computedRoot = MerkleRoot(commitment, pathElements, pathIndices) assert(computedRoot == root) // (1) membership, leaf stays hidden nullifierHash = Poseidon(secret, externalNullifier) // (2) unique + unlinkable tag signalHashSquared = signalHash * signalHash // (3) bind the message into the proof // ^ a dummy constraint so the proof commits to signalHash and can't be // replayed to carry a different vote/message
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IZKVerifier {
function verifyProof(uint256[8] calldata proof, uint256[4] calldata input)
external view returns (bool);
}
contract AnonymousVote {
IZKVerifier public verifier;
uint256 public immutable externalNullifier; // = id of THIS poll
mapping(uint256 => bool) public nullifierUsed; // stops double-voting
mapping(uint256 => uint256) public tally; // vote => count
function castVote(
uint256 root, // Merkle root of eligible voters
uint256 nullifierHash, // anonymous, one-per-voter tag
uint256 vote, // the signal, e.g. 0 = no, 1 = yes
uint256[8] calldata proof
) external {
require(isKnownRoot(root), "unknown member set");
require(!nullifierUsed[nullifierHash], "already voted");
require(
verifier.verifyProof(
proof,
[root, nullifierHash, uint256(keccak256(abi.encode(vote))), externalNullifier]
),
"invalid proof"
);
nullifierUsed[nullifierHash] = true; // voter stays anonymous; the vote counts once
tally[vote] += 1;
}
}這些都是真實的部署。Semaphore 是一套建構在這個電路上的通用匿名表態工具。Worldcoin 的 World ID 在以虹膜驗證過的真人之上使用 Semaphore,讓你能匿名地證明「我是一個獨一無二的人」。一波zk 護照專案則在電路內部驗證政府對你護照 NFC 晶片上的數位簽章,讓你能證明「已成年」或「某國公民」卻不透露其他任何資訊——這是保護隱私的選擇性揭露。但要誠實面對侷限:必須有人正確地建立成員集合或核發憑證(一個可信的核發者),而且 ZK 給你的是隱私,不是 女巫抵抗力——證明你在集合裡並不能證明你是個獨立的人,這正是為何這類專案要訴諸生物特徵,也正是那些做法各自帶有隱私爭議的原因。
私密狀態:當輸入本身就是祕密
擴容用的證明什麼都不隱藏——交易是公開的,ZK 只是為了簡潔性而存在。隱私系統走得更遠:它們證明某個陳述為真,而其輸入始終保密。你在隱私預習裡已經見過這個種子。Zcash 的屏蔽池把錢表示成承諾值:花費時會建立新的票據承諾,並為每張被消耗的票據揭露一個作廢符,同時由一份 SNARK 證明這筆花費有效——你擁有這些票據,且輸入等於輸出——卻不透露發送者、接收者或金額。這就是屏蔽交易:一筆機密轉帳,其正確性被完整證明,內容卻全然隱形。
私密智慧合約把這件事從支付推廣到任意邏輯。在 Aztec 式的設計裡,合約狀態以加密的類 UTXO 票據形式存在;使用者在自己的機器上執行並證明狀態轉換(客戶端證明),接著只公布產生出的承諾值、作廢符,以及一份有效性證明。網路驗證這份證明,卻從未看見輸入、餘額或邏輯的中間值。隱私池就是同一構想的一個簡單實例:一個共享的屏蔽集合,眾人匿名地存入又取出。
要誠實面對可組合性的代價。公開的 DeFi 之所以行得通,是因為任何合約都能在同一筆交易裡讀取任何其他合約的狀態;一旦餘額與狀態被隱藏,這種輕鬆的互動就斷了。私密系統仰賴客戶端證明(使用者的裝置要做真正的密碼學運算)、非同步流程,以及細緻的設計來找回可組合性。這正是私密 DeFi 之所以困難、至今仍在成熟的原因——隱私是真實的,但它並非免費。
最後,ZK 並不是針對祕密做運算的唯一工具,成熟的工程師會把這三者都擺在手邊。零知識能在不揭露輸入的情況下證明一個事實,但證明者看得見一切。全同態加密直接在密文上運算,因此連執行運算的機器都從不解密——代價是龐大的效能開銷。安全多方計算把一個祕密拆分給多方,由他們共同算出一個誰也無法獨自得到的結果。這些做法越來越常被組合運用(例如用 MPC 來分散一組門檻數量的證明者),但它們做的是不同的信任與效能取捨——當真正的需求是 FHE 或 MPC 時,別硬拿 ZK 來套。
一份證明的代價
在正式環境裡選擇一套證明系統,歸結為三個數字:證明大小、驗證成本與產生證明的成本。前幾篇給了你各個家族的概念,這裡來看它們實際換算成什麼。
證明大小與驗證。 Groth16 的證明小得出名——大約 200 位元組(三個橢圓曲線元素)——並透過配對預編譯,在以太坊上以大致 常數的 ~20 至 30 萬 gas 完成驗證,無論那段運算有多龐大。這種常數成本的驗證,正是簡潔性的全部重點。代價是 Groth16 需要為每個電路做一次可信設定。PLONK 改用通用設定(一套 KZG 承諾方案),以略大的證明,換來一場儀式即可服務所有電路。STARK 則完全不需要設定且抗量子,但證明大小從數十到數百 KB 不等,驗證也要花更多 gas——你是用位元組數,換掉那個設定假設。
真正的瓶頸是產生證明的成本。 為一大批 EVM 交易產生證明,在 GPU 或整座叢集上可能要花幾分鐘,它主宰著每一個 zk-Rollup 的經濟結構。緩解之道正是你已見過的那些:用遞迴與聚合來攤平並平行化、從 GPU 到 FPGA 再到 ASIC 的專用硬體加速,以及讓證明者彼此競爭的新興證明市場。趨勢線比任何單一數字都重要——產生證明的成本正在快速下降,這正是 ZK 之所以年復一年吞下技術堆疊更多部分的原因。
這條路通向何方
零知識如今墊在兩件你已經見過、以及一件你即將遇上的事情之下。它給了 zk-Rollup 快速的最終性與無須信任的提款(擴容那一階),也是你接下來要進入的隱私那一階的引擎。除此之外,前沿還在不斷拓寬:zk 協處理器讓合約能無須信任地使用一個在鏈下被證明過、針對歷史鏈上狀態的繁重查詢結果;zk 橋接把一條鏈的共識證明給另一條鏈,而不必信任一組多簽;以及 zk 身分,有朝一日或許能用證明取代密碼。可擴展性三難並未被廢除,但簡潔證明把它彎折得比過去任何東西都更遠。
當你面對一個真實的設計、拿不準 ZK 是不是對的工具時,跑一遍這份檢查清單:
- 你是否需要讓某人相信一段他無法或不願重新執行的運算?若是,你要的是簡潔性——這是擴容的故事(zkEVM、Rollup),而且你或許根本不需要零知識性質。
- 你是否需要在隱藏「使其為真的那些輸入」的同時證明一個事實?若是,你要的是零知識性質——這是隱私與身分(屏蔽轉帳、成員證明)。
- 點名你實際在用的是哪個性質。多數正式系統靠的是簡潔性或隱私,而非兩者兼具——在這裡說清楚,能省去許多混亂的威脅建模。
- 你能否承受證明者的成本與延遲,並編列預算做專門的電路稽核?若「約束不足電路」的風險無法接受、而你的運算又很小,一個較簡單的原語也許更安全。
- 依取捨挑選系統:要最小證明與最便宜驗證就選 SNARK(接受一次可信設定),要免設定與抗量子安全就選 STARK(接受較大的證明與較多的驗證 gas)。
洞穴謎題長大成人了。那個看似聰明的派對把戲——證明你知道某個祕密卻不說出它——原來是一台把信任外包的通用機器:讓任何人相信你算過的任何事,卻只揭露你選擇揭露的部分。它仍處於早期,證明者仍然沉重,電路仍需細心的目光。但你如今已從頭到尾理解,為何這麼多人相信,零知識正是接下來十年區塊鏈將奠基其上的基石。