时间锁(Timelocks)
时间锁是只允许在一段时间后才允许支出的交易。
比特币从一开始就有一个交易级的时间锁定功能,由交易中的nLocktime字段实现。后来推出新版本中有两个新的时间锁定功能,
提供了UTXO级别的时间锁定功能,这些是CHECKLOCKTIMEVERIFY和CHECKSEQUENCEVERIFY。
时间锁对于后期交易和将资金锁定到将来的日期很有用。时间锁将比特币脚本扩展到时间的维度,为复杂的多级智能合约打开了大门。
交易锁定时间(nLocktime)
比特币从一开始就有一个交易级的时间锁功能。交易锁定时间是交易级设置(交易数据结构中的一个字段),它定义交易有效的最早时间,并且可以在网络上中继或添加到区块链中。
锁定时间也称为nLocktime,是来自于Bitcoin Core代码库中使用的变量名称。在大多数交易中将其设置为零,以指示即时传播和执行。如果nLocktime不为零,低于5亿,则将其解释为块高度,这意味着交易无效,并且在指定的块高度之前未被中继或包含在块链中。
如果超过5亿,它被解释为Unix纪元时间戳(自Jan-1-1970之后的秒数),并且交易在指定时间之前无效。指定未来块或时间的nLocktime的交易必须由始发系统持有,并且只有在有效后才被发送到比特币网络。如果交易在指定的nLocktime之前传输到网络,那么第一个节点就会拒绝该交易,并且不会被中继到其他节点。使用nLocktime等同于一张延期支票。
交易锁定时间的不足
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)。
检查锁定时间验证Check Lock Time Verif(CLTV)
2015年12月,引入了一种新形式的时间锁进行比特币软分叉升级。根据BIP-65中的规范,脚本语言添加了一个名为CHECKLOCKTIMEVERIFY(CLTV)的新脚本操作符。 CLTV是每个输出的时间锁定,而不是每个交易的时间锁定,与nLocktime的情况一样。这允许在应用时间锁的方式上具有更大的灵活性。 简单来说,通过在输出的赎回脚本中添加CLTV操作码来限制输出,从而只能在指定的时间过后使用。
注释 当nLocktime是交易级时间锁定时,CLTV是基于输出的时间锁。
CLTV不会取代nLocktime,而是限制特定的UTXO,并通过将nLocktim设置为更大或相等的值,从而达到在未来才能花费这笔钱的目的。
CLTV操作码采用一个参数作为输入,表示为与nLocktime(块高度或Unix纪元时间)相同格式的数字 。如VERIFY后缀所示,CLTV如果结果为FALSE,则停止执行脚本的操作码类型。如果结果为TRUE,则继续执行。
为了使用CLTV锁定输出,将其插入到创建输出的交易中的输出的赎回脚本中。例如,如果Alice支付Bob的地址,输出通常会包含一个这样的P2PKH脚本:
DUP HASH160 <Bob's Public Key Hash> EQUALVERIFY CHECKSIG
要锁定一段时间,比如说3个月以后,交易将是一个P2SH交易,其中包含一个赎回脚本:
<now + 3 months> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <Bob's Public Key Hash> EQUALVERIFY CHECKSIG
其中<now +3个月>是从交易开始被挖矿时间起计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结合使用,交易锁定时间限制中描述的情况发生变化。 因为Alice锁定了UTXO本身,所以现在Bob或Alice在3个月的锁定时间到期之前不可能花费它。
通过将时间锁定功能直接引入到脚本语言中,CLTV允许我们开发一些非常有趣的复杂脚本。 该标准在BIP-65(CHECKLOCKTIMEVERIFY)中定义。
相对时间锁
nLocktime和CLTV都是绝对时间锁定,它们指定绝对时间点。接下来的两个时间锁定功能,我们将要考察的是相对时间锁定,因为它们将消耗输出的条件指定为从块链接中的输出确认起的经过时间。
相对时间锁是有用的,因为它们允许将两个或多个相互依赖的交易链接在一起,同时对依赖于从先前交易的确认所经过的时间的一个交易施加时间约束。换句话说,在UTXO被记录在块状块之前,时钟不开始计数。这个功能在双向状态通道和闪电网络中特别有用。
相对时间锁,如绝对时间锁定,同时具有交易级功能和脚本级操作码。交易级相对时间锁定是作为对每个交易输入中设置的交易字段nSequence的值的共识规则实现的。脚本级相对时间锁定使用CHECKSEQUENCEVERIFY(CSV)操作码实现。
相对时间锁是根据BIP-68与BIP-112的规范共同实现的,其中BIP-68通过与相对时间锁运用一致性增强的数字序列实现,BIP-112中是运用到了CHECKSEQUENCEVERIFY这个操作码实现。
BIP-68和BIP-112是在2016年5月作为软分叉升级时被激活的一个共识规则。
nSequence相对时间锁
相对时间锁可以在每个输入中设置好,其方法是在每个输入中加多一个nSequence字段。
nSequence的本义
nSequence字段的设计初心是想让交易能在在内存中修改,可惜后面从未运用过,使用nSequence这个字段时,如果输入的交易的序列值小于2^32 (0xFFFFFFFF),就表示尚未“确定”的交易。
这样的交易将在内存池中保存,直到被另一个交易消耗相同输入并具有较大nSequence值的代替。一旦收到一个交易,其投入的nSequence值为2^32,那么它将被视为“最终确定”并开采。 nSequence的原始含义从未被正确实现,并且在不利用时间锁定的交易中nSequence的值通常设置为232。对于具有nLocktime或CHECKLOCKTIMEVERIFY的交易,nSequence值必须设置为小于232,以使时间锁定器有效。通常设置为2^32 - 1(0xFFFFFFFE)。
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)“屏蔽”。
带CSV的相对时间锁
就像CLTV和nLocktime一样,有一个脚本操作码用于相对时间锁定,它利用脚本中的nSequence值。该操作码是CHECKSEQUENCEVERIFY,通常简称为CSV。 在UTXO的赎回脚本中评估时,CSV操作码仅允许在输入nSequence值大于或等于CSV参数的交易中进行消耗。实质上,这限制了UTXO的消耗,直到UTXO开采时间过了一定数量的块或秒。
与CLTV一样,CSV中的值必须与相应nSequence值中的格式相匹配。如果CSV是根据块指定的,那么nSequence也是如此。如果以秒为单位指定CSV,那么nSequence也是如此。
当几个(已经形成链)交易被保留为“脱链”时,创建和签名这几个(已经形成链)交易但不传播时,CSV的相对时间锁特别有用。在父交易已被传播,直到消耗完相对锁定时间,才能使用子交易。
中位时间过去Median-Time-Past(MTP)
作为激活相对时间锁定的一部分,时间锁定(绝对和相对)的“时间”方式也发生了变化。在比特币中,墙上时间(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.
针对费用狙击(Fee Sniping)的时间锁定
费用狙击是一种理论攻击情形,矿工试图从将来的块(挑选手续费较高的交易)重写过去的块,实现“狙击”更高费用的交易,以最大限度地提高盈利能力。
例如,假设存在的最高区块是#100,000。按理应该把#100,001号区块扩大到区块链,但矿工有动力选择最有利可图的交易来打包区块,
也就是说打包旧区块#100,000中的任何交易,以及来自当前内存池的任何交易。然后矿工重新创建区块#100,000,这样本质上可以将交易从现在提取到重写的过去中。
比特币现在挖矿奖励对于这种袭击不是非常有利可图,但未来挖矿奖励没有了,挖矿收入主要来自交易费用的时候,这种情况将变得不可避免。所以有必要设计规则避免此种情况。
为了防止费用狙击,当Bitcoin Core/钱包创建交易时,默认情况下,它使用nLocktime将它们限制为下一个区块。
Bitcoin Core/钱包将所有新交易的nLocktime设置为<current block #+ 1>,并将所有呼入上的nSequence设置为0xFFFFFFFE以启用nLocktime。