你好,我是陈皓,网名左耳朵耗子。
前面一讲中提到的技术解决了交易信息不能被篡改的问题。但还有一个比较重要的问题,那就是,我们每个人只能发起和自己有关的交易,也就是能发起自己对别人付钱的交易,我们不能发起别人对我付钱,或是别人向别人付钱的交易。
那么,在比特币中是怎么解决这个问题的?让我们先看一些基础的加密技术。
比特币的加密方法
密钥对/签名/证书
所谓密钥对,也就是一种非对称加密技术。这种技术,在对信息进行加密和解密时,使用两个不同的密钥。这样一来,我们就可以把其中一个密钥公布出去,称之为公钥,另一个密钥私密地保管好,称之为私钥。
现实社会中,有人使用公钥加密,私钥解密,也有反过来用私钥加密,公钥解密,这得看具体的场景。(比特币使用了非对称加密的技术,其使用了ECDSA 密钥对比技术。)
比如,我把我加密的密钥发布给所有人,然后大家都用这个公钥加密信息,但其他人没有私钥,所以他们解不了密文,只有我能解密文,也只有我能看得懂别人用我的公钥加密后发给我的密文。如下图所示。

但是,这会有个问题,那就是每个人都有我的公钥,别人可以截获Mike发给我的信息,然后自己用我的公钥加密一个别的信息,伪装成Mike发给我, 这样我就被黑了。于是,我们需要对Mike的身份进行验证,此时就需要用到“数字签名”的概念了。
Mike也有一对密钥对,一个公钥给了我,私钥自己保留。
-
Mike发自己想要的信息,做个SHA或MD5的hash,得到一个hash串,又叫Digest。
-
Mike用自己的私钥,把Digest加密,得到一段Digest的密文。我们把这个事叫数字签名,Signature。
-
然后,Mike把他想发给我的信息用我的公钥加密后,连同他的数字签名一同发给我。
-
我用我的私钥解密Mike发给我的密文,然后用Mike的公钥解密其数字签名得到Digest。然后,我用SHA或MD5对解开的密文做Hash。如果结果和Digest一致,就说明,这个信息是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
-
发起交易。我们从第一笔交易可以看到,A用自己的私钥为交易信息和自己的地址生成了交易的签名,然后把交易信息、自己的地址、交易签名和自己的公钥放出去,这样方便别人来验证的确是A发起的。
-
验证交易。在验证时,使用A的公钥解密交易签名,得到交易的hash值。把交易信息和自己的地址做hash,看看是不是和签名解密后的hash值一致。
这里需要注意一个细节,比特币的地址是由我们的公钥生成的,生成规则比较复杂,可以参看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(调整不同的记账交易)了。
所以,一般的挖矿流程如下。
-
从网络上取得之前的区块信息。
-
从"待记账区"中获取一组交易数据(有优先级,比如成长时间、矿工小费等)。
-
形成区块头(计算Merkle Root并设计记账时间Timestamp等)。
-
开始穷举Nonce,来计算区块头的hash值。如果前面有18个零(小于Target),那么记账成功。如果没有,则从第一步重新开始。
-
一旦某矿工成功打包一个区块,他就会告诉其他矿工。收到消息的矿工会停下手上的工作,开始验证,验证通过后,广播给其他矿工。
所以,满足条件的这个难度系数成为了挖矿的关键。设置这个难度系数就是为了让全网产生的区域名平均在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 名。
看到这里,你一定要问,为什么要挖矿呢,不就是记个账呗。为了系统地说明这个问题,我们下节课来看看去中心化的共识机制。
文末给出了《区块链技术》系列文章的目录,希望这一系列内容对你有启发,有帮助。
精选留言
2018-04-05 13:02:05
2018-04-05 22:28:49
2018-04-08 08:42:51
2020-02-27 17:07:57
2018-04-05 14:49:41
2019-12-20 12:59:25
证书并不能解决黑客黑掉 Mike 电脑的问题啊,既然他能黑掉 Mike 电脑,也就能够使用 Mike 的私钥,然后假装自己是 Mike,和 Jack 通信了。
2018-10-19 11:22:38
我在阅读专栏时,顺便学习代码,发现了 一个问题,期望您的解答。
在测试程序部分:
str = data + `n`
这行代码是否存在问题? 这样的话,这个传入的字符串始终是“ChenHaon",程序就陷入死循环了。
我修改了一下,将变量名改名为input_str:
input_str = data + str(n)
程序能正常运行,得到结果,耗时约1.72秒(台式机)。
请问这样改是否合理?
多谢
2018-09-19 21:16:28
2018-06-24 20:41:44
2018-06-22 18:46:43
2018-06-03 17:07:33
2018-04-21 17:40:33
hash = hashlib.sha256(str).hexdigest()
hash = hashlib.sha256(hash).hexdigest()
2018-04-08 14:51:14
为什么比特币会是有限的?
有没有可能一笔交易额太小了没有挖矿机愿意为他记账?
2023-03-29 16:44:09
比特币中是对每个交易都要签名么,会成为性能的瓶颈么?
比特币的设计思路中,区块链和交易的签名机制,应该算是比较核心的部分;挖矿难道仅仅是为了“浪费时间”么,为什么不能计算一点有意义的东西?
在挖矿的流程里面,如果成功打包了一个区块,然后需要全网广播,感觉这个也很费电。有没有其他的办法限制每 10 分钟产生一个区块,并且能够根据工作量或者其他的方式决定记账权归属。
非中心化并不意味着平权,更不意味“均贫富”,其实还是马太效应。
2022-12-27 17:32:47
2022-10-21 14:08:27
Tom生成密钥(a_pri/a_pub), Bob生成密钥(b_pri/b_pub)两者都公开公钥 a_pub和 b_pub为了相互传递信息. 第三人(窃听者)也生成密钥(ca_pri/ca_pub)和(cb_pri/cb_pub) 生成两对.
首先John(窃听者)会把Tom的公钥 a_pub截取保存, 再把自己的 ca_pub发给Bob, 然后Bob的公钥 b_pub截取保存, 再把自己的 cb_pub发给Tom. 他们两者都会认为自己拿到的公钥是对方的.
此时Tom和Bob相互传递自认为对方的公钥(实际是窃听者John的)加密后的信息发给对方, John在两边接收密文通过自己的私钥(ca_pri, cb_pri)解密, 再发给各方 完美扮演了中间人的角色.
————————————————
版权声明:本文为CSDN博主「Shawn Jeon」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qcl108/article/details/102767276
2022-10-18 19:33:22
2020-05-19 23:01:48
2020-04-25 14:00:02
2020-03-28 22:39:12