开发者
1. 你会得到什么
一个流片在 ℵ₀ 上的电路,变成链上能便宜检查的电路:任何人都能为它出证明(256 字节),任何合约都能用约 30 万 gas 检查一次,检查过的结果缓存在电路登记处,之后谁都能免费读到。同一个电路直接在链上跑,常常一个区块都装不下。
先把电路流片到 ℵ₀:调用处理器的 tapeout(网表, 输入位数, 输出位数),付 0.0013 OKB 流片费;网表里每个与非门烧一颗晶体管,一颗 0.000066 OKB;用 REF 引用别人已经流片的电路,一颗都不烧。比如数字识别:展开后有 161,220 个门,只烧了 3,337 颗。
2. 生成证明电路
这个页面读出你的电路在链上的网表,生成它的证明电路,跑一次仪式,再导出一个验证合约。部署验证合约、在电路登记处登记之后,任何人都能为这个电路出证明,任何合约都能用约 30 万 gas 检查它。
托管生成要付费:在 ℵ₀ 上流片一个 152 门的“刻印”,约 0.012 OKB。刻印里刻着你要生成的电路和一个随机编号,服务据此确认这次付过款。
本地生成免费:在自己电脑上运行下面这条命令,做的事完全一样。
信任:服务器为仪式贡献一次随机数,用完就扔;每个任务的仪式记录都公开,可以复核;不想信任服务器,可以在本地再加一份自己的贡献。
本地生成脚本即将开源
circuits/.venv/bin/python zk/zkgen.py --rpc https://rpc.xlayer.tech \
--processor <processor> --circuit <id> --out out/my-circuit选电路
进行中填处理器地址和电路编号。先检查规模:太大的电路托管服务做不了,不会让你白付钱。
一键刻印
待完成在刻印处理器上流片一个 152 门的刻印,就是这次的付款。钱包会弹出一到两次:没有足够的晶体管时先买,再流片。
签名并生成
待完成用钱包签一段文字(不花 gas),证明这个刻印是你付的,然后提交任务。任务编号会写进网址,刷新页面也能接着看。
下载文件
待完成生成的文件都在下面,每个都附了 sha256,下载后可以自己核对。
3. 部署验证合约
部署验证合约
待完成浏览器里没法编译 Solidity,所以这一步用 Foundry 在你自己的电脑上完成(--interactive 会在终端里问私钥,不要把私钥写在命令里)。部署好后,把合约地址贴在这里。
4. 登记
登记
待完成一笔交易在电路登记处登记:处理器、电路编号、子电路列表和验证合约。登记后得到这个电路的 key。
5. 在合约里调用
两个调用:verifyEval(key, x, y, proof) 检查一次并缓存结果,一定要显式给 1,000,000 gas(gas 不够时验证合约会返回 false 而不是报错,估算不出来);getResult(key, x) 读缓存,不再花检查的钱。证明可以向托管的证明服务要:用这个网站上的电路名(例如 bnn3),或用第 4 节登记得到的 key。
试一试
待完成填一个输入 x(十六进制),让托管的证明服务用这个 key 出证明(第一次要先加载文件,会慢一点),再在链上检查它。
1. 接口
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IAlephRegistry {
/// Verify "circuit `key` maps input x to output y" and cache the result.
/// Pass an explicit gas limit (1,000,000): a starved verifier would return false.
function verifyEval(bytes32 key, bytes calldata x, bytes calldata y, uint256[24] calldata proof)
external returns (bool);
/// Read a cached result. Anyone can have proven it; nobody can fake it.
function getResult(bytes32 key, bytes calldata x)
external view returns (bool proven, bytes memory y);
}2. 使用结果
一个根据 BNN 对图像的判断做出响应的合约。读取路径只需一次存储查询;证明路径再加一次验证。
contract DigitGate {
IAlephRegistry public immutable registry;
bytes32 public immutable bnnKey; // BNN digit classifier
uint8 public constant TARGET = 7;
event Passed(address indexed who, bytes x);
constructor(IAlephRegistry r, bytes32 key) {
registry = r;
bnnKey = key;
}
/// Read path: the result was proven earlier, by anyone.
function judge(bytes calldata x) external {
(bool proven, bytes memory y) = registry.getResult(bnnKey, x);
require(proven, "not proven yet");
require(digit(y) == TARGET, "wrong digit");
emit Passed(msg.sender, x);
}
/// Prove-and-use in one transaction (verifyEval measured 296,826 gas on X Layer).
function submitAndJudge(bytes calldata x, bytes calldata y, uint256[24] calldata proof) external {
require(registry.verifyEval(bnnKey, x, y, proof), "invalid proof");
require(digit(y) == TARGET, "wrong digit");
emit Passed(msg.sender, x);
}
/// y = 10 scores x 7 bits, little-endian (see AiJudge.scores); the digit is the argmax.
function digit(bytes memory y) internal pure returns (uint8 best) {
uint256 top;
for (uint256 k; k < 10; ++k) {
uint256 v;
for (uint256 b; b < 7; ++b) {
uint256 i = k * 7 + b;
v |= ((uint256(uint8(y[i >> 3])) >> (i & 7)) & 1) << b;
}
if (v > top) (top, best) = (v, uint8(k));
}
}
}3. 请求并提交证明
import { createWalletClient, custom } from "viem";
import { xLayer } from "./chains"; // id 196 (X Layer)
import { alephRegistryAbi, REGISTRY, BNN_KEY } from "./contracts";
// 1. Prove off-chain (Groth16 via rapidsnark, about 0.5 s on the server; circuit = a site circuit name or a registry key).
const res = await fetch(`${PROVER_URL}/prove`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ circuit: "bnn3", x: "0x3c6660386060663c" }),
}).then((r) => r.json()); // { x, y, scores, proof: 24 hex words, pub, ms }
// 2. Verify on-chain. Always set the gas limit: a gas-starved verifier returns false instead of
// reverting, so eth_estimateGas cannot find the right value.
const wallet = createWalletClient({ chain: xLayer, transport: custom(window.ethereum) });
const [account] = await wallet.getAddresses();
await wallet.writeContract({
account,
address: REGISTRY,
abi: alephRegistryAbi,
functionName: "verifyEval",
args: [BNN_KEY, res.x, res.y, res.proof.map(BigInt)],
gas: 1_000_000n,
});6. 自己核对
- 1
网表在链上
任何人都能调处理器的 netlist(电路编号),读出电路完整的接线图。
- 2
指纹在电路登记处
电路登记处给每个电路存了一个指纹 flatHash:先是顶层网表的哈希,再按深度优先的顺序接上每个子电路的处理器、编号和网表哈希,整体再哈希一次。自己读链上网表算一遍,和 circuit(key).flatHash 比对;zk/zkgen.py 也会算出它。
- 3
仪式可以复核
同样的网表总是生成同样的约束。每个托管任务的仪式记录 transcript.json 就在第 2 节的文件里;node zk/ceremony.cjs verify 复核记录、信标区块和最终钥匙,再导出验证合约,和链上字节码比对。本站游戏电路的仪式记录会随脚本一起公开。
信任边界
- 可靠性
- Groth16:错误 y 的证明无法通过验证。注册表会拒绝它,不缓存任何内容。
- 证明者
- 证明服务可能变慢或宕机,但无法伪造结果。任何人都可以改用开源证明器自行运行。
- 验证器
- 可信设置:第一阶段是 PSE perpetual powers of tau;第二阶段每个电路一次仪式,由我们贡献一次随机数(只在内存里,用完即弃),再用 X Layer 主网区块哈希做公开信标,记录在 zk/ceremony/。安全前提是这次贡献的随机数确实已丢弃,主网前会邀请更多人追加贡献(任意一方诚实即可)。任何人都能从链上网表重建约束,用 snarkjs zkey verify 核对公开的证明密钥,再导出验证器和链上字节码比对。
- 绑定
- flatHash 按 DFS 顺序承诺顶层网表及每个 REF 子电路。子电路列表由注册者提供,任何人都可以在链下复核。
- 管理员
- 只能向白名单追加处理器、暂停新注册,无法改动已注册的电路或已缓存的结果。
zkgen: https://zkgen-production.up.railway.app