比特币基础技术 -- 区块链概要(01)

简介

比特币本身是一种分布式的,去中心化的数字货币。我们可以在大的层面上分为两部分:协议和实现。

协议是比特币中最重要的部分,按照官方的guide,大体上分为,Block Chain 区块链,Transactions 交易,Contracts 电子合约,Wallets 钱包,P2P Network P2P网络等几大块。

而Payment Processing 交易流程, Operating Modes 操作模式, Mining 挖矿,放在后面来介绍。我认为这些部分紧密的与实现相关,而且相关协议的内容,都会在前面的内容里面提及。

下面我们来介绍第一部分,Block Chain 区块链。

Block Chain 区块链

区块链为比特币提供了一个公开的记帐本,这个记帐本记录了交易的发生顺序和发生的时间。使用这个记帐本的目的是防止出现重复消费(一笔钱花两次),以及对已经完成的交易记录进行修改(撤回已经交易完成的资金)。

这里需要提及一个概念,“全功能节点”(full node),具体关于全功能节点的介绍会在operating mode中具体说明。所谓全功能节点,就是完整实现了比特币相关协议的节点,这个节点会独立存储一条被该节点认可的区块链(这里说的认可,也是比特币协议中的一个重要组成,如何验证一条区块链是合法的,我们也会在后文提及)。

区块链实际上由两部分组成:区块 Block, 链 Chain。

当多个节点所认可的区块链,拥有一模一样的区块时,我们可以说这些节点是“一致的”。这些节点认可区块链的方法(区块链验证规则 validation rules)被称为一致性规则。在后续的章节里,我们会描述很多比特币所使用的一致性规则。

简化的区块链

如上图所示,图中的箭头就是链。而区块则分为了两个部分,Block Header 区块头,和Block Transactions 区块的交易记录。在区块头中,我们看到了“Merkle Root”这个词,这是一个在鉴权方面应用较为广泛的树结构。这里有一篇介绍Merkle Tree的文章,推荐看一下。

在上图中可以看到,Chain的建立实际上就是构建一个Hash值,后一个Block Header存储前一个Block header的Hash值。这样,我们在逻辑层面,我们只要以每个Block header的Hash值作为唯一标识,我们就有能力构建一个链状结构了。唯一的问题就是每次都需要计算每个Block header的hash值,效率太低。关于这个问题,我们可以稍后看具体实现,来了解比特币是如何解决这个问题的。

在上图中,我们还有一个问题是Block为什么被分为了Block header和Block Transcations两个部分。这其实是一个基于性能和方便性的考虑,交易是可能持续变化增加的,这样Block本身就会不断变化,并且所占磁盘空间也会持续增长。如果我们单纯的以鉴定交易是否合法为目的,我们可以只存储Block Header,也就是使用所谓的SPV来进行校验,在这种情况下,磁盘消耗会大幅降低。

下面我们来单独说说交易,交易也是彼此被链接到一起的。比特币钱包给大众一个印象,就是钱是从一个钱包发送到另外一个钱包的。而实际上并非如此,比特币是从一笔交易移动到另外一笔交易的。(本质上,比特币从未进入过任何一个钱包。我们在比特币上写上一个名字,谁就拥有了这枚比特币的使用权。我们进行交易,实际上就是不停的在Block Transcations里面写收款人的名字)。每一笔交易都是使用相关的上一笔或者几笔交易的输出,所以,每一笔交易的输入,都是之前交易的输出。

比特币使用的三式记账法

(对于 Triple-Entry Bookkeeping 翻译为三式记账法是否合理我是存有疑虑的,因为在上图中,没有能体现负债的概念,上图看起来更像一个交易明细。)

如上图所示,一笔交易可以产生多个输出,即一笔交易同时向多个地址发起输出。(我们在这张100块钱的纸币上写,给张三40,给李四50)但是一个输出,只能被使用一次。想要被使用两次的输出会被拒绝,因为我们不允许同一笔钱被花费两次。

(备注:Transaction 在很多文档中被简写为TX)

因为每一个输出都只能被使用一次,所以我们可以统计区块链中全部未使用的输出,记作UTXO(unspent transaction output)。一个合法的交易,它的输入必须是一个UTXO。

