doraemon

doraemon

let's go write some rusty code and revolutionize the world!

比特币基础知识

reference:北京大学肖臻老师《区块链技术与应用》公开课

比特币中哈希函数的三个性质#

collision resistance#

对于两个不同的输入,输出却是相等的,这就称为哈希碰撞

好的哈希函数应该尽量避免哈希碰撞,具有 collision resistance 的性质。

没有哪个哈希算法能证明是 collision resistance,哈希碰撞是不可避免的,因为输入空间总大于输出空间

  • 该性质的作用

对一个 message 求 digest。

比如 message 的哈希值是 H (m)=digest,如果有人想篡改 m 值而 H (m) 不变,则无法做到。

hiding#

哈希函数的计算过程是单向的,不可逆的。(从 H (x) 无法推导出 x)。

hiding 性质前提是输入空间足够大,分布比较均匀。

除了密码学中要求的这两个性质外,比特币中用到的哈希函数还有第三个性质。

puzzle friendly#

比特币挖矿的过程中实际就是找一个 nonce (number of once),nonce 跟区块的块头里的其他信息合一起作为输入,得出的哈希值要小于等于某个指定的目标预值。H (block header)≤target。block header 指块头,块头里有很多域,其中一个域是我们可以设置的随机数 nonce,挖矿的过程是不停的试随机数,使得 block header 取哈希后落在指定的范围之内。

puzzle friendly 是指挖矿过程中没有捷径,为了使输出值落在指定范围,只能一个一个去试,所以这个过程还可以作为工作量证明 (proof of work)。

哈希值事先是不可预测的,挖矿很难,验证很容易。(difficult to solve ,but easy to verify)

SHA-256(secure hash algorithm)#

比特币中用的哈希函数叫作 SHA-256 (secure hash algorithm) 以上三个性质它都是满足的。

若某一区块链交易系统使用的 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),产生公私钥是随机的,如果随机源不好,就有可能产生相同的公私钥。在密码学实现上一般用物理噪声源芯片做随机源。

比特币中用的签名算法,不仅是生成公私钥的时候要有好的随机源,之后每一次签名时也要有好的随机源。只要有一次签名用的随机源不好的话,就有可能泄露私钥。

随机产生一对公私钥相同的概率概率是微乎其微,可以忽略不计。

比特币中重要的数据结构#

hash pointer#

比特币中最基本的结构就是区块链,区块链就是一个一个区块组成的链表。区块链和普通的链表相比有什么区别?

比特币用哈希指针代替了普通指针(block chain is a linked list using hash pointers)。

区块链第一个区块叫作创世纪块 (genesis block) ,最后一个区块 是最近产生的区块 (most recent block) 每一个区块都包含指向前一个区块的哈希指针 。

通过这种结构,可以实现tamper-evident log。如果有人改变了一个区块的内容,后面一个区块的哈希指针就对不上,因为后一个区块哈希指针是根据前一个区块的内容算出来的,所以后一个哈希指针也得改,以此类推,我们保留的最后一个哈希值也会变化。所以只要记录了最后一个区块的哈希,就能保证前面所有区块没有被篡改

指针保存的是本地内存的地址,那么只是在本地这台计算机上才有意义,发送到其他计算机上就没有意义了。那么在发布区块的时候哈希指针是怎么能够通过网络进行传输呢?

所谓的哈希指针只是一种形象的说法,实际系统中用的时候只有哈希,没有指针

Merkle tree#

区块头 (block header) 中包含了 Merkle tree 的根哈希 (root hash),只要记住 Merkle tree 根哈希值,就能检测出对树中任何部位的修改

找到交易在 Merkle tree 中所在的位置 (所有交易都在叶子结点),这时该区块一直往上到根节点的路径就叫 merkle proof。

轻节点只保存 block header,如果轻节点想知道某个交易是不是包含在 Merkle tree 中,需要向全节点发出请求,请求一个能够证明此交易被包含的 Merkle proof,里面包含轻节点需要的哈希,轻节点自己在本地验证。

merkle proof 能证明什么?证明某个交易是不是在给定的区块里。

比如一个轻节点,没有维护整个区块 UTXO 的内容,只知道 block header。轻节点问一个全节点:该交易是不是在这个区块里?全节点返回一个 merkle proof 作为证明,轻节点就可以验证是否属实。

Merkle treebinary tree的区别: 用哈希指针代替了普通指针

去中心数字货币需要解决的问题#

一个去中心化的数字货币,要解决两个问题。

  • 第一个问题,谁有权发行货币
  • 第二个问题,怎么验证交易的合法性

双花问题 (double spending)#

双花即一笔钱被重复花了两次,中心化账本可以轻松解决 double spending attack,去中心化方案需要有一个数据结构来维护账本的正确性,这个数据结构就是区块链。

比特币系统中每个交易都包含了输入和输出两个部分,输入部分要说明币的来源,输出部分要给出收款人的公钥的哈希。

A 向 B 转账需要的信息:A 的私钥签名和 B 的地址 (B 收款的地址是通过公钥推算出来的)。

这个地方有两种哈希指针,第一个地方就是连接各个区块组成链表,在交易详情里面有第二个哈希指针指向前面的某个交易,为了说明币的来源 (防范双花)。

分布式共识 (distributed consensus)#

每个账户都可以发布交易,那么这个交易是广播给所有的节点的。有些交易是合法的,有些交易可能是非法的。那么谁来决定哪些交易应该被写到下一个区块中?按照什么样的顺序写进去?

分布式的共识一个简单的例子就是分布式的哈希表 (distributed hash table),比如系统里有很多台机器,共同维护一个全局的哈希表。

这里需要取得共识的内容是什么?哈希表中包含了哪些键值对 key valve pair。假如有人在自己电脑上插入一个键值对,'xiao' 这个 pair 对应的是 12345,即 'xiao'→12345。那么别人在另一台读的时候也要能把这个读出来,这就叫一个全局的哈希表。

不可能结论 (impossibility result)#

关于分布式系统有很多不可能结论 (impossibility result),其中最著名的是FLP impossibility result。这三个字母是三个专家的名字缩写,在一个异步的 (asynchronous) 系统里,(网络传输迟延没有上限就叫异步系统),即使只有一个成员是有问题的 (faulty),也不可能取得共识。

CAP 定理 (CAP Theorem)#

另一个一个著名结论:CAP 定理 (CAP Theorem),指的是在一个分布式系统中,Consistency(一致性)Availability(可用性)Partition tolerance(分区容错性),三者不可得兼。Consistency、Availability、Partition tolerance,三者只能满足两个。

区块链的不可能三角#

区块链 ** 不可能三角(blockchain trilemma)** 由以太链创始人 Vitalik 提出。

说的是区块链技术不能同时实现去 中心化性程度、安全性、高效性。

  • 去中心化(Decentralization)
  • 可扩展性(Scability)
  • 安全性(Security)

POW - 比特币中的共识协议 (consensus in BitCoin)#

基于投票的共识方案#

有些节点可能是有恶意的,假设系统中大多数节点是好的,那么该如何设计共识协议?

一种方案是投票。任何基于投票的共识方案,首先要确定谁有投票权,对不对?要有个 membership 的问题。如果这个区块链,它的 membership 是有严格定义的。比如说你这个不是谁都可以加入的,像联盟链的 Hyperledger Fabric,就是一个联盟链的协议。只有某些符合条件的大公司才能加入。那么这种情况下,基于投票的方案是可行的。我们假设大多数成员是好的,那么我们投票就是可行的

公有链中简单的直接投票是不行的。

比特币系统不是这样的。比特币系统中创建一个账户是很容易的。你就在本地产生一个公私钥,一个账户不需要任何人批准,别人甚至都不知道。只有和外部发生交易的时候,别人才知道有这样一个账户。假如有恶意的节点,就搞一台超级计算机,别的什么都不干,不停的产生各种各样的账户,然后他产生的账户超过总数的一半,他就有控制权,他就可以操纵投票结果。这种就叫女巫攻击 (sybil attack)

比特币系统当中用了一个很巧妙的机制来解决这个问题。也是投票,但不是按照账户的数目投票,而是用计算力来投票

记账权#

每一个节点都可以在本地组装出一个候选的区块,把它认为合法的交易放到这个区块里,然后就开始尝试各种随机数 nonce (number of once) 值。如果某个节点找到了符合要求的 nonce (占 4 byte),满足不等式 H (block header)≤target 的要求,我们就认为它获得了记账权

