闪电交易:从零到英雄

  • t-bast
  • 发布于 2022-05-20 21:31
  • 阅读 16

本文深入探讨了比特币闪电网络中的交易机制,从比特币交易的基础格式、Sighash 标志、绝对和相对锁定时间讲起,详细解析了闪电网络中的各种交易类型,包括资金交易、承诺交易、HTLC交易和关闭交易,并讨论了锚定输出(Anchor Outputs)如何解决交易拥堵问题以及RBF pinning攻击,最后提出了一个替代方案以增强闪电网络的安全性。

闪电交易:从零到英雄

本文包含你需要了解的关于比特币/闪电交易的所有内容。 我们忽略了 segwit 的细微之处以及对我们的全局理解不必要的字段。

目录

比特币交易

交易格式

一个比特币交易包含以下字段:

{
  "version": 2,
  "locktime": 634346,
  "vin": [
    {
      "txid": "652cd2cc2b12ecc86e77ed067ae11815d6e6347e2ae40b8970a063880798787c",
      "vout": 2,
      "scriptSig": "<some satisfying data>",
      "sequence": 10
    }
  ],
  "vout": [
    {
      "value": 0.1893,
      "scriptPubKey": "OP_HASH160 69ef88bab45ccfaa49f8421fdb4ae39efb23ffb2 OP_EQUAL"
    }
  ]
}
  • locktime 可用于施加一个 绝对 的时间锁。
  • vin 是此交易花费的输入的列表:它们匹配已包含在区块中的先前交易中的未花费输出,以及满足花费条件的数据 (scriptPubKey)。
    • txid 引用包含我们要花费的输出的交易,vout 是该输出在交易中的索引。 它被计算为 SHA256(SHA256(raw_tx))
    • scriptSig 包含满足输入 scriptPubKey 的数据。
    • sequence 可用于施加一个 相对 的时间锁。
  • vout 是此交易创建的输出的列表(未花费的输出,直到有人通过将它们包含在交易的 vin 中来花费它们)。 scriptPubKey 是一个比特币脚本,必须由花费者来满足(通常是来自给定公钥的签名,对哈希原像的了解等等)。

Sighash 标志

比特币脚本的一个重要且非常有用的微妙之处是 sighash 标志,它可以与 OP_CHECKSIG 操作码(以及 OP_CHECKSIG 的变体)一起使用。

让我们假设我们有一个具有以下 scriptPubKey 的 UTXO:<alice_pubkey> OP_CHECKSIG,表明 Alice 可以花费该 UTXO。 Alice 可以创建一个新的交易,其中包括 vin 中的 UTXO。 在输入的scriptSig 中,她需要提供这个 交易的有效签名。 除了签名字节之外,她还附加一个字节,指定已签名交易的哪些部分(这就是 sighash 标志)。

这些 sighash 标志为复杂的、多参与者的交易提供了一些灵活性。 当交易的某些部分 签名时,这意味着其他参与者可以在保持其有效性的同时稍微更新交易,而无需签名者重新签署更改。

sighash 字节的可能值为:

  • SIGHASH_ALL(默认):整个交易都被签名,除了输入脚本(否则存在先有鸡还是先有蛋的问题)。 这意味着签名者不希望任何人在签名后修改交易。
  • SIGHASH_NONE:输出未签名。 这意味着签名者同意选定的输入(比特币的来源),但允许其他参与者自由更改输出(比特币的去向)。
  • SIGHASH_SINGLE:仅对具有与包含签名的输入相同索引的输出进行签名。 输入也存在一个小的微妙之处:它们的 sequence 字段未签名。 这意味着签名者同意选定的输入和他选择的单个输出,但允许其他参与者自由修改其他输出。
  • SIGHASH_ANYONECANPAY:仅对包含签名的输入进行签名,其他输入将被忽略,并且可以被其他参与者自由修改。

请注意,显然,SIGHASH_ALL、SIGHASH_NONE 和 SIGHASH_SINGLE 是互斥的。 但是 SIGHASH_ANYONECANPAY 可以与 SIGHASH_ALL、SIGHASH_NONE 或 SIGHASH_SINGLE 结合使用。

请注意,组合 SIGHASH_NONE | SIGHASH_ANYONECANPAY 是一种危险的自毁行为:你放弃了一个 UTXO,但对它将被发送到的地方没有任何控制。

有一些提案建议添加一个不签署任何输入的标志,仅依靠脚本兼容性来确保交易有效性。 目前围绕这个想法有两种变体:

绝对锁定时间

比特币使得限制交易生效的未来时间成为可能。 这样的时间锁可以用区块高度或时间戳表示,但我们在这里只考虑区块高度版本。

第一种可能性是在交易级别限制它,通过设置 locktime 字段。 然后,该交易将被视为无效,直到区块高度大于 locktime 值。 如果你想与某人分享签名交易,但不希望它立即包含在一个区块中,这可能会很有用。 请注意,此规则有一个例外:如果所有输入的 sequence 字段都设置为 0xffffffff,则禁用 locktime 检查(由于历史原因)。

另一种可能性是在脚本级别限制它,通过使用以下脚本: <block_height> OP_CHECKLOCKTIMEVERIFY。 这是一种更强大的机制:你可以创建一个可以被其他人赎回的 UTXO,同时阻止接收者在指定的 block_height 之前赎回它。

相对锁定时间

