7.5 时间锁(Timelocks)

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

时间锁对于延期交易和将资金锁定到将来某个日期很有用。更重要的是,时间锁将比特币脚本扩展到时间的维度,为复杂的多步骤智能合约打开了大门。

7.5.1 交易锁定时间(nLocktime)

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

7.5.1.1 交易时间锁限制

nLocktime就是一个限制,虽然将来有可能花费这些输出,但是到指定时间为止,还不能说不能花费它们。我们用下面的例子解释一下。

Alice签署了一笔交易,支付给Bob的地址,并将交易nLocktime设定为未来的3个月。Alice把这笔交易发送给Bob搁置起来。有了这个交易,Alice和Bob知道:

  • 在3个月过去之前,Bob不能完成交易进行兑换。
  • Bob可以在3个月后接受交易。

    然而:

  • Alice可以创建另一笔交易,不设置时间锁,花费上面两倍的输入。 这样,Alice就可以在3个月过去之前花费相同的UTXO。

  • Bob不能保证Alice不会这样做。

了解交易nLocktime的限制很重要。 唯一的保证是Bob在3个月过去之前无法兑换它,却无法保证Bob最终是否可以得到资金。 为了实现这样的保证,时间锁限制必须放在UTXO上,成为锁定脚本的一部分,而不是交易的一部分。 这是通过另一种形式的时间锁来实现的,称为检查锁定时间验证(CLTV)。

7.5.2 检查锁定时间验证Check Lock Time Verify (CLTV)

2015年12月,通过比特币软分叉升级引入了一种新形式的时间锁。根据BIP-65中的规范,脚本语言中添加了一个名为CHECKLOCKTIMEVERIFY(CLTV)的新脚本操作符。 CLTV是每个输出的时间锁,而不是像nLocktime一样是每个交易的时间锁。这使得在应用时间锁的方式上具有更大的灵活性。

简单地说,通过在输出的兑换脚本中添加CLTV操作码,限制了输出,因此只能在指定的时间过后花费。

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

CLTV不替换nLocktime,而是限制特定的UTXO,使它们只能在大于或等于nLocktime设置的值的将来交易中使用。

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

为了使用CLTV来锁定输出,必须将其插入到创建输出交易的输出兑换脚本中。例如,如果Alice支付Bob的地址,输出通常会包含如下P2PKH脚本:

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

要锁定一段时间,比如说3个月以后,交易将是一个包含兑换脚本的P2SH交易:

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

其中是从交易开始被挖矿时间起估计3个月的区块高度或时间值:当前块高度+12,960(块)或当前Unix纪元时间+7,760,000(秒)。现在,不要担心CHECKLOCKTIMEVERIFY之后的DROP操作码,下面很快就会解释。

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

Bob的交易评估如下。如果Alice设置的CHECKLOCKTIMEVERIFY参数小于或等于支出交易的nLocktime,脚本执行将继续(就好像执行“无操作”或NOP操作码一样)。否则,脚本执行停止,并且该交易被视为无效。

更确切地说,CHECKLOCKTIMEVERIFY失败并停止执行,标记交易无效(来自:BIP-65):

  1. 堆栈是空的要么
  2. 堆栈中的顶部项小于0;要么
  3. 顶层堆栈项和nLocktime字段的锁定时间类型(高度或者时间戳)不相同;要么
  4. 顶层堆栈项大于交易的nLocktime字段;要么
  5. 输入的nSequence字段为0xffffffff。

注释 CLTV和nLocktime描述时间锁必须使用相同的格式,无论是区块高度还是自Unix纪元以来经过的秒数。 最重要的是,在一起使用时,nLocktime的格式必须与输出中的CLTV格式相匹配,它们必须是区块高度或秒数时间。

执行后,如果满足CLTV的要求,则它前面的时间参数将作为堆栈上的顶部项保留,并且可能需要随DROP一起删除,以便正确执行后续脚本操作码。 经常在脚本中看到CHECKLOCKTIMEVERIFY后面跟着DROP就是这个原因。

通过将nLocktime与CLTV结合使用,【7.5.1.1交易时间锁限制】中描述的情况就发生了变化。 Alice就不能再花这笔钱了(因为它被Bob的密钥锁定了),Bob也不能在3个月的锁定期终止前花掉它。

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

