Skip to content

Latest commit

 

History

History
1051 lines (680 loc) · 73.2 KB

第七章.asciidoc

File metadata and controls

1051 lines (680 loc) · 73.2 KB

高级交易和脚本

概述

上一章我们介绍了比特币交易的基本元素,并研究了最常见的交易脚本类型,即P2PKH脚本。在本章中,我们将介绍更高级的脚本以及如何使用它来构建复杂条件下的交易。

首先,我们将介绍 multisignature 脚本。接下来,我们将看一下第二个常见的交易脚本 Pay-to-Script-Hash ,它打开了复杂脚本的世界。然后,我们将研究通过 timelocks 为比特币添加时间维度的新的脚本运算符。最后,我们将看看 Segregated Witness,这是对交易结构的架构更改。

多重签名

多重签名脚本设置了一个条件,N 个公钥记录在脚本中,并且需要其中至少 M 个提供签名才能解锁资金。这也被称为 M-of-N 方案,其中 N 是密钥的总数,M 是验证所需签名个数的阈值。例如,一个 2-of-3 的多重签名是三个公钥被列为潜在签名者并且其中至少两个必须被用来创建签名,从而创建有效的交易花费资金。

目前,标准的 多重签名脚本最多只能列出3个公钥,这意味着你可以执行从 1-of-1 到 1-of-3 之间的任意组合的多重签名。本书出版时,列出3个公钥的限制可能已经解除,因此请检查 IsStandard() 函数以查看网络当前接受的操作。请注意,3键的限制仅适用于标准(也称为“裸”)多重签名脚本,而不适用于包含在支付到脚本哈希(P2SH)中的多重签名脚本。 P2SH多重签名脚本限于15个键,最多允许15-of-15的多重签名。我们将在 支付到脚本哈希 Pay-to-Script-Hash (P2SH) 中学习P2SH。

M-of-N 多重签名条件的锁定脚本设置通常形式如下:

M <Public Key 1> <Public Key 2> ... <Public Key N> N CHECKMULTISIG

其中 N 是列出的公钥数量,M 是花费这笔支出所需的签名个数。

一个 2-of-3 多重签名条件的锁定脚本设置如下:

2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

上面的锁定脚本可以被包含签名和公钥对儿的解锁脚本满足:

<Signature B> <Signature C>

或者3个公钥中的任意两个对应的私钥生成的签名的组合

两个脚本组合起来形成下面的验证脚本

<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

执行时,只有在解锁脚本与锁定脚本设置的条件匹配时,此组合脚本才会评估为TRUE。在这种情况下,条件是解锁脚本是否具有来自3个公钥中的两个对应私钥的有效签名。

CHECKMULTISIG执行中的一个错误

在 CHECKMULTISIG 的执行过程中有一个错误,需要稍微解决一下。当 CHECKMULTISIG 执行时,它应该消耗堆栈中的 M + N + 2 个项目作为参数。 但是,由于该错误,CHECKMULTISIG 会弹出额外的值或超出预期的值。

让我们用前面的验证示例更详细地看一下:

<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

首先,CHECKMULTISIG+弹出顶部元素,它是 +N(在本例中为“3”)。然后它弹出 N 个元素,这是可签名的公钥。在这个例子中,是公钥 A,B 和 C 。然后,它弹出一个项目,即 M ,仲裁数(需要多少个签名)。这里 M = 2。此时,CHECKMULTISIG 应该弹出最后的 M 个元素,这是签名,并查看它们是否有效。然而,不幸的是,实现中的一个错误会导致 CHECKMULTISIG 弹出另一个元素( 总数为M + 1 )。额外的项目在检查签名时被忽略,因此它对 CHECKMULTISIG 本身没有直接影响。但是,必须存在额外的值,因为如果它不存在,当 CHECKMULTISIG 试图弹出空堆栈时,它将导致堆栈错误和脚本失败(将交易标记为无效)。由于额外的项目被忽略,它可以是任何东西,但通常使用 0。

由于这个bug成为了共识规则的一部分,现在必须永久复制。因此,正确的脚本验证将如下所示:

0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

所以,正确的解锁脚本不是

<Signature B> <Signature C>

而是:

0 <Signature B> <Signature C>

从现在起,如果你看到一个 multisig 解锁脚本,你应该在开始时看到一个额外的 0,其唯一目的是修正意外成为共识规则的错误。

支付到脚本哈希 Pay-to-Script-Hash (P2SH)

支付到脚本哈希(P2SH)是2012年推出的一种强大的新型交易,大大简化了复杂交易脚本的使用。为了解释对P2SH的需求,我们来看一个实际的例子。

[ch01_intro_what_is_bitcoin] 中,我们介绍了位于迪拜的电子产品进口商Mohammed。Mohammed公司的公司帐户广泛使用比特币的多重签名功能。多重签名脚本是比特币高级脚本功能的最常见用途之一,并且是一个非常强大的功能。Mohammed的公司为所有客户付款使用多重签名脚本,在会计术语中称为“应收账款”或AR。使用多重签名方案时,客户进行的任何付款都会被锁定,以至于他们需要至少两个签名才能从Mohammed及其合作伙伴或拥有备份密钥的律师处获得释放。像这样的多重签名方案提供公司治理控制并防止盗窃,盗用或损失。

最终的脚本很长,看起来是这样的:

2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 CHECKMULTISIG

尽管多重签名脚本是一个强大的功能,但它们使用起来很麻烦。对于前面的脚本,Mohammed必须在付款之前将此脚本传达给每位客户。每个客户都必须使用特殊的比特币钱包软件来创建自定义交易脚本,并且每个客户都必须了解如何使用自定义脚本创建交易。此外,由此产生的交易将比简单的支付交易大五倍,因为该脚本包含非常长的公钥。该特大交易的负担将由客户以费用的形式承担。最后,像这样的大型交易脚本将在每个完整节点的内存中的UTXO集中存储,直到耗尽内存为止。所有这些问题使得在实践中使用复杂的锁定脚本变得困难。

P2SH是为了解决这些实际困难而开发的,使复杂脚本的使用像支付比特币地址一样简单。通过P2SH支付,复杂的锁定脚本将被其数字指纹(一种加密哈希)所取代。当试图花费UTXO的交易在之后出现时,除了解锁脚本外,它还必须包含与锁定脚本的指纹相同的脚本。简而言之,P2SH的意思是“支付给与该哈希值相匹配的脚本,这个脚本将在稍后花费输出时使用”。

在P2SH交易中,由哈希值代替的锁定脚本称为 赎回脚本 redeem script,因为它在赎回时提供给系统,而不是作为锁定脚本。 Complex script without P2SH 显示没有P2SH的脚本,Complex script as P2SH 显示与P2SH编码的脚本相同。

Table 1. Complex script without P2SH

Locking Script

2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 CHECKMULTISIG

Unlocking Script

Sig1 Sig2

Table 2. Complex script as P2SH

Redeem Script

2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 CHECKMULTISIG

Locking Script

HASH160 <20-byte hash of redeem script> EQUAL

Unlocking Script

Sig1 Sig2 <redeem script>

如你所见,使用P2SH时,复杂的脚本详细说明了花费输出(赎回脚本)的条件,但不是在锁定脚本中显示。只有它的散列在锁定脚本中,而且赎回脚本本身稍后会作为解锁脚本的一部分在花费输出时呈现。这将费用和复杂性的负担从交易的发送者转移到了接收者(消费者)。

让我们看看Mohammed的公司,复杂的多重签名脚本,以及由此产生的P2SH脚本。

首先,Mohammed公司为所有客户的付款使用多重签名脚本:

2 <Mohammed's Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 CHECKMULTISIG

如果占位符被实际的公钥取代(这里显示为以04开头的520位数字),你可以看到该脚本变得非常长:

2
04C16B8698A9ABF84250A7C3EA7EEDEF9897D1C8C6ADF47F06CF73370D74DCCA01CDCA79DCC5C395D7EEC6984D83F1F50C900A24DD47F569FD4193AF5DE762C58704A2192968D8655D6A935BEAF2CA23E3FB87A3495E7AF308EDF08DAC3C1FCBFC2C75B4B0F4D0B1B70CD2423657738C0C2B1D5CE65C97D78D0E34224858008E8B49047E63248B75DB7379BE9CDA8CE5751D16485F431E46117B9D0C1837C9D5737812F393DA7D4420D7E1A9162F0279CFC10F1E8E8F3020DECDBC3C0DD389D99779650421D65CBD7149B255382ED7F78E946580657EE6FDA162A187543A9D85BAAA93A4AB3A8F044DADA618D087227440645ABE8A35DA8C5B73997AD343BE5C2AFD94A5043752580AFA1ECED3C68D446BCAB69AC0BA7DF50D56231BE0AABF1FDEEC78A6A45E394BA29A1EDF518C022DD618DA774D207D137AAB59E0B000EB7ED238F4D800 5 CHECKMULTISIG

整个脚本可以使用20字节的加密散列取代,首先应用SHA256散列算法,然后对结果应用RIPEMD160算法。

我们在命令行上使用 libbitcoin-explorer(bx)来生成脚本哈希,如下所示:

