本文深入探讨了比特币闪电网络中的交易机制,从比特币交易的基础格式、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
标志,它可以与 OP_CHECKSIG
操作码(以及 OP_CHECKSIG
的变体)一起使用。
让我们假设我们有一个具有以下 scriptPubKey
的 UTXO:<alice_pubkey> OP_CHECKSIG
,表明 Alice 可以花费该 UTXO。 Alice 可以创建一个新的交易,其中包括 vin
中的 UTXO。 在输入的scriptSig
中,她需要提供这个 新 交易的有效签名。 除了签名字节之外,她还附加一个字节,指定已签名交易的哪些部分(这就是 sighash
标志)。
这些 sighash
标志为复杂的、多参与者的交易提供了一些灵活性。 当交易的某些部分 未 签名时,这意味着其他参与者可以在保持其有效性的同时稍微更新交易,而无需签名者重新签署更改。
该 sighash
字节的可能值为:
sequence
字段未签名。 这意味着签名者同意选定的输入和他选择的单个输出,但允许其他参与者自由修改其他输出。请注意,显然,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
。 这确保了双方在交换签名时对承诺交易的确切细节达成一致。
它将 locktime
和 sequence
设置为允许在没有延迟的情况下花费它的值(这些字段也用于以一种非常巧妙的方式隐藏一些数据,对于本文档我们不关心)。
当通道中的任何内容发生更改时(添加了新的付款,待处理的付款失败或已完成):
容量为 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 输出。 它们需要来自双方参与者的 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
以确保所有时间锁都被禁用。
我们刚刚描述的交易格式工作得很好,如果以下假设成立:
但是,如果你的对等方变得无响应并且突然链上手续费大幅上涨怎么办? 由于承诺交易和 HTLC 交易需要来自双方参与者的签名,因此你无法提高手续费; 你的交易只有在链上手续费恢复到较低水平后才能确认。 我们的第二个条件不再成立,这可能是一个真正的问题,因为 HTLC 在特定的绝对区块高度超时。
你需要能够提高你的承诺交易和 HTLC 交易的手续费,以激励矿工将它们包含在一个区块中。 比特币为此提供了两种机制,我们将在下面解释:手续费替换 (RBF) 和子为父偿 (CPFP)。
RBF 允许 花费者 增加手续费; CPFP 允许 接收者 增加手续费。
RBF 让你替换一个内存池交易。 但是替换交易会给网络带来成本,因为新版本的交易需要传播到整个网络。 为了避免创建容易的 DoS 攻击向量,RBF 对新版本的交易施加了严格的限制,详细信息请参见 BIP 125。
这些规则具有非常微妙的后果,因此值得详细阅读 BIP。 其要点是新交易必须支付比它替换的整个未确认的“包”严格更多的手续费(否则矿工将没有动力支持此新交易)。
也可以通过将所有输入的 sequence
设置为 0xFFFFFFFF
在交易级别显式禁用 RBF。
CPFP 让你将一个子交易附加到一个未确认的交易。 通过在子交易上支付高额手续费,你可以激励矿工将父交易和子交易都包含在一个区块中,从而加速确认父交易。
如果子交易仍然没有支付足够的手续费来使交易快速确认(例如,因为手续费一直在上涨),则 RBF 和 CPFP 的组合可以帮助你解决这种情况。 你可以在你的子交易上使用 RBF(或者在父交易上,如果你能够这样做,但通常你将无法生成签名来替换父交易),或者通过将另一个子交易附加到未确认的交易链上来使用 CPFP 。
内存池
+------------------------------------------------------------+
| +---------------+ |
| +---------+ | 一些其他的交易 | |
| | 一些交易 | +---------------+ |
| +---------+ |
| |
| +---------+ +-----------+ |
| | 低手续费 |----------->| 子交易 1 | |
| | 交易 |----+ +-----------+ |
| +---------+ | +-----------+ +-----------+ |
| +------>| 子交易 2 |------>| 子交易 3 | |
| +-----------+ +-----------+ |
+------------------------------------------------------------+
但是,你可以添加到内存池的未确认的交易链存在限制。 一旦达到该限制,加速确认的唯一选择是:
内存池:txA -> txB -> txC 链具有允许的最大长度
+------------------------------------------------------------+
| +---------------+ |
| +---------+ | 一些其他的交易 | |
| | 一些交易 | +---------------+ |
| +---------+ |
| |
| +---------+ +-----------+ +-----------+ |
| | txA |----->| 子交易 B |----->| 子交易 C | |
| | 低手续费 | +-----------+ +-----------+ |
| +---------+ |
+------------------------------------------------------------+
carve-out rule 允许我们绕过大小限制并添加子交易 txD
+------------------------------------------------------------+
| +---------------+ |
| +---------+ | 一些其他的交易 | |
| | 一些交易 | +---------------+ |
| +---------+ |
| |
| +---------+ +-----------+ +-----------+ |
| | txA |----->| 子交易 B |----->| 子交易 C | |
| | 低手续费 |--+ +-----------+ +-----------+ |
| +---------+ | +-----------+ |
| +-->| 子交易 D | |
| +-----------+ |
+------------------------------------------------------------+
注意:当使用 CPFP 时,你添加的子交易必须有效(矿工必须能够在下一个区块中包含它)。 如果你的子交易在未来设置了一个绝对或相对时间锁,它将被拒绝。 特别是,如果你的子交易对仍然在内存池中的父交易具有相对时间锁,则你不能将其用于 CPFP。
正如我们之前所看到的,闪电交易中有 5 种类型的交易:
在高层次上,该表总结了哪些可以被 单方面 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 标志来解决这些缺点。
在高层次上,它由四个主要变化组成:
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
。 这允许你通过添加新的输入/输出来提高手续费来 RBF 你的 HTLC 交易。锚点输出
)添加到承诺交易中,专门用于 CPFP carve-out。 这允许你对卡在内存池中的承诺交易执行 CPFP。锚点输出
。 这确保了只有这两个输出可以用于 CPFP carve-out; 所有其他输出不能用于 CPFP,它们必须等待承诺交易被确认后才能被花费。让我们关注第一点。 现在一个 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
虽然 anchor 输出提案解决了承诺交易确认缓慢的问题,但它并没有解决与交易 pinning 相关的其他问题,甚至可能提供更多的攻击面来窃取 HTLC 输出。
这个 邮件线程 详细介绍了这些问题,并开始了围绕替代提案的讨论。
攻击最简单的化身如下:
Mallory -> Alice -> Mallory
请注意,这种攻击在实践中很难进行:确保你的交易进入比你的目标更多的 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
<---------------------------><------------------------>
当我们广播我们的承诺交易时,我们需要处理以下情况:
N
个区块内确认N
个区块之前了解了 HTLC preimagefulfill_safety_delta
的时间内 timeout。我们希望确保我们的 HTLC-success 交易确认(防止远程通过 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
注意:
N
个区块内了解该 preimage。如果我们有:
cltv_expiry_delta
(以及足够高的 N
值)
- 原文链接: github.com/t-bast/lightn...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!