该标准在BIP-65(CHECKLOCKTIMEVERIFY)中定义(附录BIP-65)。

7.5.3 相对时间锁

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

相对时间锁是有用的,因为它们允许两个或多个相互依赖的交易链被下链处理,同时对一个交易施加时间限制,该时间限制依赖于其前一个交易从被确认开始经过的时间。换句话说,只有这个UTXO被记录在区块链,时钟才开始计数。这个功能在双向状态通道和闪电网络中特别有用,我们将在后面章节【2.6支付通道和状态通道】中看到。

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

相对时间锁是根据【BIP-68】【BIP-112】的规范共同实现的,其中BIP-68通过与相对时间锁运用一致性增强的数字序列实现,BIP-112中是运用到了CHECKSEQUENCEVERIFY这个操作码实现。

BIP-68和BIP-112是在2016年5月作为软分叉升级时被激活的一个共识规则。

7.5.4 nSequence相对时间锁

相对时间锁可以设置在每个交易输入中,方法是设置每个输入中的nSequence字段。

7.5.4.1 nSequence的本义

nSequence字段的最初设计是想在内存中修改交易(但是从未运用过)。这种情况下,一笔交易的输入包含的nSequence值低于232-1(0xffffffff),就表示该交易尚未“完成”。这样的交易将一直保留在内存池中,直到被花费相同输入,具有更高nSequence值的的另一笔交易代替。一旦收到一笔交易,其nSequence值为0xFFFFFFFF,那么它将被视为“完成”并开始挖矿。

nSequence的最初设计从未被正确实现,在不使用时间锁的交易中,nSequence的值通常设置为0xFFFFFFFF。对于具有nLocktime或CHECKLOCKTIMEVERIFY的交易,nSequence值必须设置为小于231,以使时间锁保护有效,如下面所述。

7.5.4.2 nSequence作为一个共同执行的相对时间锁定

由于BIP-68的激活,新的共识规则适用于任何包含nSequence值小于2^31的输入的交易(bit 1<<31 is not set)。以编程方式,这意味着如果没有设置最高有效(bit 1<<31),它是一个表示“相对锁定时间”的标志。否则(bit 1<<31set),nSequence值被保留用于其他用途,例如启用CHECKLOCKTIMEVERIFY,nLocktime,Opt-In-Replace-By-Fee以及其他未来的新产品。

一笔输入交易,当输入脚本中的nSequence值小于2^31时,就是相对时间锁定的输入交易。这种交易只有到了相对锁定时间后才生效。例如,具有30个区块的nSequence相对时间锁的一个输入的交易只有在从输入中引用的UTXO开始的时间起至少有30个块时才有效。由于nSequence是每个输入字段,因此交易可能包含任何数量的时间锁定输入,所有这些都必须具有足够的时间以使交易有效。

交易可以包括时间锁定输入(nSequence <2^31)和没有相对时间锁定(nSequence> = 2^31)的输入。 nSequence值以块或秒为单位指定,但与nLocktime中使用的格式略有不同。类型标志用于区分计数块和计数时间(以秒为单位)的值。类型标志设置在第23个最低有效位(即值1 << 22)。如果设置了类型标志,则nSequence值将被解释为512秒的倍数。如果未设置类型标志,则nSequence值被解释为块数。

当将nSequence解释为相对时间锁定时,只考虑16个最低有效位。一旦评估了标志(位32和23),nSequence值通常用16位掩码(例如nSequence&0x0000FFFF)“屏蔽”。 下图显示由BIP-68定义的nSequence值的二进制布局。 BIP-68 definition of nSequence encoding Figure 1. BIP-68 definition of nSequence encoding (Source: BIP-68)

基于nSequence值的一致执行的相对时间锁定在BIP-68中。标准定义在BIP-68, Relative lock-time using consensus-enforced sequence numbers.

7.5.5 带CSV的相对时间锁

就像CLTV和nLocktime一样,有一个脚本操作码用于相对时间锁定,它利用脚本中的nSequence值。该操作码是CHECKSEQUENCEVERIFY,通常简称为CSV。 在UTXO的赎回脚本中评估时,CSV操作码仅允许在输入nSequence值大于或等于CSV参数的交易中进行消耗。实质上,这限制了UTXO的消耗,直到UTXO开采时间过了一定数量的块或秒。