我们暂时忽略币基交易,如果一个交易的输出大于输入,这笔交易会被拒绝。但是如果输入大于输出,那么输入和输出之间的差额,会被作为交易费支付给发现这个Block的矿工。如上图所示,每一次交易,输入和输出之间总有10k聪的差值,这个差值就是交给矿工的交易手续费。

Block Chain实现细节

Block Headers 区块头

区块头被序列化为80字节的二进制格式。对区块头执行哈希函数,得到的哈希值会参与到比特币的工作证明算法中。因此,区块头的序列化协议是一致性规则中的一部分。

字节数 名称 数据类型 描述
4 版本号 int32_t 区块版本号指明区块需要遵守的验证规则,具体可以查看区块版本号的相关章节
32 前一个区块头的哈希值 char[32] 对于前一个区块头,使用sha256(sha256())进行运算,并以内部字节顺序进行排列(哈希值按照展示为string的顺序进行排序)
32 merkle root哈希值 char[32] 根据区块中全部交易构建的merkle root,对该root进行sha256(sha(256()))运算,取值以内部字节顺序存储
4 时间戳 uint32_t 矿工开始执行哈希的unix时间戳。该时间戳必须严格大于前11个区块的中位数时间。全功能节点将会拒绝晚于当前时间两小时的区块。
4 nBits uint32_t 经过编码的区块头哈希目标阈值。注意和挖矿难度进行区分。
4 nonce uint32_t 一个可以由矿工定义的任意数字,目的是使得区块头的哈希值不高于目标阈值。如果32位全部经过了测试而没有符合目标阈值的哈希,我们可以更新时间戳,或者更新coinbase transaction以及merkle root。

哈希值全部采用内部字节顺序,其他值均采用小字节序。

02000000 ........................... 版本号: 2

b6ff0b1b1680a2862a30ca44d346d9e8
910d334beb48ca0c0000000000000000 ...前一个区块头的哈希值
9d10aa52ee949386ca9385695f04ede2
70dda20810decd12bc9b048aaab31471 ... Merkle root哈希值

24d95a54 ........................... unix时间戳: 1415239972
30c31b18 ........................... Target: 0x1bc330 * 256**(0x18-3)
fe9f0864 ........................... Nonce

Block Version 区块版本号

  • V1 创世块首次使用(2009年1月)
  • V2 Bitcoin Core 0.7.0 首次通过软分叉的方式使用(2012年9月),V2版本需要提在coinbase中提供区块高度这一参数,在BIP34中详细说了该版本。2013年3月,V2版本的区块开始拒绝区块高度在224412之后的不能提供区块高度的区块,并于三周后,(区块高度为227930)拒绝使用V1版本的区块。
  • V3 Bitcoin Core 0.10.0 首次通过软分叉的方式使用(2015年2月)。当分叉达到执行点时(2015年9月),V3要求区块必须严格以BIP66中描述的DER编码方式来编码全部的ECDSA签名。不使用DER编码方式的交易,在Bitcoin Core 0.8.0中就被标记为非标准的了(2012年2月)
  • V4 Bitcoin Core 0.11.2 在2015年12月以软分叉的形式引入。该标准详尽描述中BIP65中。V4支持了新的opcode,OP_CHECKLOCKTIMEVERIFY

V2,V3,V4采用的升级方法为 IsSuperMajority(),该方法的目标就是管理通过软分叉发生的修改。关于该方法的描述可以参见BIP 34.

当这篇文章正在写作的时候,一种更新的,称之为“version bits”的方法正在设计中。该方法的目标是以全新的方式来管理软分叉,然而,我们并不确定,V4会不会是最后一个使用 IsSuperMajority()来实现软分叉的版本。BIP9 草案描述了version bits,这个草案仍然处于活跃状态,并持续变更。

Merkle Trees

