Tapscript:新的操作码、降低限制和制约
Liquid Network Blockstream Research

Tapscript:新的操作码、降低限制和制约

Andrew Poelstra

简介

这是我们Taproot系列文章中的第三篇也是最后一篇,内容是谈谈作为Taproot的一部分,我们在液态网络部署上的改进。我们之前的两篇文章讨论了Taproot部分签名的Elements转账。在这篇文章中,我们将介绍引入到Elements Script的新操作码(opcodes)、它们的用途以及我们正在用它们做什么。

这组操作码是自2015年Elements Alpha推出以来,对Script的重大改进。我们的研究团队在比特币相关的应用密码学方面不断的进行创新,在MuSig、Elements区块链和闪电网络方面开展了大量工作,并致力于脚本化和简洁性。尤其是,我们对比特币上的Taproot做出了重大贡献,并且继承了Elements Script的一些更改,包括:

  • 取消对操作码计数器和脚本大小的固定限制;
  • 用Schnorr替换ECDSA签名;
  • 将CHECKMULTISIG替换为CHECKSIGADD,允许批量验证多个签名
  • 新增许多OP_SUCCESS操作码,相对于旧的NOP操作码,它们扩展了我们可以软分叉到网络中的操作码集。

我们还将讨论契约,它赋予Script约束支付流以构建“智能合约”的能力,从而为币或其他资产附加条件。多年来,比特币的契约以多种形式提出,但从一开始就存在于Elements中。借助Tapscript,我们从原始契约实现中收到了一些开发人员的反馈,并引入了新的操作码,这将使契约表达性更好、效率更高并且更符合使用习惯。

最后,我们将简单讨论Elements Miniscript,这是我们对比特币Miniscript语言的扩展,它用这些操作码来生成启用契约的脚本。这个项目正在快速发展中,我们很高兴看到它在液态网络上产生了新的用例。我们将在以后的文章中更多地谈到这个话题。

契约和资产

比特币中的契约是由Greg Maxwell最初在2013年以一种半开玩笑的方式提出的,并要求用户为他们想出一些糟糕但有趣的应用程序。虽然这些鬼点子很有趣,但不幸的是,它们似乎使契约的概念在今天黯然失色,即使这些问题并不是特别实际,甚至不是契约所特有的。在关于风险的持续讨论中,契约在比特币领域仍然是一个备受争议的话题。

比特币行业中最近流行的契约提案是OP_CTV,它故意减少了一些功能以避免潜在的争议。

我们认为,这些担忧在很大程度上被夸大了。它们源于这样一种想法,即契约可用于对现有比特币实现“污点”,例如要求使用第3方签名,这将在整个代币供应中传播并且无法移除。但是这样的方案实际上可以在今天实施,通过使用2-of-2 CHECKMULTISIG脚本,其中一方拒绝签署不保留2-of-2“契约”限制的交易。但还没有人这样做过,并且理由很充分:这些代币显然不会被用户接受以代替普通代币,并且不会被信托或其他法律义务实体的用户接受。此外,由此产生的代币由于其额外的权重(weight)而需要更高的网络转账费用,并增加了可以使用它们的钱包的复杂性。

用契约做同样的事情会更加昂贵,并且需要更复杂的钱包,没有人会对实现感兴趣,而且用户也会拒绝使用。

进一步证明契约不会造成问题的证据是,它们自2018年推出以来就存在于液态网络上,并没有出现病毒式契约在整个系统中传播的问题。(不过,正如我们将看到的,液态网络上的契约接受率低是有技术原因的。)

契约将为比特币引入新的功能,例如金库或速度限制,让用户能够更加灵活的保管代币。

我们在Elements中发布了资产支持,而契约提供的功能要强大得多;我们可以:

  • 构建开放式限价单或其他算法交易。
  • 构建期权等金融衍生品。
  • 创建“控制代币”,它们是可以启用其他合约中功能的NFT。

具有丰富算法操作码集的多资产场景中,我们将能够匹配以太坊和其它竞争对手的特征集,并且通过避免图灵完备性,我们应该能够实现强大的分析形式。但正如我们将看到的,这个故事会更复杂。

Elements和比特币脚本

Elements于2015年作为具有额外功能的比特币分叉被编写,并于当年以Elements Alpha的名字推出测试网。这个网络早于Segwit,更不用说Taproot;它早于已发行资产;它有一个两段式挂钩方案,最终被证明是复杂和容易出错的,所以在部署到液态网络之前进行了重新设计。它的主要功能是隐私转账,后来扩展到支持已发行资产,一个用于隔离见证的新生计划,该计划被简化并作为隔离见证移植到比特币,以及一组用于启用契约的额外操作码。我们将在本文中关注后者。

