协议演进 / BIP/比特币改进提案 / BIP-140
BIP-140
计算交易 ID 时剔除签名脚本,得到不随签名变化的规范化 TXID,并配合新操作码 OP_CHECKSIGEX 与版本 2 交易,以消除包括签名者重签在内的交易可塑性。本 BIP 目前处于已关闭状态。
一、档案卡
| 编号 | BIP-140 |
|---|---|
| 标题 | Normalized TXID |
| 作者 | Christian Decker <[email protected]> |
| 指定日期 | 2015-10-14 |
| 层级 | Consensus (soft fork)(共识(软分叉)) |
| 类型 | Specification(规范类) |
| 状态 | Closed(已关闭) |
| 阅读版 | https://bips.dev/140 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0140.mediawiki |
二、放在哪一族
交易可塑性修复族。BIP-140 要根除的是 txid 会随签名表示变化这一件事,思路是让引用输出用的 ID 从一开始就不包含签名。
同族文档:
BIP-62:处理可塑性。靠给签名定规范表示来缓解第三方修改,但原文指出其检查复杂、且未必能消除全部第三方可塑性来源。
BIP-66:严格 DER 签名。与 BIP-62 一同缓解第三方修改问题。
BIP-11:M-of-N 标准交易。BIP-140 的新标准脚本格式直接沿用 BIP-11 的 m-of-n 形态,再加版本参数与新操作码。
BIP-141:隔离见证。同为解决签名可塑性对 txid 的影响,走的却是把见证移出交易本体的路线。两份文档解决同一问题、给出不同结构,放在一起读最能看清取舍。
BIP-140 在族内的独特主张是覆盖面:不只防第三方改签名表示,也防签名者本人重签——因为 ECDSA 带随机参数,同一签名者能给出任意多份不同签名,规范表示管不住这一点,而把签名移出哈希可以。
三、它要解决什么
交易可塑性指的是:一笔交易可以被修改——或由签名者之一重签,或由第三方修改签名的表示形式。麻烦在于,序列化表示一变,交易的哈希就变,而花费交易正是用这个哈希来引用要花的资金的。
若一笔交易被改后最终上链确认,所有依赖原交易的后续交易都会失效、成为孤儿。链式交易、上层协议与多方共同签名的结构首当其冲。
原文还指出规范化带来的附带好处:交易模板。模板是一笔未签名的交易骨架,可以先在其上搭出整串后续交易,各方确认整串结构符合预期后再补签名生效。这样就不必由一方先收齐全部签名、握有完整已签交易才能算出 ID,也降低了一方单方面锁死资金的可能。多签输出上的高层协议(如支付通道类构造)正是原文点名的受益场景。
需要留意的例外原文也写明了:使用 SIGHASH_NONE、SIGHASH_SINGLE 或 SIGHASH_ANYONECANPAY 时,交易仍可被单方面修改,但那是创建者明确允许修改的情形,不算问题。
四、规则本身
规范分三部分:规范化 TXID 的计算、新操作码、UTXO 跟踪的改动。整体以软分叉方式引入,交易版本升至 2,以显式选择加入规范化 ID 的跟踪。
4.1 规范化 TXID 怎么算
对非 coinbase 交易,把每个输入的签名脚本剥除——把脚本长度置 0、移除其字节数组;再把每个输入引用的前交易哈希替换为其规范化版本(若有;版本 1 交易没有规范化 ID,就用普通 ID)。规范化 TXID 就是对这样规范化后的交易做双重 SHA256,与既有 txid 算法同构。
原文给两个 ID 分了名字:含签名算出的叫交易实例 ID(transaction instance ID),剔除签名算出的叫规范化 TXID。同一个规范化 TXID 对应一类语义相同、只是签名不同的交易,签名被替换或被延展,规范化 TXID 不变。
coinbase 交易是例外:其规范化 ID 计算时保留 coinbase 输入的签名脚本,以免哈希碰撞。网络消息(inv、getdata、tx、block)仍继续使用实例 ID 引用交易,不改用规范化 ID。
由此还有一条修复路径:若一笔花费交易因其输入被延展而失效,观察者可以把它的引用改指到被改后的实例 ID 并重新注入——因为签名只用规范化 ID,只要资金交易的语义不变,原签名依然有效。
4.2 新操作码 OP_CHECKSIGEX
本 BIP 引入 OP_CHECKSIGEX,占用 OP_NOP4 的位置,以软分叉方式启用。它统摄 OP_CHECKSIGVERIFY 与 OP_CHECKMULTISIGVERIFY,并新增一个 VERSION 参数:执行前先把单个整数版本压栈,操作码弹出版本后按该版本做签名检查;客户端若不支持所给版本,必须把 OP_CHECKSIGEX 当作 OP_NOP 处理。以一个操作码加版本参数演进签名检查,是为了少消耗 OP_NOP 位置、为日后软分叉更新签名算法留口。
版本 1 实现规范化 TXID,并改用 Schnorr 签名替代当时的 ECDSA。其标准脚本格式为:
m {pubkey}...{pubkey} n v OP_CHECKSIGEX
即 BIP-11 的 m-of-n 形式加版本参数 v 与新操作码,单签交易统一编码为 1-of-1。OP_CHECKSIGEX 不重现 OP_CHECKMULTISIG 多弹一个栈元素的历史缺陷,因此无须再压占位假值。刻意跳过版本 0,是要保证未升级客户端(把 OP_CHECKSIGEX 看作 OP_NOP4)执行后栈顶非零,不被误判为脚本失败。
签名哈希的规范化在造签名与验签名时都要做:把交易全部输入的 ID 换成规范化变体、剥除签名脚本后再哈希。原文还提到 Schnorr 的附带收益,如聚合后多签体积与单签相当、批量验签更快,但把更复杂的应用留给后续 BIP。
4.3 UTXO 跟踪
收到版本 2 交易的客户端,除实例 ID 外还要算出规范化 TXID、给其输出做标注,并存入 UTXO 集;输出仍按实例 ID 索引。版本 1 交易的输出不做规范化标注,其哈希在输入规范化时不改。
原文算过成本:为未花费输出所属交易多存一个哈希,按当时约 700 万笔带未花费输出的交易估计,磁盘约多占 224 MB。采用版本 2 显式选择加入,也避免了重算整条链所有既有输出的规范化 ID(还要递归算到 coinbase,实现复杂)。
coinbase 为何能例外保留签名脚本,原文也做了核查交代:
它检查过 coinbase 交易的哈希碰撞,除区块高度 91842 与 91880 两处已知相同的 coinbase(即 BIP-30 所记的重复交易、并促成 BIP-34 的那类情形)外,没有发现碰撞。
再加上 coinbase 交易在区块之外传输无效、且要埋入链中很久才成熟,原文据此认为这一例外是安全的。
这段核查是理解 4.1 中 coinbase 例外的钥匙:例外不是疏忽,而是论证过的取舍。
五、谁必须动
普通持币人:不直接操作。是否使用版本 2 交易与新脚本,由钱包决定;未升级钱包按原文仍能跟踪输入,只是认不出 OP_CHECKSIGEX 输出是自己的,升级并重扫链后才能找回。
钱包开发者:要实现规范化 ID 的计算与 UTXO 标注、OP_CHECKSIGEX 版本 1 的 Schnorr 签名与验签,并按新标准脚本格式构造 m-of-n(含 1-of-1)输出。
全节点与矿工:升级客户端才能验证新脚本与跟踪规范化 ID;未升级节点把 OP_CHECKSIGEX 当 OP_NOP4,仍会接受含此类交易的区块。
共识层:本 BIP 是软分叉提案——以替换 OP_NOP4 的方式生效,原文自评对网络影响很小。状态虽已关闭,读其规则仍应按一份完整的软分叉设计来读。
六、现在算不算数,以及一个常见误读
状态为 Closed(已关闭)。这份以“另算一个不含签名的 ID、再在 UTXO 里双轨跟踪”为核心的方案没有成为主网规则;今天要看签名与 txid 如何实际脱钩,应去看已部署的 BIP-141 如何用 txid 与 wtxid 双 ID 处理同一问题——两者的结构对应关系,正是这份关闭文档留给读者的主要价值。
原文的理据部分坚持一个判断:规范化 ID 是可证明不可延展的,因为签名哈希里纳入的数据,其完整性都同时被签名本身证明,任何让哈希改变的修改都会同时让签名失效。
常见误读:
“规范化 TXID 就是把交易 ID 换个算法重算一遍,其他什么都不用改。”
不对。原文方案是三件套:ID 计算、新操作码 OP_CHECKSIGEX(签名创建与验证都要先规范化交易)、以及 UTXO 集对规范化 ID 的额外跟踪与标注,缺一不可。只改哈希口径、没有新脚本与跟踪规则,依赖交易的安全性并不会自动成立。
七、延伸
- 阅读版:https://bips.dev/140
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0140.mediawiki
- 相关:BIP-62 处理可塑性、BIP-66 严格 DER 签名、BIP-11 M-of-N 标准交易、BIP-141 隔离见证(共识层)
- 仓库总目录:https://github.com/bitcoin/bips
