比特币实现

UTXO

区块链是去中心化的账本,比特币采用的是基于交易的账本模式(transaction-based ledger)。每个区块里记录的是交易信息,有转账交易,铸币交易,但是系统当中并没有哪个地方显式的记录账户中有多少钱,这得需要通过交易记录推算。比特币系统的全节点要维护一个UTXO:Unspent Transaction Output 数据结构。区块链上有很多交易,有些交易的输出已经被花掉了,有些没有被花掉,所有交易中输出还没有花掉的组成一个集合就是UTXO。注意,一个交易可能有多个输出,所以会出现一个交易中有的输出在UTXO里面,有的不在。UTXO集合当中每个元素要给出产出这个输出的交易的hash值以及他在这个交易里是第几个输出,这两个信息就可以定位到UTXO中的输出。

UTXO的作用

为了检测double spending。没有花掉的币必须在UTXO中。全节点要在内存中维护UTXO,以便快速检测double spending 。每个交易会消耗掉一些输出,同时会产生新的输出。如果某人收到比特币的转账交易后,这个钱始终都不花,那么这个信息就要永久存储在UTXO中。

每个交易可以有多个输入和多个输出,所有输入的金额加起来要等于所有输出的金额,这个叫做total inputs =total outputs 。这意味着交易可能不止一个签名。有些交易的total inputs和total outputs有略微的差异,差额会作为交易费给获得记账权发布区块的节点。节点争夺记账权的动力之一是出块奖励,但是光有出块奖励是不够的,获得记账权的节点如果只打包自己的交易记录怎么办?所以,动力之二是就是交易费,就是前面提到的差额。比特币当中的交易费通常很小0.00几,也有些简单的交易是没有交易费的。目前矿工挖矿主要还是为了得到出块奖励。随着出块奖励的减少,以后的交易费会成为争夺记账权的主要动力。

除了比特币这种transaction-based ledger外,与之对应的还有基于账户的模式(account-based ledger),以太坊用的就是这种交易模式,在这种模式当中,系统要显示记录每个账户有多少币,这个和现实体验非常接近。比特币这种交易模式隐私保护性较好,但是也有代价,比如转账交易要说明币的来源,这就是没有账户的代价。以太坊系统就不存在这种问题。

比特币例子

图 1
Number Of Transactions : 表示这个区块包含了686个交易
Output Total:总的输出是4220.46616378个比特币
Transaction Fees:总的交易费
Height:区块序号
TimeStamp:产出的时间戳
Difficulty:挖矿的难度,每隔2016个区块会进行调整,保持出块时间在每10分钟产出一个
Nonce:挖矿的随机数
Block Reward:出块奖励
Hash:区块块头的hash
Previous Block:前一个区块的块头hash
Hash 和 Previous Block 前面都有一串0,这不是偶然的,所谓的挖矿就是不断试nounce,使得整个block header的hash小于等于给定的目标阈值,这个目标阈值表示成16进制就是前面有一长串的0,所以凡是符合难度要求的区块,它的块头的hash值都带一串0
Merkle Root:merkle tree的根hash值
图 2

注意,nounce的类型是32位的无符号整数,我们知道挖矿的时候要调整nonce,但是这个nonce最多只有232次方个取值,按照比特币现在的挖矿难度,就算把这232 个的取值都遍历一遍,很可能仍然找不到符合难度要求的。因为近年来挖矿的人不断增多,挖矿难度不断增大,单纯调正nonce是很可能找不到符合难度要求的,所以还要调整block header其他的域,下图是block header 里面的各个域的描述

图 3
version:当前使用的比特币协议的版本号,这个无法更改
previous block header hash:前一个区块的块头hash,也是无法修改
merkle root hash:merkle tree 的根hash值,可以修改
time:区块产生时间,有一定的调整余地,比特币系统并不要求非常精确的时间,可以对时间在一定范围内调整
nBits:挖矿时候用的目标阈值编码后的版本,4个字节,只能按照协议的要求定期调正,不能更改
nonce:我们能改的nonce
图 4

为什么merkle root hash可以改?
每个发布的区块有一个铸币交易,这是比特币系统中产生新的比特币的唯一方式,这个交易没有输入(也就是上面的no input),因为币是凭空造出来的,它有一个CoinBase域,它可以写入任何东西,没有人管。
图 5

上图是一个小型区块链的示意图,左下角是coinbase transaction,我们改变这个交易的CoinBase域之后,这个交易的hash就发生了变化,这个变化会沿着merkle tree 不断向上传播,最终会导致block header 里的Merkle Root发生变化,所以我们可以把这个域当作extra nonce,块头的4字节nonce不够用,这里有很多字节可以用,比如把CoinBase域的前8个字节当作extra nonce来用,这样搜索空间一下子增大到了296次方。真正挖矿是有两层循环的,外层循环是调整CoinBase域的extra nonce,算出block header里的Merkle Root之后,内层循环再调整nonce。
图 6

