第六章. 交易

  • berry
  • 发布于 2025-02-08 11:16
  • 阅读 20

第六章. 交易

综合介绍

通常情况下,我们转移实物现金的方式与转移比特币的方式几乎毫无相似之处。实物现金是一种持有人凭证。Alice支付Bob的方式是将一定数量的代币,比如美元纸币,交给他。相比之下,比特币既不存在实物形式,也不以数字数据形式存在,Alice不能将比特币交给Bob,也无法通过电子邮件发送。

相反,想象一下Alice如何将对一块土地的控制权转移给Bob。她无法实际拿起土地并交给Bob。相反,存在一种记录(通常由地方政府维护),记录着Alice拥有的土地。Alice通过说服政府更新记录,将土地转让给Bob。

比特币的运作方式类似。每个比特币全节点都维护着一个数据库,显示Alice控制着一定数量的比特币。Alice通过说服这些全节点更新其数据库,将部分比特币转移到Bob手中。Alice用于说服全节点更新其数据库的信息称为交易。值得注意的是,这个过程在不直接使用Alice或Bob的身份的情况下进行,如第7章中所述。

在本章中,我们将解构一个比特币交易,并检查其各个部分,以了解它们如何以高度表现力和令人惊叹的可靠性促成价值转移。

序列化的比特币交易

在“探索和解码交易”第43页中,我们使用了启用了 txindex 选项的 Bitcoin Core 来检索 Alice 支付给 Bob 的交易的副本。让我们再次检索包含该付款的交易,如示例 6-1 所示。

示例 6-1. Alice 的序列化交易

$ bitcoin-cli getrawtransaction 466200308696215bbc949d5141a49a41\\ 38ecdfdfaa2a8029c1f9bcecd1f96177

01000000000101eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da13569 8679268041c54a0100000000ffffffff02204e0000000000002251203b41daba 4c9ace578369740f15e5ec880c28279ee7f51b07dca69c7061e07068f8240100 000000001600147752c165ea7be772b2c0acb7f4d6047ae6f4768e0141cf5efe 2d8ef13ed0af21d4f4cb82422d6252d70324f6f4576b727b7d918e521c00b51b e739df2f899c49dc267c0ad280aca6dab0d2fa2b42a45182fc83e81713010000 0000

Bitcoin Core 的序列化格式很特殊,因为它是用于对交易进行承诺并在比特币的 P2P 网络中传递它们的格式,但是其他程序可以使用不同的格式,只要它们传输了所有相同的数据。然而,Bitcoin Core 的格式对于传输的数据来说相当紧凑且易于解析,因此许多其他比特币程序都使用这种格式。

注意: 我们知道的唯一其他广泛使用的交易序列化格式是部分签名比特币交易(PSBT)格式,其在 BIP 174 和 370 中有文档记录(其他 BIP 中有扩展文档记录)。PSBT 允许一个不受信任的程序生成一个交易模板,可以由具有必要的私钥或其他敏感数据填充模板的受信任程序(例如硬件签名设备)进行验证和更新。为了实现这一点,PSBT 允许存储关于交易的大量元数据,使其比标准序列化格式要不紧凑得多。本书不会详细介绍 PSBT,但我们强烈建议支持使用多个密钥进行签名的钱包开发人员使用它。

示例 6-1 中以十六进制显示的交易在图 6-1 中被复制为一个字节映射。请注意,显示 32 字节需要 64 个十六进制字符。此映射仅显示顶级字段。我们将按照它们在交易中出现的顺序逐个检查它们,并描述它们包含的任何其他字段。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/6.1.png" alt=""><figcaption><p>图 6-1. 爱丽丝的交易的字节映射。</p></figcaption></figure>

版本

序列化比特币交易的前四个字节是其版本号。比特币交易的最初版本是版本 1(0x01000000)。所有比特币交易必须遵循版本 1 交易的规则,其中许多规则在本书中有所描述。

版本 2 的比特币交易是在 BIP68 软分叉更改比特币共识规则时引入的。BIP68 对序列字段增加了额外的约束条件,但这些约束条件仅适用于版本为 2 或更高的交易。版本 1 的交易不受影响。BIP112 也是与 BIP68 同一软分叉的一部分,它升级了一个操作码(OP_CHECKSEQUENCEVERIFY),如果它作为版本小于 2 的交易的一部分进行评估,将会失败。除了这两个更改之外,版本 2 的交易与版本 1 的交易相同。

保护预签名交易

在将交易广播到网络以包含在区块链中之前的最后一步是对其进行签名。然而,可以在不立即广播的情况下对交易进行签名。你可以将这种预签名交易保存几个月甚至几年,认为在以后广播时可以将其添加到区块链中。在此期间,甚至可能丢失访问签署另一笔消费资金所需的私钥(或密钥)。这并非是假设性的:建立在比特币之上的几个协议,包括闪电网络,都依赖于预签名交易。

对于协议开发人员来说,当他们帮助用户升级比特币共识协议时,这就产生了挑战。添加新的约束(例如 BIP68 对序列字段所做的)可能会使一些预签名交易无效。如果没有办法为等价交易创建新的签名,那么在预签名交易中使用的资金就永久丢失了。

这个问题通过将一些交易特性保留给升级解决,比如版本号。在 BIP68 之前创建预签名交易的任何人都应该使用版本 1 的交易,因此仅将 BIP68 的额外约束应用到版本 2 或更高版本的交易上不应该使任何预签名交易无效。

如果你实施了一个使用预签名交易的协议,请确保它不使用任何保留给未来升级的功能。比特币核心的默认交易中继策略不允许使用保留的功能。你可以通过在比特币主网上使用比特币核心的 testmempoolaccept RPC 来测试交易是否符合该策略。