与CLTV一样,CSV中的值必须与相应nSequence值中的格式相匹配。如果CSV是根据块指定的,那么nSequence也是如此。如果以秒为单位指定CSV,那么nSequence也是如此。

当几个(已经形成链)交易被保留为“脱链”时,创建和签名这几个(已经形成链)交易但不传播时,CSV的相对时间锁特别有用。在父交易已被传播,直到消耗完相对锁定时间,才能使用子交易。这个用例的一个应用可以在state_channelslightning_network/请加上链接 章节中看到。 CSV 细节参见 BIP-112, CHECKSEQUENCEVERIFY.

7.5.6中位时间过去Median-Time-Past

作为激活相对时间锁定的一部分,时间锁定(绝对和相对)的“时间”方式也发生了变化。在比特币中,墙上时间(wall time)和共识时间之间存在微妙但非常显著的差异。比特币是一个分散的网络,这意味着每个参与者都有自己的时间观。网络上的事件不会随时随地发生。网络延迟必须考虑到每个节点的角度。最终,所有内容都被同步,以创建一个共同的分类帐。比特币在过去存在的分类账状态中每10分钟达成一个新的共识。

区块头中设置的时间戳由矿工设定。共识规则允许一定的误差来解决分散节点之间时钟精度的问题。然而,这诱惑了矿工去说谎,以便通过包括还不在范围内的时间交易来赚取额外矿工费。有关详细信息,请参阅以下部分。

为了杜绝矿工说谎,加强时间安全性,在相对时间锁的基础上又新增了一个BIP。这是BIP-113,它定义了一个称为“中位时间过去 /(Median-Time-Past)”的新的共识测量机制。通过取最后11个块的时间戳并计算其中位数作为“中位时间过去”的值。这个中间时间值就变成了共识时间,并被用于所有的时间计算。过去约两个小时的中间点,任何一个块的时间戳的影响减小了。通过这个方法,没有一个矿工可以利用时间戳从具有尚未成熟的时间段的交易中获取非法矿工费。

Median-Time-Past更改了nLocktime,CLTV,nSequence和CSV的时间计算的实现。由Median-Time-Past计算的共识时间总是大约在挂钟时间后一个小时。如果创建时间锁交易,那么要在nLocktime,nSequence,CLTV和CSV中进行编码的估计所需值时,应该考虑它。 Median-Time-Past细节参见BIP-113.

7.5.7针对费用狙击(Fee Sniping)的时间锁定

费用狙击是一种理论攻击情形,矿工试图从将来的块(挑选手续费较高的交易)重写过去的块,实现“狙击”更高费用的交易,以最大限度地提高盈利能力。

例如,假设存在的最高块是块#100,000。如果不是试图把#100,001号的矿区扩大到区块链,那么一些矿工们会试图重新挖矿#100,000。这些矿工可以选择在候选块#100,000中包括任何有效的交易(尚未开采)。他们不必使用相同的交易来恢复块。事实上,他们有动力选择最有利可图(最高每kBB)的交易来包含在其中。它们可以包括处于“旧”块#100,000中的任何交易,以及来自当前内存池的任何交易。当他们重新创建块#100,000时,他们本质上可以将交易从“现在”提取到重写的“过去”中。

今天,这种袭击并不是非常有利可图,因为回报奖励(因为包括一定数量的比特币奖励)远远高于每个区块的总费用。但在未来的某个时候,交易费将是奖励的大部分(甚至是奖励的整体)。那时候这种情况变得不可避免了。

为了防止“费用狙击”,当Bitcoin Core /钱包 创建交易时,默认情况下,它使用nLocktime将它们限制为“下一个块”。在我们的环境中,Bitcoin Core /钱包将在任何创建的交易上将nLocktime设置为100,001。在正常情况下,这个nLocktime没有任何效果 - 交易只能包含在#100,001块中,这是下一个区块。 但是在区块链分叉攻击的情况下,由于所有这些交易都将被时间锁阻止在#100,001,所以矿工们无法从筹码中提取高额交易。他们只能在当时有效的任何交易中重新挖矿#100,000,这导致实质上不会获得新的费用。 为了实现这一点,Bitcoin Core/钱包将所有新交易的nLocktime设置为,并将所有输入上的nSequence设置为0xFFFFFFFE以启用nLocktime。