零知識證明在以太坊智能合約的實際應用:從 zkSNARK 到 zkSTARK 的落地指南
本文深入探討零知識證明在以太坊智能合約中的實際應用,提供完整的程式碼範例和工具鏈教學。涵蓋 Circom 電路設計、snarkjs 證明生成、Solidity 驗證合約撰寫、Tornado Cash 隱私機制分析、zkSync 和 Starknet 的 zkEVM 實作,以及新興的 zkML 應用場景。適合希望將 ZK 技術實際應用於專案的開發者。
零知識證明在以太坊智能合約的實際應用:從 zkSNARK 到 zkSTARK 的落地指南
老實說,每次看到「零知識證明」這四個字,我都有一種又愛又恨的感覺。愛是因為它真的超級優雅,用數學保證了隱私和可驗證性;恨是因為網上大部分的教學都在跟你講什麼「驗證者從來不會知道證明者的秘密」——然後給你一個童話故事版本的比喻,看完還是不會寫 code。
今天這篇文章,我不跟你談童話故事。我要直接打開 code editor,給你看真正的零知識證明在以太坊智能合約裡是怎麼運作的。從最基礎的zk-SNARK 驗證合約,到實際跑在主網上的應用——我們一個一個拆開來看。
先搞清楚:我們到底要證明什麼?
零知識證明的基本邏輯很簡單:你想要證明你知道一個秘密,但又不把秘密暴露出來。但在區塊鏈的世界裡,這個「秘密」的形式可以很多樣:
範例一:知道一個 Hash 的 Preimage
我知道一個值 x,使得 SHA256(x) = 某個已知的 hash
我想要在不透露 x 的情況下,證明我知道 x
範例二:滿足某個計算電路
我執行了某個計算,輸入是 x,輸出是 y
我想要證明這個計算是正確執行的,但不想透露 x
範例三:某個條件成立
我的銀行帳戶餘額 > 100萬
我想要證明這件事,但不透露具體數字
區塊鏈上的 ZK 應用基本都是在處理這三類問題。現在讓我們來看看怎麼用代碼實現它們。
工具生態:你要選哪套工具鍊?
在開始寫 code 之前,你得先選對武器。目前主流的 ZK 工具鍊有幾個選擇:
| 工具鍊 | 證明系統 | 語言 | 特點 | 知名項目 |
|---|---|---|---|---|
| Circom + SnarkJS | Groth16 / PLONK | Circom | 成熟、社群大 | Tornado Cash |
| Noir | PLONKish | Rust-like | 語法簡潔 | Aztec |
| Cairo + Starknet | STARK | Cairo | 無信任假設、量子抗性 | Starknet |
| ZoKrates | Groth16 | DSL | 易上手、教學友好 | - |
我個人比較推薦從 Circom + SnarkJS 開始,為什麼?因為文件最完整、社群最大,而且目前主網上跑的大多數項目都是這個技術棧。搞懂這套,其他的基本上都能觸類旁通。
實作一:用 Circom 做 Range Proof
Range Proof 是零知識證明最常見的應用場景之一。場景是這樣的:我想證明我的年齡在某個範圍內(例如 18-65 歲可以投票),但不透露具體數字。
第一步:設計電路(circuit)
Circom 的電路用一種 DSL 來描述,我習慣叫它「約束描述語言」。來看一個簡化的 range proof 電路:
pragma circom 2.0.0;
// 這個電路證明:a <= signal < b
// 但不透露 signal 的具體值
template RangeProof(bit_len) {
signal input secret; // 要證明的值
signal input lower; // 下界
signal input upper; // 上界
signal output valid; // 輸出:1 = 在範圍內
// 計算 secret - lower (必須 >= 0)
signal diffLower;
diffLower <== secret - lower;
// 計算 upper - secret (必須 >= 0)
signal diffUpper;
diffUpper <== upper - secret;
// 檢查:secret 在 [lower, upper) 範圍內
// 我們用乘法約束來實現
// 如果 diffLower 和 diffUpper 都 >= 0,
// 那麼它們的乘積會 > 0(大多數情況)
// 但這只是簡化版本,真實電路更複雜
// 這裡我們用更簡單的方法:轉成二進位
// 然後驗證範圍
component lowerBits[bit_len];
component upperBits[bit_len];
var i;
for (i = 0; i < bit_len; i++) {
lowerBits[i] = Num2Bits(1);
upperBits[i] = Num2Bits(1);
// 這個約束確保我們能恢復信號
// 但實際的 range proof 電路更複雜
}
// 簡化的約束:確保在範圍內
// 實際上需要用到範圍證明的專門技術
valid <== 1;
}
// 主模板
component main {public [lower, upper]} = RangeProof(32);
我知道這段電路超級簡化,實際上真正的 range proof 電路用的是更聰明的技巧,比如「位元分解 + 約束」。但重點是讓你感受一下電路長什麼樣子。
第二步:編譯電路
# 安裝 circom
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
git clone https://github.com/iden3/circom.git
cd circom && cargo build --release
# 下載 snarkjs
npm install -g snarkjs
# 編譯電路
circom range_proof.circom --r1cs --wasm --sym
# 這會產生三個檔案:
# range_proof.r1cs - 約束系統
# range_proof_js/ - WASM 證明者工具
# range_proof.sym - 符號檔案
第三步:設置Trusted Setup
ZK-SNARK 需要一個「信任儀式」(trusted setup)。這是個有點拗口的概念——本質上是你需要生成一堆「有毒廢物」(toxic waste),然後假設所有參與者都誠實地刪除了它。聽起來不太靠譜對吧?但這就是目前主流方案的現實。
# 初始化 powers of tau
snarkjs powersoftau new bn128 12 pot12_final.ptau -v
# 貢獻隨機性(這是信任儀式的第一步)
snarkjs powersoftau contribute pot12_final.ptau pot12_contrib1.ptau --name="First contribution" -v
# 準備相位(phase 2)
snarkjs powersoftau prepare phase2 pot12_contrib1.ptau pot12_final.ptau -v
# 生成 zkey(最終的金鑰)
snarkjs groth16 setup range_proof.r1cs pot12_final.ptau pot12_0000.zkey
# 貢獻 zkey
snarkjs zkey contribute pot12_0000.zkey pot12_0001.zkey --name="Second contribution" -v
# 匯出最終 zkey
snarkjs zkey export verificationkey pot12_0001.zkey verification_key.json
實際上,這個流程在 production 環境裡會更複雜,通常會有多個獨立的貢獻者,每個人都貢獻一段隨機性。Groth16 的 trusted setup 是「通用的」(per circuit),而 PLONK 是「通用的」(universal),不需要每個電路都重做 setup。
第四步:撰寫 Solidity 驗證合約
這是最激動人心的部分!我們要寫一個合約,能驗證 ZK 證明:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "hardhat/console.sol";
import { Groth16Verifier } from "./Groth16Verifier.sol";
contract ZKRangeProof {
Groth16Verifier public verifier;
// 存儲驗證結果
mapping(bytes32 => bool) public verifiedProofs;
// 驗證金鑰的 ABI 編碼版本
// 由 snarkjs 生成的 verifer.sol 提供
address public verificationKey;
constructor(address _verifierAddress) {
verifier = Groth16Verifier(_verifierAddress);
}
/**
* @notice 驗證一個範圍證明
* @param proof ZK 證明(a, b, c 三個點)
* @param input 公開輸入 [lower, upper, hash_of_secret]
*/
function verifyProof(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[3] memory input
) public returns (bool) {
// 調用生成的驗證合約
bool valid = verifier.verifyProof(a, b, c, input);
// 如果驗證成功,存儲結果
if (valid) {
bytes32 proofHash = keccak256(abi.encodePacked(a, b, c));
verifiedProofs[proofHash] = true;
}
return valid;
}
/**
* @notice 檢查某個值是否在指定範圍內(不透露值本身)
* @dev 這是個演示函數,真實應用中前端會做更多處理
*/
function checkRangeAndDoSomething(
uint256 secret,
uint256 lower,
uint256 upper
) public pure returns (bool) {
return secret >= lower && secret < upper;
}
}
snarkjs 會幫你生成一個 Groth16Verifier.sol,看起來像這樣:
// 自動生成的 Groth16 驗證合約(簡化版)
contract Groth16Verifier {
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
struct Proof {
uint256[2] a;
uint256[2][2] b;
uint256[2] c;
}
struct Vk {
uint256[2] alpha;
uint256[2] beta;
uint256[2][2] gamma;
uint256[2][2] delta;
uint256[2][] IC; // 線性組合係數
}
function verifyProof(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[3] memory input
) public view returns (bool) {
// 配對檢查
// 這是 zkSNARK 驗證的核心數學
// 需要驗證 e(A, B) = e(alpha, beta) * e(C, delta) * product(e(IC[i], gamma))
uint256[2] memory temp;
// 計算配對
// ...
// 如果所有檢查通過,返回 true
return true;
}
}
gas 消耗方面,Groth16 驗證合約大約需要 300,000-500,000 gas。這在 L2 上是可以接受的,但在 L1 主網上就比較貴了。這就是為什麼很多項目開始轉向 PLONK 或 STARK——它們的驗證成本更低,或者說驗證者不需要信任假設。
實作二:zkSNARK 在 DeFi 的實際應用——Tornado Cash
Tornado Cash 是 ZK 在隱私領域最著名的應用了。它的原理說穿了很簡單:
- 存款:你把 ETH 發到一個智能合約,同時提交一個「承諾」(commitment)——這是
hash(secret, nullifier)的值。合約存儲這個 commitment。 - 等待:你要等待一段時間,讓別人無法追蹤你存款和提款的時間關聯。
- 提款:你出示一個 ZK 證明,證明你知道某個 commitment 對應的 secret,但又不透露是哪個。
來看 Tornado Cash 的簡化版電路:
pragma circom 2.0.0;
include "../circomlib/poseidon.circom";
// 存款電路:計算 commitment
template DepositCircuit() {
signal input secret;
signal input nullifier;
signal output commitment;
signal output nullifierHash;
component poseidon = Poseidon(2);
poseidon.inputs[0] <== secret;
poseidon.inputs[1] <== nullifier;
commitment <== poseidon.out;
nullifierHash <== poseidon.out;
}
// 提款電路:證明知道 secret 但不透露
template WithdrawalCircuit() {
signal input secret;
signal input nullifier;
signal input root;
signal input pathElements[20]; // Merkle tree path
signal input pathIndices[20]; // 0 = left, 1 = right
// 計算 commitment
component deposit = DepositCircuit();
deposit.secret <== secret;
deposit.nullifier <== nullifier;
// 驗證 commitment 在 Merkle tree 中
component merkleVerifier = MerkleTreeChecker(20);
merkleVerifier.leaf <== deposit.commitment;
merkleVerifier.root <== root;
merkleVerifier.pathElements <== pathElements;
merkleVerifier.pathIndices <== pathIndices;
// 這個約束確保:
// 1. 提款者知道某個在樹中的 leaf
// 2. 提款者知道對應的 nullifier
// 但驗證者不知道是哪個 leaf!
}
component main {public [root]} = WithdrawalCircuit();
Merkle Tree 的技巧在這裡超級重要。它讓你只需要存儲一個 root(32 bytes),就能代表任意數量的 commitment。任何人都可以驗證某個 commitment 在樹中,但不知道它的位置——因為路徑資訊不會透露整棵樹的結構。
實作三:PLONK 的實際應用——zkSync Era
zkSync Era(現在也叫 ZKsync Era)用的是 PLONK 證明系統,它比 Groth16 有幾個優勢:
- 通用 Trusted Setup:只需要一次 setup,就能驗證任意電路(只要不超過設定上限)
- 透明度:不需要多個參與者做 trusted setup,可以用「powers of tau」公開儀式
zkSync 的 ZK 電路是用 Zinc(他們自己的 DSL)或 Yul 寫的。來看一個簡化的 transfer 電路概念:
// 簡化的 zkSync transfer 電路概念
// 實際代碼要複雜得多
const FIELD_SIZE: u64 = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
fn transfer_circuit(
// 公開輸入
root_before: Field,
root_after: Field,
pub_token_id: Field,
pub_amount: Field,
// 私密輸入(只有證明者知道)
account_nonce: Field,
account_balance: Field,
signature: [u8; 64], // ECDSA 簽章
merkle_proof: [Field; 10],
) -> bool {
// 1. 驗證 Merkle 證明
let leaf = hash(account_nonce, account_balance, pub_token_id);
let computed_root = compute_merkle_root(leaf, merkle_proof);
assert(computed_root == root_before);
// 2. 驗證餘額足夠
assert(account_balance >= pub_amount);
// 3. 驗證簽章
let message = hash(root_before, pub_token_id, pub_amount);
let pubkey = recover_pubkey(message, signature);
let account_id = hash(pubkey);
assert(hash(account_id) == leaf); // 驗證帳戶擁有者
// 4. 計算新狀態
let new_balance = account_balance - pub_amount;
let new_leaf = hash(account_nonce + 1, new_balance, pub_token_id);
// 5. 輸出新的 Merkle root
// 這個會是 root_after
true
}
zkSync 的厲害之處在於它把整個 EVM 執行都搬到了 ZK 電路裡。也就是說,你可以用 Solidity 寫任何合約,zkSync 會生成一個 ZK 電路來證明這個合約的執行是正確的。這個技術叫做「zkEVM」,是個非常複雜的工程——但它讓你不需要學新的 DSL 就能寫隱私應用。
實作四:zkSTARK 在 Starknet 的應用
STARK 是 ZK-SNARK 的「無需信任」版本。它不需要 trusted setup,但代價是證明大小更大、驗證成本更高。
Cairo 是 Starknet 的智慧合約語言。用 Cairo 寫的合約會被編譯成 STARK 證明,然後提交到以太坊主網驗證。
%builtins range_check
from starkware.cairo. Common.cairo_builtins import HashBuiltin
// 簡化的轉帳函數
func transfer{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr
}(
from_account: felt,
to_account: felt,
amount: felt
) -> (success: felt) {
alloc_locals;
// 讀取發送者餘額
let (from_balance) = balances.read(from_account);
// 確保餘額足夠(這是個約束)
assert 1 = from_balance - amount;
// 扣除發送者餘額
balances.write(from_account, from_balance - amount);
// 增加接收者餘額
let (to_balance) = balances.read(to_account);
balances.write(to_account, to_balance + amount);
return (success=1);
}
Cairo 的語法有點像 Python + Rust 的混合體。它的厲害之處在於每一行 Cairo 代碼都對應一個 STARK 約束——編譯器會自動處理所有的 ZK 魔法。
混合應用:zkML——讓鏈上 AI 成真
現在有個超酷的新方向:zkML(零知識機器學習)。場景是這樣的:
- 你在鏈外訓練了一個 ML 模型
- 你想在不透露模型權重的情況下,證明模型對某個輸入的輸出是正確的
這在預言機、遊戲、保險等場景都有應用。
# 用 Python + zkML 庫定義一個簡單的神經網路
import tensorflow as tf
# 定義模型
model = tf.keras.Sequential([
tf.keras.layers.Dense(10, activation='relu', input_shape=(5,)),
tf.keras.layers.Dense(1)
])
# 量化模型(ZK 電路只支援整數運算)
quantized_weights = quantize_weights(model.get_weights())
# 編譯成 ZK 電路
# 這部分需要用 circomlib 或其他工具
zkML 的挑戰在於:神經網路充滿了浮點數運算,而 ZK 電路只能處理有限域的整數。你需要:
- 量化:把所有浮點數轉成定點整數
- 約束:確保量化誤差不會破壞正確性
- 優化:減少電路規模,因爲推理可能需要數百萬個約束
常見坑與最佳實踐
坑一:Trusted Setup 的安全假設
Groth16 的 trusted setup 意味著如果所有參與者都是誠實的,你的證明就是安全的。但現實是:
- 你永遠無法 100% 確認每個人都刪除了「有毒廢物」
- 有些項目用「儀式」來降低風險,讓全世界幾百人一起貢獻隨機性
建議:選擇有公開、經過審計的 trusted setup 的項目。
坑二:電路設計失誤
ZK 電路有個特性:如果約束數量不夠,你可能會繞過某些檢查。
// 錯誤示例:忘記加約束
template BadCircuit() {
signal input secret;
signal output result;
// 問題:如果不約束 secret,攻擊者可以任意輸入
result <== 1; // 永遠輸出 1,但完全沒用到 secret!
}
// 正確做法:確保 secret 被約束
template GoodCircuit() {
signal input secret;
signal output result;
signal mid;
// 約束 secret 必須在某個範圍內(或其他約束)
secret * 1 === mid; // 這個約束把 secret 加入電路
result <== mid;
}
建議:找專業的安全公司審計你的電路。Certik、Trail of Bits 都有 ZK 審計服務。
坑三:隨機性不足
ZK 證明需要「高品質的隨機性」來防止攻擊。如果你的隨機數生成有漏洞,攻擊者可能構造假證明。
// 錯誤做法:使用可預測的隨機數
uint256 badRandom = uint256(keccak256(block.timestamp));
// 正確做法:使用多個 entropy 來源
uint256 goodRandom = uint256(keccak256(
abi.encodePacked(
block.timestamp,
msg.sender,
tx.origin,
gasleft()
)
));
坑四:Gas 成本失控
驗證一個 ZK 證明的 gas 成本可能高達幾十萬。這在 L1 上可能還行,但在複雜應用裡就會成為瓶頸。
優化策略:
- 把驗證搬到 L2(如 zkSync、Starknet)
- 使用聚合證明(Bunzz、Polyhedra 等項目在做這個)
- 選擇驗證成本更低的證明系統(PLONK、STARK)
結語:零知識的世界才剛開始
寫到這裡,我已經帶你看了 ZK 證明在以太坊上的主要應用場景和實作方式。從簡單的 range proof,到複雜的 zkEVM,每個應用都在挑戰著密碼學和工程的邊界。
但說實在的,2026 年的今天,ZK 技術還遠遠沒有成熟。大部分應用還停留在概念驗證階段,真正跑在主網上的實用案例屈指可數。電路設計工具不夠好用、驗證成本太高、Trusted Setup 的安全假設不夠好——這些都是亟需解決的問題。
好消息是,每天都有新的突破:新的證明系統、新的硬體加速、新的工具鏈。如果你想在這個領域深耕,現在是最好的時機。
下次當你看到「某某 DeFi 協議宣稱使用零知識證明保護隱私」的時候,你可以問問自己:他們用的是哪套工具鏈?trusted setup 是否靠譜?驗證成本是否可控?電路是否經過審計?這才是真正判斷一個 ZK 項目靠不靠譜的方法。
延伸閱讀
免責聲明:本網站內容僅供教育與資訊目的。零知識證明技術涉及複雜的密碼學原理,實際應用需要專業的安全審計。在部署任何基於 ZK 的智能合約前,請諮詢合格的密碼學家和安全專家。
數據截止日期:2026-03-27
相關文章
- 零知識證明系統與以太坊整合完整技術指南:從理論到實際部署 — 本文從密碼學理論出發,深入分析各類零知識證明系統(ZK-SNARK、ZK-STARK、Bulletproofs)的數學原理,並提供在以太坊上實際部署的完整技術指南。涵蓋 Groth16、PLONK、FRI 協議、Circom/Noir 電路開發、以及 zkSync/Starknet 應用實例。
- 以太坊零知識證明完整實作指南:從密碼學基礎到 zk-SNARKs/STARKs 智能合約部署 — 零知識證明(Zero-Knowledge Proof,ZKP)是現代密碼學中最具革命性的技術之一,其核心特性——在不透露任何額外資訊的情況下證明陳述的正確性——為區塊鏈隱私保護和可擴展性帶來了前所未有的可能性。本文從以太坊開發者的視角出發,深入探討零知識證明的密碼學基礎、zk-SNARKs 與 zk-STARKs 的技術差異、主流實作框架(如 Circom、ZoKrates、Groth16、PLONK)的使用方法,以及如何在以太坊上部署零知識證明智能合約。我們將提供完整的程式碼範例,涵蓋從電路設計、證明生成到鏈上驗證的整個流程,同時深入分析每個環節的 Gas 消耗、安全考量與最佳實踐。
- 零知識電路開發完整教學:從理論基礎到智能合約整合的開發者路徑 — 本文提供從電路設計基礎到實際智能合約整合的完整開發者路徑。涵蓋:零知識證明的形式化定義與代數電路視角、Circom 開發環境架設與工具鏈配置、常見電路設計模式(比較器、範圍證明、Merkle 樹驗證)、複雜電路設計原則與調試技巧、從電路到 Solidity 驗證合約的完整工作流、信任設置(Powers of Tau)詳解、以及 NOIR 和 Halo2 等新一代工具的入門介紹。提供完整的代碼範例和開發實踐指導。
- Privacy Pool ZK-Proof 驗證合約完整實作指南:從電路設計到 Solidity 部署 — 本文深入探討 Privacy Pool 系統中零知識證明(ZKP)驗證合約的完整實作流程。我們從密碼學基礎出發,詳細解釋 Groth16 和 PLONK 兩種主流零知識證明系統的原理,提供完整的 Circom 電路代碼範例,並展示如何將這些電路部署到以太坊區塊鏈上進行驗證。涵蓋 Merkle 樹驗證電路、承諾方案實現、完整隱私池合約代碼、以及可信設置教學。
- Privacy Pools 智能合約實現深度分析:從源碼架構到鏈上隱私驗證 — Privacy Pools 是以太坊隱私領域的重要創新,由 Vitalik Buterin 與研究團隊提出,其核心理念是透過零知識證明實現「選擇性披露」。本文深入分析 Privacy Pools 的智能合約源碼架構、零知識電路設計(Circom 電路代碼)、Merkle 樹管理器實現、以及如何在實際區塊鏈環境中驗證其隱私效果。同時提供完整的 Solidity 合約程式碼、安全審計要點,以及 Privacy Pools 與 Aztec 網路的實際交易隱私效果量化比較。截至 2026 年第一季度,Privacy Pools 協議已處理超過 47,000 ETH 的隱私交易。
延伸閱讀與來源
- zkSNARKs 論文 Gro16 ZK-SNARK 論文
- ZK-STARKs 論文 STARK 論文,透明化零知識證明
- Aztec Network ZK Rollup 隱私協議
- Railgun System 跨鏈隱私協議
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!