截至撰写本文时,普遍考虑开始使用版本 3 交易的提案正在广泛讨论中。该提案并不旨在改变共识规则,而只是改变比特币全节点用于中继交易的策略。根据该提案,版本 3 交易将受到额外约束,以防止某些拒绝服务(DoS)攻击,我们将在第 212 页的“交易固定”中进一步讨论这些攻击。

扩展Marker和Flag字段

示例序列化交易的下两个字段是作为隔离见证(SegWit)软分叉对比特币共识规则进行的修改的一部分添加的。这些规则根据 BIP 141 和 BIP 143 进行了修改,但扩展的序列化格式在 BIP 144 中定义。

如果交易包含见证结构(我们将在第133页“见证结构”中描述),则标记必须为零(0x00),标志必须为非零。在当前的P2P协议中,标志应始终为1(0x01);备用标志保留用于以后的协议升级。

如果交易不需要见证堆栈,则标记和标志不得存在。这与比特币交易序列化格式的原始版本兼容,现在称为传统序列化。有关详细信息,请参阅第142页“传统序列化”。

在传统序列化中,标记字节将被解释为输入数量(零)。一笔交易不能有零个输入,因此标记向现代程序发出了使用扩展序列化的信号。标志字段提供了类似的信号,并且还简化了将来更新序列化格式的过程。

输入

输入字段包含其他几个字段,因此让我们首先在图6-2中显示这些字节的映射。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/6.2.png" alt=""><figcaption><p>图 6-2. Alice的交易输入字段中的字节映射</p></figcaption></figure>

交易输入列表长度字段

交易输入列表以一个整数开始,该整数表示交易中输入的数量。最小值为一个。虽然没有明确的最大值,但是对于交易大小的最大限制实际上将交易限制在几千个输入之内。该数字被编码为一个紧凑型无符号整数。

紧凑型无符号整数

比特币中的无符号整数通常具有较低的值,但有时可能具有较高的值,这些值通常使用CompactSize数据类型进行编码。CompactSize是可变长度整数的一种版本,因此有时被称为var_int或varint(例如,参见BIP 37和144的文档)。

在不同的程序中,包括不同的比特币程序中,使用了几种不同的可变长度整数。例如,比特币核心使用称为VarInts的数据类型来序列化其UTXO数据库,这与CompactSize不同。此外,在比特币区块头中,nBits字段使用称为Compact的自定义数据类型进行编码,这与compactSize无关。在讨论比特币交易序列化和比特币P2P协议的其他部分中使用的可变长度整数时,我们将始终使用全名compactSize。

对于0到252之间的数字,紧凑大小无符号整数与C语言数据类型uint8_t相同,这可能是任何程序员熟悉的本地编码方式。对于其他数字,可以将一个字节作为前缀添加到数字前,以指示其长度,但除此之外,这些数字看起来就像常规的C语言编码的无符号整数:

值范围 使用字节数 格式
≥ 0 && ≤ 252 (0xfc) 1 uint8_t
≥ 253 && ≤ 0xffff 3 0xfd后面跟着一个uint16_t类型的数字。
≥ 0x10000 && ≤ 0xffffffff 5 0xfd后面跟着一个uint32_t类型的数字。
≥ 0x100000000 && ≤ 0xffffffffffffffff 9 0xfd后面跟着一个uint64_t类型的数字。

每个交易输入必须包含三个字段:outpoint字段、带长度前缀的输入脚本字段和序列字段。我们将在接下来的章节中逐一查看这些字段。一些输入还包括见证堆栈,但这些在交易的末尾进行序列化,因此我们稍后会对其进行检查。

Outpoint字段

比特币交易是要求全节点更新其包含的货币所有权信息的请求。为了将她的一些比特币转让给鲍勃,Alice 首先需要告诉全节点如何找到她接收这些比特币的先前交易。由于比特币的控制是通过交易输出进行分配的,Alice 使用一个输出点字段指向先前的输出。每个输入必须包含一个输出点。

输出点包含一个32字节的 txid,用于指向 Alice 接收比特币的交易。这个 txid 使用比特币内部的字节顺序表示(参见“内部和显示字节顺序”)。

因为交易可能包含多个输出,Alice 还需要标识该交易的特定输出索引,称为其输出索引。输出索引是从零开始的4字节无符号整数。

当一个全节点遇到一个输出点时,它会使用这些信息来查找被引用的输出。全节点只需要查看区块链中的先前交易。例如,Alice 的交易包含在块774,958中。验证她的交易的全节点只会在该块及之前的块中查找她的输出点引用的先前输出,而不会查找任何更晚的块。在块774,958中,它们只会查看排在 Alice 交易之前的区块中的交易,这由块的默克尔树中叶子节点的顺序决定。

在找到先前的输出后,全节点从中获取了几个关键的信息:

  • 被分配给先前输出的比特币数量。这些比特币将在本次交易中转移。在示例交易中,先前输出的价值为100,000 satoshis。
  • 先前输出的授权条件。这些是必须满足的条件,才能花费分配给先前输出的比特币。
  • 对于已确认的交易,确认它的区块高度和该区块的中位时间过去(MTP)。这对于相对时间锁(在“作为共识强制相对时间锁的序列”中描述)和 coinbase 交易的输出(在“Coinbase 交易”中描述)是必要的。
  • 先前输出存在于区块链中(或作为已知的未确认交易),并且没有其他交易花费它的证据。比特币的一个共识规则禁止任何输出在有效的区块链中被花费超过一次。这是针对双重支付的规则:Alice 不能使用同一个先前输出在不同的交易中分别支付给 Bob 和 Carol。试图花费相同先前输出的两个交易称为冲突交易,因为只有一个可以包含在有效的区块链中。