echo \
2 \
[04C16B8698A9ABF84250A7C3EA7EEDEF9897D1C8C6ADF47F06CF73370D74DCCA01CDCA79DCC5C395D7EEC6984D83F1F50C900A24DD47F569FD4193AF5DE762C587] \
[04A2192968D8655D6A935BEAF2CA23E3FB87A3495E7AF308EDF08DAC3C1FCBFC2C75B4B0F4D0B1B70CD2423657738C0C2B1D5CE65C97D78D0E34224858008E8B49] \
[047E63248B75DB7379BE9CDA8CE5751D16485F431E46117B9D0C1837C9D5737812F393DA7D4420D7E1A9162F0279CFC10F1E8E8F3020DECDBC3C0DD389D9977965] \
[0421D65CBD7149B255382ED7F78E946580657EE6FDA162A187543A9D85BAAA93A4AB3A8F044DADA618D087227440645ABE8A35DA8C5B73997AD343BE5C2AFD94A5] \
[043752580AFA1ECED3C68D446BCAB69AC0BA7DF50D56231BE0AABF1FDEEC78A6A45E394BA29A1EDF518C022DD618DA774D207D137AAB59E0B000EB7ED238F4D800] \
5 CHECKMULTISIG \
| bx script-encode | bx sha256 | bx ripemd160
54c557e07dde5bb6cb791c7a540e0a4796f5e97e

上述一系列命令首先将Mohammed的multisig redeem脚本编码为十六进制的序列化的比特币脚本。下一个 bx 命令计算其SHA256散列值。下一个 bx 命令再次使用RIPEMD160进行哈希运算,产生最终的脚本哈希:

Mohammed的赎回脚本的20字节哈希值是:

54c557e07dde5bb6cb791c7a540e0a4796f5e97e

P2SH交易使用以下锁定脚本将输出锁定到此哈希值,而不是之前更长的赎回脚本:

HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL

如你所见,它要短得多。与“支付到5个密钥的多重签名脚本”不同,P2SH等价交易是“支付到这个哈希值的脚本”。向Mohammed公司付款的客户只需在付款中包含更短的锁定脚本。当Mohammed和他的合作伙伴想要使用这个UTXO时,他们必须出示原始赎回脚本(用哈希值锁定UTXO的那个脚本)和解锁它的必要签名,如下所示:

<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG>

这两个脚本组合为两个阶段。首先,根据锁定脚本检查赎回脚本以确保哈希值匹配:

<2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG> HASH160 <redeem scriptHash> EQUAL

如果赎回脚本哈希值匹配,解锁脚本将自行执行,以解锁赎回脚本:

<Sig1> <Sig2> 2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG

本章介绍的几乎所有脚本都只能作为P2SH脚本实现。它们不能直接用在UTXO的锁定脚本中。

P2SH 地址

P2SH功能的另一个重要部分是将脚本哈希编码为地址的能力,如BIP-13中所定义的那样。P2SH地址是脚本的20字节散列的Base58Check编码,就像比特币地址是公钥的20字节散列的Base58Check编码一样。 P2SH地址使用版本前缀“5”,这导致以“3”开头的Base58Check编码地址。

例如,Mohammed的复杂脚本,通过哈希和Base58Check编码,生成P2SH地址 39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw。我们可以用 bx 命令来确认

echo \
'54c557e07dde5bb6cb791c7a540e0a4796f5e97e'\
 | bx address-encode -v 5
39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw

现在,Mohammed可以给他的客户提供这个“地址”,他们几乎可以使用任何比特币钱包进行简单付款,就像它是一个比特币地址一样。前缀3给他们一个暗示,这是一种特殊的地址类型,对应于脚本而不是公钥,但是它的作用方式与支付比特币地址的方式完全相同。

P2SH地址隐藏了所有的复杂性,因此付款人看不到脚本。

P2SH 的好处

与在锁定输出时直接使用复杂脚本相比,P2SH具有以下优点:

  • 复杂的脚本在交易输出中被更短的指纹代替,从而使交易数据更小。

  • 脚本可以编码为地址,发件人和发件人的钱包不需要复杂的工程来实现P2SH。

  • P2SH将构建脚本的负担转移给收件人,而不是发件人。

  • P2SH将长脚本的数据存储负担从输出中(存储在区块链中的UTXO集中)转移到输入中(仅存储在区块链中)。

  • P2SH将长文件的数据存储负担从当前时间(支付)转移到未来时间(花费时间)。

  • P2SH将长脚本的交易费用从发件人转移到收件人,收件人必须包含很长的兑换脚本才能使用。

赎回脚本和验证

在Bitcoin Core客户端版本0.9.2之前,Pay-to-Script-Hash通过 IsStandard() 函数仅限于标准类型的比特币交易脚本。这意味着消费交易中提供的赎回脚本只能是标准类型之一:P2PK,P2PKH或multisig。

从Bitcoin Cor客户端版本0.9.2开始,P2SH交易可以包含任何有效的脚本,使P2SH标准更加灵活,并允许对许多新型和复杂类型的交易进行实验。

你无法将P2SH放入P2SH赎回脚本中,因为P2SH规范不是递归的。另外,技术上可以在赎回脚本中包含 RETURN( 参见 数据记录输出 (RETURN) ),规则中的任何内容都无法阻止你这样做,但没有实际意义,因为在验证期间执行 RETURN 将导致交易被标记为无效。

请注意,因为赎回脚本在你尝试使用P2SH的输出之前未呈现给网络,所以如果你使用无效的赎回脚本的哈希锁定该输出,它将被忽略。 UTXO将被成功锁定,但你将无法花费这笔费用,包含赎回脚本的花费交易不会被接受,因为它是无效的脚本。这会产生风险,因为你可以将比特币锁定在以后不能使用的P2SH中。网络会接收对应于无效的赎回脚本的锁定脚本。

Warning

P2SH锁定脚本包含赎回脚本的散列,但不会提供有关赎回脚本本身内容的线索。即使赎回脚本无效,P2SH交易也将被视为有效的并被接受。你可能会意外锁定比特币,之后无法花费。

数据记录输出 (RETURN)

比特币的分布式时间戳账本,区块链(blockchain),具有远远超出支付范围的潜在用途。许多开发人员尝试使用交易脚本语言,利用系统的安全性和灵活性,应用于数字公证服务,股票证书和智能合约等。将比特币的脚本语言用于这些目的的早期尝试包括创建交易输出,在区块链上记录数据;例如,记录文件的数字指纹,使得任何人都可以通过引用该交易作为该文件在特定日期存在的证明。

使用比特币区块链来存储与比特币付款无关的数据是一个有争议的话题。许多开发人员认为这种使用是滥用,并希望阻止它。其他人则认为这是区块链技术强大功能的一个示例,并且希望鼓励这种实验。那些反对纳入未付款数据的人争辩说,它会导致“区块链膨胀”,使那些运行完整比特币节点的人承担存储区块链无意承载的数据带来的成本。此外,此类交易创建了不能用于支付的,使用20字节的目标比特币地址的UTXO。由于该地址用于数据,因此它不对应于私钥,生成的UTXO不会被花费;这是虚假的付款。因此,永远不会花费的这些交易永远不会从UTXO集中移除,并导致UTXO数据库的大小永远增加或“膨胀”。

在Bitcoin Core客户端的0.9版本中,通过引入 RETURN 运算符达成了一个折衷方案。 RETURN 允许开发人员将80个字节的非付款数据添加到交易输出中。但是,与使用“假”UTXO不同,RETURN 运算符会创建一个显式的 可验证不可消费 的输出,该输出不需要存储在UTXO集合中。 RETURN 输出记录在区块链中,因此它们消耗磁盘空间并会导致区块链大小的增加,但它们不存储在UTXO集中,因此不会使UTXO内存池膨胀,完整节点页不用承担昂贵的内存负担。

RETURN 脚本看起来如下

RETURN <data>

数据部分被限制为80字节,并且通常表示哈希,例如SHA256算法的输出(32字节)。许多应用程序在数据前加上前缀以帮助识别应用。例如,http://proofofexistence.com[Proof of Existence] 数字公证服务使用8字节前缀 DOCPROOF,十六进制ASCII编码为 44 4f 43 50 52 4f 4f 46。

请记住,没有对应于 RETURN 的“解锁脚本”,用于“花费” RETURN 输出。 RETURN 的全部意义在于你不能把钱锁定在那个输出中,因此它不需要被保存在UTXO集合中(潜在可花费的)—— RETURN 是可验证不可花费的。RETURN 通常是比特币金额为零的输出,因为分配给这种输出的任何比特币都会永久丢失。如果在交易中引用 RETURN 作为输入,脚本验证引擎将暂停验证脚本的执行并将交易标记为无效。RETURN 的执行本质上导致脚本以 FALSE “返回”并暂停。因此,如果你意外地将 RETURN 输出引用为交易中的输入,则该交易无效。

标准交易(符合 IsStandard() 检查的交易)只能有一个 RETURN 输出。但是,一个 RETURN 输出可以与任何其他类型的输出组合在一个交易中。