比特币还可以限制 UTXO 何时可以被花费,相对于 UTXO 创建的时间。 当你离线交换签名交易时,这尤其有用,但不知道这些交易何时会在链上广播(因此不知道在 locktime 字段中放入什么区块高度是有意义的)。

这是通过使用交易输入的 sequence 字段完成的。 当正确设置时(根据 BIP68 中定义的规则),它可以确保交易仅在它花费的输出包含在一个区块中之后的 n 个区块后才生效。 如果你想与某人分享签名交易,并且该交易花费来自一个未确认的交易,并且你想确保这两个交易确认之间存在特定的延迟,这可能会很有用。

另一种可能性是在脚本级别限制它,通过使用以下脚本: <n> OP_CHECKSEQUENCEVERIFY。 这是一种更强大的机制:你可以创建一个可以被其他人赎回的 UTXO,同时确保接收者只能在第一个交易确认至少 n 个区块后才能赎回它。

闪电交易

闪电交易利用了上述部分中解释的机制,因此请确保在继续阅读之前你已经理解了它们。

Bolt 3 也包含有关交易的所有详细信息,但它非常紧凑,对于非实现者来说可能难以阅读。 希望本文档有助于填补该规范中的空白。

概述

以下是用于 A 和 B 之间通道的所有交易的高级视图(请注意,在本例中我们是 B,当我们在以下部分中说“我们”时,我们指的是 B):

+------------+
| 资金交易 |
+------------+
      |
      |        +-------------+
      +------->| 承诺交易 B |
               +-------------+
                  |  |  |  |  
                  |  |  |  | A 的主要输出
                  |  |  |  +-----------------> 发送到 A
                  |  |  |
                  |  |  |                 +---> 在相对延迟后发送到 B
                  |  |  | B 的主要输出 |
                  |  |  +-----------------+
                  |  |                    |
                  |  |                    +---> 通过撤销密钥发送到 A
                  |  |
                  |  |                                              +---> 在相对延迟后发送到 B
                  |  |                        +-----------------+   |
                  |  |                   +--->| HTLC-超时 交易 |---+
                  |  | B 提供的 HTLC |    +-----------------+   |
                  |  +-------------------+      (超时之后)     +---> 通过撤销密钥发送到 A
                  |                      |
                  |                      +---> 通过支付原像发送到 A
                  |                      |
                  |                      +---> 通过撤销密钥发送到 A
                  |
                  |                                                     +---> 在相对延迟后发送到 B
                  |                            +-----------------+      |
                  |                    +------>| HTLC-成功 交易 |------+
                  | B 收到的 HTLC |       +-----------------+      |
                  +--------------------+     (通过支付原像)    +---> 通过撤销密钥发送到 A
                                       |
                                       +---> 超时后发送到 A(绝对延迟)
                                       |
                                       +---> 通过撤销密钥发送到 A

符号:

  • 交易包含在框中
  • 入箭头表示输入
  • 出箭头表示输出
  • 输出中的分支(带有 +)表示脚本中的分支条件 (OP_IF)

A 具有一组对称的交易,其中 A 和 B 已被交换(并且金额也进行了适当的交换)。 参与者始终能够生成 另一方 持有的交易(这对于向他们发送他们的签名是必要的)。

注意:在下面的示例交易中,为了清楚起见,我们直接在 scriptPubKey 中编写花费脚本(在实际交易中,它们是 P2WSH 脚本)。

资金交易

通道始终以资金交易开始。 这个非常简单。 它只需要创建一个具有以下 scriptPubKey 的输出:

2 <pubkey1> <pubkey2> 2 OP_CHECKMULTISIG

我们不关心使用的输入,也不关心是否创建了其他输出。 资金交易的结构如下所示:

{
  "版本": 2,
  "锁定时间": 0,
  "vin": [
    {
      "...": "..."
    },
    ...
  ],
  "vout": [
    {
      "value": 1.0,
      "scriptPubKey": "2 <pubkey1> <pubkey2> 2 OP_CHECKMULTISIG"
    },
    ...
  ]
}

承诺交易

承诺交易包含通道状态,该状态包括两个部分:每个参与者的余额和所有待处理的付款(HTLC)。

它只有一个输入,即资金交易,并使用 SIGHASH_ALL。 这确保了双方在交换签名时对承诺交易的确切细节达成一致。

它将 locktimesequence 设置为允许在没有延迟的情况下花费它的值(这些字段也用于以一种非常巧妙的方式隐藏一些数据,对于本文档我们不关心)。

当通道中的任何内容发生更改时(添加了新的付款,待处理的付款失败或已完成):

  • 创建一个新的承诺交易来替换先前的交易
  • 参与者交换该新承诺交易的签名
  • 参与者显示先前承诺交易的撤销密钥

容量为 1 BTC 的通道的承诺交易可能如下所示:

{
  "version": 2,
  "locktime": 543210000,
  "vin": [
    {
      "txid": "...",
      "vout": ...,
      "scriptSig": "0 <signature_for_pubkey1> <signature_for_pubkey2>",
      "sequence": 2500123456
    }
  ],
  "vout": [
    {
      "value": 0.5,
      "scriptPubKey": "
        # to_local 输出
        OP_IF
          # 资金会发送给任何拥有撤销密钥的人
          <revocationpubkey>
        OP_ELSE
          # 或在相对延迟后返回给我们 (<to_self_delay>)
          <to_self_delay>
          OP_CHECKSEQUENCEVERIFY
          OP_DROP
          <local_delayedpubkey>
        OP_ENDIF
        OP_CHECKSIG
      "
    },
    {
      "value": 0.3,
      "scriptPubKey": "
        # to_remote 输出:立即返回给另一个通道参与者
        P2WKH(<remote_pubkey>)
      "
    },
    {
      "value": 0.05,
      "scriptPubKey": "
        # 我们发送的待处理的 HTLC(付款)
        OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
        OP_IF
          # 资金会发送给任何拥有撤销密钥的人
          OP_CHECKSIG
        OP_ELSE
          <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
          OP_NOTIF
            # 资金通过二级 HTLC-超时交易返回给我们(其中包含绝对延迟)
            # 注意:我们还需要远程签名,这可以防止我们单方面更改 HTLC-超时交易
            OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
          OP_ELSE
            # 资金会发送给拥有付款原像的远程节点。
            OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
            OP_CHECKSIG
          OP_ENDIF
        OP_ENDIF
      "
    },
    {
      "value": 0.03,
      "scriptPubKey": "
        # 我们发送的另一个待处理的 HTLC(付款)
        OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
        OP_IF
          # 资金会发送给任何拥有撤销密钥的人
          OP_CHECKSIG
        OP_ELSE
          <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
          OP_NOTIF
            # 资金通过二级 HTLC-超时交易返回给我们(其中包含绝对延迟)
            # 注意:我们还需要远程签名,这可以防止我们单方面更改 HTLC-超时交易
            OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
          OP_ELSE
            # 资金会发送给拥有付款原像的远程节点。
            OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
            OP_CHECKSIG
          OP_ENDIF
        OP_ENDIF
      "
    },
    {
      "value": 0.08,
      "scriptPubKey": "
        # 发送到我们的待处理的 HTLC(付款)
        OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
        OP_IF
          # 资金会发送给任何拥有撤销密钥的人
          OP_CHECKSIG
        OP_ELSE
          <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
          OP_IF
            # 一旦我们拥有付款原像,资金将通过二级 HTLC-成功交易发送给我们
            # 注意:我们还需要远程签名,这可以防止我们单方面更改 HTLC-成功交易
            OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
            2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
          OP_ELSE
            # 资金将在绝对延迟(超时)后发送到远程节点
            OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
            OP_CHECKSIG
          OP_ENDIF
        OP_ENDIF
      "
    },
    {
      "value": 0.04,
      "scriptPubKey": "
        # 发送到我们的另一个待处理的 HTLC(付款)
        OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
        OP_IF
          # 资金会发送给任何拥有撤销密钥的人
          OP_CHECKSIG
        OP_ELSE
          <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
          OP_IF
            # 一旦我们拥有付款原像,资金将通过二级 HTLC-成功交易发送给我们
            # 注意:我们还需要远程签名,这可以防止我们单方面更改 HTLC-成功交易
            OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
            2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
          OP_ELSE
            # 资金将在绝对延迟(超时)后发送到远程节点
            OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
            OP_CHECKSIG
          OP_ENDIF
        OP_ENDIF
      "
    },
  ]
}

需要注意的几个要点:

  • 承诺交易中可以有许多传入和传出的待处理付款 (HTLC)
  • 知道撤销密钥允许参与者声明通道中的所有资金,因此 确保你没有发布过时的承诺交易非常重要
  • 如果另一方发布它的承诺交易总是更好:HTLC 可以 直接完成或超时,而无需二级交易
  • 这些激励措施确保你以协作方式关闭通道,而不是发布承诺 交易:你应该发布你的承诺交易的唯一情况是,如果对方 变得无响应或恶意

HTLC 交易

HTLC 交易是二级交易,它们花费你的承诺交易中的 HTLC 输出。 它们需要来自双方参与者的 SIGHASH_ALL 签名。 这个第二阶段 对于解耦 HTLC 超时(一个绝对的区块高度)与应用于本地资金的相对超时 (to_self_delay) 是必要的; 否则,我们将无法在 to_self_delay 期间完成或失败 HTLC。

一个 HTLC 成功的交易看起来像:

{
  "版本": 2,
  "锁定时间": 0,
  "vin": [
    {
      "txid": "...",
      "vout": 42,
      "scriptSig": "0 <remotehtlcsig> <localhtlcsig> <payment_preimage>",
      "sequence": 0
    }
  ],
  "vout": [
    {
      "value": 0.04,
      "scriptPubKey": "
        OP_IF
          # 资金会发送给任何拥有撤销密钥的人
          <revocationpubkey>
        OP_ELSE
          # 或在相对延迟后返回给我们 (<to_self_delay>)
          `to_self_delay`
          OP_CHECKSEQUENCEVERIFY
          OP_DROP
          <local_delayedpubkey>
        OP_ENDIF
        OP_CHECKSIG
      "
    }
  ]
}

一个 HTLC 超时的交易看起来像:

{
  "版本": 2,
  "锁定时间": <cltv_expiry>,
  "vin": [
    {
      "txid": "...",
      "vout": 42,
      "scriptSig": "0 <remotehtlcsig> <localhtlcsig> <>",
      "sequence": 0
    }
  ],
  "vout": [
    {
      "value": 0.04,
      "scriptPubKey": "
        OP_IF
          # 资金会发送给任何拥有撤销密钥的人
          <revocationpubkey>
        OP_ELSE
          # 或在相对延迟后返回给我们 (<to_self_delay>)
          `to_self_delay`
          OP_CHECKSEQUENCEVERIFY
          OP_DROP
          <local_delayedpubkey>
        OP_ENDIF
        OP_CHECKSIG
      "
    }
  ]
}