H(blockheader)targetH( 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 transaction 铸币交易是比特币系统中发行新的比特币的唯一方法,这个交易不用指出币的来源,后面的交易都是基于比特币的转移。

比特币这个争夺记账权的过程叫做挖矿。所以争夺记账权的节点被称为矿工。如果他获得记账权,我们就说他挖到矿了,或者说是挖到了区块。

怎么获得记账权 (POW 工作量证明)#

通过尝试各种随机数 nonce 解 puzzle

比特币的共识机制是靠算力来投票。我们讲比特币的哈希函数的三个性质,其中puzzle friendly这个性质保证了求解这个 puzzle 的过程没有捷径,只能一个一个 nonce 去尝试。

所以如果某一个节点他的算力是另一个节点的 10 倍,那么他获得记账权的概率也是那个节点的 10 倍。就靠算力来投票,这就是比特币中投票的特殊性。它不是一人一票,也不是一台计算机一票,而是看你每秒钟能够是尝试多少个 nonce 的数目。这个我们有时候管它叫hash rate,这个决定投票的权重,你这个节点的 hash rate 越高,那么你获得记账权得到这个出块奖励的概率也是越大。

比特币中pow 工作量证明 防范了女巫攻击,按算力记票,即使创建再多的账户,也无法使算力增强。

比特币求解 puzzle 的过程, 除了比拼算力,没有其他的实际意义。比特币越来越难被挖到,是因为出块奖励被人为减少了,比特币的稀缺性是人为造成的。虽然挖矿求 puzzle 本身是没有实际意义,但是挖矿过程对维护比特币系统安全性是很重要的,**Bitcoin is secured by mining。** 挖矿提供了一种凭借算力投票的有效手段,只要大部分算力掌握在诚实的人的手里,系统的安全性就能够得到保证。

挖矿时会不会有的矿工偷答案#

不会。发布的区块里有 coinbase transaction,里面有一个收款人地址,是挖到矿的矿工的地址。假如 A 挖到了矿,里面就是 A 的收款地址。如果要偷答案的话,就要把 A 的地址换成自己的地址,而地址如果一变化,coinbase transaction 的内容就发生了改变。

这样会导致 merkle tree 的根哈希值变化,因为这个交易和区块中所包含的其他交易是合在一起构成了 merkle tree。任何一个地方发生改变,根哈希值就会变。而 nonce 是在块头里面,根哈希值也是在块头里面,block header 的内容发生了变化之后,原来找到的 nonce 就作废了。所以不可能偷答案,因为每个矿工挖到的 nonce 是和他自己的收款地址绑定在一起的。

伯努利试验 (Bernoulli trial)#

Bernoulli trial:a random experiment with binary outcome.

伯努利试验(Bernoulli trial) 是只有两种可能结果的单次随机试验。

伯努利过程

Bernoulli process:a sequence of independent Bernoulli trials.

挖矿过程每次尝试一个 nonce 可以看作是一个 Bernoulli trial (伯努利实验),每一个随机的伯努利实验就构成了一个伯努利过程

它的一个性质是:无记忆性。Bernoulli process 一个性质是无记忆性 memoryless即做大量的实验,前面的结果对后面没有影响。

每尝试一个 nonce 成功的概率是很小的,要进行大量的实验,这时可以用泊松过程来代替伯努利过程。

progress free - 挖矿公平性的保证#

我们真正关心的是系统出块时间,在概率论中可以推导出块时间是服从指数分布的(exponential distribution)。

整个系统平均出块时间为 10min,是比特币协议设计的定期调整挖矿难度,使得平均出块时间维持在 10min 左右。具体到每一个矿工,他能够挖到下一个区块的时间,取决于矿工的算力占系统总算力的百分比。

指数分布也是无记忆的,是挖矿公平性的保证。

指数分布无记忆性,因为概率分布曲线的特点是:随便从一个地方截断,剩下一部分曲线跟原来是一样的。比如:已经等十分钟了,还没有人找到合法的区块,那么还需要等多久呢?仍然参考概率密度函数分布 ,平均仍然要等十分钟。将来还要挖多长时间,跟过去已经挖了多长时间是没有关系的。这个过程也叫 free

可以画出一个坐标轴,纵轴表示概率密度,横轴表示出块时间 time (整个系统的出块时间,并不是每个矿工的出块时间)。具体到每一个矿工,他能挖到下一个区块的时间取决于矿工的算力占系统算力的百分比。假如一个人的算力占系统总算力的 1%,那么系统出 100 个区块,就有一个区块是这个人挖的。

如果没有 progress free ,会出现什么现象:算力强的矿工会有不成比例的优势。因为算力强的矿工过去做的工作是更多的,过去尝试了那么多不成功的 nonce 之后,后面 nonce 成功的概率就会增大。以此 progress free 是挖矿公平性的保证。

能凭空产生多少币#

一开始的时候,比特币刚上线,每一个发布的区块可以产生 50 个比特币的区块奖励。

比特币系统中大约每 10 分钟就会产生一个区块,比特币协议中规定,21 万个区块以后,出块奖励就要减半,就变成了 25BTC,再过 21 万个区块,又要减半。

出块奖励大约每隔 4 年要减半,这样产生出来的比特币数量就构成了几何序列 geometric series

21000050(1+12+14+...)210000 * 50 (1+\frac{1}{2}+\frac{1}{4}+...)

照此计算最多共有 2100 万枚比特币。

截至 2022 年,比特币矿工每成功开采一个区块,将获得 6.25 个比特币。

比特币安全性分析#

前提假设大部分算力掌握在诚实的人的手里。挖矿给出的只是概率上的保证,只能说有比较大的概率下一个区块是由一个诚实的矿工发布的,但是不能保证记账权不会落在有恶意的人手里。

如果有恶意的节点获得记账权,他可能会做的事:

1. 伪造一笔不合法交易#

比如把别人账上的钱转到自己的账户上,但是由于自己不知道别人的私钥,所以不合法,验证不通过。那么其他诚实节点不会认同该区块,因为包含不合法交易,所以还会接着上一个区块去扩展,那么根据最长合法链,该区块不会获得 block reward,反而浪费大量电力,人力,最终还没有得到区块奖励。

2. double spending#

通过把花出去的钱再次花出。比如 M 转账给 A,M 又发布另一个交易,把这个钱转回给自己 (或其他人)。此时有两种情况

  1. 新区块已写入区块链 - 分叉攻击回滚交易

恶意节点需要对区块链进行分叉,在之前的区块中分叉一个新链,这种情况下攻击难度很大。有恶意节点获得一次记账权是不够的,还需要不断地获得记账权,因为诚实节点只认可最长合法链。详情见 selfish mining 。

防范这种攻击就需要多等几个区块确认 confirmation,缺省 (默认) 情况下需要 6 个 confirmation,这个时候认为前面的交易不可篡改,等待时间大概是一个小时左右

  1. 交易所在区块未被写入区块链中 - 短时间发起多笔交易

此种情况又叫 zero confirmation,在实际应用中很普遍。

每一笔交易都含有时间戳,比特币协议当中,在缺省 (默认的意思) 情况下,每个节点是接受它最早收到的那个交易。如果正确的交易先被接受,后面发起的重复交易则不会被接受,否则收款人可以直接拒绝承认这笔交易。

其实此时还是有一定的风险,因为恶攻击者的恶意节点会有极小的概率获得记账权,要绝对的安全可以等 6 个 confirmation (一小时)。

3. 故意不写合法的交易#

问题不大,合法的交易还是可以被写到下一个区块里,总会有诚实的节点写合法交易

正常情况下也会出现合法交易没有被写到区块中,因为可能某段时间交易数目太多,比特币协议中规定每个区块大小是有限制的,区块大小不能超过 1M 字节,所以如果交易太多那么有些交易只能等到下一区块发布

4. selfish mining (相似于 51% 算力攻击)#

正常情况下如果挖到区块会及时发布,以免别人挖到后自己的区块无效,作废了。但是在 selfish mining 中,恶意节点挖到区块先不发布,隐藏一条链,等待超过最长合法链时发布。

这是 forking attack 的一种手段,但是恶意节点一般需要超过 51% 的算力,难以实现。

如果只是为了多赚取正常的出块奖励,挖到第一时间不发布,那么其他人还会接着上一个区块挖,自己延着已挖到区块的挖,减少该区块的竞争。如果自己已经挖到第二个区块,当其他人宣称挖到第一个区块之后,自己把手里的两个一起发布,成为最长合法链,理论上是很可行的。但这种做法风险大,要别人挖出第一个区块时,自己已经能够挖出第二个,否则当别人挖出第一个发布时,自己手里只有一个,这时候是等长链,只能去竞争,这样可能还会白白损失第一个的区块奖励

比特币是怎么保证安全的#

两个方面

  1. 密码学
  2. 共识机制

别人没有你的私钥,就没有办法伪造你的签名,所以不能把你账上的钱转走。前提是系统中拥有大多数算力的矿工是好的,是遵守协议的,不会接受那些没有合法签名的交易。如果没有这些,密码学上的保证也就没有用武之地。

比如你去银行取钱,按照规定取钱得出示合法的证件,银行工作人员才能把钱给你。合法的证件就相当于密码学上的签名,密码学的性质保证了别人没有办法伪造你的签名,也就没有办法伪造你的身份。产生私钥以及签名的时候,都要有好的随机源,产生的随机数要足够随机。但又这些也不是足够的,银行工作人员要足够自觉,不能把钱交给那些没有合法证件的人,只有这两条合在一起,才能保证别人不能把你账上的钱转走。

基于交易的账本模式 (transaction-based ledger)#

一个区块中包含转账交易,铸币交易,但并不包含账户的余额,想知道哪个账户上有多少钱需要通过交易来推算。区块链是去中心化的账本,比特币使用的是基于交易的这种账本模式 (transaction-based ledger),系统当中并不会显示每个账户有多少钱。

除了比特币这种基于交易的账本模式,与之对应的还有基于账户的账本模式 (account-based ledger),比如以太坊系统。在这种模式中,系统是要显示的记录每个账户上有多少币。

基于交易的账本模式,隐私保护性较好,缺点是比特币当中的转账交易要说明币的来源。为什么要说明币的来源?比如说你要转给别人 10 个比特币,那谁知道你有没有这 10 个比特币?比特币系统中没有这个账户的概念,没有地方记录你一共有多少个比特币。所以每个交易都必须得说清楚你这个币是从哪来的,是从其之前的哪一个交易的哪个输出中哪一个。

以太坊系统当中就不存在这个问题,基于账户的模式就不用说明币的来源,就像你想要知道你银行账户的余额,你去登录银行网站就可以查得到。

UTXO(unspent transaction output)#

比特币系统中的全节点要维护一个叫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 的作用是为了快速检测 double spending,节省时间,笨办法就是往前追溯整个链也能查清楚

UTXO 集合只是每个全节点自己在内存中维护的,主要是为了快速查找、判断该交易是不是属于 double spending,但这个集合的内容并没有写到区块链里

交易费 (transaction fee)#

每个交易可以有多个输入,也可以有多个输出,所有输入金额之和要等于输出金额之和,即 total inputs=total outputs,但实际上交易 total inputs 略微大于 total outputs

假如输入 1 比特币,输出 0.99 比特币,另外 0.01 比特币作为交易费给获得记账权发布区块的节点

如果挖矿的奖励只有区块奖励,发布区块的节点为什么一定要把你的交易打包在区块呢?

他们还要验证你的交易的合法性,如果交易较多占用的带宽会比较大,网络传播速度也会更慢。所以只有区块奖励是不够的

因此比特币系统设计了第二个激励机制:交易费 (transaction fee),也就是你把我的交易打包在区块里,我给你一些小费。交易费一般很小,也有一些简单的交易没有交易费。

怎么判断交易费该给哪个矿工?即事先怎么知道哪个矿工会挖到矿?

事先不需要知道哪个矿工会得到这个交易费。哪个矿工挖到矿了,就可以把这个区块里所包含的交易差额收集起来,作为他自己的交易费。

transaction fee=total inputstotal outputstransaction\ fee = total\ inputs - total\ outputs

如何计算一个地址中比特币的余额#

全节点看一下这个账户在 UTXO 里对应的输出总共收到多少个币,就是该账户上有多少钱。若没有维护 UTXO,从当前区块可以追溯到第一个区块,这样遍历一遍所有区块,并在内存中保留所有交易信息为 UTXO。

区块链的存储和接受#

区块链交易系统使用 Berkeley DB(文件数据库)作为钱包数据库,使用LevelDB(键值数据库)存储区块的索引UTXO(Unspent Transaction Output,未使用的交易输出)。节点在启动时,将整个区块链的索引从 LevelDB 加载入内存。当收到一个新区块时,节点对新区块中的所有交易进行检测,如验证交易格式、交易大小、交易签名、UTXO 是否匹配、交易签名、脚本合规等方面。

如果验证成功,检查上一区块头与链头区块哈希值是否一致如果一致,则更新 UTXO 数据库和回滚交易数据库;如果不一致 (即分叉),则将该区块放在孤儿区块池中。当节点发现网络中存在另一条更长的区块链时,就需要断开现有的区块并对区块链进行重组。

如果验证不成功,会抛弃该区块,继续等待新区块的到来(矿工会继续计算新区块的数学难题)。

比特币的具体实现#

reference:Block Chain — Bitcoin

区块的基本结构#

区块的基本结构由 4 部分组成

  1. 区块分隔符(4 Byte)
  2. 区块大小(4 Byte)
  3. 区块头部 (80 Byte)
  4. 区块体(不确定)

比特币协议对区块的大小有 1M 字节的限制。比特币系统采用的传播方式是非常耗费带宽的,带宽是瓶颈。按 1M 的区块大小限制来算的话,一个新发布的区块有可能需要几十秒,才能传输到网络大部分地方,这已经是挺长的时间了,所以这个限制值不算小。

区块头部#

区块头部由 6 部分组成

  1. 区块版本号(4 Byte)
  2. 上一个区块头的哈希值(32 Byte)
  3. 默克尔树的根哈希值(32 Byte)
  4. 时间戳(4 Byte)
  5. 目标值(4 Byte)
  6. 随机数(4 Byte)

区块体#

里面记录了 区块的 交易记录每条交易记录的详情

通过对区块体中所有交易记录,以二叉树的形式迭代的两两拼接,进行哈希操作,就可以得到默克尔树的根哈希值。

某个区块的情况#

Blockchain.com Explorer | BCH | ETH | BCH

image-20230226170039708

左边

第一行表明:该区块包含了 686 个交易
第二行:总输出 XXX 个比特币
第四行:总交易费 (686 个交易的交易费之和)
最下面一行:区块奖励 (矿工挖矿的主要动力)
第五行:区块的序号
第六行:区块的时间戳
第九行:挖矿的难度 (每隔 2016 个区块要调整挖矿的难度,保持出块时间在 10 分钟左右)
倒数第二行:挖矿时尝试的随机数

右边:

第一行:该区块块头的哈希值
第二行:前一个区块块头的哈希值
(注意:计算哈希值只算块头)
两个哈希值的共同点:前面都有一串 0。是因为,设置的目标预值,表示成 16 进制,就是前面一长串的 0。所以凡是符合难度要求的区块,块头的哈希值算出来都是要有一长串的 0。
第四行 root 是该区块中包含的那些交易构成的 merkle tree 的根哈希值。

比特币区块结构体#

image-20230226170233323

字段解释

image-20230226170939750

image-20230226171347701

nonce 不够用了怎么办?

nonce 是 32 位的无符号整数。nonce 只有 2 的 32 次方个可能的取值。按照比特币现在的挖矿情况来说,很可能把 2 的 32 次方个取值都验了一遍也找不到合适的。那怎么办呢?block header 的数据结构里还有哪些域是可以调整的呢?

块头里各个域的描述
第一行:比特币协议的版本号 (无法更改的)
第二行:前一个区块的块头的哈希值 (无法更改)
第三行 tree 的根哈希值 (可以更改)
第四行:区块产生的时间 (可以调整) 比特币系统不要求特别精确的时间,可以在一定范围内调整。
第五行:目标预值 (编码后的版本)(只能按协议中的要求定期调整)
第六行:随机数

挖矿时只改随机数不够,还可以更改根哈希值
铸币交易没有输入,它有一个 coinbase 铸币交易,可以写入任何的内容。也可以把 digital commitment 里的 commit 的哈希值写入里面。也可以把第一节讲到的预测股市的内容写入里面,coinbase 的内容是没有人会检查的,甚至可以写你的心情。那这个域对我们有什么用呢?

对应的是最后一个 block header 里的根哈希值对应的 merkle tree,左下角的交易是 coinbase,把它的域改了之后,其上的哈希值就发生了变化,然后沿着 merkle tree 的结构往上传递。最后导致 block header 里的根哈希值发生变化 (merkle root 是 block header 的一部分)。块头里 4 个字节的 nonce 不够用,还有其他字节可以用,比如 coinbase 域的前八个字节当做 extra nonce 来用,这样子搜索空间就增大到了 2 的 96 次方。

所以真正挖矿的时候只有两层循环,外层循环调整 coinbase 域的 extra nonce。算出 block header 里的根哈希值之后,内层循环再调整 header 里的 nonce。

如图,普通的转账交易的例子

image-20230226172236697该交易有两个输入和两个输出。
左上角:这里的 output 其实是输入,指的是之前交易的 output。
右上角:这里的 output 都是 unspent,都没有被花掉,会保存在 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 的过程,只有满足条件的哈希才会被区块链接受。

H(blockheader)targetH( block header )≤target

区块头包含一个难度系数(Difficulty),该值决定了计算哈希的难度。

区块链协议规定,使用一个常量除以难度系数,可以得到目标值(Target)

target=targetMaxdifficultytarget = \frac{targetMax}{difficulty}

显然,目标阈值越小,挖矿的难度越大

为什么要调整挖矿难度#

为什么平均出块时间要设置为 10min?

系统里的总算力越来越强,挖矿难度保持不变的话,出块时间是越来越短的

出块时间如果越来越短的话,区块分叉会成为常态,而且不仅会出现二分叉,可能会出现很多的分叉。比如 10 个区块同时被挖出来,系统可能会出现 10 分叉。分叉如果过多,对于系统达成共识是没有好处的,而且危害了系统的安全性

比特币协议是假设大部分算力掌握在诚实的矿工手里。系统当中的总算力越强,安全性就越好,因为有恶意的节点想掌控 51% 的算力就越难。如果掌握了 51% 的算力,它就可以干很多坏事,比如分叉攻击。

如果后面分叉多的话,前面某个区块里的某个交易,很可能就遭受分叉攻击,恶意节点会试图回滚。

因为后面分叉多,算力就会被分散,恶意节点得逞的概率更大。这个时候恶意节点就不需要 51% 的算力了,可能 10% 的算力就够了,因此出块时间不是越短越好。

10 分钟的出块时间是最优的吗#

不一定。改成其他值也可以,有间隔只是说应该有个常数范围。以太坊系统出块时间就降低到了 15s,所以以太坊的出块速度是比特币的 40 倍。

出块时间大幅度下降之后,以太坊就要设计新的协议,叫ghost。在该协议中,这些分叉,产生的orphan block(比如 10 个同时被挖出来的区块) 就不能丢弃掉了,而是也要给它们一些奖励,这叫uncle reward。以太坊也要调整挖矿难度,使出块时间保持在 15s。

难度系数的动态调节#

挖矿具有随机性,无法保证正好 10 分钟产出一个区块,有时 1 分钟就算出来了,有时几个小时可能也没结果。总体来看,随着硬件设备的提升,以及矿机数量的增长,计算速度一定会越来越快

为了将产出速率恒定在 10 分钟,中本聪还设计了难度系数的动态调节机制。

难度系数大概每两周(2016 个区块)调整一次。如果这两周中,区块的平均生成速度是 9 分钟,就意味着比法定速度快了 10%,因此接下来的难度系数就要调高 10%;如果平均生成速度是 11 分钟,就意味着比法定速度慢了 10%,因此接下来的难度系数就要调低 10%。难度系数越调越高,目标值越来越小,导致挖矿越来越难。

difficulty=difficultyexpected  timeactual  timedifficulty = difficulty* \frac{expected\ \ time}{actual\ \ time}

actual time 指产生 2016 个区块实际花费的时间,expected time 指产生 2016 个区块应用的时间,即 2016×10min

实际上,上调和下调都有四倍的限制。假如实际时间超过了 8 个星期,那么我们计算公式时也只能按 4 倍算,目标预值增大最多只能增大 4 倍。

如何让所有的矿工同时调整目标阈值 target#

计算 target 的方法写在比特币系统的代码里,每挖到 2016 个区块会自动进行调整。

如果一个节点不调,将区块发布出去,大部分诚实的节点是不会认的

nBits 是 target 一个编码的版本,在 block header 里没有直接存储 target 的域,因为 target 的域是 256 位,直接存 target 的话要 32 个字节,nBits 在 header 里只有四个字节,所以可以认为是它的一个压缩编码。如果遇到有恶意的矿工,该调的时候不调,这时检查区块的合法性就通不过,因为每个节点要独立的验证发布的区块的合法性。检查的内容就包括,目标阈值设的对不对。

image-20230303230815659

Bitcoin Difficulty Chart

上面是难度调整曲线,可以看出很明显是一段一段的。每隔两个星期,难度上一个台阶,说明挖矿的人越来越多,用的设备越来越先进,反应出大家对比特币的热情越来越高。

如果出现相反的情况,比如某个加密货币的挖矿难度越调越小,说明挖矿变得越来越容易了。但这不是好事,说明大家对币的热情是逐渐减小的,持续出现这种情况说明这个币将被淘汰。

比特币节点#

比特币系统中有两种节点,一种是全节点,一种是轻节点。

全节点#

  • 一直在线
  • 在本地硬盘上维护完整的区块链信息
  • 在内存中维护 UTXO 集合,以便快速检验交易的正确性
  • 监听比特币网络上的交易信息,验证每个交易的合法性
  • 监听别的矿工挖出的区块,验证其合法性。

挖矿:

  • 决定沿着哪条链挖下去
  • 决定哪些交易被打包进区块
  • 决定当出现等长分叉时选择哪个分叉(缺省情况是选择最先接收到的区块的分叉)

轻节点#

  • 不是一直在线
  • 不用保存完整区块链,只要保存每个区块块头(这样和全节点的大小相差大约 1000 倍)
  • 不用保存全部交易,只需要保存和自己相关的交易
  • 没法验证大多数交易的合法性,只能检验与自己相关的交易的合法性
  • 无法检测比特币网络上发布的区块的正确性
  • 可以验证挖矿的难度(因为挖矿时候计算哈希值只用到了块头信息,而块头信息轻节点是保存了的)
  • 只能检测哪个是最长链,不知道哪个是最长合法链(因为无法检测这条链上所包含的交易都是合法的)

轻节点假设矿工(全节点)大多是有理智的,即假设矿工们不会沿着不合法的链一直挖下去。工作量证明要做很多的工作才能把一个区块挖出来。如果你挖出这个区块里被发现包含非法交易的话,那就白挖了。所以轻节点假设大多数全节点矿工不会干这种事。

比特币网络中大部分节点都是轻节点,如果只是想转账,而不是去挖矿的话,只用轻节点就可以了

挖矿的设备#

第一代挖矿设备:CPU#

最早时候大家都是用普通计算机来挖矿,但如果专门搞一台计算机来挖矿是很不划算的。因为计算机大部分内存是闲置的(挖矿只要用到很少一部分内存),CPU 大部分部件是闲置的(计算哈希值的操作只用到通用 CPU 中的很少一部分指令),硬盘和其它很多资源也都是闲置的。随着挖矿难度提高,用通用计算机上的 CPU 挖矿很快就无利可图了。

第二代挖矿设备:GPU#

GPU 主要用来做通用的大规模并行计算,用来挖矿还是会有不少浪费,如用于浮点数计算的部件。

第三代挖矿设备:ASIC 芯片#

ASIC 即 Application Specific Integrated Circuit(专用集成电路),这之中有专门为了挖矿而设计的芯片,没有多余的电路,干不了别的事,它的性价比是最高的,而且为某一种加密货币设计的 ASIC 芯片只能挖这一种加密货币的矿,除非两个货币用同一个 mining puzzle

有些加密货币在刚启动的时候,为了吸引更多的人来挖矿,特意用一个和已有的其它加密货币一样的 mining puzzle,这种情况叫merge mining

研制挖特定加密货币的 ASIC 芯片需要一定周期,但和研制通用芯片的速度相比已经是非常快的了,如研制比特币挖矿的 ASIC 芯片大约用一年的时间。不过加密货币的价格变化是比较剧烈的,曾经就发生过比特币价格在几个月内下跌 80%,因为加密货币多变的价格,这些挖矿设备的研制风险也是很大的。

挖矿的竞争越来越激烈,定制的 ASIC 芯片可能用了几个月就过时了,到时候又要买新的 ASIC 芯片参与竞争。ASIC 矿机上市后的大部分利润也就在前几个月,这个设备的迭代也是很块的。

要买 ASIC 矿机往往要先交钱预定,过一段时间厂商才会发过来。实际上有些黑心厂商在生产出来以后也不交付给用户,声称还没成产好,然后自己在这段黄金时间用矿机挖矿赚取比特币。不过这其实看得出来,比特币系统中算力突然有了大的提高,那一般是某个大的厂商生产出了新的矿机。所以真正赚钱的未必是挖矿的,而是卖矿机的。

ASIC resistance#

为了让通用计算机也能参与挖矿过程,抗 ASIC 芯片化,有些加密货币采用Alternative mining puzzle,以去对抗那些只为了解决特定 mining puzzle 而设计出来的 ASIC 矿机。

比特币挖矿的趋势:大型矿池#

单个矿工挖矿的收益是很不稳定的,平均出块时间 10 分钟是对于比特币系统中的所有矿工而言的。一个矿工用一个矿机挖出矿的时间可能要很久,并且除了挖矿之外还要承担全结点的其它责任。

矿池将很多矿工组织起来,** 一般的架构就是一个矿主(pool manager)** 全结点去驱动很多矿机,下属矿工只负责计算哈希值,全结点的其它职能只由矿主来承担。有了收益以后再大家一起分配。
img

矿池收益怎么分配 - 工作量证明#

如果矿池中的矿机都是属于同一个机构的,那怎么分配就只是公司内部怎么发工资的问题了。

如果矿机来自不同机构,这时候矿工很可能分布在世界各地,只是都加入了这个矿池。矿工和矿主联系,矿主将要计算的哈希值的任务分配给他,矿工计算好后将结果发给矿主,最终得到出块奖励后一起参与分红。

能否平均分配?即挖到区块后奖励平分给所有矿工。这样就完全是吃大锅饭的模式了,有的矿工完全可以不干活或者少干活,所以需要按矿工的贡献大小进行分配,所以这里也需要工作量证明,来证明每个矿工所做的工作。

每个矿工单打独斗之所以收入不稳定,是因为挖矿难度太大了(相比比特币系统的平均出块时间),所以可以考虑矿池将挖矿的难度降下来。

比如本来要求前面有 70 个 0,现在矿池只要求前面有 60 个 0,这样挖到的是一个share(almost valid block),即这个区块差不多在一定程度上是符合难度要求的。矿工挖到这样的区块之后,将其提交给矿主,矿主拿到这些区块并没有什么用,仅仅是因为目标空间是这个问题的解空间的子集,并且求解两个问题的过程是一样的(都是计算哈希),因此这些区块可以作为证明矿工所做的工作量的证明。等到某个矿工真正挖到矿,获取出块奖励之后,再按照大家提交的 share 的多少来进行分配。

矿工能否在参与矿池时独吞出块奖励#

是否会有这样的矿工:挖到 share 提交给矿主,挖到真正的矿自己发布出去以获取出块奖励?

这是没法独吞出块奖励的,因为每个矿工的任务是由矿主来分配的,矿主负责组装好区块,然后交给矿工去不断尝试 nonce 和 CoinBase transaction 中的 extra nonce,有可能就是讲它们划分一下,然后分配给不同的矿工去做,要注意铸币交易 CoinBase transaction 中的收款人地址是矿主的地址。所以矿工挖到区块之后,如果他不提交给矿主自己发不出去是没有用的。里面的收款地址是矿主的,他取不出钱来,所以只要是当初按矿主给分配的任务进行挖矿的,就不可能偷区块奖励。

如果他一开始就不管矿主的任务,自己组装一个区块,偷偷把收款地址改成自己地址,会怎样?

那样他提交 share 给矿主的话,矿主是不认的,因为里面交易列表被改过了,coinbase transaction 里面的内容发生了变化,算出的 merkle tree 的根哈希值也是不一样的。这种情况下矿主是不会给他工作量证明的,那就相当于矿工一开始就单干,跟矿池是没关系的。

矿池之间的竞争#

矿池之间是有竞争对手的,一种竞争方式就是到对方的矿池里去捣乱,即派遣一些矿工去加入到对方的矿池里去挖矿,只提交 share,但挖到真正的矿就将其丢弃掉,故意不提交。然而如果这个对手矿池仍然获得了出块奖励,这些矿工也能参与分红。

大型矿池带来的危害#

如果没有矿池,如果要发动 51% 攻击,攻击者要花费大量的硬件成本。有了矿池以后,矿池实际上将算力集中了起来,攻击者未必拥有很多算力,只要吸引大量的不明真相的群众将算力集中到自己的矿池就可以。

在 2014 年的时候 GHash 矿池的总算力就超过了比特币系统中总算力的一半,引起了恐慌,然后 GHash 主动减少了算力,以防止大家对比特币失去信心。

如今的矿池的算力还算比较分散,有好几家矿池在竞争,但可能只是一个表面现象

假如一个机构有一半以上的算力,他不一定要把算力集中在一个矿池里,而可以把算力分散隐藏在很多矿池里,真正需要发动攻击的时候再集中起来发动攻击,因为矿工要转换矿池是很容易的

这就是矿池带来的危害,如果没有矿池,想要发动 51% 的攻击,攻击者要投入大量的成本来购买到足够的矿机,能够达到系统中半数以上的算力。有了矿池之后,他可能只占很小一部分比例的算力,只要能够吸引到足够多的矿工,足够多的不明真相的群众加入到他的矿池里来就行了

一般来说,矿池的矿主要收取一定比例的出块奖励作为管理费。矿主也要按照比例收取管理费,有的是按照出块奖励的比例,也有的是抽取交易费。有的一些有恶意的矿池在发动攻击之前,可能故意把管理费降得特别低,甚至是赔本赚吆喝,吸引足够多的矿工加入之后就可以发动攻击了。这是大型矿池的一个弊端,使得 51% 的攻击更加容易了

假设出现超大型矿池,具体能发动哪些攻击#

分叉攻击 - 51% 算力攻击#

假如一个区块链,其中一个区块包含了一个大笔的交易,又等了几个确认区块之后,自认为已经安全了。然后这时就可能有人在该交易前面的区块发动分叉攻击。看上去好像追赶的道路是很漫长的,但如果拥有 51% 的算力,最终还是可以成功攻击。因为算力占了半数以上,并且矿工挖矿任务被分配开并行进行,分叉出来的链的增长速度很快,最终势必成为最长合法链。

另外,不要把 51% 当成绝对的门槛,有可能不到 51% 就可以。算力都是估计的,而且算力还在不断变化,都是概率问题。

Boycott#

比如说攻击者不喜欢某个账户,怀疑某个账户参与非法交易,想把这个账户封锁掉,所有跟这个账户相关的交易都不让上链。

假如 A 把某个交易 A→B 发布到区块链上,攻击者就会马上进行分叉,产生一个不包含这个交易的区块,所有跟 A 有关的交易也都不包含进去。

这种攻击跟分叉攻击区别是什么?

他没必要等后面几个确认区块。这时候如果攻击者等待确认区块,是为了让 B 放心,B 以为后面有六个确认区块,已经没事了,然后攻击者再发动分叉攻击。

而如果目的是为了 boycott 的话,就没有必要等后面区块生成。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)#

  1. P2PKH(比特币网络默认类型)
  2. P2PK (多出现在矿工接收奖励和手续费的 coinbase 交易中)
  3. Multi-signature (多签)
  4. P2SH(将另一笔交易的 script 哈希后作为新 script,压缩字节)
  5. P2WPKH、P2WSH:隔离见证采用后 P2PKH 的另一种实现方式,字节占用更少,且签名从 unlocking script 中移出
  6. nulldata (OP_RETURN): 隔离见证采用后用于存储见证信息的默克尔树根( Merkle root of the witness tree),在 coinbase 交易的接收方出现,该笔 output 将直接从 UTXO 中移除,后续不能被花费,转移的比特币数量一般都为零。vout 中没有 reqSigs 和 addresses 这两对键值。

