好书/好文 [学习笔记] 比特币交易的时间锁

aaron67 · 2019年01月04日 · 311 次阅读
本帖已被设为精华帖!

https://aaron67.cc/2019/01/02/bitcoin-transaction-timelocks/


时间锁功能让比特币交易拥有了时间维度,这篇文章介绍时间锁的详细内容

本文也是介绍比特币交易内幕细节的最后一篇文章

区块

为了理解时间锁,需要提前介绍一点区块的概念

你可以把区块(Block)想象成一个箱子,里面装着交易

这个“箱子”

  • 可以按先来后到的顺序,用高度标识,第一个箱子的高度为0
  • 也可以用哈希标识,相当于这个箱子的标签
  • 每个箱子,都记录了它前一个箱子的标签(哈希)

网络不断产生新的交易,人们根据规则,将这些交易“塞到”新箱子里,随后将它摞在之前整理好的最后一个箱子上

容易想象,网络中的交易,被不断整理到箱子中,这些依次摞起来的箱子,形成了一条不断延长的“箱子”

比特币网络的总帐本,就是这样的一条链,交易填充区块,一个个区块前向引用,形成区块链

关于区块和区块链更详细的介绍,放到之后的文章

nLocktime

回忆一下比特币交易的结构

长度(字节) 描述
4 交易结构的版本
1~9 VarInt 交易包含几个输入,非零正整数
变长 输入数组
1~9 VarInt 交易包含几个输出,非零正整数
变长 输出数组
4 nLockTime

通过最后4字节的nLockTime字段,可以实现交易粒度的时间锁

  • nLockTime = 0,表示这笔交易没有时间锁定,可以被“随时”写入账本,“即时生效”
  • nLockTime < 500000000,指示块高度,这笔交易在块高度nLockTime之后,才可以被写入账本
  • nLockTime >= 500000000,指示具体的Unix时间戳,这笔交易在Unix时间戳nLockTime之后,才可以被写入账本

Unix时间戳是一种时间表示法,它的值,表示从1970年1月1日 0时0分0秒 UTC开始,经历的秒数

你可以用这个工具转换,1546434313表示的时间为2019年1月2日 13时5分13秒 UTC,即北京时间2019年1月2日 21时5分13秒 UTC+8

设想如下的需求

父亲想立个遗嘱,在自己去世后,儿子可以拥有自己所有的比特币

需要保证

  • 父亲在世时,可以随时修改遗嘱
  • 父亲过世后,儿子确定可以拿到币

设置交易的nLockTime字段,可以实现这样的遗嘱需求

  1. 父亲计算出自己80岁时的Unix时间戳,值为T
  2. 父亲构造一笔P2PKH交易,将自己所有的比特币,付款到儿子的公钥哈希,并设置交易nLockTime字段的值为T
  3. 父亲用自己的私钥,对这笔交易签名(设置正确的解锁脚本),将签名后的交易数据交给儿子

这笔交易是合法的,但因为时间锁的设置,即使向网络“展示”这笔交易,它也不会被提前写入账本,转账不会在父亲80岁前发生,即儿子不会在父亲80岁前,拿到这笔钱

如果父亲去世时没到80岁,儿子也可以在未来,在父亲80岁这天之后,向网络“展示”交易,拿到这笔钱

如果父亲想修改遗嘱,只需要

  1. 将所有比特币先转到自己的另一个公钥哈希上,即时生效
  2. 重新按照自己的意愿,构造交易并设置nLockTime,签名后分发

完成操作1后,原来那些签名过的交易,都会变得无效,因为对应的UTXO已经被消费

一般的,Alice签名了一笔交易,付款到Bob的公钥哈希,并将交易的nLocktime设为三个月

Alice把这笔交易发送给Bob,此时,两人都知道

  • 在三个月过去之前,Bob不会收到这笔钱
  • 这三个月内,Alice可以随时构造另外的交易,即时生效,消费同样的UTXO,即Alice可以在这三个月内,随时花费付给Bob的这笔钱
  • Bob无法保证Alice不这么做

这正是nLocktime的局限性

nLocktime唯一能保证的,是这笔交易在时间锁释放之前无法被写入账本,即收款人无法在时间锁释放之前,收到资金

