前面一篇文章中讲的技术解决了交易信息不能被篡改的问题。但还有一个比较重要的问题,那就是,我们每个人只能发起和自己有关的交易,也就是能发起自己对别人付钱的交易,我们不能发起别人对我付钱,或是别人向别人付钱的交易。
那么,在比特币中是怎么解决这个问题的?让我们先看一些基础的加密技术。
所谓密钥对,也就是一种非对称加密技术。这种技术,在对信息进行加密和解密时,使用两个不同的密钥。这样一来,我们就可以把其中一个密钥公布出去,称之为公钥,另一个密钥私密地保管好,称之为私钥。
现实社会中,有人使用公钥加密,私钥解密,也有反过来用私钥加密,公钥解密,这得看具体的场景。(比特币使用了非对称加密的技术,其使用了ECDSA 密钥对比技术。)
比如,我把我加密的密钥发布给所有人,然后大家都用这个公钥加密信息,但其他人没有私钥,所以他们解不了密文,只有我能解密文,也只有我能看得懂别人用我的公钥加密后发给我的密文。如下图所示。
但是,这会有个问题,那就是每个人都有我的公钥,别人可以截获 Mike 发给我的信息,然后自己用我的公钥加密一个别的信息,伪装成 Mike 发给我, 这样我就被黑了。于是,我们需要对 Mike 的身份进行验证,此时就需要用到 " 数字签名 " 的概念了。
Mike 也有一对密钥对,一个公钥给了我,私钥自己保留。
这个过程如下图所示。
但是问题还没完。假设有个黑客偷偷地把 Jack 电脑上的 Mike 的公钥给换了,换成自己的,然后截获 Mike 发出来的信息,用自己的密钥加密一段自己的信息,以及自己的数字签名。
于是,对于 Jack 来看,因为他用了黑客的公钥,而不是 Mike 的,那么对他来说,他就以为信息来自 Mike,于是黑客可以用自己的私钥伪装成 Mike 给 Jack 通信。反之亦然,于是黑客就可以在中间伪装成 Jack 或 Mike 来通信,这就是中间人攻击。如下图所示。
这个时候就比较麻烦了。Mike 看到有人在伪造他的公钥,想了想,他只能和 Jack 找了个大家都相信的永不作恶的权威的可信机构来认证他的公钥。这个权威机构,用自己的私钥把 Mike 的公钥和其相关信息一起加密,生成一个证书。
此时,Jack 就可以放心地使用这个权威机构的证书了。Mike 只需要在发布其信息的时候放上这个权威机构发的数字证书,然后 Jack 用这个权威机构的公钥解密这个证书,得到 Mike 的公钥,再用 Mike 的公钥来验证 Mike 的数字签名。
上面就是整个密钥对、签名和证书的全部基础细节。比特币也用了这样的基础技术来认证用户的身份的。下面,我们来看看比特币的一些细节。
在比特币的世界里,每一笔交易的 From 和 To 都是每个用户的公钥(Public Key)。也就是说,使用用户的公钥来做交易的账户。于是,这个过程很简单。
为什么不需要那个证书机构呢?不怕中间人攻击吗?这是因为,如果黑客想要伪造一笔别人的交易,那么他需要换掉半数以上结点上的被攻击者的公钥,这不太现实。与其这样做,还不如去偷被攻击者的私钥,可能还简单一些。
下面是一个交易链的图示。这个交易链的钱从 A -> B -> C -> D,一共 3 笔交易。
图片来源:Ken Shirriff Blog
这里需要注意一个细节,比特币的地址是由我们的公钥生成的,生成规则比较复杂,可以参看 Bitcoin 的 Wiki 页 - Technical background of version 1 Bitcoin addresses。
前面说到,在比特币的区块 hash 算法中,要确保下面这个公式成立:
SHA-256(SHA-256 (Block Header)) < Target
而在区块头中,可以完全自由修改的只有一个字段,就是 Nonce,其他的 Timestamp 可以在特定范围内修改,Merkle Root 和你需要记录的交易信息有关系(所有的矿工可以自由地从待确认交易列表中挑选自己想要的交易打包)。
所以,基本上来说,你要找到某个数字,让整个 hash 值小于 Target。这个 Target 是一个数,其决定了,我们计算出来的 hash 值的字符串最前面有几个零。我们知道,hash 值本身就是一串相对比较随机的字符串。但是要让这个随机的字符串有规律,是一件很困难的事,除了使用暴力破解,没有其他办法。在计算机世界里,我们把这个事叫 " 哈希碰撞 "(hash collision),碰撞前几个位都是 0 的哈希值。
下面是一个示例。我想找到一个数,其和 "ChenHao" 加起来被 hash 后的值前面有 5 个零。
测试程序如下:
import hashlib
data="ChenHao"
n=1
while n < 2**32:
str = data + `n`
hash = hashlib.sha256(str).hexdigest()
hash = hashlib.sha256(hash).hexdigest()
if hash.startswith('00000'):
print str, hash
break
n = n + 1
这是一个暴力破解的算法。这个程序在我的 MacBook Pro 上基本要 10 秒钟才跑得出来结果。
找到 1192481 时,找到了第一个解,如下所示:
ChenHao1192481
00000669e0eeb33ee5dbb672d3bd2deb0c32ef9879ef260f0debbdcb80121160
那么,控制前面有多个 0 的那个 Target 又是怎么来的呢?是由 Bits 这个字段控制的,也就是难度系数,前面需要的 0 越多,难度也就越大。其中的算法你可以看一下 Bitcoin 的 Wiki 上的Difficulty 词条,这里我就不多说了。
这个难度系数,会在每出 2016 个区块后就调整一次。现在,这个难度是要在前面找到有 18 个零。如下所示 (一个真实的区块链的 Hash 值):
000000000000000000424118cc80622cb26c07b69fbe2bdafe57fea7d5f59d68
一个 SHA-256 算法算出来的哈希值有 $2^{256}$ 种可能性,而前面有 18 个零意味着前面有 72 个 bits 是零。于是,满足条件的哈希值是有 $2^{184}$ 种可能性,概率是 $\frac{1}{2^{72}}$ 。
是的,很有可能你穷举完 Nonce 后还找不到,那就只能调整 Timestamp 和 Merkle Root(调整不同的记账交易)了。
所以,一般的挖矿流程如下。
所以,满足条件的这个难度系数成为了挖矿的关键。设置这个难度系数就是为了让全网产生的区域名平均在 10 分钟一块。而根据比特币无中心服务器的架构,也就是其挖矿的机器数量是想来就来想走就走的,计算力可能会不一样。因此,为了保证每 10 分钟产生一个区块,当算力不足的时候,难度下降,当算力充足的时候,难度提高。
今天的这 18 个零,基本上来说,一般的电脑和服务器就不用想了,必须要算力非常非常高的机器才能搞定。所以,在今天,挖矿这个事,已经不是一般老百姓能玩的了。
下图展示了整个比特币的难度历史。
(图片来源:http://bitcoin.sipa.be )
上面这个图只是算力的表现,可能并不直观。我们还是用其耗电量来说可能会更好一些。根据 "Bitcoin Energy Consumption Index" 统计,截至 2017 年 11 月 20 日,比特币过去一年挖矿的电力总消耗已累计达 29.51 TWh(1TWh = $10^{12}$ Wh),约占全球总电力消耗的 0.13%。该数字甚至已经超过近 160 个国家或地区一年的电力消耗,包含冰岛和尼日利亚。若全球的比特币矿工自成一国,该国的电力消耗排名可排到全球第 61 名。
看到这里,你一定要问,为什么要挖矿呢,不就是记个账呗。为了系统地说明这个问题,我们下面来看看去中心化的共识机制。