P2PK(Pay to Public Key)#

输出脚本直接给出收款人的公钥。

这种形式是最简单的。

input script:
    OP_PUSHDATA(Sig)
output script:
    OP_PUSHDATA(PubKey)
    OP_CHECKSIG

输出脚本中直接给出收款人的公钥。输入脚本中直接给出签名就行。CHECKSIG 是检查签名的操作。

脚本的执行 (这里拼接在了一起,实际要分开):

  1. PUSHDATA (Sig) —— 把输入脚本的签名压入栈
  2. PUSHDATA (PubKey) —— 把输出提供的公钥压入栈
  3. CHECKSIG —— 将 栈内两个元素弹出,用公钥检验签名是否正确。若正确返回 True

P2PKH(Pay to Public Key Hash)#

reference: Transactions — Bitcoin

Pay to Public Key Hash 是最常用的脚本形式。常常以 “1” 开头的地址就是 P2PKH 地址。

**【示例】**Alice 向 BOb 转账(Pay to Public Key Hash)

  1. Bob 创建公钥私钥对,向 Alice 提供自己的公钥哈希
  2. Alice 创建一个输出脚本 (锁定脚本) scriptPubKey,包含 Bob 的公钥哈希

image-20230310020246167

  1. Alice 广播交易到区块链网络中