交易粒度的nLocktime时间锁,只在下列情况满足时,才会释放

  • nLocktime = 0,没有时间锁
  • nLockTime < 500000000,且当前的区块高度,已经超过了nLockTime的值
  • nLockTime >= 500000000,且当前的Unix时间戳,已经超过了nLockTime的值

OP_CHECKLOCKTIMEVERIFY

为了改善交易nLocktime时间锁的局限性,有更细粒度的控制,时间锁需要跟UTXO关联,即放到锁定脚本中

2015年12月,BIP-65引入了全新的操作码OP_CHECKLOCKTIMEVERIFYCLTV,Check Lock Time Verify),来实现UTXO粒度的时间锁定

对一般的P2PKH,其锁定脚本为

OP_DUP OP_HASH160 [公钥哈希] OP_EQUALVERIFY OP_CHECKSIG

如果一个UTXO的锁定脚本是如下的形式

 [过期时间] OP_CHECKLOCKTIMEVERIFY OP_DROP  OP_DUP OP_HASH160 [公钥哈希] OP_EQUALVERIFY OP_CHECKSIG

|<--------------CLTV时间锁--------------->|

我们说,这是一个被CLTV锁定的UTXO,只能在锁定脚本中的CLTV时间锁释放后才可以被消费

其中,[过期时间]与交易的nLockTime字段有相同的格式,指示一个区块高度(< 500000000),或一个Unix时间戳(>= 500000000)

即,只有在当前区块高度超过[过期时间],或当前Unix时间戳超过[过期时间]时,CLTV时间锁才会释放,这个UTXO才可以被消费

CLTV的幕后细节

逻辑上CLTV很好理解,但其工作方式稍显复杂,具体在BIP-65里定义,这里也做个说明

回忆一下交易输入的结构,最后4字节的nSequence字段,这里会用到

长度(字节) 描述
32 引用的交易哈希,UTXO来自哪笔交易
4 引用的输出序号,UTXO是那笔交易的第几个输出,从0开始计数
1~9 VarInt 后面紧跟的解锁脚本,有多少字节
变长 解锁脚本的内容
4 nSequence

如果一笔交易要消费CLTV锁定的UTXO,需要同时满足下列所有条件

  • 输入nSequence字段的值,必须小于0xffffffff
  • 锁定脚本中[过期时间]的值,必须大于等于0
  • 这笔交易的nLockTime和锁定脚本中的[过期时间],必须同时大于等于500000000或同时小于500000000,即要么都指示Unix时间戳,要么都指示区块高度
  • 这笔交易nLockTime字段的值,必须大于等于[过期时间]的值

你能看到,交易要消费CLTV锁定的UTXO,需要配合使用nLockTime字段

这些条件组合有些复杂,与当前区块高度或当前Unix时间戳,好像并没有什么关系

让我们换个角度看,着重关注最后一个条件

对一笔交易

  • 为了保证交易能“即时生效”,你需要将nLockTime的值,设置为小于等于当前的区块高度或小于等于当前的Unix时间戳,否则这笔交易不会被写入账本
  • 为了满足上述最后一个条件,你需要将nLockTime的值,设置为大于等于锁定脚本中的[过期时间]的值,否则时间锁不会释放

画个图,直观的看一下

如果当前时间Tc,未到CLTV锁定的过期时间Tb

那么,你找不到这么一个nLockTimeT,同时满足T <= Tc(交易即时生效)且T >= Tb(释放时间锁)

如果当前时间Tc,超过了CLTV锁定的过期时间Tb

那么,nLockTime可以被设置为大于等于Tb且小于等于Tc的任意值,一般的,都直接设置为当前的区块高度或当前的Unix时间戳Tc

对于

  • 使用nLockTime字段的交易时间锁
  • 使用CLTV操作码的UTXO时间锁

这两种时间锁都是指定某个具体的绝对时间点为过期时间的绝对时间

nSequence

nSequence字段最初被设计为,标识某些还未被写入账本(仍在内存池中)的交易,允许它们在之后被更新

  • 如果某个交易的输入,其nSequence字段的值,小于0xffffffff,表示这笔交易尚未“确定”,还不是最终版本
  • 这笔交易会被暂时搁置,直到被另一个消耗了同样输入的,并且有一个更大nSequence值的交易替换
  • 直到收到nSequence值为0xffffffff的交易,才认为这笔交易已经准备就绪,可以随时被写入账本

