首页 > 技术文章 > 比特币 四、实现

lazyuan 2021-02-17 14:54 原文

区块链是一个去中心化的账本,比特币采用的是基于交易的账本模式(transaction-based ledger)每个区块里记录的是交易信息,有转账交易和铸币交易,但是系统中并没有哪个地方显示的记录每个账户上有多少钱,比如你想知道A账户上有多少钱,这个需要通过交易记录来推算,区块链中一共有多少往A这个地址上转账的交易,转入了多少个币,这些币中有哪些已经被花掉,有哪些还没有被花掉,由此推算出A账户上当前的余额
 
比特币系统的全结点维护一个叫UTXO(Unspent Transaction Output)的数据结构,还没有被花出去的交易的输出,区块链上有很多交易,有些交易的输出可能已经被花掉了,有些还没有被花掉,所有那些还没有被花掉的交易的输出组成的集合就称为UTXO,注意一个交易可能有多个输出,可能有的输出在UTXO,有的输出不在
比如A这个转账交易:A 给 B 转了 5 个 BTC,又给 C 转了 3 个 BTC
B 把五个比特币花掉了,将 5 个 BTC 转给了 D
第一个输出就不在UTXO里了,而第二个输出仍然在UTXO内
 
UTXO集合中的每个元素要给出产生这个输出的交易的哈希值,以及它在这个交易里是第几个输出,有了这两个信息就可以定位到UTXO中的输出
 
那么要这个UTXO集合干吗用呢?为什么要维护这样一个数据结构?为了检测double spending,新发布的交易是否合法,要检查一下UTXO,你想要花的币只有在UTXO集合里才是合法的,否则你花的币要么是不存在的、要么是已经被花过的,所以全结点要在内存中维护UTXO这样一个数据结构,以便快速检测double spending
 
每个交易会消耗一些输出,同时也会产生新的输出,比如上图 B 把五个比特币花掉了,将 5 个BTC转给了 D,那么第一个输出就不在UTXO里了,后面的输出被写入到UTXO里
 
如果某人收到比特币的转账交易记录后,这个钱始终都不花,那么这个信息就要永久的保存在UTXO里。统计数据显示:UTXO集合在逐渐增大,但是到目前为止,装在一个普通服务器的内存里还是完全没有问题的。每个交易可以有多个输入和多个输出,所有输入的金额加起来要等于所有输出的金额(total inputs = total outputs)
 
交易的 total inputs略微大于 total outputs,如下图,这0.01个差额就作为交易费给获得记账权发布区块的结点
发布一个区块可以有一个特殊的coinbase transaction,获得一定数量的比特币作为报酬,所谓的block reward,但是光有出块奖励可能是不够的,发布区块的结点,为什么要把你的交易打包在区块里?这样做对它有什么好处吗?
 
比如某个比较自私的结点,它可能发布区块的时候,只包含它自己的那些交易,别的交易都不管,因为把别的交易打包进去对他来说没什么好处,而且还有一定的代价,因为你要验证这个交易的合法性,而且区块里如果装了的交易多了的话,占用的带宽也比较多,在网络上传输的速度也比较慢,所以如果只是有出块奖励这个机制的话,就可能出现这个结点如果比较自私的话,他就不管别人的交易,它只打包自己的交易。
 
所以比特币系统设计了第二个激励机制,即交易费(transaction fee),这个可以理解成是一种小费,你把我的交易打包到区块里,我给你一点小费,目前比特币系统中交易费的金额都很小,像我们这个例子里的交易费就算是很大的交易费了,也有一些简单的交易是没有交易费的,目前来说,矿工去挖矿,争夺记账权,主要的目的还是为了获得出块奖励,因为出块奖励相较于交易费而言是很多的,但是出块奖励每隔21万个区块会减半,比特币系统设置的平均出块时间是十分钟,每隔十分钟会产生一个新的区块,21万个区块大约是4年时间,每隔4年出块奖励会减半,很多年以后出块奖励可能变得很小了,到那个时候,可能交易费就变成主要的收益了
 
