区块链-多重签名中CHECKMULTISIG执行中的bug

目录:http://www.javashuo.com/article/p-nitsjfvy-ey.html.net

来看看多重签名(http://www.javashuo.com/article/p-csbaluys-gn.html)一个例子,比特币的正确的验证脚本以下:code

0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

对比多重签名中的验证脚本会发现,这个正确的验证脚本前面多了个0,这个0是什么回事?blog

这是由于在CHECKMULTISIG的执行中有一个bug,来看看这个bug是怎么产生的。get

<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

首先根据“错误”的脚本,CHECKMULTISIG弹出最上面的项目,这是N(在这个例子中N是“3”)。class

而后它弹出N个项目,这是能够签名的公钥。在这个例子中,公钥 A,B 和C。比特币

而后, 它弹出一个项目,即M,仲裁(须要多少个签名)。这里M=2。此时, CHECKMULTISIG应弹出最终的 M 个项目,这些是签名,并查看它们是否有效。bug

然而,不幸的是,实施中的错误致使CHECKMULTISIG再弹出一个项目(总共M+1个)。检查签名时,不考虑额外的项目,所以它对CHECKMULTISIG自己没有直接影响。可是,必须存在额外的值,由于若是不存在,则当CHECKMULTISIG尝试 弹出空堆栈时,会致使堆栈错误和脚本失败(将交易标记为无效)。由于额外的项目被忽略,它能够是任何东西,但一般使用0。qq

0取出后它被马上丢去,并不用于以后的运算。这个数具体是什么值可有可无,可堆栈上不能缺失它,否则没法正常执行CHECKMULTISIG。为了保证共识机制的向后兼容,每一个解锁脚本就多压入一个零,而CHECKMULTISIG实现代码里的错误只能年复一年地遗留下来。脚本

来看正确的脚本执行:项目

0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

解锁脚本是:

0 <Signature B> <Signature C>
// 首先运行解锁脚本
0: [] // 初始状态:空堆栈
1: [0] // 0 被压入栈顶
2: [0, 签名B] // PUSHDATA 入栈第一个签名
3: [0, 签名B, 签名C] // PUSHDATA 入栈第二个签名
4: [0, 签名B, 签名C, 脚本] // PUSHDATA 入栈完整脚本
   // 复制堆栈后运行上锁脚本
5: [0, 签名B, 签名C, 脚本哈希] // HASH160 取出栈顶脚本,压入它的哈希
6: [0, 签名B, 签名C, 脚本哈希, 目标脚本哈希] // PUSHDATA 入栈目标脚本哈希
7: [0, 签名B, 签名C, TRUE] // EQUAL 取出栈顶两个哈希,判断是否相等,压入检测结果
   // 取出栈顶结果,若是非真,则验证失败,不然运行原始脚本
8: [0, 签名B, 签名C] // 准备运行原始脚本
9: [0, 签名B, 签名C, 2] // 2 被压入栈顶
10: [0, 签名B, 签名C, 2, 公钥A] // PUSHDATA 入栈第一个公钥
11: [0, 签名B, 签名C, 2, 公钥A, 公钥B] // PUSHDATA 入栈第二个公钥
12: [0, 签名B, 签名C, 2, 公钥A, 公钥B, 公钥C] // PUSHDATA 入栈第三个公钥
13: [0, 签名B, 签名C, 2, 公钥A, 公钥B, 公钥C, 3] // 3 被压入栈顶
14: [0, 签名B, 签名C, 2, 公钥A, 公钥B, 公钥C] // CHECKMULTISIG 先取出公钥数N(3)
15: [0, 签名B, 签名C, 2] // 接着取出N(3)个公钥
16: [0, 签名B, 签名C] // 再取出签名数M(2)
17: [TRUE] // 接着取出M(2)个签名和数字0,根据交易验证签名,压入检查结果
    // 栈顶为真则验证成功