ZK Escrow 实战教程:纯操作版
目标:按命令一步步把项目从本地跑到 Base Sepolia,完成
deposit -> prove -> Kurier aggregation -> finalize。 本文只讲操作,不讲源码原理。 项目仓库:JetHalo/zk-Escrow
0. 先锁定模式
本教程固定使用:
- Submission mode:
aggregation-kurier - Verification route:
aggregation-gateway(合约里调用zkVerify.verifyProofAggregation) - Indexer strategy:
thegraph
不要在同一个分支混用 direct/aggregation。
1. 准备环境
1.0 获取项目代码
git clone https://github.com/JetHalo/zk-Escrow.gitcd zk-Escrowexport REPO_ROOT=$(pwd)
1.1 必备工具
# Foundrycurl -L https://foundry.paradigm.xyz | bashfoundryupforge --versioncast --version# Node + npmnode -vnpm -v# snarkjsnpm i -g snarkjssnarkjs --help# circomcircom --help
1.2 安装依赖
cd "$REPO_ROOT/apps/web"npm installcd "$REPO_ROOT/contracts"forge installcd "$REPO_ROOT/circuits/escrow"npm install
2. 配置环境变量
2.1 contracts/.env
PRIVATE_KEY=0x...RPC_URL=https://base-sepolia.g.alchemy.com/v2/...# zkVerify Base Sepolia gateway proxyZKVERIFY_PROXY=0xEA0A0f1EfB1088F4ff0Def03741Cb2C64F89361E# vk hash(由 vkey 注册后得到)VK_HASH=0x...# 业务域(电路里的 domain)DOMAIN=1APP_ID=1CHAIN_ID=84532# 稍后部署 hasher 后填入HASHER_ADDRESS=0x...
2.2 apps/web/.env.local
KURIER_API_URL=https://api-testnet.kurier.xyz/api/v1KURIER_API_KEY=...KURIER_VKEY_PATH=public/zk/escrow/vkey.jsonKURIER_VK_HASH=0x...NEXT_PUBLIC_ESCROW_ADDRESS=0x...NEXT_PUBLIC_BASE_SEPOLIA_RPC_URL=https://base-sepolia.g.alchemy.com/v2/...NEXT_PUBLIC_DOMAIN=1NEXT_PUBLIC_APP_ID=1NEXT_PUBLIC_DEPLOY_BLOCK=...# 聚合域(不是业务 DOMAIN)KURIER_ZKVERIFY_DOMAIN_ID=2NEXT_PUBLIC_KURIER_ZKVERIFY_DOMAIN_ID=2# 推荐:聚合态就允许进入链上预检NEXT_PUBLIC_KURIER_REQUIRE_FINALIZED=false# The GraphINDEXER_STRATEGY=thegraphTHEGRAPH_SUBGRAPH_URL=https://api.studio.thegraph.com/query/.../escrow-base-sepolia-aggregation/v.0.1NEXT_PUBLIC_THEGRAPH_SUBGRAPH_URL=https://api.studio.thegraph.com/query/.../escrow-base-sepolia-aggregation/v.0.1
3. 构建电路产物(wasm/zkey/vkey)
cd "$REPO_ROOT/circuits/escrow/circom"mkdir -p build# 1) 编译circom escrowRelease.circom --r1cs --wasm --sym -o build# 2) ptau(首次)snarkjs powersoftau new bn128 16 build/pot16_0000.ptau -vsnarkjs powersoftau contribute build/pot16_0000.ptau build/pot16_0001.ptau --name="first" -v -e="random-entropy"snarkjs powersoftau prepare phase2 build/pot16_0001.ptau build/pot16_final.ptau -v# 3) zkeysnarkjs groth16 setup build/escrowRelease.r1cs build/pot16_final.ptau build/escrowRelease_0000.zkeysnarkjs zkey contribute build/escrowRelease_0000.zkey build/escrowRelease_final.zkey --name="final" -v -e="random-entropy-2"# 4) vkeysnarkjs zkey export verificationkey build/escrowRelease_final.zkey build/vkey.json
复制到前端静态目录:
cd "$REPO_ROOT"mkdir -p apps/web/public/zk/escrowcp -f circuits/escrow/circom/build/escrowRelease_js/escrowRelease.wasm apps/web/public/zk/escrow/cp -f circuits/escrow/circom/build/escrowRelease_final.zkey apps/web/public/zk/escrow/cp -f circuits/escrow/circom/build/vkey.json apps/web/public/zk/escrow/
4. 注册 VK 到 Kurier
cd "$REPO_ROOT"node - <<'NODE'const fs = require('fs');const vk = JSON.parse(fs.readFileSync('apps/web/public/zk/escrow/vkey.json','utf8'));const payload = { proofType:'groth16', vk, proofOptions:{ library:'snarkjs', curve:'bn128' } };fs.writeFileSync('/tmp/kurier-vk.json', JSON.stringify(payload));console.log('payload saved: /tmp/kurier-vk.json');NODE# 确保当前 shell 有 KURIER_API_URL / KURIER_API_KEYcurl -s -X POST "$KURIER_API_URL/register-vk/$KURIER_API_KEY" \-H "Content-Type: application/json" \--data @/tmp/kurier-vk.json
如果返回 uniq_vk_hash,表示这个 vk 之前已经注册过,可直接复用。
5. 部署 Hasher 合约
cd "$REPO_ROOT"node scripts/compile-hasher.jsBYTECODE=$(node -p "require('./scripts/hasher.json').bytecode")cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" --create "$BYTECODE"
记录输出里的 contractAddress,写回 contracts/.env 的 HASHER_ADDRESS。
6. 部署 Escrow 合约
cd "$REPO_ROOT/contracts"set -a; source .env; set +a# 注意:带 --broadcast,且参数顺序保持一致forge create --broadcast --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \src/ZKEscrowRelease.sol:ZKEscrowRelease \--constructor-args "$ZKVERIFY_PROXY" "$VK_HASH" "$DOMAIN" "$APP_ID" "$CHAIN_ID" "$HASHER_ADDRESS"
把返回的 Deployed to 写到 apps/web/.env.local 的 NEXT_PUBLIC_ESCROW_ADDRESS。
7. 启动前端
cd "$REPO_ROOT/apps/web"npm run dev
访问 http://localhost:3000/escrow。
8. 端到端操作顺序
- 连接钱包(Base Sepolia)
- 输入 recipient + amount,执行
deposit - 复制凭证(credential)
- 粘贴凭证后点击 unlock
- 前端会:
- 本地生成 proof
- 调
/api/submit-proof - 轮询
/api/proof-status - 拉取
/api/proof-aggregation - 做
verifyProofAggregation预检 - 满足条件后发
finalize交易(钱包弹窗)
9. 常用排查命令(按优先顺序)
9.1 看 Kurier job 状态
curl -s "$KURIER_API_URL/job-status/$KURIER_API_KEY/$JOB_ID"
9.2 看本地 API
curl -s "http://localhost:3000/api/proof-status?proofId=$JOB_ID"curl -s -X POST "http://localhost:3000/api/proof-aggregation" \-H "Content-Type: application/json" \--data "{\"proofId\":\"$JOB_ID\"}"
9.3 查合约绑定参数
cast call $ESCROW "vkHash()(bytes32)" --rpc-url "$RPC_URL"cast call $ESCROW "expectedDomain()(uint256)" --rpc-url "$RPC_URL"cast call $ESCROW "expectedAppId()(uint256)" --rpc-url "$RPC_URL"cast call $ESCROW "expectedChainId()(uint256)" --rpc-url "$RPC_URL"cast call $ESCROW "zkVerify()(address)" --rpc-url "$RPC_URL"
9.4 查是否有 Finalized 事件
cast logs --rpc-url "$RPC_URL" \--address "$ESCROW" \--from-block "$DEPLOY_BLOCK" \--to-block latest \"Finalized(bytes32,address,uint256)"
10. The Graph 子图部署(可选但推荐)
cd "$REPO_ROOT/indexer/subgraph"npm installnpm run rendernpm run codegennpm run build# token 用 Studio deploy keyexport GRAPH_DEPLOY_KEY=...export SUBGRAPH_SLUG=escrow-base-sepolia-aggregationnpm run authnpm run deploy
部署后把 Query URL 回填到 THEGRAPH_SUBGRAPH_URL 与 NEXT_PUBLIC_THEGRAPH_SUBGRAPH_URL。