区块链研习社金马带你定投区块链区块链大学

《精通比特币第二版》中文版第七章高级交易和脚本2/2

2017-10-28  本文已影响823人  云天明ytm_ltd

精力有限,后期修订以github为主,建议大家移步github链接阅读更新版本,感谢理解:
https://github.com/tianmingyun/MasterBitcoin2CN

邀请你的加入,和我一起研读

特别预告:专栏早鸟价:99元,11月5日后升级为199元,11月14日零时前报名加入的朋友均赠送《精通比特币第二版》中文版纸质打印本一本。

本文6200字。

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脚本:
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):
堆栈是空的要么
堆栈中的顶部项小于0;要么
顶层堆栈项和nLocktime字段的锁定时间类型(高度或者时间戳)不相同;要么
顶层堆栈项大于交易的nLocktime字段;要么
输入的nSequence字段为0xffffffff。
注意CLTV和nLocktime使用相同的格式来描述时间锁定,无论是块高度还是自Unix纪元以秒钟以来所经过的时间。 最重要的是,在一起使用时,nLocktime的格式必须与输入中的CLTV格式相匹配,它们必须以秒为单位引用块高度或时间。
执行后,如果满足CLTV,则其之前的时间参数仍然作为堆栈中的顶级项,并且可能需要使用DROP进行删除,才能正确执行后续脚本操作码。 为此,您将经常脚本中看到CHECKLOCKTIMEVERIFY,后跟DROP。
通过将nLocktime与CLTV结合使用,交易锁定时间限制中描述的情况发生变化。 因为Alice锁定了UTXO本身,所以现在Bob或Alice在3个月的锁定时间到期之前不可能花费它。
通过将时间锁定功能直接引入到脚本语言中,CLTV允许我们开发一些非常有趣的复杂脚本。
该标准在BIP-65(CHECKLOCKTIMEVERIFY)中定义(附录部分)。

7.5.3相对时间锁

nLocktime和CLTV都是绝对时间锁定,它们指定绝对时间点。接下来的两个时间锁定功能,我们将要考察的是相对时间锁定,因为它们将消耗输出的条件指定为从块链接中的输出确认起的经过时间。
相对时间锁是有用的,因为它们允许将两个或多个相互依赖的交易链接在一起,同时对依赖于从先前交易的确认所经过的时间的一个交易施加时间约束。换句话说,在UTXO被记录在块状块之前,时钟不开始计数。这个功能在双向状态通道和闪电网络中特别有用,我们将在后面章节[state_channels]中看到。
相对时间锁,如绝对时间锁定,同时具有交易级功能和脚本级操作码。交易级相对时间锁定是作为对每个交易输入中设置的交易字段nSequence的值的共识规则实现的。脚本级相对时间锁定使用CHECKSEQUENCEVERIFY(CSV)操作码实现。
相对时间锁是根据BIP-68的规范实现的,相对锁定时间使用共同执行的序列号和BIP-112,CHECKSEQUENCEVERIFY。
BIP-68和BIP-112在2016年5月被激活,作为软叉升级到共识规则。

7.5.4 nSequence相对时间锁

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

7.5.4.1 nSequence的本义

nSequence字段最初是(但从未被正确实现的)允许在内存池中修改交易。在这种使用中,包含nSequence值低于2^32 (0xFFFFFFFF)的输入的交易表示尚未“确定”的交易。这样的交易将在内存池中保存,直到被另一个交易消耗相同输入并具有较高nSequence值的代替。一旦收到一个交易,其投入的nSequence值为2^32,那么它将被视为“最终确定”并开采。
nSequence的原始含义从未被正确实现,并且在不利用时间锁定的交易中nSequence的值通常设置为2^32。 对于具有nLocktime或CHECKLOCKTIMEVERIFY的交易,nSequence值必须设置为小于2^32, 以使时间锁定器有效。通常设置为2^32 - 1(0xFFFFFFFE)。

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值的二进制布局。

图7-1由BIP-68定义的nSequence值的二进制布局

基于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_channels]和[lightning_network]章节中看到。
CSV 细节参见 BIP-112, CHECKSEQUENCEVERIFY.

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

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

块头中设置的时间戳由矿工设定。共识规则允许一定的纬度来解决分散节点之间时钟精度的差异。然而,这给矿工造成了一个不幸的诱因,即矿工在一段时间内就这么说谎,以便通过包括还不成熟的时间交易来赚取额外费用。有关详细信息,请参阅以下部分。
为了消除谎言和加强时间安全性的动机,在相对时间段的BIPs的同时提出并激活了1个新的BIP。这是BIP-113,它定义了一个称为“中位时间过去”的新的共识测量。
通过取最后11个块的时间戳并找到中位数来计算“中位时间过去”的值。那个中间时间就变成了共识时间,并被用于所有的时间计算。过去约两个小时的中间点,任何一个块的时间戳的影响减小了。通过合并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设置为<current block #+ 1>,并将所有输入上的nSequence设置为0xFFFFFFFE以启用nLocktime。