区块链网络将其归类为未花费的交易输出(UTXO),Bob 的钱包软件将其显示为可花费余额。

  1. 一段时间后,Bob 决定使用 UTXO 时,他必须创建一个输入脚本 (解锁脚本) signature script

Signature scripts are also called scriptSigs.

输入脚本中需要包含:

  • Alice 创建的那个交易txid(Transaction Identifier)
  • 交易的第几个输出 (output index)
  • Bob 的原始公钥(未哈希)
  • Bob 通过私钥对交易数据的签名
image-20230310021622298 image-20230310021854025

As illustrated in the figure above (如上图所示), the data Bob signs includes the txid and output index of the previous transaction, the previous output’s pubkey script, the pubkey script Bob creates which will let the next recipient spend this transaction’s output, and the amount of satoshis (聪) to spend to the next recipient.

In essence, the entire transaction is signed except for any signature scripts, which hold the full public keys and signatures. 从本质上讲,除了任何签名脚本之外,整个事务都已签名,这些脚本包含完整的公钥和签名。

  1. Bob 将交易广播到比特币网络,矿工对其进行验证。

The validation procedure requires evaluation of the signature script and pubkey script.

input script(signature script):
    OP_PUSHDATA(Sig)  		//压入对此交易的签名
    OP_PUSHDATA(PublicKey)  //压入自己的公钥