上图是一个普通转账交易的记录,这个交易有两个输入,两个输出,左上方虽然写的是Output,其实对这个交易来说是输入,这里写的Output意思是说他们花掉的是之前哪个交易的Output。右边两个输出还没有被花掉,处于Unspent状态,会保存在UTXO里面

Inputs and Outputs中
Total Input:输入的总金额
Total Output:输出的总金额
Fees:前面两者的差值,这笔交易的交易费

Input Scripts和Output Scripts
输入和输出都是用脚本的形式指定,比特币系统中验证交易的合法性就是把Input Scripts 和Output Scripts配对后执行,注意上图中的Input Scripts和Output Scripts不是一对,上图的Input Scripts要和提供币的来源的交易的Output Scripts进行配对,如果输入脚本和输出脚本拼接在一起能顺利执行,那么这个交易就是合法的。

图 7

注意,上图中的所有的tx在计算时就是是一个Merkle Root

挖矿的过程的概率分析

挖矿的过程就是不断尝试各种nonce来求解puzzle,每次尝试nonce 都可以看作一个Bernoulli trial:a random experiment with binary outcome(伯努利试验:一个二元结果的随机试验)。伯努利实验的经典案例是抛硬币,如果硬币不均匀,每次实验,正面的概率就是p,反面的概率是1-p。对于挖矿来说,这两个概率相差甚远,每次尝试一个nonce,成功的概率是微乎其微的,大概率不行的,如果我们做大量的伯努利实验,每个实验都是随机的,那么这些伯努利实验就构成了一个Bernoulli process:a sequence of independent Bernoulli trial(伯努利过程:一系列独立伯努利试验)。Bernoulli process的一个特性是无记忆性(memoryless),意思是说做大量实验,前面的实验结果对后面的结果没有影响,挖矿过程就是不断尝试nonce,这种情况下Bernoulli process可以用Poisson process来近似,实验次数很多,每次成功的概率很小就可以用Poisson process来近似。我们关心的是出块时间,也就是系统里产生下一个区块的时间,这个在概率上可以推导出来,服从指数分布(exponential distribution)
图 8

注意,上图的出块时间是整个系统的出块时间,并不是矿工的出块时间,整个系统的平均出块时间是10分钟,平均时间是比特币协议规定的,通过定期调整挖矿难度,使得平均出块时间维持在10分钟左右,具体到每一个矿工,出块时间取决于矿工的算力,算力大,概率就大,出块时间就短。指数分布也是无记忆的,概率密度曲线的特点有:从任何一个地方把它截断,剩下曲线的形状仍然和原来一样,仍然服从指数分布,这就是无记忆的性质,假如过去了10分钟,仍然没有人找到区块,那么接下来还要等多久呢?还是平均下来10分钟,这个和直觉不太一致。这个概率分析告诉我们将来还要挖多少时间和过去挖了多少时间是没有关系的,仍然是服从指数分布,平均还要10分钟,这个性质也叫progress free。

progress free 或者 memoryless有什么作用?

设想一下,如果有某个puzzle不满足这个性质会出现什么情况,比如说过去做的工作越多,接下来尝试nonce的时候成功的概率越大,相当于抛硬币的时候每次结果不是随机的,过去抛了好多次,都是反面朝上,下次再抛硬币的时候,正面朝上的概率会增加。如果有某一个加密货币设计出这样的puzzle,会有什么结果?算力强的矿工会有不成比例的优势,因为算力强的矿工过去的工作量更大。什么是不成比例的优势?比如系统中有两个矿工,一个的算力是另一个的10倍,理想状况下,算力强的矿工能挖到矿的概率应该是另一个矿工的10倍,这才算公平,因为算力强的矿工能尝试的nonce是另一个的10倍,这就是我们说的progress free 或者 memoryless所保证的,如果不是这样的话,算力强的矿工获得记账权的概率就会超过10倍,因为它过去尝试了更多的不成功nonce,那么下次成功的概率就会增大,这就是不成比例的优势。所以progress free 或者 memoryless保证了挖矿公平性。

比特币的总量

我们知道出块奖励是系统产生比特币的唯一途径,而这个奖励是每隔4年减半的,这样产生的比特币数量构成了一个几何序列(geometric series)。一开始的21W个区块能产生21W 50的比特币,接下来的区块能够产生21W 25,再往下就是21W12.5…… 总量就是21W 50 *(1+1/2+1/4+……)约等于2100W

不像寻找某个符合条件的质数这类数学难题,比特币求解puzzle除了比拼算力外并没有实际意义,比特币越来越难挖到是因为出块奖励被人为减少,比特币的稀缺性是人为造成的。虽然挖矿求解的puzzle本身没有实际意义,但是挖矿过程对于维护比特币系统的安全性是至关重要的,也叫做BitCoin is secured by mining。对于一个去中心化的没有membership控制的系统来说,挖矿提供了一种凭借算力投票的有效手段,只要大部分算力掌握在诚实的节点手里,系统的安全性就能得到保证,所以挖矿这一过程,表面上没有意义,但是这个机制对于维护系统安全性是非常有效的。