7.6具有流量控制的脚本(条件条款 (Conditional Clauses))

比特币脚本的一个更强大的功能是流量控制,也称为条件条款。您可能熟悉使用构造IF ... THEN ... ELSE的各种编程语言中的流控制。比特币条件条款看起来有点不同,但是基本上是相同的结构。
在基本层面上,比特币条件操作码允许我们构建一个具有两种解锁方式的赎回脚本,这取决于评估逻辑条件的TRUE / FALSE结果。例如,如果x为TRUE,则赎回脚本为A,ELSE赎回脚本为B.
此外,比特币条件表达式可以无限期地“嵌套”,这意味着条件语句可以包含其中的另外一个,其中包含另一个等等。Bitcoin脚本流控制可用于构造非常复杂的脚本,具有数百甚至数千个可能的执行路径。嵌套没有限制,但协商一致的规则对脚本的最大大小(以字节为单位)施加限制。
比特币使用IF,ELSE,ENDIF和NOTIF操作码实现流量控制。此外,条件表达式可以包含布尔运算符,如BOOLAND,BOOLOR和NOT。
乍看之下,您可能会发现比特币的流量控制脚本令人困惑。那是因为比特币脚本是一种堆栈语言。同样的方式,当1+1看起来“向后”当表示为1 1 ADD时,比特币中的流控制条款也看起来“向后”(backward)。
在大多数传统(程序)编程语言中,流控制如下所示:
大多数编程语言中的流控制伪代码
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脚本流控制
condition
IF
code to run when condition is true
ELSE
code to run when condition is false
ENDIF
code to run in either case
阅读Bitcoin脚本时,请记住,评估的条件是在IF操作码之前。

7.6.1条件条款与VERIFY操作码

Bitcoin Script中的另一种条件是任何以VERIFY结尾的操作码。 VERIFY后缀表示如果评估的条件不为TRUE,脚本的执行将立即终止,并且该交易被视为无效。
与提供替代执行路径的IF子句不同,VERIFY后缀充当保护子句,只有在满足前提条件的情况下才会继续。
例如,以下脚本需要Bob的签名和产生特定哈希的前置映像(秘密地)。 解锁时必须满足这两个条件:
1)具有EQUALVERIFY保护条款的赎回脚本。
HASH160 <expected hash> EQUALVERIFY <Bob's Pubkey> CHECKSIG
为了兑现这一点,Bob必须构建一个解锁脚本,提供有效的前图像和签名:
2)一个解锁脚本以满足上述赎回脚本。
<Bob's Sig> <hash pre-image>

没有呈现前图像,Bob无法访问检查其签名的脚本部分。
该脚本可以用IF编写:
具有IF保护条款的兑换脚本
HASH160 <expected hash> EQUAL
IF
<Bob's Pubkey> CHECKSIG
ENDIF
Bob的解锁脚本是一样的:
解锁脚本以满足上述兑换脚本
<Bob's Sig> <hash pre-image>

使用IF的脚本与使用具有VERIFY后缀的操作码相同; 他们都作为保护条款。 然而,VERIFY的构造更有效率,使用较少的操作码。
那么,我们什么时候使用VERIFY,什么时候使用IF? 如果我们想要做的是附加一个前提条件(保护条款),那么验证是更好的。 然而,如果我们想要有多个执行路径(流控制),那么我们需要一个IF ... ELSE流控制子句。
提示
诸如EQUAL之类的操作码会将结果(TRUE / FALSE)推送到堆栈上,留下它用于后续操作码的评估。 相比之下,操作码EQUALVERIFY后缀不会在堆栈上留下任何东西。 在VERIFY中结束的操作码不会将结果留在堆栈上。

7.6.2在脚本中使用流控制

比特币脚本中流量控制的一个非常常见的用途是构建一个提供多个执行路径的赎回脚本,每个脚本都有一种不同的赎回UTXO的方式。
我们来看一个简单的例子,我们有两个签名人,Alice和Bob,还有一个可以兑换。 使用多重签名,这将被表示为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。 要花费,我们构建一个解锁脚本,通过在每个流量控制点的堆栈上放置相应的TRUE和FALSE值来导航执行路径。

7.7复杂的脚本示例

