比特币

Page content

写这篇文章时,每枚比特币的价格已经高于五万美元。我不懂、也不去讨论其背后的金融原理,但是比特币的技术细节我还是可以探究一二。比特币以“去中心化”闻名,那这么一个没有中心节点的系统是如何运作的,正是本文的主题。

从记账方式说起

在一个古老的部落里,人们还保持着以物易物的习惯。由于这种物物交换过于繁琐,部落首领宣布用贝壳作为交易的助记符,1个贝壳的价值等于1个鸡蛋。并且,首领拥有的100个贝壳是大家都公认的有效贝壳。这时,首领想吃煮鸡蛋了,于是到鸡蛋贩子那儿用5个贝壳换了5个鸡蛋。鸡蛋贩子又找鹅蛋贩子用其中的2个贝壳换了1个鹅蛋。如果我们想记录下来经过这两次交易后各人贝壳数的变化,可以给每个人一个账户,记录账户的变动。

初始状态:

户主 金额
首领 100
鸡蛋贩子 0
鹅蛋贩子 0

交易1后:

户主 金额
首领 95
鸡蛋贩子 5
鹅蛋贩子 0

交易2后:

户主 金额
首领 95
鸡蛋贩子 3
鹅蛋贩子 2

这种记录方式是一种最自然的想法,其实还可以通过记录交易来计算出每个人的贝壳数。

初始状态:

交易ID 输入 输出
0 (首领,100)

交易1后:

交易ID 输入 输出
0 (首领,100)
1 0-0 (鸡蛋贩子,5)(首领,95)

交易2后:

交易ID 输入 输出
0 (首领,100)
1 0-0 (鸡蛋贩子,5)(首领,95)
2 1-0 (鹅蛋贩子,2)(鸡蛋贩子,3)

在上面的表格中,输入即代表贝壳的来源,首领100个贝壳的来源暂时不考虑。在交易1中,输入为0-0,代表交易0的0号输出,即(首领,100);输出为(鸡蛋贩子,5)和(首领,95),也就是说,交易0中首领获得的100个贝壳被分为了鸡蛋贩子的5个和首领的95个。在交易2中,输入为1-0,代表交易1的0号输出,即(鸡蛋贩子,5);输出为(鹅蛋贩子,2)和(鸡蛋贩子,3),也就是说,交易1中鸡蛋贩子获得的5个贝壳被分为鹅蛋贩子的2个和鸡蛋贩子的3个。

可以看到,在这种记账方式中,一个交易的输入来自之前交易的输出,并且还得是没有被花费的输出,这个输出就叫做未花费交易输出(unspent transaction output,UTXO)。那么如何统计一个人还有多少贝壳呢?只要找到他所有的UTXO加起来就好了。

下面再来探讨探讨UTXO的花费问题。当一个人要消费n时,要从他的UTXO中找到一个或多个记录,使其和大于等于n。如果UTXO记录和恰好是n,则输出只有一个接收该笔支付的人;否则,输出中还要有一个消费者本身的地址来接收找零的钱,如交易2所示。

一笔比特币交易

上面的描述以贝壳为例来简化讨论,下面再来看看真实的比特币交易。不过在这之前,还需要介绍一下比特币的交易所依赖的公钥、私钥、地址。

公钥、私钥、地址

比特币系统中的每一个用户都拥有一个或多个私钥,每一个私钥都对应一个公钥,公钥不能逆推出私钥。私钥不能给别人看,公钥是公开的。私钥加密的东西可以用公钥解开,公钥加密的东西可以用私钥解开。

在比特币系统中,假设A要向B付款,实际是向B的公钥(的某一形式)付款,使用一个含有B的公钥的脚本将这笔比特币锁住。假设B要取用这笔UTXO,就带着含有自己公钥和私钥签名的解锁脚本去解锁锁定脚本。

目前使用最多的是向公钥哈希(HASH160)付款,但是这个值不太易读易记易传播,因此将这个哈希值进行base58check编码,就成了比特币的地址。这个编码是可逆的,因此公钥哈希和编码后的地址是等价的。

锁定脚本与解锁脚本

下面来看一个具体交易的例子:

{
  "version": 1,
  "locktime": 0,
  "vin": [
    {
      "txid":"7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
      "vout": 0,
      "scriptSig": "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
      "sequence": 4294967295
    }
 ],
  "vout": [
    {
      "value": 0.01500000,
      "scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
    },
    {
      "value": 0.08450000,
      "scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
    }
  ]
}

在这笔交易中最值得关注的是vin和vout两个字段,分别对应了贝壳问题表格中的输入和输出。在vin中,txid是某笔交易的id,该交易的0号(即vout值)输出是当前这笔交易的输入。scriptSig是解锁脚本,下面会介绍。在vout中有两项,value值代表比特币个数,value值有0.015和0.0845,意味着比特币支付到了两个地址,一个地址为收款地址,另一个为付款人的找零地址。vout中的scriptPubKey是锁定脚本。