Bitcoin Core 0.10中增加了两个新的命令行选项。选项 datacarrier 控制是否中转和开采 RETURN 交易,默认设置为“1”以允许。选项 datacarriersize 接受一个数字参数,指定 RETURN 脚本的最大字节数,缺省为83字节,表示 RETURN 数据最多80个字节,加上 RETURN 操作码的一个字节,和 PUSHDATA 操作码的两个字节。

Note

RETURN 最初提出的最大限制为80个字节,但在发布功能时限制已减少到40个字节。2015年2月,在比特币核心版本0.10中,限制提高到80字节。节点可以选择不中转或使用 RETURN,或者只中转和开采包含少于80字节数据的 RETURN 交易。

时间锁 Timelocks

时间锁是对交易或输出的限制,只允许在某个时间点之后花费。比特币从一开始就具有交易级别的时间锁定功能。它由交易中的 nLocktime 字段实现。 2015年末和2016年中推出了两个新的时间锁功能,可提供UTXO级别的时间锁定。这些是 CHECKLOCKTIMEVERIFY 和 CHECKSEQUENCEVERIFY。

时间锁定对于推迟日期的交易非常有用,将资金锁定在未来的日期。更重要的是,时间锁将比特币脚本延伸到时间维度,为复杂的多步智能合约打开了大门。

交易时间锁 (nLocktime)

从一开始,比特币就具有交易级别的时间锁定功能。交易锁定时间是交易级别的设置(交易数据结构中的一个字段),用于定义交易有效的最早时间,并且可以在网络上中转或添加到区块链。Locktime也被称为 nLocktime ,来自Bitcoin Core代码库中使用的变量名称。在大多数交易中它被设置为0以表示立即传播和执行。如果 nLocktime 非零且低于5亿,会被解释为为区块高度,表示交易无效并且不会在指定块高度之前中转或包含在区块链中。如果它超过5亿,它会被解释为Unix纪元时间戳(自1970年1月1日以来的秒数),表示交易在指定时间之前无效。使用 nLocktime 指定未来区块或时间的交易必须由发起的系统持有,只有在它们生效后才传输到比特币网络。如果交易在指定的 nLocktime 之前传输到网络,交易将被第一个节点认为无效并拒绝,不会被中转到其他节点。 nLocktime 的使用等同于推迟日期的纸质支票。

交易锁定时间限制

nLocktime 具有局限性,虽然它允许一些输出在将来被花费,但不会使这些输出在那个时间之前不能被花费。我们用下面的例子来解释一下。

Alice签署了一笔交易,将其的一个输出指定到Bob的地址,并将 nLocktime 设置为3个月之后。Alice将该交易发送给了Bob。通过这次交易,Alice和Bob知道:

  • 在3个月过去之前,Bob不能发起赎回资金的交易。

  • Bob可能会在3个月后发起交易。

但是:

  • Alice可以创建另一个交易,在没有锁定时间的情况下重复使用相同的输入。因此,Alice可以在3个月过去之前花费相同的UTXO。

  • Bob无法保证Alice不这么做。

了解交易 nLocktime 的局限性非常重要。唯一的保证是鲍勃在3个月之前不能赎回,而无法保证鲍勃将获得资金。要达到这样的保证,时间限制必须放在UTXO上,并成为锁定脚本的一部分,而不是交易的一部分。这是通过称为 检查锁定时间验证 Check Lock Time Verify (CLTV) 的下一种时间形式实现的。

Check Lock Time Verify (CLTV)

2015年12月,一种新的时间锁形式作为软分叉升级引入了比特币。根据BIP-65中的规范,一种名为 CHECKLOCKTIMEVERIFYCLTV)的脚本操作符添加到脚本语言中。 CLTV 是每个输出的时间锁,而不是 使用 nLocktime 情况下的每个交易的时间锁。允许时间锁的应用更加灵活。

简而言之,通过在输出的赎回脚本中添加 CLTV 操作码,可以限制输出只能在指定的时间过后才能使用。

Tip

nLocktime 是交易级别的时间锁,CLTV 是基于输出的时间锁。

CLTV 并没有取代 nLocktime,而是限制特定UTXO,以使它们只能在 nLocktime 设置为更大或相等的值的未来交易中使用。

CLTV 操作码将一个参数作为输入,该参数以与 nLocktime(区块高度或Unix纪元时间)相同的格式表示。如 VERIFY 后缀所示,CLTV 是在结果为 FALSE 时停止执行脚本的操作码。如果结果为TRUE,则继续执行。

为了用 CLTV 锁定输出,可以在创建这笔输出的交易中,将其插入到输出的赎回脚本中。例如,如果Alice正在向Bob的地址支付,输出通常会包含如下所示的P2PKH脚本:

DUP HASH160 <Bob's Public Key Hash> EQUALVERIFY CHECKSIG

为了将其锁定一段时间,比如从现在开始3个月,这笔交易将带有如下的赎回脚本:

<now + 3 months> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <Bob's Public Key Hash> EQUALVERIFY CHECKSIG

其中 <now {plus} 3 months> 是从这笔交易被开采后3个月的区块高度或者时间戳估计,当前区块高度 + 12,960 (区块) 或者 当前Unix时间戳 + 7,760,000 (秒). 现在,不要在意 CHECKLOCKTIMEVERIFY 之后的 DROP 操作符,我们之后会解释。

当Bob尝试花费这个UTXO时,构建一个以UTXO作为输入的交易,在输入的解锁脚本中使用他的签名和公钥,并将交易的 nLocktime 设置为等于或大于 CHECKLOCKTIMEVERIFY 中Alice设置的 timelock,然后在比特币网络上广播交易。

Bob的交易被进行如下的评估,如果Alice设置的 CHECKLOCKTIMEVERIFY 的参数小于或等于消费交易的 nLocktime,则脚本执行继续(如同执行 "no operation" 或NOP操作码一样)。否则,脚本执行会停止,并且交易被视为无效。

更准确地说,CHECKLOCKTIMEVERIFY 失败并暂停执行,标记交易无效,如果达成以下条件(来源:BIP-65):

  1. 栈为空;或者

  2. 栈顶元素小于0;或者

  3. 栈顶元素锁定时间的类型(区块高度或时间戳)与 nLocktime 字段不同;或者

  4. 栈顶元素大于交易的 nLocktime 字段;或者

  5. 输入的 nSequence 字段为 0xffffffff。

Note

CLTV 和 nLocktime 使用相同的格式来描述时间锁,可以是区块高度,也可以是自Unix纪元以来的秒数。重要的是,当一起使用时,nLocktime 的格式必须与输出中的 CLTV 的格式匹配 —— 它们都必须表示区块高度,或以秒为单位的时间。

执行后,如果满足 CLTV,则其前面的时间参数将保留为栈顶元素,需要使用 DROP 将其删除,以正确执行后续脚本操作码。出于这个原因,你经常会在脚本中看到 CHECKLOCKTIMEVERIFY 和 DROP。

通过将 nLocktime 与 CLTV 结合使用,交易锁定时间限制 中描述的场景会发生变化。Alice不能再花费这笔资金了(因为它被Bob的密钥锁定),Bob在3个月的锁定时间到期之前也不能花费。

通过将时间锁功能直接引入脚本语言,CLTV 允许我们开发一些非常有趣的复杂脚本。

标准的定义参见 BIP-65 (CHECKLOCKTIMEVERIFY)

相对时间锁

nLocktime 和 CLTV 都是 绝对的时间锁 absolute timelocks,表示一个绝对的时间点。接下来我们要研究的两个时间锁功能是 相对时间锁 relative timelocks,它们指定从输出在区块链中被确认时开始的一段时间,作为花费输出的条件。

相对时间锁是有用的,它们允许两个或多个相互依赖的交易组成的交易链进行脱链处理,对一个依赖于前一个交易确认后一段时间的交易施加时间限制。换句话说,直到UTXO被记录在区块链上时,时钟才会开始计数。这个功能在双向状态通道(bidirectional state channels)和闪电网络(Lightning Networks)中特别有用,我们将在 [state_channels] 中看到。

相对时间锁与绝对时间锁一样,都是通过交易级功能和脚本级操作码实现的。交易级别的相对时间锁实现为 nSequence(每个交易输入中设置的字段)值的共识规则。脚本级别的相对时间锁使用 CHECKSEQUENCEVERIFY(CSV)操作码实现。

BIP-68和BIP-112于2016年5月作为共识规则的软分叉升级启用。

nSequence相对时间锁

通过设置 nSequence 字段,可以在交易的每个输入上设置相对时间锁。

nSequence的原始含义

nSequence 字段的本意在于(但从未正确实施)允许修改 mempool 中的交易。在该用途中,包含 nSequence 值低于 232 - 1(0xFFFFFFFF)的输入的交易指示尚未“完成”的交易。这样的交易将保留在 mempool 中,直到它被另一个花费相同的输入但具有更高的 nSequence 值的交易替代。一旦接收到输入的 nSequence 值为 0xFFFFFFFF 的交易,它将被视为“完成的”并被开采。

nSequence 的原始含义从未正确实现,nSequence 的值通常在不使用时间锁定的交易中被设置为0xFFFFFFFF。对于具有 nLocktime 或 CHECKLOCKTIMEVERIFY 的交易,必须将 nSequence 值设置为小于 231,才能使时间保护具有效果,如下所述