但这个功能没有实现,从未在系统中使用过

BIP-68通过复用交易输入的nSequence字段,实现交易粒度的相对时间

相对时间的意思是,从被引用的交易写入账本后,经过的时间

Imgur

  • 高度10000的区块中有一笔交易,创建了一个UTXO
  • 你现在创建一笔新交易消费这个UTXO,并设置新交易输入nSequence字段的值为100个区块

那么,这笔新交易不会被写入账本,除非当前区块高度已经达到或超过10100

nSequence是输入的一个字段,交易可以包含多个输入

只有在满足了所有输入上的nSequence相对时间锁(如果有)要求后,交易才被认为合法,才会被写入账本

根据BIP-68的设计,如果nSequence字段的值小于2^310x80000000),表示这是一笔激活了nSequence相对时间锁的交易

nSequence表示相对时间锁的过期时间,格式上与nLockTimeCLTV略有不同

对于这个4字节的值

  • 最高位第31位,作为开关,为1表示禁用相对时间锁
  • 22位作为类型标志,为1指示多少个512,为0指示多少个区块
  • 16位,作为值

Imgur

如果nSequence的值为0x0040005a

31         22       15                0
|          |        |                 |
0000 0000 0100 0000 0000 0000 0101 1010

可以知道

  • 31位是0,启用了相对时间锁
  • 22位是1,指示多少个512
  • 16位是0x005a,值为90
  • 超时时间是90 * 512 = 46080

整理下nSequence的不同情况

  • 等于2^32 - 1,即等于0xffffffff,表示没有设置任何时间锁
  • 小于2^32 - 1,表示启用了nLockTimeCLTV绝对时间锁,一般都设置为0xfffffffe
  • 小于2^31,即小于0x80000000,表示启用了nSequence相对时间锁

OP_CHECKSEQUENCEVERIFY

通过BIP-112引入的全新操作码OP_CHECKSEQUENCEVERIFYCSV,Check Sequence Verify),可以实现UTXO粒度的相对时间锁定

锁定脚本,形如

 [过期时间] OP_CHECKSEQUENCEVERIFY OP_DROP  OP_DUP OP_HASH160 [公钥哈希] OP_EQUALVERIFY OP_CHECKSIG

|<---------------CSV时间锁--------------->|

简单的说,被CSV锁定的UTXO,从创建它的交易被写入账本开始计时,只有在经过一定的秒数或一定的区块数后,这个UTXO才可以被消费

CLTV类似,在消费一个CSV锁定的UTXO时,要求

  • 交易的nVersion字段的值,必须大于等于2
  • 输入nSequence字段的值,必须小于0x800000000
  • 锁定脚本中[过期时间]的值,必须大于等于0且小于0x800000000
  • 输入nSequence和锁定脚本中的[过期时间],要么都指示秒数,要么都指示区块数
  • 输入nSequence的值,必须大于等于[过期时间]的值

总结

这篇文章介绍了四种比特币交易时间锁

  • 基于交易粒度的nLockTime绝对时间锁,在达到指定的区块高度或具体的Unix时间戳前,这笔交易不会被写入账本
  • 基于交易粒度的nSequence相对时间锁,这笔交易不会被写入账本,除非其输入引用的那笔已经被写入账本的交易,经过了指定的时间或区块数
  • 基于UTXO粒度的CLTV绝对时间锁,在达到指定的区块高度或具体的Unix时间戳前,这笔UTXO无法被消费
  • 基于UTXO粒度的CSV相对时间锁,在创建这个UTXO的交易写入帐本后,除非经过了指定的时间或区块数,否则这个UTXO无法被消费

关于Median-Time-PastFee Sniping,因为需要理解区块和挖矿的相关内容,所以我把他们放在之后的文章中再介绍

时间锁的概念非常好理解,但细节繁多略显晦涩,建议你在阅读本文的同时,也看看下面列出的资料

参考

共收到 0 条回复
aaron67 将本帖设为了精华贴 01月04日 02:48
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册