《精通比特币》解读 第六章 - 交易

比特币交易是比特币系统中最重要的部分。根据比特币系统的设计原理,系统中任何其他的部分都是为了确保比特币交易可以被创建、在比特币网络中传播、通过验证,并最终添加入全球比特币交易总账簿(比特币区块链)。比特币交易的本质是数据结构,这些数据结构中含有比特币交易参与者的价值转移相关信息。比特币区块链是一本全球复式记账总账簿,每笔交易都是在比特币区块链上的一个公开记录。

在这一章,我们将会剖析各种比特币交易的形式、所包含的信息、如何被创建、如何被验证以及如何成为所有比特币交易永久记录的一部分。当我们在本章中使用术语“钱包”时,我们指的是构建交易的软件,而不仅仅是密钥的数据库。

交易 - 幕后细节
我们可以使用Bitcoin Core的命令行界面(getrawtransaction和decodeawtransaction)来检索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",
    }
  ]
}

交易的输入输出
比特币交易中的基础构建单元是交易输出。 交易输出是比特币不可分割的基本组合,记录在区块上,并被整个网络识别为有效。比特币完整节点跟踪所有可找到的和可使用的输出,称为 “未花费的交易输出”(unspent transaction outputs),即UTXO。 所有UTXO的集合被称为UTXO集,目前有数百万个UTXO。 当新的UTXO被创建,UTXO集就会变大,当UTXO被消耗时,UTXO集会随着缩小。每一个交易都代表UTXO集的变化(状态转换)。

一笔交易会消耗已存在的UTXO,并创建新的UTXO,新的UTXO可以被之后的交易消耗。通过这种消耗和创建UTXO的方式,一定数量的比特币价值在交易链中的不同所有者之间转移。一笔比特币交易通过使用所有者的签名来解锁UTXO,并通过使用新的所有者的比特币地址来锁定并创建UTXO。
但是有个例外,每个区块中的第一笔交易即coinbase交易,这个交易是不消耗UTXO的,而是有一个特殊的输入叫”coinbase”。
输入和输出,哪一个是先产生的呢?先有鸡还是先有蛋呢?严格来讲,先产生输出,因为可以创造新比特币的coinbase交易没有输入,但它可以无中生有地产生输出。

交易输出

交易输出包含两部分:

  • 一定量的比特币,单位是“聪”(satoshis) ,是最小的比特币单位;
  • 确定花费输出所需条件的加密难题(cryptographic puzzle)

这个加密难题也被称为锁定脚本(locking script), 见证脚本(witness script), 或脚本公钥 (scriptPubKey)。
现在,我们来看看 Alice 的交易,交易输出位于名为 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",
  }
]

包含两个输出,每个输出都由一个值和一个加密难题来定义。

交易序列化 - 输出

当交易通过网络传输或在应用程序之间交换时,它们需要被序列化。 序列化是将内部表示的数据结构转换为可以一次发送一个字节的格式(也称为字节流)的过程。 序列化最常用于编码数据结构以通过网络传输或用于文件中存储。 交易输出的序列化格式如下表所示:
Table 6-1. Transaction output serialization
这里写图片描述

大多数比特币函数库和框架不会在内部将交易以字节流的形式存储,因为每次需要访问单个字段时,都需要复杂的解析。为了方便和可读性,比特币函数库在内部将交易存储在数据结构(通常是面向对象的结构)中。

从交易的字节流表示转换为函数库的内部数据结构表示的过程称为反序列化或交易解析。转换回字节流以通过网络传输、哈希化(hashing)或存储在磁盘上的过程称为序列化。大多数比特币函数库具有用于交易序列化和反序列化的内置函数。

看看是否可以手动解码序列化的十六进制形式的Alice的交易 ,从而找到我们上述看到的一些元素。包含两个交易输出的部分在下面中已加粗显示:
这里写图片描述

这里有一些提示:

  • 加粗显示的部分有两个输出,每个都如本节之前Table 6-1所述进行了序列化。
  • 0.015比特币的价值是1,500,000 satoshis。 这是十六进制的16 e3 60。
  • 在串行化交易中,值16 e3 60以小端(最低有效字节优先)字节顺序进行编码,所以它看起来像60 e3 16。
  • scriptPubKey的长度为25个字节,以十六进制显示是19。

交易输入

交易输入将识别出(通过引用)哪些UTXO将被消费,并通过解锁脚本提供所有权证明。

要构建一个交易,一个钱包从它控制的UTXO中选择足够的价值来执行请求的付款。 有时一个UTXO就足够,有时候不止一个。 对于将用于进行此付款的每个UTXO,钱包将创建一个指向UTXO的输入,并使用解锁脚本解锁它。

来更详细地看一下输入的各个部分,
第一部分是一个指向UTXO的指针,通过指向UTXO被记录在区块链中所在的交易的哈希值和序列号来实现。
第二部分是解锁脚本,钱包构建它用以满足设定在UTXO中的支出条件。 大多数情况下,解锁脚本是一个证明比特币所有权的数字签名和公钥,但并不是所有的解锁脚本都包含签名。
第三部分是序列号,稍后再讨论。

考虑我们在之前提到的例子。交易输入是一个名为 vin 的数组(列表):

"vin": [
  {
    "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
    "vout": 0,
    "scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
    "sequence": 4294967295
  }
]

