Covenants
演讲者: Jeremy Rubin, Andrew Poelstra
日期: November 5, 2021
记录者: Jeremy Rubin
译者: Ajian
标签: Covenants
根据 “苏格拉底村” 规则,(除了演讲者之外)所有与会者都使用了假名;音频也不会公开,以保护提问者的匿名。
限制条款的概念
Shaun Apps(SA):我先做个开场吧。Andrew 和 Jeremy 都会来。然后我们就以问答的形式开展,最后再回答观众的提问。这就是本场演讲的形式。
SA:言归正传,限制条款(covenant)。这个术语有宗教和法律方面的历史语境。大多数人理解的可能是物权法范畴内的概念,比如使用限制条款来约束土地的用途。但是,你们有多少人知道它在比特币领域内的意思?好的,看来很多人都听过。所以,基本上,限制条款给予了比特币脚本内省的能力,例如你可以通过预先定义的约束来限制一个输出的花费方式。我不太清楚限制条款的概念可以追溯到多久远以前。但是这一篇影响力很大的 BitcoinTalk 论坛帖子,作者是 Greg Maxwell,就介绍了限制条款的概念,并提出了一些假想的案例。举个例子,一种 Smashcoin 限制条款,在花费由此种条款锁定的资金时,必须提供攻击另一种密码货币的证据;甚至是一种 adultcoin 限制条款,必须提供某人的生日才能花费,它会限制接收者的年龄必须大于 18 岁。当然,这些都只是假设的、博君一笑的例子,这篇帖子也只是一个玩笑。但是,今天我们能够看到,限制条款有一些非常激动人心的用途。有请 Andrew Poelstra 和 Jeremy Rubin。Andrew 准备从 CHECKSIGFROMSTACK(也叫 “CSFS”)的视角出发,理解限制条款。Jeremy 的演讲则讨论 CHECKTEMPLATEVERIFY(也叫 “CTV”)。其实,有不止一种办法可以在比特币中实现限制条款。不同的想法、不同的提议,都有自己的取舍也应用场景。所以,我们来好好探究一番。Andrew,你想先来吗?
Andrew Poelstra(AP):当然。如 Shaun 所说,某种程度上我代表着限制条款的 CHECKSIGFROMSTACK 流派。这种流派似乎代表着一种更普遍的视角。所以,为了帮助大家建立限制条款的概念 —— 我认为,这个概念确实肇始于那个 BitcoinTalk 帖子 …… 我不知道有什么讨论比这个更早 ……
Jeremy Rubin(JR):那也是我所知最早的。
递归和非递归的限制条款
AP:有两种类型的限制条款,我把它们叫做 “递归型限制条款” 和 “非递归型限制条款”。我想这两个术语可能也源自那篇文章。“限制条款” 是且只是一种限制资金可以花到哪里去的办法。使用比特币脚本,你可以决定在什么条件下一笔资金可以移动,然而,一旦条件满足,资金就可以去任何地方。而限制条款则更进一步,允许你限制它的去向,实现上面说的种种傻事。
非递归的限制条款和递归的限制条款有一个区别。在非递归的限制条款中,你可以限制一笔资金的去向。你可以把它引到另一个限制条款中,从而进一步约束其去向,等等。但是,你必须规划出这笔资金的全部未来,你可以让它经历很多个限制条款,但你必须在一开始就把所有的限制条款写出来。递归型限制条款则不同,你可以让资金回到一个本质上与其来源相同的限制条款中;这就创造了一种有限的状态机,资金可以在不同的状态中移动。要么资金可以只待在一个地方,只是改变一些常数;要么它们可以移动到不同的状态,甚至它们可以返回原来的状态。凭借它,你可以创建一种让资金无法逃离的限制条款。甚至我的用词,“无法逃离”,也反映出其内在的危险性,同时也反映了许多年来人们对启用递归限制条款的恐惧。
不同的限制条款提议
AP:这些年出现了许多的限制条款提议。其中一个是 Jeremy Rubin 的 OP_CTV,曾用名 “OP_SECURETHEBAG”。在它的整个生命中,它有过多个名称,也经历了几次迭代。OP_CTV 被有意设计成规避递归型限制条款,以避免所有这些看似是玩笑、某个层面上又不是玩笑的东西。不过,另一种非常著名的限制条款提议,是非常通用的,它基于两种新的操作码,CHECKSIGFROMSTACK 和 CAT。 还有一种更为高效的变种,它增设的操作码可以直接检查正在花费这笔资金的交易。也就是说你可以直接把花费资金的交易本身拉取到堆栈中,然后执行你的脚本、直接地约束它们。一定程度上,你是在直接检查花费资金的交易、为之施加任意的约束。尤其是,如果你限制了交易的输出,那你就实现了一种递归型限制条款,可以永久困住这笔资金。这两种方法之间存在取舍。如果你尝试做一些事情来避免递归型限制条款 ……
比特币的限制条款有一段挣扎的历史,令人惊讶的是比特币中实际上不存在任何限制条款。脚本的本性意味着它必须检查交易。而比特币脚本的 CHECKSIG(检查签名)操作码让你可以检查交易的一个签名。脚本必须能够获得签名数据,这样 CHECKSIG 操作码才能访问签名。但从数学上来说,数字签名工作的方式内在地跟交易数据本身有关。使用一些聪明的数学技巧,你可以迫使一笔签名 “往回走”、获得交易的一个哈希值并推入堆栈,然后开始分析它。事实上,你可以使用比特币的 CHECKSIG 操作来表示 “这笔资金只有在你提供的签名全都是 0 的时候才能花费”;这是一个有效的签名,其中有一些元数据,但本质上就是特定位置有一些 1 和 2 的全部为 0 的数据;脚本中不包含公钥。也就是说,正常情况下,你的脚本的意思是:“我需要这个公钥对花费它的交易的一个签名”;而在这种情况下,你是在说 “我需要拥有某一个公钥,其对花费这笔资金的交易的签名拥有这样的形式”。这样编写脚本之后,为了满足它的条件,你就必须提供一个能够让这个签名有效的公钥。而这个公钥,事实证明,在代数上是一笔交易的哈希值的奇怪混合。因此,你可以在脚本中约束花费这笔资金的交易的哈希值是什么样的,也就是你得到了一种限制条款。这种脚本无法工作的理由是,比特币中的所有 sighash 标签,除了其中一种,全部都覆盖了用来签名的公钥,所以你尝试这样做会遇到循环。但是,如果我们拥有 SIGHASH_ANYPREVOUT,一种热门的提议,那么你就可以像上面说的,靠 CHECKSIG 获得一种限制条款。
这个故事的重点不是提出一种严肃的限制条款实现,而是证明要避免实现递归型限制条款是非常困难的。所以我的立场是 —— 这不是一场辩论哈,但我的立场是,我们应该拥有一种非常通用、专门设计的方法来实现递归型限制条款,而不是尝试窄化一些东西。因为首先,如果你实现了一种窄化的限制条款提议,你就损失了一些灵活性。然后,该要经历的共识变更流程,你也一样要经历,但你只得到了更少的收益。不过,也有可能你最后会得到递归型限制条款。我打赌你会,我认为这是非常难以避免的。这就是我的部分。
JR:我认为,可能除了结论,AP 讲的大部分东西我都同意。完美的电视节目少不了戏剧,但现实生活没有那么多戏剧。我认为,Andrew 和我可能并不是比特币社区的绝对中心,但我们都认为限制条款对比特币是好事。我的观点之所以不同、我之所以开发 CHECKTEMPLATEVERIFY,都是出于工程师视角的实用主义(后面我会花点时间解释一下 CTV 的行为)。得到这种完美的元件 —— 给我们安全又有表达力的递归型限制条款 —— 是非常困难的。社区里也有人反对这种东西。所以,在 CHECKTEMPLATEVERIFY 上,我尝试的事情是不断寻找更狭窄的协议设计,让最反对限制条款的人也能接受。在一定程度上,这就是 CHECKTEMPLATEVERIFY 的目标。我们可以用来解决一些重要问题的最小方案是什么样的?我认为这就是人们会混淆的东西,我这样做不是因为我认为我们只应该实现这样的最小方案,而是因为我要的东西是比特币渐进式升级的一部分。与其一次搞得天翻地覆,也许我们应该用简单的元件引入可以实现许多有趣事物的能力。我们可以依据最小方案,先培育跟限制条款开发有关的工具链和基础设施。随着时间推移,当我们知晓了人们希望用限制条款来实现的更多应用之后,可以再实现一些充分通用的、对广泛的其他用途都有用的东西,而不至于引入漏洞,也不会为了设计对开发者基础设施、工具链、用户接口的限制条款框架性而一次性消化大量工程任务。
所以,尤其是 CHECKTEMPLATEVERIFY,它实际上非常接近于 Andrew 所谓的 “你可以使用公钥来做的复杂的事”。实际上它还要更简单一些,它做的事情仅仅让你限定你要用哪一笔交易来花费这笔资金。比如,我准备把钱打到这个地址,然后让这个地址可以给我的 100 个朋友支付,这就是一个你可以用 CHECKTEMPLATEVERIFY 来实现的东西。你在这个地址中承诺的,就是你接下来准备使用的交易。那么,什么使用你会想要我说的这种功能呢?这就是 CHECKTEMPLATEVERIFY 的主要用场之一,拥堵控制。
在开发过程中,我思考这些问题的方式是,我希望找出具体的一个用途,是 CHECKTEMPLATEVERIFY 绝对可以克服的,同时为人们需要这种(或这一小组)功能给出可靠的论述,然后设计出可以安全地满足这种需求的东西。然后再回到原点,看看还有没有别的东西可以做。但我希望确定的是 CHECKTEMPLATEVERIFY 作为一种解决方案,在产品市场上可以满足最终用户的需要。我给出的这个例子,是非常巧妙的,它将分割交易的任务从支付端转移到了接收端(from a spending side to expanding side)。举个例子,如果现在交易池很慢、网络手续费很贵,如果你想获得区块确认,你可以让你的交易只有一个输出,但你的 100 个朋友都可以完全验证,他们将随着限制条款的拆解,在未来收到支付。这对(比如说)交易所来说是非常棒的,而且这种思路可以通过利用 CHECKTEMPLATEVERIFY 可以实现的其它东西、扩展成复杂得多的形式,比如免交互地创建闪电通道。我认为,总的来说,当你开始深入思考你可以用这种非常简单的限制条款来做什么,甚至有一种我为 Sapio 编写的编译器。它可以制作基于 CHECKTEMPLATEVERIFY 的智能合约,可以用来制作所有基于限制条款的奇妙事物。这个编译器自身是通用的,对未来可能定义的限制条款也兼容。如果有人走过来说 “我发明了一种新的限制条款”,我们就有机会开发可以在未来、我们扩展出更多类型的限制条款时使用的工具链。
这就是我的观点。从现在来看,我不知道我们要多久才能实现充分通用的限制条款,Andrew 可能会说你只需要 CHECKSIGFROMSTACK,但这些限制条款的体积都非常非常大,在链上展开它们是非常昂贵的。CHECKTEMPLATEVERIFY,因为其简洁性,以及你要知道自己想把资金发送到哪里的提前编译的概念,其链上足迹是非常少的。就链上效率而言,稍微复杂一点点的限制条款都很难打败基于 CHECKTEMPLATEVERIFY 的限制条款。就像有一条河需要限制条款造的桥才能渡过,而 CHECKTEMPLATEVERIFY 做的事情就是砍倒一棵树,我把它扔到河上并表示 “现在你可以走过去了”。我感觉许多开发者会说 “不,我们想造一座华丽的桥,花点时间吧”。而我表示,“靠这个我们今天就可以过河了,也许日后我们可以用它造出更高效的东西,甚至于在未来我们可以双管齐下,等等。”
哪些情况会搬起石头砸自己的脚
SA:提醒一下,在我们提问的时候,观众也可以自由回答这些问题。那么,你们能不能多谈谈,在设计限制条款的时候,哪些因素会导致搬起石头砸自己的脚?哪些部分需要最仔细的考虑、哪些地方可能会出错?
JR:我认为,基本上,只要你搞错一笔交易的任何一个细节,你就完蛋了。比如,在 CHECKTEMPLATEVERIFY 中,有一种情形是你发送了错误数量的资金到一个限制条款中(其实所有类型的限制条款在这种情况下都会出错);你制作了一个限制条款,预期它会使用 1 BTC,但你只发送了 1 BTC减 1 聪 到这个限制条款中,那么整个限制条款就没法按你预期的方式执行了。这个领域对任何开发限制条款的人都是一个大挑战。另一个大问题是 —— 我自己已经有某种程度的解决方案了,但在社区中还不是那么知名 —— 支付手续费。假定你有一个限制条款,你限制了资金只能以某一种规定得很细致的方式花费,那么你怎么支付手续费呢?如果你必须支付比自己预期更多的手续费,那么你可能会过量消耗限制条款的资金,导致它无法正确执行。所以,支付手续费也是一个非常棘手的问题。我的通用解决方案是,在这里讲可能有点离题哈,我们应该建立一种手续费支付方法,可以完全独立于交易的执行。不论你使用的是限制条款还是别的智能合约代码(比如闪电通道),直接执行你想要的交易图,另外考虑手续费支付。
AP:关于手续费问题,我绝对我们不必在细节上过度纠缠。但我认为,应该有一种应用 “子为父偿(CPFP)” 的办法,跟当前建构交易的方法没有很大差别。
但是,回到最初的问题,交易的哪一部分最危险、哪些部分相对没那么危险?不论你限制你的资金花费到哪个输出,那个输出就是最危险的部分。就像 Jeremy 说的,你可以让你的输出变成没法执行的东西。 输出的具体数值,在你建构限制条款所用的交易时可能是无法预计的。如果你的限制条款要求你构建一个太大、违反共识限制的脚本签名,这个输出也将变得无法花费。或者,你的执行步骤可能超出脚本签名的余量(balance)。也许你的限制条款 会要求你把两个数相加得出一个大于 32 位的数,等等。在一些奇怪的状态下,你的限制条款可能会让你受到惊吓:这些钱变成无法花费的了。即使你可以看到结局,限制条款也会强迫你往那个方向走。输出本身就是最危险的东西。
限制条款还可以约束交易的其余部分。比如你可以约束时间锁必须是某一个区高度。就算你搞砸了,你也必须等待网络达到这个区块高度。我猜,如果你搞砸了,你可能会让资金锁定几年乃至几十年 —— 可能没有什么安慰作用,但至少从原理上说它是可以恢复的。或者,如果你要求交易的版本号是一些奇怪的、非标准的数值,虽然这样的交易是可以被区块链接受的,但你就必须直接找到矿工,因为这样的交易是无法通过网络来传播的(尽管可以得到区块链确认)。诸如此类的东西还有你的输入的 sequence 字段,这个字段会约束交易的 “手续费替换行为”;等等。但实际上这些都是输出。当我们讨论限制条款时,我们真正在讨论的是限制输出,虽然在我看来,这是一种更通用的限制交易的形式的做法。最终来说,无论你是要做出酷炫的应用,还是危险的影响,都必须限制输出。
JR:我补充一点我的解释:输出也包括了的交易的输入。如果你的一笔交易有两个输入,你可以让两者都约束被创建的一个输出,这会得到一些让人惊喜的结果。所以,你可以设置两个输入,两个输入都表示需要创建一个价值 1 BTC 的输出,然后一个输出就能同时满足这两个输入。因此你在两个不同的限制条款中都丢失了一半的价值。所以,两个限制条款之间可能存在某些令人惊喜的互动,这也是为什么至少在目前,我对 CHECKTEMPLATEVERIFY 的做法是尽可能缩减它的范围,这样我们就能做出一些我们知道它一定程度上可以工作的东西。更令人意外的是,随着时间的推移、我们得到全部的复杂性之后,会更难得出正确的东西。
限制条款提议对 Layer 2 协议的影响
SA:Jeremy,你提到限制条款的用途之一是拥堵控制。你们能不能从它们会如何影响 Layer 2 解决方案(比如闪电网络)的角度讲讲这些提议?我们能从这些提议中得到什么?它们能不能优化现有的东西、如何优化?
JR:Layer 2 加上限制条款会非常精彩。当前,Layer2,额,好吧,Layer 2,管它呢。现在开发 Layer 2 会遇到很多问题。不要误会,我认为 Layer 2 是许许多多问题的良方,包括闪电网络,侧链,等等。我希望将比特币和区块链想象成 CPU,而 Layer 2 就是操作系统。那么,如果你给基础层增加了新功能,你的操作系统自然能利用这些新的工具、取得很棒的成果。
我喜欢的一个跟闪电网络有关例子是创建通道。当前这有点困难,因为双方都必须在线。如果你想用存放在 Coinbase 交易所中的钱开启一条通道,那么你必须先从 Coinbase 交易所取出资金,然后跟某人开设一条通道。如果我们能做到直接说 “Coinbase,请为我开设一条通道”、然后就离线、等待 Coinbase 给你发送一条通道呢?这里面无需对 Coinbase 的信任假设。这会给吸引流动性带来非常大的影响。
还有一种情况,是拥堵控制跟 Layer 2 有密切关系的地方。假设你想一次性创建 10000 条通道。现在,你必须消耗大量的区块空间才能做到。如果有 CHECKTEMPLATEVERIFY 或其它限制条款,你可以推迟产生链上负载,在你想要关闭通道的时候才懒洋洋地释放。如果你开启了一条通道,而且无意关闭它,你可能在一年以后才实际关闭它。这对于吸引流量有很大的影响。
还有关于退出的。在某一些侧链中,你可能要同时给大量用户支付,我知道 Stacks 在共识协议中安排了一些措施,以应对要给一定数量的用户支付、又无法让交易得到确认的情形。但结果是产生了一种共识错误。有了 CHECKTEMPLATEVERIFY,你的 Layer-2 侧链就可以无视交易池的天气。你总是可以为你的单输出交易支付很高的手续费,然后让人们慢慢取走它们。
限制条款也对 Layer 2 和个人的保管问题又很大帮助,我们可以分别讲讲。Blcokstream,是一家出色的公司了,在他们的 Liquid 联盟侧链中留下了 bug:在你将资金存入他们的 Liquid 托管服务之后,资金必须每隔几周就移动一次,以刷新时间锁。如果你有限制条款,就不需要这样移动资金了。现在,因为他们有这样的限制,Liquid 网络中需要运行一项服务,保证资金定期移动了、时间锁刷新了、错误的情形不会出现。有一次这项服务没有正确运行,Andrew 也许可以给出详细的解释,但可能这也离题了。然后时间锁的保证就被打破了。限制条款可以解决这个问题:你可以更多表达关于必须按构造发生的行动的逻辑,而不是自动化保证行动发生。
还有不同类型的存款。你可以设置让资金自动返回给存款人,而不是出错、交给另一个签名人。所以限制条款有许许多多可以帮助 Layer 2 的地方,要是能够部署,我们都会觉得很激动。
AP:这是一个非常棒的问题。说回那个 bug,问题在于资金必须每 2 周移动一次,而我们设置定时器的方式是错的。在比特币网络上你没法即时响应。创建交易之后你也没法期待它们会在下一个区块立即确认。计时器到期之后你还必须等待生成一个新区块。后来我们改变了逻辑,将移动资金的时间提早了一些。我记得我们好像提早了 300 个区块,差不多吧。但这是一个很棒的例子,说明了限制条款在哪些方面有帮助,因为它也有点特别:a)不断清扫资金有点烦人,也很昂贵;b)当你在处理签名和时间锁时,希望矿工能及时接受你的交易,是非常令人挫败的体验。你没办法保证它会工作,而且它导致了这样令人尴尬的 bug,然后就被 Telegram 上的人注意到了。
在 Jeremy 前面说的东西 —— 理论上你可以让资金原地不动,等待 Coinbase 为你开启一条支付通道 —— 的基础上,你可以将它通用化。在许多情况下,你都不必在乎是不是 Coinbase,你可以说,“任何人,只要有足够多的通道容量、可以连接到我的支付目标,都可以拿走这些钱并开启一条支付通道”。你可以将大量操作都外包出去,而在常规情况下你必须跟通过身份验证的对等节点在线交互。你可以形式化表达你对时间段的需求、把一些代码丢在区块链上,然后等待一些人捡起它。
感谢你给出了这么漂亮的回答。我本来以为限制条款跟 Layer 2 没什么关系,主要跟 “保险柜(vault)” 和保管方案有关。但你讲出了一些我通常不会想到的,很棒的应用。
SA:你们想回答观众的问题吗?
保险柜应用
Q:我想听听你们对保险柜应用的看法。
AP:我的想法吗?坦率地说,我认为保险柜对比特币的大规模采用是必要的。那么,保险柜是什么呢,它们是一种限制条款构造,你可以约束资金的流向,使它们除了去往某个特定的目的地,哪儿也去不了。资金就呆在这种限制条款脚本中,如果你想移动它们,你必须创建一笔定义了目的地的交易,然后将资金移动到一个暂存区域,然后在三天(或者限制条款逻辑规定的别的条件满足)后,资金就可以移动到目的地。在三天的等待期中,任何拥有特定私钥的人都可以重设定时器,或者可以改变目的地,而且改变目的地时必定重设定时器。这背后的想法是,如果你的私钥脱离了你的控制,盗走你的私钥的人无法拿走你的比特币。他们可以尝试并移动资金到这个为期三天的定时器中。你可以不断重设这个定时器,然后你们两个会有一场手续费竞争。如果攻击者的目标是阻止你拿回资金,那么他们可以做到,只是要不间断地在线、花钱来维持攻击。但无法拿走资金,应该就能阻遏这种昂贵的攻击,私钥也就没那么敏感了。
关于保险柜,虽然你控制着你的私钥,你可能还想要限制资金取走的速度。也许这些私钥受到外部的法律的限制,也许因为输出中的钱属于公司,你的董事不允许你一次性花太多的钱。又或者,你安排了一些严密保管的冷私钥。热私钥可能被盗,因此你希望这些热私钥只能一点一点地拿走资金。你可以使用相同的金库构造,指定资金只能在一段时间后移动并且只能移动特定的数量。
在当前的比特币中,如果你授权的资金的移动,那么移动任意数量到任意目的地都可以,它是完全二元化的,要么什么也拿不走,要么全部都可以拿走,对于尝试保管比特币的个人用户,这真的很吓人。如果有人要从事道富银行(State Street)这样的业务,或者保管大量的比特币,那真的很吓人。
我认为保险柜是比特币被广泛采用的必要条件,希望我们能做到这一点。
JR:我也喜欢保险柜,CHECKTEMPLATEVERIFY 可以做出稍有区别的变种,但也是很有意义的。我认为我制作的保险柜有 一种最重要的属性,就是它有故障应对机制,它可以说是一种 “不冷不热” 的钱包。热钱包的意思是你可以随时随地花费,而冷钱包要设计得难以动用。比如说,你去了三个不同的沙漠,然后把刻有你的种子词的金属板埋在沙子底下,需要获得其中两个才能恢复你的多签名钱包。或者你可以埋在混凝土底下,对抗核辐射什么的,这个我们改天再说。完成了这些困难的操作之后,有一天,你说,“我要把钱取出来!我要把钱从这个 2-of-3 多签名钱包取出来”,这你是做不到的。需要安排一种紧急取款流程。你可以模拟你的备份流程,因为你每次访问它都会泄露熵。每一次你动用紧急取款,一些人就能意识到“Andrew 又在整理他的资金了”。另一方面,做起来越困难,你就越是不想经常做。沙漠可能会换成你的书桌的三个抽屉,降级了,但这样你才愿意为之付出一次性的成本。你可能会想:“随便吧,等我去一个新城市时,我再带上 GPS,把种子词埋在某个地方”。而有了这种不冷不热的钱包,你可以把你的资金设置成年金。假设里面有 10 BTC,在上一次取款的一个月后,你可以取出 1 BTC。这样就可以从保险柜中取出流动资金。但是,如果你被攻击了,你也可以将它们紧急发送到深冷存储中。它所做的是将你对备份的两种担心 —— “我希望我的资金是安全的” 和 “可以花费但我也想要在紧急的时候恢复它们” —— 分离开来。很好地分切两者之后,我认为,会有多得多的用户接受这种更安全的冷存储备份。
重新启用 OP_CAT 的提议
SA:一部分围绕限制条款的讨论是重新引入一个被中本聪在 2010 年禁用的操作码,叫做 “OP_CAT”。你们能不能讲讲这会对限制条款的提议有什么样的影响?我们从种可以得到什么?它在限制条款中有什么作用?
AP:OP_CAT 是我最喜欢的操作码。CAT 是 “拼接(concatenate)” 的缩写。OP_CAT 的功能就是把你的脚本解释器环境堆栈中的两个对象拼接在一起。现在你有了一个元素,是两个对象前后相接而形成的。在比特币中,执行就是验证,所以你也可以反过来运行。如果你有一个很大的数据,那么你可以要求用户在花费时提供两半数据,然后你再 CAT 它们,检查是否相等。你总是可以这样逆转每一个操作。
就 CAT 自身,你可以用来打碎所签名。从代数上来说,不论 ECDSA 签名还是 Schnorr 签名,都有两个不同的部分,一个是数字的部分,我们通常称为 s
,另一部分是一个公钥,我们通常称为 R
。 如果没有 OP_CAT,如果你尝试实现一些有趣的限制条款技巧,比如通过发现代数上的后门来把限制条款藏进协议中,那是很难做到的,因为你无法分割签名,因此无法将 s
跟 R
分割开来、专门限制某一样。 s
和 R
是完全不同的两种东西,而且 R
更难限制。
在 Taproot 中,我们已经有 Schnorr 签名了;事实证明,使用 OP_CAT 和 Schnorr 签名验证操作码, 你就可以做出一种形式的非递归型限制条款,你可以确确实实得到一条交易的哈希值。你得到的甚至不是什么有趣的、扭曲了的交易哈希值,而就是堆栈中的交易数据的 SHA2 哈希值。我不想涉及太多代数,但这真的非常酷,所以我要说清楚一些。Schnorr 签名的生成方式是: s
等于 k
加一个哈希值乘以 x
,这里的 k
是一次性私钥,而 x
是你的真实私钥。使用限制条款和 CAT,你可以约束 k
等于 1
,而且 x
也等于 1
,这样一来,等式就化约成了 s = e + 1
。在比特币中,你没法真的对大数字作几何运算,但使用 CAT,你可以把大数字打碎成小数字。你要做的事情就是取得签名、约束它,使你的 s
值必须等于你的交易的哈希值加 1
,你可以使用 CAT 拉取 s
值的最低几个字节,然后使用普通的操作码从中减去 1,然后使用 CAT 把结果再放回去。这样,你就得到了所有交易数据的 SHA2 值。然后,你要求用户将显式的交易数据放到堆栈中,然后使用 SHA2 操作符来压缩它、检查它跟你从签名中抽取的部分是否相等。现在,你再使用 OP_CAT 来打散刚刚被你哈希的交易的组成部分,然后你就能约束它们了。这就是限制条款,你所需的只是 CAT。
JR:不知道这样说能不能让你们好受一点 —— 我只听懂了一半。你得读这篇博客才能真的理解。我会这样讲解 CAT:它有点像一个骨瘦如柴、只有 80 磅的孩子,因为每次都能投中三分球而被首选为篮球运动员。你肯定会问:“你这运动天赋是怎么来的” CAT 也可以让比特币神奇地变成量子安全的,因为你可以加入 Lamport 签名。你知道这个吗?
AP:我忘了这回事。
JR:有很多这样没有关联的事。如果我们有 CAT,我们也可以得到一个 CHECKSIGFROMSATCK 的变种。很多很多。我认为这也是 Andrew 签名的观点的一个很好的例子,也许我们会在未来意外地得到递归型限制条款,因为 CAT 这样不起眼的东西也能给比特币带来可怕的复杂性。力量都来自最让人意外的地方。
Q:你怎么使用 CAT 来分割数据呢?
JR:你把它转变成一种验证操作码,验证你传入见证数据的两个东西正是某个东西的碎片。
Q:可以处理签名数据这么大的东西?
JR:是的,在理论上是可以做到的。而且有一些代码技巧可以使之缩小。
Q:为什么当初中本聪要禁用 CAT?
AP:最初的 CAT 实现有一个内存膨胀问题,它并没有约束输出的大小。你可以把 0 字节或者别的什么东西推进去,然后使用 OP_DUP 来复制它,再使用 CAT 把它们拼再一起。只要重复使用 DUP 和 CAT,很快数据量就能长到天上去。这就是它被禁用的原因。如果当时只破坏了 OP_CAT,而且只有这个问题的话,我想我们可能已经正确地修复了它。只需要一次软分叉,表示从此开始不允许 CAT 太大的数据,就没问题了。但是,这是一个更大的、审计所有操作码的项目的一部分。有许多的操作码都被禁用了,而且各自都有一些表明需要重新评估的小问题。中本聪当时决定单方面全部禁用它们。我认为那时候还没有人真正思考过软分叉和硬分叉的区别,以及加入东西和移除东西在难度上的不对称。当时使用的语言是我们可以现在禁用它们,未来再重新启用。但实际上它们直接就被移除了。代码库里只剩下了名字。这就是 CAT 被禁用的原因,它会导致内存膨胀。
问答
Q:你能直观地告诉我计算这些程序的代码运行在哪儿吗?
JR:这取决于你使用的限制条款提议。对于通用的限制条款,代码基本上都运行在脚本内部。然后这个脚本路径的创造者也要遵循一些逻辑,比如如何创建满足脚本的数据、如何生成满足这些要求的见证数据和交易。这些代码可能比放在脚本中的验证代码更为复杂。尤其是 CHECKTEMPLATEVERIFY,它是一个极端例子,脚本里面只有交易的哈希值。所有的逻辑 —— 怎么处理交易、怎么广播交易,以及其它你可能会做的事 —— 都要作为一个单独的东西,由你自己维护。
Q:非常简单,在你可以看到一个多签名脚本的地方,常常会有一种等价的脚本,它有各种更高级的约束。
Q:但是,如何触发一笔新交易呢?
JR:推动它前进的所有东西都必须是用户创建的。没有自我执行这回事。比特币中没有代码表示 “根据共识,这笔交易必须在某个时间触发另一笔交易”。
Q:我猜递归型限制条款会更加复杂。问题是,相同的脚本,跟什么一起转移呢?
AP:没错,确实如此,必须由用户来采取行动、移动资金,但基本上,他们可以做的唯一操作就是移动资金到原始脚本的另一份拷贝中。所以限制条款会限制的你的行动,但必须有人才能发起积极操作。
Q:可以有多种脚本能满足同一个限制条款吗?
AP:简单的回答是,有。限制条款不必非得限制得很死。你可以表示一笔资金可以移动到任意数量的具体脚本中。你可以表示资金可以转移到任意脚本中,只要那个脚本以某些具体的条件开头,后面是什么条件则全无限制。都取决于具体的限制条款构造,但理论上,你在这里有完全的计算自由。
Q:在应用层,我知道关于 “coin pool” 这种概念的研究。假设我有一笔资金,我可以把它扔进一个跟其他 100 个人形成的免信任托管合约种。 随着人们花费资金,这个 UTXO 会逐块逐块地变化。也许有人会运行潜水艇互换服务器,在闪电网络上把钱接回来。有点像 Green Wallet 正在使用的架构,不带限制条款。缺点之一是其交易的图谱可能会剧烈膨胀?我想听听你对这方面的进展的看法。
JR:当然,coin pool 是可以实现的。最近出现了一些别的限制条款操作码。一种叫做 “TLUV(TapleafUpdateVerify)”。我认为 coin pool 是一个非常棒的机会。一定程度上它需要一个声誉层,我认为闪电网络也有相同的忧虑。你肯定不想进入一个人们的手机经常离线的 coin pool 中。你需要确信无论什么时候你要求一笔交易,而且你的交易满足更高阶的业务逻辑 “这个人正在请求发送资金,这是 TA 的余额的一部分”,你都可以默认签名交易。你希望得到参与者的活性保证。但这样会让交易图保持较小规模,因为人们需要共享 UTXO。不同的提议有不同的取舍。我认为,人们应该忽略掉交易的数量,只从字节的数量来考虑,因为有时候人们会认为 “这样做只需使用更少的交易”,我认为这就是人们的 TLUV 的感觉,类似于 “这种提议只有 N 笔交易需要存储,而 CTV 将需要 1.2N 笔,所以 CTV 更糟一些”。但如果你计算字节数,CTV 的体积小 6 倍。所以,对于某些事情,如果你统计交易的数量 vs 字节数,小体积交易 vs 大体积但数量更少的交易,你会感到惊讶。
Q:活性需求需要 coin pool 的每个人都在线吗?还是说可以实现一种阈值,只需一些人在线?
JR:看你想要什么样的保证。想象你有一个 coin pool,每个人在其中都有一个死党,然后你说,“我相信我的死党和其他人会保持诚实。”然后你就可以做一个一半人参与的门限机制【注:例如, AND(OR(Key(A),Key(B)), OR(Key(C),Key(D)), OR(Key(E), Key(F))
,任意 N/2 的组合】。这样你可以做一些更复杂的事情了,但他们也就能偷盗你的资金了。在 CTV 的世界里,我思考它的方式是,只要你遇到了一个故障,你就希望这个池子分成两半。所以,当你的池子遇到了一个活性故障,你就分割它;然后,如果又有一个人离线,那么一半的池子会恢复在线,但另一半就必须再次分割。你可以自己算一下,使用两笔交易,你就让 75% 的池子恢复了在线。发起另一笔交易你就可以让人们很快回到线上。唯一一个不走运的人是那个在交易树上跟离线者靠得特别近的人,TA 必须发起 O(log(N)) 笔交易才能退出,但这也不坏。因为 O(log(N)) 非常小。
Q:交代一下背景,我问这个问题的原因是,现在我们都看到了,闪电网络已经大大扩展了,许多用户共用一个节点,但许多用户在链上都没有公钥。多名用户共用一条通道,这并不理想,而且没有人会感到高兴。但 Green Wallet 做的一个有趣的事情是,他们让多个用户都在链上拥有公钥,但依然使用同一个节点,这个节点只做从链上到链下的潜水艇互换。拓展这个模型、使之能容纳许多用户,而且不要求【语音不可分辨】在池子内部实现分布式,是非常有意思的概念。
JR:在闪电网络语境下考虑这个问题时,我想补充的另一件事情是,这是 CTV 对 TLUV 的一个优势:无需加入 ANYPREVOUT,一个基于 CTV 的 coin pool 就可以拥有嵌套的通道。所有这些东西,只有在你想要重新平衡流动性,或者发起对外支付时,才需要做一次更新。不然的会,你可以在你的交易树的叶子节点中实现所有东西,就像闪电支付一样。我认为,这是一个非常强大的元件组合,你可以跟许多人创建通道,而且只需要偶尔更新。
SA:最后一个问题。
Q:这是给 Andrew 的问题。你觉得 CTV 的主要缺点是它让我们必须审核过于狭窄的东西,还是说你真的看到了一些有问题的故障模式?
AP:不是这个原因,主要就是因为在我看来它实在太过有限了。有一种稍微接近的观点是,如果我们在未来发现了一种很酷的应用,需要更通用的限制条款来实现,但因为我们 90% 的功能都已经在 CTV 里实现了,那么就会更难通过(通用的限制条款),因为这需要大家往回走并再来一遍。同样地,也许 CTV 会跟未来某一种如无 CTV 就会得到推行的提议不兼容。或者,可能存在一些效率因素或者某种交互,是复杂得难以分析的。实际上,如果我们要给比特币增加新东西,我会倾向于得到尽可能通用的东西。我猜我们的分歧在于什么东西才是我们可以得到的最通用的东西?我认为 Jeremy 已经表示了怀疑:即使我提出了正确的东西,也无法让我的提议全部加进比特币。
JR:仅仅从社区的观点看,有一些人,比如 Shinobi 就公开表示,他不会接受任何比 CTV 更复杂的东西。我不同意这种立场,也不认为这是好的,但他有他的理由,而且社区是一位严厉的女主人。我不知道我们能否让每个人都参与进来,但我想尝试一下,甚至连 CTV,都很难让人们 “受到触动”。有一个 utxos.org/signals 网站,是那些充分审核过 CTV 并认为他们可以推动社区走向合并、释放软件然后激活的人留名的地方。看起来不错,但也不是一蹴而就的事,需要持续多年的努力,让每个人都感觉它已经得到充分审核了,而且它已经获得了足够多的应用场景。
SA:好的,我们到这里就结束了。感谢各位!