作者:David A. Harding
来源:https://gist.github.com/harding/dabea3d83c695e6b937bf090eddf2bb3
本文介绍了中本聪的论文 “比特币:一种点对点的电子现金系统” 的已知问题,同时指出了术语上的变更以及比特币的实现何以不同于这篇论文的描述。
摘要部分
“最长的链不仅证明了得到见证的事件的顺序,也证明了这样的顺序产生于 CPU 算力最大的群体(池子)。”
实现细节:如果链上的每一个链接(在比特币中被称为 “区块(block)” 都是使用相同量级的工作量证明(PoW)构筑的,那么最长的链确实就是有最大的算力池支持的链。但是,比特币的实现方式是每个区块的 PoW 量级都可以不同,所以,重要的就不是跟踪 “最长的链” 而是 “有最多 PoW 的链”,通常也被缩写为 “最健壮的链”。
从检查最长链到检查最多工作量的链的变更,发生在 2010 年 7 月,是在比特币初次发行很晚以后:
- if (pindexNew->nHeight > nBestHeight) + if (pindexNew->bnChainWork > bnBestChainWork)
术语变化:通用的 CPU 曾在比特币早期用于为区块生成工作量证明,但在今天,绝大部分工作量证明都是由专门设计的专用芯片(ASIC)产生的。所以,与其说 “CPU 算力”,说 “算力” 可能更加准确,或者直接叫 “哈希率(hash rate)”(生成 PoW 的哈希计算)。
只要大部分的 CPU 算力都由不参与攻击网络的节点控制,他们就能生成最长的链条并抵御攻击。
- 术语变化:“节点” 一次在今天指的是全验证节点(full validation nodes,也称 “全节点”),它们是强制执行比特币系统所有规则的程序。而延伸比特币链的程序(和硬件)今天称为 “矿工”,来自中本聪在白皮书第六章的黄金矿工比喻。今天,绝大部分的节点都不是矿工,而且许多拥有挖矿设备的个人都不会为之搭配自己的节点(甚至有些使用了全节点的矿工也会在新发现的区块后直接挖矿,而不会等待自己的节点校验完成、确保新区块是有效区块再行动)。在中本聪论文的前半部分,“节点” 通常不改原意,指的是使用全验证节点的挖矿程序;而在后半部分,“节点” 通常指的是 “网络节点”,主要讲的是网络节点的行为(即使它们并不挖矿)。
- 出版之后的发现:当一个新区块产生时,生产这个区块的矿工可以立即基于这个区块开始挖矿,所有其他矿工都必须等待新区块通过网络传播给他们(然后才能开始)。这给了挖出许多区块的矿工对挖出更少区块的矿工的优势,而且这一点可以被利用来发动所谓的 “自私挖矿攻击”:持有 30% 的全网哈希率的矿工可以让其他矿工收益下降,甚至驱使他们跟随自身的策略。所以,与其说 “只要大部分 CPU 算力都由不参与攻击网络的节点控制”,也许说 “只要协作攻击网络的矿工持有不到 30% 的全网算力” 更为准确。
第二章(交易)
“我们以数字签名的链条定义了一种电子货币。每个所有权人都通过签名前一笔交易的一个哈希值以及下一任所有权人的公钥,来转移货币;这些内容也会添加到现有相关货币的末端。”
- 实现细节:比特币实现了上述系统的一个更加通用的版本:并不直接使用数字签名,而是使用一种 “确定性的表达”。就像一个匹配某个公钥的签名可以授权一笔支付,满足一个已知表达式的数据也可以授权一笔支付。广义来说,在比特币中,这个为了花费货币而必须被满足的表达式叫做 “留置(encumbrance)”(译者注:就是大家平时说的 “锁定脚本”)。在比特币中,迄今为止,几乎所有的留置都要求提供至少一个数字签名。所以,与其说是 “数字签名的链条”,不如说是 “留置的链条”。
第四章(工作量证明)
“我们通过在区块中加入一个 nonce 实现了工作量证明;在寻找工作量证明时,可以不断递增 nonce 的值,直到找出一个值,使得区块的哈希值的开头有足够数量的 0。”
- 实现细节:Adam Back 的 Hashcash 实现需要找出一个开头有足够多的 0 的哈希值。比特币把哈希值当成一个整数,并要求它小于某个整数;所以比特币实际上允许指定小数位。
“PoW 本质上就是一 CPU 一票。”
- 重要提醒:这里的 “投票” 并不能用于决定系统的规则,只用于决定交易的顺序,以提供这种 “电子货币” 无法轻易被重复花费的保证。这一点在第十一章中有更详细的描述:“我们考虑一个攻击正尝试以更快速度生成一条替代链的例子。即使攻击者做到了,也不意味着系统允许 TA 作出任意变更,比如凭空产生货币、花费不属于 TA 的资金。节点不会认可无效的交易,诚实的节点不会接受一个包含了无效交易的区块。”
“工作量证明的难度是由一个瞄准每小时的平均出块数量的移动平均值决定的。”
实现细节:移动平均值已不再使用。相反,每 2016 个区块会报告一个生成时间,跟一个更早的区块的生成时间相比较,其中的差值将用于计算调整需要用到的平均值。
而且,比特币中实现的平均值瞄准的是每两周生成的平均区块(而不是文本中暗示的每个小时)。已经实现的其它规则可能会进一步减少调整量,例如有一条规则是调整不能让每个周期的区块生产量提高 300% 以上,也不能使之降低 75% 以上。
第七章(减少空间占用)
“一旦某个币的最新交易已被埋在足够数量的区块之下,这笔交易以前的交易都可以删除,已节省硬盘空间。”
- 可能出版后发现:虽然这一章中的默克尔树结构可以证明一笔交易被包含到了某一个区块中,但在当前的比特币中,没办法证明某一笔交易尚未被花费,你只有处理这笔交易上链后的所有数据才行。这意味着,这里描述的方法无法被所有节点用于拿回硬盘空间,因为新的节点需要处理所有交易。
第八章(简易支付验证)
“一种对抗策略是接收来自网络节点的警告,当节点发现一个无效区块时,会提醒用户的软件下载完整的区块和警告交易,以确认交易的不一致性。”
- 重要提醒:虽然曾经有软件实现了这一章的某些部分,叫做 “简易支付验证(SPV)”,这些程序实际上不会从网络节点(全节点)处接收发现无效区块的警报。在过去,这曾经导致放在这样的 SPV 钱包中的比特币面临风险。
第十章(隐私性)
“一些关联依然不可避免,例如多输入的交易必然会曝光这些输入都由同一个人拥有。”
出版之后的发现:如果资金的所有者经常将自己的输入和其他人的输入混合,他们的同一笔交易的不同输入就并不必然属于同一个人。举个例子,Alice 和 Bob 每人为支付给 Charlie 和 Dan 的交易贡献一个输入,跟 Alice 自己一个人提供两个输入然后支付给两人,从表面上看是没有区别的。
今天,这种技术叫做 “CoinJoin”,从 2015 年开始已经有了许多软件实现。
第十一章(计算)
“支付的接收者生成一对新的密钥,并在发送者签名交易之前把公钥交给发送者。这可以防止发送者提前准备好一长串的区块并持续不断地开挖、直到自己足够幸运将它们全部挖出,然后才执行这笔交易。”
出版后的发现:支付的接收者在发送者签名之前才生成公钥,完全无法防止发送者提前准备好一长串的区块。早期的比特币用户 Hal Finney 发现了这种攻击并解释说:“假设攻击者正随意生成区块,并且在每一个生成的区块中,都包含一笔从地址 A 到地址 B 的转账,这两个地址都是他自己的。”
“为了欺骗你,他生成区块时,并不广播出去。相反,他来到你的商店,并给属于你的地址 C 支付。你等了一会,没有发现什么异常,于是发了货。他现在把自己提前准备的区块放出来,于是他左手倒右手的交易优先于你的交易。”
这种攻击对任意的确认数量要求都有可能奏效,有时候也被称为 “芬尼攻击”。
- - -
免责声明:本文的作者不是上述任何问题的第一发现人 —— 他只是收集了这些问题的描述、归纳成了一篇文章。
更新:
- 2018-06-14:添加了中本聪改变共识凝聚机制从最长链到最多工作量的代码提交的链接。代码提交考古学的贡献归于 Gregory Maxwell。
- 2018-06-14:移动平均值已不再使用。一个 IRC 频道提到了此事,但该频道禁用了日志。
- 2018-06-14:延迟的密钥分发不能防止攻击者提前准备好分叉,例如芬尼攻击。Kalle Rosenbaum 在 Twitter 上提到了。
- 2018-06-14:得益于混币,同一笔交易的多个输入并不必然导致隐私降级。Chris Belcher 在 GitHub 评论中提及。
(完)