我们知道出块奖励每隔4年减半,是不是说挖矿的动力也会越来越小呢,从过去几年情况来看,恰恰相反,挖矿竞争越来越激烈,因为比特币的价格是飙升的。随着出块奖励越来越少,交易费也是越来越多的。

比特币的安全性

假设大部分算力是掌握在诚实的矿工手里,我们能得到什么样的安全保证?能不能保证写入区块的交易都是合法的?不能,挖矿只是概率上的保证,只能说有一个比较大的概率,下一个区块是诚实的矿工发布的,但是不能保证记账权不会落到有恶意的节点手里。比如,好的矿工占90%算力,坏的矿工占10%的算力,平均下来,10%的情况下,记账权会落到有恶意的节点手里。

恶意的节点掌握记账权

偷币

第一个问题,他能不能偷币?能不能把别人账上的钱转给自己?

不能,因为他不能伪造别人的签名,交易不合法。但是如果他强行把不合法的交易写进区块链里会出现什么情况?诚实的节点都不接受这个区块,他们会沿着上一个合法的区块继续添加区块,形成最长合法链,导致恶意节点写进去的区块作废,这样,恶意节点不仅没有偷到钱,还把出块奖励陪了。

双花

第二个问题,他能不能把已经花过的币再花一遍?

比如说M节点发布一个转账交易给A,现在他获得了记账权,又把钱转给自己,如果直接连在M→A的后面肯定是不合法的,因为很明显的double spending,凡是诚实的节点都不会接受这个区块,他要想发布交易就一定要插在M→A前面一个区块后面,也就是前面文章提到的分叉攻击。注意,区块插在什么位置,是要在刚开始挖矿就决定的,因为设置的block header要填上前一个区块的hash,所以M节点想插到这个位置,一开始就要把这个区块设置成前一个区块,而不是等获得了记账权再说。这种情况下会出现两个等长合法链,取决于其他节点沿着哪条链继续往下扩展,最后有一个会胜出,另一个就作废了。这种攻击有什么用?假如M是消费者,A是商家,M买了东西然后用比特币支付,M又发起一个交易,把钱转给自己,然后把下面的交易扩展成最长合法链,这样上面的区块就作废了,这种攻击的作用就是既买的了商品,又把钱收回来了,达到double spending attack的目的。
图 9

怎么防范这个问题?如果M→A这个交易不在最后一个区块,而是后面跟了几个区块,那么这种攻击的难度就会大大增加,要想回滚M→A这个交易还是得在他前面一个区块后面插入新区块,然后想办法让新区块所在分支成为最长合法链,这个难度非常大,因为诚实的节点不会沿着新区块往后发展,因为他不是最长合法链。这种情况相当于两条链在赛跑,如果大部分算力是掌握在诚实节点手里,这种攻击成功的可能性很小。所以一种防范的手段是多等几个区块或者叫确认(confirmation)。M→A这个交易刚刚写进区块的时候,称为one-confirmation,以此类图,它后面的第三个区块叫做three-confirmation。比特币协议规定,缺省情况下要等6个confirmation,到了six-confirmation时,才认为前面的交易是不可篡改的,也就是要等待10分钟(平均出块时间)*6 = 1小时。
图 10

我们知道区块链也被叫做不可篡改的分布式账本(irrevocable ledger),是不是说凡是写入区块链的内容就永远改不了呢?经过前面的分析我们可以知道,这种不可篡改性只是一种概率上的保证,刚刚写入区块链的内容,相对来说,还是比较容易被改掉的,经过一段等待时间之后,或者后面跟着好几个确认之后,这种被篡改的概率会指数级别下降。其实还有一种zero confirmation,这个意思是说,转账交易已经发布出去但是下一个区块还没有挖出来。拿电商购物的例子来说,相当于支付的时候发布一个转账交易,电商运行一个全节点或者委托某个全节点,监听区块链上的交易,收到转账交易后要验证交易的合法性,但是不用等到交易写到区块链里。这听起来风险很大,其实zero confirmation 在实际当中应用还是很普遍的,第一个原因是比特币协议缺省的设置是节点接受最先听到交易,两个交易有冲突,先听到哪个就接受哪个,所以zero confirmation处,M→A交易被节点收到,M→M交易较大概率不被诚实节点接受;第二个原因是很多购物网站从支付成功到发货,是有一定时间间隔的,天然有一定处理时间,比如说你要买个东西,在网上支付成功后,电商第二天才会发货,期间发现转账交易没有被写到最长合法链上,那么电商就可以取消发货。
图 11

故意不把某些合法交易写进区块链

第三个问题是他能不能故意不把某些合法交易写进区块链里?

比特币协议没有规定获得记账权的节点一定要把某些交易写进区块链里,但是出现这种情况问题也不大,因为总会有诚实的节点愿意发布这些交易。区块链正常情况下也会出现合法的交易没有被写进去的情况,可能就是这段时间交易数目太多了,比特币协议规定每个区块的大小是有限制的,最多不能超过1M,所以交易放不下了就得等到下一个区块。