如何在比特币上构建中继服务?
我们专注于建立一个非托管的中继器—Infura交易服务(ITX),用于接收一个预签名消息(例如元交易),并将其打包到以太坊交易中,然后逐渐提高手续费,直到它被记账。
为什么我们要关注中继器?
我们希望企业能够以服务的形式提供可靠的交易交付。中继器帮助缓解了全球区块空间手续费市场竞争的根本问题,他们擅长根据交易的优先级选择中继策略。例如,如果用户在自动做市商模式上交换代币,中继器可能会优化为快速交付,而如果用户在分布式自治组织中投票,则可能优化为较低的交易手续费。
这篇文章的重点是:我们能在比特币中构建中继服务吗?会出现什么样的问题呢?
中继器应该具备哪些属性?
在构建ITX时,我们主要关注以下四个属性:
没有托管权。ITX从来没有访问或控制用户的主要资金。
非交互式。ITX在没有用户持续互动的情况下,参与了全球区块空间收费市场。
无需初始设置。用户不需要初始设置阶段就可以使用中继服务。
中继器支付。支付网络费用的是中继器,而不是用户。
其中三个性质很简单。中继器永远不能访问用户的主要资金,当中继器发送它们的交易时,用户应该能够脱机,如果没有使用中继器的初始设置也是理想情况。
最后一个性质,即中继器支付,主要是出现在以太坊上的一种情况,因为中继器必须先支付手续费,以便发送用户的交易。
这就引出了下一个问题:
是否应该使用中继器或客户的链上资金来支付这笔交易?
这对以太坊来说很容易,但对比特币来说就不那么容易了。中继器支付网络费用的普遍说法是为用户解决协议(或托管)问题。关键的例子是支持“减少gas”的ERC20转账,因为用户可能没有足够的ETH来支付网络费用,但他们可以通过ERC20代币退款给中继器。然而,同样的用例也存在于比特币上的Tether,尽管还没有为它进化出一个生态系统。我们猜测只要能从服务中得到收益,中继器就会为用户支付网络费用。
比如比特币保险库。用户支付管理员来监控他们的保险库,如果检测到恶意行为,就会发出取消交易的命令。用户向管理员支付订阅费,作为回报,管理员承诺支付任何必要的费用来保护保险库。此外,为了简化过程,使用中继器的资金来支付网络费用可能是可行的(例如,以一种特别的方式附加多个费用输出,从而从根本上提高费用)。
下面,让我们花一些时间来回顾一些关于比特币交易的背景信息。
比特币交易(UTXO)背景
图1:一次比特币交易就有一个输入和输出列表
比特币交易有一个输入列表和一个输出列表:
输出(UTXO)与一些代币和声明脚本相关联,该脚本建立了一组必须满足的条件才能使用这些代币。
一个输入引用了一个未使用的输出,并提供了赎回脚本,这是满足使用代币条件的证明。
正如我们在图1中看到的,交易2的第一个输入引用交易1的第一个输出。交易2的其它两个输入没有显示在图片中,因为它们来自不同的交易。当交易2由网络处理时;它有效地执行每个输入输出对,以验证该交易是否可以使用0.5 BTC、0.8 BTC和0.3 BTC。
比特币交易有三个方面对费用替代协议很重要:
脚本的灵活性。交易的输入和输出创建一个脚本。大多数脚本需要单个签名来授权交易,但是它可以使用其它条件,包括multisig,在时间T之后有效,并且如果哈希的原像被泄露。
输入赎回。一个交易的每个输入都可以由不同的一方进行兑换,并且输入只有在各方都同意交易模板的情况下才有效(例如,如果一个交易有3个输入,那么在挖掘交易之前必须满足每个输入的脚本)。
SIGHASH规则。授权交易的用户签名可以覆盖一个或多个输入,以及一个或多个输出。
交易费是多少?它的定义是什么?交易费用没有显式的UTXO或字段。它的定义是:
tx_fee = total_inputs - total_outputs,其中tx_fee≥0。
例如,如果输入总共代表1个比特币,输出发送0.9个比特币,那么交易的费用为0.1个比特币。要调整网络费用——签名者必须重新定义交易输出,以扣除发送的代币总数,然后重新签署交易。
最后,比特币没有账户系统。相反,有一个全局UTXO集来定义可使用的交易输出列表。每个UTXO都与一个索赔脚本和一些比特币相关联。每个区块从集合中删除已使用的交易输出,然后将新的可使用输出追加到集合中。
UTXO模型的另一个特点是代币管理。如果用户向交易对手发送5个比特币到相同的地址,但在不同的交易中,那么交易对手将需要管理5个未花费的交易输出。这对Coinbase来说是个问题,因为他们在150万UTXO中拥有265个比特币。
中继器费用输出
图2:中继器在交易中包含了一个额外的输出来支付费用
为了满足中继器支付属性,中继器必须在客户的交易中包含一个UTXO。如图2所示,中继端输入0.3 BTC,中继端接收0.1 BTC,因此他们已经为交易网络费用分配了0.2 BTC。为简单起见,我们将其称为转发器费用输出。
然而,有一些微妙的陷阱需要考虑:
UTXO管理。转发器必须确保每个费用输出有足够的资金来支付交易的最大成本。如果一组费用输出变得不足,它们可能需要定期进行批处理;如果费用输出包含太多代币,则需要分割费用输出。所有的管理都需要链上交易,因此会给中继者带来经济成本。
并发中继交易。如果我们假设用户的每个待处理交易只使用一个费用输出,那么中继层在任何时间可以管理的并发交易的数量取决于可用的费用输出的数量。例如,如果中继器有10个UTXO,每个UTXO有1个BTC,那么他们可以支持在任何时间发送10个中继交易。与基于账户的系统不同,它不一定依赖于接力者持有的资金总量。因此,中继器的UTXO管理方式直接影响其服务质量。
把待处理的交易链起来是不安全的。理想情况下,相同的费用输出可以用于多个待处理交易,这些交易被链接在一起,以最大化其效用。然而,为中继服务将依赖的交易链接在一起并不总是安全的。这是因为一个交易的输入依赖于前一个交易的散列。因此,更改单个交易的费用将使所有相关(和待处理)交易无效。
当然,如果客户正在支付网络费用,那么中继器就不包括交易的输入。根据协议,客户可以使用自己的资金创建额外的转发器费用输出。这样,客户就可以为中继者分配资金,并增加交易费用。挖掘交易时剩余的任何资金都可以作为奖赏给中继者。
比特币交易中的“本机按费用替换”标志?
BIP125为交易实现了一个可选的费用替换标志,这样,当交易在内存池中仍未确认时,费用可以在任何时候增加。
协议是相当直接的。
可选:中继器为用户提供一个输入,可以用来支付网络费用。
用户签了几笔交易,每笔交易的费用都比之前的交易高。
用户向中继器发送所有交易。
中继器广播第一个(最低费用)交易,然后稳步发布后续交易,逐步提高费用。
我怀疑如果出现了作为服务的中继器,那么对于大多数情况来说,这是第一个也是最容易实现的方法。
它是非交互式的,因为所有的交易都是预先签名的,其次,中继器只被信任以及时的方式转发交易,帮助用户在任何给定的时间支付最好的价格。缺点是用户必须对多个交易进行签名并将它们发送到中继器。因此,中继者无法控制费用的力度,这对于避免费用过高或过低是很重要的。
如果一个交易带有RBF标记,建议你不要轻易相信它为0-confirmation。BIP标识如果第一个交易的RBF=on,而被替换的交易的RBF=off,那么该交易仍然是可替换的。因此,如果你只是验证你接收到的交易,以检查RBF=off,那么你仍然可以被愚弄!!
“子为父偿”交易
图3:中继器广播第二个交易,子交易支付父交易,它覆盖了两个交易的费用
图3突出显示了子元素为父元素付费。它允许第二笔交易提供一个网络费用,覆盖两笔未决交易的成本(例如,它自己和它的父交易)。假设矿商对待定交易链进行评估,那么它应该会诱使矿商将两笔交易包含在同一个区块中。
我们可以通过在第一个待处理的交易中包含一个转接费输出来利用“子为父偿”。如前所述,用于资助此输出的代币可能来自中继者或客户,但中继者将对费用输出有完全的托管权。如果第一个待处理的交易在网络上卡住了,那么中继器可以使用锚输出创建第二个交易,以覆盖两个交易的网络费用。
协议很简单:
可选:中继器为用户提供一个输入,可以用来支付网络费用。
客户创建、签署并向中继器发送交易。它包括额外的中继费输出。
中继器广播交易到网络,监控交易,并只发送第二个交易,在费用需要受挑战的情况。
如果没有费用增加,并且用户的交易被挖掘,那么中继者的费用输出可以用于下一个客户。
在初始的待处理交易发送后,协议是非交互式的,中继器从来没有访问用户的主要资金,中继器可以为用户支付网络费用,当然也没有初始设置。
缺点是交易更大(专用中继器输出),费用可能需要覆盖两个交易,而不是单个交易。
SIGHASH的神奇之处
正如我提到的吞吐量,我们必须签署交易授权。但是签名者实际上签的是什么呢?这给我们带来了微妙的SIGHASH规则。
有些情况下,用户只希望为一个交易的输入和输出的组合签名,而不关心为整个交易签名。用户可以声明一个SIGHASH规则,该规则规定用户将对交易中的哪些信息进行签名。
图4:“黄色”中的字段由用户使用SIGHASH_SINGLE签名
SIGHASH_SINGLE:用户对所有输入进行签名,但只对一个输出进行签名。
图4突出显示了用户签署的交易模板,其中包括他们想要的输出(例如,向商家转账代币)和两种输入。中继器的输出没有被用户签名,因此当用户的输入被验证时,它会被忽略。
不完整的交易模板被发送给负责包含第二个输出和授权交易的中继器。没有转发者的签名,交易无效;因此,不存在矿工挖掘交易并窃取中继者投入资金的风险。同样,对于每个输入,SIGHASH标志也可以是不同的。因此,当用户使用SIGHASH_SINGLE时,中继层可以简单地使用SIGHASH_ALL(默认选项)。
SIGHASH_SINGLE实现有一个bug(比特币确实是一个未完成的frankenstein系统),因此即使没有输入签名的相应输出(例如,输入的数量超过输出的数量),交易仍然有效。检查错误报告以找出原因。
因此,如果我们希望为用户支持更改输出,那么用户必须包含第二个也使用SIGHASH_SINGLE的虚拟输入。
除此之外,协议是直接明了的:
中继器必须为用户提供一个输入来包含在交易中,让中继器为RBF重新签名交易。
用户创建一个3输入2输出的交易模板。第一个输出将代币发送到目的地,第二个输出退还剩余的更改。第三个输入是中继器的锚UTXO。
用户使用声明的标志SIGHASH_SINGLE为每个输入提供签名(图5)。
用户将部分签名的交易发送给中继器。
中继器创建第三个输出,返回中继器并支付交易网络费用。它们的输入使用SIGHASH_ALL进行签名,并将交易广播到网络。
为了增加费用,中继者可以简单地调整第三个输出,重新签署交易,并广播它。
当然,中继器只能将费用提高到他们所提供的输入的最大值。
我们可以混合SIGHASH_SINGLE和ANYONECANPAY吗?是的。用户只对交易的一个输入和一个输出进行签名。但是我们警告不要使用它,因为如果输入的代币>输出的代币,然后矿工可以简单地忽略交易的其余部分,并只挖掘输入的代币>输出的代币的输入输出对,如果用户希望通过更改输出返回代币,这个漏洞可能会导致严重的盗窃。
总结
我们已经介绍了在比特币中支持第三方中继的三种方法,我们可以根据初始属性对它们进行简单比较:
没有托管权。在所有的解决方案中,中继器永远不能访问用户的资金。但是,如果用户为转发器的费用输出提供资金,则相信转发器会选择适当的费用。
非交互式。从用户的角度来看,所有的解决方案都是非交互式的,因为它们只需在脱机之前提供足够的信息,然后让中继器接管。不过,按费用替换方法确实需要用户进行大量设置,因为他们需要对交易列表进行预签名并将它们发送到中继器。
中继者支付。中继者可以在所有三种方法中包括额外的输入并支付网络费用。尽管,SIGHASH方法还不清楚客户是否可以支付网络费用。但似乎中继器必须包含交易的额外输入和输出对。
无需初始设置。所有的方法都不需要用户在与中继层交互之前进行初始设置,我们将用户定义为链上交易。
会出现什么问题?
最重要的问题是额外的交易。如果中继者支付了网络费用,那么客户的交易必须包括额外的输入。如果使用“子为父偿”的方法,那么客户可能会为第二笔交易付费(成本的2倍)。而且由于SIGHASH_SINGLE错误,用户可能需要包括一个虚拟输入和中继器的输入。
按费用替代方法与SIGHASH的对比。我认为sighash_single方法是对按费用替代方法的优化。它不要求用户对交易列表进行预签名,而是让中继器接受用户签署的单个交易,然后将交易费用附加到该交易列表上。研究一个名为SIGHASH_MULTIPLE的新sighash规则可能是明智的,该规则允许用户对一组输入/输出进行签名,并为中继器为已存在的交易附加网络费用提供更大的灵活。
我们可以混合使用这些方法吗?
所有的方法都可以包括一个中继器的费用输出,它被扣除来支付网络费用。专门的费用产出可能无法覆盖交易的全部成本。如果发生了这种情况,那么中继器可以简单地执行“子为父偿”交易,并在交易中包含两个输入。第一个输入通过中继器输出费用花费客户的交易,第二个输入是由中继器控制的另一个UTXO,以在交易中包含更多的资金。因此,如果网络费用突然飙升到意想不到的高度,中继者可以随时添加额外的UTXOs。