不同的全节点实现在不同的时期尝试了跟踪先前输出的不同方法。比特币核心目前使用被认为是最有效的解决方案,即保留所有必要信息同时最小化磁盘空间的解决方案:它维护一个数据库,存储每个未花费交易输出(UTXO)及其关键元数据(如其确认块高度)。每当新的交易块到达时,它们花费的所有输出都将从 UTXO 数据库中移除,而它们创建的所有输出都将添加到数据库中。

内部和显示字节顺序

比特币在各种情况下使用哈希函数的输出,称为摘要。摘要为区块和交易提供唯一标识符;它们用于地址、区块、交易、签名等的承诺;并且摘要在比特币的工作量证明功能中进行迭代。在某些情况下,哈希摘要以一种字节顺序显示给用户,但在内部使用另一种字节顺序,这可能会引起混淆。例如,考虑我们示例交易中的前一个输出的事务ID(txid):

eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a

如果我们尝试使用Bitcoin Core来使用该txid检索该交易,我们会收到一个错误,并且必须反转其字节顺序:

$ bitcoin-cli getrawtransaction \> eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a

error code: -5

error message:

No such mempool or blockchain transaction.

Use gettransaction for wallet transactions.

$ echo eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a | fold -w2 | tac | tr -d "\n" 4ac541802679866935a19d4f40728bb89204d0cac90d85f3a51a19278fe33aeb

$ bitcoin-cli getrawtransaction \> 4ac541802679866935a19d4f40728bb89204d0cac90d85f3a51a19278fe33aeb 02000000000101c25ae90c9f3d40cc1fc509ecfd54b06e35450702...

这种奇怪的行为可能是早期比特币软件设计决策的无意后果。从实际角度来看,这意味着比特币软件的开发者需要记住将显示给用户的交易和区块标识符中的字节顺序反转。

在本书中,我们使用内部字节顺序一词来表示出现在交易和区块中的数据。我们使用显示字节顺序来表示显示给用户的形式。另一组常见术语是内部版本的小端字节顺序和显示版本的大端字节顺序。

输入脚本字段

\ 输入脚本字段是遗留交易格式的残余。我们的示例交易输入花费了一个不需要输入脚本中的任何数据的本地隔离见证输出,因此输入脚本的长度前缀设置为零(0x00)。

对于一个长度前缀的输入脚本示例,它花费了一个遗留输出,我们使用了一个在此书撰写时的最新区块中的任意交易: 6b483045022100a6cc4e8cd0847951a71fad3bc9b14f24d44ba59d19094e0a8c fa2580bb664b020220366060ea8203d766722ed0a02d1599b99d3c95b97dab8e 41d3e4d3fe33a5706201210369e03e2c91f0badec46c9c903d9e9edae67c167b 9ef9b550356ee791c9a40896

长度前缀是一个紧凑型无符号整数,表示序列化的输入脚本字段的长度。在这种情况下,它是一个字节(0x6b),表示输入脚本为107字节。我们将在第7章详细介绍解析和使用脚本。

序列号字段

输入的最后四个字节是它的序列号。这个字段的用途和含义随着时间的推移而发生了变化。

最开始是基于序列号的交易替换

\ 最初,序列字段旨在允许创建相同交易的多个版本,随后的版本可以替换之前的版本作为确认的候选项。序列号跟踪交易的版本。

例如,假设Alice和Bob想要在一场纸牌游戏上进行赌注。他们首先各自签署一笔交易,将一些资金存入一个脚本,该脚本要求双方签名才能支出,这是一个多重签名脚本(简称多签)。这被称为建立交易。然后,他们创建一个花费该输出的交易:

  • 交易的第一个版本,带有nSequence 0(0x00000000),将最初存入的资金返还给Alice和Bob。这被称为退款交易。此时他们都不会广播退款交易。只有在出现问题时才需要它。
  • Alice赢得了纸牌游戏的第一轮,因此交易的第二个版本,带有序列1,增加了支付给Alice的金额,并减少了Bob的份额。他们都签署了更新后的交易。同样,除非出现问题,否则他们不需要广播此版本的交易。
  • Bob赢得了第二轮,所以序列递增为2,减少了Alice的份额,增加了Bob的份额。他们再次签署但不广播。
  • 在经过了许多轮游戏,序列被递增,资金被重新分配,得到的交易被签署但不广播后,他们决定完成交易。创建一个具有最终资金余额的交易,他们将序列设置为其最大值(0xffffffff),以完成交易。他们广播了这个版本的交易,它在网络中传播,并最终被矿工确认。

我们可以看到序列的替换规则是如何工作的,如果我们考虑替代情况:

\ • 想象一下,如果Alice广播具有0xffffffff序列的最终交易,然后Bob广播其中一笔余额较高的早期交易。由于Bob的交易版本具有较低的序列号,使用原始比特币代码的全节点不会将其中继到矿工,而且也使用原始代码的矿工也不会挖掘它。

• 在另一种情况下,想象一下,Bob在Alice广播最终版本之前几秒钟广播了早期版本的交易。节点将中继Bob的版本,并且矿工将尝试对其进行挖掘,但是当带有较高序列号的Alice的版本到达时,节点也将中继它,并且使用原始比特币代码的矿工将尝试挖掘它,而不是Bob的版本。除非Bob幸运地在Alice的版本到达之前发现了一个区块,否则将确认Alice的交易版本。