除了比特币这种transaction-based ledger之外,与之对应的还有另外一种模式,是基于账户的模式(account-based ledger),以太坊用的就是这种模式,在这种模式中,系统需要显示的记录每个账户上有多少币,这个和我们平常的日常体验就比较接近,就比如你要知道你银行账户的余额,去银行就可以查到。比特币这种基于交易的模式,隐私保护性比较好,但是也有一些代价,比如之前反复强调的,比特币中的转账交易要说明币的来源,因为比如说你要转给别人10个 BTC,那谁知道你有没有这十个比特币,它没有账户的概念,那谁知道你有没有这十个比特币,它没有账户的概念,没有地方记录你一共有多少个比特币,所以每个交易都要说清楚你这个币是从哪来的,是从之前的哪个交易的哪个输出中来的,没有账户的概念就要付出这个代价
 
下面看一些区块链中的具体例子,看一下比特币系统中具体的区块信息:
 
 
 
 
 
 
 
 
下面对挖矿的过程做一些概率分析,挖矿就是不断地尝试各种nonce来求解puzzle,每次尝试nonce可以看作是一个bernoulli trial:a random experiment with binary outcome,bernoulli trial的一个例子就是抛硬币,每次抛硬币,两种可能性,正面朝上或者是反面朝上,这两种可能性的概率不一定一样,比如说证明朝上的概率是p,反面朝上的概率是1-p,因为硬币可能不均匀
 
对于我们挖矿的例子来说,这两个概率是非常的不一样,每次尝试一个nonce,成功的概率是微乎其微的,大概率是不行的,如果我们做很大的bernoulli trial,每个实验都是随机的,这些bernoulli trial就构成了一个bernoulli process:a sequence of indenpendent bernoulli trials。
 
bernoulli process的一个性质是无记忆性,这个意思是说:你做大量的实验,前面的实验结果对后面的实验是没有影响的,比如抛硬币,抛了很多次都是反面朝上,但并不是下次再抛硬币正面朝上的概率就会大一些,仍然是原来的概率,和过去的实验结果是没有关系的,对挖矿来说每次尝试nonce,成功的可能性很小,需要尝试大量的nonce才能找到复合要求的,这种情况下,bernoulli process可以用poisson process,实验的次数很多,每次实验成功的概率很小,这是可以用poisson process来近似。
 
我们关心的是出块时间,系统里产生下一个区块的时间,这个在概率上可以推到出来出块时间是服从指数分布的(exponential distribution)
 
 
这个平均时间是比特币协议中设计出来的,通过定期调整挖矿难度,使得平均出块时间维持在十分钟左右,具体到每一个矿工,他能够挖到下一个区块的时间取决于这个矿工的算力占系统总算力的百分比,比如说你的算力占到总算力的1%,那么平均下来系统里每产生一百个区块,其中有一个区块是你挖矿挖到的,那么你平均就要等1000分钟才能产生一个区块,这个指数分布也是无记忆的(memoryless)
 
probability density密度曲线的特点:从任何一个地方截断,剩下部分曲线的形状和原来是一样的,仍然是服从指数分布的,这就是它memoryless的性质。比如现在已经过了十分钟了,还没有人找到合法的区块,那么接下来还要再等多久呢?仍然是这个概率密度分布,平均还是要等十分钟。这个可能不太复合大家的直觉,因为感觉上说,平均出块时间是十分钟,现在已经挖了十分钟都还没有挖到,感觉上应该很快就能挖到下一个区块了,但实际不是这样的,概率分析来看,将来还要挖多少时间,和过去已经挖了多少时间,是没有关系的,仍然服从指数分布,平均还要十分钟,所以这个性质有的时候也管他叫做progress free,过去的progress是没有用的
 
设想一下如果有哪个puzzle不满足这个性质,不是progress free的,那么会出现什么情况?比如说过去做的工作越多,接下来尝试nonce成功的概率就越大,就相当于抛硬币的时候每次结果不是随机的,过去抛硬币都是反面向上,下一次抛硬币的时候正面向上的概率就会增加。如果有某一个加密货币设计出这样一个puzzle,会有什么结果呢?
 
