《比特币:一种点对点电子货币系统》详解
最近开始学习比特币,第一步就是阅读它的白皮书:https://bitcoin.org/files/bitcoin-paper/bitcoin_zh_cn.pdf。白皮书其实不长,中文版只有 9 页,很快就能读完。不过读完后我发现,里面省略了不少细节,只讲了大致的思路。为了搞清楚具体流程,我又查阅了很多资料,结合具体的 Bitcoin Core 程序实现。在这篇文章里,我会按段落列出白皮书内容,并加上自己的扩充,希望能帮到也在学习比特币的你。
我添加的段落,会以“注:”开始。
摘要:一种完全的点对点电子货币应当允许在线支付从一方直接发送到另一 方,而不需要通过一个金融机构。数字签名提供了部分解决方案,但如果仍需 一个可信任的第三方来防止双重支付,那就失去了电子货币的主要优点。我们 提出一种使用点对点网络解决双重支付问题的方案。该网络通过将交易哈希进 一条持续增长的基于哈希的工作量证明链来给交易打上时间戳,形成一条除非 重做工作量证明否则不能更改的记录。最长的链不仅是被见证事件序列的证 据,而且也是它本身是由最大 CPU 算力池产生的证据。只要多数的 CPU 算 力被不打算联合攻击网络的节点控制,这些节点就将生成最长的链并超过攻击 者。这种网络本身只需极简的架构。信息将被尽力广播,节点可以随时离开和 重新加入网络,只需接受最长的工作量证明链作为它们离开时发生事件的证 据。
简介
互联网贸易已经变得几乎完全依赖金融机构作为可信任的第三方来处理电子支付。尽管对于大部分交易这种系统运行得足够好,但仍需忍受基于信任模型这个固有缺点。由于金融机构不可避免的需要仲裁纠纷,完全不可撤销的交易实际是做不到的。仲裁成本增加了交易成本,限制了最小实际交易额度从而杜绝了日常小额交易的可能性,而且由于不支持不可撤销支付,对不可撤销服务进行支付将需要更大的成本。由于存在交易被撤销的可能性,对于信任的需求将更广泛。商家必须警惕他们的客户,麻烦他们提供更多他本不需要的信息。一定比例的欺诈被认为是不可避免的。虽可通过当面使用实物货币来避免这些成本及支付的不确定性,但不存在一个无可信任方而能在通信通道上进行支付的机制。
我们需要的是一个基于密码学原理而不是信任的电子支付系统,该系统允许任何有交易意愿的双方能直接交易而不需要一个可信任的第三方。交易在计算上的不可撤销将保护卖家不被欺诈,用来保护买家的程序化合约机制也应该较容易实现。
在这篇论文中,我们提出一种使用点对点分布式时间戳服务器为基于时间的交易序列生成计算上的证据来解决双重支付问题的方案。只要诚实节点集体控制的CPU 算力大于任何一个合作攻击节点群的CPU 算力,这个系统就是安全的。
注:中本聪在2009年1月3日挖出了比特币区块链中的第一个区块,他在区块的矿工注释区写入了
The Times 03/Jan/2009 Chancellor on brink of second bailout for banks
这句话来自《泰晤士报》(The Times)2009年1月3日的头版标题,意思是“财政大臣正站在第二轮救助银行业的边缘”。这有些耐人寻味。有观点认为,救助银行往往是道德败坏的表现——银行家低息揽储,高息放贷,本质上是一种寄生。如果银行经营不好快要倒闭,政府公众还去救,这难道不更不公平吗?
由此可见山本聪对传统金融行业的不满,也注解了比特币“创世”(创建首个区块)的动机。
交易
我们将一枚电子货币定义为一条数字签名链。每个拥有者都通过将上一次交易和下一个拥有者的公钥的哈希值的数字签名添加到此货币末尾的方式将这枚货币转移给下一个拥有者。收款人可以通过验证数字签名来证实其为该链的所有者。
这里的问题是收款人不能证实某个拥有者没有对此货币进行双重支付。通常的做法是引入一个可信任的中央机构或铸币厂来检查每笔交易是否存在双重支付。每笔交易之后,都需要将这枚货币退回铸币厂以换取发行一枚新的货币,只有由铸币厂直接发行的货币才能被确认没有被双重支付。这个方案的问题在于整个货币系统的命运都依赖于运营铸币厂的公司,每笔交易都需要经过它们,就像银行一样。
我们需要一种能让收款人知道上一个货币拥有者没有对任何更早的交易签名的方法。对我们来说,最早的那次交易是唯一有效的,所以我们不需要关心本次交易后面的双重支付尝试。唯一能确认一笔交易不存在的方法是知晓所有之前的交易。在铸币厂模型中,铸币厂知晓所有交易并能确定哪笔交易最先到达。在不引入一个可信任方的前提下要达到这个目的,所有交易就必须公开发布 [1],而且需要一个能让所有参与者对交易收到顺序的单一历史达成共识的系统。收款人在每笔交易时,都需要多数节点认同此交易是最先收到的证据。
注:我认为这里的图示画的不是很清楚。为什么在第二笔交易中同时需要所有者2的公钥和所有者1的签名呢?想要弄明白这个问题,我们可能要首先回到此节第一段上来。
我们将一枚电子货币定义为一条数字签名链。
这种表述有些奇怪。换言之,比特币区块链中的“比特币“并不像你的钱包夹中的百元大钞一张一张叠在一起。交易的收据才是你钱包的存货。当你向A支付比特币时,不是递给他一张”比特币钞“,而是拿出其他人给你转比特币时的收据,可能有好几张,加到一起,再给收款方重新立一张字据,写着”我转账1比特币给A,依据为如下此前收据…“,同时,此前收据在转账后就作废了。比特币转账转的不是“比特币钞”,而是“比特币收据”,每次转账都作废一张或几张此前的收据,同时立一张的收据。因此,也有人说使用比特币时花的不是比特币,而是花的比特币中的交易。一笔交易不能花两遍。
明白了这一点,就更容易理解图中第二笔交易了。“所有者2的公钥”说明了这笔交易的收款人。“所有者1的签名”就是付款人新立的字据。哈希则指向此前收据。虚线的“验证”,“签名”则分别表示,付款人的签名字迹符合此前收据中收款人的身份,付款人用自己独特的笔迹写下签名证明身份。
时间戳服务器
我们提出的方案从时间戳服务器开始。时间戳服务器计算包含多个需要被打时间戳的数据项的区块的哈希值并广泛地发布这个哈希值,就像在报纸或新闻组帖子里 [2-5]。时间戳能证明要得到这个哈希值,显然这些数据当时一定是存在的。每个时间戳的哈希值都纳入了上一个时间戳,形成一条链,后面的时间戳进一步增强前一个时间戳。
注:这一段也太简短了!我看的时候不知所云。说是时间戳,但图示和时间戳也没有关系。
实际上,这里的时间戳并不是真的指时间,而是指交易的顺序。第二个区块的哈希产生中使用了第一个区块的哈希,而第三个区块的哈希中又使用了第二个区块的哈希,以此类推,这样给所有区块定下了一个可验证的顺序。区块中包含的交易也因此定下了顺序,这就好像交易有了时间轴。
尽管比特币区块中确实含有“区块生成时间戳”这一项,但它更多是一种注释说明信息,并不是区块链核心机制的一部分。“区块生成时间戳”也不一定准确 - 比特币节点允许时间有两小时的误差!它是不可能用来给交易定序的,只用来大概估算区块链的增长速率。
工作量证明
为了实现一个基于点对点的时间戳服务器,我们需要使用一个类似 Adam Back 提出的哈希货币 [6] 的工作量证明系统,而不是报纸或新闻组帖子那样。工作量证明采取搜索一个数,使得被哈希时(如使用 SHA-256)得到的哈希值以数个 0 比特开始。平均所需工作量将随所需 0 比 特呈指数级增长而验证却只需执行一次哈希。
对于我们的时间戳网络。我们通过在区块中加入一个随机数,直到使得区块的哈希值满足所需 0 比特的数被找到的方式实现工作量证明。一旦消耗了 CPU 算力使区块满足了工作量证明,那么除非重做这个工作否则就无法更改区块。由于后面的区块是链接在这个区块后面的,改变这个区块将需要重做所有后面的区块。
工作量证明同时解决了在多数决定中确定投票方式的问题。如果多数是按 IP 地址投票来决定,那么它将可能被能分配大量 IP 地址的人破坏。工作量证明本质上是按 CPU 投票。最长的链代表了多数决定,因为有最大的计算工作量证明的算力投入到这条链上。如果多数的 CPU 算力被诚实节点控制,诚实的链就会增长得最快并超过其他的竞争链。要修改过去的某区块,攻击者必须重做这个区块以及其后的所有区块的工作量证明,从而赶上并超过诚实节点的工作。我们后面会证明随着后续的区块被添加一个更慢的攻击者赶上诚实节点的概率将呈指数级递减。
为了抵消硬件运算速度的增加及平衡不同时期运行节点的利益,工作量证明的难度将由移动平均数法来确定每小时生成区块的平均数。如果区块生成得过快,那么生成的难度就会增加。
注:我认为这一段可能是最广为人知的一段,也是原文最清晰,最容易理解的一段。笔者在此补充一些实现细节。
具体实现中,区块链是由一个一个区块头串联起来的,区块头是被哈希和指向的元素。
新的区块头需要
包含上一个区块的哈希
包含这一个区块的交易( merkle tree root hash 形式 )
包含一个“解答数字”,这个解答数字可以正好让这个新区块头的哈希值末尾位为几个0。
区块头具体长什么样,哪些数据以怎样的方式被哈希,可以参见以下表格(摘自 https://developer.bitcoin.org/reference/block_chain.html )。
区块头以下方描述的 80 字节格式进行序列化,然后作为比特币工作量证明算法的一部分进行哈希,使得序列化头格式成为共识规则的一部分。
Bytes 字节 | Name 名称 | Data Type 数据类型 | Description 描述 |
---|---|---|---|
4 | version 版本 | int32_t | The number indicates which set of block validation rules to follow. See the list of block versions below.区块版本号指示应遵循的区块验证规则集。请参阅下方的区块版本列表。 |
32 | char[32] | A SHA256(SHA256()) hash in internal byte order of the previous block’s | |
header. This ensures no previous block can be changed without also | |||
changing this block’s header.上一区块头在内部字节序中的 SHA256(SHA256())哈希。这确保了任何对先前区块的更改都必须同时更改此区块头。 | |||
32 | merkle root hash Merkle 根哈希 | char[32] | A SHA256(SHA256()) hash in internal byte order. The merkle root is |
derived from the hashes of all transactions included in this block, | |||
ensuring that none of those transactions can be modified without | |||
modifying the header. See the below.内部字节顺序的 SHA256(SHA256())哈希值。梅克尔根是从该区块中包含的所有交易的哈希值派生出来的,确保这些交易中的任何一个都无法在不修改头部的情况下被修改。请参阅下方的梅克尔树部分。 | |||
4 | time 时间 | uint32_t | The block time is a when the miner started hashing the header (according to the miner). |
Must be strictly greater than the median time of the previous 11 blocks. | |||
Full nodes will not accept blocks with headers more than two hours in | |||
the future according to their clock.区块时间是指矿工开始对头部进行哈希运算的 Unix 时间戳(根据矿工的记录)。必须严格大于前 11 个区块的中位数时间。全节点将不接受根据其时钟显示时间超过两小时的区块头部。 | |||
4 | nBits n 位 | uint32_t | An encoded version of the target threshold this block’s header hash must |
be less than or equal to. See the nBits format described below.这是该区块头部哈希必须小于或等于的目标阈值的一个编码版本。请参考下文的 nBits 格式说明。 | |||
4 | nonce 非零值 | uint32_t | An arbitrary number miners change to modify the header hash in order to |
produce a hash less than or equal to the target threshold. If all 32-bit | |||
values are tested, the time can be updated or the coinbase transaction | |||
can be changed and the merkle root updated.一种由矿工更改以修改头部哈希的任意数字,目的是产生小于或等于目标阈值的哈希值。如果所有 32 位值都被测试过,时间可以更新,或者可以更改 coinbase 交易并更新默克尔根。 |
网络
运行网络的步骤如下:
新交易向所有节点广播。
每个节点将新交易收集到一个区块。
每个节点为它的区块寻找工作量证明。
当一个节点找到了工作量证明,就向所有节点广播这个区块。
节点只有在区块内所有交易都是有效的且之前没有被支付的情况下接收这个区块。
节点通过使用这个区块的哈希值作为上一个哈希值,在链中创建下一个区块的方式表示对这个区块的接受。
节点总是认为最长的链为正确的并持续致力于延长它。如果两个节点同时广播了不同的下一个区块,有些节点可能先收到其中一个而其他节点先收到另一个。这种情况,节点基于他们收到的第一个区块工作,但是也保存另一个分支以防它变为更长的链。当下一个工作量证明被找到后僵局就会被打破,从而其中一个分支变得更长;在另一个分支上工作的节点将切换到更长的链上来。 新交易的广播不必到达所有的节点。只要到达一些节点,不久就会进入到一个区块。 区块广播也是能容忍消息丢失的。如果一个节点没有收到某个区块,它将在收到下一个区块时发现它丢失了一个区块然后去请求这个区块。
注:又一个高屋建瓴的段落!这一段多半是故意略去了网络的细节,以便比特币区块链在各种不同的网络中实现。但在比特币网络按一个具体的实现运转十多年后,读起来就像是缺失了关键的细节。
Let’s think step by step
新交易向所有节点广播
谁向谁广播?假如我是一个付款人,我会用客户端程序向某人付款。那这时应该是客户端程序向“所有节点广播”。但是客户端怎么知道“所有节点”是哪里?
实际上,客户端通过查询 DNS Seed 来获得最初的节点地址。Bitcoin Core 和 BitcoinJ 客户端中都硬编码了 。这个由比特币社区维护的 DNS 服务器会自动返回目前正在活跃的若干比特币节点。此外,客户端中还包含一个硬编码的节点列表作为DNS服务器离线的备用选项。
值得一提的是,DNS响应可能被MITM攻击从而让客户端加入攻击者控制的网络,看到虚假的交易等等。我没有看到如何比特币客户端防范这种攻击。
这时我们就有了IP。结合默认的比特币端口 8333
或测试网 18333
,客户端就可以向一个比特币节点发起TCP连接。所有比特币通信都使用TCP协议。
客户端连接到任意一个节点后,可以发送 getaddr
消息,获取 addr
响应 (https://developer.bitcoin.org/reference/p2p_networking.html#addr)来获取网络中的其他节点的地址。
在客户端找到区块链网络后,就可以用 inv
消息发送交易了。
inv
消息可以用来广播新对象的哈希。交易就是对象的一种。节点在收到 inv
消息后,如果发现哈希是新的,就会回复 getdata
来请求交易的具体数据。客户端再发送 tx
消息来传输具体的交易信息。
截至到此时,只有那个和客户端直接通信的节点A知道这笔交易。节点A收到交易后,也会按照刚刚的流程,和其他它所知的节点进行相同的的通信。这笔交易就在比特币网络中扩散开了。
笔者在此处有疑问,如果一个节点是恶意的,是不是可以把手续费高的交易全留给自己打包,不告诉别人?好像比特币网络中也有对节点的评分机制,之后再具体查证。我在 stackexchange上问了这个问题 https://bitcoin.stackexchange.com/questions/127391/why-a-node-would-ever-relay-a-transaction
这里有一个回答 https://bitcoin.stackexchange.com/a/78122/175481 。大意是除非大部分都这样做,否则这并没有多大意义。
每个节点将新交易收集到一个区块。
节点收到新交易后,就会把交易暂存到自己的 mempool 里。由于产生一个区块需要消耗节点的时间和计算能力,节点可能会优先选择手续费更高的那些交易来打包。而暂时没有被打包的交易就留在 mempool 中。
See also https://developer.bitcoin.org/devguide/p2p_network.html
激励
我们约定,区块中的第一笔交易是区块创建者开创一枚属于他的新货币的特殊的交易。这就增加了对支持网络的节点的激励,并提供了一种初始分发货币到流通领域的方法,因为这里没有中央机构来发行货币。新货币按固定量稳定地增加就像金矿矿工消耗资源并增加黄金到流通领域一样。对我们而言,消耗的是 CPU 时间和电力激励也可以由交易费充当。如果交易的输出值小于其输入值,差价就作为交易费被加到包含此交易的区块的激励中。一旦预定量的货币进入了流通领域,激励将变为只含有交易费,这样可以完全避免通货膨胀。 激励会有助于鼓励节点保持诚实。如果一个贪心的攻击者有能力聚集比所有诚实节点更多的 CPU 算力,他将面临是以骗回已付款的方式欺诈别人还是使用这些算力生成新货币的抉择。他将发现遵守规则比破坏系统和他自己财产的有效性更有利,因为这些规则准许他获得比所有其他人都多的新货币。
注:十年后理解起来最简单的一节:挖矿!
值得一提的是,在比特币区块链中写注释消息,利用的就是激励(coinbase交易)中不需要付款者签名这一点。因此付款者签名处可以写矿工决定的任意信息。由于一个区块只会有一笔激励,因此一个区块也只能由一个矿工写一次注释。考虑到比特币出块的难度,可以推断这种GPU刻字服务不会很便宜……
下面的的段落比较简单,省略
回收磁盘空间
简化的支付验证
合并和分割交易额
隐私
计算
总结
https://happypeter.github.io/binfo/timestamp
评论
发表评论