开发者

把你的电路接上链:生成、部署、登记,然后在任何合约里调用它。

1. 你会得到什么

一个流片在 ℵ₀ 上的电路,变成链上能便宜检查的电路:任何人都能为它出证明(256 字节),任何合约都能用约 30 万 gas 检查一次,检查过的结果缓存在电路登记处,之后谁都能免费读到。同一个电路直接在链上跑,常常一个区块都装不下。

先把电路流片到 ℵ₀:调用处理器的 tapeout(网表, 输入位数, 输出位数),付 0.0013 OKB 流片费;网表里每个与非门烧一颗晶体管,一颗 0.000066 OKB;用 REF 引用别人已经流片的电路,一颗都不烧。比如数字识别:展开后有 161,220 个门,只烧了 3,337 颗。

2. 生成证明电路

这个页面读出你的电路在链上的网表,生成它的证明电路,跑一次仪式,再导出一个验证合约。部署验证合约、在电路登记处登记之后,任何人都能为这个电路出证明,任何合约都能用约 30 万 gas 检查它。

托管生成要付费:在 ℵ₀ 上流片一个 152 门的“刻印”,约 0.012 OKB。刻印里刻着你要生成的电路和一个随机编号,服务据此确认这次付过款。

本地生成免费:在自己电脑上运行下面这条命令,做的事完全一样。

信任:服务器为仪式贡献一次随机数,用完就扔;每个任务的仪式记录都公开,可以复核;不想信任服务器,可以在本地再加一份自己的贡献。

本地生成脚本即将开源

shell
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. 接口

IAlephRegistry.sol
// 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 对图像的判断做出响应的合约。读取路径只需一次存储查询;证明路径再加一次验证。

DigitGate.sol
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. 请求并提交证明

submit.ts
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. 1

    网表在链上

    任何人都能调处理器的 netlist(电路编号),读出电路完整的接线图。

  2. 2

    指纹在电路登记处

    电路登记处给每个电路存了一个指纹 flatHash:先是顶层网表的哈希,再按深度优先的顺序接上每个子电路的处理器、编号和网表哈希,整体再哈希一次。自己读链上网表算一遍,和 circuit(key).flatHash 比对;zk/zkgen.py 也会算出它。

  3. 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