算力强的矿工,会有不成比例的优势,因为算力强的矿工过去做的工作肯定是多的。什么叫不成比例的优势,比如系统中有两个矿工,一个的算力是另外一个的十倍,那么理想状况下,算力强的矿工能够挖到矿,找到puzzle solution也应该是另一个的十倍,这才算是公平,因为这个算力强的矿工能够尝试nonce的数目也是另一个的十倍,这就是memoryless性质所保证的。如果不是这样的话,那么算力强的矿工获得记账权的概率就会超过十倍,因为它过去尝试了那么多次不成功的nonce之后,下次nonce成功的概率就会增大,这就叫做不成比例的优势,所以大家不要觉得memoryless的性质很无情,其实它恰恰就是挖矿公平性的保证
 
 
下面分析一下比特币的总量。出块奖励是系统中产生新的比特币的唯一途径,而出块奖励,是每隔四年要减半的,这样产生出来的比特币数量就构成了一个几何序列(geometric series)
 
21万个区块能够生成的比特币是50个,接下来的21万个区块能够生成的比特币就变成了25个,再往下的21万个区块变成了12.5个,以此类推。即:
21万 * 50 + 21万 * 25+ 21万 * 12.5 + ......
=21万 * 50 *(1 + 1/2 + 1/4 + ......)
=21万 * 50 * 2
=2100万
 
这就是系统中所有比特币的总量,既包括过去已经挖出来的比特币,也包括未来将要产生的比特币,一共就这么多。有一些不太了解比特币的人有一个错误的观点:以为挖矿是在解决某个数学难题,比如说寻找符合某种条件的质数,而比特币越来越难被挖到是因为越到后面,符合这种条件的质数就越来越少,其实不是这样的。比特币求解的puzzle除了比拼算力以外,没有其他的实际意义,比特币越来越难被挖到,是因为出块奖励被人为的减少了,出块奖励不断地减半,比特币的稀缺性是人为造成的。这里大家注意一点:虽然我们说挖矿求解puzzle本身是没有什么实际意义的,但是挖矿的过程对于维护比特币系统的安全性是至关重要的(BitCion is secured by mining)对于一个去中心化的没有membership控制的系统来说,挖矿提供了一种凭借算力投票的有效手段,只要大部分算力是掌握在诚实的结点手里,系统的安全性就能够得到保证,所以挖矿的过程从表面上看没有什么实际意义,好像做的是无用功,但是这个机制的设立,对于维护整个系统的安全性是非常有效的。
 
出块奖励每隔四年要减半,那么是不是说大家挖矿的动力也会变得越来越小呢?从过去几年的状况来看,恰恰是相反的,挖矿的竞争是越来越激烈了,因为比特币的价格是飙升的,虽然你得到出块奖励的数目是减少的,但是价值反而更高了。出块奖励越来越少最后趋于零的时候,是不是大家就没有动力挖矿了呢?也不是,还有第二种激励机制,就是前面提到的交易费
 
 
下面对比特币的安全性做一些分析,假设大部分算力是掌握在诚实的矿工手里,我们能得到什么样的安全保证?能不能保证写入区块里的交易都是合法的?挖矿给出的只是概率上的保证,只能说比较大的概率下一个区块是诚实的矿工发布的,但是不能保证记账权不会落到有恶意的结点手里,比如好的矿工占90%算力,坏的矿工占10%手里,平均下来,有10%的可能性记账权落到恶意结点手里,这时候会出现什么情况?
 
(1)他能不能偷币,能不能把别人账户上的钱转给她自己
不能,因为他没有办法伪造别人的签名,如果他就要把该交易硬写到区块链上,诚实的结点不会接受这个交易,会继续沿着上一个区块挖。我们定义这种有恶意的攻击是否成功的标准是要看他能不能让诚实的结点接受这个交易,如果仅仅是有恶意的结点之间互相认账,诚实的结点都不认账,那是没有用的
 
 
这个例子中诚实的结点都会沿着上面那条链挖,比特币要求是扩展最长合法链,下面那条链不是合法链,所有他多长都没有用,他包含一个不合法的交易,下面那条链作废了。这对于这个攻击者来说,付出的代价是很大的,因为他得不到出块奖励了,等于是没有偷到钱,还白白损失了算力
 
