数字货币的交易,如果只用到密码学中的非对称加密体系,没有区块链的话,数字可以复制,会出现双花攻击。
数字货币面临的主要挑战就是怎么防范双花攻击。
图中构成一个小型区块链。如下图,假设用户A获得了铸币权(发行货币的权利),他发行了10个比特币,即自己获得了10个比特币。然后他将这10个比特币转给B和C,每个人分5个比特币。接下来B给C 2个货币,给D 3个货币。最后C将所得的7个货币全部给E。
比特币系统中每个交易都分为输入部分和输出部分,输入部分要给出这笔交易的比特币的来源以及付款方的公钥,输出部分要给出收款人的公钥的哈希值。比特币系统中的收款地址就是收款人的公钥取哈希再经过一些转换得到的。
这里涉及到两种哈希指针:
“说明币的来源”也就防止了双花攻击,如在下图中,B已经将自己的5个比特币花掉了,假设B尝试再花一次,将5个比特币转给F。这时顺着区块链去检查这个区块到来源交易之间的区块,发现B已经花了来源区块的比特币,说明这新个交易是不合法的,也就不会接受这个区块进入区块链。
类似于银行没有提供查询某用户的银行账号的功能一样,比特币系统也没有提供查询某用户的公钥或账户地址的功能,要向某用户转账,就需要对方提供公钥或账户地址。这种情况收款方可以把公钥公布在网站上。
A向B转账,除了A需要知道B的地址,相当于银行账号(公钥取哈希),B也需要知道A的公钥。因为一方面A的公钥代表A的身份,B要知道转账的是谁,另一方面是为了验证比特币交易中A的签名(私钥签名公钥验证),也就是说所有结点都需要知道A的公钥才行。
区块链上每个结点都需要独立验证,即使是一个和交易无关的旁观者也要验证这笔交易的合法性。
在这种情况下,如何知道A的公钥?A的公钥是A自己写在这笔交易的输入部分里,即在交易中付款方自己宣称的。但这样是否会造成其他人可以伪造成A来发起交易?如B的同伙B’说自己是A,然后用自己的私钥签名,将自己的公钥说是A的公钥放在交易输入部分里,尝试将A账户上的比特币转走。
倘若B’直接使用A的公钥写上去,但因为没有A的私钥,这时签名就无法用A的公钥验证了,显然也是不行的。
⚠️比特币系统中某个指定人对应的比特币地址没有提供查询渠道,但是公可以自己公开,一般是二维码。
⚠️交易的输入部分和输出部分实际上都是脚本,A的公钥也是写在这笔交易的输入脚本里面。对公钥的验证过程,实际上就是把这笔交易的输入脚本,和币的来源的交易的输出脚本拼在一起,看看能不能顺利执行。
⚠️在前面的几张图里,每个区块里只花了一个交易,实际系统中每个区块中可以有很多交易,这些交易就组成了上节课学习的Merkle Tree。
⚠️比特币系统不加密(不是用发送者的私钥加密,其他人无法解密),这里的加密指的是密码学上对信息的加密。
加密货币转账不是通信保密,而是用接收者的公加密,以及数字签名。
下图中连起来的是块头,块身挂在区块上,哈希指针和块身没有直接联系(间接联系就是通过Merkle Tree的根哈希建立的)。
简化的例子里每个节点都需要验证交易,实际上节点分为轻节点和全节点。
全结点(fully validation node)是有块身的,需要验证所有交易的合法性;
轻结点(light node)是没有块身的,没有办法独立验证交易的合法性。轻结点没有参与区块链的构造和维护,只是利用了区块链中的部分信息。
系统中大部分结点是轻结点,全结点不是很多。
块头里保存的是区块的宏观的信息。
还有一个问题就是,每个账户都可以发布交易,区块链可以看做账本,那么发布的交易应该写在哪个区块里呢?交易广播给每个区块,每个人都在自己本地的区块链上写入交易,如何保证写入后的一致性?也就是说账本的内容要取得分布式的共识。(distributed consensus)。
分布式系统研究中产生了很多不可能结论(impossibility result)。
FLP impossibility result讲的是在一个异步的系统(asynchronous system)中,网络传输的时延没有上限,即使只有一个成员是有问题(faulty)的,也不可能取得共识。(FLP是研究分布式系统的三个专家的名字开头字母。)
CAP是分布式系统想要的三个性质,Consistency一致性、Availability可用性、Partition tolerance分区容忍性。而CAP Theorem是说任何一个分布式系统中,CAP三个性质最多只能满足其中两个,不可能三个全满足。
分布式共识中一个比较著名的协议是Paxos,这个协议能够保证一致性。即如果该协议达成了共识,这个共识一定是一致的(即不会出现系统中两个成员的共识不一致)。但是Paxos协议是有一个较小的概率(虽然小但是客观存在),使得系统一直无法达成共识。
比特币系统中的共识要考虑到有些结点是有恶意的,假设系统中大部分结点是友好的,有恶意的结点占少数。
一种思路是用投票的方式,将所有交易写入一个候选区块,然后发给所有结点,大家验证这个区块中的交易是不是都是合法的,然后投赞成和反对票,按一定票比通过后将候选区块写入区块链中。这种思路的问题是一个membership的问题,任何基于投票的系统都要考虑谁有投票权,例如hyperledger fabric就是一个联盟链的协议,规定了谁可以参加。
因为比特币系统中要产生账户只要在本地生成公私钥对就可以了,所以如果用这种方式,那么有恶意的人就可以进行女巫攻击(sybil attack),只要不断产生账户,然后获取大量的投票权就能控制整个区块链了。
比特币系统中不是用账户来投票,而是用计算力来投票,每个结点都可以在本地组装出一个候选区块,把它认为合法的区块放在这个区块里,然后就开始尝试各种nonce值(4 byte),使得H(blockheader)≤target。如果某个结点找到了符合要求的nonce,也就获得了记账权——往比特币去中心化的账本(区块链)里写入下一个区块的权力,其它结点收到这个区块之后,要验证这个区块的合法性(如检查target的编码nBits域设置的是不是符合比特币协议规定的难度要求、检查带nonce的块头哈希值是不是小于target、检查块身中的每个交易是否都有合法的签名、检查每个交易都没有双花等)。
检查出的候选区块完全合法,就应当接受吗?
如下图,C转账给A,A又将这些比特币转给B,然后A又发起了一次交易把比特币转给自己,这个交易的候选区块在下面,它希望挂在如图的位置上(分支上)。这里其实A所做的相当于将A转给B这个交易回滚了,仅仅做双花的检查会发现这两个分支都是没问题的,但如果接收了,那么在这个分支上A又获得了转给B的那些比特币。
这是一个分叉攻击(forking attack) 的例子,通过往区块链中间位置插入区块,来回滚某个已经发生了的交易。这样一个候选区块检测其内容是合法的,但不应当被接受。
比特币协议中规定,接受的区块应当是在扩展最长合法链。
如何知道某个区块是插入在哪的?块头有指向前一个区块块头的哈希指针,通过这个哈希指针就能判断了。
注意,区块链在正常运行下也会出现分叉。如两个矿工都发布了在扩展最长合法链的合法区块,并且都想挂在同一个区块上。这种情况下不同结点的抉择可能不一样,就看分布式的不同的结点先接收到哪一个区块了,先接收到的那个区块就会被这个结点接受。
这种等长的,多条最长合法链(分支)的情况会维持一段时间,直到某个分支胜出。假如上面的先找到下一个区块,那么下面这个区块就成为了orphan block
被丢弃掉。
这里,部分结点接受上面那个区块,部分结点接受下面那个区块,然后他们竞争,谁先找到下一个结点,最终形成一个共识的最长合法链。
什么是接受一个区块?如果一个结点收到一个区块后,沿着这个区块继续往下扩展,那么就算该结点接受了这个区块。
为什么要竞争出块权,让大家去竞争记账权的动力是什么?获得记账权的结点本身有一定的权力,如可以决定哪些交易被写入下一个区块中,但这不应当成为竞争记账权的主要动力,因为比特币系统设计来希望所有交易都能被公平写入账本。出块奖励机制解决了这个问题。
比特币协议中规定,获得记账权的结点,在发布的区块里可以有一个特殊的交易——铸币交易,在这个交易中可以发布一定数量的比特币,这是发行比特币(产生新的比特币)的唯一方法,不必指定币的来源。这也就解决了前面谈及的去中心化系统中的第一个问题——谁来发行比特币和发行多少比特币。
比特币刚出现时,出块奖励是50个BTC,比特币协议规定每21万个区块之后,出块奖励就要减半,也就变成25个BTC。如今的情况是每个区块中能产生12.5个BTC。
因为orphan block不在最长合法链里,所以里面的出块奖励的铸币交易也就无效了。
在普通的分布式系统中,如分布式哈希表里,取得的共识就是哈希表中的内容。比特币系统中,共识协议取得的共识是去中心化的账本里的交易。
只有获得记账权的结点可以往区块里写交易,而获得记账权的途径就是解那个不等式puzzle,根据第一节课学习的哈希函数puzzle friendly的性质,求解这个puzzle的过程没有捷径,只能一个一个nonce去尝试,所以可以作为工作量的证明,算力越强得到出块奖励的概率也就越大,所以才说比特币系统中是靠算力来投票的。
因为投票是靠算力的,创建多少个账户都没有影响,创建很多账户并不会使每秒尝试的nonce数目增加。
矿的数量有限,比特币总量有限。
挖矿的过程很难,挖到矿的回报很大,比特币取得记账权来获得出块奖励也是一样。