这种类型的协议现在被称为支付通道。比特币的创始人在一封被归因于他的电子邮件中称这些交易为高频交易,并描述了协议中添加的一些功能以支持它们。我们稍后将了解到其他几个特性,也将发现当代支付通道的现代版本如何越来越多地在比特币中使用。

纯序列基础的支付通道存在一些问题。第一个问题是替换较低序列交易的规则仅仅是软件策略的问题。没有直接的激励使矿工更喜欢某一版本的交易而不是其他版本。第二个问题是第一个发送交易的人可能会很幸运,即使它不是最高序列的交易,也可能被确认。由于不幸的原因而导致的安全协议失败并不是一个非常有效的协议。

第三个问题是可以无限次替换交易的一个版本。每次替换都会消耗网络上所有中继全节点的带宽。例如,截至本书写作时,有约50,000个中继全节点;一个攻击者每分钟创建1,000个200字节的替代交易,每分钟将使用约10 GB的全节点网络带宽。除了他们每分钟20 KB的带宽成本和偶尔确认交易时的费用之外,攻击者不需要支付任何费用来承担对全节点运营者造成的巨大负担。

为了消除这种攻击的风险,早期比特币软件中禁用了原始类型的序列基础交易替换。数年来,比特币全节点不允许包含特定输入(由其输出点指示)的未确认交易被包含相同输入的不同交易替换。然而,这种情况并没有持续很久。

选择性交易替换信号

由于原始基于序列的交易替换存在滥用潜力而被禁用后,提出了一种解决方案:编写比特币核心和其他中继全节点软件,允许支付更高交易费率的交易替换支付更低费率的冲突交易。这被称为替代费率,简称RBF。一些用户和企业反对将交易替换支持重新添加到比特币核心中,因此达成了一项妥协,再次利用序列字段来支持替换。

正如BIP125所记录的那样,具有任何输入的未确认交易,其序列设置为低于0xfffffffe的值(即至少低于最大值2),向网络发出信号,表明其签名者希望它可以被支付更高费率的冲突交易替换。比特币核心允许这些未确认交易被替换,并继续禁止其他交易被替换。 这允许反对替换的用户和企业简单地忽略包含BIP125信号的未确认交易,直到它们被确认。

现代交易替换政策不仅涉及费率和序列信号,还有更多内容,我们将在“替代费率 (RBF) 费率提升”中看到。

相对时间锁定的一致性强制序列

\ 在“版本”一节中,我们了解到BIP68软分叉为版本号为2或更高的交易添加了一个新的约束。该约束适用于序列字段。

序列值小于2^31的交易输入被解释为具有相对时间锁定。这样的交易只能在前一个输出(由outpoint引用)的相对时间锁定量经过一段时间后才能包含在区块链中。例如,具有一个输入的交易,其相对时间锁定为30个区块,只能在至少有29个区块的区块中确认,这些区块在同一区块链中花费相同的输出。由于序列是每个输入的字段,一个交易可能包含任意数量的带有相对时间锁定的输入,所有这些输入都必须经过足够长的时间才能使交易有效。一个禁用标志允许一个交易包含具有相对时间锁定(序列<2^31)和不具有相对时间锁定(序列≥2^31)的输入。

序列值可以以区块或秒为单位指定。一个类型标志用于区分计算区块的值和计算以秒为单位的时间的值。类型标志设置在第23个最低有效位(即值1<<22)。如果类型标志被设置,则序列值将被解释为512秒的倍数。如果类型标志未设置,则序列值将被解释为区块数。

在将序列解释为相对时间锁定时,只考虑16位最低有效位。一旦标志(位32和23)被评估,序列值通常会被使用16位掩码“掩盖”(例如,sequence & 0x0000FFFF)。512秒的倍数大致相当于区块之间的平均时间间隔,因此从16位(2^16)中得到的最大相对时间锁定的值,无论是以区块还是秒为单位,都略多于一年。

图6-3显示了由BIP68定义的序列值的二进制布局。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/6.3.png" alt=""><figcaption><p>图 6-3. BIP68对序列编码的定义(来源:BIP68)</p></figcaption></figure>

请注意,任何使用序列设置相对时间锁定的交易也会发送用于选择性接受费用替换的信号,如“选择性交易替换信号”中所述。

输出

交易的输出字段包含与特定输出相关的几个字段。与我们对输入字段所做的一样,我们将从示例交易中的输出字段的特定字节开始,并将这些字节显示为图6-4中的映射。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/6.4.png" alt=""><figcaption><p>图 6-4. 来自Alice交易的outputs字段的字节映射</p></figcaption></figure>

交易输出列表长度字段

与交易的inputs部分相似,outputs字段的开头包含一个计数,表示此交易中的输出数量。它是一个compactSize整数,必须大于零。

示例交易有两个输出。

转出数量字段

特定输出的第一个字段是其金额,在Bitcoin Core中也称为“value”。这是一个8字节的有符号整数,表示要转移的satoshis数量。Satoshis是比特币链上交易中可以表示的最小单位。1比特币等于1亿satoshis。

比特币的共识规则允许输出的值最小为零,最大为2100万比特币(2.1千万亿satoshis,比特币限制了总量为2100万比特币不变)。

不经济的输出和不允许的小额资金

尽管没有价值,但零价值的输出可以根据与任何其他输出相同的规则进行消费。但是,消费输出(将其用作交易的输入)会增加交易的大小,从而增加需要支付的费用。如果输出的价值小于额外费用的成本,则消费它就没有经济意义。这样的输出被称为不经济的输出。

