作者:Bastien Teinturier
来源:https://github.com/lightning/bolts/blob/route-blinding/proposals/route-blinding.md
提议
前言
路径盲化是一种轻量级的技术,通过在一条洋葱路径的末端盲化任意多跳来提供支付接收方的匿名性。它比约会路由(rendezvous routing)更灵活,因为它直接用随机的公钥替换了路径上的节点的公钥,同时让发送者选择要在每一跳的洋葱消息中放置什么数据。在某些情况(例如,洋葱消息)下,盲化路径是可以复用的。
相比约会路由,路径盲化的缺点在于,发送者可以通过改变多个参数,产生更多的打探(probe)可能性,所以,这种方案需要显式地抵御打探攻击,而且在某些攻击情景中,也许只能提供更少的隐私性。
路径盲化可以派上用场的一些场景包括:
- 洋葱消息传递中的发送者和接收者匿名性
- BOLT12 offer 协议中的接收者匿名性
- 接收支付时候的接收者匿名性
- 在发票中使用未宣告的通道而且不必公开它
- 强迫一笔交易经过特定的一组可以让支付获得合法性的中介
概述
抽象地来说,盲化路由的工作原理是让支付的接收者选出一个 入口节点 以及一条从这个入口节点触达自身的路径。然后接收者使用 “ECDH(基于椭圆曲线的迪菲-赫尔曼密钥交换)” 来盲化这条路径上的每一个节点和通道。接收者将这条盲化路径的详情和一些密码学裁量发送给接收者(通过 BOLT11 发票或者 BOLT12 offer),让发送者可以建构出一条具备足够信息的洋葱消息,让盲化路径上的节点可以逐步地复明、发现路径上的下一个节点。
这个方案要求盲化路径上的所有节点以及发送者主动支持这个特性。它要在网络中获得足够大比例的节点支持,以提供有意义的隐私性保障。
记号
- 节点
N(i)
的node_id(节点 ID)
定义为:N(i) = k(i) * G
(其中k(i)
就是这个节点的私钥) - 盲化的
节点 ID
定义为:B(i) = b(i) * G
(b(i)
就是致盲因子) - Sphinx 零时公钥定义为:
E(i) = e(i) * G
要求
节点 N(r)
希望提供一条洋葱消息必须经过的盲化路径 N(0) -> N(1) -> ... -> N(r)
。
- 盲化路径中的中间节点不能知晓自己的前任和后任以外的其它中间节点的
节点 ID
和scid
- 盲化路径中的中间节点不能知晓他们跟接收者
N(r)
的距离 - 发送者不能知晓入口节点
N(0)
之后的盲化中间节点的真实节点 ID
和scid
- 如果
N(r)
创建了多跳触达自身的盲化路径,发送者不应能够发现这些路径将到达同一个接收者(除非这些信息被协议的更高层级泄露,例如使用了相同的支付哈希值
,或者由同一个 offer 生成)
加密数据
盲化路径为洋葱消息负载(payload) tlv_payload
引入了一个新的 TLV 字段:encrypted_data(加密数据)
。
这个字段用来携带来自路径的建构者的数据,而且无法被发送者修改。它需要包含足够多的数据,让中间节点能够定位路径的下一个节点(通常使用 节点 ID
或者 scid
),而且未来可能延伸到携带额外的数据。它使用 ChaCha20-Poly1305 作为 AEAD(认证加密)方案。
- 类型:10(
encrypted_data
) - 数据:
- [
...*byte
:encrypted_data
]
- [
解密之后,其中的内容就是一个 TLV 流。
创建一条盲化路径
N(r)
遵循下列步骤以创建一条盲化路径:
初始化:
e(0) <- {0;1}^256
E(0) = e(0) * G
盲化:
For i = 0 to r:
ss(i) = H(e(i) * N(i)) = H(k(i) * E(i)) // 仅为 N(r) 和 N(i) 所知的共享秘密值
B(i) = HMAC256("blinded_node_id", ss(i)) * N(i) // N(i) 的盲化节点 ID,只有 N(i) 知道其背后的私钥
rho(i) = HMAC256("rho", ss(i)) // N(r) 用来加密给 N(i) 的消息负载的密钥
e(i+1) = H(E(i) || ss(i)) * e(i) // 临时私钥,仅为 N(r) 所知
E(i+1) = H(E(i) || ss(i)) * E(i) // NB:N(i) 不应能够知晓 e(i)
注意,它跟 Sphinx 有完全相同的构造,但在每一跳中,我们都使用共享的秘密值、为 N(i)
推导出一个盲化的 节点 ID
,这个 ID 背后的私钥将仅为 N(i)
所知。
然后,接收者使用 ChaCha20-Poly1305 算法和 rho(i)
密钥加密专属于应用的数据,形成 encrypted_data(i)
。
为了使用盲化路径,发送者需要下列数据:
- 入口节点
N(0)
的真实节点 ID
(以确定盲化路由的起点) - 盲化
节点 ID
的清单:[B(1),...,B(r)]
- 给每个节点的加密数据:
[encrypted_data(0),...,encrypted_data(r)]
- 第一个致盲的临时公钥:
E(0)
发送到一个盲化路径
发送者先找出一条触达入口节点 N(0)
的路径,然后使用盲化路径来延伸这条路径。然后,发送者就能为整条路径创建一条洋葱消息,并在给 N(0)
的洋葱消息负载中包含 E(0)
和 encrypted_data(0)
。给 B(i)
的洋葱消息负载中就包含了 encrypted-data(i)
。
当 N(0)
收到洋葱消息并解密之后,TA 就会在负载中发现 E(0)
,因此能够计算下列信息:
ss(0) = H(k(0) * E(0))
rho(0) = HMAC256("rho", ss(0))
E(1) = H(E(0) || ss(0)) * E(0)
TA 使用 rho(0)
来解密 encrypted_data(0)
,就能发现 B(1)
实际上就是 N(1)
。TA 将洋葱消息转发给 N(1)
,并在闪电消息的一个 TLV 字段(例如,在一个 update_add_htlc
消息的延伸字段)包含 E(1)
。
后续所有的中间节点 N(i)
都按照下列步骤走:
E(i) <- 从闪电消息的字段中抽取出来
ss(i) = H(k(i) * E(i))
b(i) = HMAC256("blinded_node_id", ss(i)) * k(i)
使用 b(i) 而非 k(i),使用 sphinx 解密到达的洋葱消息
rho(i) = HMAC256("rho", ss(i))
使用 rho(i) 解密洋葱消息中的 `加密数据`,并发现盲化路径的下一个节点
E(i+1) = H(E(i) || ss(i)) * E(i)
将洋葱消息转发给下一个节点,并在消息的一个 TLV 字段包含 E(i+1)
从盲化路径中收款
当 N(r)
收到洋葱消息以及 E(r)
的时候,TA 需要像中间节点一样接触封装。区别仅在于 TA 收到的洋葱消息已经是最里面的一层了。
N(r)
也必须验证这条盲化路径用在了意图的环境中,而且确实是自己创建的路由。值得指出的是,任何人都能为其他人创建一条有效的盲化路径。例如,Alice 可以创建一条盲化路径 Bob -> Carol -> Dave
。在大部分时候,Dave 都希望忽略从别人创建的路径中发送过来的消息。
验证步骤的详情取决于使用盲化路径的实际应用。例如,为支付使用盲化路径时,接收者必须验证这条路径是跟正确的 支付哈希值
一起使用的。可以通过在给自己的 encrypted_data
负载中存储 支付哈希值
来做;恶意的发送者不能提前知道原像,所以无法创建出能够通过验证的路径。
没有验证步骤,接收者将让自己暴露在恶意的打探攻击中;这种攻击可能会让整条路径去匿名化。
盲化支付
本部分为盲化路径可以如何用于支付提供了更多的细节。
为了抵御恶意的打探攻击(详情放在下文的 “攻击” 章节),接收者要选择在路径中使用什么支付转发参数(例如:手续费)并在给每一个盲化路径的 encrypted_data
中编码这些信息。发送者不会在给盲化中间节点的洋葱消息负载中设定 amt_to_forward
和 outgoing_cltv_value
字段:这些节点会遵循在他们在 encrypted_data
中得到的指令。
给每个中间节点的 encrypted_data
都会包含下列字段:
short_channel_id
:应该用来转发支付的出账通道fee_base_msat
:在转发支付之前必须附加的基本手续费fee_proportional_millionths
:转发支付之前必须施加的比例手续费cltv_expiry_delta
:转发支付之前必须应用的 cltv 超时偏移量max_cltv_expiry
:这笔支付运行的最大超时时间htlc_minimum_msat
:应该使用的最小 htlc 数额allowed_features
:允许发送者使用的、跟支付转发相关的特性
接收者必须使用能提供足够好的匿名集的数值,这可以靠观察临近的通道、选出适用于足够多的通道的数值来做到。不然的话,恶意的发送者就可以找出盲化路径背后的通道:比如,你选择了一个对绝大部分人来说都太低的手续费率。
接收者也要在给自身的 encrypted_data
负载的 path_id
字段包含 支付哈希值
(或者是另一种只有自己知道、分配给各交易的唯一标识符):这使得接收者可以验证,这个路径是由自己创建的,并且只用在了某一笔支付中。
如果盲化路径上的节点收到了一笔不使用 encrypted_data
所示参数的支付,TA 应该拒绝这笔支付,回传以无法解析的报错洋葱消息。这保证了支付者无法知道是哪个节点出错了、因为什么理由出的错(否则这就会给支付者提供信息,让支付者可以打探路径上的节点)。
注意,我们也要提供一个 max_cltv_expiry
字段:这保证了这条盲化路径会在一段时间后过期,从而限制未来的打探尝试。
如果我们假设所有的节点都支持 var_onion_option
,目前我们就不需要包含 allowed_features
字段,因为还没有其它特性会影响支付转发、同时可以用作打探手法。不过,未来的升级可能会添加这样的特性(例如,PTLC 支持),这时候 allowed_features
字段必须为空。
我们用一个例子来理清这些要求。
Alice 创建了一个带有这个盲化路径 Carol -> Bob -> Alice
的发票。这条路径沿路的通道有如下状态:
- Carol -> Bob
fee_base_msat
: 10fee_proportional_millionths
: 250cltv_expiry_delta
: 144htlc_minimum_msat
: 1
- Bob -> Alice
fee_base_msat
: 50fee_proportional_millionths
: 100cltv_expiry_delta
: 48htlc_minimum_msat
: 1000
Alice 为盲化路径选择了下列参数,以满足上通道的要求,并提供了安全边际来应对节点更新自己的转发参数的情形:
fee_base_msat
: 100fee_proportional_millionths
: 500htlc_minimum_msat
: 1000cltv_expiry_delta
: 144
为简单起见,Alice 为两条通道使用了相同的数值。现在,Alice 可以为完整的路径计算出聚合的数值(从头到尾遍历),使用整数算法,将 ceil(a/b)
作为 (a+b-1)/b
(我们让数值向上取整,不然转发者将收到稍微小于预期的数额):
route_fee_base_msat(n+1) = (fee_base_msat(n+1) * 1000000 + route_fee_base_msat(n) * (1000000 + fee_proportional_millionths(n+1)) + 1000000 - 1) / 1000000
route_fee_proportional_millionths(n+1) = ((route_fee_proportional_millionths(n) + fee_proportional_millionths(n+1)) * 1000000 + route_fee_proportional_millionths(n) * fee_proportional_millionths(n+1) + 1000000 - 1) / 1000000
这产生了下列数值:
route_fee_base_msat
: 201route_fee_proportional_millionths
: 1001route_cltv_expiry_delta
: 288
我们假设当前的区块高度是 1000。Alice 希望在接下来 200 个区块内使用这条路径,所以她设定 max_cltv_expiry = 1200
,并为每一跳添加了 cltv_expiry_delta
(cltv 超时偏移量)。Alice 将下列信息传输给了发送者(大概率是通过一个发票):
盲化路径:
[N(carol), B(bob), B(alice)]
第一个致盲临时公钥:
E(carol)
聚合的路由转发参数和限制:
fee_base_msat
: 201fee_proportional_millionths
: 1001htlc_minimum_msat
: 1000cltv_expiry_delta
: 288max_cltv_expiry
: 1200allowed_features
: empty
给每一个盲化节点的加密数据:
- encrypted_payload(alice)
path_id
:payment_preimage
max_cltv_expiry
: 1200
- encrypted_payload(bob)
outgoing_channel_id
:scid_bob_alice
fee_base_msat
: 100fee_proportional_millionths
: 500htlc_minimum_msat
: 1000max_cltv_expiry
: 1344
- encrypted_payload(carol)
outgoing_channel_id
:scid_carol_bob
fee_base_msat
: 100fee_proportional_millionths
: 500htlc_minimum_msat
: 1000max_cltv_expiry
: 1488
- encrypted_payload(alice)
注意,入门节点(Carol)将使用真实的 节点 ID
,而不是盲化的节点 ID,因为发送者需要知道入门节点的位置并找出一条触达它的路径。发送者将在给 Carol 的洋葱消息 hop_payload
中发送第一个致盲临时公钥 E(Carol)
,这让 Carol 可以计算出致盲的共享秘密值,并正确传递给下一个人。我们将致盲的临时公钥放在洋葱负载中,而不是 update_add_htlc
的一个 tlv 字段中,是因为盲化路径之前的中间节点不一定支持盲化路由,那就不知道怎么继续转发。
Erin 希望发送 100000 msat 到这个盲化路径中。她通过 Dave 触达 Carol:Erin -> Dave -> Carol
,其中 Dave 和 Carol 之间的通道使用下列转发参数:
fee_base_msat
: 10fee_proportional_millionths
: 100cltv_expiry_delta
: 24
Erin 使用聚合路由参数,计算出要给 Carol 发送多少资金:
amount = 100000 + 201 + (1001 * 100000 + 1000000 - 1) / 1000000 = 100302 msat
Erin 选出一个最终的超时时间 1100,这个时间点在 Alice 的 max_cltv_expiry
之前,然后计算应该发送给 Carol 的超时时间:
expiry = 1100 + 288 = 1388
盲化路径中的节点收到一个 HTLC 的时候,洋葱消息中不会包含 amt_to_forward
和 outgoing_cltv_value
。他们必须基于属于他们的 encrypted_data
(fee_base_msat
、fee_proportional_millionths
和 cltv_expiry_delta
)计算出来。
例如,Carol 将这样计算出她要转发给 Bob 的 HTLC:
amount = ((100302 - fee_base_msat) * 1000000 + 1000000 + fee_proportional_millionths - 1) / (1000000 + fee_proportional_millionths) = 100152 msat
expiry = 1388 - cltv_expiry_delta = 1244
然后 Bob 也一样计算出转发给 Alice 的 HTLC:
amount = ((100152 - fee_base_msat) * 1000000 + 1000000 + fee_proportional_millionths - 1) / (1000000 + fee_proportional_millionths) = 100002 msat
expiry = 1244 - cltv_expiry_delta = 1100
注意,当舍入误差累加的时候,接收方将收到稍微多于自己预期的数量。发送者给接收者的洋葱消息负载中包含了 amt_to_forward
,因此他们可以验证接收到的数额(稍微)大于发送者希望发送的数额(着防止了中间节点尝试转发一个更小的数额)。
节点间交换的消息将包含下列数值:
Erin Dave Carol Bob Alice
| update_add_htlc | update_add_htlc | update_add_htlc | update_add_htlc |
| +--------------------------------+ | +------------------------------------------+ | +------------------------------------------+ | +--------------------------------+ |
| | amount: 100322 msat | | | amount: 100302 msat | | | amount: 100152 msat | | | amount: 100002 msat | |
| | expiry: 1412 | | | expiry: 1388 | | | expiry: 1244 | | | expiry: 1100 | |
| | onion_routing_packet: | | | onion_routing_packet: | | | onion_routing_packet: | | | onion_routing_packet: | |
| | +----------------------------+ | | | +--------------------------------------+ | | | +--------------------------------------+ | | | +----------------------------+ | |
| --> | | amount_fwd: 100302 msat | | --> | --> | | blinding_eph_key: E(carol) | | --> | --> | | encrypted_data: | | --> | --> | | amount_fwd: 100000 msat | | --> |
| | | outgoing_expiry: 1388 | | | | | encrypted_data: | | | | | +----------------------------------+ | | | | | outgoing_expiry: 1100 | | |
| | | scid: scid_dave_to_carol | | | | | +----------------------------------+ | | | | | | scid: scid_bob_to_alice | | | | | | encrypted_data: | | |
| | +----------------------------+ | | | | | scid: scid_carol_to_bob | | | | | | | fee_base_msat: 100 | | | | | | +-----------------------+ | | |
| | | blinding_eph_key: E(carol) | | | | | | fee_base_msat: 100 | | | | | | | fee_proportional_millionths: 500 | | | | | | | path_id: preimage | | | |
| | | encrypted_data(carol) | | | | | | fee_proportional_millionths: 500 | | | | | | | htlc_minimum_msat: 1000 | | | | | | | max_cltv_expiry: 1200 | | | |
| | +----------------------------+ | | | | | htlc_minimum_msat: 1000 | | | | | | | cltv_expiry_delta: 144 | | | | | | +-----------------------+ | | |
| | | encrypted_data(bob) | | | | | | cltv_expiry_delta: 144 | | | | | | | max_cltv_expiry: 1344 | | | | | +----------------------------+ | |
| | +----------------------------+ | | | | | max_cltv_expiry: 1488 | | | | | | +----------------------------------+ | | | | tlv_extension | |
| | | amount_fwd: 100000 msat | | | | | +----------------------------------+ | | | | +--------------------------------------+ | | | +----------------------------+ | |
| | | outgoing_expiry: 1100 | | | | +--------------------------------------+ | | | | amount_fwd: 100000 msat | | | | | blinding_eph_key: E(alice) | | |
| | | encrypted_data(alice) | | | | | encrypted_data(bob) | | | | | outgoing_expiry: 1100 | | | | +----------------------------+ | |
| | +----------------------------+ | | | +--------------------------------------+ | | | | encrypted_data(alice) | | | +--------------------------------+ |
| +--------------------------------+ | | | amount_fwd: 100000 msat | | | | +--------------------------------------+ | | |
| | | | outgoing_expiry: 1100 | | | | tlv_extension | | |
| | | | encrypted_data(alice) | | | | +--------------------------------------+ | | |
| | | +--------------------------------------+ | | | | blinding_eph_key: E(bob) | | | |
| | +------------------------------------------+ | | +--------------------------------------+ | | |
| | | +------------------------------------------+ | |
| | | | |
注意,为了简化,所有的洋葱消息负载都在各 update_add_htlc
中描述了,但只有第一条才可以被收到消息(标准的 BOLT4 洋葱加密消息)的中间节点解密。
攻击
使用支付打探解除盲化
在为支付使用盲化路径时,接收者必须谨慎,避免让攻击者猜出隐藏在路径背后的是哪些节点。我们用一个例子来说明为什么。
假设我们的路由图看起来像这样:
+-------+ +-------+
| X | | X |
+-------+ +-------+
| |
| |
+-------+ +-------+ +-------+ +-------+
| X |------| Carol |------| Bob |------| Alice |
+-------+ +-------+ +-------+ +-------+
| |
| |
+-------+ +-------+
| X | | X |
+-------+ +-------+
Alice 创建了一条盲化路径:Carol -> Bob -> Alice
。Alcie 选好了在盲化路径中使用的手续费设定。假设她选择了 fee_base_msat = 10
以及 fee_proportional_millionths = 100
。
攻击者知道接收者离 Carol 最多只有两跳。攻击者不急着发起支付,先观察以 Carol 为中心、两跳半径范围内的通道的新 channel_update(通道更新)
消息。某一时间点,攻击者看到了通道 Bob -> Alice
的 channel_update
消息,设置了 fee_proportional_millionths = 150
,这个输出超过了 Alice 为盲化路径选择的数值。然后,攻击者尝试支付。
当 Bob 收到这笔支付时,支付的手续费低于其通道当前的设定,所以 Bob 应该拒绝这笔支付。然后攻击者会受到一条表示支付出错的消息,然后就可以推断出, Alice 很有可能就是最终的接收者。
如果攻击者可以频繁向接收者请求发票(例如,通过一个 BOLT12 offer),那么他们甚至不必尝试支付,就能侦测出来。他们只需定期向接收者请求发票,看看什么时候接收者会提高手续费或改变盲化路径的 cltv,然后跟他们收到的最新的 channel_update
消息相比对,即可知晓接收者实际上在使用哪条路径。
类似地,应用在支付转发行为上的特性位(feature bits)也能用来找出盲化路径背后的节点:这就是为什么 allowed_feature
需要在 encyrpted_data
中承诺。
如果网络中的节点都为 htlc_minimum_msat
使用不同的数值,这也可以用来找出节点:这就是为什么它们要在 encrypted_data
中承诺。
这种攻击,正是接收者需要选定影响支付转发行为的所有参数(手续费、cltv、特性,等等)的原因。接收者应该在路径上的节点正在使用的数值的基础上添加一个足够大的余量,以应对这些节点提高参数的行为。这也是为什么用于支付的盲化路径需要一个由接收者设定的 max_cltv_expiry
,即使这并不能完全解决这个问题,如果攻击者可以频繁请求新的盲化路径的话。
盲化路径中的利他型转发节点可以选择转发低于自己的手续费要求的支付,这样会打破这里的线索分析:但是,他们的经济动机是聚集这些支付,所以我们不能依赖于他们来保护接收者的隐私。
类似地,我们委托转发节点仅接受使用跟 encrypted_data
内容完全相同的手续费设定的支付。不然,在观察到一个提高了某通道的手续费率的 channel_update
消息之后,攻击者可以尝试用新的手续费设置尝试支付:要是支付跑通了,他们就能更加确定盲化路径所用的通道。给转发节点的经济激励不是很大,因为我们是在要求他们拒绝给予他们正确手续费数额的支付,以保护接收者的隐私。
在重启之后揭开盲化
上一节介绍的攻击仅能用于接收者使用盲化路径接收支付的情景。但是,相同的技术的一种变种可以用来攻击任何依赖于盲化路径以转发消息的情景。
如果攻击者怀疑某个节点 N
是某一条盲化路径的一份子,他们可以等待这个节点离线,然后尝试使用使用那条盲化路径。如果路由失败,则有可能这个节点是那条盲化路径的一环。定期重复这种取样,攻击者就可以提升准确度。
为了解决这个问题,接收者应该为自己的盲化路径选择高在线时间的节点,并定期重置路径。
提示和技巧
接收者支付手续费
让支付者支付更多费用来满足接收者的匿名意愿,可能是不工牌的。应该由接收者为盲化环节支付手续费才对(而支付者仅仅为触达入口节点的路径支付手续费)。
如果某个商家售出了价值 N
聪的东西,TA 应该创建一个价值 N-f
聪的发票,其中 f
是为路径的盲化部分支付的手续费。
空跳
发送者知道接收者和 N(0)
之间的距离的上限。如果接收者离 N(0)
很近,这可能会导致问题。所以在这种情况下,接收者可以在盲化路径的末端、令 N(j) = N(r)
、来添加任意多数量的空跳。发送者将无法分辨这些空跳与正常的盲化跳。
NB:
- 接收者需要验证每一个空跳的洋葱消息负载以检测伪造(而且必须保证这些空跳都被使用了、没有被截断)
- 接收者必须使用填充技巧,以保证所有的
encrypted_data
消息负载具有相同的长度,否则支付者就可以猜出那一跳是真正的接收者
钱包和未公开的通道
盲化路径对于通过未公开的通道连接节点的钱包来说特别有用。这样的钱包可以使用一个单跳的盲化路由,实际上就是对发送者隐藏自己的 node_id
和 sicd
。当然,这会让盲化节点(也是入口节点)知道下一个节点就是最终的接收者,但具备稳定的 IP、又不是全时段在线的钱包,本来就无法对自己连入的节点隐藏这些信息(即使使用约会路由也不行)。
盲化路径的挑选
接收者在创建盲化路径时,有许多策略可以保证良好的隐私性,同时维持良好的支付可靠性。我们会在下文介绍一些这样的策略。注意,它们只是例子,实现应该找出能满足用户需要的策略。
如果接收者不是公开的节点,而且有不止一个对等节点,那事情就简单了:TA 可以为每个对等节点创建一个盲化路径。举个例子,一个 手机钱包的拓扑图一般来说是这样的:
+-------+ +-------+
+----------| Carol | | X |
| +-------+ +-------+
| | |
| | |
+-------+ +-------+ +-------+ +-------+
| Alice |------| Bob |------| X |------| X |
+-------+ +-------+ +-------+ +-------+
| |
| |
| +-------+
+-------------------------| Dave |
+-------+
Alice 可以为每一个对等节点创建一条包含空跳的盲化路径:
- Bob -> Blinded(Alice) -> Blinded(Alice) -> Blinded(Alice)
- Carol -> Blinded(Alice) -> Blinded(Alice) -> Blinded(Alice)
- Dave -> Blinded(Alice) -> Blinded(Alice) -> Blinded(Alice)
Alice 可以使用自己所有的入账流动性,同时从更大的匿名集中获益:使用这些路径的可能是 Bob、Carol 和 Dave 各自三跳距离内的任意节点。
如果接收者是一个公开节点,策略就要有所不同。TA 应该使用拥有许多对等节点的的节点作为入口,以获得良好的匿名集。假设 Alice 的邻居的拓扑图是这样的:
+-------+ +-------+
| X | | X |
+-------+ +-------+
| |
| |
+-------+ +-------+ +-------+
| N1 |------| N2 |------| X |
+-------+ +-------+ +-------+
| | |
| | |
+-------+ +-------+ +-------+ +-------+
| Alice |------| N3 |------| N4 |------| X |
+-------+ +-------+ +-------+ +-------+
Alice 可以运行一次深度为 2 的 BFS,确定 N2 和 N3 是良好的入门节点:他们可以提供较大的匿名集。她可以提供下列的盲化路径:
- N2 -> Blinded(N1) -> Blinded(Alice) -> lBlinded(Alice)
- N4 -> Blinded(N3) -> Blinded(Alice) -> Blinded(Alice)
Alice 应该分析她的匿名集中的所有通道的支付转发参数,并选出兼容尽可能多的节点的 手续费率/cltv。
注意,Alice 选择了不重复的路径:不然的话,这些路径可能没有足够多的流动性来转发她的支付,除非这些路径的容量比她预期的收款数额要大得多。
当接收者希望接收大额支付时,流动性可能会变成一个问题:它们可能过于分散。接收者可能不得不使用直接对等节点作为入门节点,以保证可以获得足够多的流动性(这种时候,在盲化路径中包含空跳是尤其有用的)。
盲化蹦床路由
路由盲化也很容易可以跟蹦床路由结合。我们不必在 encrypted_data
中提供 outgoing_channel_id
,只需提供 outgoing_node_id
。
现在,每个蹦床节点可以解密下一个节点的 node_id
,然后为下一个蹦床节点计算 E(i)
。然后这个 E(i)
可以在发出的洋葱消息负载中包含,而不必使用闪电消息的字段,这样更清楚,而且不需要蹦床节点之间的节点理解盲化路径。
使用盲化的蹦床路由是拥有许多对等节点、但因为流动性问题而影响支付可靠性的公开节点的好帮手。这样的接收者可以选择能够找出许多触达自身的路径的蹦床节点:
+-------+ +-------+
+----------| X |--------+ +--------| X |----------+
| +-------+ | | +-------+ |
| | | |
| | | |
+-------+ +-------+ +-------+ +-------+ +-------+
| T1 |------| X |------| Alice |------| X |------| T2 |
+-------+ +-------+ +-------+ +-------+ +-------+
| | | |
| | | |
| +-------+ | | +-------+ |
+----------| X |--------+ +--------| X |----------+
+-------+ +-------+
Alice 可以提供下列的盲化蹦床路径:
- T1 -> Blinded(Alice)
- T2 -> Blinded(Alice)
T1 和 T2 可以找出许多触达 Alice 的路径,而且可以在一些路径失败之后继续尝试。
常见问题和解答
为什么不使用约会路由?
虽然约会路由更加隐私,但它也不那么灵活:发送者无法在部分洋葱消息中添加数据,也无法重复使用这些消息。在用于支付时,这部分洋葱消息中的数额必须提前固定,所以难以跟多路径支付相结合,难以处理临时的流动性问题。
盲化路径让发送者可以选择放在洋葱消息负载中的大部分数据,所以灵活得多,牺牲是会产生更多的打探手法。
为什么不使用 HORNET
HORNET 在提供有意义的加速效果之前,需要一段较慢的会话启动流程。在你希望为每个会话发送一条消息(支付和洋葱消息都是这种情形)时,HORNET 的实际表现比 Sphinx 在时延、带宽和隐私性上都更差。
(完)