比特币中哈希函数的三个性质#
碰撞抗性#
对于两个不同的输入,输出却是相等的,这就称为哈希碰撞。
好的哈希函数应该尽量避免哈希碰撞,具有碰撞抗性的性质。
没有哪个哈希算法能证明是碰撞抗性,哈希碰撞是不可避免的,因为输入空间总大于输出空间。
- 该性质的作用
对一个消息求摘要。
比如消息的哈希值是 H (m)=digest,如果有人想篡改 m 值而 H (m) 不变,则无法做到。
隐藏性#
哈希函数的计算过程是单向的,不可逆的。(从 H (x) 无法推导出 x)。
隐藏性性质前提是输入空间足够大,分布比较均匀。
除了密码学中要求的这两个性质外,比特币中用到的哈希函数还有第三个性质。
难题友好性#
比特币挖矿的过程中实际就是找一个 nonce(一次性数字),nonce 跟区块的块头里的其他信息合一起作为输入,得出的哈希值要小于等于某个指定的目标预值。H (block header)≤target。block header 指块头,块头里有很多域,其中一个域是我们可以设置的随机数 nonce,挖矿的过程是不停的试随机数,使得 block header 取哈希后落在指定的范围之内。
难题友好性是指挖矿过程中没有捷径,为了使输出值落在指定范围,只能一个一个去试,所以这个过程还可以作为工作量证明(proof of work)。
哈希值事先是不可预测的,挖矿很难,验证很容易。(难以解决,但易于验证)
SHA-256(安全哈希算法)#
比特币中用的哈希函数叫作 SHA-256(安全哈希算法),以上三个性质它都是满足的。
若某一区块链交易系统使用的 SHA256 算法,会有 $2^{256}$ 种输出,如果进行 $2^{256}+1$ 次输入,那么必然会产生一次碰撞;甚至从概率的角度来看,进行 $2^{130}$ 次输入就会有 $99%$ 的可能发生一次碰撞。
假设一台计算机以每秒 10000 次的速度进行哈希运算,要经过 $10^{27}$ 年才能完成 $2^{128}$ 次哈希。
比特币中的账户#
在本地创立一个公私钥匙对(public key, private key),这就是一个账户。公私钥匙对是来自于非对称的加密技术(asymmetric encryption algorithm)。
BTC 交易时用私钥来做签名,其他人用公钥验证签名。
产生公私钥时需要一个好的随机源(a good source of randomness),产生公私钥是随机的,如果随机源不好,就有可能产生相同的公私钥。在密码学实现上一般用物理噪声源芯片做随机源。
比特币中用的签名算法,不仅是生成公私钥的时候要有好的随机源,之后每一次签名时也要有好的随机源。只要有一次签名用的随机源不好的话,就有可能泄露私钥。
随机产生一对公私钥相同的概率是微乎其微,可以忽略不计。
比特币中重要的数据结构#
哈希指针#
比特币中最基本的结构就是区块链,区块链就是一个一个区块组成的链表。区块链和普通的链表相比有什么区别?
比特币用哈希指针代替了普通指针(区块链是一个使用哈希指针的链表)。
区块链第一个区块叫作创世纪块(genesis block),最后一个区块是最近产生的区块(most recent block),每一个区块都包含指向前一个区块的哈希指针。
通过这种结构,可以实现篡改证明日志。如果有人改变了一个区块的内容,后面一个区块的哈希指针就对不上,因为后一个区块哈希指针是根据前一个区块的内容算出来的,所以后一个哈希指针也得改,以此类推,我们保留的最后一个哈希值也会变化。所以只要记录了最后一个区块的哈希,就能保证前面所有区块没有被篡改。
指针保存的是本地内存的地址,那么只是在本地这台计算机上才有意义,发送到其他计算机上就没有意义了。那么在发布区块的时候哈希指针是怎么能够通过网络进行传输呢?
所谓的哈希指针只是一种形象的说法,实际系统中用的时候只有哈希,没有指针。
默克尔树#
区块头(block header)中包含了默克尔树的根哈希(root hash),只要记住默克尔树根哈希值,就能检测出对树中任何部位的修改。
找到交易在默克尔树中所在的位置(所有交易都在叶子节点),这时该区块一直往上到根节点的路径就叫默克尔证明(merkle proof)。
轻节点只保存区块头,如果轻节点想知道某个交易是不是包含在默克尔树中,需要向全节点发出请求,请求一个能够证明此交易被包含的默克尔证明,里面包含轻节点需要的哈希,轻节点自己在本地验证。
默克尔证明能证明什么?证明某个交易是不是在给定的区块里。
比如一个轻节点,没有维护整个区块 UTXO 的内容,只知道区块头。轻节点问一个全节点:该交易是不是在这个区块里?全节点返回一个默克尔证明作为证明,轻节点就可以验证是否属实。
默克尔树与二叉树的区别:用哈希指针代替了普通指针。
去中心数字货币需要解决的问题#
一个去中心化的数字货币,要解决两个问题。
- 第一个问题,谁有权发行货币?
- 第二个问题,怎么验证交易的合法性?
双花问题(double spending)#
双花即一笔钱被重复花了两次,中心化账本可以轻松解决双花攻击,去中心化方案需要有一个数据结构来维护账本的正确性,这个数据结构就是区块链。
比特币系统中每个交易都包含了输入和输出两个部分,输入部分要说明币的来源,输出部分要给出收款人的公钥的哈希。
A 向 B 转账需要的信息:A 的私钥签名和 B 的地址(B 收款的地址是通过公钥推算出来的)。
这个地方有两种哈希指针,第一个地方就是连接各个区块组成链表,在交易详情里面有第二个哈希指针指向前面的某个交易,为了说明币的来源(防范双花)。
分布式共识(distributed consensus)#
每个账户都可以发布交易,那么这个交易是广播给所有的节点的。有些交易是合法的,有些交易可能是非法的。那么谁来决定哪些交易应该被写到下一个区块中?按照什么样的顺序写进去?
分布式的共识一个简单的例子就是分布式的哈希表(distributed hash table),比如系统里有很多台机器,共同维护一个全局的哈希表。
这里需要取得共识的内容是什么?哈希表中包含了哪些键值对(key-value pair)。假如有人在自己电脑上插入一个键值对,'xiao' 这个 pair 对应的是 12345,即 'xiao'→12345。那么别人在另一台读的时候也要能把这个读出来,这就叫一个全局的哈希表。
不可能结论(impossibility result)#
关于分布式系统有很多不可能结论(impossibility result),其中最著名的是FLP 不可能结果。这三个字母是三个专家的名字缩写,在一个异步的(asynchronous)系统里,(网络传输迟延没有上限就叫异步系统),即使只有一个成员是有问题的(faulty),也不可能取得共识。
CAP 定理(CAP Theorem)#
另一个著名结论:CAP 定理(CAP Theorem),指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),三者不可得兼。一致性、可用性、分区容错性,三者只能满足两个。
区块链的不可能三角#
区块链 ** 不可能三角(blockchain trilemma)** 由以太链创始人 Vitalik 提出。
说的是区块链技术不能同时实现去中心化性程度、安全性、高效性。
- 去中心化(Decentralization)
- 可扩展性(Scalability)
- 安全性(Security)
POW - 比特币中的共识协议(consensus in Bitcoin)#
基于投票的共识方案#
有些节点可能是有恶意的,假设系统中大多数节点是好的,那么该如何设计共识协议?
一种方案是投票。任何基于投票的共识方案,首先要确定谁有投票权,对不对?要有个 membership 的问题。如果这个区块链,它的 membership 是有严格定义的。比如说你这个不是谁都可以加入的,像联盟链的 Hyperledger Fabric,就是一个联盟链的协议。只有某些符合条件的大公司才能加入。那么这种情况下,基于投票的方案是可行的。我们假设大多数成员是好的,那么我们投票就是可行的。
公有链中简单的直接投票是不行的。
比特币系统不是这样的。比特币系统中创建一个账户是很容易的。你就在本地产生一个公私钥,一个账户不需要任何人批准,别人甚至都不知道。只有和外部发生交易的时候,别人才知道有这样一个账户。假如有恶意的节点,就搞一台超级计算机,别的什么都不干,不停的产生各种各样的账户,然后他产生的账户超过总数的一半,他就有控制权,他就可以操纵投票结果。这种就叫女巫攻击(sybil attack)。
比特币系统当中用了一个很巧妙的机制来解决这个问题。也是投票,但不是按照账户的数目投票,而是用计算力来投票。
记账权#
每一个节点都可以在本地组装出一个候选的区块,把它认为合法的交易放到这个区块里,然后就开始尝试各种随机数 nonce(一次性数字)值。如果某个节点找到了符合要求的 nonce(占 4 字节),满足不等式 H (block header)≤target 的要求,我们就认为它获得了记账权。
所谓的记账权,就是往比特币这个去中心化的账本里写入下一个区块的权利。只有找到这个 nonce 获得记账权的节点才有权利发布下一个区块。
验证区块的合法性#
那么其他节点收到这个区块之后,要验证一下这个区块的合法性。
比如,先验证一下这个 block header 的内容填的对不对?像 block header 里有一个域 nBits 域。实际上它是目标预值的一个编码,检查一下 nBits 域设置的是不是符合比特币协议中规定的难度要求。然后查一下这个 nonce 是不是满足不等式 H (block header)≤target。
就换句话说,你发布一个区块,你是不是真的有权利发布区块?你是不是真的获得了记账权?假设都符合要求,然后看一下 block body 里面的交易列表,验证一下是不是每个交易都是合法。第一,要有合法的签名。第二,用的币以前没有被花过。如果有一项不符合要求,这个区块就是不能被接受的。
分叉攻击(forking attack)#
如果所有条件都符合,这个区块也不一定被接受。
什么情况条件都满足了也不能被接受?区块里交易是合法的,但是它不在最长合法链(longest valid chain)上。这种称为分叉攻击(forking attack)。
分叉攻击通过往区块链中间位置插入一个区块,来回滚某个已经发生了的交易。
区块链在正常情况下也可能出现分岔。如果两个节点同时获得记账权。每个节点在本地自己组装一个它认为合适的区块,然后去试各种 nonce,如果两个节点在差不多同一个时间找到了符合要求的 nonce,就都可以把区块发布,这时会出现两个等长的分岔,这两条都是最长合法链。
那么该接受哪条呢?比特币协议当中,在缺省(默认的意思)情况下,每个节点是接受它最早收到的那个。所以不同节点根据在网络上的位置不同,有的节点先听到新生成的其中一个区块,那就接受这个区块;有些节点先听到另一个区块,那就接受另一个区块。
如何判断接一个区块被接受?比特币协议中用到了隐式委托(implicit consign),如果沿着这个区块往下继续扩展,就算认可了这个发布的区块。比如在新生成的其中一个区块后面又拓展一个区块,表明就认可了这个新区块。
那么同时生成新区块的两条链后续发展如何发展呢?等长的临时性的分岔会维持一段时间,直到一个分岔胜出。也就是哪一个链抢先一步生成了新的区块,哪一条就是最长合法链。另一个作废的就叫孤儿区块(orphan block)。这两个新区块有可能会各自拉拢,两个区块链看谁的算力强,有时候也是看谁的运气好,就会胜出。
比特币系统中的共识机制要取得的共识是什么#
举一个分布式哈希表的例子,有一个分布式系统,有很多的服务器,这里服务器之间要取得的共识是什么?是哈希表中的内容,包含了哪些 key-value pair。
那么比特币去中心化的账本里的内容就是要取得的共识,只有获得记账权的节点才能往里面写东西。
为什么大家要争夺记账权#
为什么大家要争夺记账权?首先记账权的节点本身有一定的权力,可以决定哪些交易写到下一个区块里。
但是我们设计这个协议的时候不应该让这个成为争夺记账权主要的动力,因为我们希望凡是合法的交易都应该写入到区块链里面。由此引出了区块奖励(block reward)和交易费(transaction fee)。
谁来决定货币的发行#
coinbase 交易是比特币系统中发行新的比特币的唯一方法,这个交易不用指出币的来源,后面的交易都是基于比特币的转移。
比特币这个争夺记账权的过程叫做挖矿。所以争夺记账权的节点被称为矿工。如果他获得记账权,我们就说他挖到矿了,或者说是挖到了区块。
怎么获得记账权(POW 工作量证明)#
通过尝试各种随机数 nonce 解难题。
比特币的共识机制是靠算力来投票。我们讲比特币的哈希函数的三个性质,其中难题友好性这个性质保证了求解这个难题的过程没有捷径,只能一个一个 nonce 去尝试。
所以如果某一个节点他的算力是另一个节点的 10 倍,那么他获得记账权的概率也是那个节点的 10 倍。就靠算力来投票,这就是比特币中投票的特殊性。它不是一人一票,也不是一台计算机一票,而是看你每秒钟能够尝试多少个 nonce 的数目。这个我们有时候管它叫哈希率(hash rate),这个决定投票的权重,你这个节点的哈希率越高,那么你获得记账权得到这个出块奖励的概率也是越大。
比特币中POW 工作量证明防范了女巫攻击,按算力记票,即使创建再多的账户,也无法使算力增强。
比特币求解难题的过程,除了比拼算力,没有其他的实际意义。比特币越来越难被挖到,是因为出块奖励被人为减少了,比特币的稀缺性是人为造成的。虽然挖矿求难题本身是没有实际意义,但是挖矿过程对维护比特币系统安全性是很重要的,比特币是通过挖矿来保障安全的。挖矿提供了一种凭借算力投票的有效手段,只要大部分算力掌握在诚实的人的手里,系统的安全性就能够得到保证。
挖矿时会不会有的矿工偷答案#
不会。发布的区块里有 coinbase 交易,里面有一个收款人地址,是挖到矿的矿工的地址。假如 A 挖到了矿,里面就是 A 的收款地址。如果要偷答案的话,就要把 A 的地址换成自己的地址,而地址如果一变化,coinbase 交易的内容就发生了改变。
这样会导致默克尔树的根哈希值变化,因为这个交易和区块中所包含的其他交易是合在一起构成了默克尔树。任何一个地方发生改变,根哈希值就会变。而 nonce 是在块头里面,根哈希值也是在块头里面,块头的内容发生变化之后,原来找到的 nonce 就作废了。所以不可能偷答案,因为每个矿工挖到的 nonce 是和他自己的收款地址绑定在一起的。
伯努利试验(Bernoulli trial)#
伯努利试验:一个具有二元结果的随机实验。
** 伯努利试验(Bernoulli trial)** 是只有两种可能结果的单次随机试验。
伯努利过程
伯努利过程(Bernoulli process):一系列独立的伯努利试验。
挖矿过程每次尝试一个 nonce 可以看作是一个伯努利试验(Bernoulli trial),每一个随机的伯努利实验就构成了一个伯努利过程。
它的一个性质是:无记忆性。伯努利过程一个性质是无记忆性(memoryless),即做大量的实验,前面的结果对后面没有影响。
每尝试一个 nonce 成功的概率是很小的,要进行大量的实验,这时可以用泊松过程来代替伯努利过程。
进度自由 - 挖矿公平性的保证#
我们真正关心的是系统出块时间,在概率论中可以推导出块时间是服从指数分布的(exponential distribution)。
整个系统平均出块时间为 10 分钟,是比特币协议设计的定期调整挖矿难度,使得平均出块时间维持在 10 分钟左右。具体到每一个矿工,他能够挖到下一个区块的时间,取决于矿工的算力占系统总算力的百分比。
指数分布也是无记忆的,是挖矿公平性的保证。
指数分布无记忆性,因为概率分布曲线的特点是:随便从一个地方截断,剩下一部分曲线跟原来是一样的。比如:已经等十分钟了,还没有人找到合法的区块,那么还需要等多久呢?仍然参考概率密度函数分布,平均仍然要等十分钟。将来还要挖多长时间,跟过去已经挖了多长时间是没有关系的。这个过程也叫:进度自由(progress free)。
可以画出一个坐标轴,纵轴表示概率密度,横轴表示出块时间(整个系统的出块时间,并不是每个矿工的出块时间)。具体到每一个矿工,他能挖到下一个区块的时间取决于矿工的算力占系统算力的百分比。假如一个人的算力占系统总算力的 1%,那么系统出 100 个区块,就有一个区块是这个人挖的。
如果没有进度自由,会出现什么现象:算力强的矿工会有不成比例的优势。因为算力强的矿工过去做的工作是更多的,过去尝试了那么多不成功的 nonce 之后,后面 nonce 成功的概率就会增大。以此进度自由是挖矿公平性的保证。
能凭空产生多少币#
一开始的时候,比特币刚上线,每一个发布的区块可以产生 50 个比特币的区块奖励。
比特币系统中大约每 10 分钟就会产生一个区块,比特币协议中规定,21 万个区块以后,出块奖励就要减半,就变成了 25BTC,再过 21 万个区块,又要减半。
出块奖励大约每隔 4 年要减半,这样产生出来的比特币数量就构成了几何序列(geometric series)。
照此计算最多共有 2100 万枚比特币。
截至 2022 年,比特币矿工每成功开采一个区块,将获得 6.25 个比特币。
比特币安全性分析#
前提假设大部分算力掌握在诚实的人的手里。挖矿给出的只是概率上的保证,只能说有比较大的概率下一个区块是由一个诚实的矿工发布的,但是不能保证记账权不会落在有恶意的人手里。
如果有恶意的节点获得记账权,他可能会做的事:
1. 伪造一笔不合法交易#
比如把别人账上的钱转到自己的账户上,但是由于自己不知道别人的私钥,所以不合法,验证不通过。那么其他诚实节点不会认同该区块,因为包含不合法交易,所以还会接着上一个区块去扩展,那么根据最长合法链,该区块不会获得区块奖励,反而浪费大量电力,人力,最终还没有得到区块奖励。
2. 双花#
通过把花出去的钱再次花出。比如 M 转账给 A,M 又发布另一个交易,把这个钱转回给自己(或其他人)。此时有两种情况。
- 新区块已写入区块链 - 分叉攻击回滚交易
恶意节点需要对区块链进行分叉,在之前的区块中分叉一个新链,这种情况下攻击难度很大。有恶意节点获得一次记账权是不够的,还需要不断地获得记账权,因为诚实节点只认可最长合法链。详情见自私挖矿(selfish mining)。
防范这种攻击就需要多等几个区块确认(confirmation),缺省(默认)情况下需要 6 个确认,这个时候认为前面的交易不可篡改,等待时间大概是一个小时左右。
- 交易所在区块未被写入区块链中 - 短时间发起多笔交易
此种情况又叫 零确认(zero confirmation),在实际应用中很普遍。
每一笔交易都含有时间戳,比特币协议当中,在缺省(默认的意思)情况下,每个节点是接受它最早收到的那个交易。如果正确的交易先被接受,后面发起的重复交易则不会被接受,否则收款人可以直接拒绝承认这笔交易。
其实此时还是有一定的风险,因为恶攻击者的恶意节点会有极小的概率获得记账权,要绝对的安全可以等 6 个确认(一个小时)。
3. 故意不写合法的交易#
问题不大,合法的交易还是可以被写到下一个区块里,总会有诚实的节点写合法交易。
正常情况下也会出现合法交易没有被写到区块中,因为可能某段时间交易数目太多,比特币协议中规定每个区块大小是有限制的,区块大小不能超过 1M 字节,所以如果交易太多那么有些交易只能等到下一区块发布。
4. 自私挖矿(selfish mining)#
正常情况下如果挖到区块会及时发布,以免别人挖到后自己的区块无效,作废了。但是在自私挖矿中,恶意节点挖到区块先不发布,隐藏一条链,等待超过最长合法链时发布。
这是分叉攻击的一种手段,但是恶意节点一般需要超过 51% 的算力,难以实现。
如果只是为了多赚取正常的出块奖励,挖到第一时间不发布,那么其他人还会接着上一个区块挖,自己延着已挖到区块的挖,减少该区块的竞争。如果自己已经挖到第二个区块,当其他人宣称挖到第一个区块之后,自己把手里的两个一起发布,成为最长合法链,理论上是很可行的。但这种做法风险大,要别人挖出第一个区块时,自己已经能够挖出第二个,否则当别人挖出第一个发布时,自己手里只有一个,这时候是等长链,只能去竞争,这样可能还会白白损失第一个的区块奖励。
比特币是怎么保证安全的#
两个方面
- 密码学
- 共识机制
别人没有你的私钥,就没有办法伪造你的签名,所以不能把你账上的钱转走。前提是系统中拥有大多数算力的矿工是好的,是遵守协议的,不会接受那些没有合法签名的交易。如果没有这些,密码学上的保证也就没有用武之地。
比如你去银行取钱,按照规定取钱得出示合法的证件,银行工作人员才能把钱给你。合法的证件就相当于密码学上的签名,密码学的性质保证了别人没有办法伪造你的签名,也就没有办法伪造你的身份。产生私钥以及签名的时候,都要有好的随机源,产生的随机数要足够随机。但又这些也不是足够的,银行工作人员要足够自觉,不能把钱交给那些没有合法证件的人,只有这两条合在一起,才能保证别人不能把你账上的钱转走。
基于交易的账本模式(transaction-based ledger)#
一个区块中包含转账交易,铸币交易,但并不包含账户的余额,想知道哪个账户上有多少钱需要通过交易来推算。区块链是去中心化的账本,比特币使用的是基于交易的这种账本模式(transaction-based ledger),系统当中并不会显示每个账户有多少钱。
除了比特币这种基于交易的账本模式,与之对应的还有基于账户的账本模式(account-based ledger),比如以太坊系统。在这种模式中,系统是要显示的记录每个账户上有多少币。
基于交易的账本模式,隐私保护性较好,缺点是比特币当中的转账交易要说明币的来源。为什么要说明币的来源?比如说你要转给别人 10 个比特币,那谁知道你有没有这 10 个比特币?比特币系统中没有这个账户的概念,没有地方记录你一共有多少个比特币。所以每个交易都必须得说清楚你这个币是从哪来的,是从其之前的哪一个交易的哪个输出中哪一个。
以太坊系统当中就不存在这个问题,基于账户的模式就不用说明币的来源,就像你想要知道你银行账户的余额,你去登录银行网站就可以查得到。
UTXO(未花费交易输出)#
比特币系统中的全节点要维护一个叫UTXO(unspent transaction output)的数据结构,即未花费交易输出。
区块链上有很多交易,有些交易的输出可能已经被花掉,有些还没有被花掉。所有没有被花掉的输出的集合就叫做 UTXO。
一个交易可能有多个输出,假如 A 给 B 转 5 个比特币,B 花掉了。A 也给 C 转了 3 个比特币,C 没有花掉。这时 5 个比特币就不算 UTXO,而 3 个比特币算。比特币中每一笔交易都要花费至少一笔输入,产生至少一笔输出,而其所产生的输出,就是 UTXO(未花费过的交易输出)。B 花掉的 5 个比特币虽然不在 UTXO 里面,但是它转账给了 D,而 D 没有花掉,那么这 5 个比特币就保存在 D 的 UTXO 里面。如果某个人收到比特币的转账交易后,这个钱始终不花,那么这个信息就会永久保存在 UTXO 里面。有可能是不想花,也有可能是把密钥丢了。
UTXO 集合当中的每个元素要给出产生输出的交易的哈希值,以及它在这个交易里是第几个输出。这两个信息就可以定位到 UTXO 中的输出。
UTXO 的作用是为了快速检测双花,节省时间,笨办法就是往前追溯整个链也能查清楚。
UTXO 集合只是每个全节点自己在内存中维护的,主要是为了快速查找、判断该交易是不是属于双花,但这个集合的内容并没有写到区块链里。
交易费(transaction fee)#
每个交易可以有多个输入,也可以有多个输出,所有输入金额之和要等于输出金额之和,即 total inputs=total outputs,但实际上交易 total inputs 略微大于 total outputs。
假如输入 1 比特币,输出 0.99 比特币,另外 0.01 比特币作为交易费给获得记账权发布区块的节点。
如果挖矿的奖励只有区块奖励,发布区块的节点为什么一定要把你的交易打包在区块呢?
他们还要验证你的交易的合法性,如果交易较多占用的带宽会比较大,网络传播速度也会更慢。所以只有区块奖励是不够的。
因此比特币系统设计了第二个激励机制:交易费(transaction fee),也就是你把我的交易打包在区块里,我给你一些小费。交易费一般很小,也有一些简单的交易没有交易费。
怎么判断交易费该给哪个矿工?即事先怎么知道哪个矿工会挖到矿?
事先不需要知道哪个矿工会得到这个交易费。哪个矿工挖到矿了,就可以把这个区块里所包含的交易差额收集起来,作为他自己的交易费。
如何计算一个地址中比特币的余额#
全节点看一下这个账户在 UTXO 里对应的输出总共收到多少个币,就是该账户上有多少钱。若没有维护 UTXO,从当前区块可以追溯到第一个区块,这样遍历一遍所有区块,并在内存中保留所有交易信息为 UTXO。
区块链的存储和接受#
区块链交易系统使用 Berkeley DB(文件数据库)作为钱包数据库,使用LevelDB(键值数据库)存储区块的索引和UTXO(未使用的交易输出)。节点在启动时,将整个区块链的索引从 LevelDB 加载入内存。当收到一个新区块时,节点对新区块中的所有交易进行检测,如验证交易格式、交易大小、交易签名、UTXO 是否匹配、交易签名、脚本合规等方面。
如果验证成功,检查上一区块头与链头区块哈希值是否一致,如果一致,则更新 UTXO 数据库和回滚交易数据库;如果不一致(即分叉),则将该区块放在孤儿区块池中。当节点发现网络中存在另一条更长的区块链时,就需要断开现有的区块并对区块链进行重组。
如果验证不成功,会抛弃该区块,继续等待新区块的到来(矿工会继续计算新区块的数学难题)。
比特币的具体实现#
区块的基本结构#
区块的基本结构由 4 部分组成。
- 区块分隔符(4 字节)
- 区块大小(4 字节)
- 区块头部(80 字节)
- 区块体(不确定)
比特币协议对区块的大小有 1M 字节的限制。比特币系统采用的传播方式是非常耗费带宽的,带宽是瓶颈。按 1M 的区块大小限制来算的话,一个新发布的区块有可能需要几十秒,才能传输到网络大部分地方,这已经是挺长的时间了,所以这个限制值不算小。
区块头部#
区块头部由 6 部分组成。
- 区块版本号(4 字节)
- 上一个区块头的哈希值(32 字节)
- 默克尔树的根哈希值(32 字节)
- 时间戳(4 字节)
- 目标值(4 字节)
- 随机数(4 字节)
区块体#
里面记录了区块的交易记录与每条交易记录的详情。
通过对区块体中所有交易记录,以二叉树的形式迭代的两两拼接,进行哈希操作,就可以得到默克尔树的根哈希值。
某个区块的情况#
Blockchain.com Explorer | BCH | ETH | BCH
左边:
第一行表明:该区块包含了 686 个交易
第二行:总输出 XXX 个比特币
第四行:总交易费(686 个交易的交易费之和)
最下面一行:区块奖励(矿工挖矿的主要动力)
第五行:区块的序号
第六行:区块的时间戳
第九行:挖矿的难度(每隔 2016 个区块要调整挖矿的难度,保持出块时间在 10 分钟左右)
倒数第二行:挖矿时尝试的随机数
右边:
第一行:该区块块头的哈希值
第二行:前一个区块块头的哈希值
(注意:计算哈希值只算块头)
两个哈希值的共同点:前面都有一串 0。是因为,设置的目标预值,表示成 16 进制,就是前面一长串的 0。所以凡是符合难度要求的区块,块头的哈希值算出来都是要有一长串的 0。
第四行:默克尔根是该区块中包含的那些交易构成的默克尔树的根哈希值。
比特币区块结构体#
字段解释
nonce 不够用了怎么办?
nonce 是 32 位的无符号整数。nonce 只有 2 的 32 次方个可能的取值。按照比特币现在的挖矿情况来说,很可能把 2 的 32 次方个取值都验了一遍也找不到合适的。那怎么办呢?block header 的数据结构里还有哪些域是可以调整的呢?
块头里各个域的描述
第一行:比特币协议的版本号(无法更改的)
第二行:前一个区块的块头的哈希值(无法更改)
第三行:默克尔树的根哈希值(可以更改)
第四行:区块产生的时间(可以调整)比特币系统不要求特别精确的时间,可以在一定范围内调整。
第五行:目标预值(编码后的版本)(只能按协议中的要求定期调整)
第六行:随机数
挖矿时只改随机数不够,还可以更改根哈希值。
铸币交易没有输入,它有一个 coinbase 铸币交易,可以写入任何的内容。也可以把 digital commitment 里的 commit 的哈希值写入里面。也可以把第一节讲到的预测股市的内容写入里面,coinbase 的内容是没有人会检查的,甚至可以写你的心情。那这个域对我们有什么用呢?
对应的是最后一个 block header 里的根哈希值对应的默克尔树,左下角的交易是 coinbase,把它的域改了之后,其上的哈希值就发生了变化,然后沿着默克尔树的结构往上传递。最后导致 block header 里的根哈希值发生变化(默克尔根是 block header 的一部分)。块头里 4 个字节的 nonce 不够用,还有其他字节可以用,比如 coinbase 域的前八个字节当做 extra nonce 来用,这样子搜索空间就增大到了 2 的 96 次方。
所以真正挖矿的时候只有两层循环,外层循环调整 coinbase 域的 extra nonce。算出 block header 里的根哈希值之后,内层循环再调整 header 里的 nonce。
如图,普通的转账交易的例子
该交易有两个输入和两个输出。
左上角:这里的 output 其实是输入,指的是之前交易的 output。
右上角:这里的 output 都是未花费,都没有被花掉,会保存在 UTXO 里面。
右边表格第一行:输入的总金额。
依次往下:输出总金额、输入输出之间的差值(交易费)。
两表格下面:可以看出输入和输出都是用脚本的形式来指定的。
比特币系统中验证交易的合法性,就是把 input scripts 和 output script 配对后执行来完成的。注意:不是把图中的 input scripts 和 output scripts 配对,因为这两个脚本是一个交易中的脚本。
是把这里的输入脚本和前面提供币来源的交易的输出脚本配对。如果输入输出脚本拼接在一起,能顺利执行不出现错误,那么该交易就是合法的。
比特币网络#
比特币工作在应用层(application layer block chain),它的底层是一个网络层(network layer overlay network)。
比特币的 P2P 网络是非常简单的,所有节点都是对等的,不像有的 P2P 网络有所谓的超级节点(super node)、主节点(master node)。
要加入 P2P 网络首先得知道至少有一个种子节点,然后你要跟种子节点联系,它会告诉你它所知道的网络中的其他节点,节点之间是通过 TCP 通信的,这样有利于穿透防火墙。当你要离开时不需要做任何操作,不用通知其他节点,退出应用程序就行了,别的节点没有听到你的信息,过一段时间之后就会把你删掉。
比特币网络的设计原则:
simple(简单),robust(健壮),but not efficient(而不是高效)。
每个节点维护一个邻居节点的集合,消息传播在网络中采取 flooding 的方式。节点第一次听到某个消息(交易)的时候,把它传播给去他所有的邻居节点,同时记录一下这个消息我已经收到过了,下次再收到这个消息的时候,就不用转发给邻居节点了,避免消息洪范。
邻居节点的选取是随机的,没有考虑底层的拓扑结构。比如一个在加利福尼亚的节点,它选的邻居节点可能是在阿根廷的。这样设计的好处是增强鲁棒性(robust),它没有考虑底层的拓扑结构,但是牺牲的是效率,你向身边的人转账和向美国的人转账速度是差不多的。
比特币协议对区块的大小有 1M 字节的限制。比特币系统采用的传播方式是非常耗费带宽的,带宽是瓶颈。按 1M 的区块大小限制来算的话,一个新发布的区块有可能需要几十秒,才能传输到网络大部分地方,这已经是挺长的时间了,所以这个限制值不算小。
比特币网络的传播属于尽最大努力交付(best effort)。一个交易发布到比特币网络上,不一定所有的节点都能收到,而且不同的节点收到这个交易的顺序也不一定是一样的。
挖矿难度#
调整挖矿的难度就是调整目标空间在整个输出空间中所占的比例。比特币用的哈希算法是 SHA-256,这个产生的哈希值是 256 位,所以整个输出空间是 2 的 256 次方。调整这个比例,即目标空间占输出空间的比例,通俗的说,就是哈希值前面要有多少个 0。比如说 256 位的哈希值,要是合法的区块,要求算出来的哈希,前面至少有 70 个 0。当然这只是通俗的说法。
难度系数 - 计算目标值 target#
我们说挖矿就是一个不断寻找 nonce 的过程,只有满足条件的哈希才会被区块链接受。
区块头包含一个难度系数(Difficulty),该值决定了计算哈希的难度。
区块链协议规定,使用一个常量除以难度系数,可以得到目标值(Target)。
显然,目标阈值越小,挖矿的难度越大。
为什么要调整挖矿难度#
为什么平均出块时间要设置为 10 分钟?
系统里的总算力越来越强,挖矿难度保持不变的话,出块时间是越来越短的。
出块时间如果越来越短的话,区块分叉会成为常态,而且不仅会出现二分叉,可能会出现很多的分叉。比如 10 个区块同时被挖出来,系统可能会出现 10 分叉。分叉如果过多,对于系统达成共识是没有好处的,而且危害了系统的安全性。
比特币协议是假设大部分算力掌握在诚实的矿工手里。系统当中的总算力越强,安全性就越好,因为有恶意的节点想掌控 51% 的算力就越难。如果掌握了 51% 的算力,它就可以干很多坏事,比如分叉攻击。
如果后面分叉多的话,前面某个区块里的某个交易,很可能就遭受分叉攻击,恶意节点会试图回滚。
因为后面分叉多,算力就会被分散,恶意节点得逞的概率更大。这个时候恶意节点就不需要 51% 的算力了,可能 10% 的算力就够了,因此出块时间不是越短越好。
10 分钟的出块时间是最优的吗#
不一定。改成其他值也可以,有间隔只是说应该有个常数范围。以太坊系统出块时间就降低到了 15 秒,所以以太坊的出块速度是比特币的 40 倍。
出块时间大幅度下降之后,以太坊就要设计新的协议,叫ghost。在该协议中,这些分叉,产生的孤儿区块(比如 10 个同时被挖出来的区块)就不能丢弃掉了,而是也要给它们一些奖励,这叫uncle reward。以太坊也要调整挖矿难度,使出块时间保持在 15 秒。
难度系数的动态调节#
挖矿具有随机性,无法保证正好 10 分钟产出一个区块,有时 1 分钟就算出来了,有时几个小时可能也没结果。总体来看,随着硬件设备的提升,以及矿机数量的增长,计算速度一定会越来越快。
为了将产出速率恒定在 10 分钟,中本聪还设计了难度系数的动态调节机制。
难度系数大概每两周(2016 个区块)调整一次。如果这两周中,区块的平均生成速度是 9 分钟,就意味着比法定速度快了 10%,因此接下来的难度系数就要调高 10%;如果平均生成速度是 11 分钟,就意味着比法定速度慢了 10%,因此接下来的难度系数就要调低 10%。难度系数越调越高,目标值越来越小,导致挖矿越来越难。
actual time 指产生 2016 个区块实际花费的时间,expected time 指产生 2016 个区块应用的时间,即 2016×10 分钟。
实际上,上调和下调都有四倍的限制。假如实际时间超过了 8 个星期,那么我们计算公式时也只能按 4 倍算,目标预值增大最多只能增大 4 倍。
如何让所有的矿工同时调整目标阈值 target#
计算 target 的方法写在比特币系统的代码里,每挖到 2016 个区块会自动进行调整。
如果一个节点不调,将区块发布出去,大部分诚实的节点是不会认的。
nBits 是 target 一个编码的版本,在 block header 里没有直接存储 target 的域,因为 target 的域是 256 位,直接存 target 的话要 32 个字节,nBits 在 header 里只有四个字节,所以可以认为是它的一个压缩编码。如果遇到有恶意的矿工,该调的时候不调,这时检查区块的合法性就通不过,因为每个节点要独立的验证发布的区块的合法性。检查的内容就包括:nBits,目标阈值设的对不对。
上面是难度调整曲线,可以看出很明显是一段一段的。每隔两个星期,难度上一个台阶,说明挖矿的人越来越多,用的设备越来越先进,反应出大家对比特币的热情越来越高。
如果出现相反的情况,比如某个加密货币的挖矿难度越调越小,说明挖矿变得越来越容易了。但这不是好事,说明大家对币的热情是逐渐减小的,持续出现这种情况说明这个币将被淘汰。
比特币节点#
比特币系统中有两种节点,一种是全节点,一种是轻节点。
全节点#
- 一直在线
- 在本地硬盘上维护完整的区块链信息
- 在内存中维护 UTXO 集合,以便快速检验交易的正确性
- 监听比特币网络上的交易信息,验证每个交易的合法性
- 监听别的矿工挖出的区块,验证其合法性。
挖矿:
- 决定沿着哪条链挖下去
- 决定哪些交易被打包进区块
- 决定当出现等长分叉时选择哪个分叉(缺省情况是选择最先接收到的区块的分叉)
轻节点#
- 不是一直在线
- 不用保存完整区块链,只要保存每个区块块头(这样和全节点的大小相差大约 1000 倍)
- 不用保存全部交易,只需要保存和自己相关的交易
- 没法验证大多数交易的合法性,只能检验与自己相关的交易的合法性
- 无法检测比特币网络上发布的区块的正确性
- 可以验证挖矿的难度(因为挖矿时候计算哈希值只用到了块头信息,而块头信息轻节点是保存了的)
- 只能检测哪个是最长链,不知道哪个是最长合法链(因为无法检测这条链上所包含的交易都是合法的)
轻节点假设矿工(全节点)大多是有理智的,即假设矿工们不会沿着不合法的链一直挖下去。工作量证明要做很多的工作才能把一个区块挖出来。如果你挖出这个区块里被发现包含非法交易的话,那就白挖了。所以轻节点假设大多数全节点矿工不会干这种事。
比特币网络中大部分节点都是轻节点,如果只是想转账,而不是去挖矿的话,只用轻节点就可以了。
挖矿的设备#
第一代挖矿设备:CPU#
最早时候大家都是用普通计算机来挖矿,但如果专门搞一台计算机来挖矿是很不划算的。因为计算机大部分内存是闲置的(挖矿只要用到很少一部分内存),CPU 大部分部件是闲置的(计算哈希值的操作只用到通用 CPU 中的很少一部分指令),硬盘和其它很多资源也都是闲置的。随着挖矿难度提高,用通用计算机上的 CPU 挖矿很快就无利可图了。
第二代挖矿设备:GPU#
GPU 主要用来做通用的大规模并行计算,用来挖矿还是会有不少浪费,如用于浮点数计算的部件。
第三代挖矿设备:ASIC 芯片#
ASIC 即应用特定集成电路(Application Specific Integrated Circuit),这之中有专门为了挖矿而设计的芯片,没有多余的电路,干不了别的事,它的性价比是最高的,而且为某一种加密货币设计的 ASIC 芯片只能挖这一种加密货币的矿,除非两个货币用同一个挖矿难题。
有些加密货币在刚启动的时候,为了吸引更多的人来挖矿,特意用一个和已有的其它加密货币一样的挖矿难题,这种情况叫合并挖矿(merge mining)。
研制挖特定加密货币的 ASIC 芯片需要一定周期,但和研制通用芯片的速度相比已经是非常快的了,如研制比特币挖矿的 ASIC 芯片大约用一年的时间。不过加密货币的价格变化是比较剧烈的,曾经就发生过比特币价格在几个月内下跌 80%,因为加密货币多变的价格,这些挖矿设备的研制风险也是很大的。
挖矿的竞争越来越激烈,定制的 ASIC 芯片可能用了几个月就过时了,到时候又要买新的 ASIC 芯片参与竞争。ASIC 矿机上市后的大部分利润也就在前几个月,这个设备的迭代也是很快的。
要买 ASIC 矿机往往要先交钱预定,过一段时间厂商才会发过来。实际上有些黑心厂商在生产出来以后也不交付给用户,声称还没生产好,然后自己在这段黄金时间用矿机挖矿赚取比特币。不过这其实看得出来,比特币系统中算力突然有了大的提高,那一般是某个大的厂商生产出了新的矿机。所以真正赚钱的未必是挖矿的,而是卖矿机的。
ASIC 抗性#
为了让通用计算机也能参与挖矿过程,抗 ASIC 芯片化,有些加密货币采用替代挖矿难题(Alternative mining puzzle),以去对抗那些只为了解决特定挖矿难题而设计出来的 ASIC 矿机。
比特币挖矿的趋势:大型矿池#
单个矿工挖矿的收益是很不稳定的,平均出块时间 10 分钟是对于比特币系统中的所有矿工而言的。一个矿工用一个矿机挖出矿的时间可能要很久,并且除了挖矿之外还要承担全节点的其它责任。
矿池将很多矿工组织起来,** 一般的架构就是一个矿主(pool manager)** 全节点去驱动很多矿机,下属矿工只负责计算哈希值,全节点的其它职能只由矿主来承担。有了收益以后再大家一起分配。
矿池收益怎么分配 - 工作量证明#
如果矿池中的矿机都是属于同一个机构的,那怎么分配就只是公司内部怎么发工资的问题了。
如果矿机来自不同机构,这时候矿工很可能分布在世界各地,只是都加入了这个矿池。矿工和矿主联系,矿主将要计算的哈希值的任务分配给他,矿工计算好后将结果发给矿主,最终得到出块奖励后一起参与分红。
能否平均分配?即挖到区块后奖励平分给所有矿工。这样就完全是吃大锅饭的模式了,有的矿工完全可以不干活或者少干活,所以需要按矿工的贡献大小进行分配,所以这里也需要工作量证明,来证明每个矿工所做的工作。
每个矿工单打独斗之所以收入不稳定,是因为挖矿难度太大了(相比比特币系统的平均出块时间),所以可以考虑矿池将挖矿的难度降下来。
比如本来要求前面有 70 个 0,现在矿池只要求前面有 60 个 0,这样挖到的是一个share(几乎有效区块),即这个区块差不多在一定程度上是符合难度要求的。矿工挖到这样的区块之后,将其提交给矿主,矿主拿到这些区块并没有什么用,仅仅是因为目标空间是这个问题的解空间的子集,并且求解两个问题的过程是一样的(都是计算哈希),因此这些区块可以作为证明矿工所做的工作量的证明。等到某个矿工真正挖到矿,获取出块奖励之后,再按照大家提交的 share 的多少来进行分配。
矿工能否在参与矿池时独吞出块奖励#
是否会有这样的矿工:挖到 share 提交给矿主,挖到真正的矿自己发布出去以获取出块奖励?
这是没法独吞出块奖励的,因为每个矿工的任务是由矿主来分配的,矿主负责组装好区块,然后交给矿工去不断尝试 nonce 和 CoinBase 交易中的 extra nonce,有可能就是讲它们划分一下,然后分配给不同的矿工去做,要注意铸币交易 CoinBase 交易中的收款人地址是矿主的地址。所以矿工挖到区块之后,如果他不提交给矿主自己发不出去是没有用的。里面的收款地址是矿主的,他取不出钱来,所以只要是当初按矿主给分配的任务进行挖矿的,就不可能偷区块奖励。
如果他一开始就不管矿主的任务,自己组装一个区块,偷偷把收款地址改成自己地址,会怎样?
那样他提交 share 给矿主的话,矿主是不认的,因为里面交易列表被改过了,coinbase 交易里面的内容发生了变化,算出的默克尔树的根哈希值也是不一样的。这种情况下矿主是不会给他工作量证明的,那就相当于矿工一开始就单干,跟矿池是没关系的。
矿池之间的竞争#
矿池之间是有竞争对手的,一种竞争方式就是到对方的矿池里去捣乱,即派遣一些矿工去加入到对方的矿池里去挖矿,只提交 share,但挖到真正的矿就将其丢弃掉,故意不提交。然而如果这个对手矿池仍然获得了出块奖励,这些矿工也能参与分红。
大型矿池带来的危害#
如果没有矿池,如果要发动 51% 攻击,攻击者要花费大量的硬件成本。有了矿池以后,矿池实际上将算力集中起来,攻击者未必拥有很多算力,只要吸引大量的不明真相的群众将算力集中到自己的矿池就可以。
在 2014 年的时候 GHash 矿池的总算力就超过了比特币系统中总算力的一半,引起了恐慌,然后 GHash 主动减少了算力,以防止大家对比特币失去信心。
如今的矿池的算力还算比较分散,有好几家矿池在竞争,但可能只是一个表面现象。
假如一个机构有一半以上的算力,他不一定要把算力集中在一个矿池里,而可以把算力分散隐藏在很多矿池里,真正需要发动攻击的时候再集中起来发动攻击,因为矿工要转换矿池是很容易的。
这就是矿池带来的危害,如果没有矿池,想要发动 51% 的攻击,攻击者要投入大量的成本来购买到足够的矿机,能够达到系统中半数以上的算力。有了矿池之后,他可能只占很小一部分比例的算力,只要能够吸引到足够多的矿工,足够多的不明真相的群众加入到他的矿池里来就行了。
一般来说,矿池的矿主要收取一定比例的出块奖励作为管理费。矿主也要按照比例收取管理费,有的是按照出块奖励的比例,也有的是抽取交易费。有的一些有恶意的矿池在发动攻击之前,可能故意把管理费降得特别低,甚至是赔本赚吆喝,吸引足够多的矿工加入之后就可以发动攻击了。这是大型矿池的一个弊端,使得 51% 的攻击更加容易了。
假设出现超大型矿池,具体能发动哪些攻击#
分叉攻击 - 51% 算力攻击#
假如一个区块链,其中一个区块包含了一个大笔的交易,又等了几个确认区块之后,自认为已经安全了。然后这时就可能有人在该交易前面的区块发动分叉攻击。看上去好像追赶的道路是很漫长的,但如果拥有 51% 的算力,最终还是可以成功攻击。因为算力占了半数以上,并且矿工挖矿任务被分配开并行进行,分叉出来的链的增长速度很快,最终势必成为最长合法链。
另外,不要把 51% 当成绝对的门槛,有可能不到 51% 就可以。算力都是估计的,而且算力还在不断变化,都是概率问题。
抵制(Boycott)#
比如说攻击者不喜欢某个账户,怀疑某个账户参与非法交易,想把这个账户封锁掉,所有跟这个账户相关的交易都不让上链。
假如 A 把某个交易 A→B 发布到区块链上,攻击者就会马上进行分叉,产生一个不包含这个交易的区块,所有跟 A 有关的交易也都不包含进去。
这种攻击跟分叉攻击区别是什么?
他没必要等后面几个确认区块。这时候如果攻击者等待确认区块,是为了让 B 放心,B 以为后面有六个确认区块,已经没事了,然后攻击者再发动分叉攻击。
而如果目的是为了抵制的话,就没有必要等后面区块生成。A→B 交易一上链马上进行分叉,越早越好,因为攻击者是希望别人沿着他的链往下挖的。
前面讲过,有些有恶意的节点故意不把某些交易写入区块里,是可以的。但没有关系,后面的区块还是会包含的。但是如果这个坏人拥有 51% 的算力的话,他可能仗着自己算力强,公开抵制他想抵制的交易。这样别的矿工也不敢随便把交易打包进去了。
矿池总结#
矿池的出现减轻了矿工的负担,矿工只需要挖矿,计算哈希值就行了,别的事情都由矿主来完成。矿工的收入分配也更加稳定。但矿池的出现也有危害,发动 51% 的攻击变得容易了。他不一定自己有这么强的算力,只要动员召集这些算力就可以了。
这有点类似于云计算中的按需计算(on demand computing)。平时不需要维护很大的计算机群,需要用的时候可以随时召回来。而矿池的情况,是按需挖矿(on demand mining)。
比特币脚本(bitcoin script)#
比特币脚本是一种基于栈的脚本语言,也被称为堆栈语言,它是图灵不完备的。
一个交易实例如下:
- 输入脚本(Input Scripts)—— 输入脚本,分别对应上边的最左边两个
- 输出脚本(Output Scripts)—— 输出脚本,分别对应上边的最右边两个输出
交易结构#
- txid:交易的 id 号,transaction ID
- hash:交易的哈希值
- version:使用的比特币协议版本号
- size:这个交易的大小
- locktime:用来设定交易的生效时间,一般为 0
- vin:输入脚本(后面会详细讲)
- vout:输出脚本
- blockhash:所在区块的哈希值,可以看到是以一长串的 0 开头,这就与前面提到的挖矿的难度要求相对应。
- confirmations:这个交易已经有多少个确认。一般 6 个确认后,交易就可以说是不可改变的了。
- time:交易产生的时间(表示从很早的一个时间点到现在过了多久)
- blocktime:区块产生的时间
交易结构是一个数组:每个交易的输入,都要说明输入花的这个币的来源,是来自之前的哪个交易的输出。
交易输入#
txid
:输入要花的这个币的,是来自于 id=txid 的那个交易的输出vout
:表示是哪个交易的第几个输出(这里是第 0 个)scriptSig
:输入脚本(解锁脚本)asm
脚本的人类可读形式,类似于操作指令的形式hex
: 脚本用十六进制编码的形式,用来在比特币交易的结构中进行传输和存储
如果有多个输入的话,就要说明每个输入的来源,并且要签名。也就是说,比特币中的一个交易可能要多个签名。
交易输出#
- value:转账的金额,单位是比特币(BTC),最小单位 Satoshi 聪(0.00000001 一亿分之一比特币)
- n:表示序号,是这个交易的第几个输出
scriptPubkey
:输出脚本(锁定脚本)- 上面 n 表示当前输出的第几个
- asm 是输出脚本的内容
- reqSigs 表示需要的签名数量
如下图:
前面一个区块中,交易是:A 把比特币转给了 B。后面的那个交易指:B 把比特币转给了 C。B->C 比特币这个交易中,币的来源是前面 A->B 这个交易。
要验证这个交易的合法性,就要 B->C 这个交易的输入脚本,和 A->B 这个交易的输出脚本,拼接在一起执行。早期是拼接在一起执行,后来为了安全起见,就先运行 B->C 这个交易的输入脚本,运行成功后,再运行 A->B 这个交易的输出脚本。
两个脚本都运行成功(栈顶的结果为非零值),表示这个交易是合法的。
标准交易类型(Standard Type)#
- P2PKH(比特币网络默认类型)
- P2PK(多出现在矿工接收奖励和手续费的 coinbase 交易中)
- 多签名(Multi-signature)
- P2SH(将另一笔交易的脚本哈希后作为新脚本,压缩字节)
- P2WPKH、P2WSH:隔离见证采用后 P2PKH 的另一种实现方式,字节占用更少,且签名从解锁脚本中移出
- nulldata (OP_RETURN):隔离见证采用后用于存储见证信息的默克尔树根(Merkle root of the witness tree),在 coinbase 交易的接收方出现,该笔输出将直接从 UTXO 中移除,后续不能被花费,转移的比特币数量一般都为零。vout 中没有 reqSigs 和 addresses 这两对键值。
P2PK(支付给公钥)#
输出脚本直接给出收款人的公钥。
这种形式是最简单的。
输入脚本:
OP_PUSHDATA(Sig)
输出脚本:
OP_PUSHDATA(PubKey)
OP_CHECKSIG
输出脚本中直接给出收款人的公钥。输入脚本中直接给出签名就行。CHECKSIG 是检查签名的操作。
脚本的执行(这里拼接在了一起,实际要分开):
- PUSHDATA (Sig) —— 把输入脚本的签名压入栈
- PUSHDATA (PubKey) —— 把输出提供的公钥压入栈
- CHECKSIG —— 将栈内两个元素弹出,用公钥检验签名是否正确。若正确返回 True
P2PKH(支付给公钥哈希)#
支付给公钥哈希是最常用的脚本形式。常常以 “1” 开头的地址就是 P2PKH 地址。
**【示例】**Alice 向 Bob 转账(支付给公钥哈希)
- Bob 创建公钥私钥对,向 Alice 提供自己的公钥哈希
- Alice 创建一个输出脚本(锁定脚本)scriptPubKey,包含 Bob 的公钥哈希
- Alice 广播交易到区块链网络中
区块链网络将其归类为未花费的交易输出(UTXO),Bob 的钱包软件将其显示为可花费余额。
- 一段时间后,Bob 决定使用 UTXO 时,他必须创建一个输入脚本(解锁脚本)signature script。
输入脚本也称为 scriptSigs。
输入脚本中需要包含:
- Alice 创建的那个交易 **txid(交易