零价值的输出始终是不经济的输出;即使交易的费率为零,它也不会为花费它的交易提供任何价值。然而,许多其他价值较低的输出也可能是不经济的,甚至是无意中的。例如,在当前网络上的典型费率下,一个输出可能会给交易增加更多的价值,而花费它的成本则更高——但是明天,费率可能会上升,使输出变得不经济。

全节点需要跟踪所有的未使用交易输出(UTXO),如“Outpoint”中所述,这意味着每个UTXO都会使全节点的运行变得稍微困难。对于包含大量价值的UTXO,有动机最终将它们花费掉,因此它们不会成为问题。但是,对于控制不经济的UTXO的人来说,没有动机花费它,这可能使其成为全节点运营者的永久负担。因此,比特币核心等几种全节点实现通过影响未确认交易的中继和挖矿来阻止不经济的输出的创建。\ 针对中继或挖矿创建新不经济输出的政策被称为尘埃政策,基于将具有非常小价值的输出与非常小尺寸的粒子进行比喻。比特币核心的尘埃政策很复杂,包含了几个任意的数字,因此我们所知道的许多程序简单地假定小于546个聪的输出是尘埃,不会默认中继或挖矿。有时会有降低尘埃限制的提案,以及提高尘埃限制的反对提案,因此我们鼓励使用预签名交易或多方协议的开发人员检查政策是否在本书出版后发生了变化。

注意:自比特币问世以来,每个全节点都需要保留每个UTXO的副本,但这可能并不总是情况。几位开发人员一直在开发Utreexo项目,该项目允许全节点存储对UTXO集合的承诺,而不是数据本身。最小的承诺可能只有一两千字节大小——与比特币核心截至本书撰写时存储的逾五千兆字节相比,这是非常小的。

然而,Utreexo仍然需要一些节点存储所有UTXO数据,特别是为矿工和其他需要快速验证新区块的操作提供服务的节点。这意味着即使在可能的未来大多数节点使用Utreexo的情况下,不经济的输出仍然可能成为全节点的问题。

比特币核心关于尘埃的策略规则有一个例外:以OP_RETURN开头的输出脚本,称为数据载体输出,可以具有零值。 OP_RETURN操作码导致脚本立即失败,无论之后发生什么,因此这些输出永远无法被花费。这意味着全节点不需要跟踪它们,比特币核心利用这一特性,允许用户在区块链中存储少量任意数据,而不会增加其UTXO数据库的大小。由于这些输出是不可花费的,它们并不是不经济的——分配给它们的任何聪将永久不可花费——因此允许金额为零可确保没有聪被销毁。

输出脚本

输出金额后面是一个compactSize整数,指示输出脚本的长度,该脚本包含了需要满足的条件,以便花费比特币。根据比特币的共识规则,输出脚本的最小大小为零。

输出脚本的共识最大允许大小取决于何时进行检查。在交易输出中,输出脚本的大小没有明确的限制,但后续交易只能花费具有大小不超过10,000字节的先前输出。隐含地,输出脚本几乎可以与包含它的交易一样大,而交易几乎可以与包含它的区块一样大。

注意:长度为零的输出脚本可以由包含OP_TRUE的输入脚本花费。任何人都可以创建该输入脚本,这意味着任何人都可以花费空的输出脚本。有一种基本上无限数量的脚本,任何人都可以花费,并且比特币协议开发人员将其称为任何人都可以花费。比特币的脚本语言升级通常会将现有的任何人都可以花费的脚本添加新的约束条件,使其仅在新条件下可花费。应用程序开发人员不应该需要使用任何人都可以花费的脚本,但如果你确实需要,我们强烈建议你大声宣布你的计划给比特币用户和开发人员,以便未来的升级不会意外地干扰你的系统。

比特币核心对于中继和挖矿交易的策略有效地将输出脚本限制在几个模板上,称为标准交易输出。这最初是在发现了与脚本语言相关的几个早期错误之后实施的,并在现代比特币核心中保留下来,以支持任何人都可以花费的升级,并鼓励将脚本条件放置在P2SH赎回脚本、segwit v0见证脚本和segwit v1(taproot)叶脚本中的最佳实践。

我们将在第7章中查看每个当前标准交易模板,并学习如何解析脚本。

见证结构(Witness Structure)

在法庭上,证人是指证明他们目睹了重要事件发生的人。人类证人并不总是可靠的,因此法院有各种程序来审问证人,以便(理想情况下)只接受那些可靠的证据。

想象一下数学问题的证人会是什么样子。例如,如果重要的问题是 x + 2 == 4,某人声称他们目睹了解决方案,我们会问什么?我们想要一个数学证明,证明存在一个值,将其与2相加得到4。我们甚至可以省去人的需要,只使用提议的 x 值作为我们的证人。如果我们被告知证人是2,那么我们可以填写方程,检查它是否正确,并决定重要的问题已经解决了。

当花费比特币时,我们想要解决的重要问题是确定该花费是否得到了控制这些比特币的人或团体的授权。执行比特币共识规则的成千上万的全节点无法审问人类证人,但他们可以接受完全由用于解决数学问题的数据组成的证人。例如,一个值为2的见证将允许花费由以下脚本保护的比特币:

2 OP_ADD 4 OP_EQUAL

\ 显然,允许任何能够解决简单方程的人花费你的比特币是不安全的。正如我们将在第8章中看到的那样,一个不可伪造的数字签名方案使用的方程只能由某人在掌握某些他们能够保密的数据时解决。他们可以使用公共标识符引用该秘密数据。该公共标识符称为公钥,方程的解称为签名。

以下脚本包含一个公钥和一个操作码,要求与花费交易中的数据相对应的签名。就像我们简单示例中的数字2一样,签名就是我们的见证:

\<public key> OP_CHECKSIG

