解锁比特币更多的应用,Miniscript的昨天、今天和明天!
译者前言:由于比特币脚本(Script)的存在,今天,比特币可实现简单的交易功能及各种多重签名、原子互换、闪电网络交易,然而,脚本的功能并非只有这些,但复杂性却限制了更多应用的可能,而Miniscript的提出,目的是让比特币脚本变得更易于访问,并最终解锁比特币更多的应用。
对此,Pieter Wuille和Andrew Poelstra撰写了一篇《 Miniscript: Streamlined Bitcoin Scripting 》来解释Miniscript的由来和作用,以及其未来的发展方向。
以下为译文:
(图片来自blockstream)
关于比特币脚本的介绍
比特币一直以来都有一种机制,它可通过更复杂的策略(而不仅仅是单个密钥)来使币可花费:这就是脚本(Script)系统。虽然脚本主要用于单密钥支付,但它也是各种多重签名钱包、原子互换结构和闪电网络的基础。
然而,这些并不是脚本所能实现功能的全部。脚本可用于表示发布交易而所需的复杂条件,例如(A, B, C中的两个)以及(D 或(E和F)) , 其中 A-F分别表示唯一的密钥,以及哈希原像检查、timelock(时间锁)以及一些更奇特的构造。
比特币脚本的问题:难以验证
但我们还无法充分利用比特币脚本的潜力,其中一个原因在于,为复杂任务构建脚本是很麻烦的:很难验证它们的正确性及安全性,甚至很难找到最经济的方法来编写它们。
这是问题的一部分:即使你知道正确的脚本、设计应用及协议来协商和构造交易,而每次设计新的构造时,都需要大量额外的开发工作。而每次添加,都需要更多的工程资源以满足分析和质量保证。
那如果比特币应用可使用任何脚本,而不仅仅是为数不多的脚本设计,这会是怎么样的呢?我们将不再局限于使用一次性设计,并且可开始设计基于用户指定需求的应用。钱包开发人员还可引入更多基于脚本的选项,同时保持与其他钱包的互操作性。
Miniscript是什么
下面进入Miniscript的世界,这是一种以结构化方式表示比特币脚本的语言,其支持高效的分析、合成、通用签名等功能。
关于比特币脚本的一个例子:
OP_CHECKSIG OP_IFDUP OP_NOTIF OP_DUP OP_HASH160 <hash160(B)> OP_EQUALVERIFY OP_CHECKSIGVERIFY OP_CSV OP_ENDIF
其中A 和 B代表公钥,而转换成Miniscript的表示法就是:
or_d(c:pk(A),and_v(vc:pk_h(B),older(144)))
这个例子清楚地表明,脚本的语义,允许在A签名或144个区块之后的B签名发生时允许支付。而大部分有意义的脚本都可以用这种方式编写。
而使得脚本可读,只是Miniscript的其中一个特性,其主要目的是实现对脚本的自动化推理,如下面的应用例子所示。
Miniscript的应用
当前我们若使用比特币脚本来构建复杂的支出条件,这样做的难度是非常大的,这需要为每个新的用例开发、测试和部署特殊用途的软件。而Miniscript 可以一种跨越任意支出条件集的方式覆盖这些条件,并且通常比专用解决方案要简单得多,也更可靠。
应用1、优化脚本
而其中一个例子,是找到一个实现给定支出条件集的最佳脚本。在比特币脚本中,有很多不同的方法需要签名,描述连接或分离,或实现一个阈值。即使对经验丰富的比特币脚本开发者,正确的选择也可能取决于满足不同条件的相对概率,而且难以进行计算。我们的 在线编译器 ,其可立即找到与给定支出策略相对应的最佳Miniscript。
介绍中的例子可通过以下编译策略获得:
or(99@pk(A),1@and(pk(B),older(144)))
这是一种书写方式,即左侧A签名发生的概率为99%,而右侧(144个区块后的B签名)发生的概率为1%。
应用2、通用支出
一旦找到了一个有效脚本,很多脚本使用存在的一个共同障碍就是,不同的支出机制之间缺乏互操作性。而希望实施长期时间锁( timelock)或复杂多重签名要求的用户,可能会因此而担心。而Miniscript,通过直接表示支出条件,允许任意策略的表达,以便任何人都可:
- 计算脚本的相关地址;
- 确定在给定时间,哪些签名者进行签署是必要或足够的;
- 生成一笔有效交易,给定一个足够的签名集;
他们也不必担心自己的需求可能会以和签名软件不兼容的方式发生变化,从而保证使用比特币脚本不会对他们造成限制。
应用3、准备金证明
与签名问题相关的是 证明储备金 的问题,这是一个公司证明其可在不实际使用储备比特币的情况下,可使用其中部分比特币的过程。虽然当前已经存在这方面的工具,例如 Blockstream的准备金证明工具 ,但行业内还没有一个通用的标准。而没有Miniscript,可能也不会有一个能够涵盖当前多样性托管解决方案的准备金证明标准。
支出策略的构成
让我们来看看受互操作性需求约束的比特币脚本开发者的一个具体例子。以一家托管大量BTC的公司为例,该公司希望这些比特币只有在其大多数董事同意的情况下才能被动用。然而,一些独立的董事希望使用他们自己现有的钱包软件和硬件,并且不希望参与(要求他们使用专用应用而提供的)单个密钥签名的方案。
在没有Miniscript的情况下,生成一个包含所有签名者要求的脚本,同时向所有签名者保证完整的脚本是健全和完整的,并且他们的钱包软件与结果兼容,这将是一个无法克服的难题。
而使用Miniscript,这些董事可简单地在自己的钱包软件中编写策略,构造一个描述阈值要求的单个策略,然后将其编译为Miniscript。他们可直接验证编译器的输出是否与原始策略匹配,以及原始策略是否满足每个人的需求。然后,他们可使用任何与Miniscript兼容的软件来计算接收币的地址,在消费时收集签名,并将这些签名组合到一笔有效的交易。
举一个简单的假设性例子:一家公司正在用一个定制的多重签名存储钱包,其有两位董事,其中一位董事使用的是Blockstream Green钱包(配置为2-of-2多签,经一些延迟后变为1-of-2),而另一位则使用的是Electrum钱包(标准1-of-1)。如果没有允许组合安全、可互操作脚本策略的工具,使用Green钱包的董事就不可能将其策略作为“参与者”添加到公司的多重签名方案中。而如果Green和用于构建公司多重签名的软件都支持Miniscript,那么两位董事就可直接使用他们自己喜欢的钱包,甚至不需要知道钱包底层的脚本到底是个啥。
动态联盟(dynamic federation)
Miniscript的作用,我们可以在Blockstream的 Liquid侧链 中找到一个具体的例子。Blockstream目前正在开发的一个功能(内部称为动态联盟),这是一个允许现有Liquid成员管理新成员添加或更新控制联盟托管比特币可使用性的脚本。Miniscript为联盟成员提供了快速、高效地构建此类脚本的工具(事实上,Miniscript编译器发现了现有Liquid脚本的一个22字节的较短版本,比我们原来的手工优化脚本节省了5%的工作量)。但更重要的是,Miniscript允许成员自动验证提议脚本的重要属性,从而减少了成员之间相互协调或对提议脚本执行昂贵的手动安全审核的需要。
特别是,成员可自动验证任何脚本提议是否包括:
- 他们自己的密钥;
- 一个时间锁紧急支出条件,确保其在未来是正确的和足够远的。
Miniscript的历史
关于Miniscript的想法,最早可追溯到2018年夏天。那年7月中旬,Pieter Wuille为Bitcoin Core引入了 输出描述符(output descriptor)方案 ,这是一种描述Core支持的多种不同地址类型的通用方法。与此同时,当时还是Blockstream实习开发者的Andy Chow在开发 部分签名比特币交易(PSBT ,也被称为 BIP 174 )方案,这是一种钱包互操作性协议,而这种提议在钱包领域获得了广泛关注。
PSBT的一个重要组成部分是 finalizer(完成者) ,其是从PSBT中包含的签名者数据集合中,组装实际比特币交易的参与者。这种装配需了解满足的脚本,这意味着支持PSBT的钱包必须实现自己的专用finalizer(完成者),而这就需要额外的开发并会限制互操作性。
而那个时期,Andrew Poelstra在努力尝试把 PSBT实现引入到rust版比特币 (通过Rust编程语言编写的通用比特币库)软件(这种实现最初是由Carl Dong提出的)。Poelstra在 IRC 交流频道 上观察到:
“如果你有高度结构化的脚本模板,你就不需要再去真正理解脚本。”正是这一想法,最终成为了Miniscript的核心。
实际上,Poelstra和Wuille一直在从事一个与托管相关的项目,但由于缺乏可用于复杂多参与者脚本的标准工具,他们感到非常沮丧。两人于2018年8月会面讨论此事,Wuille建议实现这些“高度结构化的脚本模板”,以此作为Bitcoin Core输出描述符的扩展。
随着扩展的形成,其演化为比特币脚本的结构化子集,最终成为Miniscript,以及一种更简单的“策略语言”来直接表示支出条件,并且可编译成Miniscript。2019年1月,Wuille在斯坦福区块链会议上介绍了 该方案的初步结果 。
今年5月份,Sanket Kanjalkar加入Blockstream进行了为期三个月的实习工作,而其重点就是实现Miniscript工具,并帮助它与Bitcoin Core集成。在他的帮助下,MiniScript变得更小、更高效、更容易分析、更好地防止延展性问题。
这些迭代的结果,最终形成了今天的Miniscript。
相关工作
Miniscript是建立在比特币生态系统中很多其它项目的基础上的,并与它们形成互补。特别是,如上所述,Miniscript 扩展了Bitcoin Core的钱包输出描述符,并补充了PSBT,以实现完全通用的更新者(updater)和完成者(finalizer)。
Miniscript可以与 Ivy 进行比较,后者是另一种旨在使比特币脚本的高级功能易于访问的语言。然而,相比Ivy,Miniscript 是脚本(子集)的直接表示,这意味着可有效地验证“miniscript”的正确性和可靠性。Miniscript还可接受很多其它形式的静态分析,而这是脚本(Script)和(Ivy)都无法做到的。
Miniscript的策略语言与Ivy相似,因为两者都是抽象的,它们都必须经过编译后才能在区块链上使用。然而,Miniscript的策略语言在结构上与Miniscript本身非常相似,因此可以很容易地验证编译器的输出是否与编译器的输入匹配(事实上,这甚至可手动检查),并且可以很容易地验证是否符合用户的期望。
另一种与Miniscript相关的区块链语言是Blockstream的 Simplicity 。与Miniscript一样,Simplicity是一种低level语言,其旨在直接嵌入到区块链交易当中。此外,Simplicity也支持很多形式的静态分析,这些分析在部署区块链合约时是非常重要的,但这在以太坊EVM等替代产品中却难以甚至不可能实现。
另外,Miniscript 不像simplicity那样强大到可以表示任何可计算函数,它的作用域非常有限:它可以表示签名需求、时间锁(timelock)、哈希原像(hash preimage)以及它们的任意组合。 这种范围的缩小,使得Miniscript更容易对它所涵盖的用例进行解释,而且重要的是,这允许它在现有的比特币区块链上工作。相比之下,Simplicity则是对比特币脚本的彻底背离,其目前仍处于发展的早期阶段。
未来的工作和结论
在设计 Miniscript时,我们着手让比特币脚本更容易访问。虽然很多工作都集中在调查和提议未来对脚本及比特币共识规则的修改,以添加额外功能,但我们也看到,基础设施甚至没有以通用、安全、可组合和可互操作的方式使用已存在的功能,这是当前所欠缺的。
这项工作还没有彻底完成。我们有两种Miniscript实现(分别是C++和Rust版本)和策略编译器,但是为了该技术易于访问,我们需要在常用软件中集成它。通过与Miniscript兼容的PSBT 实现(updater和finalizer),即使没有明确的支持,许多PSBT签名器(包括基于硬件钱包的签名器)也可以用于复杂的脚本。此外,编译器也可以进行改进,因为策略到到脚本转换中有很多优化尚未被考虑。
这一路走来,我们学到了很多东西:
-
脚本资源限制使得策略优化复杂化
:众多共识和标准性强加资源限制的存在(最大opcode、最大脚本大小、最大堆栈大小……),使得我们一旦接近这些限制,就很难找到给定策略的最佳脚本。这是有趣的,因为它可能会指导未来脚本的改进建议。与此相关的是,我们还惊讶地了解到,比特币共识规则实际上将参与执行的
OP_CHECKMULTISIG(VERIFY)
的密钥数,计算为每个脚本201个非推送(non-push)opcode的限制。 - 可变的验证内容(witness)大小使费用估算复杂化 :简单的支付和多签结构具有独立于现有密钥精确集合的验证内容(witness)大小。而一旦我们转移到更复杂的脚本中,验证内容(witness)大小就会变大,可能会使费用估算复杂化。特定交易输出的潜在签名者,可能需要乐观地猜测将采用的廉价路径,并构造相应的交易。如果不是该路径的所有密钥或哈希最终都可获得,则可能需要更改交易本身以考虑增加的费用。
-
隔离见证(Segwit)的堆栈编码使得优化复杂化
:因为segwit的输入堆栈不再编码为脚本,而是直接编码为堆栈元素列表。这带来了不幸的副作用,它会把“true”的编码从1字节更改为2字节。因此,Miniscript 需要关心哪些子表达式进入
if/else/endif
结构的哪一边。 - 可扩展性 :由于segwit,交易可扩展性不再是协议正确性的破坏者,但它仍然可能有不利影响(例如广播交易时对收费率的不确定性、减慢致密区块(compact block)传播以及干扰哈希锁作为全局发布机制的使用)。基于这些原因,我们在设计Miniscript时,考虑了不可延展性问题,并学习了一般验证内容(witness)不可延展性的推理。为了实现这一点,Miniscript依赖于某些segwit特定的规则。
- 常见子表达式的消除是困难的 :尽管尝试了很多次,Miniscript依然不支持优化重复的子表达式。虽然这在某些特定的情况下是可能的,但在一般的脚本中似乎很难做到。添加某些堆栈操作opcode可能会改变这种情况。