可以看到,列表中只有一个输入(因为一个UTXO包含足够的值来完成此付款)。 输入包含四个元素:

  • 一个交易ID(txid),指向包含将被花费的UTXO的交易
  • 一个输出索引(vout),用于标识引用的是该交易的哪个UTXO(第一个为零)
  • 一个 scriptSig(解锁脚本),用于满足设定在UTXO上的支出条件,解锁它以用于支出
  • 一个序列号(稍后讨论)

在 Alice 的交易中,输入指向的交易ID是:

7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18

输出索引是0(即由该交易创建的第一个UTXO)。解锁脚本由Alice的钱包通过下述步骤构建:
首先检索引用的UTXO,检查其锁定脚本,
然后使用它来构建所需的解锁脚本以满足锁定脚本。

仅仅看这个输入,你可能已经注意到,除了包含这个UTXO的交易的引用之外,我们无从了解这个UTXO的任何内容。我们不知道它输入的值(多少satoshi金额),我们不知道设置了支出条件的锁定脚本。要找到这些信息,我们必须通过检索整个交易来检索被引用的UTXO。请注意,由于输入的值未明确说明,因此我们还必须使用被引用的UTXO来计算在此交易中需要支付的费用(参见后面交易费用章节)。

不仅仅是Alice的钱包需要检索输入中引用的UTXO。一旦将该交易广播到网络,每个验证节点也将需要检索交易输入中引用的UTXO,以验证该交易。

由于缺乏上下文,交易看起来似乎不完整。他们在输入中引用UTXO,但是没有检索到UTXO,我们无法知道输入的值或其锁定脚本。当编写比特币软件时,当解码交易以验证或计算费用或检查解锁脚本时,您的代码都首先必须从区块链中检索引用的UTXO,以构建隐含但不存在于输入中引用的UTXO的上下文。例如,要计算支付总额的交易费,您必须知道输入和输出值的总和。但是,如果不检索输入中引用的UTXO,则不知道它们的值。因此,在看似简单的在单个交易中计算交易费用的操作,实际上涉及多个交易的多个步骤和数据。

我们可以使用与检索Alice的交易相同的命令序列(getrawtransaction和decodeawtransaction),来得到上述的输入中引用的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中。

交易序列化–交易输入
当交易被序列化以在网络上传输时,它们的输入被编码成字节流,如下表所示:
这里写图片描述

与输出一样,我们来看看我们是否可以从序列化格式的 Alice 的交易中找到输入。 首先,将输入解码:

"vin":
[
  {
    "txid": "7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
    "vout": 0,
    "scriptSig" : "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
    "sequence": 4294967295
  }
]

现在,我们来看看我们是否可以识别下面这些以十六进制表示法表示的字段:
这里写图片描述

提示:

  • 交易ID以反转字节顺序序列化,因此以(十六进制)18开头,以79结尾
  • 输出索引为4字节组的“0”,容易识别
  • scriptSig的长度为139个字节,或十六进制为8b
  • 序列号设置为FFFFFFFF,也容易识别

交易费
交易费的意义:
1. 作为矿工维持比特币网络、将交易打包进下一区块的报酬(激励)
2. 也作为比特币网络本身的一种安全机制,从经济学上避免了攻击者利用大量交易制造泛洪攻击

这一节解释交易费是如何被包含在一个典型的交易中的。大多数钱包自动计算并计入交易费。但是, 如果你以编程方式构造交易,或者使用命令行界面,你必须手动计算并计入这些费用。

交易费作为矿工打包(挖矿)一笔交易到下一个区块中的一种激励;同时作为一种抑制因素,通过对每一笔交易收取小额费用来防止对系统的滥用。成功挖到某区块的矿工将得到该区块内包含的交易费, 区块将交易记录在了区块链上。

交易费是根据交易的大小,按照千字节来计算的,而不是根据交易额。总的来说,交易费是根据比特币网络中的市场力量确定的。矿工会依据许多不同的标准对交易进行优先级排序,包括交易费用,他们甚至可能在某些特定情况下免费处理交易。但大多数情况下,交易费影响处理优先级,这意味着有足够费用的交易会更可能被打包进下一个挖出的区块中,反之交易费不足或者没有交易费的交易可能会被推迟,基于尽力而为的原则在几个区块之后被处理,甚至可能根本不被处理。交易费不是强制的,而且没有交易费的交易最终也可能会被处理,但是,交易费将提高处理优先级。

随着时间的推移,交易费的计算方式以及在交易处理优先级上的影响已经产生了变化。起初,交易费是固定的,是网络中的一个固定常数。渐渐地,随着网络容量和交易量的不断变化,并可能受到来自市场力量的影响,收费结构开始放松。自从至少2016年初以来,比特币网络容量的限制已经造成交易之间的竞争,从而导致更高的费用,免费交易彻底成为过去式。零费用或非常低费用的交易鲜少被处理,有时甚至不会在网络上传播。

在 Bitcoin Core 中,费用传递政策由minrelaytxfee选项设置。 目前默认的minrelaytxfee是每千字节0.00001比特币或者millibitcoin的1%。 因此,默认情况下,费用低于0.0001比特币的交易被当做是免费的,但只有在内存池有空间时才会被转发, 否则,会被丢弃。 比特币节点可以通过调整minrelaytxfee的值来覆盖默认的费用传策略。

任何创建交易的比特币服务,包括钱包,交易所,零售应用等,都必须实现动态收费。动态费用可以通过第三方费用估算服务或内置的费用估算算法来实现。如果您不确定,那就从第三方服务开始,如果您希望去除第三方依赖,您应当有设计和部署自己算法的经验。

