术语表 /协议与升级
OP_CHECKSIGADD
- OP_CHECKSIGADD 是什么?
- OP_CHECKSIGADD 在 tapscript 中逐个密钥统计签名,取代了旧的多重签名操作码,其检查方式既支持批量验证,也能适配未来的密钥类型。
规定于 BIP-342。
该操作码从堆栈中取出三个元素,即签名、累计计数和公钥,然后把计数原样或加1后压回堆栈。BIP-342为其分配了操作码186,同时禁用了 OP_CHECKMULTISIG,因此 Taproot 脚本中的所有多重签名都以这种方式编写。实际好处是,阈值策略不再止步于20个密钥。
运作方式
tapscript 阈值是一连串签名检查,检查之间传递一个累计值,最后再做一次比较。
写出来,2-of-3是这样的:压入第一个公钥,OP_CHECKSIG,压入第二个公钥,OP_CHECKSIGADD,压入第三个公钥,OP_CHECKSIGADD,压入2,OP_NUMEQUAL。执行时从左向右依次进行。OP_CHECKSIG 在堆栈上留下1或0,每个 OP_CHECKSIGADD 依次弹出一个公钥、弹出该累计总数、弹出为该密钥提供的签名,若签名验证通过,则把总数加1后压回。最后的比较决定验证通过的签名是否足够。
未参与签名的签名方以空字节串代替签名。操作码识别后,将该位置视为未签名,并原样返回累计值,这在见证数据中只占1个字节。整个机制就是这样,并且省去了 OP_CHECKMULTISIG 因原始实现中的差一错误而必须使用的那个占位堆栈元素。
每个密钥只对照一个签名检查,由此带来两个结果。验证变得可以批量进行:节点可以把区块中所有 Schnorr 签名放进一次聚合检查,而不必按顺序逐个测试候选密钥,这是 Taproot 验证提速的重要原因。另外,密钥数量受 tapscript 验证预算的限制,该预算对每个被检查的签名消耗50个权重单位,而不是受写入操作码的硬性上限限制。
在哪里会遇到它
任何需要多个签名方的 Taproot 脚本都会用到 OP_CHECKSIGADD,在钱包软件中,这通常表现为描述符,而不是原始脚本。
Bitcoin Core 在 tr() 描述符中以 multi_a 和 sortedmulti_a 片段的形式提供该功能,因此三密钥的签名阈值写作 tr(internalkey,multi_a(2,KEY1,KEY2,KEY3)),软件会为您生成操作码序列。Miniscript 的处理方式相同,支持 Taproot 策略的硬件设备也能对这些脚本签名,无需事先知道其具体结构。
这种设计还改变了签名阈值的构建方式。taptree 可以不用一个包含全部三个密钥的叶子,而是放置三个各含两个密钥的独立叶子,每个叶子对应一种可能的组合,花费时只会披露实际签名的那一对密钥。这在脚本叶子上花费更多,却在见证数据中少用一个公钥,而且相比每次花费都公布完整密钥集,这是真正的隐私改进。
OP_CHECKSIGADD vs OP_CHECKMULTISIG
OP_CHECKSIGADD 和 OP_CHECKMULTISIG 都是针对一组密钥统计签名,但只有前者能在 Taproot 叶子中使用。OP_CHECKMULTISIG 在传统脚本和 SegWit 版本0脚本中仍然完全有效,因此人们多年来使用的 bc1q 多重签名不受影响,无需迁移。
在 tapscript 内,OP_CHECKMULTISIG 和 OP_CHECKMULTISIGVERIFY 被彻底禁用。除了适用范围之外,还有三点差异:旧操作码把脚本限制在20个公钥以内,新操作码没有这个数字;旧操作码要求堆栈上有一个额外的占位元素,BIP-147后来将其强制规定为必须为空,作为一项共识规则;旧操作码无法批量验证,因为验证方事先不知道每个签名对应哪个密钥。