一旦 HTLC 交易被确认,它们的输出可以通过你的(单方面的)签名简单地花费,这允许你在任何方便的时候将资金发送回你的钱包。

关闭交易

你可能已经注意到,使用承诺交易,然后使用 HTLC 交易将链下的资金转移回链上是复杂且昂贵的(这将花费相当多的链上交易费用)。 这意味着你的对等方没有合作的特殊情况。 将资金转移回链上的推荐方式是通过以协作方式构建的关闭交易。

当通道参与者之一启动通道关闭时,该通道将停止接受新的付款。 双方等待并结算待处理的付款(完成或失败),以从承诺交易中删除所有 HTLC 输出。 一旦只剩下 2 个输出(每个参与者一个,代表其在通道中的最新余额),参与者交换地址以将资金发送到(这消除了相对的 to_self_delay,允许在链上立即且单方面地花费通道资金)。

生成的关闭交易如下所示:

{
  "版本": 2,
  "锁定时间": 0,
  "vin": [
    {
      "txid": "...",
      "vout": 42,
      "scriptSig": "0 <signature_for_pubkey1> <signature_for_pubkey2>",
      "sequence": 0xFFFFFFFF
    }
  ],
  "vout": [
    {
      "value": 0.4,
      "scriptPubKey": "A 提供的地址"
    },
    {
      "value": 0.6,
      "scriptPubKey": "B 提供的地址"
    }
  ]
}

关闭交易直接从资金交易中花费,隐藏了承诺交易和 HTLC 交易的所有复杂性。 通道的链上足迹如下所示:

+------------+
| 资金交易 |
+------------+
       |
       |          +------------+
       +--------->| 关闭交易 |
                  +------------+
                        | |
                        | |
                        | +-----> A 的输出
                        |
                        +-------> B 的输出

注意:sequence 设置为 0xFFFFFFFF 以确保所有时间锁都被禁用。

带有锚点输出的闪电交易

我们刚刚描述的交易格式工作得很好,如果以下假设成立

  1. 节点没有太多延迟地接收(链上)区块
  2. 已发布的交易很快会包含在区块中

但是,如果你的对等方变得无响应并且突然链上手续费大幅上涨怎么办? 由于承诺交易和 HTLC 交易需要来自双方参与者的签名,因此你无法提高手续费; 你的交易只有在链上手续费恢复到较低水平后才能确认。 我们的第二个条件不再成立,这可能是一个真正的问题,因为 HTLC 在特定的绝对区块高度超时。

你需要能够提高你的承诺交易和 HTLC 交易的手续费,以激励矿工将它们包含在一个区块中。 比特币为此提供了两种机制,我们将在下面解释:手续费替换 (RBF) 和子为父偿 (CPFP)。

RBF 允许 花费者 增加手续费; CPFP 允许 接收者 增加手续费。

手续费替换

RBF 让你替换一个内存池交易。 但是替换交易会给网络带来成本,因为新版本的交易需要传播到整个网络。 为了避免创建容易的 DoS 攻击向量,RBF 对新版本的交易施加了严格的限制,详细信息请参见 BIP 125

这些规则具有非常微妙的后果,因此值得详细阅读 BIP。 其要点是新交易必须支付比它替换的整个未确认的“包”严格更多的手续费(否则矿工将没有动力支持此新交易)。

也可以通过将所有输入的 sequence 设置为 0xFFFFFFFF 在交易级别显式禁用 RBF。

子为父偿

CPFP 让你将一个子交易附加到一个未确认的交易。 通过在子交易上支付高额手续费,你可以激励矿工将父交易和子交易都包含在一个区块中,从而加速确认父交易。

如果子交易仍然没有支付足够的手续费来使交易快速确认(例如,因为手续费一直在上涨),则 RBF 和 CPFP 的组合可以帮助你解决这种情况。 你可以在你的子交易上使用 RBF(或者在父交易上,如果你能够这样做,但通常你将无法生成签名来替换父交易),或者通过将另一个子交易附加到未确认的交易链上来使用 CPFP 。

 内存池
+------------------------------------------------------------+
|                     +---------------+                      |
|   +---------+       | 一些其他的交易 |                      |
|   | 一些交易 |       +---------------+                      |
|   +---------+                                              |
|                                                            |
| +---------+            +-----------+                       |
| | 低手续费 |----------->| 子交易 1 |                       |
| |   交易   |----+       +-----------+                       |
| +---------+    |       +-----------+       +-----------+   |
|                +------>| 子交易 2 |------>| 子交易 3 |   |
|                        +-----------+       +-----------+   |
+------------------------------------------------------------+

但是,你可以添加到内存池的未确认的交易链存在限制。 一旦达到该限制,加速确认的唯一选择是:

  • 等待某些交易从内存池中被逐出(然后你可以以更高的手续费重新提交它们):这可能需要一段时间
  • 在其中一个交易上使用 RBF:由于 RBF 规则,这可能非常昂贵(你将需要提供的手续费增加可能不可忽略)
  • 利用 CPFP carve-out rule:这是一个特别添加的特殊规则,用于解决这种情况,它允许你添加最后一个额外的子交易,但前提是其唯一的父交易是第一个未确认的交易。 这意味着第一个未确认的交易需要有一个可用于该目的的输出。
 内存池:txA -> txB -> txC 链具有允许的最大长度