当我们引入原始的Elements和液态网络操作码集时,Bitcoin Script依赖于手动的低级编程,也难以进行正式分析。我们的扩展增加了语言的表达能力,但没有解决这些潜在的限制。用户能够开发一些高级应用程序,例如Lending on Liquid,但这种程序更多地证明了人类的聪明才智,而不是在我们的脚本平台上可以轻松编程。其他应用程序,例如Bitmatrix,只能在升级到本文所述的脚本系统后才能部署。从那时起,Blockstream Research独立于Elements开发了Miniscript,这是一种模拟比特币脚本的新方法,可以解决这些分析问题。

尤其是使用Miniscript,可以自动枚举脚本中的所有key,并确定生成交易所需的集合;在收集签名之前找到此类交易大小的上限;回答有关必须满足哪些条件才能花费代币的语义问题;并计算脚本和见证的精确编码。Miniscript补充了同样来自Blockstream Research的Andrew Chow开发的部分签名比特币转账。Pieter Wuille也是原始概念和开发过程的一部分,它提供了一种用于组装产生交易所需的数据的协议。这允许钱包签署交易,即使他们不完全理解(或早于)确切的脚本;他们只需要检查交易是否有意义,生成ECDSA或Schnorr签名,然后让Miniscript工具完成剩下的工作。

然而,即使用Miniscript,我们也没有找到通往Elements上“简单契约”的直接途径。我们2016年的契约结构很难通用,而且它们在实践中的大尺寸很容易受到我们从比特币继承的201操作码限制的限制。因此,即使比特币脚本通过Miniscript、PSBT和Tapscript(在设计时考虑到Miniscript)变得更加强大,Elements对Script的扩展也无法顺应潮流。

突破:新的抽象

2021年2月,Blockstream研究人员Andrew Poelstra和Sanket Kanjalkar终于找到了一种将Elements的契约与Miniscript结合起来的方法。本质上,我们将在“机器”内构建启用契约的脚本,这是一个固定大小的脚本模板,它将使用CHECKSIGFROMSTACK(CSFS)操作码来提取所有交易数据并将其留在堆栈中的固定位置,其中“real code''可以有效地访问它。这使我们能够在转账的许多条件下分摊CSFS式契约的高成本。

事实上,我们后来放弃了这一最初的方法,但这个方法使我们超越了如何实际构建通用契约的概念限制,先让我们看看Elements Script的真正限制是什么:

  • 继承自比特币的31位big-endian算法,不能轻易地与转账金额所使用的64位little-endian一起使用。
  • 而且没有乘法或除法操作码;Elements添加了位移和其他算术运算,但没有添加所需的基础功能,例如费用计算。
  • 对于费用计算,CSFS风格的契约无法访问转账权重,因此无法计算其费用。
  • 比特币的520字节堆栈元素大小限制及不灵活的SHA256操作码共同导致无法散列超过520字节的数据。再加上CSFS需要一次散列大量交易数据,这对总交易规模造成了限制,使钱包开发变得更加困难。
  • 无法执行超过201个操作码的脚本,其中几十个是CSFS机器需要的,还有几十个需要将64位整数转换为32位整数并返回。
  • 除了签名验证之外,无法进行椭圆曲线算术运算,可以强制执行许多操作,但无法验证Taproot输出是否正确。如果我们在Tapscript中保留这个限制,它会减少功能,同时增加脚本的复杂性。

一旦我们认识到这些问题,它们就变得很容易解决:

  • 我们添加了用于操作64位(包括乘法)的新操作码,以及用于将它们转换为旧式32位以用于普通比特币操作码的操作码。
  • 我们添加了“direct introspection”操作码来访问转账数据,从而消除了对“the machine”的需求并减少了我们的操作码数量。我们还添加了一个TXWEIGHT操作码来访问交易的权重以计算费率。
  • 我们添加了“streaming hash”操作码,以允许将数据输入哈希引擎,而无需将其全部放入单个堆栈中。这完全避免了520字节的堆栈限制,而无需脚本解释器使用更多资源。
  • 我们添加了一个ECMULSCALARVERIFY操作码来处理Taproot输出、支付合同承诺和其他椭圆曲线加密操作。
  • 我们跟随比特币消除了201操作码的限制,尽管通过上述改进,它可能是可控的。

完成后,我们有一小部分新的操作码很容易适应比特币脚本模型(例如,没有引入磁盘访问、无限资源使用或复杂的控制流,比如循环),解决了Elements上生产级别契约的大问题。

