作者:Peter Todd
“V3 交易” 是一组提议中的交易池策略(mempool policies)集,旨在允许合约式协议(例如闪电通道)中的交易使用 “子为父偿(CPFP)”、锚点输出和交易包转发作为支付手续费的首要方法。在本文中,我们会了解对 V3 交易以及锚点输出的期待是什么样的,以及它们在闪电通道这样的协议中的作用。最终我们会看到,它们对 CPFP 的依赖,使得它们在区块空间(以及手续费)上昂贵得多,相比于 “手续费替换(RBF)” 技术,在绝大多数情况下都将强制关闭通道的成本提高了大约 2 倍。我们也会解释何以 V3 交易形式会成为对挖矿中心化的一个潜在威胁,因为协议外(out-of-band)的手续费支付手段可能会比锚点交易便宜很多。
感谢 Fulgur Ventures 资助这项研究。他们对本文的内容无任何编辑权限,也未在出版之前审核。
背景
闪电通道以及类似的协议依赖于交换预先签名的交易来工作,除非出现了意外,这些交易本无意于被挖出。意外,就是指参与者之间不能达成一致意见的时候。在这些意外情形中 —— 比如,通道的某一方下线了 —— 资金会通过签名并广播必要的交易来复原。举个例子,在典型的、没有待处理的 HTLC 的情况下,一条通道的两方都拥有一笔对应着这个通道最新状态的承诺交易,它花费持有通道资金的 2-of-2 多签名输出,将资金发回给各方控制的公钥。
问题在于,为得到区块确认而需支付的手续费率是经常变化的,而且会大幅变化。其次,Bitcoin Core 当前要求交易的手续费率必须在某个门槛之上,才能被本地节点的交易池接纳。这个手续费率门槛也会随着网络的确认需求而变化,因为节点的交易池的容量上限是固定的。
虽然闪电协议有一种让参与者能供协商预签名交易的手续费率的机制,但这种机制也无法预测未来。因此,经常出现这样的情况:预签名交易的手续费要么太低,因此无法被及时确认;要么太高,因此浪费了一些资金。
在特定情况下,网络确认需求的意外增加甚至会让通道状态的解决变得不可能,因为进入交易池的手续费门槛已经高于预签名交易的手续费率。这在还有 HTLC 待处理的时候是尤其有害的,因为 HTLC 可能具备严格的时间窗口,如果不能在窗口期结束之前处理,应该得到资金的人就可能遭受损失。
锚点通道
闪电通道已经实现了一部分的解决方案,就是 “锚点通道”。锚点通道使用了两项关键的技术:
- 子为父偿(CPFP):如果一笔子交易具备(比其未确认的父交易)更高的手续费率,那么子交易实际上也为父交易支付了手续费 —— 让父交易更值得被挖出。
- 锚点输出:添加到交易的面额为零或接近零的输出,唯一目的是被 CPFP 手续费追加交易花费。
在锚点通道中,承诺交易加入了两个面额为粉尘门槛的锚点输入,目的是允许 本地 以及/或者 远端 的参与者使用 CPFP 来提高承诺交易的实质手续费,在双方所选的手续费太低、无法在合理时间内被挖出时,能够提高交易的确认优先级。
两个锚点输出的脚本都是这样的形式:
<local_funding_pubkey/remote_funding_pubkey> OP_CHECKSIG OP_IFDUP
OP_NOTIF
OP_16 OP_CHECKSEQUENCEVERIFY
OP_ENDIF
你可能会好奇,为什么这样本质上没有价值的输出,要使用 OP_CHECKSIG
(检查签名) 操作码。为什么不使用一种裸露的、最小化的 OP_TRUE
输出? 不管怎么说,如果别人花费这个输出,不也意味着是在支付手续费、加速我们的承诺交易被挖出吗?
因为,不幸的是,我们必须处理 “交易钉死攻击”:它会让 RBF 手续费追加法变得极为昂贵,甚至完全不可行。基本上,如果这个输出只是一个 OP_TRUE
,那么一个攻击者可以广播一笔交易、花费这个 OP_TRUE
锚点输出,使得我们使用 RBF 来追加手续费变得昂贵,甚至完全不可行,因此这笔承诺交易也难以在合理时间内得到确认。
因此, OP_CHECK
保证了只有实际参与这条通道的各方才能花费锚点输出,从而阻止任何钉死攻击。这同样是设置两个锚点输出的原因:我们不希望一方能对另一方发动钉死攻击。
实际上,两个锚点输出是冗余的: to_remote
输出就可以作为 to_remote_anchor
输出,这是我在撰写本文时发现的。只需一个锚点输出就够了。1
交易包转发
当前,Bitcoin Core 会孤立地判定是否要将一笔交易纳入本地交易池 —— 不考虑该交易的任何子交易。因此,即使有了锚点通道,承诺交易依然有可能因为网络手续费率上涨得太多而无法进入任何一个交易池(因为交易池的容量都是有限制的)。这是用,锚点输出就无法使用 CPFP 机制了,因为父交易根本就无法进入交易池,因此无从开始。
“交易包转发” 机制旨在通过评估交易 包(将相互关联的多笔交易作为一个整体)来解决这个问题。有了交易包转发,即使零费率的父交易也可以进入交易池,只要这个交易包包含了(通过 CPFP)为父交易支付手续费的子交易。
如果交易包转发能够实现,锚点通道就能在所有环境下使用了。相比于 RBF,它们依然不够高效 —— 我们下文会讨论到 —— 但至少是能够工作、不必顾虑交易池的条件的。
V3 交易
“V3 交易” 提议结合了交易包转发、一次性锚点以及新的交易池接纳规则,以尝试优化锚点通道和锚点输出。值得注意的是,“V3 交易” 中的 “V3” 指的是一种新的、非共识层面的 nVersion
数值 3, 矿工和节点被期望会 利他主义 地区别对待它,从而防止 V3 交易的输出被某一些方式花费掉。
因此,V3 交易提议有三个部分:
- 交易包转发:非常简单,V3 交易假设交易包转发已经实现。虽然需要指出的是,交易包转发是一种独立的特性,而且就其自身而言跟 V3 交易没有关系。
- “一次性锚点”:(可能是)零价值并使用一个简单的
OP_TRUE
或等价脚本的锚点输出,同时,交易池规则允许一个交易包在既创建又花费掉面额低于粉尘门槛的输出时,能够无视粉尘规则。重要的是,这不会让小于粉尘面额的输出进入 UTXO 集,因为它会在同一个区块中创建然后花掉。 - 对 V3 交易输出的花费方式的非共识限制,旨在防止钉死攻击。如我们上述讨论的,
OP_TRUE
锚点是无法抵抗钉死攻击的;而 V3 花费限制就是为了防止这些攻击。
我使用 “非共识限制” 的意思是,实际上的区块有效性规则并没有被改变。相反,仅仅是 Bitcoin Core 将拒绝尝试花费 V3 交易输出的交易 —— 即使它们是有效的,并且对矿工来说有利可图 —— 只要这些交易没有满足特定的要求,其中主要的一条是:花费未确认的 V3 输出的交易的重量必须小于 1000vB,否则就会被拒绝。这种限制阻止了重大的交易钉死攻击,反过来使得锚点输出可以是简单的、任何人都可以花费的 OP_TRUE
。因为花费这个输出的交易无法被钉死,如果第三方尝试以低费率交易花费这个输出,任何人都可以直接用更高费率的交易替换掉第三方的交易。
至少,想法上是这样的。不幸的是,因为 1000vB 的限制依然显著大于标准的承诺交易以及一次性锚点的花费交易,攻击者依然可以对第三方进行便宜而有效的钉死攻击。具体有多便宜、有多高效,取决于交易池内的环境。但一个攻击攻击者只需花费少许资金(甚至不需要花费资金)就可以让受害者为一次强制关闭通道的操作支付大约 1.5 倍的手续费,最高甚至可达约 3.7 倍。
手续费替换
对相同问题的一个显然的解决方案是,直接预签名不同费率的承诺交易。虽然乍看起来这是低效的,但计算机很快,带宽也便宜:secp256k1 曲线上的一次(MuSig2)签名操作的计算时间仅在 $100us$ (100 微秒)级别(一个公钥)2 3,然后生成一个 64 字节的签名。因为交易的变体可以确定性地生成,因此只需要传递用于确定性生成的参数,以及签名本身。并不需要传输完整的交易。而且闪电网络已经在使用这种技术了:交易的确切形式是由闪电网络规范确定的。
那么需要多少个挡位的手续费呢?根据 mempool.space 的数据,在过去的六年中,进入下一个区块所需的手续费率从 $1 sat/vB$ 到 $500 sat/vB$ 不等。
如果我们的手续费范围是从 $a$ 到 $b$,而递进的比例是 $r$,我们很容易算出需要几次递进:
$$b = ar^n \implies N = \frac{ln(\frac{b}{a})}{ln(r)} + 1$$
举个例子,如果你希望覆盖 $10 sat/vB$ 到 $1000 sat/vB$ 的费率,递进的比例是 10 %,那么只需要 50 个挡位,(需要传输的的)总体积是 3200 字节,只需要 $5 ms$(5 毫秒)来签名(单核计算机)。给定闪电通道的非常好的可扩展性,这个开销是合理的。
更新:事实证明,AICNQ 的 Phoenix 钱包最近已经为通道实现了部分的 RBF,在底层的 lightning-kmq 库中。
手续费分配
现有的闪电通道实际上需要 两笔 承诺交易,remote(“对方”)承诺交易和 local(“己方”)承诺交易,因为双方手上的承诺交易有些许区别;上述手续费挡位的计算指的是每一方需要准备的数量,不是总数量。每一笔承诺交易都要由对方签名,这就引入了为不同参与者分配手续费的能力。在绝大部分情况下,让广播承诺交易的一方支付手续费是合理的;因此,每一方都签名交易、给予对方用 自己 的钱来充当手续费的能力,在他们愿意的时候,就可以补上自己的签名 4 并广播交易。
只要手续费是由选择广播承诺交易的一方来支付的,最高手续费率就可以是该方在通道中的全部余额;这样一方也将不能通过广播高手续费的承诺交易来骚扰另一方,因为他们只能广播用 自己 的钱来支付手续费的承诺交易。这种机制有个好处:不会陷入某一方没有足够的手续费的情形中。如果让一笔承诺交易被挖出需要支付己方在该通道中的全部余额,那么己方尝试让这笔交易挖出是没有意义的。可以等待手续费率下降,或者直接放弃通道中的资金。
HTLC 与手续费替换
闪电承诺交易的 HTLC 输出只能被预签名的 HTLC-超时 或者 HTLC-成功 交易花费。之所以需要这些专门的交易,是因为一方可能广播已经撤销的承诺交易然后创建出 HTLC 输出,而我们需要让另一方有机会使用撤销公钥。在锚点通道中,这些 HTLC 是使用 SIGHASH_SINGLE|SIGHASH_ANYONECANPAY
签名的,所以任何人都能够通过添加额外的输入和输出来为这些交易追加手续费。
因为可以加入额外的输入和输出, 所以转发费率门槛 不是 一个问题:我们总是可以添加足够大的 输入/输出 来够上这个门槛。因此使用 RBF 承诺交易的一种办法是直接为每一种手续费挡位预签名(一笔) HTLC 超时/成功 交易,然后继续使用这种 “添加一个输出” 的机制。这会让我们需要为每一个 HTLC 做的工作乘以 $N$,但这里也一样,因为这些交易是确定性地制作出来的,所以很便宜,也很容易验证。
为同一个 HTLC 超时/成功 操作签名多个手续费挡位的交易也是可以做到的。虽然在最糟糕的情况下,总工作量会变成 $N^2$ 级别。但只要我们意识到,通常来说,HTLC 交易通常需要接近于对应那笔承诺交易的手续费,就可以节省下来。注意,这也说明了为什么对于 Phoenix 这样的终端用户钱包来说,手续费挡位是更好的选项 —— 终端用户钱包希望每个用户只有一个 UTXO,所以不会有额外的 UTXO 能够用来为 HTLC 输出追加手续费。
最后,SIGHASH_ANYPREVOUT
可以解决这个 $N^2$ 复杂度问题,因为它可以让任意的 HTLC 交易变体都能花费任何承诺交易的变体。
混合 RBF + 锚点
混合两者的方法也是可行的。让多笔变体交易只负责一部分的手续费范围,然后让一笔携带了锚点输出的变体负责最高的手续费。
效率
RBF 要比锚点输出高效得多,因为它只需要一笔交易。一笔标准的、使用 V3 形式的闪电通道承诺交易是 163vB 5,然后需要一笔体积为 152vB 的一次性输出交易来支付手续费,总计 315vB。
因为使用 RBF 的承诺交易不需要锚点输出,所以体积只有 154vB,而且不需要第二笔交易。V3 交易在体积上 至少 是两倍,因此手续费也将会是两倍。
但 V3 其实比这还要差:V3 交易需要保留额外的、纯粹只是为追加手续费而需保留的输出。管理这些额外的输出对许多闪电节点来说都是困难而且昂贵的,尤其是更小的 节点/钱包。很难确定要为这些输出分配多大比例的资金,而且,显然,需要消耗区块空间来创建它们。
绝大多数对比特币的究极扩展容量的分析都忽略了这一点:使用锚点输出,每一个闪电网络的用户都至少需要创建 两个 UTXO,而不是一个输出,从而将闪电网络可能容纳的全球用户总量砍半。
这个问题的一个特别糟糕的例子可以在面向用户的钱包(比如 Phoenix 7)上看到:Phoenix 希望让一个用户的所有资金都放在一条闪电通道中,而使用通道拼接技术来加入和取出链上资金。
非原生资产的协议
像 RGB 闪电通道和 LND 的 Taproot Assets 这样的协议旨在让非比特币的协议也能利用闪电通道形式的通道,但可能被迫绑定更多的 BTC 以使用 RBF 技术,因为双方尝试交易的资产并不能直接用来支付手续费。对于大体量的通道来说,这可能不是一个问题,因为手续费的 BTC 价值相对于通道的价值来说是小的。但更小的通道可能需要使用其它技术。
举个例子,SIGHASH_ANYPREVOUT
让承诺交易可以用 SIGHASH_ANYONECANPAY
来签名,从而可以在有需要的时候通过添加额外的输入来使用 RBF 手续费追加。当然,这可能会引入一些问题,比如说交易钉死攻击。
感谢 Brandon Black 指出这个问题。
锚点输出可能导致挖矿中心化
因为锚点输出比 RBF 要低效得多 —— 代价大约是 2 倍 —— 它带来了显著的挖矿中心化威胁,因为用户可能尝试使用协议外(out-of-band)的手续费支付手段。问题在于,如果你拥有使用锚点输出的交易,你需要确认它,使用这个机制(锚点输出)来挖出交易,比起让矿工挖出目标交易而不花费锚点输出,要消耗大概是两倍的区块空间。
一些大矿工已经接受了协议外的支付手段,允许你通过直接支付给他们来让你的交易被挖出;仅有 大体量的矿工可以合理地提供这项服务,因为只有大矿工才能足够频繁地找出区块,来让这种协议外的手续费物有所值。去中心化的矿池协议比如 P2Pool 和 Braidpool 完全没有任何希望能够提供这样的服务。
因为使用锚点输出的标准闪电通道需要花费 2 倍的区块空间,一个大矿工可以容易地以一定的折扣(比如 25%)提供协议外的手续费支付,这也实质上给了他们 25% 的溢价(相比于他们的更小的竞争对手)。给定矿池的手续费是高度竞争的,量级在 1% 到 2%,如果能够赚取 25% 的溢价,那会是一种巨大的竞争优势。有了闪电网络,可以自动化、便利地实现这样的服务。
我们不能开发让去中心化挖矿反而处于不利地位的协议。单凭这一点,就是 V3 交易和临时锚点输出不应被实现的好理由。
协议设计
作为一个社区,我们应该追问,这样一个低效的提议,伴随去挖矿去中心化的巨大潜在伤害,为何走了如此之远。V3 交易的 PR 已经以各种形式存在了超过一年的时间,尚未有一份 BIP 或者任何真正的设计文档。尤其是,作者最近承认 8 他们对标准的闪电承诺交易的体积的认识是显著错误的,由此产生了前面提到的 V3 交易钉死问题。
在考虑这样复杂的协议决策时,我们不应该急着实现代码。我们应该定位真实的应用场景并解决这些场景的效率和隐藏后果。到目前为之,通过 PR 而公开的关于 V3 交易的文档没有提供这一类的分析;就我所知,我是第一个合理地比较了它于其它解决方案的人。
建议
- 交易包转发也许应该添加到 Bitcoin Core 中。CPFP 在缔约协议之外的地方是有用的,比如说,加快给你自己发送的支付。这些场景中的替代方法要么是让发送者追加手续费 —— 并不总是现实的 —— 要么是使用协议外的手续费支付。其次,闪电网络已经推出了锚点通道,这可以立即受益于 CPFP。
- 锚点输出现有的用法应该出局,因为上面提到的挖矿中心化风险;新的锚点输出支持不应该添加到新的协议以及 Bitcoin Core 中。
- 在当前,V3 交易不应该推出。现在,除了一次性输出,它还没有令人信服的应用场景,这不应该鼓励,而且它当前的形式依然难以抵抗交易钉死攻击。
脚注
1. [Lightning-dev] 锚点通道的对方锚点是多余的, Peter Todd, lightning-dev 邮件组, 2023-12-13 ↩
2. https://github.com/jonasnick/musig-benchmark ↩
3. https://github.com/tarcieri/rust-secp256k1-ecdsa-bench ↩
4. 记住,在交互式联合签名协议(比如 MuSig2),“联合签名” 仅仅意味着补完签名流程的最后一步。 ↩
5. 假设 1 个 taproot 输出(musig),2 个 taproot 输出,以及 1 个一次性锚点输出 ↩
6. 假设 1 个一次性锚点、一个 taproot 输入,以及 1 个 taproot 输出 ↩