nSequence 作为共识执行的相对时间锁

自BIP-68启用以来,新的共识规则适用于包含 nSequence 值小于231的输入的任何交易。从编程的角度来说,这意味着如果最高有效位(第1<<31位)未设置为1,则表示“相对锁定时间”。否则(1<<31设置为1),nSequence 的值被保留用于其他用途,例如启用 CHECKLOCKTIMEVERIFY,nLocktime,Opt-In-Replace-By-Fee以及其他未来的开发。

具有小于 231 的 nSequence 值的交易输入被解释为具有相对时间锁。这种交易只有在输入已经过相对时间锁表示的时间后才有效。例如,具有 nSequence 为30个块的相对时间锁的一个输入的交易,仅在从输入中引用的UTXO被开采的时间起,至少经过30个块时才有效。由于 nSequence 是每个输入的字段,交易可能包含任意数量的时间锁定输入,所有这些输入都必须满足时间要求交易才有效。一个交易可以同时包括时间锁定的输入( nSequence < 231 )和没有时间锁定的输入( nSequence >= 231 )。

nSequence 值以块或秒为单位,但与我们在 nLocktime 中使用的格式略有不同。类型标志(type-flag)用于区分表示区块数还是表示时间(以秒为单位)。类型标志被设置在第23个最低有效位(即值1 << 22)中。如果类型标志为1,则 nSequence 值被解释为512秒的倍数。如果类型标志为0,则 nSequence 值将被解释为区块数。

当将 nSequence 解释为相对时间锁时,仅考虑16个最低有效位。一旦标志位(比特32和23)检测完成,通常使用 nSequence 的16位掩码(例如,nSequence & 0x0000FFFF )。

BIP-68 definition of nSequence encoding (Source: BIP-68) 展示了 nSequence 的二进制结构, 由 BIP-68 定义。

BIP-68 definition of nSequence encoding
Figure 1. BIP-68 definition of nSequence encoding (Source: BIP-68)

基于 nSequence 值共识执行的相对时间锁在BIP-68中定义。

使用CSV的相对时间锁

与CLTV和 nLocktime 一样,有一个在脚本中使用 nSequence 值作为相对时间锁的脚本操作码。该操作码是 CHECKSEQUENCEVERIFY,通常简称为 CSV。

在UTXO的赎回脚本中执行时,CSV 操作码仅允许输入的 nSequence 值大于或等于 CSV 参数的交易。从本质上讲,这限制了UTXO直到相对于UTXO开采的时间已经过去了一定数量的区块或秒之后才能被花费。

与CLTV一样,CSV 中的值必须与相应的 nSequence 值中的格式匹配。如果 CSV 指定的是区块数量,nSequence 也必须是区块数量。如果 CSV 指定的是秒,那么 nSequence 也必须是秒。

当几个(链接的)交易被创建和签名,但保持“脱链”状态不会传播时,使用 CSV 的相对时间锁特别有用。直到父交易被传播,开采和沉淀到相对时间锁定中指定的时间后,才能使用子交易。可以在 [state_channels][lightning_network] 中看到这个用例的应用。

CSV 在 BIP-112, CHECKSEQUENCEVERIFY 中详细定义。

过去中位时间 Median-Time-Past

作为启用相对时间锁的一部分,时间锁(绝对和相对)的“时间”计算方式有所变化。在比特币中,实际时间(wall time)和共识时间(consensus time)之间存在着微妙但重要的差异。比特币是一个去中心化的网络,这意味着每个参与者都有自己的时间视角。网络上的事件并不是每时每刻都在发生。必须在每个节点的角度考虑网络延迟。最终,所有内容都会同步,创建一个公共的账本。与 过去 一样,比特币每隔10分钟就会对账本的状态达成共识。

区块头中的时间戳是由矿工设置的。共识规则留有一定的余地来解决分散的节点之间的时钟精度差异。然而,这带来了一种不幸的激励,促使矿工在一个区块内对时间撒谎,以便通过纳入尚未成熟的时间锁定交易来收取额外的费用。有关更多信息,请参见下面的部分。

为了消除对撒谎的激励,并加强时间锁的安全性,BIP-113与关于相对时间锁的BIP同时提出并被启用,它定义了一个称为 过去中位时间 Median-Time-Past 的新的一直的时间测量方法。

Median-Time-Past 过去11个区块的时间戳并的中位数。中位时间成为共识时间,并用于所有时间锁的计算。通过取过去大约两个小时的中点,任何一个区块的时间戳影响都会减小。通过结合11个区块,没有一个矿工可以为了获得尚未成熟的时间锁定交易费用而影响时间戳。

Median-Time-Past改变了 nLocktime,CLTV,nSequence 和 CSV 的时间计算实现。由Median-Time-Past计算的共识时间总是比实际时间晚大约一个小时。如果你创建时间锁定交易,应该在估计 nLocktime,nSequence,CLTV 和 CSV 中编码的时间时考虑这一点。

Median-Time-Past 在 BIP-113 中被定义。

对抗费用狙击的时间锁防御 Timelock Defense Against Fee Sniping

费用狙击(Fee-sniping)是一种理论上的攻击场景,表示试图改写过去的区块的矿工“狙击”未来区块中更高费用的交易,来最大化盈利的方法。

例如,假设现在的最高块是块#100,000。一些矿工尝试重新开采区块 #100,000,而不是尝试开采区块#100,001来增长区块链。这些矿工可以选择在他们的候选区块#100,000中包含任何有效的交易(尚未开采)。他们不必通过相同的交易来重新开采区块。事实上,他们有动力选择最有利可图的(每KB最高费用)交易并包含在他们的区块中。它们可以包括任何在“旧”块#100,000中的交易,也可以包含来自当前mempool的任何交易。本质上,当他们重新创建块#100,000时,他们可以选择将交易从“现在”转移到重写的“过去”。

今天,这种攻击不是很有利可图,因为区块奖励远高于每个区块的总费用。但是在将来,交易费用将成为奖励的一大部分(甚至奖励的全部)。那时候,这种情况就不可避免了。

在Bitcoin Core创建交易时,为了防止“费用狙击”,会默认使用 nLocktime 将其限制为“下一个区块”。在我们的场景中,Bitcoin Core会在其创建的任何交易中将 nLocktime 设置为100,001。正常情况下,这个 nLocktime 不起作用 —— 无论如何,交易只能包含在#100001区块中;这是下一个区块。

但是,在区块链分叉攻击下,矿工们将无法从mempool中获取高额交易,因为所有这些交易都会被锁定到#100,001区块。他们只能使用当时有效的交易重新计算#100,000,实质上不会获得新的费用。

为此,Bitcoin Core将所有新交易的 nLocktime 设置为 <当前块#+ 1>,并将所有输入的 nSequence 设置为0xFFFFFFFE以启用 nLocktime。

流程控制脚本(条件语句)

比特币脚本的一个更强大的功能是流程控制,也称为条件语句。你可能熟悉多种语言中的 IF...THEN...ELSE 流程控制。比特币的条件语句看起来有点不同,但基本构造是相同的。

比特币条件操作码允许我们构建一种有两种解锁方式的赎回脚本,取决于条件语句的结果是 TRUE 还是 FALSE。例如,如果 x 为 TRUE,则赎回脚本为A,否则(ELSE),赎回脚本为B.

此外,比特币条件表达式可以无限“嵌套”,条件语句可以包含另一个条件语句。比特币流程控制脚本可用于构建具有数百或甚至数千个可能的执行路径的复杂脚本。嵌套没有限制,但共识规则会对脚本的最大大小(以字节为单位)施加限制。

比特币使用 IF,ELSE,ENDIF 和 NOTIF 操作码实现流程控制。此外,条件表达式可以包含布尔运算符,例如 BOOLAND,BOOLOR,和 NOT。

乍一看,你可能会对比特币的流程控制脚本感到困惑。这是因为比特币脚本是一种堆栈语言。 1 AND 1 逆序表示为 1 1 ADD。

在大多数传统(过程式)编程语言中,流程控制看起来是这样的:

Pseudocode of flow control in most programming languages
if (condition):
  code to run when condition is true
else:
  code to run when condition is false
code to run in either case

在类似比特币脚本的基于堆栈的语音中,逻辑条件放在 IF 之前,使其看起来是逆序的:

Bitcoin Script flow control
condition
IF
  code to run when condition is true
ELSE
  code to run when condition is false
ENDIF
code to run in either case

在阅读比特币脚本时,记住条件判断是在 IF 操作码之前的。

条件语句的 VERIFY 操作码

比特币脚本中另外一种条件形式是以 VERIFY 结尾的任何操作码。VERIFY 后缀表示如果所评估的条件不是 TRUE,脚本将立即终止执行,并且交易被视为无效。

与 IF 语句提供不同的执行路径不同,VERIFY 后缀用作 守护语句 guard clause, 只有满足前面的条件时继续执行。

例如,以下脚本需要Bob的签名和产生特定散列的原象(pre-image)(密钥)。必须满足这两个条件才能解锁:

A redeem script with an EQUALVERIFY guard clause.
HASH160 <expected hash> EQUALVERIFY <Bob's Pubkey> CHECKSIG

为了赎回这笔资金, Bob 必须创建提供原象(pre-image)和签名的解锁脚本:

An unlocking script to satisfy the above redeem script
<Bob's Sig> <hash pre-image>

在不提供原象的情况下,Bob无法执行到检查其签名的脚本部分。

这个脚本可以用 IF 语句写成:

A redeem script with an IF guard clause
HASH160 <expected hash> EQUAL
IF
   <Bob's Pubkey> CHECKSIG
ENDIF

Bob的解锁脚本是相同的:

An unlocking script to satisfy the above redeem script
<Bob's Sig> <hash pre-image>

带 IF 的脚本与使用 VERIFY 后缀的操作码的功能相同;他们都作为守护语句运行。但是,VERIFY 构造更高效,使用两个较少的操作码。

那什么时候使用 VERIFY,什么时候使用 IF 呢?如果我们只是附加一个先决条件(guard clause),那么 VERIFY 更好。但是,如果有多个执行路径(流程控制),那么需要使用 IF...ELSE 流程控制语句。

Tip

诸如 EQUAL 之类的操作码会将结果( TRUE 或 FALSE )推到堆栈上,用于后续操作码的判断。相反,操作码 EQUALVERIFY 不会在堆栈中留下任何内容。以 VERIFY 结尾的操作码都不会将结果留在堆栈上。

在脚本中使用流程控制

比特币脚本中,流程控制的一个常见用途是构建赎回脚本,提供多个执行路径,每种赎回方式都可以赎回UTXO。

看一个简单的例子,有两个签名者,Alice和Bob,任何一个都可以兑换。使用multisig时,这将表示为 1-of-2 的多重签名脚本。为了演示,我们使用 IF 语句做同样的事情:

IF
 <Alice's Pubkey> CHECKSIG
ELSE
 <Bob's Pubkey> CHECKSIG
ENDIF

看到这个赎回脚本,你可能会想:“条件在哪里?在 IF 语句之前没有任何东西啊!”

条件不是赎回脚本的一部分。而是在解锁脚本中提供,从而允许 Alice 和 Bob “选择” 他们想要的执行路径。

Alice使用以下解锁脚本进行赎回:

<Alice's Sig> 1

最后的 1 作为条件(TRUE),使 IF 语句执行Alice签名的第一个赎回路径。

如果Bob要赎回,他必须通过给 IF 语句提供一个 FALSE 值来选择第二个执行路径:

<Bob's Sig> 0

Bob的解锁脚本在栈上放置了 0,导致 IF 语句执行第二个( ELSE )脚本,从而需要Bob的签名。

由于 IF 语句可以嵌套,我们可以创建执行路径的“迷宫”。解锁脚本可以提供一个选择执行路径实际执行的“映射”:

IF
  script A
ELSE
  IF
    script B
  ELSE
    script C
  ENDIF
ENDIF

在这种情况下,有三个执行路径(脚本A,脚本B 和 脚本C)。解锁脚本以 TRUE 或 FALSE 值的顺序提供路径。例如,要选择路径 脚本B,解锁脚本必须以 1 0( TRUE,FALSE )结尾。这些值将被压入堆栈,以便第二个值( FALSE )作为堆栈顶部。外层的 IF 语句弹出 FALSE 并执行第一个 ELSE 语句。然后 TRUE 移动到栈顶,并由内部的(嵌套的)IF 判断,从而选择 B 执行路径。

使用这种构造,我们可以用数十或数百个执行路径构赎回脚本,每个脚本都提供了一种不同的方式来赎回UTXO。为了花费UTXO,我们构建一个解锁脚本,通过在每个流程控制点的栈上放置相应的 TRUE 和 FALSE 来选择执行路径。

复杂脚本示例

在本节中,我们将本章中的许多概念结合到一个示例中。

我们的例子使用了迪拜公司所有者Mohammed的故事,该公司经营进出口业务。

在这个例子中,Mohammed希望建立一个规则灵活的公司资本账户。他创建的方案需要根据时间锁进行不同级别的授权。多重签名方案的参与者是Mohammed,他的两个合伙人Saeed和Zaira,以及他们公司的律师Abdul。三位合伙人根据多数规则作出决定,即三位合伙人中的两位必须同意。但是,如果他们的密钥出现问题,他们希望他们的律师能够用三个合伙人中一个的签名来恢复资金。最后,如果所有合作伙伴都暂时没空或无法工作,他们希望律师能够直接管理帐户。

以下是Mohammed设计的实现此目标的赎回脚本:

Variable Multi-Signature with Timelock
01  IF
02    IF
03      2
04    ELSE
05      <30 days> CHECKSEQUENCEVERIFY DROP
06      <Abdul the Lawyer's Pubkey> CHECKSIGVERIFY
07      1
08    ENDIF
09    <Mohammed's Pubkey> <Saeed's Pubkey> <Zaira's Pubkey> 3 CHECKMULTISIG
10  ELSE
11    <90 days> CHECKSEQUENCEVERIFY DROP
12    <Abdul the Lawyer's Pubkey> CHECKSIG
13  ENDIF

Mohammed的脚本使用嵌套的 IF...ELSE 流程控制语句实现了三个执行路径。

在第一个执行路径中,这个脚本作为一个简单的 2-of-3 多重签名。此执行路径由第3行和第9行组成。第3行将multisig的法定数设置为 2(2/3)。这个执行路径可以通过在解锁脚本的末尾加上 TRUE TRUE 来选择:

Unlocking script for the first execution path (2-of-3 multisig)
0 <Mohammed's Sig> <Zaira's Sig> TRUE TRUE
Tip

这个解锁脚本开头的 0 是因为 CHECKMULTISIG 中的一个错误,它会从堆栈中弹出一个额外的值。额外的值被 CHECKMULTISIG 忽略,但它必须存在。正如 CHECKMULTISIG执行中的一个错误 中所述,推入 0(通常)是该bug的解决方法。

第二个执行路径只能在创建 UTXO 30天后才能使用。到时,它需要律师Abdul和三个合伙人之一的前面( 1-of-3 的多重签名 )。这通过第7行来实现,该行将multisig的法定数设置为 1。要选择此执行路径,解锁脚本将以 FALSE TRUE 结束:

Unlocking script for the second execution path (Lawyer + 1-of-3)
0 <Saeed's Sig> <Abdul's Sig> FALSE TRUE
Tip

为什么 FALSE TRUE?因为这两个值被推送到堆栈上,所以首先推入 FALSE ,然后再推入 TRUE。 因此 TRUE 被第一个 IF 操作码弹出。

最后,第三个执行路径允许律师Abdul单独花费资金,但只能在90天后。要选择此执行路径,解锁脚本必须以 FALSE 结尾:

Unlocking script for the third execution path (Lawyer only)
<Abdul's Sig> FALSE

尝试在纸上运行脚本以查看它在堆栈上的行为。

阅读本示例时需要考虑几件事情。看看你能否找到答案:

  • 为什么律师无法通过在解锁脚本上选择 FALSE 执行第三条路径来随时赎回?

  • 在UTXO开采之后的5天,35天和105天,分别可以使用的执行路径数量?

  • 如果律师失去了密钥,资金是否会流失?如果91天过去了,你的答案会改变吗?

  • 合伙人如何每隔29或89天“重置”时钟以防止律师获得资金?

  • 为什么这个脚本中的一些 CHECKSIG 操作码有 VERIFY 后缀,而其他的则没有?

隔离见证 Segregated Witness

隔离见证 Segregated Witness (segwit) 是比特币共识规则和网络协议的升级,由BIP-9提出并作为软分叉实施,于2017年8月1日在比特币主网启用。

在密码学中,术语“见证”用于描述密码谜题的解决方案。对比特币来说,“见证”能够满足放在未支付交易输出(UTXO)上的加密条件。

在比特币的情况下,数字签名是“见证”的一种类型,但更宽泛地来说,“见证”是能够满足UTXO所设置的条件,解锁并花费UTXO的任何解决方案。术语“见证”是“解锁脚本”或“scriptSig”的更一般的术语。

在segwit引入之前,交易中的每个输入之后都是解锁它的见证数据。见证数据作为每个输入的一部分嵌入在交易中。术语 segregated_witness 或简称 segwit 仅仅意味着将特定输出的签名或解锁脚本分离。考虑最简单的形式,“单独的scriptSig”或“单独签名”。

因此,隔离见证是比特币的体系结构变化,旨在将见证数据从交易的 scriptSig(解锁脚本)字段移动到伴随交易的单独的 witness 数据结构中。客户端可以选择是否附带见证数据请求交易。

在本节中,我们将看看隔离见证的好处,描述部署和实施此架构的机制,并演示如何在交易和地址中使用隔离见证。

隔离见证由以下BIP定义:

BIP-141

Segregated Witness 的主要定义。

BIP-143

版本0见证程序的交易签名验证

BIP-144

对等服务 - 新的网络消息和序列化格式

BIP-145

隔离见证的getblocktemplate(用于挖矿)更新

BIP-173

原生 v0-16 见证输出的 Base32 地址格式