output script(pubkey script):
    OP_DUP  				//复制栈顶元素,再压入栈
    OP_HASH160  			//弹出栈顶元素,取哈希在压入栈(栈顶变成公钥哈希)
    OP_PUSHDATA(PubKeyHash)  //压入花费的输出脚本提供的公钥哈希(这时栈里有两个公钥哈希)
    OP_EQUALVERIFY   		//弹出栈顶元素,比较是否相等(两个公钥哈希)
    OP_CHECKSIG   			//检查签名是否正确,若正确栈顶留下True
image-20230310023343281

Bob 转账的输出脚本里没有直接给出收款人的公钥,而是给出了收款人公钥的哈希值,就像 Alice 给 Bob 转账时一样。

如果执行过程任何一个环节发生错误,比如输入里给出的公钥跟输出里给出的哈希值对不上,或者是输入里给出的签名跟给出的公钥对不上,那么这个交易就是非法的。

为何要对 PublicKey 做 HASH 呢?这可以避免一种偏激情景。例如,形成公私钥对的优化算法形成了漏洞,那样很有可能取得 PublicKey 便可破解出 PrivateKey。因而充分考虑偏激状况下,对 PublicKey 做一次 HASH 计算获得 PublicKey 的密文,这就在相应层度上加了一道双重保险。

P2SH(Pay to Script Hash)#

这种是最复杂的一种。Pay to Script Hash 在比特币最初版本是没有的,后期软分叉加入,最重要的一点是对多重签名的支持。

这种形式的输出脚本给出的是收款人提供的 ** 赎回脚本(redeem Script)** 的哈希,到时候收款人要花费这笔交易的时候需要输入脚本的内容和签名

收款人如何花费 Pay to Script Hash ?

(收款人要花费这笔交易时) 验证的时候分两步:

  1. 验证输入脚本的哈希是否与输出脚本中的哈希值匹配
  2. 反序列化并执行 redeem Script,验证 input script 给出的签名是否正确

下面是收款人要花费这笔交易时的输入输出脚本。

image-20230309230441188
  • 输入脚本

这里的输入脚本就是给出签名,再给出序列化的赎回脚本

  • 赎回脚本

赎回脚本的内容就是给出公钥,然后用 checksig 检查签名。

  • 输出脚本

输出脚本是用来验证输入脚本里给出的赎回脚本是否正确

  1. 执行过程第一阶段 - 检查赎回脚本是否篡改

开始也是把输入脚本和输出脚本拼接在一起,前两行来自输入脚本,后面三行来自输出脚本。

首先把输入脚本的签名压入栈,然后把赎回脚本压入栈,然后是取哈希的操作,得到赎回脚本的哈希。这里 RSH 是指 redeem script hash,赎回脚本的哈希值。接下来还要把输出脚本里给出的哈希值压入栈,这时栈里就有两个哈希值了。最后用 equal 比较这两个哈希值是否相等(保证赎回脚本没有被篡改),如果不等就失败了。假设相等,那这两个哈希值就从栈顶消失了,到这里第一阶段的验证就算结束了,接下来还要进行第二个阶段的验证。

image-20230309231140550
  1. 执行过程第二阶段 - 验证签名

第二个阶段首先要把输入脚本提供的序列化的赎回脚本进行反序列化,这个反序列化的操作在 PPT 上并没有展现出来,这是每个节点自己要完成的。然后执行赎回脚本,首先把 public key 压入栈,然后用 checksig 验证输入脚本里给出的签名的正确性。验证痛过之后,整个 pay to script hash 才算执行完成。

image-20230309231554348

为什么需要 Pay to Script Hash ?

区块恋 - 截断私钥实现 “共享账户”#

就是指,把一个私钥分成几份,有几个人各自保管,只有最终大家都拿出自己的部分私钥,才能合成完整的私钥。

情人节,据说很多情侣会买比特币,然后把比特币私钥分成二份,一人保存一半私钥。假如哪天二人分开了,这些比特币也就永远遗留在区块链上,用 Blockchain 的不可篡改性来作为两个人的爱情见证。后来我们把这种行为叫做区块恋。

这样做存在的问题是:

  1. 这些人中任何一个人把私钥丢了钱就取不出来了
  2. 还有更大一个问题:这种截断私钥的做法会降低账户的安全性

因为比特币系统中每个账户的安全性跟所用的私钥的长度是相关的。为什么要用 256 位的私钥?因为这个长度的私钥用暴力破解的方法是不可行的。就算把全世界的计算机集中起来破解 256 位的私钥,也是不可能成功的。但是如果从中截断,一对情侣中一个人分手之后想把钱取出来,他已经知道了其中一半的私钥,只要把剩下的 128 位私钥猜出来就行了

私钥长度减少一半并不意味着难度降低一半,难度由 2 的 256 次方降到了 2 的 128 次方,前者远远大于后者,破解难度降了很多

如果是四个合伙人的例子,有三个人瞒着另一个人要把钱取出来,那么他们只需要尝试 2 的 64 次方就可以了

因此对于多个人的共享账户,不要用截断私钥的方法,而最好采用多重签名,多重签名中用到的每一个私钥都是独立产生的。而且多重签名也提供一些别的灵活性,比如可以要求 N 个人当中任意给出 M 个签名就可以了。

在区块恋例子中,如果一对情侣分手了,那么他们的比特币将永久的保存在 UTXO 里面,这对矿工是不友好的。矿工是不知道这笔钱永远取不出来的,所以矿工要把这笔钱永久的保存在 UTXO 里面,造成这个集合的膨胀。

比特币原始的多重签名#

比特币系统中一个输出可能要求多个签名才能把钱取出来,比如某个公司的账户,可能要求五个合伙人中任意三个人签名才能把公司账户上的钱取走,这样为私钥的泄露提供了一些安全的保护。

比如说有某个合伙人私钥泄露出去了,那么问题也不大,因为还需要两个人的签名才能把钱取走。这同时也为私钥的丢失提供了一些冗余,即使有两个人把私钥忘掉了,省下的三个人依然可以把钱取出来,然后转到某一个安全的账户。

输出脚本里给出 N 个公钥,同时指定一个阈值 M。输入脚本只要提供接 N 个公钥对应的签名中任意 M 个合法的签名就能通过验证

image-20230309232134712

比特币中 check multiSig 的实现,有一个 bug,执行的时候会从堆栈上多弹出一个元素,这个就是它的代码实现的一个 bug。这个 bug 现在已经没有办法改了,因为这是个去中心化的系统,要想通过软件升级的方法去修复这个 bug 代价是很大的,要改的话需要硬分叉。所以实际采用的解决方案,是在输入脚本里,往栈上多压进去一个没用的元素,第一行的 “×” 就是没用的多余的元素。

另外需要注意给出的 M 个签名的相对顺序,要跟它们在 N 个公钥中的相对顺序是一致的才行。

如图是 check multiSig 的执行过程。这个例子假设三个签名中给出两个就行。图中可以看到这两个签名给出的相对顺序也是跟它们在公钥中的顺序是一样的。在公钥当中,第一个公钥排在第二个公钥前面。那么给出这两个签名的时候也是第一个签名排在第二个的前面。

image-20230309232426389

第一行的 false 就是前面说的多余的元素。首先把多余的元素压入栈里,然后把两个签名依次压入栈,这个时候输入脚本就执行完了。接下来的输出脚本里把 M 的值,即阈值 M 压入栈。然后把三个公钥压入栈,接着把 N 的值压入栈,最后执行 check multiSig,看看堆栈里是不是包含了这三个签名中的两个(N=3,M=2),如果是那么验证通过。

原始的多重签名有什么问题?