这是新操作码的完整列表

解决了这些问题后,我们准备好解决一些与契约有关的更深层次的问题,这些问题并不是Elements独有的。

举一个我们可以用这些操作码做的新的事情的一个具体例子。比如考虑一下vaulting方案,60个区块的时间内,只有MAX_WITHDRAW可以撤回。

要创建这个契约,我们需要遵守以下条件(Miniscript符号中有描述,我们将在以后的文章中详细介绍)。

  • 部分提款(partial_withdraw):如果契约中有超过MAX_WITHDRAW的聪(sats),那么契约应该至少有total_value-MAX_WITHDRAW的剩余。
  • “num64_gt(curr_inp_v,MAX_WITHDRAW)”:总输入的聪超过MAX_SATS
  • “asset_eq(curr_inp_asset,out_asset(0))”:输入资产和输出资产零相同
  • “num64_geq(out_v(0),sub64(curr_inp_v,MAX_WITHDRAW))”:输出的值至少应该是total_value-MAX_WITHDRAW
  • "spk_eq(curr_inp_spk,out_spk(0))":剩余代币发给契约
  • FullWithdrawal(full_withdraw):如果契约少于MAX_WITHDRAW聪,则输出没有约束
  • num64_leq(curr_inp_v,MAX_WITHDRAW)
  • KeyControl(keys):等待60个区块后再次花费,则需要一个key K。
  • pk(K):需要key K的签名
  • older(60):下一次提币前需要60个区块的等待时间
  • curr_idx_eq(0):当前的支出指数必须为0。这防止了所谓的半支出问题,我们可以一起花费两个契约的utxos,其中一个可以逃脱约定
  • 最后,我们结合以上所有条件:and(keys,or(full_withdraw,partial_withdraw))

可以通过紧急提币密钥、多步骤提币、白名单和多重签名托管来扩展这些功能。

未解决的问题和未来的工作

通过使用契约支持扩展Miniscript并提供操作码以有效地执行此操作,我们创建了一个框架,用户可以在其中轻松构建契约脚本,了解这些脚本的行为,并创建执行这些契约的完整交易。但是这样做,我们破坏了Miniscript的递归结构,因此破坏了某些形式的分析。

例如,可以通过创建一个其两个子节点是两个原始脚本的and_b节点来“组合”两个Miniscript。(在脚本方面,我们只是将原始脚本连接起来,并在最后添加一个BOOLAND操作码。)如果用户确信她可以独立满足每个原始脚本,那么她就可以满足组合脚本.

相反,如果她想确定一个复杂脚本的可满足性,她可以递归地单步执行该脚本:每当她看到一个and node时,她必须满足两个子脚本,而每当她看到一个or node时,她必须满足其中一个。

有了契约,这种形式的组合不再有效。虽然您仍然可以使用and_b组合两个脚本以获得语法上有效的脚本,但即使两个子脚本都可以满足,结果也可能无法满足。例如,考虑将需要交易的第一个输出来销毁资产A的脚本与需要交易的第一个输出来销毁资产B的脚本相结合。显然,两者不能同时满足。

一般来说,由于契约在构建中的交易中引入了全局条件,单个脚本片段可以对其他脚本片段产生非本地影响。或者,我们可以观察到,在比特币Miniscript中,我们可以将脚本视为支出条件的单调函数(monotone functions)。一个脚本代表了一个规则,用于决定哪些消费条件(签名、哈希原像等)对花费代币是有效的,哪些是无效的。这些规则是单调的,意味着如果某些条件集是有效的,那么任何超集都是有效的。

有了契约,这个模型在单调函数方面根本不适用,因此我们失去了Miniscript提供的通用和强大的分析工具。相反,启用契约的Elements Miniscript程序需要以一种特别的方式进行分析,具体来说就是提出并回答特定的问题。

Simplicity是我们正在并行开发的Script的替代品,它也有一些限制。为了使这种特别的分析站得住脚,Simplicity的解释器在Coq定理证明助手中有一个参考实现,这意味着虽然人类需要提出正确的问题,但答案至少会坚如磐石并且可用机器检查的。使用Elements Miniscript,程序完全依赖于人工代码审查。我们希望我们已经设计出了强大的程序但仍然可以简单且清晰正确。

展望未来,我们计划继续扩展Elements Miniscript的功能,编写支持工具和钱包库以使用它来生成和交互契约,并继续推进Simplicity,这将比新扩展过的Elements脚本更具表现力。

祝大家编码愉快!

If you have specific preferences, please, mark the topic(s) you would like to read: