第零章:最少量的基础知识

和所有介绍区块链原理的书一样,读懂本书也需要一些数学、密码学、计算机等方面的基础。但是作为普通人的我们,可能并没有这么多的学问,甚至连中学时代学过的数学课程也都快忘光了。不过不用担心,本书经过了精心设计,会尽量避开那些高深的知识,如果实在避免不了,我会用黑盒子来代替。
所谓黑盒子,你只要知道它能干些什么就行,完全不用管它内部是怎样工作的。举个例子,我们平时使用的电视机遥控器就是一个黑盒子(虽然它有可能是白色的)。我们只要知道按下某个按钮可以换台调音量就可以了,至于它内部有哪些零件,是怎么起作用的,都不需要知道。生活中这样的例子比比皆是,我们离不开各种各样的黑盒子。
本书用到的最重要的黑盒子就两个:哈希函数(Hash Function)和数字签名(Digital Signature)。本章会介绍这两个黑盒子能做一些什么样的事情,但是不会讲它们的工作原理。如果你对它们的内部工作原理感兴趣,可以找一些现代密码学的入门书来看一看。但是即使是只想了解这两个黑盒子的行为,也必须懂二进制和十六进制,以及数据的编码和解码。我相信大部分人都是可以理解这些内容的,我们从不同的进位系统开始说起。
十六进制
由于人类有十根手指头,所以我们认为十进制(Decimal,简称Dec)计数系统是非常自然的事情。现在我们来想象一下另外两个出现了文明的星球,其中一个星球上的人每双手只有两个指头,而另外一个星球上的人每双手有多达十六个指头。
对于两指星人来说,二进制(Binary,简称Bin)才是最合适的计数系统,他们只需要0和1两个数字就够了。但是对于十六指星人来说,显然十六进制(Hexadecimal,简称Hex)才是最舒服的计数系统,也就是说需要十六个数字。由于我们不了解这个外星文明,所以也不知道他们的数字到底长啥样。不妨假设他们也懂阿拉伯数字和英文字母,于是我们可以用0到9和a到f来表示从零到十五这十六个数字。简单来说就是,数字不够,字母来凑,如下图所示:

好的,二进制和十六进制已经讲完了。如果你还是不懂,可以打开你最喜欢的搜索引擎或者聊天机器人,再学习一番,然后回到本书。(一段时间以后)OK,你懂了,那我们继续学习。
无论是二、十还是十六进制系统,它们的表达能力是一样的。任何的数据都可以用任何一种进制系统来表达,而且相互之间也可以转换。打个比方,“你好”可以用中文、英文,或者世界上任何一个国家的文字系统来表达,而且可以相互翻译也不会丢失信息。以最简单的整数为例,下表列出了五个我们比较熟悉的整数以及它们的十六进制和二进制表示:
| 十进制 | 十六进制 | 二进制 |
|---|---|---|
12 | 0x0c | 0b1100 |
100 | 0x64 | 0b01100100 |
255 | 0xff | 0b11111111 |
2025 | 0x07e9 | 0b011111101001 |
21000000 | 0x01406f40 | 0b0001010000000110111101000000 |
在本书中,我们按照惯例通过增加前缀来区分二、十和十六进制整数。十进制整数就是我们熟悉的写法,不带任何前缀。二进制整数会带上0b前缀,十六进制的整数会带上0x前缀。另外,十六进制整数里面的字母通常会写成小写,但有时候也会大写,甚至大小写混合,同一个字母无论大小写表达的数字都是一样的。
此时你可能会有一个问题,既然我们已经非常习惯十进制进位系统了,它也足够用,那么干嘛还要伤脑筋去学习二进制和十六进制呢?我们也不打算去这两个星球旅行。答案是,因为我们所使用的电子计算机更喜欢二进制和十六进制。
在计算机里,信息是用二进制来表示的。任何的数据,在计算机看来都是一长串的0和1。我们可以粗略地认为计算机内部有许许多多微小的电子开关,关闭表示0,打开表示1。计算机存储信息的最小单位就是这种开关,每一个开关叫做一个比特(Bit)。你肯定听说过大名鼎鼎的比特币(Bitcoin),现在你至少应该理解它的字面含义了吧。它内在的奥秘我会慢慢揭晓。
话说回来,比特这个单位实在太小了,对我们人类并不是很友好,所以通常我们会把每八个比特分成一个小组,叫做一个字节(Byte)。下面这幅图展示了比特和字节的关系:

为什么是八个比特一组,而不是七个、九个、十个呢?这其实是个历史问题,这里就不展开讨论了。总之,一个字节包含八个比特,刚好可以用两个十六进制数字来表示(0x00~0xff)。所以我们在表示数据的时候,往往更倾向于写成十六进制,而不是二进制(写出来太长了)或者十进制(不好区分字节边界),这样读起来更容易一些。比如上图表示的数据,可以用十六进制表示为0x8f7bee94,一眼就可以看出有几个字节。但是如果用十进制表示成2407263892,就没那么直观了。
编码解码
细心的读者可能已经发现了,同样的数据用二进制表示最长,十进制次之,十六进制最短。既然如此,能不能用更大的进位系统让数据看起来更短呢?这听起来挺合情合理,毕竟英文有二十六个字母,而十六进制才用去六个。答案是肯定的。这种将数据按特定规则转换为某种形式的过程称为编码(Encoding),而将编码后的形式还原回原始数据的过程则称为解码(Decoding)。
除了前面提到的Hex等,目前已经有很多标准的编码方式了,例如Base32(使用三十二进制)和Base64(使用六十四进制)。下面这张表列出了同样一份数据使用不同编码方式编码后的结果:
| 进位制 | 编码方式 | 数据 |
|---|---|---|
| 二进制 | 二进制 | 10001111011110111110111010010100...(太长了,写不下) |
| 十进制 | 十进制 | 819149454988518274463132003182239727525499551994 |
| 十六进制 | Hex | 8f7bee940b9f27e8d12f6a4046b9ec57c940c0fa |
| 三十二进制 | Base32 | R5565FALT4T6RUJPNJAENOPMK7EUBQH2 |
| 五十八进制 | Base58 | 2zwYyPgJ9hNWDq9ajoctKKxCk5vZ |
| 六十四进制 | Base64 | j3vulAufJ+jRL2pARrnsV8lAwPo= |
如果你不懂这些编码方式是怎么把数据编码成最右列这样的,也没有关系,把它们当成黑盒子就好了。我们只要理解编码和解码是一对儿互逆的过程,一份数据经过编码之后得到另一份数据,解码后又能得到原来那份数据,如下图所示:

注意看上面的图,如果我们对一份数据进行(例如Base64)编码,我们必须使用同样的编码方式(Base64)对编码后的数据进行解码才能得到原始数据,否则我们得到的就是另外一份没有意义的数据。另外,虽然有时候编码后的确可以让数据看起来更小一点,但通常我们的目的并不是这个。如果想把数据缩小,可以用专门的压缩算法,如下图所示:

加密解密
无论是对数据进行编码还是压缩,我们的意图都不是防止别人解码或者解压缩数据。如果想要达到保密的效果,我们需要使用专门的加密算法来加密数据。加密算法听起来很难懂,实际上很容易理解。
以最古老的加密算法之一,凯撒加密算法(起源于古罗马凯撒大帝时期)为例。比如说我们想把BLOCKCHAIN加密,我们可以把每个字母都偏移3,加密之后得到EORFNFKDLQ。拿到加密消息之后,只要反过来对字母做变换就可以得到原始消息。这个加密算法很简单,只是把字母A变成D,B变成E,C变成F,...,X变成A,Y变成B,Z变成C,如下图所示:

在密码学中,我们把加密前的数据叫做明文(Plaintext),把加密后的数据叫做密文(Ciphertext)。像上面凯撒加密算法里,加密(Encrypt)是把每个字母右移3,解密(Decrypt)是把每个字母左移3。这里3必须严格保密才行,否则其他人就可以轻易破解你的消息,我们把它叫做密钥(Secret Key)。由于加密和解密都是用同一把密钥,所以我们把这样的加密算法叫做对称加密算法(Symmetric Encryption Algorithm),如下图所示:

我要提醒读者的是,这里只是以凯撒算法为例来帮助大家理解对称加密算法,千万不要在实际中用它来加密重要信息。因为凯撒加密算法实在太简单了,几乎不费吹灰之力就可以破解,现在比较推荐的是AES等标准的对称加密算法。还有一点,区块链技术其实并不使用对称加密算法,我们讨论它是为了进一步理解马上要介绍的非对称加密算法。
我们已经知道,对称加密算法使用同一把密钥来加密和解密数据。与之不同,非对称加密算法(Asymmetric Encryption Algorithm)需要两把钥匙。其中一把用来加密明文,需要保密,叫做私钥(Private Key)。另外一把用来解密密文,不需要保密,叫做公钥(Public Key)。因为这个特点,非对称加密算法也叫做公钥加密算法。
如果你觉得难以理解,可以想象一下你已经在网上下单购买了某本心仪已久的好书。第二天快递送达,但是快递员决定把它放到快递柜中。于是快递员用自己的投递码(私钥)打开9527号柜门,把快递放进去,然后关上柜门(加密)。你收到取件通知后愉快地跑到快递柜前,用系统发给你的取件码(公钥)打开9527号柜门,取出快递(解密)。这就是生活中贴近非对称加密逻辑的例子。非对称加密算法如下图所示:

这里要特别提醒一句,免得你日后产生误会。非对称加密有两个使用方向,而本书只讲其中一个。上面描述的是“私钥加密、公钥解密”,它对应的是数字签名——重点在于证明“这份数据确实出自私钥持有者之手”,这也正是区块链真正用到的那一半,我们下一小节就会讲到。而如果目的是保密传输,用的则是反过来的方向:“公钥加密、私钥解密”——任何人都可以用你公开的公钥给你加密,但只有握着私钥的你才解得开。所以你在别处看到“公钥加密、私钥解密”的说法,不要以为它和本书矛盾,那只是同一类算法的另一种用法。
顺便再说一下,区块链技术其实也并不使用非对称加密算法去加密数据,而是用它来进行数字签名,我们在后面的小节会讨论。目前常用的非对称加密算法有RSA和ECC(椭圆曲线加密)等,区块链使用的就是ECC相关的数字签名算法。
无论是对称还是非对称加密算法,我们都不需要实际去了解这些算法(黑盒子)是怎么工作的,只要知道,现代密码学里的(对称或者非对称)加密算法有两个特点:
一,难破解。除非掌握对应的密钥(如果是非对称加密,则是公钥),否则很难破解加密后的密文。以快递柜为例,如果你不知道密码,你很难打开某个仓门取走别人的快递。你只有不停地猜测密码,但这很难成功。
二,难伪造。除非掌握对应的密钥(如果是非对称加密,则是私钥),否则很难根据明文伪造密文。还是以快递柜为例,如果你不是快递员,你也很难打开某个仓门,把危险品放进去。你也只有不停地猜测密码,但这也很难成功。
上面所说的不停猜测密码这个破解方法,在密码学中叫做暴力破解(Brute-force Attack)。那么这里我们所说的难,究竟有多难呢?可以粗略地认为,就算你拥有当今世界上最强大的计算机,你可能也要花几万年(或者更长时间)才能破解或者伪造密文。
哈希运算
哈希(Hash)算法,也叫做摘要(Digest)算法,它的第一个特点是输出长度固定。无论你输入的数据有多长,得到的都是固定长度(比如256比特)的输出。我们把处理数据的过程叫做哈希运算,把得到的结果叫做哈希值或者摘要,如下图所示:

在现实世界里,我们的指纹具有唯一性,几乎不会和其他人的指纹相同。在计算机世界里,我们希望某个数据的哈希值可以成为这个数据的电子指纹。哈希算法有很多,例如著名的MD5算法。但是,为了满足电子指纹的要求,我们需要使用密码学安全的哈希算法,例如SHA-256算法。除了输出长度固定,密码学安全的哈希算法还有下面这些特点:
- 单向性。这个是显而易见的,因为哈希值是固定长度的,而且相对而言很短,你没办法根据哈希值反推出原始数据。比如你只看某个人的指纹,你甚至都无法想象这个人长啥样子。
- 敏感性。一个数据,哪怕只是改变其中一个比特,重新计算后的哈希值也会跟原来的哈希值大相径庭。两个人哪怕看起来很像(比如双胞胎),他们的指纹也有很大不同。
- 唯一性。已知某个数据,无法找到另外一个不同的数据,使它俩的哈希值一样。比如说我指定某一个人,你根本无法找到另外一个指纹和他一样的人。
- 抗碰撞。无法找到任意两个不同的数据,使它俩的哈希值相同。这就好比在地球上,你根本就找不到任何两个人,他们的指纹一样。
- 高效性。这个也是显而易见的,如果算个哈希值要等很久,那也太离谱了。我们在现实世界中采集和验证(尤其是验证)指纹也是相对较快的。
注意啦,这里的讨论并不是很严谨。在密码学中,单向性又叫做抗原像性,唯一性实际上叫做抗第二原像性。这些术语太晦涩了,对于不熟悉现代密码学的人来说,不太好理解,所以我们简化一下也无妨。总之,哈希函数的输出看起来就像是随机数,毫无规律可言。还有上面所说的“无法”,其实是“很难”的意思。到底有多难?以抗碰撞为例,你用目前世界上最强大的计算机,算上几万年,可能也找不到两个哈希值一样的数据。如果你还是没有理解,我通过一个具体的例子再来解释一下,请看下表:
| 数据(ASCII) | 哈希值(SHA-256) |
|---|---|
Hello | 0x185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969 |
Hell0 | 0x9737fccf37e39c58c9c3f4bc41130a8f8010c35c88bdeb288145781772727566 |
英文Hello使用ASCII编码后,通过SHA-256哈希函数计算得到的哈希值是0x185f8d...。如果只知道哈希值,能不能反推出Hello?不能,这就是单向性。如果我们把字母o改成数字0,虽然看起来改动不大,但是新的哈希值却完全不同:0x9737fc...。这就是输入敏感性。你能不能找到另外一个字符串,哈希值和Hello一样?不能,这就是唯一性。那你能不能找到任意两个字符串,它们的哈希值一样?也不能,这就是抗碰撞性。而我(使用网页工具)几乎瞬间就可以算出上面的哈希值,这就是高效性。
哈希运算就介绍到这里,下面这张表列举了四种常用的哈希算法,并给出了输出长度、是否密码学安全,以及把前一小节例子里的数据0x8f7bee...fa输入后得到的哈希值:
| 哈希函数 | 输出长度 | 是否安全 | 摘要(十六进制) |
|---|---|---|---|
MD5 | 16字节 | ✘ | 920248a3db8b86f35ce81d6946ddd6c8 |
SHA1 | 20字节 | ✘ | 004e54177430f59ba4dd0c8f3b74cf3a5ed23621 |
SHA2-256 | 32字节 | ✔ | 284e885b3dda348393dcbe65041cc2f102288b7f8659a0a8c3755eb69e575209 |
SHA3-256 | 32字节 | ✔ | 8ed19aad43648ed4bada097b46c1ef4d8cfe651109cd0d9dbddd55598ebdb858 |
数字签名
前面我们讲过,非对称加密算法的特点之一是很难破解密文。如果你获取一份我用私钥加密后的密文,除非你知道对应的公钥,否则你很难反推出明文。另外一个特点:很难伪造密文。给你一份数据,如果你不知道我的密钥,你很难算出对应的密文。
这两点都是很重要的。举个例子,两军对战,敌我双方各自沟通时传递的都是加密后的消息。如果我方的密文能够被敌方轻易破解,肯定是不行的,这样我方的计划就完全暴露了,没有任何秘密可言。反过来,如果敌方能轻易伪造我方的密文,那也不行,这样敌方就可以发送假消息,扰乱我方计划。
非对称加密算法的这两个特点,尤其是第二个特点,使得我们可以使用私钥构造数字签名。在现实世界中,只有我能签署某份文件,其他人很难伪造。在计算机里,只有我的私钥可以加密某份数据,其他人很难伪造。但是我们也知道,非对称加密算法生成的密文和明文几乎是一样大的。如果我签署一份文件,签名和这个文件一样大,那岂不是离谱?为了解决这个问题,我们需要把非对称加密算法和哈希算法结合起来,最终得到数字签名算法。
不管数据多大,我们先对它进行哈希运算,得到固定长度的哈希值。然后再用私钥对哈希值进行加密,得到的密文就是数字签名。生成数字签名的过程如下图所示:

那么已知数据、公钥、签名,怎么证明这个签名的确是对应私钥签署的呢?我们首先用公钥解密签名,得到一个哈希值h1。然后对数据进行哈希运算,得到另外一个哈希值h2。现在只要对比这两个哈希值是否一致就可以了。如果一致,签名得以验证。否则,这一定是个伪造的签名!验证数字签名的过程如下图所示:

当我们熟悉了数字签名的生成和验证过程之后,就可以把计算和比较哈希值等细节给隐藏起来。下面是简化后的数字签名生成(上)和验证(下)过程:

抽象函数
在结束本章之前,让我们复习一下小学低年级就已经熟练掌握的加法运算。2+3=5,对吧?很简单。在数学上,我们称加法运算是一种函数(Function),它的输入(Input)是两个整数,输出(Output)是求和结果,也是一个整数。用函数的思想,前面的加法算式可以写成add(2,3)=5。
类似的,在计算机科学里,我们也可以把上面介绍的各种算法都看成函数:输入一些数据,输出另外一些数据。如果你感觉自己一头雾水,你就把函数想象成制造爆米花的过程好了。把玉米粒(输入)放进微波炉(制作爆米花函数),一段时间后,得到爆米花(输出),如下图所示:

下面这张表整理了本章介绍的全部算法:
| 算法 | 正向 | 反向 | 正向输出长度 | 例子 |
|---|---|---|---|---|
| 编码 | 编码(数据1)=数据2 | 解码(数据2)=数据1 | 约等于输入 | UTF-8、Base64 |
| 压缩(无损) | 压缩(数据)=压缩包 | 解压缩(压缩包)=数据 | 远小于输入 | ZIP、PNG |
| 对称加密 | 加密(明文,密钥)=密文 | 解密(密文,密钥)=明文 | 约等于输入 | DES、AES |
| 非对称加密 | 加密(明文,私钥)=密文 | 解密(密文,公钥)=明文 | 约等于输入 | RSA、ECC |
| 哈希运算 | 哈希(数据)=摘要 | 不可逆 | 固定 | MD5、SHA-256 |
| 数字签名 | 生成(数据,私钥)=签名 | 验证(签名,公钥)=OK? | 固定 | ECDSA、EdDSA |
本章小结
本书认为,如果你可以理解十六进制,那么你就可以读懂区块链底层的工作原理。在本章,我们首先学习了二进制和十六进制计数法,然后学习了数据的编码和解码,以及加密和解密。
有了这些铺垫以后,我们学习了哈希运算和数字签名。哈希运算和数字签名这两个现代密码学算法是整个区块链大厦的基石,你不需要理解它们的内部细节,但必须知道它们可以做些什么,以及它们的特点。如果你还没有学会这些黑盒子,请反复阅读本章,必要时可以求助搜索引擎或者AI。如果你已经掌握了这些知识,那么你已经做好准备,跟着本书一起去探索区块链的星辰和大海吧!