(Merkle Tree的构架和交易,工作量证明紧密相关。这里我们只介绍Merkle Tree的生成方法。其他的内容,我们在后面详细说明。)
Merkle root 使用该区块中全部的交易来进行构建,但是如一致性规则所要求的:

  • 币基交易的交易ID永远排在第一位
  • 任何区块内交易的输入都可以使用一个区块内交易的输出(假顶消费是合法的)。同时,与输出相关的交易ID必须在被用作其他交易的输入前放置在某些位置,这个要求的目的是,便于使用线性方法解析区块链的程序,在输出被用作输入前成功检测每个输出。
    如果一个区块只有币基交易,那么该交易ID(conbase TXID)将被用作merkle root hash
    如果一个区块只有币基交易和另外的一笔交易,二者的交易ID将会被合并做一个64字节的字符串,然后执行SHA256(SHA256())获得新的哈希值,并以该哈希值作为新的merkle root。
    如果一个区块拥有三个或三个以上的交易。交易ID则按照发生的顺序,两两一组,拼接为64字节的字符串,然后执行SHA256(SHA256())获得对应的哈希值。如果恰好有奇数个交易,那么最后一个交易与自身重复,计算哈希值。如果一层内有多于两个的哈希值,那么我们就重复上述过程,直到只剩下两个哈希值。利用最后的两个哈希值来构建Merkle root


    Merkle Tree构建示例

    交易ID和中间层哈希值使用内部字节顺序,merkle root在保存在区块头时,也使用内部字节顺序。

Target nBits

目标阈值是一个256位无符号整数。区块头的哈希值必须不大于目标阈值,才能通过工作量认证和一致性规则成为区块链的一部分。然而,区块头中只给nBits预留了32位空间,所以这里使用了一种低精度的被称为“compact”格式的表示方法,该方法有些类似以256为基的科学表示法:

将nBits转换为目标阈值

作为一个以256为基的数字,nBits可以像以10为基(十进制)的数字一个快速写出。


nBits快速转换方法

虽然目标阈值应该是一个无符号整数,但是最初的nBits实现方法继承与有符号数据类型,这意味着,目标阈值可能会是个负数。但是这个无关紧要的问题,区块头哈希被标示为一个无符号数字,所以它永远不可能小于一个负数。Bitcoin Core 使用两种方法处理这个问题:

  • 解析nBits时,Bitcoin Core将会把目标阈值为负数的情况直接转换为0,这样,就区块头的哈希值就可能成立了(至少理论上我们能得到一个值为0的哈希……)
  • 当生成nBits时,Bitcoin Core将会检查该nBits是否会被解析为一个负数的目标阈值。如果真的被解析为负数的目标阈值,那么我们对nBits进行如下操作:我们将nBits除以256,然后在幂上加1。这样,我们就获得了一样的目标阈值,而采用了不同的nBits编码方法。
Bitcoin Core 中test case中的例子

挖矿难度最低为1,在正式环境和测试环境中,以0x1d00ffff表示。在回归测试环境中,以0x207ffff来表示挖矿难度为1,这是小于uint_32的最大nbits值,这意味着,在回归测试环境中,我们可以随时重新构建区块链。

Serialized Blocks

依照当前执行的一致性规则,一个区块序列化后占用空间大于1MB将被视为无效区块。下面列出的字段都将参与序列化过程,并占用相关空间。

字节数 名称 数据类型 描述
80 区块头 block_header 关于区块头的格式,请参数区块头相关章节
不定 txn_count CompactSize 区块中,包含币基交易的全部交易数量
不定 txns raw transaction 区块中全部的以raw transaction格式存储的交易信息。交易的排列顺序必须符合merkle tree构建时交易ID的顺序。关于这个顺序,可以查看刚刚说过的merkle tree构建方法。

一个区块内的第一个交易一定是币基交易(coinbase transaction),这个交易应该收集和使用区块内全部的交易费用。
所有的区块高度低于6930K的区块被发现的时候,都会收到额外的津贴,这个津贴应该包含在币基交易中。(最初每发现一个新区块提供津贴50个比特币,后续每210K个区块津贴减半,基本上是每四年时间减半。到了2017年11月,该津贴是12.5个比特币)
交易费和挖矿津贴统一称为区块奖励。一个尝试花费高于区块奖励的数额的币基交易是非法的。

推荐阅读更多精彩内容