比如购物网站在网上公布出来我们用了多重签名,我们用的五个签名中要给出三个,这是五个公钥,然后用户生成这个转账交易的时候,就把这些信息填进去。

那么不同的电商采用的多重签名的规则是不一样的。有的电商可能是五个签名中要任意三个,有的可能要四个。这就给用户生成转账交易带来了一些不方便的地方,因为这些复杂性都暴露给用户了

pay to script hash 简化多重签名#

pay to script hash 它的常见的应用场景是对多重签名的支持

如图是用 pay to script hash 实现的多重签名,它的本质是把复杂度从输出脚本转移到了输入脚本

现在这个输出脚本变得非常简单,只有这三行。原来的复杂度被转移到 redeem script 赎回脚本里,用户生成的输出脚本只要给出这个赎回脚本的哈希值就可以了

赎回脚本里要给出这 N 个公钥,还有 N 和 M 的值,这个赎回脚本 (serialized redeem script) 是在商家花费时的输入脚本里提供的,也就是说是由收款人提供的。

image-20230309233219389

像前面网上购物的例子,收款人是电商,他只要在网站上公布赎回脚本的哈希值,然后用户生成转账交易的时候把这个哈希值包含在输出脚本里就行了。至于这个电商用什么样的多重签名规则,对用户来说是不可见的,用户没必要知道。从用户的角度来看采用这种支付方式跟采用 pay to public key hash 没有多大区别,只不过把公钥的哈希值换成了赎回脚本的哈希值。

这个输入脚本是电商在花掉这笔输出的时候提供的,其中包含赎回脚本的序列化版本,同时还包含让这个赎回脚本验证通过所需的 M 个签名

将来如果这个电商改变了所采用的多重签名规则,比如由五个里选三个变成三个里选两个,那么只要改变输入脚本和赎回脚本的内容,然后把新的哈希值公布出去就行了。对用户来说,只不过是付款的时候,要包含的哈希值发生了变化,其他的变化没有必要知道。

下面是脚本执行过程:

image-20230309234107182 image-20230309234222925

image-20230309234253825

Proof of Burn#

image-20230310211402212

包含 OP_RETURN的这种 output 脚本形式被称为:Provably Unspendable / Prunable Outputs

image-20230310211750342

这种脚本格式是比较特殊的,这种格式的输出脚本开头是 return 的操作,后面可以跟任意的内容。return 操作的作用,是无条件的返回错误,所以包含这个操作的脚本永远不可能通过验证,执行到 return 语句,就会出错,然后执行就终止了,后面跟的内容根本没有机会执行。

Q: 当一个全节点收到一个转账交易的时候,它首先要检查一下,这个交易的合法性,只有合法的交易才会被写入区块链里。而 OP_RETURN 这个语句是无条件的返回错误,既然如此,它怎么可能通过验证,怎么可能被写到区块链里呢?

A: 验证当前交易合法性的时候,不会执行这个语句。即当前交易的输出脚本在验证交易合法性的时候,是不会被执行的。只有有人想花这笔钱,后面再有一个交易,要花这个交易的输出的时候才会执行这个交易的输出脚本。

为什么要设计这样的输出脚本呢?这样的输出岂不是永远花不出去吗?

这是一种证明销毁比特币的一种方法。

为什么要销毁比特币#

  1. 获得其他 AltCoin

有些小的币种要求销毁一定数量的比特币才能够得到这个币种,有时候把这种小币种称为 AltCoin (Alternative coin)。除了比特币之外的其他小的加密货币都可以认为是 Alternative Coin。比如有的小币种要求销毁一个比特币可以得到 1000 个小币,也就是说要用上述的方法证明已经付出了一定的代价才能够得到这个小币种。

  1. 往区块链里写入一些内容

区块链是个不可篡改的账本,有人就利用这个特性往里面添加一些需要永久保存的内容,比如第一节课讲的 digital commitment。要证明在某个时间,知道某些事情。比如涉及知识产权保护的,把某项知识产权的内容取哈希之后,把哈希值放到 return 语句的后面,其后面的内容反正是永远不会执行的,往里面写什么都没关系。而且放在这里的是一个哈希值,不会占太大的地方,而且也没有泄露出来你知识产权的具体内容。将来如果出现了纠纷,像知识产权的一些专利诉讼,再把具体的哈希值的输入内容公布出去,证明你在某个时间点已经知道某个知识了。

这个应用场景和 coinbase 域相似。coinbase transaction 里面有个 coinbase 域,在这个域里写什么内容同样是没人管的。

比特币分叉#

区块链由一条链变为两条链就叫分叉

state fork#

分叉可能是多种原因造成的,比如挖矿的时候,两个节点差不多同一个时候挖到了矿,就会出现一个临时性的分叉,我们把这个分叉叫作state fork,是由于对比特币区块链当前的状态有意见分歧而导致的分叉。

前面还讲过分叉攻击 (forking attack),它也属于 state fork,也是属于对比特币这个区块链当前的状态产生的意见分歧,只不过这个意见分歧是故意造成的,人为造成的,所以我们又叫它deliberate fork

protocol fork#

除了这种 state fork 之外,还有一种产生分叉的情况是,比特币的协议发生了改变,要修改比特币系统需要软件升级。在一个去中心化的系统里,升级软件的时候没有办法保证所有的节点同时都升级软件。

假设大部分节点升级了软件,少数节点因为种种原因可能没有升级,有可能是还没来得及升级,也可能是不同意对这个协议的修改。即假如你想把协议改成某个样子社区中可能是有人不支持的,这个时候也会出现分叉,这种分叉叫protocol fork (协议分叉)。因为对比特币协议产生了分歧,用不同版本的协议造成的分叉,我们称作 protocol fork。

硬分叉 (hard fork)#

根据对协议修改的内容的不同,我们又可以进一步分成硬分叉和软分叉

出现硬分叉的情况:如果对比特币协议增加一些新的特性,扩展一些新的功能,这些时候那些没有升级软件的这些旧的节点,它是不认可这些新特性的,认为这些特性是非法的,这就属于对比特币协议内容产生了意见分歧,所以会导致分叉。

调整区块链不止是改变一个参数那么简单。一个去中心化的系统,改变一个参数,就可能导致分叉,而且取决于这个参数是怎么改的,有可能是硬分叉,有可能是软分叉

硬分叉示例 - 区块扩容#

硬分叉的一个例子就是比特币中的区块大小限制 (block size limit)。比特币系统规定每个区块最多是 1M 字节,有些人认为 1M 的限制太小了,也增加了交易的延迟。可以计算一下,1M=1 百万 ,一个交易大概认为是 250 个字节。

1百万250=4000\frac{1百万}{250}=4000

一个区块大概是 4000 个交易,平均 10 分钟出现一个区块。

40006010=7 \frac{4000}{60*10}=7

大概每秒钟产生 7 笔交易,即 7tx/sec ,这个传输速度是非常低的。

假设有人发布一个软件更新,把 block size limit 从 1M 增加到 4M,假设大多数节点更新这个软件,把 block size limit 更新到 4M,少数节点没有更新。这里的大多数节点和少数节点不是按照账户数目来算的,而是按照算力,即系统中拥有大多数哈希算力的节点都更新了软件。新节点认为区块大小限制是 4M,旧节点认为是 1M。

假如一个新节点挖出一个区块,这个区块比较大,但旧节点不认可,它忽略大区块的存在会继续沿着它的前一个小区块接着挖。而旧节点如果挖出了区块新节点是认可的,因为 4M 的限制指不能超过 4M,比 4M 小是可以的。

那为什么会产生分叉呢?大区块挖出之后,因为大多数区块是更新了的,是认可新的大区块的,所以会沿着它继续挖。只有少数旧节点会接着下面链往下挖,这时新节点认为上下两条链都是合法的,但上面那条是最长合法链,所以会沿着上面一条挖

而且算力足够大会使上面那条链越来越长,而旧节点认为上面的链无论多长都是非法的,它们只会沿着下面的链挖。

当然上面的链也可能出现小区块,因为新节点也可能挖出大小不到 1M 的区块,虽然这种是新旧节点都认可的,但这是没有用的,因为这条链上它们认为有非法的区块。

所以这种分叉是永久性的,只要旧节点不更新软件,分叉就不会消失,所以才叫它硬分叉

比特币社区当中有些人是比较保守的,提高 block size limit 有些人就是不同意。而且区块的大小也不是越大越好,比特币底层系统是个 P2P overlay network,它的传播主要采用 flooding 的方式,所以对带宽的消耗是很大的,带宽是瓶颈。

那么旧节点挖出的小的区块还有没有出块奖励呢?出现 hard fork 后出现了两条平行运行的链,平行运行链彼此之间有各自的加密货币。下面链的出块奖励在下面链里是认的。而分叉之前的币按道理应该是上下两条链都认可,所以会拆成两部分。

chain ID#

曾经以太坊分叉出现过这样的问题:分叉前有 A→B 的交易,分叉后在上面链出现了 B→C,下面链也出现了 B→C,因为账户,私钥都是一样的,所以在两条分叉的链上 C 都收到了 B 的转账,但 B 只想在上面的那一条链转账。既然如此,就会有人利用这个特性,想收到上下两条链的转账。

为了解决这个问题,就让这两条链各带一个 chain ID,所以现在以太坊的分叉已经没有问题了,就是两条独立运行的链了。

软分叉 (soft fork)#

如果对比特币协议加一些限制,加入限制之后原来合法的交易或区块在新的协议当中有可能变的不是合法了,这就引起软分叉。

假设有人发布一个软件更新,把这个区块大小变小了。这里把区块大小变小只是为了解释软分叉这个概念,实际中是不会这么做的。

假设新节点把区块大小改为 0.5M,旧节点依然以 1M 为准,大部分节点都是新节点,假设新节点的算力远远超过老节点,这时候会出现什么情况?

假如一个区块链开始分叉,新节点挖出小区块,这种区块旧节点也是认的。而旧节点挖出的大区块新节点是不认的。这样下去,旧节点看到上面链更长,而且是合法的之后,就会转去挖上面链。

旧节点转向上面链挖的话,问题可能又会出现:它们可能又挖出了大区块。而新节点不认这个,新节点会继续沿着大区块前面一个小区块挖,旧节点就一直在做无用功

所以为什么称这种分叉是软分叉?

因为这种软分叉是临时性的,旧节点如果不更新软件,它们挖的区块可能就白挖了。