+------------------------------------------------------------+
|                     +---------------+                      |
|   +---------+       | 一些其他的交易 |                      |
|   | 一些交易 |       +---------------+                      |
|   +---------+                                              |
|                                                            |
| +---------+      +-----------+      +-----------+          |
| |   txA   |----->| 子交易 B |----->| 子交易 C |          |
| | 低手续费 |      +-----------+      +-----------+          |
| +---------+                                                |
+------------------------------------------------------------+

 carve-out rule 允许我们绕过大小限制并添加子交易 txD
+------------------------------------------------------------+
|                     +---------------+                      |
|   +---------+       | 一些其他的交易 |                      |
|   | 一些交易 |       +---------------+                      |
|   +---------+                                              |
|                                                            |
| +---------+      +-----------+      +-----------+          |
| |   txA   |----->| 子交易 B |----->| 子交易 C |          |
| | 低手续费 |--+   +-----------+      +-----------+          |
| +---------+  |   +-----------+                             |
|              +-->| 子交易 D |                             |
|                  +-----------+                             |
+------------------------------------------------------------+

注意:当使用 CPFP 时,你添加的子交易必须有效(矿工必须能够在下一个区块中包含它)。 如果你的子交易在未来设置了一个绝对或相对时间锁,它将被拒绝。 特别是,如果你的子交易对仍然在内存池中的父交易具有相对时间锁,则你不能将其用于 CPFP。

提升闪电交易

正如我们之前所看到的,闪电交易中有 5 种类型的交易:

  • 资金交易
  • 承诺交易
  • htlc-成功
  • htlc-超时
  • 关闭交易

在高层次上,该表总结了哪些可以被 单方面 RBF 或 CPFP:

交易 RBF CPFP
资金 :heavy_check_mark:* :heavy_check_mark:
承诺 :x: :heavy_check_mark:*
htlc-成功 :x: :x:
htlc-超时 :x: :x:
关闭 :x: :heavy_check_mark:

资金交易可以被 RBF(因为它完全是单方面创建的),但这将使它的交易 ID 无效,因此你需要启动一个新的通道创建流程,这非常麻烦。 如果你想让你的资金交易更快地确认,大多数时候最好使用 CPFP。

承诺交易可以被 CPFP,但只能由远程节点(通过其主要输出、撤销密钥或它具有原像的 HTLC 输出)。 所有其他输出都使用相对时间锁,因此不能用于 CPFP。

由于它们需要来自双方的签名,因此任何交易都不能被 单方面 RBF。

如果你被迫广播你的承诺交易,因为远程节点无响应并且它卡住在内存池中,这是非常不方便的。 如果能够使其更快地确认会很好,这一直是积极研究的领域。

锚点输出

锚点输出提案 通过利用 CPFP carve-out rule 和更好地使用 sighash 标志来解决这些缺点。

在高层次上,它由四个主要变化组成:

  1. 更新 HTLC 交易以使用 SIGHASH_SINGLE | SIGHASH_ANYONECANPAY。 这允许你通过添加新的输入/输出来提高手续费来 RBF 你的 HTLC 交易。
  2. 将新的输出(称为 锚点输出)添加到承诺交易中,专门用于 CPFP carve-out。 这允许你对卡在内存池中的承诺交易执行 CPFP。
  3. 将 1 个区块的相对时间锁添加到承诺交易的所有输出,除了新的 锚点输出。 这确保了只有这两个输出可以用于 CPFP carve-out; 所有其他输出不能用于 CPFP,它们必须等待承诺交易被确认后才能被花费。
  4. 将 HTLC 交易的手续费设置为 0:这可以防止 此处 描述的手续费虹吸攻击。

让我们关注第一点。 现在一个 HTLC 成功的交易看起来像:

{
  "版本": 2,
  "锁定时间": 0,
  "vin": [
    {
      "txid": "...",
      "vout": 42,
      "scriptSig": "0 <remotehtlcsig> <localhtlcsig> <payment_preimage>",
      "sequence": 1
    }
  ],
  "vout": [
    {
      "value": 0.04,
      "scriptPubKey": "
        OP_IF
          # 资金会发送给任何拥有撤销密钥的人
          <revocationpubkey>
        OP_ELSE
          # 或在相对延迟后返回给我们 (<to_self_delay>)
          `to_self_delay`
          OP_CHECKSEQUENCEVERIFY
          OP_DROP
          <local_delayedpubkey>
        OP_ENDIF
        OP_CHECKSIG
      "
    }
  ]
}

与当前格式的唯一区别是将 sequence 设置为 1(而不是 0),以及远程签名使用 SIGHASH_SINGLE | SIGHASH_ANYONECANPAY(但本地签名必须使用 SIGHASH_ALL,以防止其他人在你广播后恶意篡改交易)。 这允许你使用以下内容替换交易,而不会使签名无效:

{
  "version": 2,
  "locktime": 0,
  "vin": [
    {
      "txid": "...",
      "vout": 42,
      "scriptSig": "0 <remotehtlcsig> <localhtlcsig> <payment_preimage>",
      "sequence": 1
    },
    {
      "txid": "...",
      "vout": ...,
      "scriptSig": "...",
      "sequence": ...
    }
  ],
  "vout": [
    {
      "value": 0.04,
      "scriptPubKey": "
        OP_IF
          # 资金给拥有撤销密钥的任何人
          <revocationpubkey>
        OP_ELSE
          # 或在相对延迟 (<to_self_delay>) 后返回给我们
          `to_self_delay`
          OP_CHECKSEQUENCEVERIFY
          OP_DROP
          <local_delayedpubkey>
        OP_ENDIF
        OP_CHECKSIG
      "
    },
    {
      "value": ...,
      "scriptPubKey": "..."
    }
  ]
}

通过附加数量为 N 的输入和数量为 M 的输出,你将 N-M 的费用添加到交易中(你当然可以附加多个输入/输出)。这使你可以任意提高费用并确保 HTLC 交易及时确认。

第二点和第三点将承诺交易的格式更改为:

{
  "version": 2,
  "locktime": 543210000,
  "vin": [
    {
      "txid": "...",
      "vout": ...,
      "scriptSig": "0 <signature_for_pubkey1> <signature_for_pubkey2>",
      "sequence": 2500123456
    }
  ],
  "vout": [
    {
      "value": 0.5,
      "scriptPubKey": "
        # to_local 输出
        OP_IF
          # 资金给拥有撤销密钥的任何人
          <revocationpubkey>
        OP_ELSE
          # 或在相对延迟 (<to_self_delay>) 后返回给我们
          <to_self_delay>
          OP_CHECKSEQUENCEVERIFY
          OP_DROP
          <local_delayedpubkey>
        OP_ENDIF
        OP_CHECKSIG
      "
    },
    {
      "value": 0.3,
      "scriptPubKey": "
        # to_remote 输出: 在 1 个区块后返回给另一个通道参与者
        <remote_pubkey>
        OP_CHECKSIGVERIFY
        1 OP_CHECKSEQUENCEVERIFY
      "
    },
    {
      "value": 0.00000330,
      "scriptPubKey": "
        # 我们的 anchor 输出: 由我们使用 CPFP 增加费用
        <local_funding_pubkey>
        OP_CHECKSIG
        OP_IFDUP
        OP_NOTIF
          # 在 16 个区块的相对时间锁之后,任何人都可以声明这个少量金额
          OP_16 OP_CHECKSEQUENCEVERIFY
        OP_ENDIF
      "
    },
    {
      "value": 0.00000330,
      "scriptPubKey": "
        # remote anchor 输出: 由他们使用 CPFP 增加费用
        <remote_funding_pubkey>
        OP_CHECKSIG
        OP_IFDUP
        OP_NOTIF
          # 在 16 个区块的相对时间锁之后,任何人都可以声明这个少量金额
          OP_16 OP_CHECKSEQUENCEVERIFY
        OP_ENDIF
      "
    },
    {
      "value": 0.05,
      "scriptPubKey": "
        # 我们发送的待处理 HTLC (付款)
        OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
        OP_IF
          # 资金给拥有撤销密钥的任何人
          OP_CHECKSIG
        OP_ELSE
          <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
          OP_NOTIF
            # 资金通过第二阶段 HTLC-timeout 交易返回给我们(其中包含绝对延迟)
            # 注意:我们还需要远程签名,这可以防止我们单方面更改 HTLC-timeout 交易
            OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
          OP_ELSE
            # 如果远程节点有付款 preimage,则资金给他们。
            OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
            OP_CHECKSIG
          OP_ENDIF
          # 增加 1 个区块的延迟以防止此输出用于 CPFP
          1 OP_CHECKSEQUENCEVERIFY OP_DROP
        OP_ENDIF
      "
    },
    {
      "value": 0.08,
      "scriptPubKey": "
        # 发送给我们的待处理 HTLC (付款)
        OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
        OP_IF
          # 资金给拥有撤销密钥的任何人
          OP_CHECKSIG
        OP_ELSE
          <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
          OP_IF
            # 一旦我们有了付款 preimage,资金就会通过第二阶段 HTLC-success 交易发送给我们
            # 注意:我们还需要远程签名,这可以防止我们单方面更改 HTLC-success 交易
            OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
            2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
          OP_ELSE
            # 在绝对延迟 (timeout) 后,资金发送给远程节点
            OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
            OP_CHECKSIG
          OP_ENDIF
          # 增加 1 个区块的延迟以防止此输出用于 CPFP
          1 OP_CHECKSEQUENCEVERIFY OP_DROP
        OP_ENDIF
      "
    },
  ]
}

如果你非常努力地眯起眼睛,你会发现输出脚本的变化只是在以前无需任何延迟即可花费的输出中添加了 1 OP_CHECKSEQUENCEVERIFY

这确保了每一方都有恰好一个输出可用于 CPFP:新添加的 锚点。这样,无论恶意参与者可能采取什么措施来阻止承诺交易及时确认,他们都只能通过向他们自己的 锚点 添加子交易来实现,并且诚实的参与者始终有机会利用 CPFP carve-out 规则至少一次提高费用。

锚点 输出具有相对时间锁分支,允许任何人花费它们。这样做的原因是我们要对 Bitcoin 层友好,并避免创建许多会膨胀 UTXO 集的微小 UTXO。通过让任何人花费它们,当费用低时,它们有机会被合并。

现在的总体交易图如下所示:

+------------+
| funding tx |
+------------+
      |
      |        +-------------------+
      +------->|    commit tx B    |
               +-------------------+
                  |  |  |  |  |  |  
                  |  |  |  |  |  | A's main output
                  |  |  |  |  |  +-----------------> to A after a 1-block relative delay
                  |  |  |  |  |
                  |  |  |  |  |                 +---> to B after relative delay
                  |  |  |  |  | B's main output |
                  |  |  |  |  +-----------------+
                  |  |  |  |                    |
                  |  |  |  |                    +---> to A with revocation key
                  |  |  |  |
                  |  |  |  | A's anchor output
                  |  |  |  +--------------------> to A immediately (or anyone after 16-block relative delay)
                  |  |  |
                  |  |  | B's anchor output
                  |  |  +-----------------------> to B immediately (or anyone after 16-block relative delay)
                  |  |
                  |  |     (B's RBF inputs) ---+
                  |  |                         |                                    +---> to B after relative delay
                  |  |                         +---->+-----------------+            |
                  |  |                   +---------->| HTLC-timeout tx |------------+
                  |  | HTLC offered by B |           +-----------------+            |
                  |  +-------------------+      (after timeout + 1-block delay)     +---> to A with revocation key
                  |                      |
                  |                      +---> to A with payment preimage after a 1-block relative delay
                  |                      |
                  |                      +---> to A with revocation key
                  |
                  |        (B's RBF inputs) ---+
                  |                            |                                        +---> to B after relative delay
                  |                            +---->+-----------------+                |
                  |                    +------------>| HTLC-success tx |----------------+
                  | HTLC received by B |             +-----------------+                |
                  +--------------------+     (with payment preimage + 1-block delay)    +---> to A with revocation key
                                       |
                                       +---> to A after timeout (absolute delay + 1-block relative delay)
                                       |
                                       +---> to A with revocation key

RBF Pinning

虽然 anchor 输出提案解决了承诺交易确认缓慢的问题,但它并没有解决与交易 pinning 相关的其他问题,甚至可能提供更多的攻击面来窃取 HTLC 输出。

这个 邮件线程 详细介绍了这些问题,并开始了围绕替代提案的讨论。

攻击最简单的化身如下:

  • 攻击者 (Mallory) 与她的目标 (Alice) 建立两个通道:Mallory -> Alice -> Mallory
  • 然后,Mallory 继续通过这两个通道向自己发送付款
  • 当她收到 HTLC 时,她停止响应并等待 timeout,迫使 Alice 发布她的承诺交易,其中包含 htlc-offered 输出
  • 然后,Mallory 将发布一个交易来声明该 htlc 输出(揭示 preimage),费用很低并且 RBF 已禁用
  • 如果 Mallory 的交易在 Alice 的 htlc-timeout 交易之前进入矿工的 mempool,Alice 将无法取回资金,因为她无法替换 Mallory 的交易
  • 如果 Alice 无法检索到 preimage(因为她自己的 mempool 包含她的 htlc-timeout 交易),她将不得不让上游 htlc 过期。当 Mallory 的交易最终被挖掘时,Alice 将在下游支付 htlc 金额,但未收到上游的相应金额

请注意,这种攻击在实践中很难进行:确保你的交易进入比你的目标更多的 mempool 而不向你的目标透露 preimage 是一项非常艰巨的任务。但是 anchor 输出提案使它比以前稍微简单一些。如果没有 1 个区块的相对延迟,Alice 能够一起广播承诺交易和她的 htlc-timeout 交易:这使她抢先一步,在 Mallory 之前将她的 htlc-timeout 放入节点的 mempool 中。使用 anchor 输出,她必须等待承诺交易被确认后才能广播 htlc-timeout;她不再具有抢先优势。

这种攻击成为可能的主要原因是 Mallory 对她如何在 Alice 的承诺交易中花费 htlc 输出没有限制。链接的电子邮件线程建议使它更对称,仅允许 Mallory 通过需要两个参与者签名的预签名交易来花费 htlc 输出,并将 anchor 输出添加到 HTLC 交易中。

替代提案

让我们总结一下我们的要求,并详细说明一个结合了前两个部分想法的提案。

让我们定义以下常量(可由实现配置):

received htlc expiry                                offered htlc expiry
        |                                                     |
        |                                                     |
        v                                                     v
        +-----------------------------------------------------+
                           cltv_expiry_delta
        <----------------------------------------------------->
            fulfill_safety_delta                 N
        <---------------------------><------------------------>

当我们广播我们的承诺交易时,我们需要处理以下情况:

  • Offered HTLC timeout:在这种情况下,我们希望确保以下两种情况之一:
    • 我们的 HTLC-timeout 交易在 N 个区块内确认
    • 或者我们在 N 个区块之前了解了 HTLC preimage
  • Received HTLC 应该被 fulfill,但在小于 fulfill_safety_delta 的时间内 timeout。我们希望确保我们的 HTLC-success 交易确认(防止远程通过 timeout 分支花费它)

如果远程参与者广播了他们的承诺交易,我们希望确保:

  • 如果我们在 timeout 之前有 received HTLC 的 preimage(在他们的承诺中提供),我们可以声明输出(防止远程通过他们的 HTLC-timeout 交易花费它)
  • 如果 offered HTLC timeout(在他们的承诺中收到),我们必须:
    • 在小于 N 个区块内声明 HTLC 输出
    • 或者在 N 个区块之前了解 HTLC preimage

当前的交易格式让我们轻松处理第二种情况(当远程广播他们的承诺交易时),但不能处理第一种情况。

为了利用 HTLC 交易上的 CPFP carve-out 规则,我们需要能够快速确认承诺交易。这意味着我们需要承诺交易有两个 anchor 并且所有其他输出都有 1 个区块的相对时间锁。

HTLC-timeout 交易可以使用 SIGHASH_SINGLE | SIGHASH_ANYONECANPAY 进行远程签名,这让我们 RBF 它。但是,HTLC-success 交易必须使用 SIGHASH_ALL 并利用 anchor 输出通过 CPFP 增加费用。当远程通过提供 preimage 声明 HTLC 输出时,需要一个新的交易:我们称之为 Remote-HTLC-success 交易。它必须使用 SIGHASH_ALL 并且每个参与者都有一个输出以允许 CPFP carve-out。对于声明 received HTLC 的 timeout 分支,似乎不需要额外的交易。

防止恶意参与者广播低费用 HTLC-success 或 Remote-HTLC-success 交易的技巧是,我们始终可以在它们上盲目地进行 CPFP carve-out;我们知道它们的 txid(因为它们使用 SIGHASH_ALL),因此如果我们观察到我们的 HTLC-timeout(或我们对 received HTLC 的 timeout 分支的声明)没有确认,我们可以尝试广播花费我们在成功交易上的(潜在)anchor 输出的交易。目标是使其快速确认(如果存在),以便我们了解 preimage 并及时调整我们的上游行为。

+------------+
| funding tx |
+------------+
      |
      |        +-------------------+
      +------->|    commit tx B    |
               +-------------------+
                  |  |  |  |  |  |  
                  |  |  |  |  |  | A's main output
                  |  |  |  |  |  +-----------------> to A after a 1-block relative delay
                  |  |  |  |  |
                  |  |  |  |  |                 +---> to B after relative delay
                  |  |  |  |  | B's main output |
                  |  |  |  |  +-----------------+
                  |  |  |  |                    |
                  |  |  |  |                    +---> to A with revocation key
                  |  |  |  |
                  |  |  |  | A's anchor output
                  |  |  |  +--------------------> to A immediately (or anyone after 16-block relative delay)
                  |  |  |
                  |  |  | B's anchor output
                  |  |  +-----------------------> to B immediately (or anyone after 16-block relative delay)
                  |  |
                  |  |     (B's RBF inputs) ---+
                  |  |                         |                                    +---> to B after relative delay
                  |  |                         +---->+-----------------+            |
                  |  |                   +---------->| HTLC-timeout tx |------------+
                  |  | HTLC offered by B |           +-----------------+            |
                  |  +-------------------+      (after timeout + 1-block delay)     +---> to A with revocation key
                  |                      |
                  |                      |           +------------------------+
                  |                      +---------->| Remote-HTLC-success tx |
                  |                      |           +------------------------+
                  |                      |     (with payment preimage + 1-block delay)
                  |                      |                      |  |
                  |                      |                      |  | A's output
                  |                      |                      |  +-----------------> to A immediately (or anyone after 16-block relative delay)
                  |                      |                      |
                  |                      |                      | B's anchor output
                  |                      |                      +--------------------> to B immediately (or anyone after 16-block relative delay)
                  |                      |
                  |                      +---> to A with revocation key
                  |
                  |                                  +-----------------+
                  |                    +------------>| HTLC-success tx |
                  | HTLC received by B |             +-----------------+
                  +--------------------+     (with payment preimage + 1-block delay)
                                       |                   |  |  |                     +---> to A with revocation key
                                       |                   |  |  | B's output          |
                                       |                   |  |  +---------------------+---> to B after relative delay
                                       |                   |  |
                                       |                   |  | A's anchor output
                                       |                   |  +--------------------> to A immediately (or anyone after 16-block relative delay)
                                       |                   |
                                       |                   | B's anchor output
                                       |                   +-----------------------> to B immediately (or anyone after 16-block relative delay)
                                       |
                                       +---> to A after timeout (absolute delay + 1-block relative delay)
                                       |
                                       +---> to A with revocation key

注意:

  • 这大大提高了在链上强制执行的最低 HTLC 值,这非常令人难过,并且使交易更加复杂/昂贵
  • 盲目的 CPFP carve-out 是一次性的,因此你可能需要支付大量费用才能使其工作,这仍然会在攻击者以你为目标的情况下让你赔钱(但钱会流向矿工,而不是流向攻击者 - 除非他是矿工)。估计你应将多少费用放入盲目的 CPFP carve-out 中可能很困难,因为你不知道成功交易包的当前费用是多少(如果攻击者创建了很长的未确认交易链)
  • 如果我们退一步,我们需要防范的唯一攻击是攻击者 pinning preimage 交易,同时阻止我们在至少 N 个区块内了解该 preimage。如果我们有:
    • 足够高的 cltv_expiry_delta(以及足够高的 N 值)
    • 链下 preimage 广播
    • LN hubs(或任何在商业上投资于运行闪电节点的各方)参与各种挖掘池以帮助发现 preimage
    • 对 eclipse 攻击的体面缓解
    • 那么官方的 anchor 输出提案应该足够安全了吗?

资源

  • 原文链接: github.com/t-bast/lightn...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
t-bast
t-bast
江湖只有他的大名,没有他的介绍。