为什么要隔离见证?

隔离见证是一种体系结构变化,它对比特币的可扩展性,安全性,经济效益和性能有以下影响:

交易可锻性 Transaction Malleability

通过将见证数据移动到交易外部,用作标识符的交易哈希将不再包含见证数据。由于见证数据是交易中唯一可以由第三方修改的部分(请参阅 交易标识符 Transaction identifiers ),因此去除它也消除了交易可锻性攻击的机会。使用隔离见证,交易哈希变得不可能由交易的创建者以外的任何人改变,这极大地改进了许多其他协议的实施,这些协议依赖于先进的比特币交易建设,例如支付通道,链式交易和闪电网络。

脚本版本控制 Script Versioning

通过隔离见证脚本的进入,每个锁定脚本前面都有一个 script_version 数字,类似于交易和区块的版本号。脚本版本号的添加允许脚本语言以向后兼容的方式升级(即使用软叉升级)来引入新的脚本操作符,语法或语义。以无中断方式升级脚本语言的能力将大大加速比特币的创新速度。

网络和存储的可扩展性 Network and Storage Scaling

见证数据通常是交易总规模的主要贡献者。更复杂的脚本,比如用于multisig或支付通道的脚本非常庞大。在某些情况下,这些脚本占交易数据的大部分(超过75%)。将见证数据移到交易之外,提高了比特币的可扩展性。节点可以在验证签名后裁剪见证数据,或者在进行简单付款验证时完全忽略见证数据。见证数据不需要传输到所有节点,也不需要被所有节点存储在磁盘上。

签名验证优化 Signature Verification Optimization

隔离见证升级了签名方法( CHECKSIG,CHECKMULTISIG 等)以降低算法的计算复杂度。在隔离之前,用于生成签名的算法需要一些与交易大小成正比的散列操作。数据散列的计算复杂度相对于签名操作是O(n2),在验证签名的所有节点上引入了大量的计算负担。使用segwit时,算法将复杂度降低到O(n)。

离线签名改进 Offline Signing Improvement

隔离见证签名包含了签名的散列中每个输入引用的值(金额)。以前,离线签名设备(如硬件钱包)必须在签署交易之前验证每个输入的数量。这通常是通过流式传输大量关于以引用为输入的交易的数据来完成的。由于金额现在是已签名的散列的一部分,因此离线设备不需要先前的事务。如果金额不匹配(由被入侵的系统篡改),签名将无效。

隔离见证如何工作

隔离见证看起来改变了交易如何构建,是交易层面的特性,但事实并非如此。相反,隔离见证是对如何花费单个UTXO的改变,因此是每个输出层面的特性。

交易可以花费隔离见证的输出或传统(内联见证)的输出,或同时花费两者。因此,将交易称为“隔离见证交易”没有什么意义。我们应该将具体的交易输出称为“隔离见证输出”。

当交易花费UTXO时,它必须提供见证。在传统的UTXO中,锁定脚本要求在花费UTXO的交易的输入部分提供 在线的 见证数据。然而,隔离见证UTXO指定了一个锁定脚本,它可以被输入之外的(隔离的)见证数据满足。

软分叉 (向后兼容)

隔离见证是输出和交易架构方式的重大变化。这种改变通常需要每个比特币节点和钱包同时改变以升级共识规则 —— 所谓的硬分叉。然而,隔离见证的引入具有较少的破坏性变化,是向后兼容的,被称为软分叉。这种类型的升级允许非升级软件忽略更改并继续运行而不会中断。

隔离见证构建的输出,使不能识别"见证"的旧系统仍然可以验证它们。对于旧的钱包或节点,隔离见证输出看起来像是任何人都可以花费的输出。这样的输出可以用一个空的签名来花费,交易内部没有签名(隔离的)并不会使交易失效。然而,较新的钱包和挖矿节点会看到隔离见证输出,并期望在交易的见证数据中找到有效的见证。

隔离见证输出和交易示例

让我们来看一些示例交易,看它们将如何随着隔离见证改变。首先看一下如何使用隔离见证程序来转换Pay-to-Public-Key-Hash(P2PKH)。然后,看一下Pay-to-Script-Hash(P2SH)脚本的隔离见证等价物。最后,我们将看看如何将之前的隔离见证程序嵌入到P2SH脚本中。

Pay-to-Witness-Public-Key-Hash (P2WPKH)

[cup_of_coffee] 中,Alice创建了一笔交易,向Bob购买一杯咖啡。该交易创建了一个值为0.015 BTC的P2PKH输出,该输出可由Bob使用。输出的脚本如下所示:

Example P2PKH output script
DUP HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 EQUALVERIFY CHECKSIG

使用隔离见证,Alice将创建一个 Pay-to-Witness-Public-Key-Hash (P2WPKH) 脚本, 看起来如下:

Example P2WPKH output script
0 ab68025513c3dbd2f7b92a94e0581f5d50f654e7

如你所见,隔离见证输出的锁定脚本比传统输出简单得多。它由推送到脚本计算堆栈的两个值组成。对于老的(不支持隔离见证的 nonsegwit-aware )比特币客户端来说,这看起来像是任何人都可以花费的输出,并且不需要签名(或者更确切地说,可以使用空签名)。对于一个更新的,支持segwit的客户端,第一个数字(0)被解释为版本号(witness version),第二部分(20字节)相当于被称为 witness program 的锁定脚本。20字节的见证程序就是公钥的散列,就像在P2PKH脚本中一样。

现在,我们来看看Bob用来花费这个输出的相应的交易。对于原始脚本(nonsegwit),Bob的交易必须在交易输入中包含签名

Decoded transaction showing a P2PKH output being spent with a signature
[...]
“Vin” : [
"txid": "0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2",
"vout": 0,
     	 "scriptSig": “<Bob’s scriptSig>”,
]
[...]

但是,要花费隔离见证的输出,交易在那个输入上没有签名。相反,Bob的交易包含一个空的 scriptSig 和一个在交易之外的隔离见证。

Decoded transaction showing a P2WPKH output being spent with separate witness data
[...]
“Vin” : [
"txid": "0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2",
"vout": 0,
     	 "scriptSig": “”,
]
[...]
“witness”: “<Bob’s witness data>”
[...]
钱包的P2WPKH构建

注意到P2WPKH只能由收款人创建,而不能由付款人从已知公钥,P2PKH脚本或地址转换,这一点非常重要。付款人无法知道收款人的钱包是否有能力构建隔离见证交易并花费P2WPKH输出。

此外,P2WPKH输出必须由 _压缩_公钥的散列构造。未压缩的公钥在segwit中是非标准的,并且可能会被未来的软分支明确禁用。如果P2WPKH中使用未压缩的公钥散列,则它可能是不可靠的,你可能会失去资金。 P2WPKH输出应该由收款人的钱包通过从其私钥导出的压缩公钥来创建。

Warning

P2WPKH应由收款人通过将压缩公钥转换为P2WPKH哈希来构造。你不应将P2PKH脚本,比特币地址或未压缩的公钥转换为P2WPKH见证脚本。

Pay-to-Witness-Script-Hash (P2WSH)

第二种见证程序对应于支付到脚本哈希(P2SH)的脚本。我们在 支付到脚本哈希 Pay-to-Script-Hash (P2SH) 中看到过这种类型的脚本。在这个例子中,Mohammed的公司使用P2SH来表示多重签名脚本。对Mohammed的公司的付款用这种锁定脚本编码:

Example P2SH output script
HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL

该P2SH脚本引用了 赎回脚本 redeem_script 的散列,该脚本定义了花费资金的 2-of-3 多重签名要求。为了使用这种输出,Mohammed的公司将在交易输入中提供赎回脚本(其哈希与P2SH输出中的脚本哈希匹配)以及满足赎回脚本所需的签名:

Decoded transaction showing a P2SH output being spent
[...]
“Vin” : [
"txid": "abcdef12345...",
"vout": 0,
     	 "scriptSig": “<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>”,
]

现在,让我们看看整个示例如何升级到segwit。如果Mohammed的客户使用兼容segwit的钱包,他们将创建一个付款,包含一个Pay-to-Witness-Script-Hash(P2WSH)输出,看起来像这样:

Example P2WSH output script
0 a9b7b38d972cabc7961dbfbcb841ad4508d133c47ba87457b4a0e8aae86dbb89

同样,与P2WPKH的例子一样,你可以看到隔离见证等效脚本更简单,并且省略了你在P2SH脚本中看到的各种脚本操作数。相反,隔离见证程序由推送到堆栈的两个值组成:见证版本(0)和赎回脚本的32字节SHA256散列。

Tip

虽然P2SH使用20字节 RIPEMD160( SHA256(script) ) 散列,P2WSH见证程序使用32字节 +SHA256(script)+散列。这种散列算法选择上的差异是故意的,用于区分两种类型的见证程序(P2WPKH和P2WSH)之间的哈希长度,并为P2WSH提供更强的安全性(P2WSH中的128位安全性,对比P2SH中的80位安全性)。

Mohammed的公司可以通过提供正确的赎回脚本和足够的签名来满足它,用于花费P2WSH的输出。作为见证数据的一部分,赎回脚本和签名都将作为消费交易的一部分进行隔离。在交易输入中,Mohammed的钱包会放置一个空的 scriptSig :