软分叉示例#

某个轻节点想要证明某个账户上有多少钱,这个目前在比特币系统中是证不出来的。如果是全节点还可以算一下,方法如下:想要知道 A 账户有多少钱,就看一下 A 在 UTXO 里对应的输出总共收到多少个币,就是该账户上有多少钱。

对于全节点是可以算出来的,但如果是区块链钱包、有的手机上的 APP,它不可能在手机上维护一个完整的区块链,它实际上是个轻节点。

轻节点想要知道账户的余额需要询问全节点,全节点返回一个结果,怎么知道这个结果是否属实呢

轻节点是判断不出来的,如果你自己不维护一个 UTXO 集合,就没法用 merkle proof 证出来。

有人提议把 UTXO 集合当中的内容也组织成一颗 merkle tree,这个 merkle tree 有一个根哈希值,根哈希值写在 coinbase 域里面。因为 block header 没法再改了,改 block header 动静就太大了,coinbase 域正好是没人用的,所以就写入 UTXO 的根哈希值。coinbase 域当中的内容最终往上传递的时候会传递到 block header 里的根哈希值里。所以改 coinbase 域的内容,根哈希值会跟着改。因此这个提案就是说把 UTXO 集合的内容组织成 merkle tree,算出一个根哈希值来,写入 coinbase 域里某个位置。coinbase 域的内容本身也会算哈希,算到 block header 里的根哈希值,这样就可以用 merkle proof 证出来了。

假设有人发布一个软件更新,规定 coinbase 域要按照这个要求来填写,假设大多数节点都升级了软件,少数节点没有更新。这属于软分叉,因为新节点发布的区块旧节点认为是合法的,因为旧节点不管新节点写什么内容。但旧节点发布的区块新节点可能是不认的,因为如果 coinbase 域不按要求写它是不认的,所以属于软分叉。

著名的软分叉示例 pay to script hash#

比特币历史上比较著名的软分叉的例子是 pay to script hash。P2SH 这个功能在最初的比特币版本里是没有的,它是后来通过软分叉的功能给加进去的。

这是什么意思呢?你支付的时候不是付给一个 public key 的哈希,而是付给一个赎回脚本的哈希。花钱的时候要把这个交易的输入脚本跟前面币的来源的交易的输出脚本拼接在一起执行。执行的时候验证分为两步,第一步是要验证输入脚本中给出的 redeem script 跟前面那个输出脚本给出的 script 的哈希值是对的上的,证明输入脚本里提供的 script 是正确的。第二步再执行 redeem script,来验证输入脚本里给出的签名是合法的。

对于旧节点来说,它不知道 P2SH 的特性,只会做第一阶段的验证,即验证 redeem script 是否正确。新节点才会做第二阶段的验证,所以旧节点认为合法的交易新节点可能认为是非法的(如果第二阶段的验证通不过的话)。而新节点认为合法的交易旧节点肯定认为是合法的,因为旧节点只验证第一阶段。

软分叉与硬分叉的区别#

  • soft fork

假设大部分算力为新节点,旧节点认可新节点的区块,而新节点不认可旧节点的区块,就会出现软分叉。

只要系统中拥有半数以上算力的节点更新了软件,那么系统就不会出现永久性的分叉,只可能有一些临时性的分叉。

  • hard fork

旧节点不认可新节点的区块就会出现硬分叉。

硬分叉必须是所有的节点都要更新软件,系统才不会出现永久性的分叉,如果有小部分节点不愿意更新,那么系统就会分成两条链。

比特币的匿名性 (bitcoin and anonymity)#

一般来说,匿名是跟隐私保护联系在一起的。比特币中不要求用真名,所以比特币具有一定的匿名性。

你可以产生任意多的地址,然后用不同的地址干不同的事情。比特币相当于用的是化名,但它不是完全没有名字,所以有人把它称为pseudonymityanonym (假名)。

比特币的匿名性没有现金好,现金是完全匿名的,上面没有任何人的信息,假名都没有。

银行账户是实名制,你得提交身份信息,然后才能注册银行账户,而比特币不需要,从这点上看比特币匿名性要好。

以前银行是可以用化名的,如果银行账户匿名的话隐私性和匿名性与比特币相比哪个好?

从某种意义上来说银行账户的匿名性更好比特币区块链的账本是公开的,所有人都能查到,每个人都可以上区块链把整个信息下载下来。而银行的账本是受控制的,银行的工作人员可以查到,一些司法手段也可以调取银行的信息,但普通百姓是查不到别人的账的。

为什么保护隐私性难度挺大?本质原因是区块链是公开的,而且是不可篡改的。不可篡改性对于隐私保护来说是灾难性的。

破坏比特币匿名性 - 不同账户之间可能产生关联#

一个人可以生成很多个地址账户但这些地址账户是有可能被关联起来的

比如第一个地址账户上有 4 个比特币,第二个有 5 个。产生的两个输出第一个输出转入 6 个比特币,第二个输出转入 3 个比特币。那很明显转入 3 个比特币的输出是找零的,因为如果它是商家的地址,就用不着两个 inputs,任意一个输入都比 3 大。通过这种方法我们可以把输入地址和输出地址也关联起来。

如果想要更好的隐私保护,可以产生一些没必要的输出,为了迷惑别人。但是这些交易几乎都是用钱包软件生成的,现在很少有人手工生成这些比特币的转账交易,常用的比特币钱包就那么几种。所以把常用的比特币钱包生成交易的方式搞清楚,那么区块链上很大一部分转账交易都可以分析出来。常用的钱包到目前为止一般没有故意生成一些不必要的输出地址。

破坏比特币匿名性 - 账户与真实身份产生关联#

比特币地址账户跟现实世界中的身份也可能产生关联。

什么时候会有关联?什么情况下别人有可能知道比特币账户对应的现实生活中的哪个人呢?

比特币系统一旦跟现实世界联系起来,就可能泄露你的真实身份,最明显的例子就是资金的转入和转出 (买币卖币)。

怎么避免用比特币洗钱呢?盯住比特币的转入转出链是一个常用的手段。大笔的比特币和货币的交易想不引起司法部门的注意是很难的,转入转出也是比特币隐私容易被破坏的一个很重要的时机。

在实体世界中用比特币做支付也会泄露真实身份,比如国外有的商家是接受比特币支付。这样的话,你支付的账户就跟你的真实身份建立联系了。这个账户可能跟个人的其他账户也是有联系的,所以这样很容易泄露个人隐私和身份。而且该交易不仅是该商家会知道,其他人也会知道。

比如,A 想知道 B 的比特币地址。A 可以在 B 去买商品时,留意 B 支付的时间,然后去查找在这个时间点的交易。在 B 下一次购买商品时也留意支付时间,这样下去用不了几次就能知道哪个哈希值是 B 的。

用比特币支付可能是个 bad idea

  1. 延迟很长,等到交易确认要等六个区块生成,即一个小时
  2. 交易费很贵,如果买咖啡,交易费可能都跟咖啡差不多贵了

曾经有一个叫丝路 (silk road) 的网站 (eBay for illegal drugs),有像 eBay 一样的网上交易平台,但卖的都是非法的违禁品。为了逃避司法制裁,其支付手段就是比特币,底下的网络层用的是洋葱路由 (TOR),在美国也有匿名邮寄的服务。最后运行了两三年,就被查封了。美国政府抓到其老板时没收了其十几万个比特币,在当时价值几千万美元。但他生活简朴,因为虽然有价值连城的比特币,但一旦消费就会暴露身份

该网站被查封之后,有人又开了 silk road2。也是运行没几年就被查封。还有一些类似的网上黑店,最后下场都不好。这些事件都说明了,比特币的匿名性没有我们想象中的那么好,尤其是想用它来做坏事

匿名性是跟隐私保护相关联的,问题在于,你不想向谁暴露身份 (hide your identity from whom)?

如果你不想让身边的亲戚朋友知道,这是比较容易实现的。如果是非法组织,从事黑市活动,那保护起来就难多了。

如何提高匿名性#

一个比特币用户能采用什么样的方法尽量提高个人的匿名性?

以前曾讲过,比特币系统是运行于应用层 (application layer) 的,底层是 (network layer)。所以要提高匿名性可以从两个方面入手。

network layer - 洋葱路由#

在现实中,如果一个人去网吧发了帖子,别人是有办法知道他是谁的。因为他的身份证代表了他的身份,这和他的 IP 地址是有很大关联性的。

网络层的匿名性是比较好解决的。区块链是个新生事物,但网络层的匿名性学术界已经有了很好的方案:多路径转发

跟洋葱路由 (TOR) 是一样的原理。即消息不是由发出者直接发送给接收者,中间要经过很多次转发。中间的每一个节点,只知道它的上一个节点是谁,但并不知道最早发出消息的人是谁。当然中间一些节点可能是坏的,但路径上只要有一个节点是诚实的,就能够把最初发起人的身份隐藏起来。这也是洋葱路由的基本原理。

application layer - 混币器#

把不同人的币混在一起 (coin mixing),即把你的身份跟别人的身份混在一起,让别人分不清楚谁是谁。

有一些专门做 coin mixing 的网站,提供一定的服务收取一定的服务费。所有想做 coin mixing 的人把币发给网站,网站内部进行一些重组,然后你再把币取回来,这时取出的币就不是发布到网站上的币了,它是随机抽取一些币给你。

coin mixing 真正实施起来有一定的复杂性,如果设计不好的话,别人可以根据你当初存进去币的数额,推断出来哪些币是你存进去的。

在当今的区块链的世界里,没有什么信誉度非常高的 coin mixing 的服务。很多 coin mixing 的服务它本身也是要保持匿名的。它匿名的后果是,有可能投进去的币被他卷款跑路了,投币者是一点办法都没有的。

实际上并不一定非要做 coin mixing,有一些应用本身也带有 coin mixing 的性质,比如在线钱包。很多人会把钱存入在线钱包里,在线钱包就会把这些人的币混起来,再取回自己的币时可能就不是当初存进去的币了。但在线钱包并不保证要履行 coin mixing 的功能。

还可以通过加密货币的交易所,交易所一般有天然的 coin mixing 的性质。前提是交易所不会泄露提币、存币的记录,否则也是不行的。

零币和零钞 (专为匿名性设置的加密货币)#

零币和零钞在协议层就融合了匿名化处理,其匿名属性来自密码学保证.