费用估算算法根据网络容量和“竞争”交易提供的费用计算适当的费用。这些算法从十分简单的(最后一个块中的交易费的平均值或中位数)到非常复杂的(统计分析)均有覆盖。他们估计必要的费用(以聪每字节为单位),这将使得交易具有很高的可能性在确定个数的区块之后被选中并被打包。大多数服务为用户提供高、中、低优先费用的选择。高优先级意味着用户支付更高的交易费,但交易可能就会被打包进下一个块中。中低优先级意味着用户支付较低的交易费,但交易可能需要更长时间才能确认。

许多钱包应用程序使用第三方服务进行费用计算。一个流行的服务是http://bitcoinfees.21.co,它提供了一个API和一个可视化图表,以satoshi / byte为单位显示了不同优先级的费用。

小贴士:静态费用在比特币网络上不再可行。 设置静态费用的钱包将导致用户体验不佳,因为交易往往会被“卡住”,并不被确认。 不了解比特币交易和费用的用户因交易被“卡住” 而感到沮丧,因为他们认为自己已经失去了资金。

下图表显示了按照10个satoshi / byte增量的实时费用估计,以及每个交易费用范围的预期确认时间(按照分钟和区块数)。 对于每个收费范围(例如,61-70 satoshi /字节),两个水平栏显示了未确认交易的数量(1405)和过去24小时的交易总数(102,975)。 根据图表,此时推荐的高优先费用为80 satoshi / 字节,这使交易在下一个区块(零块延迟)就可以被打包进。 据合理判断,一笔常规交易的大小约为226字节,因此单笔交易建议费用为18,080 satoshis(0.00018080 BTC)。