见证者是用于解决保护比特币的数学问题的值,在使用它们的交易中需要包含它们,以便全节点进行验证。在所有早期比特币交易中使用的传统交易格式中,签名和其他数据放置在输入脚本字段中。然而,当开发者开始在比特币上实现合约协议时,就像我们在“原始基于序列的交易替换”中所看到的那样,他们发现将见证者放置在输入脚本字段中存在几个重大问题。

循环依赖

很多比特币的合约协议涉及一系列按顺序签名的交易。例如,Alice和Bob想要将资金存入一个只能通过两人签名才能花费的脚本,但他们每个人也希望在对方不响应时能够拿回自己的钱。一个简单的解决方案是按顺序签署交易:

  • Tx0支付了来自Alice和Bob的资金到一个脚本的输出,该脚本要求Alice和Bob的签名才能花费。
  • Tx1花费了前一个输出到两个输出,一个是退还Alice她的钱,另一个是退还Bob他的钱(减去一笔小额用于交易费)。
  • 如果Alice和Bob在签署Tx0之前签署Tx1,那么他们都能随时获得退款。这个协议不需要他们中的任何一个相信另一个,因此是一个无需信任的协议。

在传统的交易格式中,这种构造存在一个问题,即每个字段,包括包含签名的输入脚本字段,都用于生成交易的标识符(txid)。Tx1的txid是Tx1中输入的outpoint的一部分。这意味着在Alice和Bob了解Tx0的签名之前,他们无法构造Tx1。但如果他们知道Tx0的签名,其中一个人可以在签署退款交易之前广播该交易,从而消除退款的保证。这就是一个循环依赖。

第三方交易篡改

一个更复杂的交易系列有时可以消除循环依赖,但是许多协议随后会遇到一个新的问题:通常可以用不同的方式解决相同的脚本。例如,考虑我们从“见证结构”中的简单脚本:

\ 2 OP_ADD 4 OP_EQUAL

我们可以通过在输入脚本中提供值2来使此脚本通过,但在比特币中有几种方法可以将该值放入堆栈中。以下只是其中几种方式:

OP_2

OP_PUSH1 0x02

OP_PUSH2 0x0002

OP_PUSH3 0x000002

...

OP_PUSHDATA1 0x0102

OP_PUSHDATA1 0x020002

...

OP_PUSHDATA2 0x000102

OP_PUSHDATA2 0x00020002

...

OP_PUSHDATA4 0x0000000102

OP_PUSHDATA4 0x000000020002

...

\ 每种对数字2在输入脚本中的替代编码都会生成一个略有不同的交易,具有完全不同的交易ID(txid)。每个不同版本的交易都花费了与其他版本相同的输入(outpoint),使它们彼此冲突。在有效的区块链中,一组冲突交易只能包含其中的一个版本。

想象一下,Alice创建了一个在输入脚本中带有OP_2的交易版本,并有一个输出支付给Bob。然后Bob立即将该输出花费给了Carol。任何网络上的人都可以用OP_PUSH1 0x02替换OP_2,与Alice的原始版本产生冲突。如果那个冲突的交易得到确认,那么就没有办法将Alice的原始版本包含在同一区块链中,这意味着Bob的交易无法花费其输出。尽管Alice、Bob和Carol都没有做错任何事情,但Bob向Carol的付款变得无效了。某人(第三方)能够改变(突变)Alice的交易,这就是所谓的不受欢迎的第三方交易篡改问题。

注意:有些情况下,人们希望他们的交易是可篡改的,比特币提供了几个功能来支持这一点,其中最重要的是我们将在“签名哈希类型(SIGHASH)”中学到的签名哈希(sighash)。例如,Alice可以使用签名哈希允许Bob帮助她支付一些交易费用。这会改变Alice的交易,但只以Alice想要的方式。因此,我们有时会在术语“交易可篡改性”前加上“不受欢迎的”一词。即使我们和其他比特币技术撰写者使用更短的术语,我们几乎肯定是在谈论不受欢迎的篡改变体。

第二方交易篡改

当传统交易格式是唯一的交易格式时,开发人员致力于提出方案来最小化第三方篡改,例如 BIP62。然而,即使他们能够完全消除第三方篡改,合约协议的用户仍然面临另一个问题:如果他们需要来自协议中其他参与者的签名,那么该人可以生成替代签名并更改 txid。

例如,Alice 和 Bob 将他们的资金存入一个需要双方签名才能支取的脚本中。他们还创建了一个退款交易,允许他们任何一方随时取回自己的资金。Alice 决定只花费部分资金,因此她与 Bob 合作创建了一系列交易:

  • Tx0 包含来自 Alice 和 Bob 的签名,将比特币花费到两个输出。第一个输出支付了部分 Alice 的资金;第二个输出将剩余的比特币退还给需要 Alice 和 Bob 签名的脚本。在签署此交易之前,他们创建了一个新的退款交易 Tx1。
  • Tx1 将 Tx0 的第二个输出花费到两个新的输出,一个给 Alice 作为她在联合资金中的份额,另一个给 Bob。Alice 和 Bob 在签署 Tx0 之前都签署了这笔交易。

这里没有循环依赖,如果我们忽略第三方交易篡改,这看起来应该为我们提供一个无需信任的协议。然而,Bitcoin 签名的一个特性是签名者在创建签名时必须选择一个大的随机数。即使签名的一切保持不变,选择不同的随机数也会产生不同的签名,就像如果你为两份相同合同的副本提供手写签名,每个物理签名都会略有不同一样。

