术语表 /交易与手续费
SIGHASH_ALL
- SIGHASH_ALL 是什么?
- SIGHASH_ALL 是一种标志,使签名对交易的每一个输入和每一个输出作出承诺,因此签名之后,这笔付款的任何内容都无法更改。
这是您的钱包所使用的标志,也是在硬件钱包屏幕上批准交易才有意义的原因。它的值为1,您可以在区块浏览器上看到它,作为几乎每个签名的最后一个字节。Taproot 将默认值改名为 SIGHASH_DEFAULT,并且完全不再写入该字节,每个签名因此节省1个字节。
运作方式
在 SIGHASH_ALL 下,被签名的摘要涵盖交易版本、每一个被花费的 outpoint、每一个序列号、每一个输出金额、每一个输出脚本以及锁定时间。
只要改动任何一个输出中的哪怕一聪,这笔交易上的所有签名就会失效。增加一个输入、删除一个输出、调整顺序、略微改动锁定时间:结果都一样。这就是人们所说的,已签名的比特币交易在广播之前,其内容就已经是最终的。任何人唯一剩下的决定,就是是否发送它。
有一项内容被刻意排除在外:其他输入的解锁脚本。如果摘要涵盖这些脚本,那么在有两个签名者的交易中,每个签名都必须在另一个签名之后生成,这是循环依赖,不可能实现。把它们排除在外,才使得相互独立的各方能够按任意顺序对同一笔交易签名。
编码方式因输入类型而异。在传统输入或隔离见证版本0输入上,字节01会附加在 DER 编码的签名之后,常见的总长度71或72字节即由此而来。在 Taproot 密钥路径花费中,BIP341 将值0定义为 SIGHASH_DEFAULT,其涵盖范围与之相同,但完全不写入字节,因此签名恰好为64字节。在 Taproot 输入上显式写入01仍然有效,并产生相同的摘要,只是白白多花1个字节。
在哪里会遇到它
SIGHASH_ALL 几乎随处可用:Bitcoin Core、Electrum、Sparrow、所有硬件钱包以及所有交易所的提币系统,默认都以这种方式签名,而且它们都不提供更改它的设置。
实际的后果,就是您在付款卡住时所感受到的那一种。手续费是输入与输出之间剩余的部分,而所有输出都已签名,因此您无法从某个输出中减去几千聪来提高手续费。这笔交易必须从头重新构建并重新签名,而这只有持有私钥的钱包才能做到。这就是为什么 replace-by-fee 是发送方钱包的功能,而不是交易所或区块浏览器可以代您执行的操作,也是为什么在发送方无法联系时,child-pays-for-parent 就成了应急办法。
这也使硬件钱包的屏幕成为一份约定,而不是一个建议。设备会显示收款地址、金额和手续费,而它随后生成的签名涵盖这三项,因此下游的任何软件都无法在不使该签名作废的情况下改动其中任何一项。批准屏幕上显示的内容,就不会再发生其他任何事情。
SIGHASH_ALL vs SIGHASH_NONE
SIGHASH_ALL 对完整的输出列表作出承诺;SIGHASH_NONE 对输出列表不作任何承诺。两者之间的差别,就是一笔付款与一份把币转移到未指定去向的空白授权之间的差别。这两个标志的值只相差1,因此,若签名库把该标志作为整数参数接受,一个笔误就可能把客户的提币变成一笔任何监视 mempool 的人都能改变去向的币。生产环境的签名代码应当将该值写死,而不是将其作为输入接收。