一笔交易的比特币来自于输入,转移到输出。那么就有两个问题,一是如何从输入中“拿”,也就是怎么证明这个UTXO是你的;另一个是如何指定输出给谁。scriptSig和scriptPubKey就是来解决这两个问题的。

scriptSig(解锁脚本)和scriptPubKey(锁定脚本)其实是一种堆栈式的脚本语言。锁定脚本规定了要使用这笔输出应满足的条件,解锁脚本则对锁定脚本进行解锁。比特币交易验证引擎先运行解锁脚本,再运行锁定脚本,如果最后得到的结果为真,则成功解锁。

上面例子中的scriptSig和scriptPubKey已经被填入了具体的值,实际上它们的格式如下:

scriptSig -> <sig> <PubK>
scriptPubKey -> DUP HASH160 <PubKHash> EQUALVERIFY CHECKSIG

假设交易1有一个付款给A的输出,交易2中A准备把这笔UTXO付款给B。那么交易2中的scriptSig就是用来解锁交易1的scriptPubkey的。交易2解锁脚本中的<sig>是付款人(即A)使用私钥对当前交易hash的签名,PubK是付款人(即A)的公钥。而交易1锁定脚本中的<PubKHash>则是收款人A公钥的HASH160值。

因为交易验证引擎先运行解锁脚本,再运行锁定脚本,因此实际运行的脚本为:

<sig> <PubK> DUP HASH160 <PubKHash> EQUALVERIFY CHECKSIG

运行的流程如下图所示:

可以看到,在解锁脚本解锁锁定脚本时,锁定脚本主要进行了两个验证。一个是解锁脚本中的PubK经过HASH160等于锁定脚本中的PubKHash;一个是使用PubK对sig解密后的值等于解锁脚本所在交易的hash值。第一个验证的作用是确定来解锁的是当初付款人指定的公钥;第二个验证的作用是证实进行解锁的确实是公钥持有人,而不是其它人拿着该公钥;并且,解密后的内容是解锁脚本所在交易的hash值也进一步指定了该UTXO要去往的交易,防止该交易被篡改。

上面介绍的解锁与锁定方式被称作P2PKH(Pay To Public Key Hash),是使用最广泛的交易方式,更多的交易方式读者可自行搜索。

形成区块链

之前介绍的比特币交易是分隔的、一笔一笔交易。为了确定一个UTXO确实是未被消费的,避免“双花问题”,普通的方式是引入一个中心的权威机构(如银行),在消费后就将金额从从账户中减去。但是比特币是去中心化的,它采用的方式是将每一笔交易都公开,一组交易构成一个块,块与块连接形成链。

一个简化的块如下图所示:

其中,Prev Hash是前驱块块头的hash值,正是凭借这个值块与块之间连了起来。Root Hash是梅克尔树的根节点。(二叉)梅克尔树是这样构成的:计算该块中所有交易的hash值(如图中的Hash0、Hash1、Hash2、Hash3),然后将它们两两组合再计算hash值,直到算出来一个根hash值。Nonce是一个可变值,下面会介绍。

比特币从哪儿来

之前的部分讲到了一组交易形成一个块,那么谁去执行这些交易打包成块的操作呢?在比特币网络中,有一些节点专门去执行打包操作,其实就是俗称的“挖矿”。打包当然不是一个容易活,不然矿工们也不会耗巨资购买专门的设备进行打包。

上面介绍的区块头部进行了简化,实际上区块头部包含了如下部分:

字段 描述
Version 区块版本号
Prev Hash 前驱区块头的哈希值
Merkle Hash 梅克尔树根哈希值
Timestamp 该区块产生的时间
Bits 打包的难度
Nonce 一个可以随意改变的数字

头部中的Bits可以换算成一个难度值,如何换算此处不详谈。

在挖矿节点中,有一个内存池保存着一些待打包的交易,挖矿节点从里面选取一些交易,在计算梅克尔树、填写了头部必要的值后,就开始不断迭代Nonce值,意图找到一个Nonce使得头部的哈希值小于难度值。具体的计算公式如下:

SHA256( SHA256(version + prev_hash + merkle_root + timestamp + bits + nonce ) ) < 难度值

找到这样一个Nonce值需要大量的计算,并且随着技术的发展、算力的提高,难度值会在每产生2016个区块后调整一次,使得每一个区块的产生维持在大约10分钟。矿工进行打包耗费了算力,作为补偿,成功打包的矿工会获得比特币奖励,最开始是打包一个区块获得50个比特币,每4年减半。矿工不仅可以获得成功打包区块的奖励,被打包的交易还会有手续费(也可能没有)支付给矿工。注意,打包奖励的比特币是新发行的,手续费是付款人支付的。可见,比特币发行的方式就是通过奖励成功打包的矿工。

上面这种寻找一个值使得区块头哈希值小于某一难度值的方式称为工作量证明(Proof of Work,PoW),它需要大量的计算,因此使得伪造看上去合理的块十分困难。