Decoded transaction showing a P2WSH output being spent with separate witness data
[...]
“Vin” : [
"txid": "abcdef12345...",
"vout": 0,
     	 "scriptSig": “”,
]
[...]
“witness”: “<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>”
[...]
P2WPKH 和 P2WSH 的区别

在前两节中,我们演示了两种类型的见证程序:Pay-to-Witness-Public-Key-Hash (P2WPKH)Pay-to-Witness-Script-Hash (P2WSH)。两种见证程序都由一个单字节版本号和一个较长的散列组成。它们看起来非常相似,但是却有着不同的解释:一个被解释为一个公钥的哈希,它被签名满足,另一个被解释为脚本的哈希,被一个赎回脚本满足。它们之间的关键区别在于哈希的长度:

  • P2WPKH 中公钥的哈希是 20 字节

  • P2WSH 中脚本的哈希是 32 字节

这是允许钱包区分两种见证程序的一个区别。通过查看散列的长度,钱包可以确定它是什么类型的见证程序,P2WPKH或P2WSH。

升级到隔离见证

从前面的例子可以看出,升级为隔离见证是一个两步的过程。首先,钱包必须创建特殊的隔离型输出。然后,这些输出可以被知道如何构建隔离见证交易的钱包花费。在这些例子中,Alice的钱包支持segwit,能够使用Segregated Witness脚本创建特殊输出。鲍勃的钱包也是支持segwit的,能够花费那些输出。从这个例子中可能不明显的是,在实践中,Alice的钱包需要知道Bob使用了一个支持segwit的钱包并可以使用这些输出。否则,如果Bob的钱包没有升级,当Alice试图向Bob进行segwit支付,那么Bob的钱包将无法检测到这些支付。

Tip

对于P2WPKH和P2WSH付款类型,付款人和收款人的钱包都需要升级才能使用segwit。此外,付款人的钱包需要知道收款人的钱包是否具有隔离见证功能。

隔离见证不会在整个网络中同时实施。而是向后兼容的升级,新老客户可以共存。钱包开发人员将独立升级钱包软件以添加隔离见证功能。当付款人和收款人都支持隔离见证时,可以使用P2WPKH和P2WSH付款类型。传统的P2PKH和P2SH将继续为没有升级的钱包工作。这留下了两个重要的场景,下一节将讨论这些:

  • 付款人的钱包不支持隔离见证的,向支持隔离见证的收款人钱包付款

  • 付款人的支持隔离见证的钱包通过地址识别和区分收款方是否支持隔离见证的能力

P2SH中嵌入的隔离见证

举个例子,假设Alice的钱包没有升级到segwit,但是Bob的钱包已升级并可以处理segwit交易。 Alice和Bob可以使用“旧”的非segwit交易。但是Bob可能想使用segwit,利用适用于隔离见证的折扣,降低交易费用。

在这种情况下,Bob的钱包可以构建一个内部包含segwit脚本的P2SH地址。Alice的钱包将其视为“正常”的P2SH地址,并且可以在不知道segwit的情况下付款。然后Bob的钱包可以通过segwit交易来花费这笔款项,充分利用segwit并降低交易费用。

两种形式的见证脚本,P2WPKH 和 P2WSH,都可以嵌入到P2SH地址中。第一个被记作P2SH(P2WPKH),第二个被记作P2SH(P2WSH)。

Pay-to-Script-Hash 中的 Pay-to-Witness-Public-Key-Hash

我们将研究的第一种见证脚本是P2SH(P2WPKH)。这是一个Pay-to-Witness-Public-Key-Hash见证程序,嵌入在Pay-to-Script-Hash脚本中,以便它可以被不知道segwit的钱包使用。

Bob的钱包用Bob的公钥构造了一个P2WPKH见证程序。这个见证程序之后被散列,并将结果编码为P2SH脚本。这个P2SH脚本转换为比特币地址,其中一个以“3”开头,正如我们在 支付到脚本哈希 Pay-to-Script-Hash (P2SH) 部分看到的那样。

Bob的钱包从我们之前看到的P2WPKH见证程序开始:

Bob’s P2WPKH witness program
0 ab68025513c3dbd2f7b92a94e0581f5d50f654e7

P2WPKH见证程序由见证版本和Bob的20字节公钥散列组成。

Bob的钱包然后对之前的见证程序进行散列,首先是SHA256,然后是RIPEMD160,产生另一个20字节的哈希值。

让我们使用命令行中的 bx 命令来重现:

HASH160 of the P2WPKH witness program
echo \
'0 [ab68025513c3dbd2f7b92a94e0581f5d50f654e7]'\
 | bx script-encode | bx sha256 | bx ripemd160
3e0547268b3b19288b3adef9719ec8659f4b2b0b

接着,将赎回脚本的哈希值转换为比特币地址。再次使用 bx:

P2SH address
echo \
'3e0547268b3b19288b3adef9719ec8659f4b2b0b' \
| bx address-encode -v 5
37Lx99uaGn5avKBxiW26HjedQE3LrDCZru

现在,Bob可以对客户展示这个地址,让他们为咖啡付费。Alice的钱包可以支付给 37Lx99uaGn5avKBxiW26HjedQE3LrDCZru ,就像支付给任何其他比特币地址一样。

为了向Bob付款,Alice的钱包会使用如下的P2HSH脚本锁定输出:

HASH160 3e0547268b3b19288b3adef9719ec8659f4b2b0b EQUAL

即使Alice的钱包不支持隔离见证,这笔付款也可以被Bob使用隔离见证交易消费:

Pay-to-Script-Hash 中的 Pay-to-Witness-Script-Hash

类似地,多重签名脚本或其他复杂脚本的P2WSH见证程序也可以嵌入到P2SH脚本和地址中,使任何钱包都可以进行segwit兼容的支付。

正如我们在 Pay-to-Witness-Script-Hash (P2WSH) 中看到的,Mohammed的公司正在对多重签名脚本使用隔离见证付款。为了使任何客户都能向他的公司付款(无论他们的钱包是否升级到了支持segwit的版本),Mohammed的钱包可以在一个P2SH脚本中嵌入P2WSH见证程序。

首先,Mohammed的钱包用SHA256(仅此一次)将赎回脚本进行了散列。让我们在命令行上使用 bx 来完成:

Mohammed’s wallet creates a P2WSH witness program
echo \
2 \ [04C16B8698A9ABF84250A7C3EA7EEDEF9897D1C8C6ADF47F06CF73370D74DCCA01CDCA79DCC5C395D7EEC6984D83F1F50C900A24DD47F569FD4193AF5DE762C587] \
[04A2192968D8655D6A935BEAF2CA23E3FB87A3495E7AF308EDF08DAC3C1FCBFC2C75B4B0F4D0B1B70CD2423657738C0C2B1D5CE65C97D78D0E34224858008E8B49] \
[047E63248B75DB7379BE9CDA8CE5751D16485F431E46117B9D0C1837C9D5737812F393DA7D4420D7E1A9162F0279CFC10F1E8E8F3020DECDBC3C0DD389D9977965] \
[0421D65CBD7149B255382ED7F78E946580657EE6FDA162A187543A9D85BAAA93A4AB3A8F044DADA618D087227440645ABE8A35DA8C5B73997AD343BE5C2AFD94A5] \
[043752580AFA1ECED3C68D446BCAB69AC0BA7DF50D56231BE0AABF1FDEEC78A6A45E394BA29A1EDF518C022DD618DA774D207D137AAB59E0B000EB7ED238F4D800] \
5 CHECKMULTISIG \
| bx script-encode | bx sha256
9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73

接下来,散列后的赎回脚本转换为P2WSH见证程序:

0 9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73

然后,使用SHA256和RIPEMD160对见证程序本身进行散列处理,生成一个新的20字节的哈希,就像传统的P2SH那样,我们使用 bx 实验:

The HASH160 of the P2WSH witness program
 echo \
'0 [9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73]'\
 | bx script-encode | bx sha256 | bx ripemd160
86762607e8fe87c0c37740cddee880988b9455b2

再然后,钱包从这个哈希值构建一个P2SH比特币地址,使用 bx 实验:

P2SH bitcoin address
echo \
'86762607e8fe87c0c37740cddee880988b9455b2'\
 | bx address-encode -v 5
3Dwz1MXhM6EfFoJChHCxh1jWHb8GQqRenG

现在,Mohammed的客户不需要必须支持segwit就可以支付到这个地址。要向Mohammed付款,钱包将用以下P2SH脚本锁定输出:

P2SH script used to lock payments to Mohammed’s multisig
HASH160 86762607e8fe87c0c37740cddee880988b9455b2 EQUAL

然后,Mohammed的公司可以利用segwit的好处(包括较低的交易费用),构建segwit交易来花费这些款项。

隔离见证地址

即使是在segwit启用后,大部分的钱包升级也需要一些时间。一开始,segwit将被嵌入P2SH,如我们在前一节中看到的那样,来方便地兼容支持segwit和不支持segwit的钱包。

