本文档描述了v0版本的 Oninon 路由协议,该协议构建了一种 Onion 路由数据包,用于将付款从起始节点路由到最终节点,数据包通过多个中间节点(称为跃点)进行路由。消息在每个Hop上都会混淆,以确保网络级别的攻击者无法将属于同一路由的数据包关联起来。该路由由始发节点构建,该节点知道每个中间节点和最终节点的公钥,并使用 ECDH 算法为每个中间节点和最终节点创建共享密钥。
本文档描述了洋葱路由数据包的构建,该数据包用于将付款从源节点路由到最终节点。该数据包通过多个中间节点进行路由,这些节点称为跳(hop)。
路由方案基于 [Sphinx][sphinx] 结构,并扩展了每个跳的 payload。
转发消息的中间节点可以验证数据包的完整性,并且可以了解应将数据包转发到哪个节点。他们无法了解除了其前任或继任者之外的其他节点是否是数据包路由的一部分;他们也无法了解路由的长度或其在其中的位置。数据包在每个跳都被混淆,以确保网络级别的攻击者无法将属于同一路由的数据包关联起来(即,属于同一路由的数据包不共享任何相关信息)。请注意,这并不排除攻击者通过流量分析关联数据包的可能性。
路由由源节点构建,该节点知道每个中间节点和最终节点的公钥。知道每个节点的公钥允许源节点为每个中间节点和最终节点创建共享密钥(使用 ECDH)。然后,共享密钥用于生成字节的伪随机流(用于混淆数据包)和多个密钥(用于加密 payload 和计算 HMAC)。然后,HMAC 又用于确保每个跳中数据包的完整性。
路由上的每个跳仅看到源节点的临时密钥,以隐藏发送者的身份。临时密钥在转发到下一个节点之前由每个中间跳进行盲化,从而使洋葱在路由上不可链接。
本规范描述了数据包格式和路由机制的版本 0。
节点:
max_htlc_cltv
选择本文档中遵守了许多约定:
SHA256
哈希算法。secp256k1
][sec2] 中所指定。ChaCha20
][rfc8439] 用于生成伪随机字节流。对于其生成,使用固定的 96 位 null-nonce (0x000000000000000000000000
),以及从共享密钥派生的密钥和具有所需输出大小的 0x00
字节流作为消息。hop_payload
。
hop_payload
前缀为 bigsize
,用于以字节为单位编码长度,不包括前缀和尾随 HMAC。许多加密和验证密钥是从共享密钥派生的:
密钥生成函数采用密钥类型(rho=0x72686F
,mu=0x6d75
,
um=0x756d
或 pad=0x706164
)和 32 字节的密钥作为输入,并返回 32 字节的密钥。
通过计算 HMAC(使用 SHA256
作为哈希算法)来生成密钥,使用适当的密钥类型(即 rho、mu、um 或 pad)作为 HMAC 密钥,并使用 32 字节的共享密钥作为消息。然后,将生成的 HMAC 作为密钥返回。
请注意,密钥类型不包括 C 样式的 0x00
终止字节,例如 rho 密钥类型的长度为 3 个字节,而不是 4 个字节。
伪随机字节流用于混淆路径的每个跳中的数据包,以便每个跳只能恢复下一个跳的地址和 HMAC。伪随机字节流是通过加密(使用 ChaCha20
)0x00
字节流生成的,该字节流具有所需的长度,并使用从共享密钥派生的密钥和 96 位零 nonce (0x000000000000000000000000
) 进行初始化。
使用固定 nonce 是安全的,因为密钥永远不会被重复使用。
数据包由四个部分组成:
version
字节secp256k1
public_key
,用于在共享密钥生成期间使用hop_payloads
,由多个可变长度的 hop_payload
payload 组成hmac
,用于验证数据包的完整性数据包的网络格式包括将各个部分序列化为一个连续的字节流,然后将其传输给数据包接收者。由于数据包的大小固定,因此在连接上传输时无需在其前面加上其长度。
数据包的总体结构如下:
1. 类型:`onion_packet`
2. 数据:
* [`byte`:`version`]
* [`point`:`public_key`]
* [`1300*byte`:`hop_payloads`]
* [`32*byte`:`hmac`]
对于此规范(version 0),version
具有常量值 0x00
。
hop_payloads
字段是一个结构,用于保存混淆的路由信息以及关联的 HMAC。
它长 1300 个字节,并且具有以下结构:
1. 类型:`hop_payloads`
2. 数据:
* [`bigsize`:`length`]
* [`length*byte`:`payload`]
* [`32*byte`:`hmac`]
* ...
* `filler`
其中,length
、payload
和 hmac
对于每个跳重复;并且其中 filler
由混淆的、确定性生成的填充组成,如 填充生成 中所述。
此外,hop_payloads
在每个跳中都会逐渐混淆。
使用 payload
字段,源节点可以指定每个跳中转发的 HTLC 的路径和结构。
由于 payload
受数据包范围的 HMAC 保护,因此它包含的信息通过 HTLC 发送者(源节点)与路径中的每个跳之间的每个成对关系进行完全身份验证。
使用这种端到端身份验证,每个跳都可以将 HTLC 参数与 payload
的指定值进行交叉检查,并确保发送对等节点没有转发错误制作的 HTLC。
由于没有任何 payload
TLV 值可以短于 2 个字节,因此保留了长度值为 0 和 1。(0
表示不再支持的旧格式,1
保留供将来使用)。
payload
格式这根据 BOLT #1 中定义的类型-长度-值格式进行格式化。
1. `tlv_stream`: `payload`
2. 类型:
1. 类型: 2 (`amt_to_forward`)
2. 数据:
* [`tu64`:`amt_to_forward`]
1. 类型: 4 (`outgoing_cltv_value`)
2. 数据:
* [`tu32`:`outgoing_cltv_value`]
1. 类型: 6 (`short_channel_id`)
2. 数据:
* [`short_channel_id`:`short_channel_id`]
1. 类型: 8 (`payment_data`)
2. 数据:
* [`32*byte`:`payment_secret`]
* [`tu64`:`total_msat`]
1. 类型: 10 (`encrypted_recipient_data`)
2. 数据:
* [`...*byte`:`encrypted_recipient_data`]
1. 类型: 12 (`current_path_key`)
2. 数据:
* [`point`:`path_key`]
1. 类型: 16 (`payment_metadata`)
2. 数据:
* [`...*byte`:`payment_metadata`]
1. 类型: 18 (`total_amount_msat`)
2. 数据:
* [`tu64`:`total_msat`]
short_channel_id
是用于路由消息的出站通道的 ID;接收对等节点应在该通道的另一端操作。
amt_to_forward
是要转发到路由信息中指定的下一个接收对等节点或最终目的地的金额,以毫聪(millisatoshi)为单位。
对于非最终节点,这包括源节点为接收对等节点计算的费用,该费用根据接收对等节点公布的费用方案计算(如 BOLT #7 中所述)。
outgoing_cltv_value
是携带数据包的出站 HTLC 应具有的 CLTV 值。
包含此字段允许跳验证源节点指定的信息以及转发的 HTLC 的参数,并确保源节点使用当前的 cltv_expiry_delta
值。
如果这些值不对应,则表明转发节点已篡改了预期的 HTLC 值,或者源节点具有过时的 cltv_expiry_delta
值。
这些要求确保了在响应意外的 outgoing_cltv_value
时的一致性,无论它是最终节点还是非最终节点,以避免泄漏其在路由中的位置。
encrypted_recipient_data
的创建者(通常,付款的接收者):
encrypted_data_tlv
。encrypted_data_tlv.payment_relay
。encrypted_data_tlv.short_channel_id
或 encrypted_data_tlv.next_node_id
。encrypted_data_tlv.payment_constraints
,并且可以为最终节点设置:
max_cltv_expiry
设置为允许使用该路由的最大区块高度,从最终节点选择的该路由应过期的 max_cltv_expiry
高度开始,加上最终节点的 min_final_cltv_expiry_delta
,然后加上每个跳的 encrypted_data_tlv.payment_relay.cltv_expiry_delta
。htlc_minimum_msat
设置为节点将允许的最大最小 HTLC 值。encrypted_data_tlv.allowed_features
:
total_fee_base_msat(n+1) = (fee_base_msat(n+1) * 1000000 + total_fee_base_msat(n) * (1000000 + fee_proportional_millionths(n+1)) + 1000000 - 1) / 1000000
total_fee_proportional_millionths(n+1) = ((total_fee_proportional_millionths(n) + fee_proportional_millionths(n+1)) * 1000000 + total_fee_proportional_millionths(n) * fee_proportional_millionths(n+1) + 1000000 - 1) / 1000000
total_cltv_delta = cltv_delta(0) + cltv_delta(1) + ... + cltv_delta(n) + min_final_cltv_expiry_delta
encrypted_data_tlv
创建 encrypted_recipient_data
。TLV payload
的写入者:
encrypted_recipient_data
current_path_key
中包含接收者提供的 path_key
amt_to_forward
、outgoing_cltv_value
和 total_amount_msat
。outgoing_cltv_value
设置的值:amt_to_forward
和 outgoing_cltv_value
。short_channel_id
payment_data
short_channel_id
payment_secret
:payment_data
payment_secret
设置为提供的那个total_msat
设置为将要发送的总金额payment_metadata
:payment_metadata
payment_metadata
的大小施加任何限制读取者:
encrypted_recipient_data
:
update_add_htlc
中设置了 path_key
:
current_path_key
,则必须返回错误。path_key
用作解密的 path_key
。current_path_key
不存在,则必须返回错误。current_path_key
用作解密的 path_key
。encrypted_recipient_data
未使用 path_key
(如 路由盲化 中所述)解密,则必须返回错误。payment_constraints
:
encrypted_recipient_data.payment_constraints.max_cltv_expiry
。encrypted_recipient_data.payment_constraints.htlc_minimum_msat
。allowed_features
:
encrypted_recipient_data.allowed_features.features
包含未知的 feature bit(即使它是奇数)。encrypted_recipient_data
同时包含 short_channel_id
和 next_node_id
。encrypted_recipient_data.allowed_features.features
中未包含的 feature。encrypted_recipient_data
和 current_path_key
以外的其他 tlv 字段,则必须返回错误。encrypted_recipient_data
不包含 short_channel_id
或 next_node_id
中的任何一个,则必须返回错误。encrypted_recipient_data
不包含 payment_relay
,则必须返回错误。encrypted_recipient_data.payment_relay
中的值来计算 amt_to_forward
和 outgoing_cltv_value
,如下所示:amt_to_forward = ((amount_msat - fee_base_msat) * 1000000 + 1000000 + fee_proportional_millionths - 1) / (1000000 + fee_proportional_millionths)
outgoing_cltv_value = cltv_expiry - payment_relay.cltv_expiry_delta
encrypted_recipient_data
、current_path_key
、amt_to_forward
、outgoing_cltv_value
和 total_amount_msat
以外的其他 tlv 字段,则必须返回错误。amt_to_forward
、outgoing_cltv_value
或 total_amount_msat
不存在,则必须返回错误。amt_to_forward
低于它期望的付款金额,则必须返回错误。cltv_expiry
< outgoing_cltv_value
,则必须返回错误。cltv_expiry
< current_block_height
+ min_final_cltv_expiry_delta
,则必须返回错误。update_add_htlc
中设置了 path_key
或存在 current_path_key
,则必须返回错误。amt_to_forward
或 outgoing_cltv_value
不存在,则必须返回错误。short_channel_id
不存在,short_channel_id
指示的对等节点。amount_msat
- fee
< amt_to_forward
(其中 fee
是 BOLT #7 中描述的公布费用)cltv_expiry
- cltv_expiry_delta
< outgoing_cltv_value
total_msat
不存在,则必须将 total_msat
视为等于 amt_to_forward
。amount_msat
< amt_to_forward
。cltv_expiry
< outgoing_cltv_value
。cltv_expiry
< current_block_height
+ min_final_cltv_expiry_delta
。有关多部分付款的其他要求在此处指定。
HTLC 可能是更大的“多部分”付款的一部分:此类“基本”原子多路径付款将对所有路径使用相同的 payment_hash
。
请注意,amt_to_forward
仅是此 HTLC 的金额:包含更大值的 total_msat
字段是最终发送者承诺其余付款将在后续 HTLC 中进行的承诺;我们将这些具有相同 preimage 的未完成 HTLC 称为“HTLC 集”。
请注意,有两个不同的 tlv 字段可用于传输 total_msat
。最后一个 total_amount_msat
是随着盲化路径引入的,对于盲化路径来说,payment_secret
没有意义。
payment_metadata
将包含在每个付款部分中,以便可以尽早检测到无效的付款详细信息。
写者:
basic_mpp
feature:
payment_hash
。amount
:
total_msat
设置为至少该 amount
,且小于或等于 amount
的两倍。total_msat
设置为其希望支付的金额。amt_to_forward
等于或大于 total_msat
。amt_to_forward
已经大于或等于 total_msat
,则不得发送另一个 HTLC。payment_secret
。total_msat
设置为等于 amt_to_forward
。最终节点:
total_msat
字段。basic_mpp
:
total_msat
不完全等于 amt_to_forward
,则必须使 HTLC 失败。basic_mpp
:
payment_hash
对应的 HTLC 集合中。total_msat
对于集合中的所有 HTLC 都不相同,则应该使整个 HTLC 集合失败。amt_to_forward
等于或大于 total_msat
:
amt_to_forward
小于 total_msat
:
mpp_timeout
。payment_secret
。如果存在 basic_mpp
,则会导致延迟,以允许其他部分付款组合。总金额必须足以支付所需的付款,就像对于单笔付款一样。但是,这必须合理地限制,以避免拒绝服务。
因为 invoice 不一定指定金额,并且付款人可以向最终金额添加噪声,所以必须明确发送总金额。这些要求允许略微超出此范围,因为它简化了在拆分时向金额添加噪声,以及发送者真正独立的情况(例如,朋友分摊账单)。
因为一个节点可能需要支付超过其所需金额(由于所需路径中通道的 htlc_minimum_msat
值所致),因此允许节点支付超过其指定的 total_msat
。否则,节点在沿着特定路径重试付款时将受到可以采取的路径的约束。但是,任何单个 HTLC 的金额都不得小于已付总额与 total_msat
之间的差额。
一旦集合超过约定的总金额,就限制发送 HTLC,这可以防止在所有部分付款都到达之前发布 preimage:这将允许任何中间节点立即认领任何未完成的部分付款。
实现可以选择不履行其他方面满足金额标准的 HTLC 集合(例如,某些其他失败或 invoice 超时),但是如果它仅履行其中一些 HTLC,则中间节点可以简单地认领剩余的部分。
1. 子类型: `blinded_path`
2. 数据:
* [`sciddir_or_pubkey`:`first_node_id`]
* [`point`:`first_path_key`]
* [`byte`:`num_hops`]
* [`num_hops*blinded_path_hop`:`path`]
1. 子类型: `blinded_path_hop`
2. 数据:
* [`point`:`blinded_node_id`]
* [`u16`:`enclen`]
* [`enclen*byte`:`encrypted_recipient_data`]
盲化路径包括:
first_node_id
)first_path_key
) 共享密钥的初始密钥path.blinded_node_id
)path.encrypted_recipient_data
)
告诉他们下一个跳。例如,Dave 希望 Alice 通过公共节点 Bob,然后是 Carol 联系他。他创建了一个 Bob、Carol 最终是他自己的公钥链(“path_keys”),以便他可以与他们每个人共享一个秘密。这些密钥是一个简单的链,因此每个节点都可以派生出下一个 path_key
,而无需明确告知。
从这些共享秘密中,Dave 创建并加密了三个 encrypted_data_tlv
:
为了掩盖节点 ID,他还从共享秘密中派生出三个致盲因子,这些致盲因子将 Bob 变成 Bob',Carol 变成 Carol',Dave 变成 Dave'。
所以这是他交给 Alice 的 blinded_path
。
first_node_id
:Bobfirst_path_key
:Bob 的第一个 path keypath
:[Bob',encrypted_data_bob],[Carol',encrypted_data_carol],[Dave',encrypted_data_dave]Alice 有两种不同的方式来构建一个到达 Bob 的 onion(因为他可能不是她的直接对等节点),这两种方式在下面的要求中描述。
但是在 Bob 之后,路径始终相同:他会将他派生的 path_key
连同 onion 一起发送给 Carol。她将使用 path_key
来派生 onion 的调整(Alice 为 Carol' 而不是 Carol 加密),以便她可以解密它,并且还可以派生密钥来解密 encrypted_data_tlv
,这将告诉她转发给 Dave(以及 Dave 指定的可能附加限制)。
请注意,盲化路径的创建者(即接收者)正在为发送者创建它以用来创建一个 onion,并为中间节点读取指令,因此这里有两个读取者部分。
blinded_path
的写入者:
N_r
$),即 $N_0 \rightarrow N_1 \rightarrow ... \rightarrow N_r
$。first_node_id
设置为 $N_0
$e_0 \leftarrow \{0;1\}^{256}
$($e_0
$ 应该通过 CSPRNG 获得)E_0 = e_0 \cdot G
$N_i = k_i * G
$ 为 node_id
($k_i
$ 是 $N_i
$ 的私钥)ss_i = SHA256(e_i * N_i) = SHA256(k_i * E_i)
$(只有 $N_r
$ 和 $N_i
$ 知道的 ECDH 共享秘密)rho_i = HMAC256(\text{"rho"}, ss_i)
$($N_r
$ 用于加密 $N_i
$ 的 encrypted_recipient_data
的密钥)e_{i+1} = SHA256(E_i || ss_i) * e_i
$(只有 $N_r
$ 知道的临时私有路径密钥)E_{i+1} = SHA256(E_i || ss_i) * E_i
$ (path_key
。注意:$N_i
$ 不得学习 $e_i
$)first_path_key
设置为 $E_0
$B_i
$:
B_i = HMAC256(\text{"blinded\_node\_id"}, ss_i) * N_i
$($N_i
$ 的盲化 node_id
,只有 $N_i
$ 知道私钥)path
中每个 blinded_path_hop
的 blinded_node_id
设置为 $B_i
$E_{i+1}
$,但是如果这样做:
encrypted_data_tlv[i].next_path_key_override
设置为 $E_{i+1}
$encrypted_data_tlv[r].path_id
中存储私有数据,以验证该路由是否在正确的上下文中使用并由他们创建encrypted_data_tlv[i]
具有相同的长度rho_i
$ 密钥和一个全零 nonce 加密每个 encrypted_data_tlv[i]
,以产生 encrypted_recipient_data[i]
blinded_path
的读取者:
first_node_id
path
中每个 onion payload 中包含相应的 encrypted_recipient_data
path
中的第一个条目:
first_node_id
,并将 first_path_key
作为 current_path_key
包含在内。blinded_node_id
加密。next_path_key_override
设置为 first_path_key
。path
中的每个后续条目:
blinded_node_id
。encrypted_recipient_data
的读取者:
ss_i = SHA256(k_i * E_i)
$ (标准 ECDH)b_i = HMAC256(\text{"blinded\_node\_id"}, ss_i) * k_i
$rho_i = HMAC256(\text{"rho"}, ss_i)
$rho_i
$ 作为密钥解密 encrypted_recipient_data
字段。encrypted_recipient_data
字段缺失、无法解密为 encrypted_data_tlv
或包含未知的 偶数 字段:
encrypted_data_tlv
包含 next_path_key_override
:
path_key
。E_{i+1} = SHA256(E_i || ss_i) * E_i
$ 作为下一个 path_key
path_key
,发给下一个节点path_id
与其为此目的创建的 盲化路由 不匹配,则必须忽略该消息路由 盲化 是一种提供接收者匿名性的轻量级技术。它比 会合路由 更灵活,因为它只是用随机公钥替换路由中节点的公钥,同时允许发送者选择他们放入每个跳的 onion 中的数据。盲化路由 在某些情况下也是可重用的(例如 onion 消息)。
盲化路由 中的每个节点都需要接收 $E_i
$ 才能解密 onion 和 encrypted_recipient_data
载荷。
当连接由不同节点生成的两个 盲化路由 时,第一个路由的最后一个节点需要知道第二个路由的第一个 path_key
:必须使用 next_path_key_override
字段来传输此信息。从理论上讲,此方法可用于支付(不仅仅是 onion 消息),但我们建议使用非 盲化 路径来连接到 first_node_id
并在那里使用 current_path_key
:这意味着该节点可以判断它是否被用作 引入点,但也无需节点支持 盲化 路径即可到达该点,并给出关于支付的非 盲化 部分的有意义的错误。
最终接收者必须验证 盲化路由 是否在正确的上下文中使用(例如,对于特定支付)并且是由他们创建的。否则,恶意发送者可以创建不同的 盲化路由 到他们怀疑可能是真正接收者的所有节点,并尝试它们,直到其中一个接受该消息。接收者可以通过存储 $E_r
$ 和上下文(例如 payment_hash
)来防止这种情况,并在收到 onion 时验证它们是否匹配。否则,为了避免额外的存储成本,它可以将一些私有上下文信息放入 path_id
字段(例如 payment_preimage
)中并在收到 onion 时验证它。请注意,在这种情况下,使用私有信息非常重要,发送者无法访问这些信息。
每当 引入点 收到来自 盲化路由 的失败时,它应该在转发错误之前添加一个随机延迟。失败很可能是 探测 尝试,消息计时可能有助于攻击者推断其与最终接收者的距离。
可以使用 padding
字段来确保所有 encrypted_recipient_data
具有相同的长度。当在 盲化路由 的末尾添加 假 跳时,这尤其有用,以防止发送者确定哪个节点是最终接收者。
当路由 盲化 用于支付时,接收者指定 盲化节点 应该应用于支付的 费用 和 过期时间,而不是让发送者配置它们。接收者还在可以通过该路由的支付中添加额外的 约束,以防止 探测 攻击,这些攻击会让恶意节点 去盲化 盲化节点 的身份。它应该设置 payment_constraints.max_cltv_expiry
以限制 盲化路由 的生命周期,并降低 中间节点 更新其 费用 并拒绝支付的风险(这可以用于 盲化 路由内的节点)。
encrypted_recipient_data
内部:encrypted_data_tlv
encrypted_recipient_data
是一个 TLV 流,为给定的 盲化节点 加密,该流可能包含以下 TLV 字段:
tlv_stream
: encrypted_data_tlv
padding
)...*byte
:padding
]short_channel_id
)short_channel_id
:short_channel_id
]next_node_id
)point
:node_id
]path_id
)...*byte
:data
]next_path_key_override
)point
:path_key
]payment_relay
)u16
:cltv_expiry_delta
]u32
:fee_proportional_millionths
]tu32
:fee_base_msat
]payment_constraints
)u32
:max_cltv_expiry
]tu64
:htlc_minimum_msat
]allowed_features
)...*byte
:features
]加密的接收者数据由最终接收者创建并提供给发送者,其中包含有关节点如何处理消息的指令(它也可以由发送者自己创建:转发节点无法分辨)。它用于支付 onion 和 onion 消息 onion 中。请参阅 路由 盲化。
一旦节点解码了 载荷,它要么在本地接受付款,要么将其转发到 载荷 中指示的对等方作为下一跳。
节点 可以 沿着除 short_channel_id
指定的 传出通道 之外的通道转发 HTLC,只要接收者具有 short_channel_id
预期的相同 节点公钥 即可。因此,如果 short_channel_id
连接节点 A 和 B,则 HTLC 可以跨连接 A 和 B 的任何通道转发。不遵守将导致接收者无法解密 onion 数据包中的下一跳。
如果两个对等方具有多个通道,则下游节点将能够解密下一跳 载荷,而不管数据包通过哪个通道发送。
实施非严格转发的节点能够实时评估与特定对等方的 通道带宽,并使用在本地最佳的通道。
例如,如果在转发时连接 A 和 B 的 short_channel_id
指定的通道没有足够的带宽,则 A 能够使用不同的通道。这可以通过防止 HTLC 由于 short_channel_id
上的带宽限制而失败来减少付款延迟,而发送者只会尝试相同的路由,而仅在 A 和 B 之间的通道中有所不同。
非严格转发允许节点利用连接到接收节点的 私有通道,即使该通道在 公共通道图 中未知。
使用非严格转发的实施应考虑对具有相同对等方的所有通道应用相同的 费用安排,因为发送者可能会选择导致总体成本最低的通道。拥有不同的策略可能会导致转发节点根据发送者最理想的 费用安排 接受费用,即使它们在与同一对等方的所有通道上提供聚合带宽。
或者,实施可以选择仅对类似策略的通道应用非严格转发,以确保他们预期的 费用收入 不会因使用备用通道而偏离。
在构建路由时,源节点必须使用以下值的最终节点的 载荷:
payment_secret
: 设置为接收者指定的 支付密钥(例如,来自 BOLT #11 支付 发票 的 payment_secret
)outgoing_cltv_value
: 设置为接收者指定的最终 过期时间(例如,来自 BOLT #11 支付 发票 的 min_final_cltv_expiry_delta
)amt_to_forward
: 设置为接收者指定的最终金额(例如,来自 BOLT #11 支付 发票 的 amount
)这允许最终节点检查这些值并在需要时返回错误,但它也消除了倒数第二个节点进行 探测 攻击的可能性。否则,此类攻击可能会尝试通过重新发送具有不同金额/ 过期时间 的 HTLC 来发现接收对等点是否是最后一个。最终节点将从其收到的 HTLC 中提取其 onion 载荷,并将其值与 HTLC 的值进行比较。 有关更多详细信息,请参见下面的返回错误部分。
如果不是为了以上原因,由于它不需要转发付款,因此最终节点可以简单地丢弃其 载荷。
源节点使用椭圆曲线 Diffie-Hellman 在该跳的发送者的 临时密钥 和跳的 节点ID密钥 之间建立与路由上每个跳的共享密钥。生成的 曲线点 将被序列化为 压缩格式,并使用 SHA256
进行 哈希处理。 哈希输出 用作 32字节 共享密钥。
椭圆曲线 Diffie-Hellman (ECDH) 是 EC 私钥 和 EC 公钥 上的运算,输出一个 曲线点。对于此协议,使用在 libsecp256k1
中实现的 ECDH 变体,该变体在 secp256k1
椭圆曲线上定义。在数据包构建期间,发送者使用 临时私钥 和跳的 公钥 作为 ECDH 的输入,而在数据包转发期间,跳使用 临时公钥 和其自己的 节点ID私钥。由于 ECDH 的属性,它们都将得出相同的值。
为了确保沿路由的多个跳无法通过他们看到的 临时公钥 链接,密钥在每个跳上都是 盲化 的。 盲化 以一种确定性的方式完成,该方式允许发送者在数据包构建期间计算相应的 盲化私钥。
EC 公钥 的 盲化 是代表 公钥 的 EC 点与 32字节盲化因子 的单个 标量乘法。由于 标量乘法 的 交换律,盲化私钥 是输入对应的 私钥 与相同 盲化因子 的 乘积。
盲化因子 本身被计算为 临时公钥 和 32字节共享密钥 的函数。具体来说,它是以 压缩格式 序列化的 公钥 与 共享密钥 的连接的 SHA256
哈希值。
在以下示例中,假设 发送节点(源节点)n_0
想要将数据包路由到 接收节点(最终节点)n_r
。首先,发送者计算路由 {n_0, n_1, ..., n_{r-1}, n_r}
,其中 n_0
是发送者本身,n_r
是最终接收者。 所有节点 n_i
和 n_{i+1}
必须是 覆盖网络路由 中的对等点。然后,发送者收集 n_1
到 n_r
的 公钥 并生成一个随机的 32字节 sessionkey
。 可选地,发送者可以传入 关联数据,即数据包承诺但未包含在数据包本身中的数据。 关联数据将包含在 HMAC 中,并且必须与每个跳在 完整性验证 期间提供的 关联数据 匹配。
为了构建 onion,发送者将第一个跳的 临时私钥 ek_1
初始化为 sessionkey
,并通过与 secp256k1
基点 相乘从中导出相应的 临时公钥 epk_1
。 对于路由上的每个 k
跳,发送者然后迭代计算下一个跳的 共享密钥 ss_k
和 临时密钥 ek_{k+1}
,如下所示:
SHA256
进行 哈希处理 以生成 共享密钥 ss_k
。epk_k
和 共享密钥 ss_k
之间连接的 SHA256
哈希。ek_k
乘以 盲化因子 来计算下一个跳的 临时私钥 ek_{k+1}
。ek_{k+1}
导出下一个跳的 临时公钥 epk_{k+1}
。一旦发送者拥有上述所有必需的信息,它就可以构造数据包。 构建在 r
跳上路由的数据包需要 r
32字节临时公钥、r
32字节共享密钥、r
32字节盲化因子 和 r
可变长度 hop_payload
载荷。 构造返回一个 1366字节 的数据包以及第一个接收对等方的地址。
数据包构造以与路由相反的顺序执行,即首先应用最后一个跳的操作。
数据包使用从 CSPRNG (ChaCha20) 派生的 1300 个 随机 字节进行初始化。 上面引用的 pad密钥 用于从 ChaCha20 流中提取额外的 随机 字节,将其用作此目的的 CSPRNG。 一旦获得 paddingKey
,ChaCha20 就会与 全零nonce 一起使用,以生成 1300个随机字节。 这些 随机 字节然后用作要创建的 混合标头 的起始状态。
使用 共享密钥 生成 填充符(请参阅 填充符生成)。
对于路由中的每个跳,按相反的顺序,发送者应用以下操作:
shift_size
定义为 hop_payload
的长度加上长度的 bigsize编码 和该 HMAC 的长度。 因此,如果 载荷 长度为 l
,则 shift_size
为 1 + l + 32
(对于 l < 253
),否则为 3 + l + 32
,因为 l
的 bigsize编码。hop_payload
字段向右移动 shift_size
字节,丢弃超过其 1300字节 大小的最后 shift_size
字节。hop_payload
和 hmac
将复制到以下 shift_size
字节中。XOR
一起应用于 hop_payloads
字段。hop_payloads
字段的尾部将被路由信息 filler
覆盖。hop_payloads
和关联数据上计算下一个 HMAC(使用 mu-key 作为 HMAC 密钥)。生成的最终 HMAC 值将是由路由中的第一个接收对等方使用的 HMAC。
数据包生成返回一个序列化的数据包,该数据包包含 version
字节、第一个跳的 临时公钥、第一个跳的 HMAC 和 混淆的 hop_payloads
。
以下 Go 代码是数据包构造的示例实现:
func NewOnionPacket(paymentPath []*btcec.PublicKey, sessionKey *btcec.PrivateKey,
hopsData []HopData, assocData []byte) (*OnionPacket, error) {
numHops := len(paymentPath)
hopSharedSecrets := make([][sha256.Size]byte, numHops)
// Initialize ephemeral key for the first hop to the session key.
var ephemeralKey big.Int
ephemeralKey.Set(sessionKey.D)
for i := 0; i < numHops; i++ {
// Perform ECDH and hash the result.
ecdhResult := scalarMult(paymentPath[i], ephemeralKey)
hopSharedSecrets[i] = sha256.Sum256(ecdhResult.SerializeCompressed())
// Derive ephemeral public key from private key.
ephemeralPrivKey := btcec.PrivKeyFromBytes(btcec.S256(), ephemeralKey.Bytes())
ephemeralPubKey := ephemeralPrivKey.PubKey()
// Compute blinding factor.
sha := sha256.New()
sha.Write(ephemeralPubKey.SerializeCompressed())
sha.Write(hopSharedSecrets[i])
var blindingFactor big.Int
blindingFactor.SetBytes(sha.Sum(nil))
// Blind ephemeral key for next hop.
ephemeralKey.Mul(&ephemeralKey, &blindingFactor)
ephemeralKey.Mod(&ephemeralKey, btcec.S256().Params().N)
}
// Generate the padding, called "filler strings" in the paper.
filler := generateHeaderPadding("rho", numHops, hopDataSize, hopSharedSecrets)
// Allocate and initialize fields to zero-filled slices
var mixHeader [routingInfoSize]byte
var nextHmac [hmacSize]byte
// Our starting packet needs to be filled out with random bytes, we
// generate some deterministically using the session private key.
paddingKey := generateKey("pad", sessionKey.Serialize())
paddingBytes := generateCipherStream(paddingKey, routingInfoSize)
copy(mixHeader[:], paddingBytes)
// Compute the routing information for each hop along with a
// MAC of the routing information using the shared key for that hop.
for i := numHops - 1; i >= 0; i-- {
rhoKey := generateKey("rho", hopSharedSecrets[i])
muKey := generateKey("mu", hopSharedSecrets[i])
hopsData[i].HMAC = nextHmac
// Shift and obfuscate routing information
streamBytes := generateCipherStream(rhoKey, numStreamBytes)
rightShift(mixHeader[:], hopDataSize)
buf := &bytes.Buffer{}
hopsData[i].Encode(buf)
copy(mixHeader[:], buf.Bytes())
xor(mixHeader[:], mixHeader[:], streamBytes[:routingInfoSize])
// These need to be overwritten, so every node generates a correct padding
if i == numHops-1 {
copy(mixHeader[len(mixHeader)-len(filler):], filler)
}
packet := append(mixHeader[:], assocData...)
nextHmac = calcMac(muKey, packet)
}
packet := &OnionPacket{
Version: 0x00,
EphemeralKey: sessionKey.PubKey(),
RoutingInfo: mixHeader,
HeaderMAC: nextHmac,
}
return packet, nil
}
我们使用两种 onion_packet
:
update_add_htlc
中的 onion_routing_packet
,用于付款,其中包含一个 payload
TLV(请参阅添加 HTLC)onion_message
中的 onion_message_packet
,用于消息,其中包含一个 onionmsg_tlv
TLV(请参阅 Onion 消息)这些部分指定了要使用的 associated_data
、path_key
(如果有)、提取的 载荷 格式和处理(包括如何确定下一个对等点,如果有),以及如何处理错误。 处理本身是相同的。
读者:
version
不是 0:
public_key
不是有效的 公钥:
hmac
:
path_key
:
blinding_ss
为 ECDH(path_key
, node_privkey
)。HMAC256(\text{"blinded\_node\_id"}, blinding\_ss)
$ 来调整 public_key
。HMAC256(\text{"blinded\_node\_id"}, blinding\_ss)
$ 来调整其自己的 node_privkey
(如下)。ss
导出为 ECDH(public_key
, node_privkey
)(请参阅共享密钥)。mu
导出为 $HMAC256(\text{"mu"}, ss)
$(请参阅密钥生成)。HMAC256(mu, hop\_payloads || associated\_data)
$。hmac
。hmac
不同:
rho
导出为 $HMAC256(\text{"rho"}, ss)
$(请参阅密钥生成)。rho
导出 hop_payloads
长度两倍的 bytestream
(请参阅伪随机字节流)。unwrapped_payloads
设置为 hop_payloads
和 bytestream
的 XOR。unwrapped_payloads
的前面删除一个 bigsize
,作为 payload_length
。 如果该值格式不正确:
payload_length
小于 2:
unwrapped_payloads
中剩余的字节数少于 payload_length
:
unwrapped_payloads
的前面删除 payload_length
字节,作为当前的 payload
。unwrapped_payloads
中剩余的字节数少于32个:
next_hmac
,从 unwrapped_payloads
的前面。unwrapped_payloads
小于 hop_payloads
:
next_hmac
不是全零(不是最终节点):
blinding_tweak
导出为 $SHA256(public\_key || ss)
$(请参阅 盲化 临时 Onion 密钥)。version
设置为 0。public_key
设置为传入的 public_key
乘以 blinding_tweak
。hop_payloads
设置为 unwrapped_payloads
,截断为传入的 hop_payloads
大小。hmac
设置为 next_hmac
。next_hmac
):
在使用 盲化 路径的情况下,发送者实际上并没有为我们的 node_id
加密此 onion,而是为一个 调整后的 版本:我们可以从 path_key
中导出使用的 调整,该 调整 与 onion 一起给出。 然后,我们要么以相同的方式 调整 我们的 节点私钥 以解密 onion,要么 调整 到数学上等效的 onion 临时密钥。
收到数据包后,处理节点会从路由信息和 逐跳载荷 中提取目标信息。 提取是通过 取消混淆 和 左移 字段来完成的。 这会使每个跳的字段变短,从而使攻击者可以推断出路由长度。 因此,在转发之前对字段进行 预填充。 由于填充是 HMAC 的一部分,因此源节点将必须 预生成 相同的填充(与每个跳将生成的填充相同),以便为每个跳正确计算 HMAC。 填充符 也用于填充字段长度,以防所选路由短于 1300字节。
在 取消混淆 hop_payloads
之前,处理节点使用 1300个 0x00
字节 对其进行填充,以使总长度为 2*1300
。 然后,它生成匹配长度的 伪随机字节流,并使用 XOR
将其应用于 hop_payloads
。 这会 取消混淆 目标信息,同时 混淆 末尾添加的 0x00
字节。
为了计算正确的 HMAC,源节点必须为每个跳 预生成 hop_payloads
,包括每个跳添加的 增量混淆填充。 此 增量混淆填充 称为 filler
。
以下示例代码显示了如何在 Go 中生成 填充符:
func generateFiller(key string, numHops int, hopSize int, sharedSecrets [][sharedSecretSize]byte) []byte {
fillerSize := uint((numMaxHops + 1) * hopSize)
filler := make([]byte, fillerSize)
// The last hop does not obfuscate, it's not forwarding anymore.
for i := 0; i < numHops-1; i++ {
// Left-shift the field
copy(filler[:], filler[hopSize:])
// Zero-fill the last hop
copy(filler[len(filler)-hopSize:], bytes.Repeat([]byte{0x00}, hopSize))
// Generate pseudo-random byte stream
streamKey := generateKey(key, sharedSecrets[i])
streamBytes := generateCipherStream(streamKey, fillerSize)
// Obfuscate
xor(filler, filler, streamBytes)
}
// Cut filler down to the correct length (numHops+1)*hopSize
// bytes will be prepended by the packet generation.
return filler[(numMaxHops-numHops+2)*hopSize:]
}
请注意,此示例实现仅用于演示目的; 可以更有效地生成 filler
。 最后一个跳不需要 混淆 filler
,因为它不会再转发数据包,因此也不需要提取 HMAC。
onion 路由协议包括一个简单的机制,用于将加密的错误消息返回到源节点。 返回的错误消息可能是任何跳报告的失败,包括最终节点。 正向数据包的格式不适用于返回路径,因为除源之外的任何跳都无法访问其生成所需的信息。 请注意,这些错误消息是不可靠的,因为由于跳失败的可能性,它们不会被放置在链上。
中间跳 存储来自正向路径的 共享密钥,并重复使用它来 混淆 每个跳上的任何相应返回数据包。 此外,每个节点都在本地存储有关其在路由中的自身 发送对等点 的数据,因此它知道在哪里返回转发任何最终的返回数据包。 生成错误消息的节点(出错节点)生成一个返回数据包,该数据包包含以下字段:
32*byte
:hmac
]u16
:failure_len
]failure_len*byte
:failuremsg
]u16
:pad_len
]pad_len*byte
:pad
]其中,hmac
是一个 HMAC,用于验证数据包的其余部分,密钥是使用上述过程生成的,密钥类型为 um
,failuremsg
定义如下,pad
是用于隐藏长度的额外字节。
然后,出错节点将使用密钥类型 ammag
生成一个新密钥。 然后,此密钥用于生成 伪随机流,然后使用 XOR
将其应用于数据包。
混淆步骤 由返回路径中的每个跳重复执行。 收到返回数据包后,每个跳会生成其 ammag
,生成 伪随机字节流,然后将结果应用于返回数据包,然后再返回转发它。
源节点能够检测到它是返回消息的预期最终接收者,因为当然,它是相应正向数据包的始发者。 当一个源节点收到与它发起的转移匹配的错误消息(即,它无法再返回转发该错误)时,它会为路由中的每个跳生成 ammag
和 um
密钥。 然后,它使用每个跳的 ammag
密钥迭代地解密错误消息,并使用每个跳的 um
密钥计算 HMAC。 源节点可以通过将 hmac
字段与计算出的 HMAC 匹配来检测错误消息的发送者。
正向数据包和返回数据包之间的关联是在此 onion 路由协议之外处理的,例如,通过与付款通道中的 HTLC 关联。
带有 path_key
的 HTLC 的错误处理尤其令人担忧,因为实施(或版本)中的差异可能会被用来 取消匿名化 盲化 路径的元素。 因此,该决定将每个错误都转换为 invalid_onion_blinding
,引入点 会将其转换为普通 onion 错误。
出错的节点:
pad
,以使 failure_len
加上 pad_len
至少为 256。pad
,以使 failure_len
加上 pad_len
等于 256。 偏离 此值可能会导致较旧的节点无法解析返回消息。源节点:
ammag
和 um
密钥来 混淆 路由长度。对源节点的要求应该有助于隐藏付款发送者。 通过继续解密 27 次(找到错误后进行虚拟解密循环),如果发送者要多次重试同一路由,则出错节点无法通过执行 时序分析 来了解其在路由中的相对位置。
封装在 failuremsg
中的失败消息具有与普通消息相同的格式:一个 2字节 的类型 failure_code
,后跟适用于该类型的数据。 消息数据之后是一个可选的 TLV 流。
以下是当前支持的 failure_code
值的列表,以及它们的使用案例要求。
请注意,failure_code
与其他 BOLT 中定义的其他消息类型不同,因为它们不是直接在 传输层 上发送的,而是 封装 在返回数据包中。 因此,failure_code
的数值可以重复使用值,这些值也被分配给其他消息类型,而不会有引起冲突的任何危险。
failure_code
的 高位字节 可以读取为一组标志:
定义了以下 failure_code
:
temporary_node_failure
)处理节点的一般 临时 失败。
permanent_node_failure
)处理节点的一般 永久性 失败。
required_node_feature_missing
)处理节点具有此 onion 中没有的所需 功能。
invalid_onion_version
)sha256
:sha256_of_onion
]处理节点无法理解 version
字节。
invalid_onion_hmac
)sha256
:onion_的_sha256
]当 onion 到达处理节点时,onion 的 HMAC 不正确。
invalid_onion_key
)sha256
:onion_的_sha256
]临时密钥不能被处理节点解析。
temporary_channel_failure
)u16
:len
]len*byte
:channel_update
]来自处理节点的通道无法处理此 HTLC,但稍后可能能够处理它或其他 HTLC。
permanent_channel_failure
)来自处理节点的通道无法处理任何 HTLC。
required_channel_feature_missing
)来自处理节点的通道需要 onion 中不存在的功能。
unknown_next_peer
)onion 指定了一个 short_channel_id
,该 ID 与来自处理节点的任何前导不匹配。
amount_below_minimum
)u64
:htlc_msat
]u16
:len
]len*byte
:channel_update
]HTLC 金额低于来自处理节点的通道的 htlc_minimum_msat
。
fee_insufficient
)u64
:htlc_msat
]u16
:len
]len*byte
:channel_update
]费用金额低于处理节点通道所需的金额。
incorrect_cltv_expiry
)u32
:cltv_expiry
]u16
:len
]len*byte
:channel_update
]cltv_expiry
不符合处理节点通道所需的 cltv_expiry_delta
:它不满足以下要求:
cltv_expiry - cltv_expiry_delta >= outgoing_cltv_value
expiry_too_soon
)u16
:len
]len*byte
:channel_update
]CLTV 过期时间太接近当前区块高度,无法由处理节点安全处理。
incorrect_or_unknown_payment_details
)u64
:htlc_msat
]u32
:height
]最终节点不知道 payment_hash
,payment_secret
与 payment_hash
不匹配,该 payment_hash
的金额太低,htlc 的 CLTV 过期时间太接近当前区块高度,无法安全处理,或者 payment_metadata
不存在,但应该存在。
htlc_msat
参数是多余的,但为了向后兼容而保留。 htlc_msat
的值必须至少是在最后一个 hop onion payload 中指定的值。 因此,它对发送者没有任何实质性的信息价值(尽管可能表明倒数第二个节点收取的费用低于预期)。 倒数第二个 hop 发送的金额或到期日对于 htlc 来说太低,将通过 final_incorrect_cltv_expiry
和 final_incorrect_htlc_amount
来处理。
height
参数由最终节点设置为接收 htlc 时最知名的区块高度。 发送者可以使用它来区分发送具有错误最终 CLTV 过期时间的付款和中间 hop 延迟付款,从而不再满足接收者的发票 CLTV delta 要求。
注意:最初,PERM|16 (incorrect_payment_amount
) 和 17 (final_expiry_too_soon
) 用于区分不正确的 htlc 参数和未知的支付哈希。 遗憾的是,发送此响应允许探测攻击,即接收用于转发的 HTLC 的节点可以通过发送具有相同哈希但值或到期高度低得多的付款给潜在目的地并检查响应来检查对其最终目的地的猜测。 实施必须小心区分先前 final_expiry_too_soon
(17) 的非永久情况与现在由 incorrect_or_unknown_payment_details
(PERM|15) 表示的其他永久性故障。
final_incorrect_cltv_expiry
)u32
:cltv_expiry
]HTLC 中的 CLTV 过期时间小于 onion 中的值。
final_incorrect_htlc_amount
)u64
:incoming_htlc_amt
]HTLC 中的金额小于 onion 中的值。
channel_disabled
)u16
:disabled_flags
]u16
:len
]len*byte
:channel_update
]来自处理节点的通道已被禁用。 目前未定义 disabled_flags
的标志,因此目前始终是两个零字节。
expiry_too_far
)HTLC 中的 CLTV 过期时间太远,超出了未来。
invalid_onion_payload
)bigsize
:type
]u16
:offset
]解密的 onion per-hop payload 无法被处理节点理解或不完整。 如果故障可以缩小到 payload 中的特定 tlv 类型,则出错节点可以在解密的字节流中包含该 type
及其字节 offset
。
mpp_timeout
)多部分付款的全部金额未在合理的时间内收到。
invalid_onion_blinding
)sha256
:onion_的_sha256
]在盲化路径中发生错误。
一个 出错节点:
update_add_htlc
中设置了 path_key
:
invalid_onion_blinding
错误。current_path_key
并且它不是最终节点:
invalid_onion_blinding
错误。一个 出错节点 可以:
invalid_onion_payload
错误。temporary_node_failure
错误。permanent_node_failure
错误。node_announcement
features
中公布了要求,
但这些要求未包含在 onion 中:
required_node_feature_missing
错误。一个 转发节点 必须:
update_add_htlc
中设置了 path_key
:
invalid_onion_blinding
错误。current_path_key
并且它不是最终节点:
invalid_onion_blinding
错误。一个 转发节点 可以,但 一个 最终节点 不得:
version
字节未知:
invalid_onion_version
错误。invalid_onion_hmac
错误。invalid_onion_key
错误。temporary_channel_failure
错误。permanent_channel_failure
错误。channel_announcement
的 features
中公布了要求,但这些要求未包含在 onion 中:
required_channel_feature_missing
错误。unknown_next_peer
错误。amount_below_minimum
错误。fee_insufficient
错误。cltv_expiry
减去 outgoing_cltv_value
低于
传出通道的 cltv_expiry_delta
:cltv_expiry
和传出通道的当前通道设置。incorrect_cltv_expiry
错误。cltv_expiry
不合理地接近当前时间:
expiry_too_soon
错误。cltv_expiry
在未来超过 max_htlc_cltv
:
expiry_too_far
错误。channel_disabled
错误。一个 中间 hop 不得,但 最终节点:
payment_secret
与该 payment_hash
的预期值不匹配,
或者需要 payment_secret
但不存在:
incorrect_or_unknown_payment_details
错误。incorrect_or_unknown_payment_details
错误。incorrect_or_unknown_payment_details
错误。incorrect_or_unknown_payment_details
错误。
cltv_expiry
值不合理地接近当前时间:
incorrect_or_unknown_payment_details
错误。cltv_expiry
低于 outgoing_cltv_value
:
final_incorrect_cltv_expiry
错误。amount_msat
低于 amt_to_forward
:
final_incorrect_htlc_amount
错误。channel_update
:
short_channel_id
设置为传入 onion 使用的 short_channel_id
。在存在多个 short_channel_id 别名的情况下,channel_update
short_channel_id
应该引用原始发送者期望的那个,以避免混淆并避免泄漏有关其他别名(或通道 UTXO 的真实位置)的信息。
channel_update
字段过去在 failure_code
包含 UPDATE
标志的消息中是强制性的。 但是,由于节点将 onion 中包含的更新应用于其 gossip 数据是一种大规模的指纹识别漏洞,因此 channel_update
字段不再是强制性的,并且预计节点会过渡到不包含它。 不提供 channel_update
的节点应该将 channel_update
len
字段设置为零。
但是,某些节点可能仍将 channel_update
用于重试同一付款。
一个 原始节点:
failuremsg
中的任何额外字节。final_expiry_too_soon
,在这种情况下,temporary_node_failure
可能会在几秒钟内解决。channel_update
时恢复通道。channel_update
有效且比用于发送付款的 channel_update
更新:channel_update
channel_update
,包括将 channel_update
应用于本地网络图、将 channel_update
作为 gossip 发送给对等方等。Onion 消息允许对等方使用现有连接来查询发票(请参阅 BOLT 12)。 与 gossip 消息一样,它们不与特定的本地通道相关联。 与 HTLC 一样,它们使用 onion 消息 协议进行端到端加密。
Onion 消息使用与 HTLC onion_packet
相同的格式,但格式略有不同:payload 长度不是 1300 字节,而是由总长度隐含(减去标头和尾随字节的 66 字节)。 onionmsg_payloads
本身与 hop_payloads
格式相同,只是没有“传统”长度:长度为 0 表示空的 onionmsg_payload
。
Onion 消息是不可靠的:特别是,它们被设计为处理起来很便宜,并且不需要存储来转发。 因此,中间节点不会返回错误。
为了一致性,所有 onion 消息都使用 路由盲化。
onion_message
消息类型: 513 (onion_message
) (option_onion_messages
)
数据:
point
:path_key
]u16
:len
]len*byte
:onion_message_packet
]类型: onion_message_packet
数据:
byte
:version
]point
:public_key
]...*byte
:onionmsg_payloads
]32*byte
:hmac
]类型: onionmsg_payloads
数据:
bigsize
:length
]length*u8
:onionmsg_tlv
]32*byte
:hmac
]filler
onionmsg_tlv
本身是一个 TLV:中间节点期望一个 encrypted_recipient_data
,它可以使用与 onion 消息一起传递的 path_key
将其解密为 encrypted_data_tlv
。
数字 64 及以上的字段保留给最终 hop 的 payload,尽管非最终 hop 不会明确拒绝它们(当然,除非是偶数!)。
tlv_stream
: onionmsg_tlv
reply_path
)blinded_path
:path
]encrypted_recipient_data
)...*byte
:encrypted_recipient_data
]invoice_request
)tlv_invoice_request
:invreq
]invoice
)tlv_invoice
:inv
]invoice_error
)tlv_invoice_error
:inverr
]encrypted_recipient_data
的创建者(通常是 onion 的接收者):
encrypted_data_tlv
创建 encrypted_recipient_data
。encrypted_data_tlv
中包含 payment_relay
或 payment_constraints
encrypted_data_tlv
中包含 next_node_id
或 short_channel_id
。写入者:
onion_message_packet
version
设置为 0。onion_message_packet
onionmsg_payloads
。associated_data
。onion_message_packet
len
设置为 1366 或 32834。onionmsg_tlv
:
encrypted_recipient_data
以外的字段。onionmsg_tlv
:
reply_path
path_key
设置为 first_node_id
的初始路径密钥reply_path
first_node_id
设置为回复路径中第一个节点的未盲化节点 id。reply_path
path
:
blinded_node_id
设置为要加密 onion hop 的盲化节点 id。encrypted_recipient_data
设置为有效的加密 encrypted_data_tlv
流,该流在使用时满足 onionmsg_tlv
的要求。path_id
包含一个密钥,以便它可以识别此 reply_path
的使用。reply_path
。读者:
associated_data
和 path_key
解密 onion_message_packet
,以提取 onionmsg_tlv
。onionmsg_tlv
,或者它包含未知的偶数类型:
encrypted_data_tlv
包含 allowed_features
:
encrypted_data_tlv.allowed_features.features
包含未知的特征位(即使它是奇数)。encrypted_data_tlv.allowed_features.features
中的特征。onionmsg_tlv
包含 encrypted_recipient_data
以外的其他 tlv 字段:encrypted_data_tlv
包含 path_id
:next_node_id
:
short_channel_id
并且对应于已公布的 short_channel_id 或通道的本地别名:
onion_message
将消息转发到 下一个对等方**。onion_message
中的 path_key
设置为 路由盲化 中计算的下一个 path_key
。path_id
并且对应于读者先前在 reply_path
中发布过的路径:path_id
):path_id
的 onion 消息的回复:
onionmsg_tlv
包含多个 payload 字段:reply_path
创建一个 onion 消息。onion_message
将回复发送到 first_node_id
指示的节点,使用 reply_path
path_key
沿着 reply_path
path
发送。必须注意仅使用给定的确切 reply_path 接受回复,否则可能会进行探测。 这意味着检查 两种方式:非回复不使用回复路径,回复总是 使用回复路径。
要求丢弃带有 onionmsg_tlv
字段的消息
并非严格要求确保当前和
未来实施之间的一致性。 甚至是奇数字段也可能是一个问题,因为它们
会被理解它们的节点解析(因此可能会被拒绝!),
并被不理解它们的节点忽略。
所有 onion 消息都是盲化的,即使这种开销并不 总是必要的(此处为 33 个字节,onion 中每个 encrypted_data_tlv 的 16 字节 MAC)。 这种盲化允许节点使用他人提供的路径 不知道其内容。 普遍使用它可以简化 实现一点,并使其更难以区分 onion 消息。
len
允许发送比标准 1300 字节更大的消息
允许用于 HTLC onion,但应谨慎使用,因为它
降低了匿名集,因此建议它看起来
就像一个 HTLC onion,或者如果更大,则采用固定大小。
Onion 消息没有明确要求通道,但对于 垃圾邮件减少,节点可以选择对这些对等方进行速率限制,尤其是 要求其转发的消息。
max_htlc_cltv
选择此 max_htlc_cltv
值定义为 2016 个区块,基于 Lightning 实施部署的历史值。
测试向量使用以下参数:
pubkey[0] = 0x02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619
pubkey[1] = 0x0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c
pubkey[2] = 0x027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007
pubkey[3] = 0x032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991
pubkey[4] = 0x02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145
nhops = 5
sessionkey = 0x4141414141414141414141414141414141414141414141414141414141414141
failure_source = node 4
failure_message = `incorrect_or_unknown_payment_details`
htlc_msat = 100
height = 800000
tlv data
type = 34001
value = [128, 128, ..., 128] (300 bytes)
以下是错误消息创建示例的深入跟踪:
encoded_failure_message = 400f0000000000000064000c3500fd84d1fd012c80808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808002c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
shared_secret = b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328
payload = 0140400f0000000000000064000c3500fd84d1fd012c80808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808002c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
um_key = 4da7f2923edce6c2d85987d1d9fa6d88023e6c3a9c3d20f07d3b10b61a78d646
raw_error_packet = fda7e11974f78ca6cc456f2d17ae54463664696e93842548245dd2a2c513a6260140400f0000000000000064000c3500fd84d1fd012c80808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808002c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
# 转发错误包
shared_secret = b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328
ammag_key = 2f36bb8822e1f0d04c27b7d8bb7d7dd586e032a3218b8d414afbba6f169a4d68
stream = e9c975b07c9a374ba64fd9be3aae955e917d34d1fa33f2e90f53bbf4394713c6a8c9b16ab5f12fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4f5984bc119af09d471a61f39e9e389c4120cadabc5d9b7b1355a8ccef050ca8ad72f642fc26919927b347808bade4b1c321b08bc363f20745ba2f97f0ced2996a232f55ba28fe7dfa70a9ab0433a085388f25cce8d53de6a2fbd7546377d6ede9027ad173ba1f95767461a3689ef405ab608a21086165c64b02c1782b04a6dba2361a7784603069124e12f2f6dcb1ec7612a4fbf94c0e14631a2bef6190c3d5f35e0c4b32aa85201f449d830fd8f782ec758b0910428e3ec3ca1dba3b6c7d89f69e1ee1b9df3dfbbf6d361e1463886b38d52e8f43b73a3bd48c6f36f5897f514b93364a31d49d1d506340b1315883d425cb36f4ea553430d538fd6f3596d4afc518db2f317dd051abc0d4bfb0a7870c3db70f19fe78d6604bbf088fcb4613f54e67b038277fedcd9680eb97bdffc3be1ab2cbcbafd625b8a7ac34d8c190f98d3064ecd3b95b8895157c6a37f31ef4de094b2cb9dbf8ff1f419ba0ecacb1bb13df0253b826bec2ccca1e745dd3b3e7cc6277ce284d649e7b8285727735ff4ef6cca6c18e2714f4e2a1ac67b25213d3bb49763b3b94e7ebf72507b71fb2fe0329666477ee7cb7ebd6b88ad5add8b217188b1ca0fa13de1ec09cc674346875105be6e0e0d6c8928eb0df23c39a639e04e4aedf535c4e093f08b2c905a14f25c0c0fe47a5a1535ab9eae0d9d67bdd79de13a08d59ee05385c7ea4af1ad3248e61dd22f8990e9e99897d653dd7b1b1433a6d464ea9f74e377f2d8ce99ba7dbc753297644234d25ecb5bd528e2e2082824681299ac30c05354baaa9c3967d86d7c07736f87fc0f63e5036d47235d7ae12178ced3ae36ee5919c093a02579e4fc9edad2c446c656c790704bfc8e2c491a42500aa1d75c8d4921ce29b753f883e17c79b09ea324f1f32ddf1f3284cd70e847b09d90f6718c42e5c94484cc9cbb0df659d255630a3f5a27e7d5dd14fa6b974d1719aa98f01a20fb4b7b1c77b42d57fab3c724339d459ee4a1c6b5d3bd4e08624c786a257872acc9ad3ff62222f2265a658d9f2a007229a5293b67ec91c84c4b4407c228434bad8a815ca9b256c776bd2c9f
error packet for node 4: 146e94a9086dbbed6a0ab6932d00c118a7195dbf69b7d7a12b0e6956fc54b5e0a989f165b5f12fd45edd73a5b0c48630ff5be69500d3d82a29c0803f0a0679a6a073c33a6fb8250090a3152eba3f11a85184fa87b67f1b0354d6f48e3b342e332a17b7710f342f342a87cf32eccdf0afc2160808d58abb5e5840d2c760c538e63a6f841970f97d2e6fe5b8739dc45e2f7f5f532f227bcc2988ab0f9cc6d3f12909cd5842c37bc8c7608475a5ebbe10626d5ecc1f3388ad5f645167b44a4d166f87863fe34918cea25c18059b4c4d9cb414b59f6bc50c1cea749c80c43e2344f5d23159122ed4ab9722503b212016470d9610b46c35dbeebaf2e342e09770b38392a803bc9d2e7c8d6d384ffcbeb74943fe3f64afb2a543a6683c7db3088441c531eeb4647518cb41992f8954f1269fb969630944928c2d2b45593731b5da0c4e70d04a0a57afe4af42e99912fbb4f8883a5ecb9cb29b883cb6bfa0f4db2279ff8c6d2b56a232f55ba28fe7dfa70a9ab0433a085388f25cce8d53de6a2fbd7546377d6ede9027ad173ba1f95767461a3689ef405ab608a21086165c64b02c1782b04a6dba2361a7784603069124e12f2f6dcb1ec7612a4fbf94c0e14631a2bef6190c3d5f35e0c4b32aa85201f449d830fd8f782ec758b0910428e3ec3ca1dba3b6c7d89f69e1ee1b9df3dfbbf6d361e1463886b38d52e8f43b73a3bd48c6f36f5897f514b93364a31d49d1d506340b1315883d425cb36f4ea553430d538fd6f3596d4afc518db2f317dd051abc0d4bfb0a7870c3db70f19fe78d6604bbf088fcb4613f54e67b038277fedcd9680eb97bdffc3be1ab2cbcbafd625b8a7ac34d8c190f98d3064ecd3b95b8895157c6a37f31ef4de094b2cb9dbf8ff1f419ba0ecacb1bb13df0253b826bec2ccca1e745dd3b3e7cc6277ce284d649e7b8285727735ff4ef6cca6c18e2714f4e2a1ac67b25213d3bb49763b3b94e7ebf72507b71fb2fe0329666477ee7cb7ebd6b8```
shared_secret = 3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc
ammag_key = 1bf08df8628d452141d56adfd1b25c1530d7921c23cecfc749ac03a9b694b0d3
stream = 6149f48b5a7e8f3d6f5d870b7a698e204cf64452aab4484ff1dee671fe63fd4b5f1b78ee2047dfa61e3d576b149bedaf83058f85f06a3172a3223ad6c4732d96b32955da7d2feb4140e58d86fc0f2eb5d9d1878e6f8a7f65ab9212030e8e915573ebbd7f35e1a430890be7e67c3fb4bbf2def662fa625421e7b411c29ebe81ec67b77355596b05cc155755664e59c16e21410aabe53e80404a615f44ebb31b365ca77a6e91241667b26c6cad24fb2324cf64e8b9dd6e2ce65f1f098cfd1ef41ba2d4c7def0ff165a0e7c84e7597c40e3dffe97d417c144545a0e38ee33ebaae12cc0c14650e453d46bfc48c0514f354773435ee89b7b2810606eb73262c77a1d67f3633705178d79a1078c3a01b5fadc9651feb63603d19decd3a00c1f69af2dab2595931ca50d8280758b1cc91ba2dc43dbbc3d91bf25c08b46c2ecef7a32cec64d4b61ee3a629ef563afe058b71e71bcb69033948bc8728c5ebe65ec596e4f305b9fc159d53f723dfc95b57f3d51717f1c89af97a6d587e89e62efcc92198a1b2bd66e2d875505ea4046c04389f8cb0ee98f0af03af2652e2f3d9a9c48430f2891a4d9b16e7d18099e4a3dd334c24aba1e2450792c2f22092c170da549d43a440021e699bd6c20d8bbf1961100a01ebcce06a4609f5ad93066287acf68294cfa9ea7cea03a508983b134a9f0118b16409a61c06aaa95897d2067cb7cd59123f3e2ccf0e16091571d616c44818f118bb7835a679f5c0eea8cf1bd5479882b2c2a341ec26dbe5da87b3d37d66b1fbd176f71ab203a3b6eaf7f214d579e7d0e4a3e59089ebd26ba04a62403ae7a793516ec16d971d51c5c0107a917d1a70221e6de16edca7cb057c7d06902b5191f298aa4d478a0c3a6260c257eae504ebbf2b591688e6f3f77af770b6f566ae9868d2f26c12574d3bf9323af59f0fe0072ff94ae597c2aa6fbcbf0831989e02f9d3d1b9fd6dd97f509185d9ecbf272e38bd621ee94b97af8e1cd43853a8f6aa6e8372585c71bf88246d064ade524e1e0bd8496b620c4c2d3ae06b6b064c97536aaf8d515046229f72bee8aa398cd0cc21afd5449595016bef4c77cb1e2e9d31fe1ca3ffde06515e6a4331ccc84edf702e5777b10fc844faf17601a4be3235931f6feca4582a8d247c1d6e4773f8fb6de320cf902bbb1767192782dc550d8e266e727a2aa2a414b816d1826ea46af71701537193c22bbcc0123d7ff5a23b0aa8d7967f36fef27b14fe1866ff3ab215eb29e07af49e19174887d71da7e7fe1b7aa1b3c805c063e0fafedf125fa6c57e38cce33a3f7bb35fd8a9f0950de3c22e49743c05f40bc55f960b8a8b5e2fde4bb229f125538438de418cb318d13968532499118cb7dcaaf8b6d635ac4001273bdafd12c8ea0702fb2f0dac81dbaaf68c1c32266382b293fa3951cb952ed5c1bdc41750cdbc0bd62c51bb685616874e251f031a929c06faef5bfcb0857f815ae20620b823f0abecfb5
error packet for node 2: 145bc1c63058f7204abbd2320d422e69fb1b3801a14312f81e5e29e6b5f4774cfed8a25241d3dfb7466e749c1b3261559e49090853612e07bd669dfb5f4c54162fa504138dabd6ebcf0db8017840c35f12a2cfb84f89cc7c8959a6d51815b1d2c5136cedec2e4106bb5f2af9a21bd0a02c40b44ded6e6a90a145850614fb1b0eef2a03389f3f2693bc8a755630fc81fff1d87a147052863a71ad5aebe8770537f333e07d841761ec448257f948540d8f26b1d5b66f86e073746106dfdbb86ac9475acf59d95ece037fba360670d924dce53aaa74262711e62a8fc9eb70cd8618fbedae22853d3053c7f10b1a6f75369d7f73c419baa7dbf9f1fc5895362dcc8b6bd60cca4943ef7143956c91992119bccbe1666a20b7de8a2ff30a46112b53a6bb79b763903ecbd1f1f74952fb1d8eb0950c504df31fe702679c23b463f82a921a2c931500ab08e686cffb2d87258d254fb17843959cccd265a57ba26c740f0f231bb76df932b50c12c10be90174b37d454a3f8b284c849e86578a6182c4a7b2e47dd57d44730a1be9fec4ad07287a397e28dce4fda57e9cdfdb2eb5afdf0d38ef19d982341d18d07a556bb16c1416f480a396f278373b8fd9897023a4ac506e65cf4c306377730f9c8ca63cf47565240b59c4861e52f1dab84d938e96fb31820064d534aca05fd3d2600834fe4caea98f2a748eb8f200af77bd9fbf46141952b9ddda66ef0ebea17ea1e7bb5bce65b6e71554c56dd0d4e14f4cf74c77a150776bf31e7419756c71e7421dc22efe9cf01de9e19fc8808d5b525431b944400db121a77994518d6025711cb25a18774068bba7faaa16d8f65c91bec8768848333156dcb4a08dfbbd9fef392da3e4de13d4d74e83a7d6e46cfe530ee7a6f711e2caf8ad5461ba8177b2ef0a518baf9058ff9156e6aa7b08d938bd8d1485a787809d7b4c8aed97be880708470cd2b2cdf8e2f13428cc4b04ef1f2acbc9562f3693b948d0aa94b0e6113cafa684f8e4a67dc431dfb835726874bef1de36f273f52ee694ec46b0700f77f8538067642a552968e866a72a3f2031ad116663ac17b172b446c5bc705b84777363a9a3fdc6443c07b2f4ef58858122168d4ebbaee920cefc312e1cea870ed6e15eec046ab2073bbf08b0a3366f55cfc6ad4681a12ab0946534e7b6f90ea8992d530ec3daa6b523b3cf03101c60cadd914f30dec932c1ef4341b5a8efac3c921e203574cfe0f1f83433fddb8ccfd273f7c3cab7bc27efe3bb61fdccd5146f1185364b9b621e7fb2b74b51f5ee6be72ab6ff46a6359dc2c855e61469724c1dbeb273df9d2e1c1fb74891239c0019dc12d5c7535f7238f963b761d7102b585372cf021b64c4fc85bfb3161e59d2e298bba44cfd34d6859d9dba9dc6271e5047d525468c814f2ae438474b0a977273036da1a2292f88fcfb89574a6bdca1185b40f8aa54026d5926725f99ef028da1be892e3586361efe15f4a148ff1bc9
# forwarding error packet
shared_secret = a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae
ammag_key = 59ee5867c5c151daa31e36ee42530f429c433836286e63744f2020b980302564
stream = 0f10c86f05968dd91188b998ee45dcddfbf89fe9a99aa6375c42ed5520a257e048456fe417c15219ce39d921555956ae2ff795177c63c819233f3bcb9b8b28e5ac6e33a3f9b87ca62dff43f4cc4a2755830a3b7e98c326b278e2bd31f4a9973ee99121c62873f5bfb2d159d3d48c5851e3b341f9f6634f51939188c3b9ff45feeb11160bb39ce3332168b8e744a92107db575ace7866e4b8f390f1edc4acd726ed106555900a0832575c3a7ad11bb1fe388ff32b99bcf2a0d0767a83cf293a220a983ad014d404bfa20022d8b369fe06f7ecc9c74751dcda0ff39d8bca74bf9956745ba4e5d299e0da8f68a9f660040beac03e795a046640cf8271307a8b64780b0588422f5a60ed7e36d60417562938b400802dac5f87f267204b6d5bcfd8a05b221ec294d883271b06ca709042ed5dbb64a7328d2796195eab4512994fb88919c73b3e5dd7bf68b2136d34cff39b3be266b71e004509bf975a240800bb8ae5eed248423a991ae80ef751b2d03b67fb93ffdd7969d5b500fe446a4ffb4cd04d0767a5d367ebd3f8f260f38ae1e9d9f9a7bd1a99ca1e10ee36bd241f06fc2b481c9b7450d9c9704204666807783264a0e93468e22db4dc4a7a4db2963ddf4366d08e225cf94848aac794bcecb7e850113e38cc3647a03a5dfaa3442b1bb58b1de7fa7f436feb4d7c23cbd2de6d55d4025fcd383cc9d49c0b130e2fd5a9097c216683c842f898a8a2159761cca9aa1c818194e3b7bea6da6652d5189f3b6b0ca1d5398b6d14e311d9c7f00399c29e94deb98496f4cd97c5d7d6a65cabc3791f60d728d6422a422c0cff5f7dfd4ce2d7e8d38dd71ae18763acc832c57275497f61b2620cca13cc64c0c48353f3817016f91448d6fc1cc451ee1f4a429e43292bbcd54fcd807e2c47675bac1781d9d81e9e6dc69028d428f5ee261750f626bcaf416a0e7badadf73fe1922207ae6c5209d16849e4a108f4a6f38694075f55177105ac4c2b97f6a474b94c03257d8d12b0196e2905d914b8c2213a1b9dc9608e1a2a1e03fe0820a813275de83be5e9734875787a9e006eb8574c23ddd49e2347d1ecfcedf3caa0a5dd45666368525b48ac14225d6422f82dbf59860ee4dc78e845d3c57668ce9b9e7a8d012491cef242078b458a956ad67c360fb6d8b86ab201d6217e49b55fa02a1dea2dbe88d0b08d30670d1b93c35cc5e41e088fccb267e41d6151cf8560496e1beeefe680744d9dabb383a4957466b4dc3e2bce7b135211da483d998a22fa687cc609641126c5dee3ed87291067916b5b065f40582163291d48e81ecd975d0d6fd52a31754f8ef15e43a560bd30ea5bf21915bd2e7007e607abbc6261edc8430cc7f789675b1fe83e807c5c475bd5178eba2fc40674706b0a68c6a428e5dec36e413e653c6db1178923ff87e2389a78bf9e93b713de4f4753f9f9d6a361369b609e1970c91ff9bd191c472e0bf2e8681412260ad0ef5855dc39f2084d45
error packet for node 1: 1b4b09a935ce7af95b336baae307f2b400e3a7e808d9b4cf421cc4b3955620acb69dcdb656128dae8857adbd4e6b37fbb1be9c1f2f02e61e9e59a630c4c77cf383cb37b07413aa4de2f2fbf5b40ae40a91a8f4c6d74aeacef1bb1be4ecbc26ec2c824d2bc45db4b9098e732a769788f1cff3f5b41b0d25c132d40dc5ad045ef0043b15332ca3c5a09de2cdb17455a0f82a8f20da08346282823dab062cdbd2111e238528141d69de13de6d83994fbc711e3e269df63a12d3a4177c5c149150eb4dc2f589cd8acabcddba14dec3b0dada12d663b36176cd3c257c5460bab93981ad99f58660efa9b31d7e63b39915329695b3fa60e0a3bdb93e7e29a54ca6a8f360d3848866198f9c3da3ba958e7730847fe1e6478ce8597848d3412b4ae48b06e05ba9a104e648f6eaf183226b5f63ed2e68f77f7e38711b393766a6fab7921b03eba82b5d7cb78e34dc961948d6161eadd7cf5d95d9c56df2ff5faa6ccf85eacdc9ff2fc3abafe41c365a5bd14fd486d6b5e2f24199319e7813e02e798877ffe31a70ae2398d9e31b9e3727e6c1a3c0d995c67d37bb6e72e9660aaaa9232670f382add2edd468927e3303b6142672546997fe105583e7c5a3c4c2b599731308b5416e6c9a3f3ba55b181ad0439d3535356108b059f2cb8742eed7a58d4eba9fe79eaa77c34b12aff1abdaea93197aabd0e74cb271269ca464b3b06aef1d6573df5e1224179616036b368677f26479376681b772d3760e871d99efd34cca5cd6beca95190d967da820b21e5bec60082ea46d776b0517488c84f26d12873912d1f68fafd67bcf4c298e43cfa754959780682a2db0f75f95f0598c0d04fd014c50e4beb86a9e37d95f2bba7e5065ae052dc306555bca203d104c44a538b438c9762de299e1c4ad30d5b4a6460a76484661fc907682af202cd69b9a4473813b2fdc1142f1403a49b7e69a650b7cde9ff133997dcc6d43f049ecac5fce097a21e2bce49c810346426585e3a5a18569b4cddd5ff6bdec66d0b69fcbc5ab3b137b34cc8aefb8b850a764df0e685c81c326611d901c392a519866e132bbb73234f6a358ba284fbafb21aa3605cacbaf9d0c901390a98b7a7dac9d4f0b405f7291c88b2ff45874241c90ac6c5fc895a440453c344d3a365cb929f9c91b9e39cb98b142444aae03a6ae8284c77eb04b0a163813d4c21883df3c0f398f47bf127b5525f222107a2d8fe55289f0cfd3f4bbad6c5387b0594ef8a966afc9e804ccaf75fe39f35c6446f7ee076d433f2f8a44dba1515acc78e589fa8c71b0a006fe14feebd51d0e0aa4e51110d16759eee86192eee90b34432130f387e0ccd2ee71023f1f641cddb571c690107e08f592039fe36d81336a421e89378f351e633932a2f5f697d25b620ffb8e84bb6478e9bd229bf3b164b48d754ae97bd23f319e3c56b3bcdaaeb3bd7fc02ec02066b324cb72a09b6b43dec1097f49d69d3c138ce6f1a6402898baf7568c
# forwarding error packet
shared_secret = 53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66
ammag_key = 3761ba4d3e726d8abb16cba5950ee976b84937b61b7ad09e741724d7dee12eb5
stream = 3699fd352a948a05f604763c0bca2968d5eaca2b0118602e52e59121f050936c8dd90c24df7dc8cf8f1665e39a6c75e9e2c0900ea245c9ed3b0008148e0ae18bbfaea0c711d67eade980c6f5452e91a06b070bbde68b5494a92575c114660fb53cf04bf686e67ffa4a0f5ae41a59a39a8515cb686db553d25e71e7a97cc2febcac55df2711b6209c502b2f8827b13d3ad2f491c45```markdown
为节点0发送的错误数据包:2dd2f49c1f5af0fcad371d96e8cddbdcd5096dc309c1d4e110f955926506b3c03b44c192896f45610741c85ed4074212537e0c118d472ff3a559ae244acd9d783c65977765c5d4e00b723d00f12475aafaafff7b31c1be5a589e6e25f8da2959107206dd42bbcb43438129ce6cce2b6b4ae63edc76b876136ca5ea6cd1c6a04ca86eca143d15e53ccdc9e23953e49dc2f87bb11e5238cd6536e57387225b8fff3bf5f3e686fd08458ffe0211b87d64770db9353500af9b122828a006da754cf979738b4374e146ea79dd93656170b89c98c5f2299d6e9c0410c826c721950c780486cd6d5b7130380d7eaff994a8503a8fef3270ce94889fe996da66ed121741987010f785494415ca991b2e8b39ef2df6bde98efd2aec7d251b2772485194c8368451ad49c2354f9d30d95367bde316fec6cbdddc7dc0d25e99d3075e13d3de0822669861dafcd29de74eac48b64411987285491f98d78584d0c2a163b7221ea796f9e8671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4
```## 参考文献
[sphinx]: http://www.cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf
[RFC2104]: https://tools.ietf.org/html/rfc2104
[fips198]: http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf
[sec2]: http://www.secg.org/sec2-v2.pdf
[rfc8439]: https://tools.ietf.org/html/rfc8439
## 作者
[ FIXME: ]

<br>
本作品采用 [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/) 许可。
>- 原文链接: [github.com/lightning/bol...](https://github.com/lightning/bolts/blob/master//04-onion-routing.md)
>- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!