费用估算数据可以通过简单的HTTP REST API(https://bitcoinfees.21.co/api/v1/fees/recommended)来检索。 例如,在命令行中使用curl命令:

$ curl https://bitcoinfees.21.co/api/v1/fees/recommended

{"fastestFee":80,"halfHourFee":80,"hourFee":60}

API返回一个带有当前费用估算的 JSON 对象,包含了最快确认(fastestFee),在三个块(halfHourFee)内确定和在六个块(hourFee)内确认的交易费用,单位是satoshi per byte。

Figure 6-2. Fee estimation service bitcoinfees.21.co
这里写图片描述

把交易费加到交易中

交易的数据结构里并没有表示交易费的字段,而是隐含在输入总和和输出总和的差值中。从所有输入中扣除掉所有输出之后的多余的量就会被矿工作为矿工费收集走,交易费即输入总和减输出总和的余量:

Fees = Sum(Inputs) – Sum(Outputs)

正确理解交易比较困难,但又尤为重要,因为如果你要构建你自己的交易,你必须确保你没有因疏忽在交易中添加一笔大量交易费而大大减少了输入的可花费额。这就意味着你必须计算所有的输入,如有必要则要创建找零, 不然的话,结果就是你给了矿工一笔相当可观的劳务费!

举例来说,如果你消耗了一个20比特币的UTXO来完成1比特币的付款,你必须包含一笔19比特币的找零回到你的钱包。否则,那剩下的19比特币会被当作交易费,并将由挖出你交易的矿工收走。尽管你会得到高优先级的处理,并且让一个矿工喜出望外,但这很可能不是你想要的。

警告:如果你忘记了在手动构造的交易中增加找零的输出,系统会把找零当作交易费来处理。“不用找了!”也许不是你的真实意愿。

让我们重温一下Alice在咖啡店的交易来看看在实际中它如何运作。Alice想花0.015比特币购买咖啡。为了确保这笔交易能被立即处理,Alice想添加一笔交易费,比如说0.001。这意味着总花费会变成0.016。因此她的钱包需要凑齐 0.016或更多的UTXO。如果需要,还要加上找零。我们假设他的钱包有一个0.2比特币的UTXO可用。他的钱包就会消耗 掉这个UTXO,创造一个新的0.015的输出给Bob的咖啡店,另一个0.184比特币的输出作为找零回到Alice的钱包, 并留下未分配的0.001比特币,意味着作为该交易的费用。

现在让我们看看另一种情况。Eugenia,我们在菲律宾的儿童募捐项目主管,完成了一次为孩子购买教材的筹款活动。她从世界范围内接收到了好几千份小额捐款,总额是50比特币。所以她的钱包塞满了非常小的UTXO。现在她想用比特币从本地的一家出版商购买几百本教材。

现在Eugenia的钱包应用想要构造一个单笔大额付款交易,它必须从由很多小额UTXO构成的可用的UTXO集合中寻求钱币来源。这意味着交易的结果是从上百个小额UTXO中作为输入,但只有一个输出用来付给出版商。输入数量这么巨大的交易会比一千字节要大,也许总尺寸会达到两至三千字节。结果是它将需要比中等规模交易要高得多的交易费。

Eugenia的钱包应用会通过测量交易的大小,乘以每千字节需要的费用来计算适当的交易费。很多钱包会给大的交易多付一些交易费,以确保交易得到及时处理。更高的交易费不是因为Eugenia付的钱很多,而是因为她的交易很复杂并且尺寸更大——交易费与交易的比特币数值无关。

比特币交易脚本和脚本语言

比特币交易脚本语言,称为脚本,是一种类似Forth的逆波兰表达式的基于堆栈的执行语言。 放置在UTXO上的锁定脚本和解锁脚本都用此脚本语言编写。当一笔比特币交易被验证时,每一个输入值中的解锁脚本与其对应的锁定脚本同时 (互不干扰地)执行,以确定这笔交易是否满足支付条件。

脚本是一种非常简单的语言,被设计为在执行范围上有限制,可在一些硬件上执行,可能与嵌入式装置一样简单。 它仅需要做最少的处理,许多现代编程语言可以做的花哨的事情它都不能做。 但用于验证可编程货币,这是一个经深思熟虑的安全特性。

如今,大多数经比特币网络处理的交易是以“支付给Bob的比特币地址”的形式存在,并基于一种称为“P2PKH”(Pay-to-Public-Key-Hash)脚本。但是,比特币交易不局限于“支付给Bob的比特币地址”的脚本。事实上,锁定脚本可以被编写成表达各种复杂的情况。为了理解这些更为复杂的脚本,我们必须首先了解交易脚本和脚本语言的基础知识。

在本节中,我们将会展示比特币交易脚本语言的各个组件,同时我们也会演示如何使用它去表达简单的花费条件以及如何通过解锁脚本去满足这些花费条件。

小贴士:比特币交易验证并不基于静态模式,而是通过脚本语言的执行来实现的。这种语言允许表达几乎无限的各种条件。这也是比特币作为一种“可编程的货币”所拥有的力量。

图灵非完备性

比特币脚本语言包含许多操作码,但都故意限定为一种重要的模式——除了有条件的流控制以外,没有循环或复杂流控制能力。这样就保证了脚本语言的图灵非完备性,这意味着脚本有限的复杂性和可预见的执行次数。脚本并不是一种通用语言,这些限制确保该语言不被用于创造无限循环或其它类型的逻辑炸弹,这种炸弹可以植入在一笔交易中,引起针对比特币网络的“拒绝服务”攻击。记住,每一笔交易都会被比特币网络中的任何一个全节点验证,受限制的语言能防止交易验证机制被作为一个漏洞而加以利用。

去中心化验证
比特币交易脚本语言是没有中心化主体的,没有任何中心主体能优先执行脚本,也没有任何中心主体会在脚本被执行后对其进行保存。所以执行脚本所需信息都已包含在脚本中。可以预见的是,一个脚本能在任何系统上以相同的方式执行。如果您的系统验证了一个脚本,可以确信的是比特币网络中的任何一个其他系统也将验证这个脚本,这意味着一个有效的交易对每个人而言都是有效的,而且每一个人都知道这一点。这种结果可预见性是比特币系统的一项至关重要的良性特征。

脚本构建(锁定与解锁)
比特币的交易验证引擎依赖于两类脚本来验证比特币交易:锁定脚本和解锁脚本。

锁定脚本是一个放置在输出上面的花费条件:它指定了今后花费这笔输出必须要满足的条件。 由于锁定脚本往往含有一个公钥或比特币地址(公钥哈希值),在历史上它曾被称为脚本公钥(scriptPubKey)。由于认识到这种脚本技术存在着更为广泛的可能性,在本书中,我们将它称为“锁定脚本”(locking script)。在大多数比特币应用程序中,我们所称的“锁定脚本”将以scriptPubKey的形式出现在源代码中。您还将看到锁定脚本被称为见证脚本(witness script)(参见[隔离见证]章节),或者更普遍的被称为一个加密难题(cryptographic puzzle)。 这些术语在不同的抽象层次上都意味着同样的东西。

解锁脚本是一个“解答”或满足被锁定脚本设定在一个输出上的花费条件的脚本,它将允许输出被花费。解锁脚本是每一笔比特币交易输入的一部分,而且往往含有一个由用户的比特币钱包通过用户的私钥生成的数字签名。由于解锁脚本常常包含一个数字签名,因此它曾被称作ScriptSig。在大多数比特币应用的源代码中,ScriptSig便是我们所说的解锁脚本。你也会看到解锁脚本被称作“见证”(witness 参见附录的[隔离见证])。在本书中,我们将它称为“解锁脚本”,因为意识到更广范围的满足锁定脚本的必要条件,并不是所有的解锁脚本都必须包含签名。

每一个比特币验证节点会通过同时执行锁定和解锁脚本来验证一笔交易。每个输入都包含了一个解锁脚本和之前交易存在的UTXO的引用。 验证软件将复制解锁脚本,检索输入所引用的UTXO,并从该UTXO复制锁定脚本。 然后依次执行解锁脚本和锁定脚本。 如果解锁脚本满足锁定脚本的条件,则输入有效(请参阅单独执行解锁和锁定脚本部分)。 所有输入都是独立验证的,作为交易总体验证的一部分。

请注意,UTXO被永久地记录在区块链中,因此是不变的,并且不受在新交易中引用了它并尝试花费它但却交易失败的影响。 只有正确满足输出条件的有效交易才能使输出视为“已花费”,继而该输出将被从未花费的交易输出集(UTXO set)中删除。
下图是最常见类型的比特币交易(a payment to a public key hash)的解锁和锁定脚本的示例,显示了在脚本验证之前从解锁脚本和锁定脚本连接产生的组合脚本:
这里写图片描述

Figure 6-3. Combining scriptSig and scriptPubKey to evaluate a transaction script

脚本执行堆栈
比特币的脚本语言被称为基于堆栈的语言,因为它使用一种被称为堆栈的数据结构。堆栈是一个非常简单的数据结构,可以被视为一叠卡片。栈允许两个操作:push和pop(推进和弹出)。 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 OPADD 5 OP_EQUAL ” 演示了算术加法操作码 OP_ADD ,该操作码将两个数字相加,然后把结果推送到堆栈, 后面的条件操作符 OP_EQUAL 是验算之前的两数之和是否等于 5 。为了简化起见,前缀 OP_ 在逐步演示的过程中将被省略。更多关于可用的脚本操作码和函数的详细信息,请参见[附录B]。

尽管绝大多数锁定脚本都指向一个公钥哈希值(本质上就是比特币地址),因此如果想要使用资金则需验证所有权,但脚本本身并不需要如此复杂。任何解锁和锁定脚本的组合如果结果为真(TRUE),则为有效。前面被我们用于说明脚本语言的简单算术操作码同样也是一个有效的锁定脚本,该脚本能用于锁定交易输出。
使用部分算术操作码脚本作为锁定脚本的示例:

3 OP_ADD 5 OP_EQUAL

该脚本能被以如下解锁脚本为输入的一笔交易所满足:

2

验证软件将锁定和解锁脚本组合起来,结果脚本是:

2 3 OP_ADD 5 OP_EQUAL

正如在上图中所看到的,当脚本被执行时,结果是OP_TRUE,交易有效。不仅该笔交易的输出锁定脚本有效,同时UTXO也能被任何知晓这个运算技巧(知道是数字2)的人所使用。

小贴士:如果堆栈顶部的结果显示为TRUE(标记为{{0x01}}),即为任何非零值,或脚本执行后堆栈为空情形,则交易有效。如果堆栈顶部的结果显示为FALSE(0字节空值,标记为{})或脚本执行被操作码明确禁止,如OP_VERIFY、 OP_RETURN,或有条件终止如OP_ENDIF,则交易无效。详见[附录B]相关内容。
这里写图片描述
Figure 6-4. Bitcoin’s script validation doing simple math

以下是一个稍微复杂一点的脚本,它用于计算 2+7-3+1 。注意,当脚本在同一行包含多个操作码时,堆栈允许一个操作码的结果交给下一个操作码执行:

2 7 OP_ADD 3 OP_SUB 1 OP_ADD 7 OP_EQUAL

试着自行用纸和笔演算上述脚本,当脚本执行完毕时,你会在栈中得到结果true。

解锁和锁定脚本的单独执行
在最初版本的比特币客户端中,解锁和锁定脚本是先被连接起来,然后被依次执行的。出于安全因素考虑,在2010 年比特币开发者们修改了这个特性——因为存在一个“允许异常解锁脚本推送数据入栈并且污染锁定脚本”的漏洞。而在当前的实现方案中,这两个脚本是随着堆栈的传递被分别执行的。下面将会详细介绍。

首先,使用堆栈执行引擎执行解锁脚本。如果解锁脚本在执行过程中未报错(例如:没有余下“悬挂”的操作码),则复制主堆栈(而不是备用堆栈),并执行锁定脚本。如果用解锁脚本中复制而来的堆栈数据执行锁定脚本的结果为“TRUE”,那么解锁脚本就成功地满足了锁定脚本所设置的条件,因此,该输入是一个能花费该UTXO的有效授权。如果在合并脚本执行后的结果是”TRUE”以外的任何结果,则输入是无效的,因为它不能满足UTXO中所设置的花费该笔资金的条件。

Pay-to-Public-Key-Hash (P2PKH)
比特币网络处理的大多数交易都是花费由“Payto-Public-Key-Hash”(或“P2PKH”)脚本锁定的输出,这些输出都含有一个锁定脚本,将输出锁定到一个公钥哈希值,即我们常说的比特币地址。由P2PKH脚本锁定的输出可以通过提供一个公钥和由相应私钥创建的数字签名来解锁(花费)。参见数字签名ECDSA相关内容。

例如,我们可以再次回顾一下Alice向Bob咖啡馆支付的案例。Alice下达了向Bob咖啡馆的比特币地址支付0.015比特币的支付指令,该笔交易的输出内容为以下形式的锁定脚本:

OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG

脚本中的 Cafe Public Key Hash 即为咖啡馆的比特币地址,但该地址不是基于Base58Check编码。事实上,大多数比特币地址的公钥哈希值都显示为十六进制码,而不是大家所熟知的以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(分两部分)显示了组合脚本一步步检验交易有效性的过程。
这里写图片描述
Figure 6-5. Evaluating a script for a P2PKH transaction (part 1 of 2)

这里写图片描述
Figure 6-6. Evaluating a script for a P2PKH transaction (part 2 of 2)

数字签名(ECDSA)

到目前为止,我们还没有深入了解“数字签名”的细节。在本节中,我们将研究数字签名的工作原理,以及如何在不揭示私钥的情况下提供私钥的所有权证明。

比特币中使用的数字签名算法是椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm, 即 ECDSA)。 ECDSA是基于椭圆曲线私钥/公钥对的用于数字签名的算法,如椭圆曲线章节[Elliptic Curve Cryptography Explained]所述。 ECDSA用于脚本函数OP_CHECKSIG,OP_CHECKSIGVERIFY,OP_CHECKMULTISIG和OP_CHECKMULTISIGVERIFY。每当你锁定脚本中看到这些时,解锁脚本都必须包含一个ECDSA签名。