然而,一旦钱包广泛支持segwit,就有必要将目击者脚本直接编码成为segwit的原生地址格式,而不是嵌入到P2SH中。

原生segwit地址格式定义在 BIP-173 中:

BIP-173

Base32 address format for native v0-16 witness outputs

BIP-173仅对见证脚本(P2WPKH和P2WSH)进行编码。它与非segwit P2PKH或P2SH脚本不兼容。与传统的比特币地址的Base58编码相比,BIP-173是Base32校验和编码。 BIP-173地址也称为 bech32 地址,发音为 "beh-ch thirty two",暗指使用“BCH”错误检测算法和32字符编码集。

BIP-173地址使用32个小写字母的字母数字字符集,经过仔细选择以减少误读或错误输入。通过只选择小写字母集,bech32更容易阅读,朗读,并且在QR码中的编码效率提高了45%。

BCH错误检测算法比以前的校验和算法(Base58Check)有了很大的改进,它不仅检测,还能纠正错误。地址输入接口(如表单中的文本框)可以检测并突出显示在检测错误时最可能出现错误的字符。

根据BIP-173规范,这里是一些 bech32 地址的示例:

Mainnet P2WPKH

bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4

Testnet P2WPKH

tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx

Mainnet P2WSH

bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3

Testnet P2WSH

tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7

如你所见,segwit bech32字符串长达90个字符,由三个部分组成:

人类可读的部分

"bc" 或 "tb" 标明主网( mainnet )还是测试网( testnet )。

分隔符

数字 "1", 不是32字符编码集的一部分,只当做分隔符出现。

数据部分

至少6个字母数字字符,校验和编码的见证脚本

此时,只有少数钱包接受或生成原生segwit bech32地址,但随着segwit的广泛使用,你将越来越多地看到这些地址。

交易标识符 Transaction identifiers

隔离见证的最大好处之一是它消除了第三方交易可锻性。

在segwit之前,交易的签名可以被第三方微妙地修改,改变它们的交易ID(散列),而不改变任何基本属性(输入、输出、数量)。这为拒绝服务攻击和攻击编写糟糕的钱包软件创造了机会,这些钱包假定未经确认的交易的哈希是不可变的。

通过引入隔离见证,交易有了两个标识符 txid 和 wtxid。传统的交易ID txid 是序列化交易的双SHA256散列,没有见证数据。交易的 wtxid 是具有见证数据的交易的新的序列化格式的双SHA256散列。

传统的 txid 的计算方式与nonsegwit交易完全相同。但是,由于segwit交易在每个输入中都有空的 scriptSig,因此不存在可由第三方修改的交易部分。因此,在segwit交易中,即使交易未经确认,txid 也是第三方不可变的。

wtxid 就像一个“扩展”的ID,因为这个哈希还包含了见证数据。如果交易不带有见证数据传输,那么 wtxid 和 txid 是相同的。注意,由于 wtxid 包含见证数据(签名),并且由于见证数据可能具有可锻性,所以应认为 wtxid 在交易确认之前具有可锻性。只有当交易的输入都是segwit输入时,segwit 交易的 txid 才能被认为是不可变的。

Tip

隔离见证交易有两个ID:txid 和 wtxid。 txid 是不包含见证数据的交易的哈希,wtxid 是包含见证数据的哈希。所有输入都为 segwit 输入的交易的 txid 不易受第三方交易可锻性影响。

隔离见证的新签名算法

隔离见证修改了四种签名验证函数(CHECKSIG,CHECKSIGVERIFY,CHECKMULTISIG 和 CHECKMULTISIGVERIFY)的语义,改变了交易保证哈希的计算方式。

比特币交易中的签名应用于_commitment hash_,这是根据交易数据计算的,锁定数据的特定部分,表明签名者对这些值的保证。例如,在简单的 SIGHASH_ALL 类型签名中,保证哈希包括所有输入和输出。

不幸的是,计算保证哈希的方式引入了验证签名的节点可能被迫执行大量哈希计算的可能性。具体而言,散列操作相对于交易中的签名操作的数量以 O(n2) 的复杂度增长。因此,攻击者可以创建带有大量签名操作的交易,导致整个比特币网络必须执行数百或数千次散列操作才能验证交易。

Segwit提供了通过改变保证哈希计算方式来解决这个问题的机会。对于segwit版本0见证程序,使用BIP-143中规定的改进的保证哈希算法进行签名验证。

新算法实现了两个重要目标。首先,散列操作的数量随着签名操作的数量逐渐以 O(n) 增长,减少了用过于复杂的交易创建拒绝服务攻击的机会。其次,保证散列现在还将每个输入的值(金额)作为散列的一部分,这意味着签名者无需“获取”并检查输入引用的前一个交易就可以保证特定的输入值。对于离线设备(例如硬件钱包),这大大简化了主机与硬件钱包之间的通信,消除了对以前的交易进行验证的需要。硬件钱包可以接受不受信任的主机“所声明的”输入值,因为如果输入值不正确则签名无效,硬件钱包在签名输入前不需要验证该值。

隔离见证的经济效益

比特币挖掘节点和完整节点会产生用于支持比特币网络和区块链的资源的成本。随着比特币交易量的增加,资源成本(CPU,网络带宽,磁盘空间,内存)也不断增加。矿工通过收取与每次交易的大小(字节)成比例的费用来补偿这些成本。非挖矿(Nonmining)完整节点没有得到补偿,蒙受了损失,因为他们需要运行一个权威的完全验证的全索引节点,可能是因为他们使用节点来经营比特币业务。

如果没有交易费用,比特币数据的增长可能会大幅增加。费用旨在通过基于市场的价格发现机制,将比特币用户的需求与交易对网络带来的负担相匹配。

基于交易规模的费用计算将交易中的所有数据视为成本相同的。但是从完整节点和矿工的角度来看,交易的某些部分承担了更高的成本。添加到比特币网络的每笔交易都会影响节点上四种资源的消耗:

硬盘空间

每笔交易都存储在区块链中,添加到区块链的总大小上。区块链存储在磁盘上,但是可以通过“删除”旧的交易来优化存储。

CPU

每笔交易都必须被验证,这需要CPU时间。

带宽

每笔交易都在网络上至少传输一次(通过泛洪传播),如果在块传播协议中没有进行任何优化,交易将作为块的一部分再次传输,从而对网络容量的影响加倍。

内存

验证交易的节点将UTXO索引或整个UTXO集保存在内存中,以加快验证。因为内存至少比磁盘贵一个数量级,所以UTXO集的增长不成比例地增加了运行节点的成本。

从列表中可以看出,并非交易的每个部分都对运行节点的成本或比特币支持更多交易的能力产生同等影响。交易中最昂贵的部分是新创建的输出,因为它们被添加到内存中的UTXO集合中。相比之下,签名(又名见证数据)为增加了最小的网络负担和节点运行成本,因为见证数据只被验证一次,然后再也不会使用。此外,在收到新的交易并验证见证数据之后,节点立即丢弃该见证数据。如果费用是根据交易规模计算的,而不区分这两种数据,那么市场化的费用激励就不符合交易实际施加的成本。实际上,目前的费用结构实际上鼓励了相反的行为,因为见证数据是交易的最大部分。

交易在其输入中花费UTXO,并在输出中创建新的UTXO。因此,一个输入数量大于输出数量的交易将导致UTXO集的减少,而一个输出数量大于输入数量的交易将导致UTXO集的增加。让我们考虑输入和输出之间的_差异_,并称之为"净增UTXO"("Net-new-UTXO")。这是一个重要的指标,因为它告诉我们一个交易将对网络中最昂贵的资源(即内存里的UTXO集)产生什么影响。Net-new-UTXO为正的交易增加负担,Net-new-UTXO为负的交易减少负担。因此,我们希望鼓励Net-new-UTXO为负或为0的交易。

让我们看一个例子,说明在有无隔离见证的情况下,交易费用计算产生了哪些激励。我们将看两个不同的交易。交易A是有3个输入2个输出的交易,Net-new-UTXO为-1。交易B是2个输入3个输出的交易,Net-new-UTXO为1,意味着它增加了一个UTXO到UTXO集,给整个比特币网络带来了额外的成本。这两笔交易都使用多重签名(2-of-3)脚本来说明复杂脚本如何增加隔离见证对费用的影响。假设交易费为每字节30 satoshi,见证数据拥有75%的费用折扣:

Without Segregated Witness

Transaction A fee: 25,710 satoshi

Transaction B fee: 18,990 satoshi

With Segregated Witness

Transaction A fee: 8,130 satoshi

Transaction B fee: 12,045 satoshi

这两种交易在实施隔离见证时都较为便宜。但是比较这两笔交易的成本,我们发现在隔离见证之前,Net-new-UTXO为负的交易费用较高。在隔离见证后,交易费用与鼓励减少新的UTXO产生的激励相一致,不会无意地惩罚有许多输入的交易。

因此,隔离见证对比特币用户支付的费用有两个主要影响。首先,segwit通过见证数据折扣,和增加比特币区块链的能力,来降低交易的总体成本。其次,segwit对见证数据的折扣纠正了可能无意中导致UTXO集合中更加膨胀的激励错配。

赞赏译者