** 零币 (zerocoin)** 系统中存在基础币 (basecoin) 和零币,通过基础币和零币的来回转换,消除旧地址和新地址的关联性,其原理类似于混币服务。

使用零币时需要证明你有对应的基础币,你把你的基础币 (比如比特币) 搞的不能花了 (Unspendable),然后换取了零币,在花的时候只需要用零知识证明,证明你花掉的币是系统中存在的某一个合法的币就行了。但是不用透露你花的具体是系统中的哪一个。

这个是跟比特币的一个本质区别。比特币是每一笔转账交易都要说明币的来源。这样才能证明花的币的真实性不是凭空捏造出来的。但零币和零钞不是这样,零币和零钞是说证明的时候可以从数学上保证你花的币是以前区块链上某个合法存在的币,但不知道具体是哪个。这样的话就把关联性破坏掉了,就没法追溯了。

零钞 (zerocash) 没有基础币,是完全的零币。零钞系统使用 zk-SNARKs 协议,不依赖一种基础币,区块链中只记录交易的存在性和矿工用来验证系统正常运行所需要关键属性的证明。区块链上既不显示交易地址也不显示交易金额,所有交易通过零知识验证的方式进行。

零钞和零币也不是 100% 匿名安全的,在影响匿名安全的因素中依然有一个因素无法解决,就是与实体发生交互的时候。比如有人想拿这些币干坏事,把很大的金额转换成这种加密货币的时候,或者是把这些加密货币转换成现金的时候,仍然要暴露身份。这些加密货币数学上设计的再好,只是说对已经在区块链当中的转账有匿名性,跟外界交互的匿名性仍然是一个弱点。所以它依然无法提供 100% 的匿名。

比特币的稀缺性#

比特币系统中的矿工为什么要挖矿?为了获得收益,挖矿的收益要大于挖矿的开销。那么才是有利可图的。所以要想吸引大家来挖矿,要么增加这个挖矿的预期收益,要么降低挖矿的开销。

任何一种新发行的这种加密货币都有一个能启动的问题。早期的时候,你这个加密货币不是很流行,怎么吸引大家来挖矿,给早期的矿工更多的收益?这个其实也是合理的,因为早期的矿工承担的风险也是更大的。

那么比特币是怎么做到这一点的?

  1. 早期难度设置的比较低。
  2. 早期的出块奖励比较高。

实际上,比特币这种总量恒定的性质是不适合用来做货币的。后面讲的以太坊就没有出块奖励定期减半的做法,一些新型的货币甚至要自带通胀的功能,每年要把货币的通行量提高一定的比例。因为稀缺的东西是不适合用来做货币的,通货膨胀会导致钱变得更不值钱了,但一个好的货币是要有通货膨胀的功能的

有人说古代黄金不是用来作为货币吗?

黄金在古代的时候确实有很长一段时间是用来作为货币的。但是现代社会基本上都废弃了金本位制,不再用黄金来作为货币。

严格的说呢,黄金的总量并不是定死的,每年都有一些新的金矿发掘出来。但是每年黄金产量增加的速度远远赶不上社会新创造财富的速度

每年能挖出来的黄金能有多少?每年创造出的财富有多少?所以如果我们用黄金作为货币的话,会有什么样的情况?

这样黄金会变得越来越值钱,因为社会财富越来越多了,都集中在这个少量的黄金上,会变得越来越值钱。

如果某个人早期拥有黄金,比如说他们家祖上传下来一锭金子,那么这个人就不用工作了,就可以坐在家里看着财富不断的增长,后来的人永远也赶不上。就如果你早期没有买这些黄金的话,你后面怎么赶也赶不上。

这个情况大家听起来有没有觉得有点耳熟?国内这些年的房地产就是这种情况。如果房价持续保持这种疯涨的态势的话,那么已经买房的人就会变得越来越富,没有买房的人就永远也买不起,个人奋斗变得没有意义了。一个健康向上的社会是不应该出现这种情况。

比特币最近几年的平均涨幅已经超过了北京的房价。大家如果对这方面的内容比较感兴趣的话,可以看一下货币金融学的相关知识。

比特币引发的思考#

前面已经讲过,从理论上实现分布式系统的共识是不可能的,但实际当中又怎么变的可能了呢?

为什么比特币系统能够绕过分布式共识中的那些不可能结论?严格来说,比特币并没有取得真正意义上的共识,因为取得的共识随时有可能被推翻,比如出现了分叉攻击。你以为已经取得了一个共识,分叉攻击后系统会回滚到前一个状态,从理论上说甚至有可能回滚到创世纪块。

按照分布式系统理论的要求,共识一旦达成之后,就不应该再改了,所以从这方面来说比特币并没有绕过分布式系统那些不可能的结论,因为它并没有达到真正意义上的共识。

这说明理论和实际往往是有区别的。很多理论上的不可能结论对于实际当中是并不适用的,因为这种不可能结论只是对某种特定的模型下是不可能的,实际当中把模型稍微改一改不可能结论就不成立了

几十年前一个讨论会上,服务器连不上了,有可能那个服务器本身是垮掉了,也有可能就是网络有一些拥堵,所以连不上。怎么区分这两种情况?我们当时还没有讨论几句,然后旁边有一个资深的分布式系统的专家就坐不住了。他说,这个问题不用讨论了,这是不可能区分出来的。分布式系统的理论已经证明了在异步的环境中,不可能区分某台远程的服务器到底是垮掉了,还是说仅仅是运行缓慢。

他这个话说的并没有错,分布式系统的理论体系里面确实是有这个结论。其实,你仔细想一想,是很显然的,所谓的异步环境是说通讯传输的延迟是没有上限的。我发一个消息给你,你什么时候能收到谁也不知道。

所以如果你看到一个远程服务器连不上,有可能是这个服务器本身死掉了,也可能就是这个通讯的延迟太长了,也许你等到下 1 秒就能够连上了。这就是为什么说这两种情况是没有办法区分开的。

这怎么办呢?是不是说实际当中遇到这种情况,就束手无策了?

这个时候呢,我们那边有一个搞实际操作的人,他说他遇到这种情况会给那个数据中心的值班人员打一个电话,让他去看一看这台服务器是个什么情况,如果确实是死机的话,帮着给重启一下。

所以呢?这个理论上的不可能,现实中就又变成了可能。大家想一下,这个例子说明什么?

理论上很多不可能的结论只是在某个特定的模型下。现实生活中呢?把这个模型改一改。像我们这个例子当中,你通过给对方的值班人员打电话,实地查看一下。那么这个模型就不是理论上的这种异步模型,所以理论上的不可能结论也就不成立。

这个故事到这里还没有完。我们实际当中是怎么操作的呢?因为如果每次你发现有问题都可以值班人员打电话也比较麻烦。所以我们的做法是给这个服务器加 1 根电话线。就那个时候,服务器一般是有 2 个插口,一个是可以插 Internet 的,另外一个是插电话线,用于拨号上网的。这个是十几年前的事情,就那个时候,用电话线拨号上网还是比较普遍的。现在没有人这么干了,但当时是一种常见的做法。我那个时候还发表过一篇论文,就是讲在拨号上网的情况下怎么进行流量优化。那么连线有什么好处?平时的时候你操作都是用这个 Ethernet 这根线是连的,如果你发现连不上,那么就是一下。这个拨号上网,这根线,这个电话线,因为这个电话网络跟这个 internet 是 2 个网络,一般来说不会同时出现拥堵。所以如果你发现电话线也连不上,那么一般是说明他真的是死机了。

这些事情能够给我们什么样的启示?

我们说,知识改变命运。这个话本身没有错,但是对知识的一知半解有可能使你的命运变得更差。

发明比特币的中本聪,他应该不是学术界出身,否则不太可能设计出像比特币这样的系统来。同学们都是非常优秀的,搞科研也是很有意义的。但是大家注意,不要被学术界的思维限制了头脑,不要被程序员的思维限制了想象力

量子计算机对加密货币的威胁#

随着量子计算的发展,量子计算机计算力变得越来越强大,加密货币会不会变得不安全了?

这种担心是没必要的

原因 1#

量子计算技术离实用还有很长一段距离,在比特币的有生之年不一定能产生实质性的联系。

如果量子计算在将来能强大到破坏加密体系的话,首先会冲击的是传统金融业。比如我们在网上进行的很多金融活动,网上银行、网上转账、网上支付,都会变得不安全了。所以与其担心量子计算对比特币的冲击,还不如担心量子计算对传统金融业的冲击,因为大多数的钱还是放在传统金融业里面的,加密货币的市值只占了现代金融体系当中的很小一部分。

原因 2#

比特币当中用的非对称加密体系,从私钥是可以推导出公钥的。所以只要把私钥保管好,公钥其实丢了也没有关系,从公钥显然是不能推出私钥的,否则就麻烦了。

假设将来量子计算技术发达了,能够从公钥中推出私钥,那怎么办呢?比特币在设计的时候又加了一层保护,没有用公钥本身,而是用公钥的哈希。比特币当中没有把账户的公钥直接暴露出来,而是用公钥取哈希之后得到一个地址

所以如果有人想偷你账户上的钱的话,首先是要用地址推导出你的公钥,相当于把公钥的哈希值进行逆运算,而这一点即使是用量子计算机也是没有办法完成的

加密和取哈希是两个不同性质的操作,加密的目的是为了将来能够解密,所以加密算法要保证信息的完整性,加密过程是不能丢失信息的,这样解密的时候才能够还原原来的输入。但是取哈希的过程一般是会造成信息的损失的,哈希函数一般都是不可逆的,因为有些信息在取哈希的过程中就已经丢失了。

所以在比特币系统中,如果要收款就没必要把公钥暴露出来,只暴露公钥的哈希生成的地址就行了。将来要取钱的时候才需要公钥和私钥产生的签名。

假如一个坏人在网上监听到了你取钱的交易,知道了你的公钥,他要偷你的钱,就必须实时的从公钥推导出私钥来,然后要产生一个跟你竞争的交易。你要把钱转你账户,他要把你钱转给他账户,即使这个坏人拥有量子计算机也很难几分钟内把你的私钥破解了,而且他发布的交易要抢在你交易的前面。

所以如果要防范量子计算机,安全起见,一个地址取钱之后就不要再用了,每次取钱最好把钱一次取走,即使取不完,也最好把钱转给另一个安全的账户

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。