加密货币
在这一章中,我们将探索区块链技术的原始和最佳实现——加密货币。加密货币不仅仅是一个区块链应用;它利用诸如数字签名之类的密码原语,通过使用称为事务的原子事件来实现资产管理。在本章中,我们将熟悉理解加密货币与任何传统数字货币有何不同所需的所有概念。
在本章中,我们将讨论以下主题:
- 比特币的基础知识
- 钥匙和地址
- 处理
- 挖掘和共识
- 区块链
- 区块链网络
- 创建一个简单的加密货币应用程序
比特币是第一种成功部署在去中心化网络中的加密货币。由于其弹性软件和基础设施、在各个领域的广泛采用以及高市值,它是迄今为止最知名的加密货币。截至 2017 年底,比特币的市值达到 3000 亿美元,是迄今为止所有加密货币中最高的。市场上的大多数加密货币都受到比特币的启发,并使用了与比特币类似的设计。我们将使用比特币来了解加密货币中的大多数相关概念,在本章的稍后部分,我们还将实现一种类似于比特币的加密货币。
加密货币是一种数字资产,它使用加密技术来保护、消费和验证其在交易中的价值。加密货币可以从所有者转移到任何接收者,而不需要中介来结算交易。虽然加密货币的早期采用提供了许多功能,如伪匿名、更低的交易费用和消除对中介的需求,但它从未实现真正的去中心化。有一些众所周知的问题,比如重复支出。这是指一项资产被转让给多个接收方,因为没有集中的来源来核实这些交易。所有这些问题都在 2009 年被称为比特币的完全去中心化的加密货币诞生时得到了解决。通过使用不变的区块链在节点之间达成共识,这第一次解决了分散网络中的双重花费问题。
比特币基础
比特币是一个密码学和分散共识算法的集合,能够创建一个完整的分散数字货币生态系统。
比特币可以像传统货币一样使用。它可以用来买卖商品和服务,或者只是向人们转账。与传统货币相比,比特币有几个优势,比如更低的交易成本和将货币转移到世界任何地方的能力,因为它不受任何国家当局的控制。比特币也完全是虚拟的,这意味着它没有实物形态。比特币的价值是由比特币中的交易产生的。任何人都可以使用交易将比特币转移到特定的比特币地址。比特币合法接收者的地址将通过对应于该地址的密钥来识别。然后,用户可以通过使用秘密密钥构建新的交易,将比特币转移给其他人。通常,使用公钥创建比特币地址,私钥是公钥的私钥对应物。密钥通常存储在一个叫做钱包的软件应用程序中,但是如果我们需要更好的安全性,它们也可以备份并存储在任何地方。
众所周知,比特币系统为区块链的发明铺平了道路。它利用我们迄今为止讨论的所有概念来构建一种加密货币,它在一个完全分散的点对点 ( P2P )系统中运行。由于比特币是一个完全去中心化的网络,因此不需要一个中央可信机构(如银行)来充当仲裁者并验证交易。相反,比特币生态系统中的每个人都参与确保有效交易的发生。
比特币软件是开源的,任何人都可以通过在智能手机或电脑等设备上运行该软件来加入比特币网络。比特币软件的简化版可以在计算和存储能力有限的设备上使用。有一种特殊类型的节点称为挖掘器,它使用处理能力来验证交易,并通过解决硬加密难题来创建块。这是一个哈希难题,更具体地说叫做工作共识算法证明,在第三章、【区块链中的 T2】密码学中讨论过。每 10 分钟,一个矿工可以发布一个有效块,然后由比特币网络上的每个人进行传播和验证。矿工因创造区块所耗费的计算能力而获得比特币奖励。由于采矿竞争的增加,拼图的难度已经调整,因此平均创建时间保持在 10 分钟左右。
因此,每当矿工创造一个新的区块,新的比特币就会被铸造出来,在比特币网络中流通。网络中流通的比特币总数是有限制的,上限是 2100 万个。
总而言之,以下创新帮助比特币在一个完全不可信的网络中生存下来:
- 一个分散的 P2P 网络
- 区块链(公共分类账)
- 分散共识算法(工作证明)
- 交易验证规则
在本章中,我们将试图解释比特币是如何使用这些概念的,这些概念使其创造成为可能。
比特币核心入门
比特币是一种实验性的数字货币,由开源社区维护。比特币核心是允许使用这种货币的开源软件的名称。这是比特币系统的最初实现,最初的发行是由中本聪创建的。
开源软件是其源代码对公众开放的软件,公众有权阅读、修改和重新发布。尽管开源代码可以被不同的许可所覆盖,但是大部分都可以免费用于任何目的。比特币是在麻省理工学院许可证下授权的。
设置比特币全节点
比特币完整节点可以出于开发目的而设置,或者只是为了让用户成为比特币网络的一部分,以便验证或探索交易。如果用户想要建立一个完整的开发环境,他们必须建立他们可能需要的所有工具、库和相关应用程序,而比特币节点可以通过安装软件而无需太多努力即可建立。
安装比特币全节点
如前所述,安装一个比特币全节点比设置一个开发环境简单得多。比特币全节点非常适合那些希望成为比特币网络一部分但不想担心其任何实现的用户。
运行比特币全节点有一定的硬件要求。它需要专用存储,因为它必须存储公共分类账的所有块。在撰写本文时,比特币区块链区块占用了大约 180 GB 的存储空间。一个比特币完整节点还需要相当大的内存和处理能力,以便验证每个区块的交易。比特币可以很容易地安装在 Linux、macOS 和 Windows 平台上。
我们将不在这里提供关于安装的细节,因为它因平台而异。你可以在本书的 GitHub 资源库(https://GitHub . com/packt publishing/Foundations-of-block chain)中找到不同平台的安装细节。此外,您可以在 https://bitcoin.org/en/full-node 的找到所有平台的安装说明。
从源代码编译
通过编译从比特币储存库获得的源代码来建立比特币开发环境。比特币核心的源代码托管在 MIT 许可下的 GitHub 存储库中。您可以克隆并获取所有分支,或者下载特定版本的 ZIP 文件。
您可以使用 Git 工具从https://github.com/bitcoin/bitcoin.git存储库中克隆比特币核心项目。一旦项目被克隆,您就可以使用最新的主代码或者使用 Git 标签签出到任何版本。
根据系统的硬件配置,构建过程可能需要一个小时。编译源代码只需要几个步骤,但它们非常耗时:
- 作为第一步,比特币核心需要你运行一个名为
autogen.sh
的脚本,它会创建一组自动配置脚本来检查系统,并确保你的系统拥有所有的库来编译代码。shell 脚本执行如下:
$ ./autogen.sh
- 下一步是使用配置脚本,通过启用或禁用某些特性来自定义构建过程。我们可以使用默认功能构建比特币核心,因为设置节点需要比特币核心的大部分功能。配置脚本执行如下:
$ ./configure
- 最后,编译源代码以创建可执行文件并安装创建的可执行文件。这是使用以下命令实现的:
$ make
$ make install
的安装将创建一个名为bitcoind
的二进制文件,用于创建比特币守护进程,以及一个名为bitcoin-cli
的命令行工具,用于调用比特币 API 与本地比特币节点进行通信。
运行比特币节点
当通过创建配置文件来执行bitcoind
时,比特币守护进程被创建。基本配置文件由 JSON-RPC 接口的用户名和密码组成。运行到比特币节点时,可以指定几个选项来改变其行为。这些选项也可以在配置文件中指定。执行bitcoind --help
列出可用选项。bitcoind
可以在配置文件创建后执行:
$ bitcoind -daemon
这将作为后台进程启动比特币核心。加载块索引和验证块可能需要几分钟时间。一旦创建了bitcoind
流程,就可以使用bitcoin-cli
工具来检查状态。
以下命令显示了关于比特币核心的一些基本信息以及一些本地区块链信息。该 API 是在当时开采了 519,993 个区块的mainnet
链上调用的:
$ bitcoin-cli getblockchaininfo
{
"chain": "main",
"blocks": 519993,
"headers": 519993,
"bestblockhash": "0000000000000000000d4715ff499c5ce23c4b355634d4b59a2fe3823387dd12",
"difficulty": 3839316899029.672,
"mediantime": 1524733692,
"verificationprogress": 0.999994783377989,
"chainwork": "0000000000000000000000000000000000000000019897317fc702c4837762b2",
"pruned": false,
...
}
比特币有几个由比特币节点组成的区块链网络。这些网络中的每一个都维护不同的区块链。比特币的主网络名为mainnet
,它有一个名为testnet
的测试网络。本章的区块链和网络部分涵盖了关于这个主题的更多信息。
与比特币节点通信
比特币核心通过 JSON-RPC 接口提供 API,方便与比特币节点的通信。大多数共识、钱包和 P2P 比特币操作都可以在使用该接口的节点上执行。比特币使用8332
作为 mainnet 的默认 JSON-RPC 服务器端口。用户应该确保不允许任意机器访问 JSON-RPC 端口。暴露该接口将允许外部机器访问私人信息,这可能导致盗窃。
比特币有一个命令行接口工具bitcoin-cli
,可以用来访问所有的 JSON-RPC API。
通过指定事务 ID,可以使用getrawtransaction
RPC 命令获取任何事务细节:
$ bitcoin-cli getrawtransaction
4289bf1e7a4295e75fcff0644c44bd1c114511b7ec5407afea64de2d280bddb802000000010e1bd74a37fa90e5e8de8e4c20ec42a26c70ef40330b5361c560d03f3c8ba7e9000000006a47304402201a62b24dcbeba9ec65478be8a12ccd31c3c984
9813782d1ca0bcab657a88762402204897f9c9e5e99de969fd5d076d80aebbaef19493f5e273663d4727864a67295b012102b21f43b03f57e029ea43f2cec448d4ff43740af4a68607507f34fd93be97bc30feffffff028096980000000000197
6a914523f63d0e9f8cb9519482fc6a8476689e57555e688ac59065701000000001976a91469cac07f09af880832eedbcbc7e0dea94fb68e2688acb1bb1300
输出是十六进制格式的序列化事务。然后可以使用decoderawtransaction
API 对这些数据进行解码:
$ bitcoin-cli decoderawtransaction
02000000010e1bd74a37fa90e5e8de8e4c20ec42a26c70ef40330b5361c560d03f3c8ba7e9000000006a47304402201a62b24dcbeba9ec65478be8a12ccd31c3c9849813782d1ca0bcab657a88762402204897f9c9e5e99de969fd5d076d80aebbaef19493f5e273663d4727864a67295b012102b21f43b03f57e029ea43f2cec448d4ff43740af4a68607507f34fd93be97bc30feffffff0280969800000000001976a914523f63d0e9f8cb9519482fc6a8476689e57555e688ac59065701000000001976a91469cac07f09af880832eedbcbc7e0dea94fb68e2688acb1bb1300
这将生成一个人类可读的 JSON 格式的解码事务。我们将在本章的事务部分讨论解码后的事务。
通过 JSON-RPC 实现使用脚本语言进行通信
任何编程语言的 JSON-RPC 实现都可以用来与比特币节点通信。让我们使用 Python JSON-RPC 实现来执行一个 API,它将自动为 RPC 调用生成所有 Python 方法。Python 有几个支持 JSON-RPC 的库,但我们将使用python-bitcoinlib
,它不仅提供 JSON-RPC 实现,还可以与比特币数据结构和协议接口。以下 Python 脚本接受十六进制格式的事务 ID,并使用lx()
函数将其转换为原始字节。创建了一个可以用来调用任何 API 的 RPC 对象proxy_connection
。gettransaction
API 将获取所提供的事务 ID 的解码事务:
import bitcoin.rpc
from bitcoin.core import lx
bitcoin.SelectParams('testnet')
proxy_connection = bitcoin.rpc.Proxy()
tx_id = lx(input())
print(proxy_connection.gettransaction(tx_id))
钥匙和地址
我们已经涵盖了理解加密货币中使用的密钥、地址和钱包所需的所有密码学概念。在这一节中,我们将深入了解如何通过密码原语使用密钥和地址来控制资金的所有权。
我们已经介绍了如何使用非对称加密技术来创建公钥/私钥,它们可以识别区块链网络中的用户帐户。比特币生成公钥/私钥对,可识别用户身份,并帮助他们通过数字签名主张资金所有权。私钥也称为加密货币中的秘密密钥,因为它对公众保密。数字签名是密码学中的一个流行概念,它允许密钥的所有者为交易创建签名,并允许任何人验证交易。密钥所有者使用称为钱包的轻量级软件来管理他们所有的密钥。密钥独立于区块链协议,由钱包创建和管理。
比特币使用地址来识别比特币用户。这些地址是从用户的秘密密钥导出的公共密钥的编码版本。让我们考虑一个银行业的例子:Alice 通过签署一张写给 Bob 的支票将钱转给 Bob。比特币使用类似的方法,使用密钥签署交易,并提供接收者的账号,即比特币公共地址。唯一公开的信息是账号,这在概念上类似于把比特币地址公开。
公钥和私钥
公钥和私钥是使用非对称加密生成的。私钥保存在用户的钱包里,而公钥以比特币地址的形式公开。比特币使用椭圆曲线加密技术来生成公钥/私钥对。私钥是随机选择的,并且执行椭圆曲线乘法来生成公钥。椭圆曲线乘法是一种单向加密函数,它使得不可能从公开的公钥中导出私钥。你可以在第二章、一点密码学中探索椭圆曲线密码的数学解释和分析。
比特币的私钥是随机选取的 256 位字符串或 64 位十六进制字符串。这意味着私钥可以是 1 和 2 256 之间的任何数字。所以,仅仅通过暴力破解 2 256 个组合是不可能找到公钥对应的私钥的。随机生成的私钥与比特币网络隔离,秘密保存在比特币钱包中。
比特币的命令行界面可以用来生成密钥。比特币有一套处理密钥和地址的 API。以下命令在本地 wallet 上执行。dumprivkey
获取已经存在的私钥,该私钥是从公钥生成的:
$ bitcoin-cli getnewaddress
1JK1yCXbP2WkwgzbAUqpWTeo9rQkA9seNg
$ bitcoin-cli dumpprivkey 1JK1yCXbP2WkwgzbAUqpWTeo9rQkA9seNg
L2NAKvQsbkeQZyhfPRWw1juQ19ohxGCFbdr8izQSHEmKWYFtVjXK
比特币公共地址
比特币中的公共地址是由公钥产生的,公钥由以 1 开头的数字组成,对于 mainnet 来说是以 3 开头的数字。大多数比特币地址长度为 33 或 34 个字符,使用 Base58 编码。比特币公钥地址始终代表私钥的所有者,用于交易的接收方领域。然而,地址也可以有不同的用途,例如表示在Pay-to-Script-Hash(P2SH)交易中使用的支付脚本,这将在本章的交易部分中介绍。
比特币地址是通过构造一个名为 Base58Check 的编码字符串从公钥中导出的。Base58Check 是一个 Base58 编码的字符串,带有固定字符,用作错误检查代码。代表比特币地址的 Base58Check 编码字符串有三个部分——前缀版本字节、源自公钥的有效载荷和校验和。版本字节代表比特币地址的类型。表 5.1 显示了在比特币地址中发现的各种前缀:
| 前缀(Base58) | 使用 | | one | 比特币公钥哈希(P2PKH 地址) | | three | 比特币脚本哈希(P2SH 地址) | | five | 私钥(WIF) | | m 或 n | Bitcoin 测试和 pubkey hash | | 2 | Bitcoin 测试和散列脚本 |
表 5.1:用作版本字节的前缀(来源:https://en.bitcoin.it)
比特币公共地址的第二部分是通过使用SHA256
和RIPEMD160
哈希算法对公钥进行哈希运算而得到的。我们已经知道,哈希函数是单向函数,这使得从计算的比特币地址中导出公钥是不可行的。
在下面的函数中,K
是从私钥导出的公钥,H
是比特币公钥 hash:
H = RIPEMD160(SHA256(K))
在使用散列函数构造散列值H
之后,地址的第三部分,即校验和,从结果值中计算出来。最后,使用 Base58 编码对所有三个部分进行连接和编码,使用 58 个字符(请参考第二章、一点密码术,了解有关 Base58 编码的更多详细信息)创建 Base58Check 格式的比特币公共地址,如前所述:
图 5.1:从公钥生成比特币公共地址
图 5.1 显示了一个 64 字节的公钥和一个 1 字节的公钥最初是如何被散列以产生一个 20 字节的摘要的。这是有效载荷,然后对其进行编码以生成比特币地址。但最终地址也由校验和位组成,用于防止出错。这个 4 字节校验和的计算方法是,使用 SHA256 哈希函数对有效负载进行两次哈希处理,并从得到的 32 字节摘要中提取最初的 4 个字节。
该校验和与有效载荷和版本字节连接在一起。最后,使用 Base58 编码系统对结果字符串进行编码,以生成比特币地址,该地址将识别持有相应密钥的用户。
众所周知,任何人都不可能从公开的比特币地址生成私钥。这是由于用于导出公钥的函数。公钥是使用椭圆曲线乘法创建的,椭圆曲线乘法是一种单向函数。类似地,比特币地址是通过应用哈希函数从公钥中导出的,哈希函数本质上是单向函数。这也防止任何人从比特币公共地址生成公钥:
图 5.2:比特币地址生成的单向功能
图 5.2 描述了生成比特币地址时使用的单向函数。虽然代表持有私钥的实体的身份的比特币地址为公众所知,但由于这些功能的单向性质,无法检索私钥。
处理
我们已经介绍了比特币中使用的许多概念,这些概念有助于在分散式网络中创建加密货币。虽然每个概念在比特币中都有重要作用,但交易起着核心作用。比特币的其他一切都是为了确保有效的交易安全地包含在区块链中,并传播到整个节点网络。
与使用基于账户的分类账的传统簿记不同,比特币维护基于交易的分类账。进入比特币的每一笔交易都必须经过验证,才能被纳入区块链。比特币节点指的是其他区块或交易内存池中包含的交易 来验证每一笔交易。在这一节中,我们将深入探讨事务创建、验证和事务组件等概念。
交易内存池是由每个比特币节点维护的未确认交易的集合。mempool 中的事务最终将包含在区块链中。比特币内存池的实时视图可以在https://www.blockchain.com/btc/unconfirmed-transactions观察到。
高层次的交易
在深入交易及其组件的底层细节之前,我们先来看一个例子,从用户的角度说明一个简单的比特币交易。假设爱丽丝从自己的比特币钱包里发了 0.1 个比特币到鲍勃的钱包里。由于她的钱包中已经有超过 0.1 的比特币,因此将创建一个有效的交易并传播到网络。图 5.3 显示了 block explorer 应用程序中的交易详情:
图 5.3:从爱丽丝到鲍勃的比特币交易
本部分显示的所有交易细节都是使用比特币的testnet
区块链创建的。您可以通过将比特币节点切换到 testnet 区块链来检查交易细节。block explorer 应用程序也在 testnet 区块链中运行。你可以在https://testnet.blockexplorer.com核实交易。
有权访问区块链的每个人都可以看到交易。必须使用十六进制编辑器来读取事务,因为它们不是以人类可读的形式存储的。比特币核心的命令行界面可以读取原始交易并解码:
{ "txid": "4289bf1e7a4295e75fcff0644c44bd1c114511b7ec5407afea64de2d280bddb8", "hash": "4289bf1e7a4295e75fcff0644c44bd1c114511b7ec5407afea64de2d280bddb8", "version": 2, "size": 225, "vsize": 225,
"locktime": 1293233,
"vin": [
{
"txid": "e9a78b3c3fd060c561530b3340ef706ca242ec204c8edee8e590fa374ad71b0e",
"vout": 0,
"scriptSig": {
"asm": "304402201a62b24dcbeba9ec65478be8a12ccd31c3c9849813782d1ca0bcab657a88762402204897f9c9e5e99de969fd5d076d80aebbaef19493f5e273663d4727864a67295b[ALL] 02b21f43b03f57e029ea43f2cec448d4ff43740af4a68607507f34fd93be97bc30",
"hex": "47304402201a62b24dcbeba9ec65478be8a12ccd31c3c
9849813782d1ca0bcab657a88762402204897f9c9e5e99de969fd5d076d80aebbaef19493f5e273663d4727864a67295b012102b21f43b03f57e029ea43f2cec448d4ff43740af4a68607507f34fd93be97bc30"
},
"sequence": 4294967294
}
],
"vout": [
{
"value": 0.10000000,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 523f63d0e9f8cb9519482fc6a8476689e57555e6 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914523f63d0e9f8cb9519482fc6a8476689e57555e688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"mo1qeC6G2cLZqeBiSeLgKL2z1QcvsG9Mu3"
]
}
},
{
"value": 0.22480473,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 69cac07f09af880832eedbcbc7e0dea94fb68e26 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a91469cac07f09af880832eedbcbc7e0dea94fb68e2688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"mqAL8eXgSE7tHGF9fdYhYnwMFW3nnNyH9z"
]
}
}
]
}
getrawtransaction
API 用于检索编码的事务。可以用decoderawtransaction
解码。这些命令可以使用比特币的命令行界面或任何 RPC 客户端调用。
原始事务的可读版本有许多字段,乍一看可能让人不知所措。为了理解原始事务,我们将在下一节中介绍事务的一些组件。
交易输入和输出
事务主要由输入和输出构成。每个事务可以有多个输入和输出。与基于账户的记账不同,比特币需要记录每笔交易的产出。一个节点需要有所有的交易输出信息,以便知道一个帐户的可消费余额。每当用户想要使用他们的加密货币时,可以在交易的输入中引用该输出。这种产出由不可分割的货币块组成,只有在交易中被消费后才能被分解。没有在任何事务输入中引用的输出被称为未用事务输出或 UTXO 。
每当用户想要使用 UTXO 时,它必须被完全使用。在消费一个 UTXO 后,任何超出的部分都会作为另一个 UTXO 返还给用户。这类似于真实世界的货币,其中硬币不能被分解成更低的值,并且为支付的超出金额接收零钱。让我们考虑一下我们之前的例子,爱丽丝给鲍勃发送 0.1 个比特币。正如我们在图 5.3 中看到的,Alice 没有值为 0.1 的 UTXO。所以,Alice 花费 0.325 的事务输出,大于 0.1。在向 Bob 发送 0.1 个比特币后,剩余的金额被发送回 Alice,创建一个新的 UTXO。从交易中我们可以看到,爱丽丝从她的账户中拿回了略少于 0.225 比特币。
这是由于将交易插入区块链时征收的交易费。该费用将支付给履行工作证明的矿工。这个交易创造了两个输出值,0.1 和~0.225。这两个输出值必须全部被其他输入所消耗。
交易输出
如前一节所述,每个事务都创建输出,这些输出可以在以后被事务输入使用。每个完整节点客户机跟踪所有的 UTXO,以便通过检查 UTXO 池可以很容易地验证每个事务输入。
让我们调查一下 Alice 和 Bob 之间的早期事务的输出。使用vout
键引用交易输出:
"vout": [
{
"value": 0.1,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 523f63d0e9f8cb9519482fc6a8476689e57555e6 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914523f63d0e9f8cb9519482fc6a8476689e57555e688ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"mo1qeC6G2cLZqeBiSeLgKL2z1QcvsG9Mu3"
]
}
}
]
前面的vout
是事务的输出之一。每个输出有两个主要组成部分,值和密码条件,它们解释了谁拥有该事务。在前面的交易中,输出值表示转给 Bob 的比特币金额,scriptPubKey
是密码条件(或锁定脚本),保证输出金额只能由 Bob 花费。scriptPubKey
有几个字段包含序列化(hex
)和反序列化(asm
)格式的锁定脚本。它还提供了一些额外的信息,比如所需的签名(reqSigs
)、类型以及收件人的公共地址。虽然事务输出有几个字段,但是事务只对锁定脚本感兴趣,其他字段可以从它派生出来。大多数锁定脚本都是用户公共地址的简单表示。我们将在本章的后面研究锁定脚本。
交易输入
每当用户希望进行比特币交易时,交易输入都会引用 UTXOs。事务输入使用解锁脚本来声明 UTXO。在这个解锁脚本的帮助下,有效的事务输入证明了 UTXO 的所有权。
一个事务输入可以有多个指向多个 UTXOs 的输入。事务输入确保有足够的 UTXOs 来使事务发生。在前面的例子中,事务输入只有一个指向 UTXO 的输入:
"vin": [
{
"txid": "e9a78b3c3fd060c561530b3340ef706ca242ec204c8edee8e590fa374ad71b0e",
"vout": 0,
"scriptSig": {
"asm": "304402201a62b24dcbeba9ec65478be8a12ccd31c3c9849813782d1ca0bcab657a88762402204897f9c9e5e99de969fd5d076d80aebbaef19493f5e273663d4727864a67295b[ALL] 02b21f43b03f57e029ea43f2cec448d4ff43740af4a68607507f34fd93be97bc30",
"hex": "47304402201a62b24dcbeba9ec65478be8a12
ccd31c3c9849813782d1ca0bcab657a88762402204897f9c9e5e99de969fd5d076d80aebbaef19493f5e273663d4727864a67295b012102b21f43b03f57e029ea43f2cec448d4ff43740af4a68607507f34fd93be97bc30"
},
"sequence": 4294967294
}
],
一个 UTXO 就足以完成交易。图 5.3 显示这个 UTXO 的值为 0.325,这足以向 Bob 发送 0.1 的值。通过使用事务 ID ( txid
)和创建该 UTXO 的事务的序列号,事务输入指向 UTXO。与事务输出一样,事务输入包含一个解锁脚本,该脚本证明用户对 UTXO 的声明,并确保事务有效。消费者最初检索 UTXO,并使用事务 ID 引用它。使用解锁资金所需的秘密信息创建解锁脚本。一个简单的解锁脚本将具有用私钥和相应的公钥签名的数字签名。然而,表示可能很复杂,但这是我们将在下一节更详细讨论的内容。
在示例中,Alice 使用txid
指向事务中可消费的 UTXO。然后,Alice 创建一个解锁脚本,并将其放在事务的scriptSig
字段中。每个获得该事务的人都将通过检查 UTXO 中的锁定脚本来验证它。
如前所述,在爱丽丝打算转给鲍勃的 0.1 比特币之外,还有一笔交易费需要她支付。但是,在原始交易结构中没有交易费字段。它的计算方法是检查所有被引用的 UTXOs 的值,然后从事务中的事务输入值中减去该值。交易输出中没有跟踪的这一附加值构成了交易费。每个矿工将计算每笔交易的费用,并在一个特殊的 coinbase 交易中将合并后的价值奖励给自己。我们将在本章的挖掘和共识一节中详细介绍 coinbase 事务。
交易验证
使用解锁脚本和锁定脚本来执行交易的验证。比特币使用一种简单的自定义脚本语言,称为 Script,类似于基于堆栈的执行语言 Forth。为了验证事务,输入中的解锁脚本与其对应的锁定脚本一起执行。基于堆栈的执行应该返回真值并成功执行解锁脚本。
在这个例子中,输入的scriptSig
和被引用的输出的scriptPubKey
被求值(按这个顺序),其中scriptPubKey
使用scriptSig
留在堆栈上的值。如果scriptPubKey
返回真,则输入被授权。
脚本
在试图理解锁定和解锁脚本之前,我们需要理解脚本语言的基本执行。Script 很简单,是一种基于堆栈的语言,从左到右进行处理。它故意不是图灵完全的。除了条件之外,它没有复杂的控制流,比如循环。这确保了程序在可预测的时间内执行。这样做是为了避免造成无限执行循环的拒绝服务攻击。脚本语言的简单性确保了比特币不容易受到任何这种性质的攻击。
脚本也是无状态的,这意味着在执行之前或之后没有存储任何信息。这确保了执行不受系统的任何其他方面的影响,并且脚本可以在任何系统上执行。
脚本是一种基于堆栈的语言,因为它在执行过程中使用堆栈数据结构。堆栈数据结构对数据项执行推入和弹出操作。Push 操作将一个项添加到堆栈的顶部,pop 操作移除最后插入的项。脚本通过从左到右处理项目来执行程序。每当遇到数据项时,它们就被推到堆栈中。
运算符从堆栈中弹出一项并对其执行操作,然后将结果推回堆栈。脚本有一组庞大的操作符,它们由可以在项目上使用的操作码来表示。算术操作码如OP_ADD
对顶部两项执行加法,而条件操作码OP_EQUAL
计算一个条件,产生一个布尔结果。比特币交易脚本主要由一个条件运算符组成,如果交易被视为有效,其最终结果必须评估为真值。
脚本示例
让我们举一个简单的例子来执行这个脚本:
2 4 OP_ADD 6 OP_EQUAL
脚本的执行从左侧开始。一旦执行开始,数据常量2
和4
就被插入堆栈。接下来,脚本执行一个加法操作,由OP_ADD
操作符表示。在弹出栈顶的两个项目后,对它们执行加法操作,因此 2 + 4 = 6 。结果被推回堆栈。当遇到数据常量6
时,将其压入堆栈。最后,对堆栈项目执行条件OP_EQUAL
操作。从堆栈中弹出最后两项,并比较它们是否相等。由于堆栈中的最后两个数据项是6
,等式条件将返回一个TRUE
值。
锁定和解锁脚本
比特币使用类似的脚本执行方法,但有一套不同的操作码。比特币交易使用锁定和解锁脚本,它们一起执行以验证交易。如前所述,锁定脚本是在事务输出中指定的支出条件,当两个脚本一起执行时,解锁脚本满足该条件。
我们可以通过分解前面的脚本示例来创建一个简单的锁定和解锁脚本。该脚本的一部分可以形成一个锁定脚本,如下所示:
4 OP_ADD 6 OP_EQUAL
这只能通过解锁脚本来满足:
2
任何节点都将通过依次组合和执行锁定和解锁脚本来验证这些脚本,如前面的示例所示:
2 4 OP_ADD 6 OP_EQUAL
但这些都是基本的脚本,任何具有基本算术技能的人都可以创建一个解锁脚本来使用交易输出。这就是为什么比特币使用一个带有密码难题的复杂条件作为锁定脚本。只有拥有私钥的合法所有者才能够通过用解锁脚本创建证明来花费资金。
比特币中的锁定脚本被称为scriptPubKey
,如在交易一节中的早期交易示例所示。这是因为在锁定脚本中使用了公钥散列(比特币地址)来向相应私钥的所有者转移资金。类似地,可以在事务输入的scriptSig
字段中找到解锁脚本。解锁脚本通常通过创建数字签名来证明与公钥相对应的私钥的所有权。这就是解锁脚本通常被称为scriptSig
的原因。
与示例类似,比特币交易验证器通过执行组合锁定和解锁脚本来验证交易。验证器检索事务输入引用的 UTXO,并将锁定和解锁脚本并排放置,以便顺序执行。
交易脚本的类型
比特币目前创造了两种不同的基本scriptSig
/ scriptPubKey
对。比特币交易中很少使用复杂的交易脚本。Pay-to-pubkey Hash(P2PKH)和Pay-to-Script-Hash(P2SH)是最流行的脚本。比特币网络中执行的大多数脚本都使用 P2PKH 作为其交易脚本。
P2PKH
比特币交易使用名为 P2PKH 的公钥哈希创建输出交易。公钥哈希表示用户持有的相应私钥的比特币公共地址。用户使用收件人的公共地址创建一个锁定脚本。除了相应私钥的持有者之外,没有人能够要求事务输出。
在我们前面的例子中,Alice 创建了包含scriptSig
的事务输入和包含scriptPubKey
的事务输出。
以下脚本显示了scriptPubKey
和scriptSig
的语法:
scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY
OP_CHECKSIG
scriptSig: <sig> <pubKey>
验证程序将组合这些脚本,并按顺序执行它们:
<sig> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY
OP_CHECKSIG
挥霍者记录的签名(sig
)和公钥(pubKey
)被推入堆栈。OP_DUP
操作符复制了堆栈中的pubKey
。下一个操作符OP_HASH160
使用SHA256
和RIPEMD160
算法计算公钥的 160 位散列值:
RIPEMD160(SHA256(pubKey))
锁定脚本中的pubKeyHash
值被推入堆栈。OP_EQUALVERIFY
操作符验证从解锁脚本创建的公钥散列和先前被推入堆栈的pubKeyHash
值是否相等。如果锁定和解锁脚本的公钥散列匹配,它将返回一个TRUE
值。最后,OP_CHECKSIG
从堆栈中弹出sig
和pubKey
,进行数字签名验证,验证签名有效。一旦验证成功,脚本返回一个TRUE
值,表示解锁脚本有效。
P2SH
P2SH 是由早期比特币开发者之一 Gavin Andresen 推出的。根据 Gavin Andresen 的说法,创建 P2SH 是有目的的:
“将提供交易赎回条件的责任从资金发送方转移到赎回方。”安德烈森
在 P2SH 中,资金被寻址到脚本的散列,而不是公共地址。该脚本被称为赎回脚本,它包含了花费资金所需的所有条件。正如 Gavin Andresen 提到的,交易的创建者不必担心花费资金的条件,只需要提到包含条件的脚本的散列。当需要花费资金时,赎回者应该提供与提到的脚本哈希匹配的脚本,并确保脚本评估为 true。
P2SH 为复杂的交易提供了一种方式,不像 P2PKH 对scriptPubKey
和scriptSig
有明确的定义。该规范对脚本没有任何限制,因此绝对可以使用这些地址资助任何脚本。脚本的概念类似于智能合约,将在第七章、潜入区块链——所有权证明中涉及。
挖掘和共识
比特币挖矿是在分散的比特币网络中就区块链的状态达成共识的一个至关重要的概念。比特币网络中的任何节点都可以执行挖掘操作,这些节点因其对挖掘的贡献而获得奖励。这导致了采矿和激励之间的混淆。虽然激励是采矿的一部分,但这不是采矿的唯一目的。挖掘是支撑比特币网络去中心化的一种机制。它通过构造一个被所有人接受的区块链来帮助在一个不可信网络中的节点之间达成共识。
像任何其他比特币节点一样,矿工也验证新的交易,并将它们存储在本地的内存池中。除了执行验证之外,矿工创建交易块并解决散列难题,以将创建的块包括在全球比特币分类账中。一旦创建的区块被纳入区块链,矿工将获得两种类型的奖励:每次交易的交易费和每个区块中新创建的比特币。交易费是对每笔交易的处理收取的费用,该费用由交易的创建者附加。每个区块都有一个特殊的交易,可以创建新的比特币,并将它们奖励给负责创建该区块的矿工。这种特殊的交易称为 coinbase 交易,负责创造新的比特币。
由于可以创造的比特币总数有一个硬上限(2100 万),在未来的某个时间点,矿工只会从交易费中获得奖励。每创建 210,000 个区块,矿工可获得的新创建比特币的最大数量减半。由于每个块的创建时间保持在 10 分钟左右,因此每四年创建 210,000 个块。这项激励措施始于 2009 年比特币推出时的每块 50 个比特币,后来在 2012 年减半,并在 2016 年再次减半。
目前,矿工每创造一个区块可获得 12.5 个新铸造的比特币奖励:
图 5.4:截至 2018 年的历年比特币货币供应量(来源:www.blockchain.info)
图 5.4 显示了一段时间内比特币的供应量。由于每个区块的比特币奖励减半,曲线呈几何级数下降。到 2140 年,比特币网络可能会供应 2100 万枚比特币。当接近 2040 年时,图中的线将几乎平行于 x 轴。
开采一个区块
比特币网络中的任何节点都可以创建一个区块,并自称为矿工。矿工必须运行一个比特币节点来维护完整的区块链。虽然挖掘操作可以在最低硬件要求下进行,但由于比特币节点之间的高度竞争,在具有最低硬件配置的独立计算硬件上进行挖掘不再有利可图。这就是为什么比特币矿工经常运行处理能力更高的专门计算机硬件,如 GPU。
由于矿工竞争加剧,比特币的开采难度增加。矿工们开始建造专门为采矿目的设计的专用集成电路。这些专用集成电路被称为专用集成电路(ASIC),它们不能用于通用计算。有几家比特币专用集成电路制造商生产不同容量的专用集成电路。比特大陆的 Antminer 设备是应用最广泛的专用集成电路。
每个矿工节点还将监听比特币网络的新交易和区块。他们执行了几项任务,然后得出结论,他们已经成功开采了一个新区块:
- 交易的验证
- 将事务聚合到块中
- 使用工作证明算法挖掘区块
交易的验证
正如我们所看到的,每个有效的事务都是通过收集 UTXOs 并使用适当的脚本解锁来创建的,然后使用锁定脚本为下一个所有者锁定资金。交易被广播到比特币网络,这样每个人都知道区块链的最新状态。广播事务还可以确保事务到达一个 miner 节点,并包含在所创建的任何块中。
虽然事务在被广播到网络之前被验证,但是 miner 节点总是在将它包括在块中之前验证每一个事务。一次无效交易就可能导致整个区块被比特币网络拒绝。为了防止不必要的损失,挖掘器总是确保块中只包含有效的事务。
将事务聚合到一个块中
就像比特币网络中的任何完整节点一样,一个矿工积累了它收到的所有交易,并将它们添加到本地内存池中。采矿者将开始构建候选区块,通过在该区块中包括一组交易,可以将该候选区块插入到区块链中。该节点将确保在块构造过程中任何时候新的块到达,新到达的块中的所有事务都应该从候选块中省略,因为这将产生重复的事务。
一旦为块创建了块的所有元数据和事务,挖掘器就通过执行工作证明来解决散列难题。一旦成功地为其创建了工作证明,块就被广播到比特币网络。
比特币基地交易
比特币区块链中创建的每个区块都有一种特殊的交易类型,即区块的第一次交易。如前所述,这种交易是由矿工创建的,矿工用从交易费和新创建的比特币中获得的激励来奖励自己。
这里是比特币 mainnet block 520,956 的 coinbase 交易。解码该块的第一个事务将给出这些细节:
$ bitcoin-cli getrawtransaction fc72760e6339eb43111034d76e67ffce69f9f3a4a5aa53f29dfe7299623bbba8
{
"txid":
"fc72760e6339eb43111034d76e67ffce69f9f3a4a5aa53f29dfe7299623bbba8",
"hash":
"d06aecb12c942dfd059b0d6ef7fbb76f8310fb2cfd4159984c8dce32d3f94b8f",
"version": 2,
"size": 243,
"vsize": 216,
"locktime": 0,
"vin": [
{
"coinbase":
"03fcf20704ff5bea5a622f4254432e434f4d2ffabe6d6dcc95de16874f4618351fc3946c8590509f1c5b0ac2ce802d71786eaef2f0da4301000000000000006e9694143ddc55d500000000",
"sequence": 4294967295
}
],
"vout": [
{
"value": 12.59334356,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160
78ce48f88c94df3762da89dc8498205373a8ce6f OP_EQUALVERIFY
OP_CHECKSIG",
"hex":
"76a91478ce48f88c94df3762da89dc8498205373a8ce6f88ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"1C1mCxRukix1KfegAY5zQQJV7samAciZpv"
]
}
},
{
"value": 0.00000000,
"n": 1,
"scriptPubKey": {
"asm": "OP_RETURN
aa21a9edcde65b6fb3a180d2d81bdaab66592c9c2deb778ca3b3464e31d5209737
e67f1b",
"hex":
"6a24aa21a9edcde65b6fb3a180d2d81bdaab66592c9c2deb778ca3b3464e31d52
09737e67f1b",
"type": "nulldata"
}
}
]
}
比特币基地交易在交易输入中没有任何对 UTXO 的引用,因为金额是由新硬币和交易费创建的。交易输出中提到的地址是矿工自己的比特币地址,因此 coinbase 交易的整个资金都是单独转移给矿工的。
使用工作证明算法挖掘块
工作证明算法用于比特币,以在比特币网络中就属于区块链的区块达成共识。它有助于在网络中的节点之间实现分类帐中数据的一致性。工作证明算法可以证明为创建块已经做了一定量的工作。
比特币的工作证明是一个哈希难题,使用 SHA256 哈希函数找到所需的哈希值,从而解决难题。比特币中使用的工作证明算法类似于第三章、【区块链中的 T2】密码学中解释的算法。你可以参考第三章、区块链中的密码学,了解更多关于工作证明算法的实现和分析的细节。
采矿池
我们知道比特币的挖掘难度被调整为保持平均 10 分钟的区块创建时间。但由于矿工之间的高度竞争,解决哈希难题的难度随着时间的推移而增加。
这迫使矿商升级硬件以实现更高的散列率。资源有限的矿工无法与拥有大量计算资源的矿工竞争。当时引入了采矿池,将资源有限的单个矿工的资源集中起来。
挖掘池是挖掘者的计算资源的汇集,以共享计算能力并获得更高的散列率来解决散列难题。如果挖掘池的组合散列能力解决了块的散列难题,则基于贡献的散列能力的量来奖励作为挖掘池的一部分的每个矿工。有许多用不同语言实现的挖掘池。矿工可以加入任何挖掘池服务器,并开始贡献散列能力。Slush Pool 是最古老的采矿池,原名为 Bitcoin.cz Mining 。
挖掘池使用不同的协议在挖掘器和挖掘池服务器之间进行通信。getblocktemplate、getwork 和 stratum 协议是在挖掘池中使用的一些挖掘协议。stratum mining 协议是一种广泛使用的协议,旨在替代 getwork 协议。
连接到挖掘池服务器的每个矿工必须遵循几个步骤来成功地对池挖掘做出贡献:
-
矿工必须在开始工作之前用正确的凭证授权他们自己
-
然后,他们需要获取一个作业的事务集
-
最后,矿工需要将工作连同用户名和工作细节一起提交给服务器
有几种方式可以分配矿工的股份。大多数矿池根据矿工在矿池创造的区块中的份额来奖励矿工。
散列率是区块链矿业公司用来确定矿工的计算能力或散列能力的单位。它只不过是每秒产生的散列数。
区块链
比特币中的区块链是块的有序列表的集合,它用哈希指针连接前一个块。比特币使用谷歌的LevelDB
数据库来存储区块链元数据。通过使用块头的 SHA256 散列值来创建每个块的标识,并且该块散列被存储在区块链中的下一个块的块头中,以便形成链接。
方块图
比特币区块的数据结构包含交易和区块元数据信息的集合。一个块由一个头和一个体组成,体由所有的事务组成。一个块平均包含 500 多个事务。
表 5.2 显示了块中包含的所有字段。块的主要部分是标题和事务,它们占据可变的大小。每个字段显示块中占用的事务的大小:
| 字段 | 描述 | 尺寸 | | 魔法没有 | 该值始终为 0xD9B4BEF9 | 4 字节 | | 块大小 | 到块末尾的字节数 | 4 字节 | | 块标题 | 由六项组成 | 80 字节 | | 交易计数器 | 整数计数 | 1 - 9 字节 | | 处理 | 交易列表 | 可变的 |
表 5.2:块结构
块标题
块头存储块的元数据,大小为 80 字节,如上表所示。它存储版本信息,您可以用这些信息来识别该块。hashPrevBlock
存储前一个块的 256 位哈希值,链接各块,确保区块链的完整性。hashMerkleRoot
是事务的哈希摘要,确保事务的完整性。
时间、难度位和随机数字段与工作一致性算法的证明相关:
| 字段 | 目的 | 尺寸 |
| Version
| 块版本 | 4 字节 |
| hashPrevBlock
| 前一个块头的 256 位哈希 | 32 字节 |
| hashMerkleRoot
| 基于块中所有事务的 256 位哈希 | 32 字节 |
| Time
| 自 1970-01-01T00:00 UTC 以来的当前时间戳,以秒为单位 | 4 字节 |
| Difficulty Bits
| 紧凑格式的当前目标(https://en.bitcoin.it/wiki/Target) | 4 字节 |
| Nonce
| 32 位数字(从 0 开始) | 4 字节 |
表 5.3:块标题
创世街区
创世纪区块是比特币区块链中的第一个区块,由中本聪创建。它是静态编码的,所以每个运行比特币核心节点的人只会相信一个区块链国家。
可以通过获取第 0 个到第个索引的块散列来获取起源块散列:
$ bitcoin-cli getblockhash 0 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
以下是比特币创世纪区块的区块详情。该块有一个 coinbase 事务,没有其他事务:
$ bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
{
"hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
"confirmations": 521239,
"strippedsize": 285,
"size": 285,
"weight": 1140,
"height": 0,
"version": 1,
"versionHex": "00000001",
"merkleroot": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
"tx": [
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
],
"time": 1231006505,
"mediantime": 1231006505,
"nonce": 2083236893,
"bits": "1d00ffff",
"difficulty": 1,
"chainwork": "0000000000000000000000000000000000000000000000000000000100010001",
"nextblockhash": "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
}
块的 coinbase 事务具有以下文本,以及其事务输入中的正常数据:
The Times 03/Jan/2009 Chancellor on brink of second bailout for banks
这证明第一个区块是在 2009 年 1 月 3 日或之后开采的。coinbase 事务也有一个 50 个比特币的输出事务,就像其他任何 coinbase 事务一样。
Merkle 树
比特币报头包含总结区块中所有交易的元数据。这是通过使用一种称为 Merkle hash 树的特殊类型的树创建一个摘要来实现的。正如在第二章、的中提到的,Merkle hash 树是一种二进制 hash 树,用于汇总大型数据集。Merkle 树用于总结比特币的所有交易,从而确保交易的完整性。它们提供了一种有效的方法来验证事务是否包含在块中。
Merkle 树递归地散列节点,从树叶开始,树叶是事务的散列,直到只有一个散列。这个哈希值总结了块中的所有事务,称为 Merkle 根。比特币两次应用 SHA256 哈希函数来创建交易的哈希。
当一个块中有 N 个事务时,Merkle 树确保不需要超过 2log2(N)* 的计算来检查该事务是否包含在该块中。这使得 Merkle 树实现成为验证是否包含事务以及检查完整性的非常有效的方式。Merkle 树在有大量事务的情况下是有效的。尽管块中的事务数量呈指数增长,但由于 Merkle 树的二叉树性质,验证事务所需的路径将总是对数的。
区块链网络
比特币区块链网络由分散的节点组成,每个节点使用 P2P 网络协议与其他节点通信。运行比特币客户端的每个节点都为区块链和网络的发展做出了贡献。令人惊讶的是,比特币网络由处理多个公共区块链的节点组成。用于持有具有实际价值的交易的主区块链称为 mainnet 。这是最长的区块链,参与节点数量最多。除了 mainnet,比特币还有其他几个用于测试的区块链。目前,比特币有 testnet 、 segnet 和 regtest 区块链网络。
测试集
Testnet 是比特币区块链版本,专门用于测试目的。testnet 区块链与 mainnet 在同一网络上工作,具有钱包、交易和挖掘等功能。唯一不同的是,testnet 中流通的硬币没有任何货币价值。Testnet 是作为一个测试网络创建的,开发人员可以在 mainnet 中部署之前使用它来检查功能和修复。在 testnet 上进行测试是至关重要的,因为由于分散化,不可能恢复 mainnet 区块链。Testnet 应该通过将挖掘难度保持在最低来与轻量级配置一起工作,以便即使简单的硬件也可以用于测试。但是人们倾向于在测试网络中使用高配置的硬件,这增加了挖掘的难度。时不时地,testnet 会通过传播新的 genesis 块和重置难度来重新创建。testnet 的当前迭代被称为 testnet 3。
通过创建单独的守护进程,可以使用比特币核心创建 testnet 区块链:
$ bitcoind -testnet &
这创建了一个新的进程,它创建了一个新的 testnet 区块链副本。testnet 区块链比 mainnet 小得多。测试网区块链同步所有块比 mainnet 区块链快得多。比特币的命令行界面是以类似的论点调用的:
$ bitcoin-cli -testnet getinfo
2016 年,一个名为 segnet 的专用测试网被创建,以测试比特币隔离见证功能。然而,它不再需要运行单独的网络,因为 segnet 特性已经被添加到 testnet 3 中。
Regtest
Regtest 是一个用于回归目的的测试区块链。与 testnet 不同,regtest 不是公共的区块链。Regtest 是一个区块链,可以由用户创建用于本地测试目的。这对于测试不需要与网络进行太多交互的特性来说是非常理想的。你可以用本地的创世积木创造你的区块链版本。与 testnet 类似,regtest 标志被添加到启动进程的命令中:
$ bitcoind -regtest
由于区块链是一个本地副本,用户可以挖掘数据块而不用担心一致性。以下命令在几秒钟内挖掘 500 个块,用户将在每次 coinbase 交易中获得硬币奖励:
$ bitcoin-cli -regtest generate 500
比特币硬叉和 altcoins
比特币的硬分叉是对协议的更新,不会接受旧协议,因此需要所有人升级。硬分叉升级通常包括重大变更,如更改区块链结构、交易或共识规则。软分叉和硬分叉之间的主要区别是后者不向后兼容,这意味着旧系统将无法在更新的协议中运行。
区块链硬分叉后会有两个不同版本的区块链。区块链的多个版本是区块链节点不同意遵循单一协议的结果。区块链硬分叉往往会导致协议升级。比特币曾经有过几次硬分叉,这就产生了比特币分叉加密货币。比特币现金是第一种成功硬分叉的加密货币,于 2017 年 8 月 1 日在第 478558块比特币上分叉。创建比特币现金主要是为了将块大小增加到 8 MB。比特币黄金和比特币私人是继比特币现金之后的另外两个成功的硬分叉。
Altcoins,或称替代币,是比特币成功后推出的加密货币。替代币是在单独的区块链上创建的,不同于比特币硬分叉的加密货币。大部分 altcoins 都使用了比特币提供的基本框架,并试图解决其现有的局限性。很少有硬币试图通过使用替代算法来提高交易速度,也很少有硬币试图通过提高交易的匿名性来增强安全性。
Namecoin 是最初基于比特币的知名替代币之一。莱特币、Zcash、Dogecoin 和以太币是紧随 Namecoin 之后的几种硬币。莱特币是比特币最接近的实现,有着比特币黄金之银的美誉。莱特币的总供应量为 8400 万枚,是比特币的 4 倍。它还通过减少块创建时间来提高事务速度。Litecoin 使用一种称为 Scrypt 的内存密集型工作证明算法。
自从比特币发明以来,成千上万的替代币被创造出来,而且这个数字每天都在增长。然而,比特币是迄今为止使用最广泛的加密货币。
一个简单的加密货币应用
创建一个加密货币应用程序将允许我们实现迄今为止我们已经研究过的所有区块链概念,以及比特币中使用的交易结构,然后我们可以将其部署在一个完全的 P2P 网络中。我们在第四章的去中心化 P2P 网络中创建了一个区块链应用,在区块链中创建了网络。我们将使用同一个应用程序来创建和传播网络中的数据块,但也将使用交易和钱包的概念来扩展应用程序,以创建一种完全去中心化的加密货币:
图 5.5:连接应用程序所有组件的流程图
图 5.5 展示了如何通过添加钱包和交易功能来扩展加密货币应用。我们将遍历每个组件的实现,以便理解其功能。
处理
我们创建了一个应用程序,在该应用程序中,数据块是在没有验证数据内容的情况下创建的。我们将通过限制块只接受事务数据来扩展这个功能。交易数据类似于我们在本章中讨论的内容。它由输入和输出组件组成。输出指定交易的接收方,输入确保用户有足够的资金来成功进行交易。该输入引用了现有的未用输出。
交易输出
交易输出的结构只接受接收者的地址和交易金额。该地址是椭圆曲线加密 ( ECC )密钥对的对应公钥:
class TxOut:
def __init__(self, address, amount):
self.address = address
self.amount = amount
交易输入
交易输入通过引用可花费的交易输出来提供关于将要花费的资金的信息:
class TxIn:
def __init__(self, tx_out_id, tx_out_index, signature):
self.tx_out_id = tx_out_id
self.tx_out_index = tx_out_index
self.signature = signature
tx_out_id
和tx_out_index
用于引用交易输出,签名提供了支出者是资金合法所有人的证明。与比特币不同,我们没有使用类似脚本的语言来锁定和解锁交易。在椭圆曲线数字签名算法 ( ECDSA )的帮助下,只需验证签名即可进行交易验证。
交易结构
事务是有效事务输入和输出的集合,如下所示:
class Transaction:
def __init__(self, tx_ins, tx_outs, tx_id=None):
self.tx_ins = tx_ins
self.tx_outs = tx_outs
self.id = tx_id if tx_id else get_transaction_id(self)
事务 ID 来自整个事务的摘要。SHA256
散列函数用于计算连接的事务输入和输出内容的摘要,如下所示:
def get_transaction_id(transaction):
tx_in_content = reduce(lambda a, b : a + b, map(
(lambda tx_in: str(tx_in.tx_out_id) + str(tx_in.tx_out_index)), transaction.tx_ins), '')
tx_out_content = reduce(lambda a, b : a + b, map(
(lambda tx_out: str(tx_out.address) + str(tx_out.amount)), transaction.tx_outs), '')
return SHA256.new((tx_in_content + tx_out_content).encode()).hexdigest()
UTXO
事务输入将总是引用 UTXO。本地维护一个 UTXOs 列表。每当处理事务时,该列表都会更新,并在事务验证期间被引用。尽管该列表可以通过遍历整个区块链在任何时候生成,但是它被保存在内存中以便于快速的交易验证:
class UnspentTxOut:
def __init__(self, tx_out_id, tx_out_index, address, amount):
self.tx_out_id = tx_out_id
self.tx_out_index = tx_out_index
self.address = address
self.amount = amount
UTXOs 列表是一个简单的列表,最初是通过处理 genesis 事务创建的。
self.unspent_tx_outs = process_transactions([self.genesis_transaction], [], 0)
每当节点创建或接收事务时,它在处理它的同时更新未用完的事务输出。根据新添加的事务计算一组新的 UTXOs,如下所示:
def update_unspent_tx_outs(a_transactions, a_unspent_tx_outs):
def find_utxos(t):
utxos = []
for index, tx_out in enumerate(t.tx_outs):
utxos.append(UnspentTxOut(t.id, index, tx_out.address, tx_out.amount))
return utxos
new_utxos = reduce((lambda a, b: a + b), map(lambda t: find_utxos(t), a_transactions), [])
事务输入中引用的所有 utxo 都作为消耗的 utxo 收集:
consumed_utxos = list(map(lambda txin: UnspentTxOut(txin.tx_out_id, txin.tx_out_index, '', 0),
reduce((lambda a, b : a + b), map(lambda t: t.tx_ins, a_transactions), [])))
更新的 UTXO 列表是通过添加新创建的 UTXO 并删除所有已使用的 UTXO 来创建的:
resulting_utxos = list(filter(lambda utxo : not find_unspent_tx_out(utxo.tx_out_id, utxo.tx_out_index, consumed_utxos), a_unspent_tx_outs)) + new_utxos
return resulting_utxos
交易验证
每当一个节点接收到一个事务池或一个新的块时,所有的事务都会在它们将事务数据存储到本地区块链之前进行验证。通过检查每个字段的数据结构来测试每个事务的结构。交易输入和输出也被验证,以便不包括无效的输入或输出:
def validate_transaction(transaction, a_unspent_tx_outs):
if not is_valid_transaction_structure(transaction):
return False
如果交易结构无效,交易将被拒绝:
if get_transaction_id(transaction) != transaction.id:
print('invalid tx id: ' + transaction.id)
return False
该应用程序中的事务 ID 是使用 SHA256 散列函数计算的,这证明了事务的完整性。如果 id 被篡改并且没有通过完整性检查,则交易被视为无效:
has_valid_tx_ins = reduce((lambda a, b: a and b), map(lambda tx_in: validate_tx_in(tx_in, transaction, a_unspent_tx_outs), transaction.tx_ins), True)
if not has_valid_tx_ins:
print('some of the tx_ins are invalid in tx: ' + transaction.id)
return False
通过检查事务输入是否具有对有效 UTXO 的引用,以及用 UTXO 中提到的公钥的私钥签名的有效签名,来验证事务输入:
total_tx_in_values = reduce((lambda a, b : a + b),
map(lambda tx_in : get_tx_in_amount(tx_in, a_unspent_tx_outs), transaction.tx_ins), 0)
total_tx_out_values = reduce((lambda a, b : a + b),
map(lambda tx_out : tx_out.amount, transaction.tx_outs), 0)
if total_tx_out_values != total_tx_in_values:
print('total_tx_out_values !== total_tx_in_values in tx: ' + transaction.id)
return False
return True
通过合计输出和输入金额,将总交易输出金额与总交易输入金额进行比较。如果输入金额与输出金额不匹配,则交易被视为无效。在比特币中,由于交易费用的原因,交易产出总是低于交易投入。该金额包含在 coinbase 交易中。因为我们在这个应用程序中没有交易费用,所以输入和输出金额应该总是匹配的。
交易签名
交易签名是解锁资金的关键过程,以便将资金转移给新的所有者。每个交易输入包括签名字段,该签名字段包含由所引用的交易输出资金的所有者签名的签名。每个事务输入都签署了事务 ID,这确保了任何事务输入都不会被篡改,因为事务 ID 是整个事务的摘要。修改任何输入都将导致所有签名无效。
我们将使用包来执行事务信息的序列化、签名和验证:
import binascii
from ecdsa import SigningKey, VerifyingKey, SECP256k1
尽管我们在整本书中使用了pycryptodome
作为加密的核心库,但由于它对数字签名的独家支持,我们将在这个应用程序中使用ecdsa
包进行数字签名。
获取引用的 UTXO 是为了找到公钥来验证签名者是否是资金的所有者:
def sign_tx_in(transaction, tx_in_index, private_key, a_unspent_tx_outs):
tx_in = transaction.tx_ins[tx_in_index]
data_to_sign = str(transaction.id)
referenced_utxo = find_unspent_tx_out(tx_in.tx_out_id, tx_in.tx_out_index, a_unspent_tx_outs)
if referenced_utxo is None:
print('could not find referenced txOut')
return False
将签名者的公钥与引用的 UTXO 公钥进行比较,以检查签名者是否被授权对事务输入进行签名:
referenced_address = referenced_utxo.address
if get_public_key(private_key) != referenced_address:
print('trying to sign an input with private' + ' key that does not match the address that is referenced in tx_in')
return False
最后,使用用户的私钥对事务 ID 进行签名。签名是使用带有secp256k1
曲线的 ECDSA 创建的。参见第 2 章,一点密码学,了解更多关于 ECDSA 签名和用 secp256k1 曲线验证的细节:
sk = SigningKey.from_string(private_key, curve=SECP256k1)
signature = binascii.b2a_hex(sk.sign(data_to_sign.encode())).decode()
return signature
钱包
众所周知,钱包通过存储用户的私钥来保护资金的所有权。
钱包的实现给我们的只是抽象的操作,比如查看用户账户的余额,给另一个用户发送资金。对于那些不想或不需要了解交易的内部实现的人来说,钱包通常被认为是一个最终用户应用程序。
密钥管理
我们将实现密钥对的生成,并以明文形式存储密钥,也就是说,不应用任何加密。虽然 wallet 可以存储多个密钥(也可以由种子短语生成),但是在这个应用程序中,我们将对每个 wallet 使用一个密钥,以使 wallet 的实现尽可能简单。以下方法将读取私钥并将十六进制转换为字节表示:
PRIV_KEY_LOC = 'private_key'
from ecdsa import SigningKey
def generate_private_key():
sk = SigningKey.generate(curve=SECP256k1)
with open(PRIV_KEY_LOC, 'wt') as file_obj:
file_obj.write(binascii.b2a_hex(sk.to_string()).decode())
通过使用 ecdsa 包创建私钥来初始化 Wallet。SigningKey 类有一个 generate 方法,用于在 ecdsa 中创建签名密钥。这个密钥然后被转换成十六进制格式,然后存储在一个文件中。
def get_private_from_wallet():
return binascii.a2b_hex(open(PRIV_KEY_LOC).read())
可以随时使用私钥生成公钥。下面的方法通过读取原始私钥来创建一个SigningKey
对象。该对象可以生成一个验证密钥,即公钥:
def get_public_from_wallet():
sk = SigningKey.from_string(get_private_from_wallet(),
curve=SECP256k1)
vk = sk.get_verifying_key()
return binascii.b2a_hex(vk.to_string()).decode()
钱包余额
拥有加密货币的资金可以归结为要求获得针对用户的交易输出。钱包的余额是通过收集所有地址与用户私钥的公钥对应部分相匹配的 UTXOs 来计算的。
由于我们的应用程序为每个钱包维护一个单独的私钥,所以用户的所有 utxo 都将被引用到一个单独的公共地址,而在用户拥有的 utxo 将被寻址到多个地址的实现中,情况并非如此。以下方法查找地址与用户公钥匹配的 UTXOs 中指定的所有资金金额的总和:
def get_balance(address, unspent_tx_outs):
return sum(map(lambda utxo : utxo.amount, find_unspent_tx_outs(address, unspent_tx_outs)))
我们应用程序中的公共地址是一个公钥。在其他加密货币应用程序中,情况并非如此,这些应用程序可能使用锁定和解锁脚本,公共地址是通过使用哈希函数从公钥生成的。
创建交易
创建事务的过程就是构造一个事务对象,该对象具有一组有效的事务输入和输出,可以满足用户的事务请求。
消费 UTXOs
要创建转移资金的交易,您需要组合一个或多个 UTXOs,就像收集硬币或现金支付给某人一样。图 5.6 显示了两个值为 40 和 10 的 UTXOs 如何组合在一起,以创建一个交易输出 45 来支付给另一个用户。剩余的输出值 5 被称为找零金额,它将被返回给交易的创建者,就像我们在商店买东西时收到找零一样:
图 5.6:从可用的 UTXOs 创建新的输出事务输出
用户钱包选择一组可用于消费特定金额的 UTXOs。所选 UTXOs 的总值将总是等于或大于所需金额。下面的方法连续遍历并选择所有足以满足请求数量的事务输出。计算创建新交易输出所需的超额金额,并将其发送给交易创建者:
def find_tx_outs_for_amount(amount, my_unspent_tx_outs):
current_amount = 0
incl_unspent_tx_outs = []
for my_unspent_tx_out in my_unspent_tx_outs:
incl_unspent_tx_outs.append(my_unspent_tx_out)
current_amount = current_amount + my_unspent_tx_out.amount
if current_amount >= amount:
left_over_amount = current_amount - amount
return incl_unspent_tx_outs, left_over_amount
e_msg = 'Cannot create transaction from the available unspent transaction outputs.' \
' Required amount:' + str(amount) + '. Available unspent_tx_outs:' +
json.dumps(my_unspent_tx_outs)
print(e_msg)
return None, None
构建交易
通过构造有效的事务输入和输出来创建事务。如前一节所述,可消耗的 UTXOs 通过find_tx_outs_for_amount
方法获取。将为这些 UTXOs 创建事务输入。剩余金额用于创建变更交易:
def create_transaction(receiver_address, amount, private_key,
unspent_tx_outs, tx_pool):
my_address = get_public_key(private_key)
my_unspent_tx_outs_a = list(filter(lambda utxo: utxo.address == my_address, unspent_tx_outs))
my_unspent_tx_outs = filter_tx_pool_txs(my_unspent_tx_outs_a, tx_pool)
用户的未用交易被过滤,并将在交易输入中引用:
incl_unspent_tx_outs, left_over_amount = find_tx_outs_for_amount(amount, my_unspent_tx_outs)
if not incl_unspent_tx_outs:
return None
事务输入是使用TxIn
类通过最初将签名字段保持为空来创建的:
def to_unsigned_tx_in(unspent_tx_out):
tx_in = TxIn(unspent_tx_out.tx_out_id, unspent_tx_out.tx_out_index, '')
return tx_in
unsigned_tx_ins = list(map(to_unsigned_tx_in, incl_unspent_tx_outs))
事务是用通过create_tx_outs
方法创建的无符号事务输入和输出值创建的。此方法创建接收人并更改事务输出:
tx = Transaction(unsigned_tx_ins,
create_tx_outs(receiver_address, my_address, amount, left_over_amount))
最后,未签名的交易输入由钱包所有者使用私钥签名:
def sign_transaction(tx, index):
tx.tx_ins[index].signature = sign_tx_in(tx, index, private_key, unspent_tx_outs)
for index, txIn in enumerate(tx.tx_ins):
sign_transaction(tx, index)
return tx
事务管理
一旦创建了事务,就应该将它们包括在区块链中,以便更新全局事务状态。尚未纳入区块链的交易称为未确认交易。未确认的事务总是本地存储在一个称为事务池的池中。这和比特币的 mempool 是一样的。
交易池
由用户和其他节点创建的所有未确认的事务都包括在事务池中。事务池可以是本地文件或内存池。我们将在内存中的一个列表中维护所有事务:
transaction_pool = []
每当节点创建一个事务时,都会在广播之前将其添加到本地事务池中。下面的send_transaction
方法在创建事务后将事务添加到池中:
def send_transaction(self, address, amount):
tx = create_transaction(address, amount, get_private_from_wallet(),
self.get_unspent_tx_outs(), get_transaction_pool())
add_to_transaction_pool(tx, self.get_unspent_tx_outs())
return tx
这些交易保留在池中,直到它们被包括在区块链中。一旦事务被包括在块中,就需要更新事务池。每当接收到新的块时,节点都会更新其池。当无法找到事务输入引用的 UTXO 时,以下方法会将事务从池中删除:
def update_transaction_pool(unspent_tx_outs):
global transaction_pool
for tx in transaction_pool[:]:
for tx_in in tx.tx_ins:
if not has_tx_in(tx_in, unspent_tx_outs):
transaction_pool.remove(tx)
print('removing the following transactions from txPool: %s' % json.dumps(tx))
break
缺失的 UTXO 表示该事务已经包含在区块链中,因此可以从池中删除该事务。
广播
用户节点可以自己挖掘事务块,或者将事务传播到区块链网络,以便某个其他节点可以挖掘事务。到目前为止,我们的区块链应用程序只传达了块信息。因为不是所有的事务创建者都想自己挖掘块,所以事务需要与节点通信。我们将向应用程序添加另外两种消息类型。更多信息请参考第四章、【区块链中的 T2】联网中描述的消息类型。
这些是将在节点之间交换的查询和响应消息的格式。类似于块的广播,当节点创建新的事务时,以及当它从其他节点接收到未确认的事务时,事务被广播。当节点第一次连接到另一个节点时,它将广播事务池查询消息:
QUERY_TRANSACTION_POOL = 3
RESPONSE_TRANSACTION_POOL = 4
def query_transaction_pool_msg(self):
return {
'type': QUERY_TRANSACTION_POOL,
'data': None
}
def response_transaction_pool_msg(self):
return {
'data': JSON.dumps(get_transaction_pool())
}
区块链
虽然该应用程序的区块链部分与在第四章、区块链中创建的应用程序非常相似,但由于引入了交易数据结构,我们增加了一些功能。
挖掘一个没有交易的区块链很简单;它只需要构造一个带有报头和数据的块。但是当任意数据被事务数据替换时,节点需要从本地事务池中获取事务:
def construct_next_block(self):
coinbase_tx = get_coinbase_transaction(get_public_from_wallet(), self.blocks[-1].index + 1)
block_data = [coinbase_tx] + get_transaction_pool()
return self.generate_next_block(block_data)
这个方法构造并挖掘一个块的数据,其中包含一个 coinbase 事务和池中的事务。一旦块标题和事务被验证,块将被添加到区块链。
应用程序端点
如前所述,应用程序有一个用于管理节点的 HTTP 接口和一个用于节点间 P2P 通信的 WebSocket 接口。以下是管理节点所需的一些端点:
-
/blocks
-
/block/<hash>
-
/mineBlock
-
/transaction/<id>
-
/sendTransaction
正如在事务管理一节中所解释的那样,sendTransaction
端点基本上会创建事务并将其添加到池中。未确认的交易通过使用/mineBlock
端点包括在区块链中。
以下输出显示了区块链在执行任何其他事务之前的状态,因为它包含一个源块:
[
{
"data": {
"id": "baeece2d8e57aef79ef4e693df0485ca8938ad1f27fa9a0426c8788a3802f02f",
"tx_ins": [
{
"signature": "",
"tx_out_id": "",
"tx_out_index": 0
}
],
"tx_outs": [
{
"address": "0ae66e6adc350ec5c7961cc59cb53372dd421447d4d1b6d11ef8637ac21972068
8f8019485ac751414049162f1a71c1cc86c4e58bffb836a0d2eea3f324708df",
"amount": 50
}
]
},
"difficulty_bits": 0,
"hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7",
"index": 0,
"nonce": 0,
"previous_hash": "0",
"timestamp": 1465154705
}
]
一旦事务通过插入到区块链中得到确认,就可以通过向blocks
或transaction
端点发送 HTTP GET 请求来查看它。以下是命中事务 ID 为的事务端点时抛出的事务结果:
{
"data": {
"id": "ac3d108ebbde3b657a5875ff4237682decf530e6dd6c4b7a77711b89e23a8618",
"tx_ins": [
{
"signature": "901ea472a28294280fb7468fbc61efa0ddc5a98e375d022b4b7724a4184325c4c
2182c1091b493aec69f7ef81d912648a9e29b7941651c5fd660f72764698383",
"tx_out_id": "baeece2d8e57aef79ef4e693df0485ca8938ad1f27fa9a0426c8788a3802f02f",
"tx_out_index": 0
}
],
"tx_outs": [
{
"address": "0ae66e6adc350ec5c7961cc59cb53372dd421447d4d1b6d11ef8637ac21972068
8f8019485ac751414049162f1a71c1cc86c4e58bffb836a0d2eea3f324708d2",
"amount": 20
},
{
"address": "0ae66e6adc350ec5c7961cc59cb53372dd421447d4d1b6d11ef8637ac21972068
8f8019485ac751414049162f1a71c1cc86c4e58bffb836a0d2eea3f324708df",
"amount": 30
}
]
}
}
该事务消耗了 genesis 块的事务输出,其值为 50。由于创建了总计 20 个硬币的交易,剩余的 30 个硬币被送回给了所有者。
整个应用程序的代码可以在 GitHub 存储库中找到。因为这里没有描述应用程序的所有组件,所以请参考存储库中的代码,以便理解和执行实现。您还可以找到这个应用程序的块浏览器和钱包用户界面。
摘要
由于区块链的概念源于加密货币,因此理解区块链技术的真正实现的最佳方式是通过加密货币应用程序。在这一章中,我们借助比特币讲述了加密货币的所有概念,以便理解加密货币如何在分散的网络中发挥作用。
我们从比特币的基础知识开始,以及如何设置它们以形成一个去中心化的网络。然后我们深入到比特币中使用的几个概念,比如钥匙、地址、钱包。比特币交易得到了深入探讨,因为这些是为比特币网络带来价值的事件。我们还深入探讨了区块链的本质,包括比特币的挖掘和共识。最后,我们通过创建一个简单的加密货币应用程序来结束本章。
本章作为参考,因为它详细阐述了用于部署基本加密货币的大多数概念。既然我们已经熟悉了分散式应用程序的关键概念,我们将在下一章通过使用现有平台创建一个应用程序来深入研究区块链。