作者:Anony
一个重要但很少被强调的事实是,比特币交易被构造和验证的方式会影响用户的资金安全性。因为其构造方式决定了用户可以多大程度上精确地表达自己的同意(而不会容易出现无意或被有意引导的误操作),而其被验证的方式也直接影响了资金被非法花费的可能性。而无论是资金可能被盗窃,还是可能在使用过程中被诈骗,都给用户的财产安全带来了威胁、影响了比特币作为一种健全货币的价值。这一环的安全,可被定义为 “交易操作安全性”。
比特币的交易操作安全性并不是一成不变的,事实上人们设想了也实施了许多加固措施。本文将回顾已经执行的软分叉中带来此种加固措施的部分。检视比特币在交易操作安全性上的进步,可以帮助我们解决社区中许多长期存在的迷思。
Bitcoin 0.3.5:禁用 OP_RETURN
发布于 2010 年 7 月 28 日 [2],修复了一个允许任何人花费任何比特币的致命 bug。
要了解这个 bug 是怎么造成的,我们先要了解比特币中的 “脚本”。
比特币资金的形式是交易的输出(TXO),而在一笔资金被创造出来的时候,交易要以 “脚本公钥(scriptPubkey)” 的形式指定该笔资金的花费条件。这个脚本公钥就构成了一种验证程序,验证尝试花费该资金的交易。而在该笔资金被实际花费(被引用为一笔交易的输入)时,交易需要提供一段 “脚本签名(scriptSig)”,传入一些数据以通过脚本公钥验证程序的验证。
如这个 Bitcoin Stack Exchange 回答 [3] 所指出的,中本聪在设计脚本系统时可能仅有一种直觉(脚本系统可以启用 “单公钥签名验证” 之外的验证方式)而没有深思熟虑,因此,其最初为比特币脚本实现的工作方式是有缺陷的:花费交易所附带的脚本签名,会跟其输入资金的脚本公钥直接拼接(脚本签名 + (OP_分隔符) + 脚本公钥
),然后传入堆栈(stack)中求值。
并且,当时 OP_RETURN
操作码的行为是直接跳到脚本的末尾(而不是让交易失败)。
这就意味着,通过使用 OP_TRUE OP_RETURN
这样的脚本签名,你可以花费任何比特币:无论这笔比特币的脚本公钥是什么,最终的脚本都是 OP_TRUE OP_RETURN XXX
;先推入堆栈的是 OP_TRUE
,在堆栈中留下一个 TRUE
布尔值;然后是 OP_RETURN
,该操作码被处理时,会把后面的 XXX
也即脚本公钥完全跳过,堆栈中只剩 TRUE
,脚本通过。这就完全绕过了脚本公钥所定义的验证程序!
Bitcoin 0.3.5 的修复方式是改变 OP_RETURN
的行为,使得它在处理时会让脚本求值失败、退出。 这最终让 OP_RETURN
成了一个标记不可花费输出的方式。
几天后(8 月 1 日),Bitcoin 0.3.7 [4] 发布,自此,脚本签名和脚本公钥会 “分别求值”:脚本系统会先将脚本签名推入堆栈求值(实际上就是对脚本签名提出一些要求),确定没有问题之后,再完全复制堆栈,然后将脚本公钥推入堆栈中求值。这实际上是一次硬分叉 [5],但它完全消除了上述缺陷。
Bitcoin 0.3.10:修复数值溢出漏洞;禁用 OP_CAT
发布于 2010 年 8 月 15 日 [6]。
修复了一个数值溢出漏洞,该漏洞允许无限量增发比特币。具体来说,当时的比特币软件会检查每一个交易输出的面额是否为负值(类同于增发),以及输入的面额总和是否大于等于输出的面额的总和,但是,没有检查所有输出的面额总和是否为负值。那么,当一笔交易有两个(或更多)非常大面额的输出时,其总和(由于超过一定长度的数字可以表达的上限)会 “溢出” 为负数,然后恰好通过 “输入面额总和 大于等于 输出面额总和” 的检查,无论其输入的面额是多大。
在 8 月 15 日,最早被发现的一笔利用该漏洞的交易 [7] 使用面额为 0.5 BTC 的输入创造除了面额总和高达 50 BTC 的输出。并且,保留了这笔交易的 “坏链” 在挖出 51 个区块之后 [8],才被修复之后的链打败。
在同一次软分叉中,还有一些脚本操作码出于安全理由而被禁用 [9]。其中最著名的当属 “OP_CAT”。该操作码的行为是将两个数据前后拼接,被认为具有非常强大的功能 [10]。但在当时,由于未实现限制措施,它可以用来创造一种会导致节点宕机的脚本:对一段数据不断重复调用 OP_DUP OP_CAT
。第一个操作码会复制该数据,然后第二个操作码会将两段数据前后拼接; 每调用一轮,数据长度就倍增一次,需要处理的数据的长度会呈平方级上升,最终让节点无法处理、崩溃。
BIP30:禁止出现相同的交易 ID
“交易 ID(txid)” 是交易的唯一标识符,对比特币交易和钱包的工作至关重要。钱包需要交易 ID 来追踪一笔交易是否得到了处理(被纳入了区块);全节点在验证交易时,需要确认交易输入是否存在、有多少面额,而这种检查就依赖于交易输入的 “输出点(output point)” 信息,它的形式是 交易 ID : 索引号
,意思是 “我是这个 ID 所代表的交易的第 n 个交易输出”。
如 BIP30 [11] 所述,人们曾经以为比特币交易 ID 是不会重合的(因为用到了哈希算法,而且依赖于前序交易),但实际上,coinbase 交易(发行新的 bitcoin 并收集区块内手续费的交易)的 ID 很容易重合,基于相同交易 ID 的 coinbase 也容易产生相同 ID 的后序交易。
因此,BIP30 要求,如果一笔交易未被花费掉,那么不允许产生具有相同 ID 的新交易:如果一个区块打包了这样会产生混淆效果的交易,该区块需要被拒绝。
自 2012 年 9 月开始,这条规则开始全面应用。只有两个历史区块(91842 和 91800)被特殊处理。
BIP 34:要求 coinbase 交易包含区块高度
此举进一步强化了每个区块的 coinbase 交易的唯一性,缓解了上述 BIP30 要缓解的问题。同时,它也使 coinbase 交易的花费变得更容易验证:比特币的共识要求一笔 coinbase 交易至少要静置 100 个区块才能花费,要求 coinbase 交易携带其创生高度的信息让这种检查变得更加容易。
自 2013 年 3 月 24 日 [12] 开始,这条规则开始应用。
隔离见证升级
隔离见证创造了一种新的交易结构,并允许新的输出类型使用这种交易结构。这种新的结构解决了过往的比特币交易在安全性上的一些长期存在的问题。
概要来说,这种结构将原本要放在交易输入的 脚本签名
字段中的数据放到了一个单独的、属于交易但并不属于某个输入的 witness(见证)
字段中。这也正是其名字 “隔离见证(SegWit,segregated witness)” 的由来。要了解它所带来的提升,我们必须先了解旧的交易结构的一些问题。
交易的第三方熔融性问题
比特币的交易 ID 由该交易的 “内容” 决定,而交易输入的脚本签名也是这 “内容” 的一部分。也即,如果脚本签名改变了,即使交易的实质意义 —— 花费某一些输入,形成某一些输出 —— 并没有改变,其交易 ID 也会改变。
巧合的是,并没有机制能够保证脚本签名一定不会被第三方改变。你可能想到了公钥的数字签名,但很遗憾,用在比特币交易中的签名会承诺交易的许多部分,但并不承诺交易输入的脚本签名 —— 因为它自身也是这个脚本签名的一部分,如果要求它承诺脚本签名,就会形成一个循环依赖,使我们不可能得到签名。
于是,麻烦来了。在一些情况下,第三方可以毫不费力的改变输入的脚本签名,而不会使脚本签名字段中的数字签名作废,在并不改变交易实质意义的前提下,神不知鬼不觉地让它具有一个完全不同的交易 ID,让网络上的节点认为这是两笔不同的交易。这就是所谓的 “第三方熔融性” 问题。它让交易 ID 变成了一种完全不可靠的追踪交易的手段,钱包将无法通过交易 ID 来了解一笔交易到底有没有得到区块确认:也许它进入区块了,但却拥有一个完全不同的 ID。钱包将只能通过完全比对每一笔交易的输入和输出来知晓一笔交易有没有得到区块确认(不可能穷尽一笔交易可能具有的交易 ID),这效率会大打折扣。
隔离见证将原本放在脚本签名中的数据(用来通过脚本公钥所定义的验证程序的数据)放到不会影响交易 ID 的专门字段中,釜底抽薪地解决了这个问题。只要交易输入和输出是确定的,交易 ID 就是确定的。即使能熔铸 witness,也不能改变交易的 ID。
第二方熔融性问题
如前所述,如果交易输入的脚本签名改变了,那么交易 ID 就会改变。但是,并不是只有第三方可以改变脚本签名,为交易提供输入的人也始终具有改变脚本签名的能力(最简单的方法就是改变放在脚本签名中的数字签名,因为有效的签名不是唯一的)。而且有时候,他们是有动力这样做的。
在隔离见证升级的讨论期间,人们已经开始研究在比特币上编程 “合约式协议” —— 允许多方以比特币的资金形式和脚本系统为基础,在内部运行电子化的经济合约 —— 的方法。闪电通道(两方可以在内部无限次相互支付的合约)一个著名的例子。人们发现,一种关键的技巧叫做 “承诺交易”。就是以有效但并不公开的比特币交易,来提供一种可信的承诺。
一种具体的用途是,比如,假定双方要参与一个打赌合约,这意味着他们要把钱锁入一个使用 2-of-2 多签名脚本公钥的交易输出中(这笔交易可称为 “注资交易”),这种脚本公钥意味着谁都不能仅凭自己的公钥签名就将资金全部卷走,这显然是运行合约的基础;然而,一旦将资金锁入,其中一方不合作、不再响应,资金就会在其中一直锁定,受害的一方也无法独自撤回资金(甚至作恶的这一方可以趁机勒索);为了应对这种风险,双方可以预先(在锁入资金之前)构造并签名一笔花费该 2-of-2 多签名输出、将资金退回给双方的交易,在其中一方没有响应的一段时间之后,允许另一方以这笔交易自救。
然而,隔离见证以前的比特币交易实际上无法让这样的承诺交易获得安全性。因为这是一笔预先签名的交易,其有效性以双方签名的有效性为前提;而签名承诺了交易的输入;交易的输入以其 “输出点” 界定。也就是说,这笔交易有效(它可用来保护用户)的前提是这个 2-of-2 输出真的被具有这样交易 ID 的交易创造出来,否则,被创造出来的 2-of-2 输出就将具有另一个输出点,而这笔交易也不能用来花费它。
而我们前面讲过,交易输入的提供者(在交易被确认之前)是可以改变交易的 ID 的,只需要换个数字签名就行。假定 Alice 要攻击 Bob,她可以在双方构造并广播 Bob 预期的注资交易之后,通过改变其数字签名,让实质相同的交易具备另一个 ID。假定是这个恶意版本交易得到区块确认,它所形成的 2-of-2 多签名输出会具有另一个输出点,Bob 得到的预先签名的退出交易就作废掉了。Bob 重新陷入了可被勒索的境地。
这就是 “第二方熔融性” 问题。隔离见证同样也解决了这个问题。当一笔交易只有隔离见证输入(而没有传统类型的输入)时,其交易 ID 是确定的;这个确定的交易 ID 可以保证预先签名的承诺交易是有效的,参与者可以得到这样的承诺交易的保护。
(传统脚本类型包括 P2PK、P2PKH 和 P2SH。)
现在你知道隔离见证是意义多么重大的升级了吧!
在一系列思想和力量的交锋之后,比特币在 2017 年 8 月 24 日迎来了隔离见证升级,为比特币的交易操作安全性翻开了全新的篇章。
Taproot 升级
在隔离见证的基础上,Taproot 通过一个 “小” 设计进一步加强了比特币的交易操作安全性。
在 Taproot 脚本规则中,用公钥来验证的签名要承诺更多东西。 在默认的 SIGHASH_ALL 模式下,用于一个 Taproot 输入的签名除了要承诺该交易所有输入的输出点,还要承诺所有输入的面额,以及所有输入的脚本公钥 [13]。(在传统输入类型中,在上述几项中签名只需承诺所有输入的输出点 [14];在隔离见证输入中,增加了一项,是所在输入的面额 [15])。
增加这些项目(意味着签名的有效性变得更加严格、输入提供者的交易意图表达得更加准确)有什么好处呢?
其中一大好处是大大便利了硬件签名器的使用。众所周知,硬件签名器 [16] 是一种专用的离网设备,用来保存私钥和签名比特币交易。这意味着它不可能知道一个交易输入有多大面额,这都是联网且具备一定计算能力的设备才能知道的事。问题是,如果它不知道输入有多大价值,它实际上就不知道自己正在签名的交易会提供多少手续费。因此,就有可能诱导用户提供价值很大的输入,然后以手续费的形式让用户损失资金,硬件签名器将并不能成为你的屏障。
以往,解决这个问题的方式是向硬件签名器提供交易的每一个输入的前序交易(严格来说需要所有前序交易,一直能追溯到 coinbase 交易的交易链条),让硬件签名器能独立地验证每一个输入的价值,并显示出正确的手续费。但现在,由于硬件签名器签出的签名承诺了交易其它输入的面额,相等于说:“如果你们在输入的价值上骗了我,不好意思,你们会得到一个无效的签名”,这就消除了这种诱骗可能性,让硬件签名器可以轻松签名而不必先执行一系列的面额验证。
另一大好处来自于签名也承诺所有输入的脚本公钥。如前所述,由于传统输入的脚本签名是可以熔铸的,这导致需要可靠交易 ID 的交易(比如前述 “注资交易”)必须拒绝传统类型的输入,以防止交易 ID 改变。但是,你如何确保一个输入不是传统类型的输入呢?在 Taproot 升级之前,你只能获得该输入的完整前序交易 [17],来验证该输入的类型;但现在,如果你使用 Taproot 输入,你可以相信其他人会为输入给出真实的脚本公钥(相应便知道其类型),因为 “如果你们在输入的脚本公钥上骗了我,不好意思,你们会得到一个无效的签名”。
总之,Taproot 大大改善了包括硬件签名器在内的资源有限的 硬件/程序 在参与多方输入交易时候的安全性 [18];这样的交易不仅包括合约式协议的注资交易,还包括 Coinjoin 这样的混合交易。都是我们未来会越来越多用上的交易。
此外,Taproot 升级还引入了 “MINIMALIF” 规则,规定在执行 OP_IF
这样的流程控制操作码时,只能使用数字 1
来获得 True
语句的执行,而不像原来那样,只要非零数字就可以。这也消除了一种第三方熔融性界面。
在介绍 Taproot 升级时,人们常常强调其最亮眼的部分:Schnorr 签名和默克尔脚本树。但上述不起眼的东西,实际上对交易操作安全性有巨大提升,是不应该被忽略的。
2021 年 11 月 14 日,在万众期待中,比特币激活 Taproot 升级。
广义 “安全性” 有关的软分叉
如你所见,本文没有历数所有的软分叉 [1]。因为许多软分叉都不是为了我们这里所说的交易操作安全性而激活的。
不过,如果我们采取一个更宽松的 “安全性” 定义,例如,包括比特币整个系统的一些安全性的,显然会有更多的软分叉进入我们的视野。其中有一些可能比上述一些软分叉更强有力地塑造了比特币的性格(比如在 2010 年 9 月加入的 1MB 区块体积限制)。
不过,如果要采取这种广义视角,那么还得指出,有许许多多的影响安全性的漏洞,都不是以软分叉的形式修复的,它们可能出现在各种环节。对此,Bitcoin Wiki 提供了一个非常好的清单 [19]。
结语
很长一段时间以来,都有一种迷思认为比特币的创造者中本聪是一位近乎于神的人物、在各种事情上都做得天衣无缝。连带着,这种迷思会将比特币也描述成一个一出生就通体无瑕,不需要改进(至少不需要结构上的改进,而只需要参数上的改变)的东西。
我得说,这不是真的,而且没有比这对比特币更危险的想法了。
如前所述,今天吸引无数开发者探索其可能性的比特币脚本系统,在中本聪自己作出的初版实现中就有致命漏洞。中本聪自己也发现了这一点,并且与其他开发者一起修复了它。而一直到他隐入人海的那一刻,比特币事实上都不能够安全地运行合约式协议,能做到这一点的交易结构要等到他离开的 7 年后(2017 年)才成为现实。
为中本聪赋予神性的同时,人们也否认了现实、低估了比特币要成为一个坚实、可靠、能够存活数百年的系统所面临的挑战,并且(傲慢地)忽视了人们作出的各种改进比特币的努力。这不公允,对中本聪本身也不公平。
我希望这篇文章也对另一些朋友有所帮助。如果你犹豫要不要运行新版本的比特币软件、要不要使用新的比特币输出类型的话,这篇只关注交易操作安全性的文章可以告诉你,新的输出在哪些方面更能保障普通用户的操作安全性。撇开那些令人炫目的新特性,比特币的软分叉也在持续改变普通用户的体验和资金安全性。只能说,到目前,我们还算有进步,可以尝试。
(完)
脚注
1. https://www.btcstudy.org/2022/09/05/a-complete-history-of-bitcoins-consensus-forks/ ↩
2. https://en.bitcoin.it/wiki/Common_Vulnerabilities_and_Exposures#CVE-2010-5141 ↩
3. https://bitcoin.stackexchange.com/a/29763 ↩
4. https://github.com/bitcoin/bitcoin/commit/73aa262647ff9948eaf95e83236ec323347e95d0 ↩
5. https://bitcoin.stackexchange.com/questions/111673/was-bitcoin-0-3-7-actually-hard-forking ↩
6. https://github.com/bitcoin/bitcoin/commit/08fee75201e82f2e34fcc1549ee8edd152f5d040 ↩
7. https://bitcointalk.org/index.php?topic=822.0 ↩
8. https://bitcointalk.org/index.php?topic=823.msg9734#msg9734 ↩
9. https://github.com/bitcoin/bitcoin/commit/4bd188c4383d6e614e18f79dc337fbabe8464c82#diff-8458adcedc17d046942185cb709ff5c3R94 ↩
10. https://blog.blockstream.com/cat-and-schnorr-tricks-i/ ↩
11. https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki ↩
12. https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki ↩
13. https://github.com/bitcoin/bips/blob/deae64bfd31f6938253c05392aa355bf6d7e7605/bip-0341.mediawiki#signature-validation-rules ↩
14. https://en.bitcoin.it/wiki/OP_CHECKSIG ↩
15. https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification ↩
16. https://www.btcstudy.org/2022/12/19/what-is-bitcoin-hardware-wallet-by-unchained-capital/ ↩
17. https://delvingbitcoin.org/t/malleability-issues-when-creating-shared-transactions-with-segwit-v0/497 ↩
18. https://www.btcstudy.org/2021/11/02/the-taproot-upgrade-explainer-from-Suredbits/#%E6%96%B0%E7%9A%84-SigHash-%E7%AE%97%E6%B3%95 ↩
19. https://en.bitcoin.it/wiki/Common_Vulnerabilities_and_Exposures#CVE-2010-5141 ↩