在本节中,我们将本章中的许多概念合并成一个例子。
我们的例子使用了迪拜公司所有者穆罕默德(Mohammed)的故事,他们正在经营进出口业务。
在这个例子中,穆罕默德希望用灵活的规则建立公司资本账户。他创建的方案需要不同级别的授权,具体取决于时间锁定。 Multisig计划的参与者是穆罕默德,他的两个合作伙伴赛义德和扎拉,以及他们的公司律师阿卜杜勒。三个合作伙伴根据多数规则作出决定,因此三者中的两个必须同意。然而,如果他们的钥匙有问题,他们希望他们的律师能够用三个合作伙伴签名之一收回资金。最后,如果所有的合作伙伴一段时间都不可用或无行为能力,他们希望律师能够直接管理该帐户。
这是Mohammed设计的脚本:
具有时间锁定的变量多重签名
IF
IF
2
ELSE
<30 days> CHECKSEQUENCEVERIFY DROP
<Abdul the Lawyer's Pubkey> CHECKSIGVERIFY
1
ENDIF
<Mohammed's Pubkey> <Saeed's Pubkey> <Zaira's Pubkey> 3 CHECKMULTISIG
ELSE
<90 days> CHECKSEQUENCEVERIFY DROP
<Abdul the Lawyer's Pubkey> CHECKSIG
ENDIF
Mohammed的脚本使用嵌套的IF ... ELSE流控制子句来实现三个执行路径。
在第一个执行路径中,该脚本作为三个合作伙伴的简单的2-of-3 multisig操作。 该执行路径由第3行和第9行组成。第3行将multisig的定额设置为2(2 - 3)。 该执行路径可以通过在解锁脚本的末尾设置TRUE TRUE来选择:
解锁第一个执行路径的脚本(2-of-3 multisig)
0 <Mohammed's Sig> <Zaira's Sig> TRUE TRUE
提示此解锁脚本开头的0是因为CHECKMULTISIG中的错误从堆栈中弹出一个额外的值。 额外的值被CHECKMULTISIG忽略,但它必须存在或脚本失败。 推送0(通常)是错误的解决方法,如CHECKMULTISIG执行中的错误章节所述。

第二个执行路径只能在UTXO创建30天后才能使用。 那时候,它需要签署阿卜杜勒的律师和三个合作伙伴之一(三分之一)。 这是通过第7行实现的,该行将多选的法定人数设置为1。要选择此执行路径,解锁脚本将以FALSE TRUE结束:
解锁第二个执行路径的脚本(律师+ 1/3)
0 <Saeed's Sig> <Abdul's Sig> FALSE TRUE
提示为什么先FALSE后TRUE? 反了吗?这是因为这两个值被推到堆栈,所以先push FALSE,然后push TRUE。 因此,第一个IF操作码首先弹出的是TRUE。

最后,第三个执行路径允许阿卜杜勒律师单独花费资金,但只能在90天之后。 要选择此执行路径,解锁脚本必须以FALSE结束:
解锁第三个执行路径的脚本(仅适用于律师)
<Abdul's Sig> FALSE
在纸上运行脚本来查看它在堆栈上的行为。

阅读这个例子还需要考虑几件事情。 看看你能找到答案吗?
为什么律师可以随时通过在解锁脚本中选择FALSE来兑换第三个执行路径?
在UTXO开采后分别有多少个执行路径可以使用5,35,105天?
如果律师失去钥匙,资金是否流失? 如果91天过去了,你的答案是否会改变?
合作伙伴如何每隔29天或89天“重置”一次,以防止律师获得资金?
为什么这个脚本中的一些CHECKSIG操作码有VERIFY后缀,而其他的没有?

本章完。

参考内容:
1、本文部分内容摘自《精通比特币》第一版中译本,特此说明并致谢。


我正在发起“和我一起研读《精通比特币第二版》”活动。
我声明,这不是讲课,是讨论,是研读学习,所以需要你的加入,更需要你的参与。
我把这种学习方式作为一次认知学习法[注]的实践。
我希望大家可以和我一起通过将认知学习法与《精通比特币第二版》的研读结合起来,尝试总结出一套可行的区块链知识技能快速入门的方法。
尝试,反思,总结,生成以及组合进行的实践、检验并逐步迭代升级,这是我们的经历也是我们的结果。
这个结果也将成为我们大家一起合作编写的一本书《认知学习比特币》的雏形(所有有价值的讨论都将成为这本书的素材)。
我更希望大家通过本次认知学习方法论的实践以及迭代升级,将这种经过实践的学习方法迁移到更多领域的学习中。
所以希望我们携手走入第二季,第三季......

欢迎扫描二维码加入。

欲知详情请扫描二维码
上一篇 下一篇

猜你喜欢

热点阅读