何为欺诈证明?
欺诈证明默认发生的状态转移是有效的(EVM 维护了一个庞大的状态机),发布者将交易前状态树的根哈希值以及交易发送到 Layer 1 上,Layer 1 上的智能合约确认交易前状态树根哈希是否和存储的根哈希一致。但是,Layer 1 上的智能合约并不能保证交易过程是正确的。在 Optimistic Rollups 中则默认这些状态的转变是正确的,其核心在于有挑战者发起挑战(说明状态的执行存在错误)时,证明交易前状态树的根哈希在执行这一组交易之后可以变为新的状态根哈希。在默认交易的执行过程没有错误的情况下,Optimistic Rollups 的方案就较为简单:在 Layer 2 中执行一系列交易后得到的新的状态树哈希和交易存放到 Layer 1 上即可 [3],而在发生挑战的情况下进入到欺诈证明的流程。而欺诈证明的流程不是本文的重点,不再进行赘述。 BitVMBitVM 采用了类似 Optimistic Rollups 的方法:证明者和验证者(BitVM 考虑只有两者的简单情况以简化方案,也是后续会频繁提到的两个角色)将程序编译为巨大的二进制电路,证明者将电路提交到一个为电路中的每个逻辑门都有一个叶子脚本的 Taproot 地址中。并且证明者和验证者预签名了一系列交易以启用两者之间的挑战-应答。
在这里,证明者和验证者之间共享了这个二进制电路,并且被证明者提交到 Taproot 地址中,在有效的时间内执行欺诈证明的一系列操作。从 0 和 1 开始比特值承诺(Bit Value Commitment, BVC)是 BitVM 的最基本的组成部分,它允许证明者将特定位的值设置为 0 或 1。证明者可以在不同的脚本和 UTxO 中设置变量的值。而这会是实现 BitVM 的关键,它通过将运行时分布在多个交易中来扩展 Bitcoin 虚拟机。 一个 BVC 包含两个哈希值,$$hash_0$$ 和 $$hash_1$$,在此后的某个时刻,证明者可以实现下面的操作:
- 揭露 $$preimage_0$$ 将这个位设置为 0;
- 揭露 $$preimage_1$$ 将这个位设置为 1;
- 同时揭露 $$preimage_0$$ 和 $$preimage_1$$。此时验证者将它们用作欺诈证明,并收回证明者的存款,该操作被称为 equivocation;
在上面的这些操作中,$$preimage$$ 是 $$hash$$ 的原象,假设有一个哈希函数 $$H$$,那么有 $$hash = H(preimage)$$结合 BVC 和时间锁能够强制要求验证者在一定时间内决定 bit 的值是 0 还是 1,这样的脚本可以表现为: <0x47c31e611a3bd2f3a7a42207613046703fa27496> <1> OP_IF // 如果揭露的是 hash1,则 push 一个 1 到栈中 OP_HASH160 <0xf592e757267b7f307324f1e78b34472f8b6f46f3> <1> OP_ELSE // 如果揭露的是 hash0,则 push 一个 0 到栈中 OP_HASH160 <0xb157bee96d62f6855392b9920385a834c3113d9a> <0> OP_ENDIF 在这个示例中,验证者需要揭露 $$hash_0$$ 或 $$hash_1$$ 的原像来设置当前位的值,验证者选择了揭露 $$hash_1$$ 的原像并且设置值为 1。在后续为了简化流程,假设存在一个 OP_BITCOMMITMENT 的操作码,它接收两个哈希值和其中一个原像,通过判断原像可以将一个 bit 存放到脚本的执行栈中4。而这样的操作是一个最小单元,BitVM 可以将一系列的最小单元组合在一起,最终得到一个可执行的二进制程序。 NAND 逻辑门任何的可计算函数都能够被表示为布尔电路,NAND 门(与非门)是一个普适的逻辑门,任何的布尔函数都可以通过它来组成。 脚本语言下的 NAND 门通过两个 BVC 来实现,实现一个验证$ A NAND B = C $的脚本为: // 揭露哈希 c1 或 c0 的原像 <0xc468a29472cacf3ef179ba2352f88587b91e3e15> <0x829923b22b9e831822e0a783f92687d27128157b> OP_BITCOMMITMENT // 此时将 C 的比特值移动到 ALT 栈中 OP_TOALTSTACK // 揭露哈希 b1 或 b0 的原像 <0x34f0132278e874836da82f8a6c1e10a21a153d17> <0xf9fce46cefe9d9392108480ad42b4ce69557d27d> // 此时将 B 的比特值移动到 ALT 栈中 OP_TOALTSTACK // 揭露哈希 a1 或 a0 的原像 <0x5acfde72a8e37111cba96d3dd705ba983f47af4d> <0xa0172816a2d1b20ef0d5a1093958e9564e590baf> // 此时将 A 的比特值在主运行栈中,此时栈中的情况为 A // 验证 A NAND B == C // 从 ALT 栈中取出 B,此时栈中的情况为 B、A OP_FROMALTSTACK // 执行 NAND 位运算操作,此时栈中情况为 B NAND A OP_NAND // 从 ALT 栈中取出 C,此时栈中的情况为 C、B NAND A OP_FROMALTSTACK // 判断是否相等,取出 C 和 B NAND A,并判断它们是否相等 OP_EQUALVERIFY OP_TOALTSTACK 将主运行栈的值放入到另外一个被称为 ALT 栈的栈顶,并将它从主运行栈中弹出。 而 OP_NAND 是一个假设存在的操作码,它在实际的 Bitcoin 脚本语言中并不存在,它可以容易地使用 OP_BOOLAND 和 OP_NOT 操作码来实现。 二进制电路通过组合一系列的 NAND 门可以表达任何的电路,每一步都在 Taproot 的叶子节点下进行提交,最后合并到同一个 Taproot 地址中[5],以便于证明者可以执行电路中的任何门。执行门需要证明者打开相应的 NAND 门并且设置输入和输出位的值。二进制电路在链下的结构可以非常巨大,但是由于 Taproot 的特性,可以使得它在链上的占用空间非常小。