数字签名在比特币中有三种用途(请参阅下面的侧栏)。
第一,签名证明私钥的所有者,即资金所有者,已经授权花费这些资金。
第二,授权证明是不可否认的(不可否认性)。
第三,签名证明交易(或交易的具体部分)在签名之后没有也不能被任何人修改。

请注意,每个交易输入都是独立签名的。这一点至关重要,因为不管是签名还是输入都不必由同一“所有者”实施。事实上,一个名为 “CoinJoin” 的特定交易方案就使用这个特性来创建多方交易来保护隐私。

注意:每个交易输入和它可能包含的任何签名完全独立于任何其他输入或签名。 多方可以协作构建交易,并各自仅签名一个输入。

维基百科对 “数字签名 ”的定义:
数字签名是用于证明数字消息或文档的真实性的数学方案。 有效的数字签名给了一个容易接受的理由去相信:1)该消息是由已知的发送者(身份认证性)创建的; 2)发送方不能否认已发送消息(不可否认性;3)消息在传输中未被更改(完整性)。来源: https://en.wikipedia.org/wiki/Digital_signature

数字签名如何工作
数字签名是一种由两部分组成的数学方案:
第一部分是使用私钥(签名密钥)从消息(交易)创建签名的算法;
第二部分是使用同样的消息(交易)和一个公钥,允许任何人验证签名的算法。

创建数字签名
在比特币的ECDSA算法的实现中,被签名的“消息”是交易,或更确切地说是交易中特定数据子集的哈希值(参见签名哈希类型(SIGHASH))。签名密钥是用户的私钥,结果是签名:
这里写图片描述
这里的:

  • dA 是签名私钥
  • m 是交易(或其部分)
  • Fhash 是散列函数
  • Fsig 是签名算法
  • Sig 是签名的结果

ECDSA数学运算的更多细节可以在ECDSA Math章节中找到。
函数Fsig产生一个签名Sig, 该签名由两个值组成,通常称为R和S:

Sig = (R, S)

现在R和S的值已经计算出来了,它们会被序列化为字节流,使用一种称为“可辨别编码规则”(Distinguished Encoding Rules,即DER)的国际标准编码方案。

签名序列化(DER)
我们再来看看Alice创建的交易。 在交易输入中有一个解锁脚本,其中包含了Alice的钱包创建的DER编码的签名:

3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298c
ad530a863ea8f53982c09db8f6e381301

该签名是由Alice的钱包生成的由R和S组成的序列化的字节流,能够证明她拥有授权花费该输出的私钥。 序列化格式包含以下9个元素:

  • 0x30 — indicating the start of a DER sequence
  • 0x45 — the length of the sequence (69 bytes)
  • 0x02 — an integer value follows
  • 0x21 — the length of the integer (33 bytes)
  • R — 00884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb
  • 0x02 — another integer follows
  • 0x20 — the length of the integer (32 bytes)
  • S — 4b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813
  • A suffix (0x01) indicating the type of hash used (SIGHASH_ALL)

看看您是否可以使用此列表来解码 Alice 的序列化(DER编码)后的签名。 重要的数字是R和S;数据的其余部分是DER编码方案的一部分。

验证签名
要验证签名,必须有签名(R和S)、序列化的交易和公钥(对应于用于创建签名的私钥)。本质上,签名的验证意味着“只有生成此公钥的私钥的所有者,才能在此交易上产生此签名。”

签名验证算法采用消息(交易或其部分的哈希值)、签名者的公钥和签名(R和S),如果签名对该消息和公钥有效,则返回 TRUE 值。

签名哈希类型(SIGHASH)
数字签名被应用于消息,在比特币中,就是交易本身。签名意味着签字人对特定交易数据的承诺( commitment)。在最简单的形式中,签名适用于整个交易,从而承诺(commit)所有输入,输出和其他交易字段。但是,一个签名可以只承诺一个交易数据的子集,这对于我们将在本节中看到的许多场景是有用的。

比特币签名通过使用 SIGHASH 标志位来指示交易数据的哪一部分是会被哈希进而被私钥进行签名的。 SIGHASH 标志是单字节的,附加在签名末尾。每个签名都有一个SIGHASH标志,该标志在不同输入之间也可以不同。具有三个签名输入的交易可以具有三个不同SIGHASH标志的签名,每个签名签署(承诺)交易的不同部分。

记住,每个输入可能在其解锁脚本中包含一个签名。因此,包含多个输入的交易可以拥有具有不同SIGHASH标志的签名,这些标志在每个输入中承诺交易的不同部分。还要注意,比特币交易可能包含来自不同“所有者”的输入,他们在部分构建(这是无效的)的交易中可能仅签名一个输入,继而与他人协作收集所有必要的签名后再使交易生效。许多SIGHSASH标志类型,只有在你考虑到由许多参与者在比特币网络之外共同协作去更新仅部分签名了的交易,才具有意义。

有三个SIGHASH标志:ALL,NONE和SINGLE,如Table 6-3所示。
Table 6-3. SIGHASH types and their meanings
这里写图片描述

另外还有一个修饰符标志SIGHASH_ANYONECANPAY,它可以与前面的每个标志组合。 当设置ANYONECANPAY时,只有一个输入被签名,其余的输入(及其序列号 sequence numbers)允许进行修改。 ANYONECANPAY的值为0x80,并通过按位OR运算,得到如下所示的组合标志:
Table 6-4. SIGHASH types with modifiers and their meaning
这里写图片描述

SIGHASH标志在签名和验证期间应用的方式是创建交易的副本,删节副本交易中的某些字段(设置长度为零并清空),所得到的交易将被序列化,SIGHASH标志被添加到序列化的交易的结尾,并将结果哈希化 ,得到的哈希值即是被签名的“消息”。 根据使用不同的SIGHASH标志,交易的不同部分将被删节。 所得到的哈希值取决于交易中数据的不同子集。 添加SIGHASH是进行哈希前的最后一个步骤,签名也会对SIGHASH类型进行承若,因此SIGHASH是不能被更改的(例如,被矿工)。
小贴士:所有SIGHASH类型都会签名交易的nLocktime字段(请参阅[Transaction Locktime (nLocktime)]部分)。 此外,SIGHASH类型本身在签名之前已经附加到交易,因此一旦签名它就不能被修改。