签名的可变性意味着,如果 Alice 尝试广播 Tx0(其中包含 Bob 的签名),Bob 可以生成另一种签名,创建一个与不同 txid 的冲突交易。如果 Bob 的替代版本的 Tx0 被确认,那么 Alice 就无法使用预签名版本的 Tx1 来要求退款。这种变异称为不受欢迎的第二方交易篡改。

隔离见证

\ 随着早在2011年,协议开发者就知道如何解决循环依赖、第三方篡改和第二方篡改等问题。这个想法是避免将输入脚本包含在生成交易 txid 的计算中。回想一下,输入脚本中保存的数据的抽象名称是见证。将交易的其余数据与其见证分开,以便生成 txid 的想法称为隔离见证(Segregated Witness,简称 SegWit)。

明显的实现 SegWit 的方法需要对比特币的共识规则进行更改,这将与旧的全节点不兼容,也称为硬分叉。硬分叉带来了很多挑战,我们将在第 291 页的“硬分叉”中进一步讨论。

另一种实现 SegWit 的方法是在2015年底提出的。这将使用向后兼容的方式更改共识规则,称为软分叉。向后兼容意味着实现更改的全节点不得接受旧节点认为无效的任何区块。只要它们遵守这个规则,较新的全节点就可以拒绝旧全节点会接受的区块,从而使它们有能力执行新的共识规则(但前提是较新的全节点代表比特币用户之间的经济共识——我们将在第 12 章中探讨更新比特币的共识规则的细节)。

软分叉 SegWit 方法基于任何人可支配的输出脚本。以数字 0 到 16 中的任何一个数字开头,后跟 2 到 40 字节的数据的脚本被定义为隔离见证输出脚本模板。数字表示其版本(例如,0 是隔离见证版本 0,或者 SegWit v0)。数据称为见证程序。还可以将 SegWit 模板包装在 P2SH 承诺中,但我们在本章中不处理这个。

从旧节点的角度来看,这些输出脚本模板可以使用空的输入脚本进行支出。从了解新 SegWit 规则的新节点的角度来看,对隔离见证输出脚本模板的任何支付必须仅使用空的输入脚本进行支出。请注意这里的区别:旧节点允许空的输入脚本;新节点要求空的输入脚本。

\ 空的输入脚本使得见证不会影响 txid,消除了循环依赖、第三方交易篡改和第二方交易篡改。但是,由于不能在输入脚本中放置数据,使用隔离见证输出脚本模板的用户需要一个新的字段。该字段称为见证结构(Witness Structure)。

引入见证程序和见证结构使比特币变得更加复杂,但它遵循了增加抽象的现有趋势。回想一下第 4 章,最初的比特币白皮书描述了一个系统,比特币被接收到公钥(pubkeys)并使用签名(sigs)进行支出。公钥定义了谁有权花费比特币(控制相应私钥的人),而签名提供了验证,即支出交易来自控制私钥的人。为了使该系统更加灵活,比特币的初始版本引入了脚本,允许比特币被接收到输出脚本并使用输入脚本进行支出。后来对合同协议的经验启发了允许比特币被接收到见证程序并使用见证结构进行支出。不同版本比特币中使用的术语和字段如表 6-1 所示。

表6-1. 比特币中不同部分用于授权和认证数据的术语

授权(Authorization) 认证(Authentication)
Whitepaper Publick key Signature
Original(Legacy) Output script Input script
Segwit Witness program Witness structure

见证结构序列化

见证结构类似于输入和输出字段,包含其他字段,因此我们将从Alice交易的字节映射开始,如图6-5所示。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/6.5.png" alt=""><figcaption><p>图 6-5. Alice的交易见证结构的字节映射</p></figcaption></figure>

与输入和输出字段不同,整体的见证结构没有以包含的见证堆栈总数为开始。相反,这是由输入字段隐含的——每个交易的输入都有一个见证堆栈。

特定输入的见证结构确实以一个元素数量的计数开头。这些元素称为见证项。我们将在第7章详细探讨它们,但现在我们需要知道每个见证项都以一个表示其大小的紧凑尺寸整数为前缀。

传统的输入不包含任何见证项,因此它们的见证堆栈完全由一个计数为零的元素组成(0x00)。

Alice的交易包含一个输入和一个见证项。

锁定时间

序列化交易的最后一个字段是其锁定时间。这个字段是比特币最初的序列化格式的一部分,但最初只由比特币选择哪些交易挖矿的政策来执行。比特币最早的已知软分叉添加了一个规则,从区块高度31,000开始,禁止在区块中包含一个交易,除非满足以下规则之一:

  • 交易指示应该有资格包含在任何块中,将其锁定时间设置为0。
  • 交易指示它想要限制可以包含在其中的块,将其锁定时间设置为小于500,000,000的值。在这种情况下,该交易只能包含在高度等于锁定时间或更高的块中。例如,具有锁定时间为123,456的交易可以包含在块123,456或任何更高的块中。
  • 交易指示它想要限制可以包含在区块链中的时间,将其锁定时间设置为500,000,000或更高的值。在这种情况下,该字段被解析为纪元时间(自1970年01月01日T00:00 UTC以来的秒数),并且该交易只能包含在具有大于锁定时间的中位时间过去(MTP)的块中。 MTP通常比当前时间晚大约一两个小时。有关MTP的规则,请参阅“过去的中位时间(MTP)”第280页。

Coinbase交易

每个区块中的第一笔交易都是一个特例。大多数较旧的文档称之为生成交易,但大多数较新的文档称之为Coinbase交易(不要与名为“Coinbase”的公司创建的交易混淆)。

