术语表 /开发者参考
OP_VERIFY
- OP_VERIFY 是什么?
- OP_VERIFY 弹出栈顶元素,除非该元素为真,否则中止整个脚本,从而使比特币脚本(Bitcoin Script)中的任何检验都成为硬性要求。
字节0x69 是该语言的通用断言。脚本可以用 OP_IF 分支,但无法抛出错误,除栈中留存的内容外也没有返回值,因此合约要强制某个条件成立,就必须先算出结果,再交给这条指令。几乎所有已发生的支付都经过它,通常隐藏在合并式操作码之中。
运作方式
OP_VERIFY 从栈中移除一个元素,只问一个问题:它是否为真?
这里的真假是字节层面的概念,而非数值层面的。空元素为假。一串零字节为假,负零(以0x80 结尾的一串零)也为假。其余一切都算真,包括20字节的哈希或33字节的公钥。如果该元素为假,脚本立即终止,持有它的输入无效,其所在的交易也随之无效。如果该元素为真,不会有任何内容放回栈中,求值继续执行下一个操作码。
正是这种行为使脚本得以依次检查多项条件。脚本只有在结束时恰好剩下一个真值才算成功,因此如果没有断言,每个条件都必须合并为最后的一个布尔值。有了断言,合约可以先要求时间锁,再要求哈希原像,再要求签名,并在第一个不满足的条件处失败。
有四个操作码内置了断言,其存在纯粹是为了节省一个字节:OP_EQUALVERIFY、OP_CHECKSIGVERIFY、OP_CHECKMULTISIGVERIFY 和 OP_NUMEQUALVERIFY。由于它们涵盖了常见情形,独立的0x69 在主网上很少见。
有两个操作码名称中带有 VERIFY,行为却完全不同。BIP65 中的 OP_CHECKLOCKTIMEVERIFY 和 BIP112 中的 OP_CHECKSEQUENCEVERIFY 是通过软分叉加到原先毫无意义的空操作码上的,为了让旧节点正常运行,它们必须让栈保持原样。两者都检查栈顶元素,条件不满足时失败,但都不消耗该元素,因此实际的脚本总会在其后接上 OP_DROP。
在哪里会遇到它
凡是脚本有多件事需要证明的地方,断言就会集中出现。
一个简单的时间锁保险库的写法是:一个公钥、OP_CHECKSIGVERIFY、一个延迟(例如144个区块,约一天),然后是 OP_CHECKSEQUENCEVERIFY。闪电网络的承诺脚本和 HTLC 脚本也是同样的构造,把原像检查和签名检查串联起来,使其中任何一项单独都不足以转移资金。Taproot 的脚本花费大量使用这种模式,因为 tapscript 用 OP_CHECKSIGADD 取代了 OP_CHECKMULTISIG,并使用 verify 形式让多密钥策略保持紧凑。
Miniscript 是策略编译器,目前有多款桌面钱包用它以可读形式描述花费条件,它会替您生成这些操作码。想观察断言是如何生成的而不必手写原始脚本,这是最简便的办法。
OP_VERIFY vs OP_RETURN
OP_VERIFY 和 OP_RETURN 都会让脚本以失败告终,但只有其中一个会先提出问题。OP_VERIFY 在交给它的值为假时失败,因此正确的花费会直接越过它,不留痕迹。OP_RETURN(字节0x6a)只要被执行就会失败,没有任何附带条件,这正是它被用来构造永远无法花费的输出以及向交易附加数据的原因。在前者处终止的脚本,是因为收到了不正确的内容。在后者处终止的脚本,从一开始就不打算可被花费。