比特币交易是比特币系统中最重要的部分。比特币中的其他一切都是为了确保交易可以被创建、在网络上传播、验证,并最终添加到全局交易分类账本(区块链)中。比特币交易的本质是数据结构,这些数据结构是对比特币交易参与者价值传递的编码。比特币区块链是一本全局复式记账总账簿,每个比特币交易都是在比特币区块链上的一个公开记录。
在这一章,我们将会剖析比特币交易的多种形式、所包含的信息、如何被创建、如何被验证以及如何成为所有比特币交易永久记录的一部分。在本章中使用的术语“钱包”,指的是构建交易的软件,而不仅仅是密钥数据库。
在【第二章比特币概述】中,我们使用区块浏览器查看了Alice曾经在Bob的咖啡店支付咖啡的交易(图6-1)。
区块浏览器应用程序显示从Alice的“地址”到Bob的“地址”的交易。 这里显示的是交易内容的简化视图。 实际上,正如我们将在本章中看到的,所显示的大部分信息都是由区块浏览器构建的,并不在交易中。
图6-1 Alice与Bob咖啡店的交易
在幕后,实际的交易看起来与典型的区块浏览器提供的交易非常不同。 事实上,我们在各种比特币应用程序用户界面中看到的大多数高级结构并不存在于比特币系统中。
我们可以使用Bitcoin Core的命令行界面(getrawtransaction和decodeawtransaction)来检索Alice的“原始”交易,对其进行解码,并查看它包含的内容。 结果如下:
Alice的交易被解码后是这个样子:
{
"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",
}
]
}
您可能会注意到这笔交易似乎少了些什么东西,比如:Alice的地址在哪里?Bob的地址在哪里? Alice发送的“0.1”个币的输入在哪里? 在比特币里,没有具体的货币,没有发送者,没有接收者,没有余额,没有帐户,没有地址。为了使用者的便利,以及使事情更容易理解,所有这些都构建在更高层次上。
你可能还会注意到很多奇怪和难以辨认的字段以及十六进制字符串。 不必担心,本章将详细介绍这里所示的各个字段。
比特币交易的基本组成部分是交易输出transaction output。 交易输出是比特币不可分割的基本组合,记录在区块链上,并被整个网络认可有效。 比特币全节点跟踪所有可找到的和可使用的输出,称为 “未花费的交易输出”(unspent transaction outputs),即UTXO。 所有UTXO的集合被称为UTXO集,目前有数百万个UTXO。 当新的UTXO被创建,UTXO集就会变大,当UTXO被消耗时,UTXO集会随着缩小。每一个交易都代表UTXO集的变化(状态转换)。
当用户的钱包已经“收到”比特币时,就意味着,钱包已经检测到了可用的UTXO,这些UTXO可以用钱包所控制的其中一个密钥消费。 因此,用户的比特币“余额”是指用户钱包中可用的UTXO总和,这些UTXO分散在几百个交易和几百个区块中。用户的 “比特币余额”,这个概念是比特币钱包应用创建的。比特币钱包扫描区块链,得到可以用这个钱包控制的密钥进行消费的所有UTXO,加到一起就计算出了该用户的余额 。大多数钱包维护一个数据库或使用数据库服务来存储所有UTXO的快速引用集,其中包含可以使用用户的密钥进行消费的所有UTXO。
一个UTXO可以是1“聪”(satoshi)的任意倍数(整数倍)。就像美元可以被分割成表示两位小数的“分”一样,比特币可以被分割成八位小数的“聪”。尽管UTXO可以是任意值,但一旦被创造出来,即不可分割。这是UTXO值得被强调的一个重要特性:UTXO的面值为“聪”的整数倍,是离散(不连续)且不可分割的价值单位,一个UTXO只能在一次交易中作为一个整体被消耗。
如果一个 UTXO比一笔交易金额大,它仍会被当作一个整体而消耗掉,但同时会在交易中生成找零。例如,你有一个价值20比特币的 UTXO,想支付1比特币,那么你的交易必须消耗掉整个20比特币的UTXO,产生两个输出:一个支付了1比特币给收款人,另一个支付19比特币的找零到你的钱包。这样的话,由于UTXO(或交易输出)的不可分割特性,大部分比特币交易都会产生找零。
想象一下,一位顾客要买1.5元的饮料。她掏出钱包并试图从所有硬币和钞票中找出一种组合来凑齐她要支付的1.5 元。如果可能的话,她会选刚刚好的零钱(比如一张1元纸币和5个一毛硬币)或者是小面额的组合(比如3个五毛硬币)。如果都不行的话,她会用一张大面额的钞票,比如5元纸币。如果她把5元给了商店老板,她会得到3.5元的找零,并把找零放回她的钱包供未来使用。
类似的,一笔比特币交易可以是任意金额,但必须从用户可用的UTXO中创建出来。用户不能再把UTXO进一步细分,就像不能把一元纸币撕开而继续当货币使用一样。用户的钱包应用通常会从用户可用的UTXO中选取多个来拼凑出一个大于或等于当前交易所需的比特币量。
就像现实生活中一样,比特币应用可以使用一些策略来满足付款需求:组合若干小额UTXO,并算出准确的找零;或者使用一个比交易额大的UTXO然后进行找零。所有这些复杂的、由可花费UTXO组成的集合,都是由用户的钱包自动完成, 并不为用户所见。只有当你以编程方式用UTXO来构建原始交易时,这些才与你有关。
一笔交易会消耗先前的已被记录(存在)的UTXO,并创建新的UTXO以备未来的交易消耗。通过这种方式,一定数量的比特币价值在不同所有者之间转移,并在交易链中消耗和创建UTXO。
交易的输出与输入链存在一个例外,即被称为“币基交易”(Coinbase Transaction)的特殊交易,它是每个区块中的第一笔交易,这笔交易是由“赢家”矿工放置的,创造了支付给该矿工的全新比特币,作为挖矿奖励。这个特殊的币基交易不消耗UTXO,相反,它有一个称为“coinbase”的特殊类型的输入。这也就是为什么比特币可以在挖矿过程中被创造出来,我们将在“第十章挖矿”进行详述。
小贴士: 输入和输出,先有那个呢?先有鸡还是先有蛋?严格来讲,先产生输出,因为创造新比特币的 “币基交易”没有输入,但它可以无中生有产生输出。
每一笔比特币交易都会创造输出,并被比特币账簿记录下来。几乎所有的输出,除了一个例外(见“数据输出操作符”(OP_RETURN)),都能创造称为UTXO的比特币块,然后被整个网络识别,供所有者在未来交易中使用。
UTXO集中的UTXO被每一个全节点比特币客户端追踪。 新的交易从UTXO集中消耗(花费)一个或多个输出。
交易输出包含两部分:
一定量的比特币,面值为“聪”(satoshis) ,是最小的比特币单位;
确定花费输出所需条件的加密难题(cryptographic puzzle)
这个加密难题也被称为锁定脚本(locking script), 见证脚本(witness script), 或脚本公钥 (scriptPubKey)。
有关交易脚本语言会在后面的“交易脚本和脚本语言”一节中详细讨论。
现在,我们来看看 Alice 的交易(之前的6.2.1“交易 - 幕后细节”所示),看看我们是否可以找到并识别输出。 在 JSON 编码中,输出位于名为 vout 的数组(列表)中:
"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",
}
]
如您所见,交易包含两个输出。 每个输出都由一个值和一个加密难题来定义。 在 Bitcoin Core 显示的编码中,这里的value值以比特币为单位,但在交易本身中,它被记录为以 “聪”为单位的整数。 输出的第二部分是设定支付条件的加密难题。 Bitcoin Core 将其显示为 scriptPubKey,后面跟一个可读的脚本。
稍后将在6.4.3 脚本构建(锁定与解锁)中讨论UTXO的锁定和解锁。 在ScriptPubKey 中使用的脚本语言在6.4比特币交易脚本和脚本语言中讨论。 但在深入研究这些话题之前,需要先了解交易输入和输出的整体结构。
当交易通过网络传输或在应用程序之间交换时,它们是序列化的。 序列化是将数据结构的内部表示转换为可以一次发送一个字节的格式(也称为字节流)的过程。 序列化最常用于编码通过网络传输或用于文件中存储的数据结构。 交易输出的序列化格式如表6-1所示:
表6-1交易输出序列化
Size | Field | Description |
---|---|---|
8 bytes (little-endian) | Amount | Bitcoin value in satoshis (10-8 bitcoin) |
1–9 bytes (VarInt) | Locking-Script Size | Locking-Script length in bytes, to follow |
Variable | Locking-Script | A script defining the conditions needed to spend the output |
大多数比特币库和框架在内部不会将交易存储为字节流,因为每次需要访问单个字段时,都需要复杂的解析。为了方便和可读性,比特币库在数据结构(通常是面向对象的结构)中存储交易。
从交易的字节流表示转换为库的内部表示数据结构的过程称为反序列化或交易解析。转换回字节流通过网络传输、哈希或存储在磁盘上的过程称为序列化。大多数比特币库都有用于交易序列化和反序列化的内置函数。
看看是否可以从序列化的十六进制形式手动解码 Alice 的交易,找到我们以前看到的一些元素。包含两个输出的部分在下面例6-1中加粗显示:
例6-1。Alice的交易,序列化十六进制表示
0100000001186f9f998a5aa6f048e51dd8419a14d8a0f1a8a2836dd73 4d2804fe65fa35779000000008b483045022100884d142d86652a3f47 ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039 ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813 01410484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade84 16ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc1 7b4a10fa336a8d752adfffffffff0260e31600000000001976a914ab6 8025513c3dbd2f7b92a94e0581f5d50f654e788acd0ef800000000000 1976a9147f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a888ac 00000000
这里有一些提示:
交易输入标识哪个UTXO(通过引用)将被消费,并通过解锁脚本提供所有权证明。
要构建一个交易,一个钱包从它控制的UTXO中选择足够的面值来支付请求的付款。 有时一个UTXO就足够,有时候需要不止一个。 对于用于付款的每个UTXO,钱包将创建一个指向该UTXO的输入,使用解锁脚本解锁它。
让我们更详细地看一下输入的组成内容。输入的第一部分是一个指向UTXO的指针,引用交易哈希和输出索引,这个索引标识了交易中的特定UTXO。 第二部分是解锁脚本,钱包构建它用以满足在UTXO中的支付条件。 大多数情况下,解锁脚本是一个证明比特币所有权的数字签名和公钥,但是并不是所有的解锁脚本都包含签名。 第三部分是序列号,稍后再讨论。
考虑一下我们在【6.2.1“交易 - 幕后细节】提到的例子。交易输入是一个名为 vin 的数组(列表):
"vin": [
{
"txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
"vout": 0,
"scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
"sequence": 4294967295
}
]
如您所见,列表中只有一个输入(因为一个UTXO的面值足够完成付款)。 输入包含四个元素:
在 Alice 的交易中,输入指向的交易ID是:
7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18
输出索引是0(即由该交易创建的第一个UTXO)。解锁脚本由Alice的钱包创建,首先检索引用的UTXO,检查其锁定脚本,然后用它来构造满足要求的解锁脚本。
仅仅看这个输入,你可能已经注意到,除了涉及包含该UTXO的交易之外,我们无从了解这个UTXO的任何内容。不知道它的金额(多少聪),不知道锁定脚本的消费要求。要找到这些信息,必须通过检索整个交易来检索被引用的UTXO。请注意,由于输入的值未明确说明,因此还必须使用被引用的UTXO来计算需要支付的交易费(参见【6.3.3 交易费】)。
不仅仅是Alice的钱包需要检索输入中引用的UTXO。一旦将该交易广播到网络,每个验证节点也将需要检索交易输入中引用的UTXO,以验证该交易。
因为缺乏上下文,这些交易本身似乎不完整。它们在输入中引用了UTXO,但是如果不检索UTXO,我们就无法知道输入的值或其锁定条件。在编写比特币软件时,无论何时,只要是解码交易以验证交易、计算费用或检查解锁脚本,所编的代码就必须首先从区块链中检索引用的UTXO,以便构造输入引用的UTXO隐含但不存在的上下文。例如,要计算支付总额的交易费,必须知道输入和输出值的总和。但是,如果没有检索输入中引用的UTXO,就不能知道这些值。因此,在单个交易中计算交易费用看似简单,实际上涉及多个交易的多个步骤和数据。
我们可以使用与Bitcoin Core相同的命令序列,就像我们在检索Alice的交易(getrawtransaction和decodeawtransaction)时一样。可以得到在前面的输入中引用的UTXO,如下:
Alice 的UTXO,输入中引用的来自以前的交易:
"vout": [
{
"value": 0.10000000,
"scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG"
}
]
我们看到这个UTXO的值为0.1BTC,并且它有一个包含“OP_DUP OP_HASH160 ...”的锁定脚本(scriptPubKey)。
提示 为了充分了解Alice的交易,我们必须检索作为输入引用的之前的交易。 检索以前的交易和未花费的交易输出的函数很常见,大多数比特币函数库和API中都有。
当交易被序列化在网络上传输时,它们的输入被编码成字节流,如下表所示
表6-2 交易输入序列化
Size | Field | Description |
---|---|---|
32 bytes | Transaction Hash | Pointer to the transaction containing the UTXO to be spent |
4 bytes | Output Index | The index number of the UTXO to be spent; first one is 0 |
1–9 bytes (VarInt) | Unlocking-Script Size | Unlocking-Script length in bytes, to follow |
Variable | Unlocking-Script | A script that fulfills the conditions of the UTXO locking script |
4 bytes | Sequence Number | Used for locktime or disabled (0xFFFFFFFF) |
与输出一样,可以从序列化格式的 Alice 的交易中找到输入。 首先,将输入解码:
"vin":
[
{
"txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
"vout": 0,
"scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
"sequence": 4294967295
}
]
现在,看一下下面例6-2中序列化以十六进制表示的字段:
例6-2 Alice的交易,序列化并以十六进制表示
0100000001186f9f998a5aa6f048e51dd8419a14d8a0f1a8a2836dd73 4d2804fe65fa35779000000008b483045022100884d142d86652a3f47 ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039 ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813 01410484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade84 16ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc1 7b4a10fa336a8d752adfffffffff0260e31600000000001976a914ab6 8025513c3dbd2f7b92a94e0581f5d50f654e788acd0ef800000000000 1976a9147f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a888ac00000 000
提示:
大多数交易都包含交易费,这是对比特币矿工保护网络安全的补偿。交易费本身也是一种安全机制,增加攻击者向网络中大量发送交易的经济成本。我们将在【第十章挖矿】详细讨论挖矿、交易费和矿工得到的奖励。
这一节解释交易费是如何被包含在一个典型的交易中的。大多数钱包自动计算并包含交易费。但是, 如果你以编程方式构造交易,或者使用命令行界面,你必须手动计算并包含这些费用。
交易费用是一种激励措施,激励将交易纳入(挖矿)下一个区块,但通过对每笔交易收取小额费用,也起到抑制滥用系统的作用。交易费由成功挖到区块的矿工收取,该区块把交易记录在区块链上。
交易费是基于交易的千字节(KB)大小来计算的,而不是交易比特币的价值。总的来说,交易费是根据比特币网络中的市场力量确定的。矿工会依据许多不同的标准,比如交易费,对交易进行优先级排序,甚至在某些特定情况下免费处理交易。但大多数情况下,交易费影响处理优先级,这意味着有足够费用的交易会更可能被打包进下一个挖出的区块中;反之交易费不足或者没有交易费的交易可能会被推迟,基于尽力而为的原则在几个区块之后被处理,甚至可能根本不被处理。交易费不是强制的,而且没有交易费的交易最终也可能会被处理,但是,有交易费会鼓励优先处理。
随着时间的推移,交易费的计算方式以及在交易处理优先级上的影响已经发生了变化。起初,交易费是固定的,是网络中的一个固定常数。随着网络容量和交易量的增加,收费结构逐渐放松,并可能受到市场力量的影响。至少从2016年初以来,比特币网络容量的限制已经造成交易之间的竞争,从而导致更高的费用,免费交易彻底成为历史。零费用或非常低费用的交易很少被处理,有时甚至不会在网络上传播。
在 Bitcoin Core 中,费用中继策略由minrelaytxfee选项设置。 目前默认的minrelaytxfee是每KB大小0.00001比特币或者mBTC的1%。 因此,默认情况下,费用低于0.0001比特币的交易被视为零费用,只有在内存池有空间时才会被转发; 否则,会被丢弃。 比特币节点可以通过调整minrelaytxfee的值来覆盖默认的费用中继策略。
任何创建交易的比特币服务,包括钱包,交易所,零售应用等,都必须实施动态收费。动态费用可以通过第三方费用估算服务或内置的费用估算算法来实现。如果不确定,请从第三方服务开始,随着经验积累,如果希望删除第三方依赖项,再设计并实现自己的算法。
费用估算算法根据网络能力和“彼此竞争”的交易提供的费用计算适当的费用。这些算法从十分简单的(最后一个块中的平均值或中位数)到非常复杂的(统计分析)都有。它们估计必要的费用(以字节的聪为单位),这将使得交易具有很高的可能性被选择打包进一定数量的块内。大多数服务为用户提供高、中、低优先级费用的选择。高优先级意味着用户支付更高的交易费,但交易就更可能被打包进下一个区块中。中低优先级意味着用户支付较低的交易费,但交易可能需要更长时间才能确认。
许多钱包应用程序使用第三方服务进行费用计算。常用的一个是earnbitcoin网站这个页面 ,它提供了一个API和一个可视化图表,以聪/字节为单位显示了不同优先级的费用。
小贴士: 固定费用在比特币网络上不再可行。 设置固定费用的钱包将导致用户体验很差,因为交易往往会被“卡住”,不被确认。不了解比特币交易和费用的用户因交易被“卡住” 而感到沮丧,因为他们认为自己已经失去了这笔钱。
下面图6-2中的图表显示了按照10聪/字节增长的实时估算费用,还有每个收费区间中交易的预期确认时间(分钟和块数)。 对于每个收费区间(例如,61-70 聪/字节),两个横条显示该区间内过去24小时中交易总数(102,975笔)和未确认交易的数量(1405笔)。 根据图表,此时推荐的高优先级费用为80 聪/字节,这就使交易在最近的下一个区块(零块延迟)中被挖出。 一般来说,一笔常规交易的大小约为226字节,因此单笔交易费建议为18,080 聪(0.00018080 BTC)。
费用估算数据可以通过简单的HTTP REST APIhttps://bitcoinfees.earn.com/api/v1/fees/recommended 例如,在命令行中使用curl命令:
使用费用估算API
$ curl https://bitcoinfees.earn.com/api/v1/fees/recommended
{"fastestFee":80,"halfHourFee":80,"hourFee":60}
API返回一个satoshi/byte 形式的JSON对象,其中包括当前”最快确认“ (fastestFee),三个块(halfHourFee)确认和六个块(hourFee)确认的费用估算值。
图6-2 bitcoinfees.earn.com提供的费用估算服务
交易的数据结构没有交易费这个字段。相反,交易费是指输入和输出之间的差值。从所有输入中扣掉所有输出之后的剩余的金额是矿工收取的交易费:
Fees = Sum(Inputs) – Sum(Outputs)
正确理解交易比较困难,但又尤为重要。因为如果你要构建你自己的交易,你必须确保没有因为疏忽没支付够输入,反而在交易中添加一大笔交易费。这意味着你必须计算所有的输入,如有必要则加上找零, 不然的话,就给了矿工一笔相当可观的交易费!
举例来说,如果你消耗了一个20比特币的UTXO来完成1比特币的付款,你必须包含一笔19比特币的找零回到你的钱包。否则,那剩下的19比特币会被当作交易费,并将由挖出你交易的矿工收走。尽管你会得到高优先级的处理,并且让一个矿工喜出望外,但这一定不是你想要的结果。
警告: 如果你忘记了在手动构造的交易中增加找零的输出,系统会把找零当作交易费来处理。“不用找了!”也许不是你的真实意愿。
让我们重温一下Alice在咖啡店的交易来看看在实际中它如何运作。Alice想花0.015比特币购买咖啡。为了确保这笔交易能被立即处理,Alice想添加一笔交易费,比如说0.001。这意味着总花费会变成0.016。因此她的钱包需要凑齐一些UXTO加起来是0.016比特币或更多金额。如果更多的话,就得加上找零。我们假设她的钱包有一个0.2比特币的UTXO可用。钱包就会消耗掉这个UTXO,创造一个新的0.015的输出给Bob的咖啡店,另一个0.184比特币的输出作为找零回到Alice的钱包,并留下未分配的0.001交易费作为交易的隐含费用。
再看看另一种情况。Eugenia,菲律宾的儿童募捐项目主管,完成了一次为孩子购买教材的筹款活动。她从世界范围内接收到了好几千份小额捐款,总额是50比特币。所以她的钱包塞满了非常小的UTXO。现在她想用比特币从本地的一家出版商购买几百本教材。
现在Eugenia的钱包应用想要构造一个单笔大额付款交易,它必须来自可用的由许多较小的金额组成的UTXO集。这意味着这笔交易是把上百个小额UTXO作为输入,产生一个用来付给出版商的输出。输入数量这么巨大的交易一定会比1KB要大,也许会达到两至三KB。结果就是它需要比通常交易要高得多的交易费。
Eugenia的钱包应用会测量交易的大小,乘以每KB需要的费用来计算适当的交易费。很多钱包会支付较大的交易费,确保交易得到及时处理。更高交易费不是因为Eugenia付的钱很多,而是因为她的交易很复杂并且尺寸更大——交易费与交易的比特币金额无关。
比特币交易脚本语言,称为脚本Scripts,是一种类似Forth的逆波兰表示法的基于堆栈的执行语言。 如果听起来不知所云,可能是你没有学习过20世纪60年代的编程语言,但是没关系,我们将在本章中解释这一切。 放置在UTXO上的锁定脚本和解锁脚本都以此脚本语言编写。 当一笔比特币交易被验证时,每一笔输入中的解锁脚本与其相应的锁定脚本一起执行,以确定这笔交易是否满足支付条件。
脚本是一种非常简单的语言,设计范围有限,可在一些硬件上执行,与嵌入式设备一样简单。 它仅需要做最少的处理,许多现代编程语言可以做的事情它都不能做。 但将它用于验证可编程货币,这就是一个深思熟虑的安全特性。
如今,大多数比特币网络处理的交易都是“Alice付给Bob”的形式,基于一种称为“P2PKH”(Pay-toPublic-Key-Hash)脚本。但是,比特币交易不局限于“支付给Bob的比特币地址”的脚本。事实上,锁定脚本可以被编写成表达各种复杂的情况。为了理解这些更为复杂的脚本,我们必须首先了解交易脚本和脚本语言的基础知识。
本节将会展示比特币交易脚本语言的各个内容;同时,也会演示如何使用它去表达简单的花费条件以及这些条件如何通过解锁脚本来满足。
小贴士: 比特币交易验证并不基于静态模式,而是通过执行脚本语言来实现的。这种语言允许表达几乎无限的各种条件。这也就是比特币拥有“可编程的货币”的力影响力的方式。
比特币脚本语言包含许多操作码,但在一个重要方面受到故意的限制——除了条件流程控制之外,没有循环或复杂的流程控制功能。这样就确保了脚本语言是非图灵完备的Turing Complete,这意味着脚本的复杂性有限,执行时间也是可预见的。脚本并不是一种通用语言,这些限制确保该语言不被用于创造无限循环或其它类型的逻辑炸弹,这样的炸弹可以植入在一笔交易中,引起针对比特币网络的“拒绝服务”攻击。记住,每一笔交易都会被网络中的全节点验证,受限制的语言能防止交易验证机制被作为漏洞。
比特币交易脚本语言是无状态的的,在执行脚本之前没有状态,或者在执行脚本之后也没有保存状态。所以执行脚本所需信息都已包含在脚本中。可以预见的是,一个脚本能在任何系统上以相同的方式执行。如果您的系统验证了一个脚本,可以确信的是比特币网络中的任何其他系统也将能验证这个脚本,这意味着一个有效的交易对每个人而言都是有效的,而且每一个人都知道这一点。这种结果的可预见性是比特币系统的一项至关重要的优势。
比特币的交易验证引擎依赖于两类脚本来验证比特币交易:锁定脚本和解锁脚本。
锁定脚本是一个放置在输出上面的花费条件:它指定了今后花费这笔输出必须要满足的条件。 由于锁定脚本往往含有一个公钥或比特币地址(公钥哈希值),在历史上它曾被称为脚本公钥scriptPubKey。在这本书中,我们称之为“锁定脚本”,以承认这种脚本技术的广泛可能性。在大多数比特币应用程序中,我们所称的“锁定脚本”将以scriptPubKey的形式出现在源代码中。您还将看到被称为见证脚本(witness script)的锁定脚本(参见[隔离见证]章节),或者更普遍称为加密难题cryptographic puzzle。 这些术语在不同的抽象层次都代表同样的东西。
解锁脚本是这样一个脚本,它“解决”或满足由锁定脚本放置在输出上的条件,并允许使用输出。解锁脚本是每一笔比特币交易输入的一部分,而且往往含有一个由用户的比特币钱包(通过用户的私钥)生成的数字签名。由于解锁脚本常常包含一个数字签名,因此它曾被称作脚本签名ScriptSig。在大多数比特币应用的源代码中,ScriptSig便是我们所说的解锁脚本。你也会看到解锁脚本被称作“见证”(witness 参见[隔离见证]章节)。在本书中,我们将它称为“解锁脚本”,用以承认更广泛的锁定脚本需求,因为并非所有解锁脚本都一定包含签名。
每一个比特币验证节点会通过同时执行锁定和解锁脚本来验证一笔交易。每个输入都包含一个解锁脚本,并引用了之前存在的UTXO。 验证软件将复制解锁脚本,检索输入所引用的UTXO,并从该UTXO复制锁定脚本。 然后依次执行解锁脚本和锁定脚本。 如果解锁脚本满足锁定脚本的条件,则输入有效(请参阅【6.4.3.3 解锁脚本和锁定脚本的单独执行】)。 所有输入都是作为交易总体验证的一部分独立验证的。
请注意,UTXO被永久地记录在区块链中,因此是不变的,并且不受在新交易中引用失败的尝试的影响。 只有正确满足输出条件的有效交易才会导致输出被视为“已花费”,然后从未花费交易输出集(UTXO set)中移除。
下图是最常见类型的比特币交易(P2PKH:对公钥哈希的付款)的解锁和锁定脚本的示例,显示在脚本验证之前解除锁定和锁定脚本的连接所产生的组合脚本:
图6-3 结合scriptSig和scriptPubKey评估交易脚本
比特币的脚本语言被称为基于堆栈的语言,因为它使用一种被称为堆栈stack的数据结构。堆栈是一个非常简单的数据结构,可以被视为一叠卡片。堆栈允许两个操作:推送push和弹出pop。 推送就是在堆栈顶部添加一个项目。 弹出从堆栈中删除最顶端的项。堆栈上的操作只作用于堆栈最顶端项目。堆栈数据结构也被称为“后进先出”( Last-In-First-Out)或 “LIFO” 队列。
脚本语言执行脚本时,从左到右处理每个项目。数字(数据常量)被推送入堆栈。操作码(Operators)从堆栈中推送或弹出一个或多个参数,对其进行操作,并可能将结果推送到堆栈上。例如,操作码 OP_ADD 将从堆栈中弹出两个项目,对它们求和,并将求和结果值推送到堆栈上。
条件操作码(Conditional operators)对一个条件进行评估,产生一个 TRUE 或 FALSE 的布尔结果(boolean result)。例如, OP_EQUAL 从堆栈中弹出两个项目,如果它们相等,则推送为 TRUE(由数字1表示),否则推送为 FALSE(由数字0表示)。比特币交易脚本通常包含条件操作码,以便它们可以产生用来表示有效交易的 TRUE 结果。
现在让我们将学到的关于脚本和堆栈的知识应用到一些简单的例子中。
如图6-4,脚本“ 2 3 OP_ADD 5 OP_EQUAL ”演示了算术加法操作码 OP_ADD ,该操作码将两个数字相加,然后把结果推送到堆栈, 后面的条件操作符 OP_EQUAL 是验算之前的两数之和是否等于 5 。为了简化起见,前缀OP_在演示步骤过程中被省略了。有关可用脚本操作码和函数的更多详细信息,请参见【附录:交易脚本】。
图6-4比特币的脚本验证中,执行简单的数学运算
尽管绝大多数解锁脚本都指向一个公钥哈希值(本质上就是比特币地址),因此需要使用资金的所有权证明,但脚本不必那么复杂。任何解锁和锁定脚本的任何组合如果结果为真(TRUE),则为有效。前面用于脚本语言示例的简单算术计算同样也是一个有效的锁定脚本,该脚本能用于锁定交易输出。
使用部分算术脚本作为锁定脚本的示例:
3 OP_ADD 5 OP_EQUAL
该脚本能被包含解锁脚本输入的一笔交易所满足:
2
验证软件将锁定和解锁脚本组合起来,结果脚本是:
2 3 OP_ADD 5 OP_EQUAL
正如在上图6-4中所看到的一步步例子,当脚本被执行时,结果是OP_TRUE,交易有效。这不仅是一个有效的交易输出锁定脚本,同时产生的UTXO也能被任何具备算术技能知道数字2能够满足脚本的人所花费。
小贴士: 如果堆栈顶部的结果显示为TRUE(标记为{0x01}),即为任何非零值,或脚本执行后堆栈为空,则交易有效。如果堆栈顶部的结果显示为FALSE(0字节空值,标记为{})或脚本执行被操作码明确终止,如OP_VERIFY、 OP_RETURN,或条件终止符如OP_ENDIF,则交易无效。详见【附录:交易脚本】。
以下是一个稍微复杂一点的脚本,它用于计算 2+7-3+1 。注意,当脚本在同一行包含多个操作码时,堆栈允许一个操作码的结果由下一个操作码执行。
2 7 OP_ADD 3 OP_SUB 1 OP_ADD 7 OP_EQUAL
请试着用纸笔自行演算脚本,当脚本执行完毕时,堆栈中会留下一个TRUE值。
在最初版本的比特币客户端中,解锁脚本和锁定脚本按顺序连起来执行。出于安全因素考虑,在2010年发生了改变,因为存在一个漏洞,允许格式错误的解锁脚本将数据推送到堆栈并损坏锁定脚本。而在当前的方案中,脚本是单独执行的,在两次执行之间传输堆栈,如下所述。
首先,使用堆栈执行引擎执行解锁脚本。如果解锁脚本在执行过程中未报错(例如:没有留下“dangling”操作码),则复制主堆栈,并执行锁定脚本。如果从解锁脚本中复制而来的堆栈数据执行锁定脚本的结果为“TRUE",那么解锁脚本就成功地满足了锁定脚本所设置的条件,因此,该输入是一个能使用该UTXO的有效授权。如果合并脚本执行后的结果是”TRUE“以外的任何结果,输入都是无效的,因为它不能满足UTXO中所设置的使用该笔资金的条件。
比特币网络处理的大多数交易都是由“付款至公钥哈希”或P2PKH脚本锁定的输出,这些输出都含有一个锁定脚本,将输入锁定为一个公钥哈希值,即我们常说的比特币地址。由P2PKH脚本锁定的输出可以通过提供一个公钥和由相应私钥创建的数字签名来解锁(花费)。参见【6.5数字签名(ECDSA)】。
例如,我们再次回顾一下Alice向Bob咖啡馆支付的案例。Alice向Bob咖啡馆的比特币地址支付0.015比特币,该笔交易的输出内容为以下形式的锁定脚本:
OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
脚本中的 Cafe Public Key Hash 即为咖啡馆的比特币地址,但该地址不是基于Base58Check编码。事实上,大多数比特币地址的公钥哈希public key hash都显示为十六进制码,而不是大家所熟知的以1开头的基于Bsase58Check编码的比特币地址。
上述锁定脚本相应的解锁脚本是:
<Cafe Signature> <Cafe Public Key>
将两个脚本结合起来可以形成如下组合验证脚本:
<Cafe Signature> <Cafe Public Key> OP_DUP OP_HASH160
<Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
只有当解锁脚本与锁定脚本的设定条件相匹配时,执行组合验证脚本时才会显示结果为真(TRUE)。换句话说,只有当解锁脚本得到了咖啡馆的有效签名,交易执行结果才会被通过(结果为真),该有效签名是从与公钥哈希相匹配的咖啡馆的私钥中所获取的。
图6-5和图6-6(分两部分)显示了组合脚本一步步检验交易有效性的过程。
图6-5评估P2PKH交易的脚本(1/2)
图6-6评估P2PKH交易的脚本(2/2)
到目前为止,我们还没有深入了解“数字签名”的细节。在本节中,我们将研究数字签名的工作原理,以及如何在不出示私钥的情况下提供私钥的所有权证明。
比特币中使用的数字签名算法是椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm)或ECDSA。 ECDSA是基于椭圆曲线私钥/公钥对用于数字签名的算法,如【4.1.5 椭圆曲线加密(Elliptic Curve Cryptography)解释】所述。ECDSA用于脚本函数OP_CHECKSIG,OP_CHECKSIGVERIFY,OP_CHECKMULTISIG和OP_CHECKMULTISIGVERIFY。每当在锁定脚本中看到这些时,解锁脚本都必须包含一个ECDSA签名。
数字签名在比特币中有三种用途(请参阅下面的侧栏)。第一,签名证明私钥的所有者,即资金所有者,有权花费这些资金。第二,授权证明是不可拒绝的(不可否认性)。第三,签名证明交易(或交易的具体部分)在签字之后没有也不能被任何人修改。
请注意,交易的每笔输入都是独立签名的。这一点至关重要,因为不管是签名还是输入都不必属于同一“所有者”或者被其使用。事实上,一个名为 “CoinJoin” 的特定交易方案就使用这个特性来创建多方私密交易。
注意: 每个交易输入和它包含的任何签名完全独立于任何其他输入或签名。 多方可以协作构建交易,但是每方只能签名一个输入。
维基百科对 “数字签名 ”的定义:
数字签名是用于证明数字消息或文档的真实性的数学方案。 有效的数字签名使收件人有理由相信消息是由已知的发件人创建的(身份验证),发件人不能否认已发送的消息(不可否认性),并且消息在传输过程中没有更改(完整性)。
来源: https://en.wikipedia.org/wiki/Digital_signature*
数字签名是一种数学方案mathematical scheme,由两部分组成的:第一部分是使用私钥(签名密钥)从消息(交易)创建签名的算法; 第二部分是允许任何人给定依据给定的消息和公钥验证签名的算法。
在比特币的ECDSA算法的实现中,被签名的“消息”是交易,或更确切地说是交易中特定数据子集的哈希值(参见【6.5.3签名哈希类型(SIGHASH)】)。签名密钥是用户的私钥,结果就是签名:
((Sig = F{sig}(F{hash}(m), dA))) 这里的:
ECDSA数学运算的更多细节可以在【6.5.4 ECDSA数学】找到。
函数Fsig 产生由两个值组成的签名Sig,通常称为R和S:
Sig = (R, S)
现在已经计算了两个值R和S,它们就使用一种称为可分辨编码规则Distinguished Encoding Rules或DER的国际标准编码方案,序列化为字节流。
我们再来看看Alice创建的交易。 在交易输入中有一个解锁脚本,其中包含Alice的钱包中的以下DER编码签名:
3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e381301
该签名是Alice的钱包生成的R和S值的序列化字节流,证明她拥有授权花费该输出的私钥。 序列化格式包含以下9个元素:
看看您是否可以使用此列表解码 Alice 的序列化(DER编码)签名。 重要的数字是R和S; 数据的其余部分是DER编码方案的一部分。
要验证签名,必须有签名(R和S)、序列化交易和公钥(对应于用于创建签名的私钥)。本质上,签名的验证意味着“只有生成此公钥的私钥的所有者,才能在此交易上产生此签名。”
签名验证算法采用消息(交易或其部分的哈希值)、签名者的公钥和签名(R和S值),如果签名对该消息和公钥有效,则返回 TRUE 值。
数字签名被应用于消息,在比特币中,就是交易本身。签名意味着签名者对特定交易数据的承诺commitment。最简单的形式是,签名应用于整个交易,从而承诺所有输入,输出和其他交易字段。但是,一个签名也可以只承诺交易数据的子集,这对于我们将在本节中看到的许多场景是有用的。
比特币签名使用 SIGHASH 标志显示,交易数据的哪一部分包含在私钥签名的哈希中。 SIGHASH 标志是附加到签名的单个字节。每个签名都有一个SIGHASH标志,该标志在随输入不同而不同。一笔交易如果有三个签名输入,就会有不同SIGHASH标志的三个签名,每个签名签署(承诺)交易的不同部分。
记住,每个输入可能在其解锁脚本中包含一个签名。因此,包含多个输入的交易可以拥有具有不同SIGHASH标志的多个签名,这些标志在每个输入中承诺交易的不同部分。还要注意,比特币交易可能包含来自不同“所有者”的输入,他们在部分构建(和无效)的交易中可能仅签名一个输入,需要与他人协作收集所有必要的签名后才能使交易生效。许多SIGHSASH标志类型,只有考虑到由许多参与者在比特币网络之外共同协作去更新仅部分签名了的交易,才具有意义。
有三个SIGHASH标志:ALL,NONE和SINGLE,如下表6-3所示。
表6-3 SIGHASH类型和意义
SIGHASH flag | Value | Description |
---|---|---|
ALL | 0x01 | 签名应用到所有输出输入 |
NONE | 0x02 | 签名只应用到所有输入,不包括任何输出 |
SINGLE | 0x03 | 签名应用到所有输入和与签名输入具有相同索引号的那个输出 |
另外还有一个修饰符标志SIGHASH_ANYONECANPAY,它可以与前面的每个标志组合使用。 当设置ANYONECANPAY时,只有一个输入被签名,其余的(及其序列号)保持开放以进行修改。 ANYONECANPAY的值为0x80,并通过按位OR运算,得到如下表6-4所示的组合标志:
表6-4 带修饰符的SIGHASH类型及其含义
SIGHASH flag | Value | Description |
---|---|---|
ALL|ANYONECANPAY | 0x81 | 签名应用到一个输入和所有输出 |
NONE|ANYONECANPAY | 0x82 | 签名应用到一个输入,不包括任何输出 |
SINGLE|ANYONECANPAY | 0x83 | 签名应用到一个输入和具有相同索引号的输出 |
这些标志组合总结如下图6-7。
图6-7 不同sighash组合的总结
签名和验证期间应用sighash标志的方式是生成交易的副本并截断其中的某些字段(设置为零长度并清空)。继而交易被序列化,SIGHASH标志被添加到序列化交易的结尾,并将结果哈希,得到的哈希值即是被签名的“消息”。 根据使用的SIGHASH标志,交易的不同部分会被截断。 所得到的哈希值取决于交易中数据的不同子集。 在进行哈希前的最后一步,借助于包含SIGHASH,签名提交了SIGHASH类型,因此不能再更改(例如,被矿工)。
小贴士: 所有SIGHASH类型都签署到交易的nLocktime字段(请参阅【7.5.1交易锁定时间(nLocktime)】)。 此外,SIGHASH类型本身在交易签名之前就被附加到交易中了,因此一旦签名就不能再修改它。
在Alice的交易(参见【6.5.1.2 签名序列化(DER)】)的例子中,我们看到DER编码签名的最后一部分是01,这是SIGHASH_ALL标志。这会导致锁定交易数据,因此Alice的签名承诺的是所有的输入和输出状态。 这是最常见的签名形式。
我们来看看其他一些SIGHASH类型,以及如何在实践中使用它们:
ALL | ANYONECANPAY
这种结构可以用来做“众筹”交易,试图筹集资金的人可以用单笔输出来构建一个交易,单笔输出将“目标”金额付给众筹发起人。这样的交易显然是无效的,因为它没有输入。但是现在其他人可以添加自己的输入来修改它,实现捐赠。他们用ALL | ANYONECANPAY签名自己的输入,除非收集到足够的输入以达到输出的价值,否则交易无效,每次捐赠是一项“抵押”,直到募集到整个目标金额,筹款人才能收取。
NONE
该结构可用于创建特定金额的“不记名支票”或“空白支票”。它对输入进行承诺,但允许更改输出锁定脚本。任何人都可以将自己的比特币地址写入输出锁定脚本并兑换交易。不过,输出值本身被签名锁定。
NONE | ANYONECANPAY
这种结构可以用来建造一个“吸尘器”。用户的钱包中拥有微小UTXO,因为微小UTXO的面值还不够支付交易费,所以用户无法花费这些费用。借助这种类型的签名,微小UTXO可以捐赠给任何人,收集起来随时消费。
有一些修改或扩展SIGHASH系统的建议。其中一个是Blockstream的Glenn Willen作为Elements项目的一部分提议的Bitmask Sighash模式。其目的是创建一个灵活的SIGHASH类型的替代方案,允许使用“输入和输出的任意的,矿工可以重写的位掩码”来表示“更复杂的合同预承诺方案,例如分布式资产交易所中有变更的已签名的报价”。
注释: 您不会在用户的钱包应用程序中看到SIGHASH标志作为一个功能呈现。 有极少数例外,钱包会构建P2PKH脚本,并使用SIGHASH_ALL标志进行签名。 要使用不同的SIGHASH标志,必须编写软件来构造和签名交易。 更重要的是,SIGHASH标志可以被特殊用途的比特币应用程序使用,实现新的用途。
如前所述,签名由数学函数Fsig 创建,该函数生成了由R和S两个值组成的签名。在本节中,我们将更详细查看Fsig 函数。
签名算法首先生成一个 短暂的ephemeral(临时的)私公钥对。 该临时密钥对用于在涉及签名私钥和交易哈希的转换之后计算R和S值。
临时密钥对基于随机数k,后者用作临时私钥。 从k,生成相应的临时公钥P(以P = k G计算,与派生比特币公钥相同);参见【4.1.4 公钥】部分)。数字签名的R值则是临时公钥P*的x坐标。
在此基础上,算法计算签名的S值,使得:
S = k-1 (Hash(m) + dA * R) mod n
其中:
验证是签名生成函数的倒数,使用R,S值和公钥来计算值P,该值是椭圆曲线上的一个点(签名创建时使用的临时公钥):
P = S-1 Hash(m) G + S-1 R Qa
其中:
如果计算出的点P的x坐标等于R,则验证者就得出结论,签名是有效的。
请注意,在验证签名时,私钥既不知道也不会泄露。
小贴士: ECDSA是一个相当复杂的数学问题,完整的解释超出了本书的范围。 网上有一些很棒的指南会指导你一步步学习:搜索“ECDSA解释”或尝试这个:http://bit.ly/2r0HhGB。
如我们在【6.5.4 ECDSA数学】中所看到的,签名生成算法使用随机密钥k作为临时私有公钥对的基础。 k 的值不重要,只要它是随机的。如果使用相同的k值用在不同的消息(交易)上生成两个签名,则任何人都可以计算签名私钥。在签名算法中使用相同的 k 值会导致私钥泄露!
警告 如果在两个不同的交易中,签名算法使用相同的 k值,则私钥就能被计算出来,暴露给全世界!
这不仅仅在理论上可能,我们已经看到在比特币交易签名算法的几种不同实现中,这个问题导致了私钥暴露。人们由于无意中重复使用 k 值导致资金被盗。重用 k 值的最常见原因是未正确初始化的随机数生成器。
为了避免此漏洞,行业最佳实践是,不使用以熵为种子的随机数生成器生成k,而是使用以交易数据本身为种子的确定性随机流程。这确保每个交易产生不同的 k 值。在互联网工程任务组(Internet Engineering Task Force)发布的RFC 6979中定义了 k 值的确定性初始化的行业标准算法。
如果您正在部署在比特币中签名交易的算法,则必须使用RFC 6979或类似的确定性随机算法来确保为每个交易生成不同的 k 值。
我们在本章开始时发现,交易的“幕后”看起来与钱包、区块链浏览器和其他面向用户的应用程序中呈现出来的非常不同。 前几章中的许多简单而熟悉的概念,如比特币地址和余额,似乎都没有出现在交易结构中。 我们看到交易本身并不包含比特币地址,而是通过锁定和解锁比特币离散值的脚本进行操作。 余额也不存在于这个系统中的任何地方,但是每个钱包应用程序都明明白白地显示了用户钱包的余额。
现在,我们已经探讨了比特币交易中实际包含的内容,我们可以研究如何从交易的看似原始的组件中派生出更高级别的抽象。
我们再来看看Alice的交易在一个常用的区块浏览器(下图6-8)中呈现的:
图6-8 Alice与Bob咖啡馆的交易
在交易的左侧,区块链浏览器将Alice的比特币地址显示为“发送者”。其实这个信息本身并不在交易中。当区块链浏览器检索到交易时,它还检索在输入中引用的上一个交易,并从该旧交易中提取第一个输出。在该输出内是一个锁定脚本,将UTXO锁定到Alice的公钥哈希(P2PKH脚本)。区块链浏览器提取公钥哈希,并使用Base58Check编码对其进行编码,以生成和显示表示该公钥的比特币地址。
同样,在右侧,区块链浏览器显示了两个输出:第一个到Bob的比特币地址,第二个到Alice的比特币地址(作为找零)。为了创建这些比特币地址,区块链浏览器再次从每个输出中提取锁定脚本,将其识别为P2PKH脚本,从中提取公钥哈希。最后,区块链浏览器使用Base58Check重新编码公钥哈希,生成和显示比特币地址。
如果点击Bob的比特币地址,区块链浏览器将显示Bob的比特币地址的余额,如下图6-9:
图6-9 Bob的比特币地址的余额
区块链浏览器显示了Bob的比特币地址的余额。但是比特币系统中却没有“余额”的概念。这里显示的余额其实是由区块链浏览器按如下方式构建出来的:
为了构建“总收入”数量,区块链浏览器首先解码比特币地址的Base58Check编码,检索编码在地址内的Bob公钥的160位哈希值。然后,区块链浏览器搜索交易数据库,使用包含Bob公钥哈希的P2PKH锁定脚本查找输出。通过汇总所有输出的值,区块链浏览器就得出了接收总额。
构建当前余额(显示为“最终余额”)需要更多的工作。区块链浏览器保持一个单独的数据库,即UTXO集,其中保存着当前未被使用的输出。为了维护这个数据库,区块链浏览器必须实时监控比特币网络中出现的未经确认的交易,添加新创建的UTXO,删除已用UTXO。这是一个复杂的过程,依赖于在交易传播过程中跟踪交易,以及与比特币网络保持共识,以确保跟随正确的主链。区块链浏览器有时也会出现未能保持同步,导致其对UTXO集的跟踪扫描不完整或不正确。
区块链浏览器从UTXO集中,汇总引用Bob的公钥哈希的所有未花费输出的金额,就得出显示给用户的“最终余额”数值。
为了生成这张画面,得到这两个“余额”,区块链浏览器必须索引和搜索数十、数百甚至数十万的交易。
总之,通过钱包应用程序,区块链浏览器和其他比特币用户界面呈现给用户的信息通常由更高级别的抽象组成,这些抽象是通过搜索许多不同的交易、检查其内容和操作其中包含的数据而派生出来的。通过呈现这种简单的比特币交易视图(类似于从一个发送者到一个接收者的银行支票),这些应用程序必须抽象出许多潜在的细节。它们主要关注常见的交易类型:每个输入上具有SIGHASH_ALL签名的P2PKH交易。因此,虽然比特币应用程序以易于阅读的方式呈现所有了80%以上的交易,但有时候会被偏离规范的交易困扰。对于包含更复杂的锁定脚本,或不同SIGHASH标志,或多个输入和输出的交易,这种抽象就显得简单和不足。
每天都有数百个不包含P2PKH输出的交易在区块链上被确认。 区块链浏览器经常向他们发出红色警告信息,表示无法解码地址。以下链接包含最近不能完全解码的“奇怪交易”:https://blockchain.info/strange-transactions.
正如我们将在下一章中看到的,这些并不一定是奇怪的交易。它们是包含比常见的P2PKH更复杂的锁定脚本的交易。接下来我们将学习如何解码和理解更复杂的脚本及其支持的应用程序。