在Alice的交易(参见序列化签名(DER)的列表)的例子中,我们看到DER编码的签名的最后一部分是01,这是SIGHASH_ALL标志。这会锁定交易数据,因此Alice的签名承诺的是所有的输入和输出的状态。 这是最常见的签名形式。

我们来看看其他一些SIGHASH类型,以及如何在实践中使用它们:

ALL | ANYONECANPAY
这种构造可以用来做“众筹”交易,试图筹集资金的人可以用单笔输出来构建一个交易,单笔输出将“目标”金额付给众筹发起人。这样的交易显然是无效的,因为它没有输入。但是现在其他人可以通过添加自己的输入作为捐赠来修改它,他们用ALL | ANYONECANPAY签名自己的输入,除非收集到足够的输入以达到输出的数值,否则交易无效。每次捐赠是一项“抵押”,直到募集到整个目标金额后才能由募款人收取。

NONE
该结构可用于创建特定数量的“不记名支票”或“空白支票”。它对输入进行承诺,但允许输出锁定脚本被更改。任何人都可以将自己的比特币地址写入输出锁定脚本并兑换交易。然而,输出值本身被签名锁定。

NONE | ANYONECANPAY
这种构造可以用来建造一个“吸尘器”。在他们的钱包中拥有微小UTXO的用户无法花费这些费用,因为手续费用超过了这些微小UTXO的价值。借助这种类型的签名,微小UTXO可以捐赠给任何人以便聚集,并随时消费。

有一些修改或扩展SIGHASH系统的建议。比如Blockstream的Glenn Willen提出的Bitmask Sighash Modes建议,作为Elements项目的一部分。这旨在为SIGHASH类型创建一个灵活的替代品,允许“任意的,矿工可重写的输入输出位掩码”来表示“更复杂的合同预付款方案,例如分布式资产交易所中的带变更的已签名报价”。

注意: 您不会在用户的钱包应用程序中看到SIGHASH标志作为一个功能呈现。 除了一些少数例外,钱包会构建P2PKH脚本,并使用SIGHASH_ALL标志进行签名。 要使用不同的SIGHASH标志,您必须编写软件来构造和签名交易。 更重要的是,SIGHASH标志可以被专用的比特币应用程序使用,从而实现新颖的用途。

ECDSA数学
如前面所述,签名是由数学函数 Fsig 创建的,该函数创建了一个由R和S组成的签名。在本节中,我们将查看函数 Fsig 的更多细节。

签名算法首先生成一个临时私公钥对。 在相关涉及的签名私钥和交易哈希经过一些变换之后,该临时密钥对用于计算R和S值。

临时密钥对是基于一个随机数k,该随机数k用作临时私钥。 基于k,我们生成相应的临时公钥P(以P = k*G计算)。数字签名的R值则是临时公钥P的x坐标。

基于这些,算法按如下方法计算签名的S值:
这里写图片描述

  • k is the ephemeral private key
  • R is the x coordinate of the ephemeral public key
  • dA is the signing private key
  • m is the transaction data
  • p is the prime order of the elliptic curve

验证是签名生成函数的相反过程,使用R,S值和公钥来计算一个P值,该值是椭圆曲线上的一个点(签名创建中使用的临时公钥):
这里写图片描述

  • R and S are the signature values
  • Qa is Alice’s public key
  • m is the transaction data that was signed
  • G is the elliptic curve generator point

如果计算出的点P的x坐标等于R,则验证者可以得出结论,该签名是有效的。
请注意,在验证签名时,私钥既不知道也不显示。

小贴士:ECDSA的数学很复杂,难以理解。 网上有一些很棒的指南可能有帮助。 搜索“ECDSA解释”或尝试这个:http://bit.ly/2r0HhGB

随机性在签名中的重要性
如我们在ECDSA Math中所看到的,签名生成算法使用随机密钥k作为临时私有/公钥对的基础。 k 的值不重要,只要它是随机的。如果使用相同的 k 值在不同的消息(交易)上产生两个签名,那么签名私钥可以被任何人计算出来。在签名算法中重复使用相同的 k 值会导致私钥的暴露!

警告
如果在两个不同的交易中,在签名算法中使用相同的 k 值,则私钥可以被计算出并暴露给世界!

这不仅仅是一个理论上的可能性。我们已经看到在比特币交易签名算法的几种不同实现中由于这个问题导致了私钥的泄露。人们由于无意中重复使用 k 值而导致资金被窃取。重用 k 值的最常见原因是未正确初始化的随机数生成器。

为了避免这个漏洞,业界最佳实践是,不使用熵播种的随机数生成器生成 k 值( not generate k with a random-number
generator seeded with entropy),而是使用交易数据播种的确定性随机数生成流程。这确保每个交易产生不同的 k 值。在互联网工程任务组(Internet Engineering Task Force)发布的 RFC 6979 中定义了 k 值的确定性初始化的行业标准算法。

如果您正在实现一种用于在比特币中签名交易的算法,则必须使用 RFC 6979 或类似的确定性随机算法来确保为每个交易生成一个不同的 k 值。

比特币地址,余额和其他抽象概念