有一个问题值得探讨,就是如何保证找到的Nonce不会被其它挖矿节点窃取呢?其实很简单,每一个挖矿节点要打包的交易是不一样的,Nonce并不通用。或者说,至少交易中有一项是不一样的,那就是作为成功打包奖励的交易,它的支付地址是矿工本人。要打包的交易不一样,梅克尔树的根值就不一样,Nonce就不一样。

比特币网络

现在能讨论比特币网络中的诸多节点是如何一起运行的了:

  1. 新的交易向所有节点广播。
  2. 节点收集新的交易放入区块中。
  3. 节点为区块寻找合适的Nonce值。
  4. 当节点找到合适的Nonce值后,向所有节点广播该区块。
  5. 当被广播区块中的所有交易是有效的、且没有双重花费时,节点接受被广播的区块。
  6. 当节点使用被广播区块的区块头哈希作为Prev Hash来创建链中的下一个区块时,意味着它们已经接受了被广播区块。

节点总是认为最长的链是正确的链,并不断耗费算力扩充最长的链。那么当两个正确的区块A和B同时被广播怎么办呢?这时产生了两个同样长的链。比特币网络中的一些节点先收到了A块,还有一些先收到了B块。节点在先收到的区块上接着工作,但是也保存后收到的区块。当其中的一个区块先产生了后继区块后,它所在的链就变的更长了,其它工作在另一区块的节点就会切换到该链上工作。

回收磁盘空间

这不是比特币中的一个重要问题,但是白皮书中提到了,所以提一下。所谓回收磁盘空间,是指假如一笔交易所在块已经成为足够多块的前驱(这意味着这笔交易已经很确定了),就可以把这笔交易的输入来源的交易记录给舍弃掉,因为那个被舍弃掉的交易记录的输出已经不是UTXO了,没有必要再保存它了。一个对区块进行修剪的示例如下图所示:

简化的支付验证

简化的支付验证(Simplified Payment Verification,SPV)就是以一种简单的方式验证已经进行了支付。如果一个节点存储了区块链上的所有信息,它便不需要这种简化的支付验证,因为它的信息足够进行完整的验证。但是,有一些设备性能较低,没有能力存储和处理整个区块链的信息,就需要SPV来验证一笔交易是否已经被支付。在SPV下,设备只需要存储最长链的区块头信息。

SPV的验证流程如下:

  1. 节点从网络中获取最长链的所有区块头信息保存到本地。
  2. 节点从区块链获取待验证支付对应的梅克尔分支(Merkle Branch)。
  3. 根据梅克尔分支,与本地区块头中的梅克尔树比较,定位到对应本次交易的区块头。
  4. 验证该待验证支付存在于区块中。
  5. 根据区块头位置,查看后继区块数,来得知此笔支付已经获得了多少确认。

其中,梅克尔分支就是从某一笔交易对应的哈希值计算得到根哈希值所额外需要的哈希值,如下图所示,交易Tx3的梅克尔分支是Hash2,Hash01。还是以下图为例,上述步骤第四步的验证就是计算Tx3的哈希值Hash3,然后使用Hash3和Hash2计算Hash23,再使用Hash01和Hash23计算根哈希,然后比较该结果与本地对应区块头中的根哈希值是否一致。

可见,SPV主要的验证作用有两个,一是确认交易存在于区块中,二是查看所在区块已经得到多少后继区块的确认。后继区块的确认代表这笔支付不存在双花问题;后继区块越多,代表这笔支付越可靠,不可撤销。

其实,上述步骤的第二步有些逻辑问题,看上去获取梅克尔分支是用待验证支付去查询的;如果该支付没问题,能够查到相应的梅克尔分支,计算根哈希当然也没问题;如果该支付有问题,也许它根本都不存在任何区块中,它怎么能够去查到梅克尔分支呢?我查阅后找到了一个合乎逻辑的解释:其实梅克尔分支不是用待验证支付查询的,而是SPV节点通过设置布隆过滤器来接收支付到目标地址的交易信息,网络中的其它节点以梅克尔块(Merkle Block)的格式将相关信息发送给SPV节点,梅克尔块中包含梅克尔分支信息。

尾声

比特币的介绍到此结束,文章中的资料为多方查阅,难免有纰漏错误,还请读者指正。最后,向对比特币知识进行无私分享的人致以衷心的感谢!

参考

《Bitcoin: A Peer-to-Peer Electronic Cash System》

《白话区块链》 蒋勇 文延 嘉文 著

Mastering Bitcoin - Second Edition by Andreas M. Antonopoulos LLC

https://github.com/tianmingyun/MasterBitcoin2CN

https://btcinformation.org/en/developer-reference#merkleblock

https://en.bitcoin.it/wiki

https://www.liaoxuefeng.com/wiki/1207298049439968/1311929771491361

https://shuwoom.com/?p=692