区块链可谓是最近一段时间被提及最多、最火的一个名词,以比特币为代表的各种区块链代币也层出不穷。而关注到技术本身,区块链到底是什么、区块链带来了什么等问题,却很少有清晰的介绍。
笔者在近一段时间也翻阅查找了各种技术资料以及研究,因此希望能写出一篇简单易懂的入门教程,从技术的角度来解读区块链。当然,笔者也并非这方面的专家,仅是在业余时间断断续续研究,有阐述不明确的地方也请谅解,如有错误也请指正,同时若想一起探讨区块链技术也是十分欢迎。
本篇文章主要介绍以比特币为代表的区块链技术,通过本文,你将理解区块链的加密原理、区块链网络运行原理,以及钱包、交易、区块、挖坑等概念。
比特币是第一个使用区块链技术实现的应用,而区块链技术的本身并非是凭空出现,在此之前则要提到“密码朋克(Cypherpunk)”,这是一个由密码天才们松散组成的神秘团体,早先的数字货币则是在该团体中提出并传播的,比如大卫·乔姆的现金公司(DigiCash)使用的 Ecash、亚当·贝克的哈希现金(HashCash)(其中就用到了 POW)、戴伟的 B-money 等等,而他们都以失败告终。
2008 年 10 月 31 日,一个自称是中本聪(Satoshi Nakamoto)的发布了一篇名为《比特币:一个点对点的电子现金系统》的论文,其中所描述的比特币的区块链技术正是结合了以往失败的数字货币的技术特点而提出的一种去中心化的电子现金系统。
同年 11 月 6 日,中本聪发布了比特币代码的先行版本。
次年 1 月 3 日,中本聪在位于芬兰的一个小型服务器上挖出了比特币的第一个区块 —— 创世区块,并获得了首批挖矿奖励 —— 50 个比特币。
简单地说,比特币中的区块链可以理解为一种分布式的数据库,其中存储着整个网络中的交易账本。而每个人都可以通过 P2P 的形式连接到区块链网络成为其中的一个节点,并同步所有的数据,同时可以读取其中的任何一笔交易。而新生成的交易数据通过挖矿机制定时地打包成新的区块并且写入数据库,并通过节点的共识机制得到确认。
由于其底层原理,比特币具有以下几点特性:
密码学在比特币中的作用十分重要,不管是涉及交易认证、比特币的所有权、挖矿等等,都依赖于密码学。以下主要简介一些用到的密码学基础:
Hash(哈希/散列)算法是现代信息基础中非常重要的技术,它可以将任意长度的二进制值(明文)映射为固定长度的二进制值(Hash 值),而却难以通过 Hash 值找出其对应的明文。
这里以 MD5 Hash (Hash 函数的一种) 为例:
$ echo "Hello world by qiutc.me" | md5
$ 6bebc6c6a6e6f31d36d2e2d493e5229b
一个理想的 Hash 函数 H(x) 是一个映射函数,函数将任意长度的输入映射成 n-bit 的输出,并且需要遵循几个原则:
理论缺陷:理论上对于输入的值,其个数是无穷无尽的,但是对于输出,其个数限制为 m^n 个,也就是说很可能出现对于多个输入会得到同一个输出,因此这也是单向性的一个特征,一个输入绝对对应一个输出,而一个输出不一定只对应一个输入;当然这个缺陷理论上是无法解决的,因此只有通过算法去避免这种情况的出现,使其概率极小,在一定范围内的缺陷还是被认为可接受的。
以上例子中的 MD5 Hash 一个经典的 Hash 算法,然而它已经被证明为安全性不高,类似的还有目前流行的 SHA-1 、 SHA-2 等 Hash 算法,在比特币中主要用到的则是 SHA256 和 RIPEMD160。
对称加密又称为古典密码学,原始数据和加密数据的互相转换使用同一个密钥 key ,可以这么理解:y = F(x, key) ,x 为原始数据,y 为加密数据,F 为可逆的加密算法(一般来说加密算法是公开通用的,因为你自己很难发明新的加密算法)。
因此对于数据加密解密过程的安全性完全取决于密钥 key ,发送者和接收者都持有该密钥,发送者使用密钥加密数据发送给接收者,接收者使用密钥解密得到原始数据。一般来说,只要密钥不泄露信息则是安全的,然而在一个大的网络系统中,密钥的分发和保存有成为一个难题,很难确保密钥不被泄露,而一旦密钥泄露那么信息的传输则是不安全的。
对称加密在大的网络系统中被认为是不靠谱、不安全的,而非对称解密可谓是现代密码学中的一大里程碑。以下将主要简单介绍非对称解密的概念,其具体算法细节不做考究。
在对称加密中,大家使用同一份密钥对数据进行加密和解密,而其安全缺陷则是对密钥分发和保管的过程难以保证密钥不被泄露。
在非对称加密系统中,每一个用户都有一对密钥 —— 公钥和私钥,公钥是对系统中其他所有用户公开可见的,私钥则是该用户自己保存保密的,并且公钥和私钥之间存在着一定的数学关系,由私钥可以很容易地推导出公钥,而由公钥则很难破解得出私钥。
假设系统中有 A、B、C 三个用户,A 想要发送信息给 B,该过程如下:
A 拿到 B 的公钥(Public key B),然后用它加密数据;
A 将加密的数据发送给 B;
B 拿到了加密数据,使用自己的私钥(Private key B)解密数据,从而得到信息;
这里的关键则是一对 公钥 和 私钥之间的关系,使用公钥加密的数据只能用其对应的私钥才能解密;假设 C 为破解者,在 A 发送给 B 加密数据的过程中,C 拿到了加密数据,而用它自己的 私钥 是无法解密的,同时 C 又无法拿到 B 的私钥,因此 C 无法破解数据。
常见的非对称加密算法有 RSA、ElGamal、椭圆曲线(Elliptic Curve Crytosystems,ECC),在比特币中主要使用的是 椭圆曲线 算法。
类似在纸质合同中的签名,使用数字签名可以确保信息的来源正确,数字签名是基于非对称加密之上的应用。
继续上文例子,当 B 收到信息之后,想要给 A 回复,同时希望 A 能确认该回复信息确实是 B 发送的,那么:
B 使用自己的私钥(Private key B)生成一个数字签名,然后使用 A 的公钥(Public key A)加密数据和签名;
B 将附带签名的加密信息发送给 A;
A 拿到了附带签名的加密信息,使用自己的私钥(Private key A)解密数据,得到信息和签名,然后 A 使用 B 的公钥(Public key B)对签名进行对比,如果匹配成功则说明该信息确实是 B 发送而非 C 发送的。
在比特币中,对于其货币的所有权由用户存有的密钥来决定,也就是说并非你拥有多少个比特币,而是密钥对应了其所有的比特币,你拥有的是密钥,由此可见,密钥是相当重要的,一旦丢失密钥则意味着你对这笔比特币失去了控制权。
密钥包含一对私钥和公钥,其中私钥是最重要的。
比特币的私钥是可以是 1 到 n-1 之间的任何数字,其中 n 是一个常数(n = 1.158*(10^77) < 2256)。
生成密钥的第一步则是需要保证其随机性,可以通过程序随机选择,或者鼠标随机晃动,以 www.bitaddress.org (可随机生成比特币密钥)为例,当我随机晃动鼠标之后得到了我的私钥:
L2h5b4L4HzEjF4GprS31ismL6KVSLKe4VbuKNYW6gz1uBVRdZqg3
它以十六进制格式表示(256 位的二进制数,以 64 位十六进制数显示,每个十六进制数占 4 位);
之后通过非对称算法 —— 椭圆曲线算法,计算得到公钥;
最后将公钥通过哈希算法得到地址,也就是对外公开的比特币钱包地址。
简单表示:
其中,私钥由拥有者自己保管(一般存在于钱包中),地址是唯一对外暴露的信息。
比特币的钱包最主要的功能是作为密钥的容器,可以理解为只要你拥有了密钥也就拥有了钱包。比特币的钱包也是多种多样,比如冷钱包、纸钱包、脑钱包,它们的区别实质上是对密钥的存储形式不同。一般而言我们的密钥是需要导入到比特币客户端中才能进行交易与管理比特币,当然客户端的作用除了存储密钥,还存储比特币的交易数据并且可以进行交易操作,比特币客户端主要分为以下几种:
交易是比特币系统中最重要的部分,根据比特币系统的设计原理,系统中的其他部分都是为了确保交易可以被生成、在比特币网络中传播、通过验证确认,并且可追溯。
交易的实质是所有者部分资产的转移,最简单的交易至少需要以下几点:
首先,一笔交易需要被传递到比特币网络中才能传播,比特币网络基于 P2P 网络实现的,因此其传播速度是指数增长的。
当一笔交易传播到某一个节点,该节点会通过这笔交易的签名验证发送者是有真实拥有转移的比特币数量的所有权,如果通过则继续传播,否则该笔交易则是不合法,该节点会拒绝该笔交易并向前一个节点发起该交易被拒绝的消息。
当一笔交易通过节点的共识验证通过,并且被矿工打包到新区块之后,该笔交易才真正算是被确认。
这里首先得否定一个概念 —— 余额,实际上你的钱包不存在多少数量的比特币余额,你拥有的只是对一定数量比特币的所有权,那么既然不存在余额如何牵扯到所有权呢?
比特币系统中有这么一个概念 —— UTXO (Unspent Transaction Output),从字面意思中可以理解它表示未交易的输出。UTXO 是不能再分割、被所有者锁住或者记录于区块链中的并被整个网络识别成货币单位的一定量的比特币货币。
可以这么理解,在区块链(比特币账本)中并不存在独立的一个个比特币,当前的比特币网络中的总额(总的比特币数量)由一个个 UTXO 组成,称为 UTXO 池。
一个 UTXO 可以是一“聪”的任意多倍(比特币的最小单位,1 聪 = 0.00000001 比特币),它标明了该 UTXO 指代的比特币数量;
一个 UTXO 还包含了其上一笔交易的 Hash ,UTXO 并非凭空出现的,因此每一 UTXO 都是可追溯的;
最重要的是每一个 UTXO 都指向了一个钱包地址,它真正表示该 UTXO 的所有权,因此比特币钱包中的余额实质上是指向该钱包的所有 UTXO 的总和的比特币数量。
以上面的一个简单的示意图可以看出,当前的所有比特币总和是一个一个 UTXO 组成的 UTXO 池,其中分布着有限数量的 UTXO ,每个 UTXO 代表了一定数量的比特币,同时指向某一个比特币地址,比如上图中的 Bob 就拥有 3.3 个比特币,当然实际上 Bob 是一个比特币的 Hash 地址,因此保证了资产所有者的匿名性。
那么既然 UTXO 是不可分割的,那么如何使用它进行交易呢?同时 UTXO 又是如何产生的呢?
上文说到,交易比特币实质上是交易 UTXO,每一笔交易都存在输入于输出。输入一定数量的 UTXO 被消耗掉,同时输出一定数量的 UTXO。可以把 UTXO 理解为现实生活中的纸币,只是其每一张纸币的面额是不同并且非固定的。比如 Bob 拥有 1.5、0.7、1.0、0.1 四张纸币,当他想要给 Alice 转账 1.2 个比特币的时候:
由于 UTXO 是不可分割的,所以 Bob 使用 0.7 和 1.0 的 UTXO 作为输入,转账给 Alice 1.2 个比特币,因此输出两个 UTXO,一个是指向 Alice 的 1.2 个比特币,一个是指向 Bob 的 0.5 个比特币,可以理解为找余。因此在这一笔交易中消耗了两个 UTXO 作为输入同时产生了 两个新的 UTXO 作为输出。输出的 UTXO 还包含了该笔的交易信息,因此每个 UTXO 都是可追溯的。以上的例子中 Bob 还可以使用 1.5 的 UTXO 作为输入,其实都是一样的。
当然,该例子只是简单的示意,其中包含的更多信息并没有涉及,比如交易的手续费、交易的 Script 脚本等。
比特币网络基于点对点网络(P2P,peer-to-peer),以下摘自维基百科:
对等式网络(peer-to-peer, 简称P2P),又称点对点技术,是无中心服务器、依靠用户群(peers)交换信息的互联网体系,它的作用在于,减低以往网络传输中的节点,以降低数据丢失的风险。与有中心服务器的中央网络系统不同,对等网络的每个用户端既是一个节点,也有服务器的功能,任何一个节点无法直接找到其他节点,必须依靠其户群进行信息交流。 P2P节点能遍布整个互联网,也给包括开发者在内的任何人、组织、或政府带来监控难题。P2P在网络隐私要求高和文件共享领域中,得到了广泛的应用。使用纯P2P技术的网络系统有比特币、Gnutella,或自由网等。另外,P2P技术也被使用在类似VoIP等实时媒体业务的数据通信中。有些网络(如Napster、OpenNAP,或IRC @find)包括搜索的一些功能,也使用客户端-服务器结构,而使用P2P结构来实现另外一些功能。这种网络设计模型不同于客户端-服务器模型,在客户端-服务器模型中通信通常来往于一个中央服务器。
比特币网络是去中心化的,没有中央服务器,它不同于传统的 CS 架构,每个连接到比特币网络中的节点即使服务端也是客户端,当你运行一个完整的比特币客户端的时候,你就作为一个节点连接到了比特币网络。
比特币网络中的每个节点都是对等的,以扁平(flat)的拓扑结构相互连通。
当一个节点启动的时候,它必须至少与一个现有网络中的节点相连接才能接入比特币网络,节点间的拓扑关系与地理位置无关,对于比特币客户端(作为节点)来说,启动时会做如下几件事:
节点间的相连采用 TCP 协议,使用 8333 端口。
节点间通过发送 version 与 verack 进行初次握手连接:
节点通过发送 version 消息连接到一个对等节点, version 中包含了该节点的版本信息、块信息和距离远程节点的时间。当这个消息被对等节点收到,它必须回复一个 verack。如果它愿意建立对等关系,它将发送自己的version消息。
当建立对等关系,节点可以向其发送 getaddr 和 addr 消息来获得其它的对等节点信息。为了维持与对等节点的连接,节点默认情况下每 30 分钟内会给对等节点至少发送一次信息。如果超过 90 分钟没有收到回复,节点会认为连接已经断开。
当节点建立连接成功之后,则需要互相交换“库存” —— 区块数据,节点间互相发送 getblocks 交换自身的区块信息,如果一个节点认为自身区块更高(拥有更长的链,更多的数据),则会向对方发送一个 inv 消息,其中包括了最多 500 个区块信息,接收方会向其发送 getdata 获取更多信息,之后通过 block 消息同步补全更多的区块数据。
交易的广播也是通过 inv 消息实现的,如果收到了getdata信息,那么交易通过发送 tx 实现。对等节点收到有效的交易的信息后会通过类似的方式将其扩散。
前文说了如此多,不管时交易的确认还是数据的同步都涉及区块与区块链,那么区块链又是什么呢?
在比特币的白皮书中可以看到以下两张图:
前者表示交易数据的存储,后者表示区块链的数据结构。交易数据是存储于某个区块之中,区块之间按照产生的时间顺序以链式结构从前到后相连接。
可以理解为每一个区块都是一个账本,其中记录了多笔交易信息,每隔 10 分钟左右都会有一个新的账本(区块)产生,账本(区块)按照时间顺序一本本往上叠加。
每个区块的上一个区块即是该区块的父区块,一个区块有且只有一个父区块;但是每个区块可以暂时拥有多个子区块(原因后面阐述),而随着新区块的产生(一般为 6 个),该区块也仅会有一个子区块。
观察以上图片,一个区块中主要包含以下几个信息:
在一个区块中,其最重要的是唯一标识该区块的区块哈希值,区块哈希值由该区块的区块头经过 Hash 算法多次计算得出,然而在上图中我们并未看到一个区块中包含的区块哈希值,区块哈希值计算比较简单,一般不独立存储在一个区块之中。
从区块哈希值的来源可以得出,它主要取决于父区块的哈希值、时间戳、Merkle 根,其中 Merkle 根主要与该区块包含的交易数据有关,因此可以得出区块哈希值主要关系到它的父区块、时间、交易数据。
区块间的连接主要是依赖于区块的区块哈希值,从区块结构中可以得到,每个区块的区块头中包含其父区块的区块哈希值,而该区块的子区块的区块哈希值又依赖于它自身的区块头中包含的它的父区块的区块哈希值。
因此可以这么认为:每一个区块的区块哈希值都与其父节点的区块哈希值存在某种关系(Hash 计算),因此区块间的关系以此为连接,只要确认一个区块,就可以追溯其父区块,依次不断直到创世区块。
区块链中的第一个区块称为创世区块,比特币中的创世区块有中本聪在 2009 年挖出。每个比特币客户端中都存有创世区块的数据,以此为根节点去同步所有区块。
一个区块的区块高度则是指以此为起点不断追溯父区块直到创世区块的数量,以前文中将区块比作账本为例,某一本账本(区块)的高度指的是在它下面的账本(区块)的数量。
一个区块的区块高度也可以用于识别找到某一区块,当然如果对于较新的区块它不一定准确,因为区块链存在分叉。
挖矿是维持比特币稳定、安全运行的最重要的部分,挖矿的实质是产生打包新的区块。
挖矿并非是我们现实中对贵金属的挖掘,它是对比特币打包新区块的一种指代。比特币中的挖矿过程需要使用矿机来不断计算,维持一定的算力,其消耗的实际上主要是是电力。那么为何会有人(矿工)愿意耗费资源来挖矿呢?
比特币系统中设计了对挖矿的奖励机制,当一个矿工成功打包一个区块时,他会得到其相应的奖励 —— 比特币。正是由于这种奖励机制,才有越来越多的矿工加入进来。
通过挖矿的产生的部分奖励也正是比特币的产生的过程 —— 比特币的发行,类似于央行对于法币的发行。因此比特币的产生并非凭空出现,是通过每一次的挖矿来产生的。
比特币的总量是有上限的,按照比特币系统的设计规则,对于挖矿产生的奖励机制是会逐渐递减的,矿工通过挖矿获得的奖励每隔大概四年会减少一半,最开始的 2009 年 1 月每个区块尝试的奖励是 50 个比特币,到了 2012 年 11 月减为 25 个比特币。目前,每个区块的奖励是 12.5 个比特币;随着奖励的逐渐递减,到 2040 年,所有的比特币(20999990080 聪,接近 2100W)将会被发行完毕。
正是比特币的发行机制以及其固定的总额,保证了比特币发行的公平性,其去中心化的特性保证的整个货币体系的稳定性,同时不会出现通货膨胀等一系列金融问题。
上文提到,在 2040 年之后通过挖矿将不会产生新的比特币,那么到那时候又如何让矿工继续维持挖矿呢?
对矿工的奖励除了给予该区块新产生的比特币,还包括交易的手续费,这里弥补一下前文中在交易过程中的一个遗漏。
之前提到一个例子:Bob 向 Alice 转账 1.2 个比特币,该交易的输入为 2 个 UTXO:一个 UTXO 有 0.7 BTC、一个 UTXO 有 1.0 BTC,而在输出的 UTXO 总和实际上为 1.6 BTC,如下图:
其中输入总和和输出总和的差值为 0.1BTC,它则是该笔交易需要付的手续费(实际上并不会这么多),它作为奖励会给予打包了包含该交易区块的矿工。
因此,当在 2040 年比特币全部发行完之后,矿工的主要收入来自于交易的手续费。
接入比特币网部分的节点是挖矿节点,挖矿节点间存在着竞争关系。
挖矿节点会接收、传播并整合未被确认的交易,同时挖矿节点会计算打包新的一个区块,它们间的竞争关系存在于谁先打包出新的区块;当一个新的区块被打包产生并传播,也就意味着一轮竞争的结束,成功打包该区块的挖矿节点将其广播出去,同时获得奖励;而对于其他节点来说,它们在这场竞争中失败了,同时它们会立即停止手头的工作。竞争中的胜利指的是谁最先打包出区块,因此后者的继续工作是无意义的。
同时一轮竞争的结束意味着新一轮竞争的开始,胜利者和失败者都会立即进行下一轮竞争 —— 打包新区块。
挖矿的目的是成功打包出新区块,而如何打包区块?其过程又是怎样的呢?
挖矿的第一步是构建区块头,前文说到区块头包含以下几个信息:
首先挖矿节点填充版本标识其版本号,接着是需要填充父区块的哈希,也就是将要打包区块的上一个区块的区块哈希值。
然后将整合的未确认的交易组成一个 Merkle 树并获取到其 Merkle 根,再填充当前时间戳。
之后会填充难度目标,难度目标是一个 Hash 值,它定义了计算的工作量。
最后是一个 Nonce 值,其初始值为 0。
构建完成区块头之后则开始正式挖矿,其目标则是需要寻找到一个满足一定条件的 Nonce 值,该条件为:区块哈希值小于难度目标。
区块哈希值取决于区块头,而区块头中的其他条件是一定的,因此只要寻找到正确的 Nonce 值则可以完成目标。
而如何找到这个 Nonce 值?由 Hash 算法的原理可知,其计算过程是不可逆的,我们可以很容易的找到满足条件的区块哈希值,但是却无法通过该值直接求得 Nonce。
因此挖矿节点能做的则是暴力穷举,不断列举 Nonce 值,然后求得区块哈希值,判断是否满足条件,直到找到满足条件的 Nonce 值。
因此保证了挖矿节点打包一个区块需要做一定的工作量 —— 计算次数,当一个挖矿节点成功打包区块并广播给其他节点之后,对其找到的 Nonce 值(也就是做出的工作量)是很容易验证正确的。
对一个区块的打包需要付出一定次数的 Hash 计算,而对于矿机而言,每秒可以做出的 Hash 计算的次数称为算力。
从硬件角度可以知道,性能越高的设备,其运行计算速度则越快,因此算力则越大。对于同样的难度目标而言,算力越大则表示其计算获得符合条件的 Nonce 的概率越大。
从最早使用 CPU 挖矿到后来的 GPU 挖矿,到目前的 ASIC 矿机,全网挖矿的算力是越来越大。
对于比特币系统而言,其设计为平均每 10 分钟产生一个区块,并保持恒定。因此随着算力的增强,其需要的工作量也会加大,而工作量取决于目标难度,因此目标难度是会自动调节的,全网算力越大,那么目标难度则越大,以保证其区块产生的时间保持恒定。
当区块产生之后会在比特币网络中传播,而在网络中的每个节点接收到该信息的时候都会去验证该区块的合法性,验证的过程仅需要计算区块哈希值并匹配条件,对于没有通过验证的区块,判定该区块为不合法的,因此会被拒绝。
只有付出了一定工作量的节点才能找到正确的 Nonce 值并被认可,因此伪造的区块会被一眼识破并拒绝。对于伪造区块的节点,其非但无法造假,同时也浪费了一次工作量证明的时间,平白消耗了电费。
区块链是去中心化的数据存储结构,因此不同的节点间的副本不一定实时保持一致。节点间信息的同步依赖于 P2P 网络的传播,其必然存在一定的延时性。
当两个挖矿节(A 和 B)点几乎同时挖到了下一个区块时,那么会导致一部分的节点先收到 A 节点的区块信息,一部分节点先收到 B 节点的区块信息,我们将其描述为 A 派和 B 派,它们在各自的区块上以不同的区块作为顶点延长,这就产生了区块链的分叉。
在下一个区块的挖矿中,A 派和 B 派各种在自己的链上计算下一个区块,而一旦其中某一派的节点中挖到了下一个区块,我们假设为 B 派中的某个节点挖到了下一个区块,他会立刻将其广播出去。A 派中的其他节点当然是接收了该区块,而从 B 派节点的视角而言,它们发现该区块的链更长,因此立刻抛弃在原来 A 节点产生的区块上挖矿,转而基于新的更长的区块链上挖矿,因此全网节点在更长的链上重新达成了共识。
区块的分叉是经常出现的,一般来说区块的分叉会在一个区块内解决。而对于分叉的区块再次分叉,其概率是极小的;在 6 个区块确认之后,可以认为该区块是被全网共识且无法篡改的。