(2)他能不能把已经花出的币再花一遍?
比如说M这个结点发布一个转账交易给A,已经写到区块链里了,现在他获得记账权,他又发布另外一个交易,把这个钱再转回给自己,这个区块如果直接连在后面,肯定是不行的,因为这是很明显的double spending,凡是诚实的结点都不会接受这个区块,就我们前面说的分叉攻击(forking attack)的例子,大家注意区块插入在哪个位置,是要在刚开始挖矿的时候就要决定的,因为设置的block header要填上前一个区块的哈希,所以M这个结点想要插到这个位置的话,一开始就要把前面的区块设置为前一个区块,而不是等获得了记账权以后再说,那么这种情况下出了两个等长的合法链都是合法的,取决于其他结点沿着哪一个链往下扩展,最后有一个会胜出,另外一个作废。
这种攻击的目的是,如果上面的转账交易A转给B的钱,产生了某种不可逆的外部效果,然后下面再把交易给回滚,那么A就可以从中不当获利。比如说网上购物,A购买了一些商品,然后这个网站接受比特币支付,发起了一个交易把账转给了网站,网站监听到这个交易写到了区块链里,以为支付成功,所以就把商品给了A,A拿到这个商品以后,又发起一个交易,把钱转给自己,把下面的那条链扩展为最长合法链,这样上面的区块就作废了,这个攻击的目的是既得到了商品,又把钱收回来了
 
那么如何防范这种攻击呢?如果这个转账交易不是在最后一个区块,而是后面又跟了几个区块,那么这种攻击的难度就会大大增加,你要想回滚这个交易,还是得在之前的地方插入,然后要想办法让下面的那条链成为最长合法链,这个难度是很大的,因为诚实的结点不会沿着下面的区块往下扩展,因为它不是最长合法链,上面的链比下面的长,所以相当于上下两条链在赛跑,如果大部分算力掌握在诚实的结点里,那么这样攻击成功的可能性很小,有恶意的结点仅获得一次记账权是不够的,还需要接下来不断地获得记账权才行,所以一种简单的防范这种攻击的方法就是多等几个区块,或者称为多等几个确认
 
在A转给B这个交易刚写到区块里的时候,我们称之为one conformation,到了后面,又进行了two conformation和three conformation,比特币协议中默认情况下要等6个conformation,到这个时候,才认为前面的交易是不可篡改的,平均出块时间是10分钟,6个conformation就是一个小时,所以这个等待时间还是很长的。大家应该都听过一种说法:区块链是不可篡改的账本(irrebocable ledger),那是不是说凡是写入到区块链里的内容就永远都改变不了呢?经过上面的分析,可以看到,这种不可篡改性只是一种概率上的保证,刚刚写入到区块链里的内容,相对来说还是比较容易被改掉的,经过一段等待时间之后,或者说后面跟上好几个确认之后,被篡改的概率就大幅度的下降
 
其实还有一种zero conformation,这个意思是说,转账交易发布出去了,但是还没有被写入到区块链里,在电商购物的例子当中,相当于支付的时候发布一个转账交易,告诉电商我已经把钱转给你了,电商运行一个全结点,或者委托某个全结点,监听区块链上的全结点,他收到这个转账交易以后,要验证一下这个交易的合法性,有合法的签名,以前没有被花过,但是不用等到这个交易被写入到区块链里,这个听起来风险很大,这个交易刚发不出去,还没有往区块链里写。
 
其实zero conformation在实际当中用的还是比较普遍的,原因如下
  • 比特币协议默认设置下,结点接受最先听到的那个交易,两个交易有冲突,最先收到哪个就接收哪个,上图中A转给B了,结点收到了,再发布一个A转给他自己的交易,有比较大的概率,诚实的结点是不会接受的
  • 很多购物网站,从你支付成功到他把货品发给你之间,是有一定的时间间隔的,天然的有一定的处理时间,比如说你要买一个笔记本电脑,你在网上支付成功,但是这个电商可能要第二天才会把电脑发给你,所以如果发现你的转账交易最后没有被写入到最长合法链中,电商可以选择取消发货
 
 
(3)能不能故意不把某些合法的交易写到区块链里,他发布的区块就故意不包含某些交易?
这是可以的。比特币系统没有规定获得记账权的结点一定要把哪些交易发布到区块链,但是出现这种情况问题也不大,因为这些合法的交易反正可以被写到下一个区块里,总有诚实的结点愿意发布这些交易。其实区块链在正常工作的情况下,也会出现合法的交易没有被包含进去的情况,可能是这段时间交易的数目太多了,比特币协议规定每个区块的大小是有限制的,最多不能超过1M字节,如果交易的数目太多了,有些交易就只能等到下一个区块再发布
 
 
 


推荐阅读