Coinbase交易是由包含它们的区块的矿工创建的,允许矿工领取该区块中所有交易支付的任何费用。此外,在区块6,720,000之前,矿工被允许领取一笔由以前从未流通过的比特币组成的补贴,称为区块补贴。矿工可以领取的区块的总金额——包括费用和补贴——称为区块奖励。 Coinbase交易的一些特殊规则包括:

  • 它们只能有一个输入。
  • 单个输入的输出点必须具有空的txid(完全由零组成)和最大的输出索引(0xffffffff)。这样做是为了防止Coinbase交易引用先前的交易输出,因为Coinbase交易支付费用和补贴,引用先前的交易输出至少会令人困惑。
  • 在普通交易中包含输入脚本的字段称为coinbase。正是这个字段赋予了Coinbase交易其名称。coinbase字段的长度必须至少为两个字节,但不超过100个字节。虽然此脚本不会执行,但是对于签名检查操作(sigops)的传统交易限制仍然适用,因此应在其中放置的任意数据应以数据推送操作码为前缀。自2013年BIP34定义的一个软分叉以来,此字段的前几个字节必须遵循我们将在“Coinbase数据”中描述的其他规则。
  • 输出的总和不得超过区块中所有交易所收取的费用和补贴的价值。补贴从每个区块的50个比特币开始,每210,000个区块减半一次(大约每四年)。补贴值会向下舍入到最近的satoshis。
  • 自2017年在BIP141中记录的segwit软分叉以来,包含消费segwit输出的交易的任何区块都必须包含一个输出到Coinbase交易的输出,该输出承诺该区块中所有交易(包括它们的见证)。我们将在第12章探讨这种承诺。 Coinbase交易可以具有任何在普通交易中有效的其他输出。但是,在Coinbase交易获得100次确认之前,消费其中一笔输出的交易不能包含在任何区块中。这称为成熟规则,尚未获得100次确认的Coinbase交易输出称为未成熟。 大多数比特币软件不需要处理Coinbase交易,但它们的特殊性质意味着它们偶尔可能会导致未经设计以接受它们的软件出现异常问题。

权重和V字节(Vbytes)

每个比特币区块在包含的交易数据量方面都有限制,因此大多数比特币软件都需要能够测量其创建或处理的交易。比特币的现代计量单位称为权重。权重的另一种版本是V字节(Vbytes),其中四个权重单位等于一个V字节(Vbytes),这样可以轻松地与传统比特币区块中使用的原始字节测量单位进行比较。

区块的权重限制为4百万。区块头占据240个权重。另外一个字段,交易计数,使用4或12个权重。所有剩余的权重可以用于交易数据。

要计算交易中特定字段的权重,需要将该序列化字段的大小(以字节为单位)乘以一个因子。要计算交易的权重,请将其所有字段的权重相加。交易中每个字段的因子如表6-2所示。为了提供示例,我们还计算了本章中从Alice到Bob的示例交易中每个字段的权重。

选择这些因子以减少花费未使用交易输出(UTXO)时使用的权重。这有助于避免不经济的输出的创建,如“不经济的输出和禁止灰尘”中所述。

表6-2. 比特币交易中所有字段的权重因子

<table><thead><tr><th width="228">字段</th><th>因子</th><th>Alice的交易权重</th></tr></thead><tbody><tr><td>Version</td><td>4</td><td>16 (因为version占4个字节,乘以4后即为16),16权重=4Vbytes</td></tr><tr><td>Marker & Flag</td><td>1</td><td>2</td></tr><tr><td>Inputs Count</td><td>4</td><td>4</td></tr><tr><td>Outpoint</td><td>4</td><td>144</td></tr><tr><td>Input script</td><td>4</td><td>14</td></tr><tr><td>Sequence</td><td>4</td><td>16</td></tr><tr><td>Output script</td><td>4</td><td>4</td></tr><tr><td>Amount</td><td>4</td><td>64 (2 outputs)</td></tr><tr><td>Output script</td><td>4</td><td>232 (2 outputs with different scripts)</td></tr><tr><td>Witness Count</td><td>1</td><td>1</td></tr><tr><td>Witness items</td><td>1</td><td>66</td></tr><tr><td>Lock time</td><td>4</td><td>16</td></tr><tr><td><strong>Total</strong></td><td><em>N/A</em></td><td><strong>569</strong></td></tr></tbody></table>

我们可以通过从比特币核心获取Alice的交易总额来验证我们的权重计算:

$ bitcoin-cli getrawtransaction 466200308696215bbc949d5141a49a41\\ 38ecdfdfaa2a8029c1f9bcecd1f96177 2 | jq .weight

569

Alice在本章开头的示例6-1中的交易以权重单位表示如图6-6所示。通过比较两个图像中各个字段的大小差异,可以看到因子的作用。

<figure><img src="https://img.learnblockchain.cn/masterbitcoin3/assets/6.6.png" alt=""><figcaption><p>图 6-6. Alice交易的字节映射</p></figcaption></figure>

比特币权重详细介绍可参见:比特币权重维基百科

历史遗留序列化

\ 历史遗留序列化格式是比特币交易中较旧的序列化格式,仍然用于许多交易。对于任何具有空见证结构的交易(仅在交易不花费任何见证程序时有效),必须在比特币P2P网络上使用历史遗留序列化。

历史遗留序列化不包括标记、标志和见证结构字段。在本章中,我们看了交易中的每个字段,并了解了它们如何向全节点传达有关要在用户之间转移的比特币的详细信息。我们只是简要地看了输出脚本、输入脚本和见证结构,这些脚本允许指定和满足限制谁可以花费什么比特币的条件。

了解如何构建和使用这些条件对确保只有Alice能够花费她的比特币至关重要,因此它们将是下一章的主题。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论