在本章开始,我们发现交易的 “幕后”看起来与它在钱包、区块链浏览器和其它面向用户的应用程序中呈现的非常不同。 许多来自前几章的简单而熟悉的概念,如比特币地址和余额,似乎在交易结构中不存在。 我们看到交易本身并不包含比特币地址,而是通过锁定和解锁比特币离散值的脚本进行操作。 这个系统中的任何地方都不存在余额,而每个钱包应用程序都明明白白地显示了用户钱包的余额。

既然我们已经探讨了一个比特币交易中实际包含的内容,我们可以检查更高层次的抽象概念是如何从交易的看似原始的组成部分中派生出来的。

我们再来看看Alice的交易是如何在一个受欢迎的区块浏览器(前面章节Alice与Bob’s Cafe的交易)中呈现的:
这里写图片描述
Figure 6-7. Alice’s transaction to Bob’s Cafe

在交易的左侧,区块浏览器将Alice的比特币地址显示为“发送者”。其实这个信息本身并不在交易中。当区块链接浏览器检索到交易时,它还检索在输入中引用的先前的交易,并从该旧交易中提取第一个输出。在该输出内是一个锁定脚本,将UTXO锁定到Alice的公钥哈希(P2PKH脚本)。区块浏览器提取公钥哈希,并使用Base58Check编码对其进行编码,以生成和显示表示该公钥的比特币地址。

同样,在右侧,区块浏览器显示了两个输出:第一个到Bob的比特币地址,第二个到Alice的比特币地址(作为找零)。再次,为了创建这些比特币地址,区块链浏览器从每个输出中提取锁定脚本,将其识别为P2PKH脚本,并从其内部提取公钥哈希。最后,区块链浏览器使用Base58Check编码重新编码了公钥哈希,以生成和显示比特币地址。

如果您去点击Bob的比特币地址,则区块链浏览器将显示如下图所示:
这里写图片描述
Figure 6-8. The balance of Bob’s bitcoin address

区块链浏览器显示了Bob的比特币地址的余额。但是比特币系统中却没有“余额”的概念。这么说吧,这里显示的余额其实是由区块链浏览器按如下方式构建出来的:

为了构建“总接收”数量,区块链浏览器首先解码以Base58Check编码的比特币地址,以检索编码在地址中的Bob的公钥的160位哈希值。然后,区块链浏览器搜索交易数据库,寻找P2PKH锁定脚本中包含Bob公钥哈希的输出。通过将所有输出的值相加,浏览器产生了总接收量。

构建当前余额(显示为“最终余额”)需要稍微更多的工作。区块链浏览器用一个独立的数据库保存当前未被花费的输出,称为UTXO集。为了维护这个数据库,区块链浏览器必须监视比特币网络,添加新创建的UTXO,并实时地删除已花费的UTXO,依据是当它们出现在未确认的交易中时。这是一个复杂的过程,不但要实时地跟踪交易在网络上的传播,同时还要保持与比特币网络的共识,以确保在正确的链上。有时,区块链浏览器未能保持同步,导致它所看到的UTXO集不完整或不正确。

通过UTXO集,区块链浏览器将引用了Bob的公钥哈希的所有未花费输出的值相加,产生了向用户显示的“最终余额”数目。

为了生成这张带有两个“余额”的图片,区块链浏览器必须索引并且搜索数十、数百、甚至是成千上万(hundreds of thousands)的交易。

总之,通过钱包应用程序、区块链浏览器和其他比特币用户界面呈现给用户的信息通常是由更高层次的抽象组成的,这需要通过搜索许多不同的交易,检查其内容,以及操纵其中包含的数据而得到。为了呈现出比特币交易类似于银行支票从发送人到接收人的这种简单视图,这些应用程序必须抽象许多底层细节。他们大多数关注常见的交易脚本类型:每个输入上的具有SIGHASH_ALL签名的P2PKH脚本。因此,虽然比特币应用程序以易于阅读的方式呈现了80%以上的交易,但有时候会被与常规交易不同的交易给难住。包含更复杂的锁定脚本,或不同SIGHASH标志,或很多输入和输出的交易显示了这些抽象的简单性和弱点。

每天,都有数百个不包含P2PKH的输出的交易在块上被确认。 区块浏览器经常以红色警告信息的方式呈现这些,表示他们无法解码出地址。以下链接包含未完全解码的最新的“奇怪交易”:https://blockchain.info/strange-transactions

正如我们将在下一章中看到的,这些并不一定是奇怪的交易。它们是包含比常见的P2PKH更复杂的锁定脚本的交易。我们将学习如何解码和了解更复杂的脚本及其支持的应用程序。


单词:
denominate 以(某种货币)为单位;将…命名为;称…为
This is an important characteristic of outputs that needs to be emphasized: outputs are
discrete and indivisible units of value, denominated in integer satoshis.

sophisticated 复杂的; 精致的; 富有经验的; 深奥微妙的;
somewhat 稍微,有点
inadvertently 疏忽地
promptly 迅速地,敏捷地
referred to as 被称为…
encumbrance 累赘; 负担; 阻碍; 妨碍;
revealing 泄露; 显示,展示; 揭示,揭露;
complicated 结构复杂的; 混乱的,麻烦的;

这章内容比较重要,更进一步的了解相关内容可参考:
理解比特币脚本
揭秘比特币和区块链(五):深入理解比特币交易的脚本
SIGHASH_NONE, SIGHASH_SINGLE 和SIGHASH_ALL

猜你喜欢

转载自blog.csdn.net/yzpbright/article/details/80494226