比特币采用的是基于交易的账本模式。
每个区块里记录的是交易信息,有转账交易,铸币交易,但是系统当中,没有哪个地方显示的记录每个账户上多少钱,比如想知道A账户上有多少钱,这个需要通过交易记录来推算,区块链中一共有多少个往A账户上转钱的交易,转入了多少个币,这些币当中有哪些已经被花掉了,有那些还没有,所以这样就可以推算出A账户上当前的余额是多少。
比特币系统中的全节点要维护一个叫UTXO(Unspent Transaction Output,还没有被花出去的交易的输出)的数据结构,区块链上有很多交易,有些交易的输出有可能被花掉了,有些还没有被花掉,所有那些还没有别花掉的交易的输出组成的集合就是这个UTXO,注意,一个交易可能有多个输出,比如说A的一个交易,给B转了5BTC,给C转了3BTC,B收到5BTC后,把它花掉了,所以就不在UTXO里,C收到之后,还没有花出去,所以这个输出是在UTXO里面的,就同一个交易,可能有的交易在里面,有的交易不在里面。UTXO集合当中的每个元素要给出产生这个输出的交易的哈希值以及它在这个交易里是第几个输出,就这两个信息就可以定位到这一个UTXO中的输出。
UTXO集合的作用
为了检测double spending attack,新发布的一个交易是不是合法,要查一下UTXO,花掉的币只有在UTXO才是合法的,如果不在这个集合里面,说明你要花掉的这个币要么是不存在的,要么是以前已经被花过,所以全节点要在内部维护UTXO这样一个数据结构,以便快速检测double spending attack,每个交易会消耗掉一些输出,同时也会产生新的输出,比如A把5BTC转给B之后,B把它花出去了,这时,A转给B5BTC的输出就不在UTXO里面了,把5BTC给D了,这时又产生了一个新的输出,这个输出就保存在UTXO里面。如果某个人收到比特币的转账交易之后,这个钱始终都不花,这个信息就要永久的保存在UTXO里面,可能是这个人不想花,可能想花没法花,可能这个人把密钥给丢了。
每个交易有多个输入,也有多个输出。所有输入的金额加起来要等与所有输出的金额,这叫做total input =total output,有些交易可能total input略微大于total output,假如total input =1BTC,total output=0.99BTC,这里面的差额0.01BTC就作为交易费,给获得记账权,发布区块的那个节点。
为什么节点要消耗计算资源来竞争这个记账权,为了获得出块奖励,发布一个区块,可以有一个特殊的coinbase transaction获得一定数量的比特币作为报酬,所谓的block reward,但是光有这个出块奖励可能是不够的,发布区块的那个节点,为什么要把你的交易打包到区块里,这样做对它有什么好处吗,比如说有个自私的节点,他发布的区块当中只包含他自己的交易,别的交易都不管,因为把别的交易打包进去对他来说没什么好处,而且还有一定的代价,因为要验证交易的合法性,而且如果区块里装的交易多了的话,占用的带宽也比较多,在网络上传播的速度也会慢,所以如果只有出块奖励这个机制的话,可能会出现这个只打包自己的交易,不管别人的交易的情况,所以比特币系统设置了第二个激励机制,就是这个交易费,叫transaction fee,可以理解成是一种小费,你把我的交易打包到区块里,我给你点小费,目前比特币系统中,交易费的金额都很小,像0.01BTC算是比较大的交易费了,有很多交易费只有0.00几个比特币,也有一些简单的交易是没有交易费的,目前来说矿工去挖矿,去争夺这个记账权,主要目的是为了得到出块奖励,因为有12.5个比特币,但是这个出块奖励是要逐渐减小的,每隔21万个区块要减半,21万个区块大概是多长时间呢,比特币系统中,设置的出块时间是十分钟,就只能整个系统,每隔十分钟会产生一个新的区块
算出来大约是4年,就是说21万个区块平均下来是4年的时间,每隔4年,出块奖励会减半,很多年以后这个区块奖励就会变得很小了,到那个时候,可能这个交易费就变成主要的。
基于账户的模式。
系统是要显示的记录每个账户上有多少个币,和现实生活比较接近,比如说你登录银行网站,就可以查到账户余额。
比特币中基于交易的模式,保护性比较好一点,但是有代价,比特币当中的转账交易要说明币的来源。
下面看一个区块的例子
这里包含了686个交易,总的输出是422046616378BTC,总的交易费是0.12458867BTC,最下面一行是出块奖励12.5BTC。
可以比较一下,两者差了有100倍。出块奖励还是大头,跟交易费相比,出块奖励差不多是交易费的100倍,这是矿工挖矿的主要动力,主要还是block rewards。
Height:区块的序号
Timestamp:时间戳
Difficulty:挖矿的难度,每隔2016个区块,要调整这个难度,保持出块时间在十分钟左右
Nonce:挖矿时尝试的随机数,这里的nonce值是最后找到的,符合难度要求的
右边显示的三个哈希值
Hash:区块块头的哈希值
Previous Block:前一个区块的块头的哈希值,计算哈希值的时候,都是只算Block header,不包含Block body中,具体的交易列表
这两个哈希值的特点:
前面都是一长串的0,所谓挖矿就是不断地调整随机数nonce,使得整个block header地哈希值小于等于给定地目标域值这个目标域值表示成16进制就是前面有一长串的0,所以凡是符合难度要求的区块,它的块头的哈希值算出来都是要有一长串的0
Merkle Root:这个区块中包含的那些交易构成的Merkle tree的根哈希值
这是Block header的数据结构
nonce这个域,是32位无符号整数,挖矿的时候要调整这个nonce,但是这个nonce最多只有2的32次方个可能的取值,按照比特币现在的挖矿难度,就算把这个2的32次方个可能的取值都遍历一遍,可能也找不到符合难度要求的。
version:当前使用的比特币版本号,没法改
previous block header hash:前一个区块块头的哈希值,没法改
merkcle root hash:Merkcle tree的根哈希值
time:区块产生的时间,这个有一定的调整的余地,比特币系统并不要求非常精确的时间,可以对这个时间在一定范围内进行调整,只要别太离谱就行,这个域也只有四个字节
nBits:挖矿时候用的目标域值,这里是一个目标域值编码后的版本,因为只有四个字节,只能按照协议当中的要求进行调整,不能随便改
nonce:随机数
每个发布的区块里有一个铸币交易coinbase transaction,这是比特币系统中,产生新的比特币唯一的方式
这个交易没有输入,因为他是凭空造出来的
有一个CoinBase可以写入任意的内容,是没有人管的,前面讲的digital commitment(数字承诺),可以把commitment的哈希值写在里面,digital commitment在前面密码学原理讲过,预测股市的那个,提前公布预测结果会影响股市,可以预先公布一个预测结果的哈希值,这个哈希值就可以写在CoinBase这个域里,这个域里写什么内容反正是没有人检查的,也可以写点别的东西,比如人生的感想,挖矿不容易,好不容易挖到,且挖且珍惜。那么这个域对我们有什么用,如果改变这个域的内容,会对块头里的根哈希值有什么影响吗
这是一个小型的区块链的示意图,上面一排是几个block header组成的链表,把最后一个block header里的根哈希值对应的Merkcle tree画出来了
左下角这个交易是coinbase transaction,改了coinbase域之后,这个交易的哈希值就发生了变化,这个变化会沿着Merkcle tree的结构往上传递,最后导致block header里的根哈希值发生变化。
所以可以把这个域当作extra nonce ,块头里四个字节的nonce不够用,这里还有很多字节可以用,比如把coinbase的前八个字节当作extra nonce来用,这样搜索空间一下子就增大到了2的96次方,所以真正挖矿的时候有两层循环,外层循环调整这个域的extra nonce,算出block header里的根哈希值之后,内层循环再调整header里的nonce。
下面来看看普通转账的交易的例子
这个交易有两个输入,两个输出
这两个写的是output,其实对这个交易来说是输入,这里的output是说,他们花掉的是之前哪个交易的output
这两个输出都还没有被花掉,会保存在UTXO里面
Total Output:输出的总金额
Fees:输出总金额和输入总金额的差值,就是交易费fees
输入和输出都是用脚本的形式来指定的,比特币系统中,验证交易的合法性,就是把Input Scripts和Output Scripts配对后执行来完成的,注意不是把这两个脚本配对,这两个脚本是同一个交易的脚本,不是把同一个交易里的输入脚本和输出脚本配对,而是把这个交易里的输入脚本跟前面那个提供币的来源的那个交易里的输出脚本配对,如果输入脚本和输出脚本能够顺利执行,不出现错误,那么这个交易就是合法的
下面对挖矿的过程做一些概率分析
挖矿就是不断地尝试各种nonce来求解puzzle,每次尝试nonce可以看作是一个Bernoulli trial。
Bernoulli trial:a random experiment with binary outcome
典型地Bernoulli trial的例子:掷硬币、
每次仍一下硬币,两种可能性,正面朝上或者是反面朝上,这两种概率不一定一样,比如说正面朝上的概率是p,反面朝上的概率是1-p,就这个硬币不一定是均匀的,就挖矿的例子来说,这两个概率是非常不一样,每次尝试一个nonce,成功的概率是微乎其微的,大概率是不行的,如果做很多的Bernoulli trial,每个实验都是随机的,那么这些Bernoulli trial就构成了一个Bernoulli process
Bernoulli process:a sequence of independent Bernoulli trials。
Bernoulli trail的一个性质是无记忆性(memoryless),这个意思是说,做了大量的实验,前面的实验对后面的实验结果是没有影响的,对于挖矿来说,每次尝试nonce,成功的几率很小,尝试了大量的nonce,才有可能找到符合要求的,那么这种情况下呢,Bernoulli process可以用Poisson process来近似,实验的次数很多,每次实验的概率很小。
出块时间服从指数分布(exponential distribution)
纵轴是概率密度,横纵是出块时间,这是整个系统的出块时间,并不是每个矿工的出块时间,整个系统平均的出块时间是10分钟,平均时间是比特币协议设计出来的,通过定期的调整挖矿难度,使得平均的出块时间维持在十分钟左右,具体到每一个矿工,他们能挖到下一个区块的时间取决于这个矿工的算力占系统总算力的百分比,比如说算力占到1%,平均下来,系统当中产生一百个区块,其中有一个区块是你挖矿挖到的,平均就要等1000分钟,才能产生一个区块,这个指数分布也是无记忆的。
概率密度有什么特点呢
从任何一个地方把他截断,剩下的曲线的形状跟原来是一样的,仍然服从是指数分布,这就是他memoryless的性质,比如说现在已经过去十分钟了,还没有人找到合法的区块,那接下来还要等多久呢,仍然是这个概率密度分布,平均还是要等10分钟,这个性质也叫progress free ,就过去的progress是没有用的,是不算数的,是不是觉得这个性质很无情,过去做的工作都白做了。
那我们设想一下,如果有某个puzzle,他不满足这个性质,不是progress free ,那么会出现什么情况。
比如说过去做的工作越多,那么接下来尝试nonce,成功的概率就越大,算力强的矿工会有不成比例的优势,因为算力强的矿工过去做的工作肯定是多的。
不成比例的优势就是,比如说,系统当中,有两个矿工,一个的算力是另一个的十倍,那么理想状况下,算力强的那个矿工能够挖到矿,找到这个puzzle solution的概率也应该是另一个人的十倍,这才算是公平,这恰恰就是说的这个progress free这个性质所保证的,如果不是这样的话,算力强的矿工获得记账权的概率就会超过十倍,因为他过去尝试了那么多不成功的nonce之后,下次nonce成功的概率就会增大,这就叫做不成比例的优势。所以呢,大家不要觉得progress free 的性质很无情,其实他恰恰是挖矿公平性的保证。
下面分析一下比特币的总量
出块奖励block reward,是系统中产生新的比特币的唯一途径,而这个出块奖励是每隔四年要减半的,这样产生的比特币数量就构成了集合数列geometric series。
BitCoin is secured by mining.
对于一个去中心化的,没有membership控制的系统来说,挖矿提供了一种凭借算力的有效手段是飙升的,虽然得到的出块奖励的数目是减少的,但是价值反而更高了,出块奖励越来越少,最后趋于0的时候,会不会大家都没有动力挖矿了,不是这样的,还有第二种激励机制,就是前面讲过的交易费。
比特币的安全性分析
假设大部分算力掌握在诚实的矿工手里,能不能保证写入区块链的交易都是合法的,挖矿给出的是概率上的保证,只能说比较大的概率下一个区块是由一个诚实的矿工发布的,但是不会保证记账权不会落到有恶意的人手里,比如好的矿工占百分之九十的算力,坏的矿工占百分之十的算力,平均下来,百分之十的情况下,记账权会落到有恶意的人手里,这时候会出现什么情况?
他会不会偷币,会不会把别人账户里的钱转移到自己的账户里
答案是不能,因为他无法伪造别人的签名,假如这个有恶意的节点是M,他想发布A->M这个交易,因为这个交易需要A的签名,又因为他不知道A的私钥,所以无法签名,那假如他硬要把这个交易写到区块链里呢
会出现以下情况。
诚实的节点不会接收不合法的交易,他会继续沿着上一个的区块挖,定义有恶意的攻击,是否成功的标准是让诚实的节点能不能接受这个交易,如果仅仅是有恶意的节点相互认账,诚实的节点都不认帐,那时没有用的。
诚实的节点都会沿着上面的链挖,因为比特币中,是扩展最长合法链,下面那条链不是合法链,多长都没有用,它包含一个不合法的交易,那个不合法的区块等于是作废了,这对于攻击者来说,付出的代价是很大的,因为他得不到出块奖励了,等于是没有偷到钱,还白白损失钱了,所以这个攻击是成功不了的
他能不能把以前花过的币再花一遍
比如说M这个节点发布一个转账交易给A,已经写到区块链里了,那么他现在获得了一个记账权,他又发布了另外一个交易,把这个钱再转会给他自己,这个区块如果是接在下面,就是前面讲过的分叉攻击,这个区块插在哪个位置是在挖矿的时候就决定的,因为设置的block header里要填上前一个区块的哈希,所以M要插在下面的话,一开始就要把他前一个区块设置成第三个区块,而不是说等他获得记账权再说。
如果两个等长的区块链都是合法的,取决于其他节点沿着哪一条链扩展,最后有一个会胜出,另一个就作废了,那么这种攻击的目的是什么?
如果上面那个M->A的交易产生了某种不可逆的外部效果,然后下面那个交易再把这个回滚了,M就可以从中不断获利。
比如说网上购物,M购买一些商品,然后这个网站接受比特币支付,他发起一个交易说把账已经转给这个网站了,这个网站监听到这个交易写到区块链中,以为支付成功了,把商品给了M,M拿到这个商品之后,又发起一个交易,把钱转给他自己,再把这个交易扩展成最长合法链,这样上面的区块就作废了,他这样攻击的目的是,既得到了钱,又把花出去的钱给收回来了,那怎么防范这种攻击呢,如果转账交易不是在最后一个区块,而是后面又跟了几个区块,那么攻击的难度会大大增加,要想回滚这个交易的话,还是得在下图中的位置插入这个回滚的交易,然后想办法在这个回滚交易所在的链成为最长合法链,这个难度是很大的,因为诚实的节点不会沿着这个区块往下扩展,因为他不是最长合法链,上面那条链比他长,就相当于那两条链在赛跑,如果大部分算力是掌握在诚实的节点手里的,那这样的攻击成功的可能性很小,有恶意的节点只获得一次记账权是不够的,还需要接下来记账权,所以简单的防范方法就是多等几个区块,或者多等几个确认(confirmation),就在M->A这个地方,这个交易刚刚写到区块里的时候,叫one confirmation,在后面又跟了一个区块叫two confirmation,以此类推
比特币协议中,缺省情况下是要等六个confirmation,到了第六个确认之后才认为前面那个交易是不可篡改的,平均出块时间是10分钟,那要等一个一个小时。
一种说法是说区块链是不可篡改的账本(irrevocable ledger),那凡是写入区块链里的内容就永远都改不了了呢,经过刚才的分析,这种不可篡改性只是概率的保证。刚刚写入区块链的内容相对来说还是比较容易被改掉的,经过一个小时之后,或者说六个确认之后,被篡改的概率会大幅度下降,是指数级别的下降。还有一种是说0的确认,转账交易被发布出去了,但是没被写到区块链里。相当于电商交易的时候,发布一个转账交易,告诉电商,我已经把钱转给你了,电商呢,就是一个全节点或者委托某个全节点监听
区块链上的交易,他收到这个转账交易的时候就验证一下合法性,有合法的签名,以前没有被花过,但是不用等到这个交易被写到区块链里。
zero confirmation在实际当中用的比较普遍,有以下原因。
(1)在比特币协议缺省的设置时,节点接收最先听到的那个交易
(2)很多购物网站,从你支付成功到他把货品发给你,是有一定的时间间隔的,他有一定的处理时间,比如说买个笔记本电脑,在网上已经支付成功了,但是这个电商可能要等到第二天才会发货,所以呢,如果发现这个转账交易没有被写到最长合法链上,电商就可以选择取消发货,就是比特币系统以外还是有很多手段的。
回到前面的问题,假设某个有恶意的节点获得记账权,他还能干什么坏事
能不能故意不把合法的交易写道区块链上,他发布的区块就故意不包含某些交易
这其实是可以的,比特币协议并没有规定一定要把哪些交易发布到区块里,出现这些情况,问题也不到,因为这些合法的交易反正可以被写到下一个区块里,总有诚实的节点,原意发布这些交易,其实呢,区块链的正常工作情况下,也会出现合法的交易没有被包含进去的情况,可能就是这段时间交易的数目太多了,比特币协议中规定,每个区块的大小是有限制的,最多不能超过一兆字节,如果数量太多,那么就只能等到下一个交易再发布
selfish mining
正常情况下,挖到一个区块就马上发布出去,因为你不发布的话,别人就可能发布了,别人发布之后你这个区块就作废了,等于得不到出块奖励了。、
但是selfish mining不是,他是挖到区块后,先藏着,等到我藏了很多个,分叉攻击的时候,突然甩出来,就像打扑克的时候,我手里有四个2,我突然把他们给炸了。这是分叉攻击的一种手段,但这样做的话,有一个前提,想想,普通的矿工这样做,成功的概率不大,就前面的例子,好的节点占百分之九十的算力,有恶意的节点占百分之十的算力,那这种藏着selfish mining的方法成功的概率并不大
好处:
如果一个矿工挖到了一个区块但是不发布,别人以为第三个就是最后一个区块,所以大家会沿着这个区块往下挖,如果刚刚挖到区块不发布的矿工的算力比较强,已经挖到不发布的那个区块的下一个区块了,听到有人已经挖到第三个的下一个区块后,把刚刚挖到的两个区块马上发布出去,那这样的话,上面那个矿工就白挖了,而偷偷藏着的那个矿工得到了两个出块奖励,好处就是,他减少了竞争,如果他把第一个挖到的发布出去的话,大家就抢着挖他接下来的一个,现在他先不发第一个挖到的,其他人都在浪费算力挖第三个接下来的一个,前提还是那个偷偷藏着不发布的矿工的算力要足够强,然后别人挖出一个的时候,他已经挖出了两个,然后发布出去,这样那个挖到第一个就发布出去的就白挖了。但是风险也大,比如说你挖到之后不发布,是因为你以为别人挖到第一个的时候,你可以挖到下一个,如果你没发布还没挖到下一个,别人已经挖到第三个的下一个了,这时候就变成等长的链了,这个时候要赶快发布出去,跟别人抢一抢,有可能还会抢得赢。