BOLT 1:基础协议

  • lightning
  • 发布于 2025-02-25 13:30
  • 阅读 11

本文档是闪电网络基础协议规范(BOLT 1),定义了闪电网络节点之间通信的基本规则。内容涵盖连接处理、消息格式、类型-长度-值(TLV)编码、基本数据类型以及设置、控制消息,并详细阐述了节点如何通过ping/pong消息保持连接活跃,以及如何使用peer_storage消息进行对等存储。文档还包含了BigSize和TLV格式的测试向量,便于开发者验证实现。

BOLT #1:基础协议

概述

本协议假定了一个底层的认证且排序的传输机制,该机制负责构建单独的消息。 BOLT #8 指定了 Lightning 中使用的规范传输层,尽管它可以被任何满足上述保证的传输层所替代。

默认的 TCP 端口取决于所使用的网络。最常见的网络有:

  • Bitcoin mainnet 使用端口号 9735 或相应的十六进制 0x2607
  • Bitcoin testnet 使用端口号 19735 (0x4D17);
  • Bitcoin signet 使用端口号 39735 (0x9B37)。

LIGHTNING 的 Unicode 代码点 <sup>1</sup> 和端口约定尝试遵循 Bitcoin Core 的约定。

除非另有说明,所有数据字段均为无符号大端数据。

目录

连接处理和多路复用

实现必须为每个对等方使用单个连接;通道消息(包括通道 ID)在此单个连接上进行多路复用。

Lightning 消息格式

解密后,所有 Lightning 消息的格式如下:

  1. type:一个 2 字节的大端字段,指示消息的类型
  2. payload:一个可变长度的 payload,它包含消息的其余部分,并且符合与 type 相匹配的格式
  3. extension:一个可选的 TLV 流

type 字段指示如何解释 payload 字段。 每种单独类型的格式都由本仓库中的规范定义。 类型遵循 保持奇数 规则,因此节点可以发送_奇数_编号的类型,而无需确定接收方是否理解它。

这些消息在逻辑上分为五个组,按设置的最高有效位排序:

  • 设置 & 控制(类型 0-31):与连接设置、控制、支持的功能和错误报告相关的消息(如下所述)
  • 通道(类型 32-127):用于设置和拆除微支付通道的消息(在 BOLT #2 中描述)
  • 承诺(类型 128-255):与更新当前承诺交易相关的消息,包括添加、撤销和结算 HTLC,以及更新费用和交换签名(在 BOLT #2 中描述)
  • 路由(类型 256-511):包含节点和通道声明以及任何活动的路由探索的消息(在 BOLT #7 中描述)
  • 自定义(类型 32768-65535):实验性和应用程序特定的消息

消息的大小由传输层要求在一个 2 字节的无符号整数中;所以,最大可能的大小是 65535 字节。

一个发送节点:

  • 如果没有事先协商,必须不发送此处未列出的偶数类型消息。
  • 如果没有事先协商,必须不extension 中发送偶数类型的 TLV 记录。
  • 协商了本规范中的选项:
    • 必须包含所有用该选项注释的字段。
  • 当定义自定义消息时:
    • 应该选择一个随机的 type,以避免与其他自定义类型冲突。
    • 应该选择一个 type,该 type 不会与 此问题 中列出的其他实验冲突。
    • 应该在常规节点应忽略附加数据时选择奇数 type 标识符。
    • 应该在常规节点应拒绝消息并关闭连接时选择偶数 type 标识符。

一个接收节点:

  • 收到_奇数_的、未知类型的消息时:
    • 必须忽略接收到的消息。
  • 收到_偶数_的、未知类型的消息时:
    • 必须关闭连接。
    • 可以使通道失效。
  • 收到已知消息,但内容长度不足时:
    • 必须关闭连接。
    • 可以使通道失效。
  • 收到带有 extension 的消息时:
    • 可以忽略 extension
    • 否则,如果 extension 无效:
      • 必须关闭连接。
      • 可以使通道失效。

理由

默认情况下,SHA2 和比特币公钥都编码为大端,因此对其他字段使用不同的字节序是不寻常的。

长度受加密包装限制为 65535 字节,并且协议中的消息无论如何都不会超过该长度。

保持奇数 规则允许将来可选的扩展,而无需客户端进行协商或特殊编码。扩展 字段类似地允许将来的扩展,允许发送方包含额外的 TLV 数据。 请注意,仅当消息 payload 尚未填满 65535 字节的最大长度时,才能添加 扩展 字段。

实现可能希望使消息数据与 8 字节边界对齐(此处任何类型的最大自然对齐要求); 但是,在类型字段之后添加 6 字节的填充被认为是浪费:可以通过将消息解密到具有 6 字节预填充的缓冲区中来实现对齐。

类型-长度-值格式

在整个协议中,使用 TLV(类型-长度-值)格式来允许向现有消息类型向后兼容地添加新字段。

tlv_record 表示单个字段,编码形式为:

  • [bigsize: type]
  • [bigsize: length]
  • [length: value]

tlv_stream 是一系列(可能为零)tlv_record,表示为编码的 tlv_record 的串联。 当用于扩展现有消息时,tlv_stream 通常放置在所有当前定义的字段之后。

type 使用 BigSize 格式进行编码。 它充当消息特定的 64 位标识符,用于 tlv_record,确定应如何解码 value 的内容。 小于 2^16 的 type 标识符保留供本规范使用。 大于或等于 2^16 的 type 标识符可用于自定义记录。 本规范中未定义的任何记录均被视为自定义记录。 这包括实验性和应用程序特定的消息。

length 使用 BigSize 格式进行编码,以指示 value 的大小(以字节为单位)。

value 完全取决于 type,并且应根据 type 确定的消息特定格式进行编码或解码。

要求

发送节点:

  • 必须按严格递增的 typetlv_stream 中的 tlv_record 进行排序,因此必须不生成具有相同 type 的多个 TLV 记录
  • 必须以最小方式编码 typelength
  • 定义自定义记录 type 标识符时:
    • 应该选择随机 type 标识符,以避免与其他自定义类型冲突。
    • 应该在常规节点应忽略附加数据时选择奇数 type 标识符。
    • 应该在常规节点应拒绝包含自定义记录的完整 tlv 流时选择偶数 type 标识符。
  • 不应该tlv_record 中使用冗余的可变长度编码。

接收节点:

  • 如果在解析 type 之前剩余零字节:
    • 必须停止解析 tlv_stream
  • 如果 typelength 未以最小方式编码:
    • 必须无法解析 tlv_stream
  • 如果解码的 type 不是严格递增的(包括遇到相同 type 的两次或更多次的情况):
    • 必须无法解析 tlv_stream
  • 如果 length 超过消息中剩余的字节数:
    • 必须无法解析 tlv_stream
  • 如果 type 是已知的:
    • 必须使用已知的 type 编码来解码接下来的 length 个字节。
    • 如果 length 与已知的 type 编码所需的不完全相等:
      • 必须无法解析 tlv_stream
    • 如果已知的 type 编码中的可变长度字段不是最小的:
      • 必须无法解析 tlv_stream
  • 否则,如果 type 是未知的:
    • 如果 type 是偶数:
      • 必须无法解析 tlv_stream
    • 否则,如果 type 是奇数:
      • 必须丢弃接下来的 length 个字节。

理由

使用 TLV 的主要优点是,读取器能够忽略它不理解的新字段,因为每个字段都承载了编码元素的精确大小。 如果没有 TLV,即使节点不希望使用特定字段,该节点也必须为该字段添加解析逻辑,以便确定任何后续字段的偏移量。

严格的单调性约束确保所有 type 都是唯一的,并且最多出现一次。 映射到复杂对象(例如,向量,映射或结构)的字段应通过定义编码来实现,以便在单个 tlv_record 中序列化该对象。 除其他事项外,唯一性约束还启用了以下优化:

  • 规范排序的定义与编码的 value 无关。
  • 规范排序可以在编译时知道,而不是在编码时动态确定。
  • 验证规范排序所需的资源较少,并且成本较低。
  • 可变大小的字段可以预先保留其预期的大小,而不是顺序地追加元素并导致双重复制开销。

typelength 使用 bigsize 允许节省小型 type 或短 value 的空间。 这可能会在线或在 onion payload 中为应用程序数据留出更多空间。

所有 type 必须按递增顺序出现,以创建基础 tlv_record 的规范编码。 这在计算 tlv_stream 上的签名时至关重要,因为它确保验证者将能够重新计算与签名者相同的消息摘要。 请注意,即使验证者不了解字段包含的内容,也可以强制执行字段集上的规范排序。

编写者应避免在 tlv_record 中使用冗余的可变长度编码,因为这会导致对长度进行两次编码并使计算外部长度变得复杂。 例如,在编写可变长度字节数组时,value 应该仅包含原始字节,并放弃额外的内部长度,因为 tlv_record 已经带有后续字节数。 另一方面,如果 tlv_record 包含多个可变长度元素,则这不被认为是冗余的,并且需要允许接收者从 value 中解析单个元素。

基本类型

消息规范中引用了各种基本类型:

  • byte:一个 8 位字节
  • s8:一个 8 位有符号整数
  • u16:一个 2 字节无符号整数
  • s16:一个 2 字节有符号整数
  • u32:一个 4 字节无符号整数
  • s32:一个 4 字节有符号整数
  • u64:一个 8 字节无符号整数
  • s64:一个 8 字节有符号整数

有符号整数使用标准的大端二进制补码表示形式(参见下面的测试向量 below)。

对于 TLV 记录中的最终值,可以使用截断的整数。 截断整数中的前导零必须省略:

  • tu16:一个 0 到 2 字节的截断无符号整数
  • tu32:一个 0 到 4 字节的截断无符号整数
  • tu64:一个 0 到 8 字节的截断无符号整数

当用于编码金额时,前面的字段必须符合 2100 万 BTC 的上限:

  • 聪金额必须最多为 0x000775f05a074000
  • 毫聪金额必须最多为 0x1d24b2dfac520000

还定义了以下便捷类型:

  • chain_hash:一个 32 字节的链标识符(参见 BOLT #0
  • channel_id:一个 32 字节的 channel_id(参见 BOLT #2
  • sha256:一个 32 字节的 SHA2-256 哈希
  • signature:一个 64 字节的比特币椭圆曲线签名
  • bip340sig:根据 BIP-340 的 64 字节比特币椭圆曲线 Schnorr 签名
  • point:一个 33 字节的椭圆曲线点(根据 SEC 1 标准 的压缩编码)
  • short_channel_id:一个 8 字节的值,用于标识通道(参见 BOLT #7
  • sciddir_or_pubkey:分别是 9 或 33 个字节,用于引用或标识节点
    • 如果第一个字节是 0 或 1,则跟随一个 8 字节的 short_channel_id,总共 9 个字节
      • 第一个字节为 0 表示它指的是 channel_announcementshort_channel_idnode_id_1
      • 第一个字节为 1 表示它指的是 channel_announcementshort_channel_idnode_id_2 (参见 BOLT #7
    • 如果第一个字节是 2 或 3,则该值为 33 字节的 point
  • bigsize:一个可变长度的无符号整数,类似于比特币的 CompactSize 编码,但采用大端字节序。在BigSize 中描述。
  • utf8:作为 UTF-8 字符串一部分的一个字节。编写器必须确保这些字节数组是有效的 UTF-8 字符串,读取器可以拒绝任何包含无效 UTF-8 字符串的此类消息。

设置消息

init 消息

身份验证完成后,第一条消息会显示此节点支持或需要的功能,即使这是重新连接。

BOLT #9 指定了功能列表。 每个功能通常用 2 位表示。 最低有效位编号为 0,这是 偶数,下一个最高有效位编号为 1,这是 奇数。 出于历史原因,功能分为全局和本地功能位掩码。

如果对等方在当前连接的 init 消息中设置了某个功能(作为偶数或奇数),则该功能将被提供。 如果两个对等方都提供了某个功能,或者本地节点将其作为偶数提供了,则该功能将被协商:它可以假定对等方支持该功能,因为它没有断开连接(这是必需的)。

features 字段必须用 0 填充到字节。

  1. 类型: 16 (init)

  2. 数据:

    • [u16:gflen]
    • [gflen*byte:globalfeatures]
    • [u16:flen]
    • [flen*byte:features]
    • [init_tlvs:tlvs]
  3. tlv_stream: init_tlvs

  4. 类型:

    1. 类型: 1 (networks)
    2. 数据:
      • [...*chain_hash:chains]
    3. 类型: 3 (remote_addr)
    4. 数据:
      • [...*byte:data]

可选的 networks 指示节点感兴趣的链。 可选的 remote_addr 可用于绕过 NAT 问题。

要求

发送节点:

  • 必须init 作为任何连接的第一条 Lightning 消息发送。
  • 必须按照 BOLT #9 中的定义设置功能位。
  • 必须将任何未定义的功能位设置为 0。
  • 不应该globalfeatures 中大于 13 的功能设置为 0。
  • 应该使用表示 features 字段所需的最小长度。
  • 应该networks 设置为其将传播流言或打开通道的所有链。
  • 如果节点是接收者并且连接是通过 IP 完成的,则应该设置 remote_addr 以反映传入连接的远程 IP 地址(和端口)。
  • 如果设置了 remote_addr
    • 必须将其设置为有效的 address descriptor(1 字节类型和数据),如 BOLT 7 中所述。
    • 不应该将私有地址设置为 remote_addr

接收节点:

  • 必须等待接收 init,然后再发送任何其他消息。
  • 必须将两个功能位图组合(按位或)成一个逻辑 features 映射。
  • 必须按照 BOLT #9 中的指定响应已知的功能位。
  • 收到未知的_奇数_非零功能位时:
    • 必须忽略该位。
  • 收到未知的_偶数_非零功能位时:
    • 必须关闭连接。
  • 收到 networks,但不包含常用链时:
    • 可以关闭连接。
  • 如果功能向量未设置所有已知的传递依赖项:
    • 必须关闭连接。
  • 可以使用 remote_addr 来更新其 node_announcement
理由

过去这里有两个功能位字段,但为了向后兼容,它们现在合并为一个。

这种语义允许将来不兼容的更改和将来向后兼容的更改。 位通常应成对分配,以便将来的可选功能可以变为强制性的。

节点等待接收对方的功能以简化功能不兼容时的错误诊断。

由于所有网络共享相同的端口,但大多数实现仅支持单个网络,因此 networks 字段可避免节点错误地认为它们将收到有关其首选网络的更新,或者它们可以打开通道。

errorwarning 消息

为了简化诊断,告诉对等方某些内容不正确通常很有用。

  1. 类型: 17 (error)

  2. 数据:

    • [channel_id:channel_id]
    • [u16:len]
    • [len*byte:data]
  3. 类型: 1 (warning)

  4. 数据:

    • [channel_id:channel_id]
    • [u16:len]
    • [len*byte:data]
要求

通道由 channel_id 引用,除非 channel_id 为 0(即,所有字节均为 0),在这种情况下,它引用所有通道。

使用通道建立 v1 (open_channel) 的资金节点:

  • 对于在 funding_created 消息之前(包括该消息)发送的所有错误消息:
    • 必须使用 temporary_channel_id 代替 channel_id

使用通道建立 v1 (accept_channel) 的被资助节点:

  • 对于在 funding_signed 消息之前(不包括该消息)发送的所有错误消息:
    • 必须使用 temporary_channel_id 代替 channel_id

使用通道建立 v2 (open_channel2) 的开启者节点:

  • 对于在收到 accept_channel2 消息之前发送的所有错误消息:
    • 必须使用 temporary_channel_id 代替 channel_id

使用通道建立 v2 (open_channel2) 的接受者节点:

  • 对于在 accept_channel2 消息之前(包括该消息)发送的所有错误消息:
    • 必须使用 temporary_channel_id 代替 channel_id

发送节点:

  • 应该针对协议违规或导致通道无法使用或导致进一步通信无法使用的内部错误发送 error
  • 应该在回复与未知通道相关的 32-255 类型的消息时,发送带有未知 channel_iderror
  • 发送 error 时:
    • 必须使错误消息引用的通道失效。
    • 可以channel_id 设置为全零,以指示所有通道。
  • 发送 warning 时:
    • 如果警告与特定通道无关,可以channel_id 设置为全零。
  • 可以发送一个空的 data 字段。
  • 当失败是由无效签名检查引起的时:
    • 应该在回复 funding_createdfunding_signedclosing_signedcommitment_signed 消息时,包含原始的、十六进制编码的交易。

接收节点:

  • 收到 error 时:
    • 如果 channel_id 全为零:
      • 必须使与发送节点的所有通道失效。
    • 否则:
      • 必须使 channel_id 引用的通道失效(如果该通道与发送节点之间存在)。
  • 收到 warning 时:
    • 应该记录该消息以供以后诊断。
    • 在此时允许的情况下,可以尝试 shutdown
  • 如果 channel_id 没有引用现有通道:
    • 必须忽略该消息。
  • 如果 data 不仅由可打印的 ASCII 字符组成(供参考:可打印字符集包括字节值 32 到 126,包括端值):
    • 不应该完全按照字面打印出 data
理由

存在不可恢复的错误,需要中止对话; 如果仅仅丢弃连接,则对等方可能会重试连接。 描述用于诊断的协议违规也很有用,因为这表明一个对等方存在错误。

另一方面,过度使用错误消息已导致实现忽略它们(以避免原本昂贵的通道中断),因此添加了“warning”消息以允许对虚假错误进行一定程度的重试或恢复。

可能明智的做法是在生产环境中不区分错误,以免泄漏信息 - 因此,添加了可选的 data 字段。

控制消息

pingpong 消息

为了允许长期存在的 TCP 连接的存在,有时可能需要在应用程序级别上使两端保持 TCP 连接的活动状态。 这样的消息还可以使流量模式混淆。

  1. 类型: 18 (ping)
  2. 数据:
    • [u16:num_pong_bytes]
    • [u16:byteslen]
    • [byteslen*byte:ignored]

每当收到 ping 消息时,都将发送 pong 消息。 它用作答复,还用于保持连接活动,同时明确通知另一端接收者仍处于活动状态。 在收到的 ping 消息中,发送者将指定要包含在 pong 消息的数据 payload 中的字节数。

  1. 类型: 19 (pong)
  2. 数据:
    • [u16:byteslen]
    • [byteslen*byte:ignored]
要求

发送 ping 消息的节点:

  • 应该ignored 设置为 0。
  • 不得ignored 设置为敏感数据,例如密钥或已初始化内存的片段。
  • 如果未收到相应的 pong
    • 可以关闭网络连接,
      • 并且在这种情况下不得使通道失效。

发送 pong 消息的节点:

  • 应该ignored 设置为 0。
  • 不得ignored 设置为敏感数据,例如密钥或已初始化内存的片段。

接收 ping 消息的节点:

  • 如果 num_pong_bytes 小于 65532:
    • 必须通过发送 pong 消息来响应,其中 byteslen 等于 num_pong_bytes
  • 否则(num_pong_bytes 小于 65532):
    • 必须忽略 ping

接收 pong 消息的节点:

  • 如果 byteslen 与其已发送的任何 pingnum_pong_bytes 值不对应:
    • 可以关闭连接。

理由

最大可能的消息是 65535 字节;因此,最大合理的 byteslen 为 65531 - 为了考虑类型字段 (pong) 和 byteslen 本身。 这允许 num_pong_bytes 的便捷截止值,以指示不应发送任何答复。

网络中节点之间的连接可能是长期的,因为支付通道具有不确定的生命周期。 但是,很可能在连接的生命周期的很大一部分时间内不会交换任何新数据。 另外,在某些平台上,Lightning 客户端可能会在没有事先警告的情况下进入睡眠状态。 因此,使用了不同的 ping 消息,以便探测另一侧连接的活跃性,并保持已建立的连接处于活动状态。

此外,发送者请求接收者发送具有特定字节数的响应的能力使网络上的节点能够创建 合成 流量。 这种流量可用于部分防御数据包和时间分析 - 因为节点可以伪造典型交换的流量模式,而无需对其各自的通道应用任何真正的更新。

当与 BOLT #4 中定义的 onion 路由协议结合使用时,小心地进行统计驱动的合成流量可以进一步增强网络中参与者的隐私。

建议采取有限的预防措施以防止 ping 泛洪,但是由于网络延迟会给予一定的灵活性。 请注意,还有其他传入流量泛洪的方法(例如,发送_奇数_未知消息类型,或最大程度地填充每个消息)。

最后,定期使用 ping 消息有助于促进 BOLT #8 中指定的频繁密钥轮换。

节点存储

peer_storagepeer_storage_retrieval 消息

声明了 option_provide_storage 功能的节点会为其对等节点提供存储任意数据的功能。 存储的数据不得超过 65531 字节,这使其可以放入闪电网络消息中。

节点可以通过比较检索到的数据的内容与上次发送的数据,来验证其 option_provide_storage 对等节点在每次重新连接时是否正确存储了其数据。 但是,节点不应期望对等节点始终具有其最新的可用数据。

节点使用 peer_storage 消息要求其对等节点存储数据,并期望对等节点使用 peer_storage_retrieval 消息将最新的数据返回给它们:

  1. 类型: 7 (peer_storage)

  2. 数据:

    • [u16:length]
    • [length*byte:blob]
  3. 类型: 9 (peer_storage_retrieval)

  4. 数据:

    • [u16:length]
    • [length*byte:blob]

要求:

peer_storage 的发送者:

  • 可以在必要时发送 peer_storage
  • 必须将其 blob 限制为 65531 字节。
  • 必须以确保在收到时确保其完整性的方式加密数据。
  • 应该填充 blob 以确保持其长度始终完全为 65531 字节。

peer_storage 的接收者:

  • 如果它提供了 option_provide_storage
    • 如果它与发送者有开放的通道:
      • 必须存储该消息。
    • 可以还是存储该消息。
  • 如果它确实存储了该消息:
    • 可以延迟存储以限制对等节点,使其不超过每分钟一次更新。
    • 必须将旧的 blob 替换为最新收到的 blob
    • 在交换 init 消息后,必须在重新连接后再次发送 peer_storage_retrieval

peer_storage_retrieval 的发送者:

  • 必须包括它为该对等节点存储的最后一个 blob
  • 当与该对等节点的所有通道都关闭时:
    • 应该在删除 blob 之前至少等待 2016 个区块。

peer_storage_retrieval 的接收者:

  • 当收到带有过时或不相关数据的 peer_storage_retrieval 时:
    • 可以发送警告。
理由:

peer_storagepeer_storage_retrieval 消息使节点能够安全地存储与网络中其他节点共享的数据,从而充当重要信息的备份机制。 通过利用它们,节点可以保护关键数据,从而提高网络的弹性和可靠性。 此外,即使我们没有开放的通道,某些节点也可以通过存储 peer_storage 来换取一些 sats 来提供此服务。

节点应填充 blob 以掩盖其实际大小,从而通过使基于大小的分析对接收者更加困难来增强隐私。

不应在 channel_reestablish 之后发送 peer_storage_retrieval,因为这样用户将无法恢复节点并在丢失数据的情况下更新其状态。

节点应在希望更新与其对等节点存储的 blob 时发送 peer_storage 消息。 该 blob 可用于分发加密数据,这可能有助于恢复节点。

附录 A:BigSize 测试向量

以下测试向量可用于断言 TLV 格式中使用的 BigSize 实现的正确性。 该格式与比特币中使用的 CompactSize 编码相同,但是用大端字节序替换了多字节值的小端字节序编码。

使用 BigSize 编码的值将根据整数的大小生成 1、3、5 或 9 个字节的编码。 编码是一个分段函数,它采用一个 uint64x 并生成:

        uint8(x)                if x &lt; 0xfd
        0xfd + be16(uint16(x))  if x &lt; 0x10000
        0xfe + be32(uint32(x))  if x &lt; 0x100000000
        0xff + be64(x)          otherwise.

此处 + 表示串联,be16be32be64 分别生成 16 位,32 位和 64 位整数的输入的大端编码。

如果一个值不能用更少的字节编码,则称该值被最小编码。 例如,一个占用 5 个字节但其值小于 0x10000 的 BigSize 编码不是最小编码的。 应该检查使用 BigSize 解码的所有值,以确保它们已最小编码。

BigSize 解码测试

以下是如何执行 BigSize 解码测试的示例。

func testReadBigSize(t *testing.T, test bigSizeTest) {
        var buf [8]byte 
        r := bytes.NewReader(test.Bytes)
        val, err := tlv.ReadBigSize(r, &buf)
        if err != nil && err.Error() != test.ExpErr {
                t.Fatalf("expected decoding error: %v, got: %v",
                        test.ExpErr, err)
        }

        // 如果我们期望一个解码错误,那就没必要检查这个值了。
        if test.ExpErr != "" {
                return
        }

        if val != test.Value {
                t.Fatalf("expected value: %d, got %d", test.Value, val)
        }
}
```一个正确的实现应该通过这些测试向量:
```json
[
    {
        "name": "zero",
        "value": 0,
        "bytes": "00"
    },
    {
        "name": "one byte high",
        "value": 252,
        "bytes": "fc"
    },
    {
        "name": "two byte low",
        "value": 253,
        "bytes": "fd00fd"
    },
    {
        "name": "two byte high",
        "value": 65535,
        "bytes": "fdffff"
    },
    {
        "name": "four byte low",
        "value": 65536,
        "bytes": "fe00010000"
    },
    {
        "name": "four byte high",
        "value": 4294967295,
        "bytes": "feffffffff"
    },
    {
        "name": "eight byte low",
        "value": 4294967296,
        "bytes": "ff0000000100000000"
    },
    {
        "name": "eight byte high",
        "value": 18446744073709551615,
        "bytes": "ffffffffffffffffff"
    },
    {
        "name": "two byte not canonical",
        "value": 0,
        "bytes": "fd00fc",
        "exp_error": "decoded bigsize is not canonical"
    },
    {
        "name": "four byte not canonical",
        "value": 0,
        "bytes": "fe0000ffff",
        "exp_error": "decoded bigsize is not canonical"
    },
    {
        "name": "eight byte not canonical",
        "value": 0,
        "bytes": "ff00000000ffffffff",
        "exp_error": "decoded bigsize is not canonical"
    },
    {
        "name": "two byte short read",
        "value": 0,
        "bytes": "fd00",
        "exp_error": "unexpected EOF"
    },
    {
        "name": "four byte short read",
        "value": 0,
        "bytes": "feffff",
        "exp_error": "unexpected EOF"
    },
    {
        "name": "eight byte short read",
        "value": 0,
        "bytes": "ffffffffff",
        "exp_error": "unexpected EOF"
    },
    {
        "name": "one byte no read",
        "value": 0,
        "bytes": "",
        "exp_error": "EOF"
    },
    {
        "name": "two byte no read",
        "value": 0,
        "bytes": "fd",
        "exp_error": "unexpected EOF"
    },
    {
        "name": "four byte no read",
        "value": 0,
        "bytes": "fe",
        "exp_error": "unexpected EOF"
    },
    {
        "name": "eight byte no read",
        "value": 0,
        "bytes": "ff",
        "exp_error": "unexpected EOF"
    }
]

BigSize编码测试

以下是如何执行BigSize编码测试的示例。

func testWriteBigSize(t *testing.T, test bigSizeTest) {
        var (
                w   bytes.Buffer
                buf [8]byte
        )
        err := tlv.WriteBigSize(&w, test.Value, &buf)
        if err != nil {
                t.Fatalf("unable to encode %d as bigsize: %v",
                        test.Value, err)
        }

        if bytes.Compare(w.Bytes(), test.Bytes) != 0 {
                t.Fatalf("expected bytes: %v, got %v",
                        test.Bytes, w.Bytes())
        }
}

一个正确的实现应该通过以下测试向量:

[
    {
        "name": "zero",
        "value": 0,
        "bytes": "00"
    },
    {
        "name": "one byte high",
        "value": 252,
        "bytes": "fc"
    },
    {
        "name": "two byte low",
        "value": 253,
        "bytes": "fd00fd"
    },
    {
        "name": "two byte high",
        "value": 65535,
        "bytes": "fdffff"
    },
    {
        "name": "four byte low",
        "value": 65536,
        "bytes": "fe00010000"
    },
    {
        "name": "four byte high",
        "value": 4294967295,
        "bytes": "feffffffff"
    },
    {
        "name": "eight byte low",
        "value": 4294967296,
        "bytes": "ff0000000100000000"
    },
    {
        "name": "eight byte high",
        "value": 18446744073709551615,
        "bytes": "ffffffffffffffffff"
    }
]

附录 B: Type-Length-Value测试向量

以下测试假定存在两个独立的TLV命名空间:n1 和 n2。

n1 命名空间支持以下 TLV 类型:

  1. tlv_stream: n1
  2. 类型:
    1. type: 1 (tlv1)
    2. data:
      • [tu64:amount_msat]
    3. type: 2 (tlv2)
    4. data:
      • [short_channel_id:scid]
    5. type: 3 (tlv3)
    6. data:
      • [point:node_id]
      • [u64:amount_msat_1]
      • [u64:amount_msat_2]
    7. type: 254 (tlv4)
    8. data:
      • [u16:cltv_delta]

n2 命名空间支持以下 TLV 类型:

  1. tlv_stream: n2
  2. 类型:
    1. type: 0 (tlv1)
    2. data:
      • [tu64:amount_msat]
    3. type: 11 (tlv2)
    4. data:
      • [tu32:cltv_expiry]

TLV 解码失败

任何命名空间中的以下 TLV 流都应触发解码失败:

  1. 无效流: 0xfd

  2. 理由: type 截断

  3. 无效流: 0xfd01

  4. 理由: type 截断

  5. 无效流: 0xfd0001 00

  6. 理由: 非最小编码 type

  7. 无效流: 0xfd0101

  8. 理由: 缺少 length

  9. 无效流: 0x0f fd

  10. 理由: (length 截断)

  11. 无效流: 0x0f fd26

  12. 理由: (length 截断)

  13. 无效流: 0x0f fd2602

  14. 理由: 缺少 value

  15. 无效流: 0x0f fd0001 00

  16. 理由: 非最小编码 length

  17. 无效流: 0x0f fd0201 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

  18. 理由: value 截断

任何命名空间中的以下 TLV 流都应触发解码失败:

  1. 无效流: 0x12 00

  2. 理由: 未知的偶数 type。

  3. 无效流: 0xfd0102 00

  4. 理由: 未知的偶数 type。

  5. 无效流: 0xfe01000002 00

  6. 理由: 未知的偶数 type。

  7. 无效流: 0xff0100000000000002 00

  8. 理由: 未知的偶数 type。

命名空间 n1 中的以下 TLV 流应触发解码失败:

  1. 无效流: 0x01 09 ffffffffffffffffff

  2. 理由: 大于 n1tlv1 的编码长度。

  3. 无效流: 0x01 01 00

  4. 理由: n1tlv1amount_msat的编码不是最小的

  5. 无效流: 0x01 02 0001

  6. 理由: n1tlv1amount_msat的编码不是最小的

  7. 无效流: 0x01 03 000100

  8. 理由: n1tlv1amount_msat的编码不是最小的

  9. 无效流: 0x01 04 00010000

  10. 理由: n1tlv1amount_msat的编码不是最小的

  11. 无效流: 0x01 05 0001000000

  12. 理由: n1tlv1amount_msat的编码不是最小的

  13. 无效流: 0x01 06 000100000000

  14. 理由: n1tlv1amount_msat的编码不是最小的

  15. 无效流: 0x01 07 00010000000000

  16. 理由: n1tlv1amount_msat的编码不是最小的

  17. 无效流: 0x01 08 0001000000000000

  18. 理由: n1tlv1amount_msat的编码不是最小的

  19. 无效流: 0x02 07 01010101010101

  20. 理由: 小于 n1tlv2 的编码长度。

  21. 无效流: 0x02 09 010101010101010101

  22. 理由: 大于 n1tlv2 的编码长度。

  23. 无效流: 0x03 21 023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb

  24. 理由: 小于 n1tlv3 的编码长度。

  25. 无效流: 0x03 29 023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb0000000000000001

  26. 理由: 小于 n1tlv3 的编码长度。

  27. 无效流: 0x03 30 023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb000000000000000100000000000001

  28. 理由: 小于 n1tlv3 的编码长度。

  29. 无效流: 0x03 31 043da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb00000000000000010000000000000002

  30. 理由: n1node_id不是有效的点。

  31. 无效流: 0x03 32 023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb0000000000000001000000000000000001

  32. 理由: 大于 n1tlv3 的编码长度。

  33. 无效流: 0xfd00fe 00

  34. 理由: 小于 n1tlv4 的编码长度。

  35. 无效流: 0xfd00fe 01 01

  36. 理由: 小于 n1tlv4 的编码长度。

  37. 无效流: 0xfd00fe 03 010101

  38. 理由: 大于 n1tlv4 的编码长度。

  39. 无效流: 0x00 00

  40. 理由: n1 命名空间未知的偶数字段。

TLV 解码成功

任何命名空间中的以下TLV流都应正确解码,并被忽略:

  1. 有效流: 0x

  2. 解释: 空消息

  3. 有效流: 0x21 00

  4. 解释: 未知的奇数类型。

  5. 有效流: 0xfd0201 00

  6. 解释: 未知的奇数类型。

  7. 有效流: 0xfd00fd 00

  8. 解释: 未知的奇数类型。

  9. 有效流: 0xfd00ff 00

  10. 解释: 未知的奇数类型。

  11. 有效流: 0xfe02000001 00

  12. 解释: 未知的奇数类型。

  13. 有效流: 0xff0200000000000001 00

  14. 解释: 未知的奇数类型。

命名空间 n1 中的以下TLV流应正确解码,并具有此处给出的值:

  1. 有效流: 0x01 00

  2. 值: tlv1 amount_msat=0

  3. 有效流: 0x01 01 01

  4. 值: tlv1 amount_msat=1

  5. 有效流: 0x01 02 0100

  6. 值: tlv1 amount_msat=256

  7. 有效流: 0x01 03 010000

  8. 值: tlv1 amount_msat=65536

  9. 有效流: 0x01 04 01000000

  10. 值: tlv1 amount_msat=16777216

  11. 有效流: 0x01 05 0100000000

  12. 值: tlv1 amount_msat=4294967296

  13. 有效流: 0x01 06 010000000000

  14. 值: tlv1 amount_msat=1099511627776

  15. 有效流: 0x01 07 01000000000000

  16. 值: tlv1 amount_msat=281474976710656

  17. 有效流: 0x01 08 0100000000000000

  18. 值: tlv1 amount_msat=72057594037927936

  19. 有效流: 0x02 08 0000000000000226

  20. 值: tlv2 scid=0x0x550

  21. 有效流: 0x03 31 023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb00000000000000010000000000000002

  22. 值: tlv3 node_id=023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb amount_msat_1=1 amount_msat_2=2

  23. 有效流: 0xfd00fe 02 0226

  24. 值: tlv4 cltv_delta=550

TLV 流解码失败

任何将无效流附加到有效流的操作都应触发解码失败。

任何将编号较高的有效流附加到编号较低的有效流的操作都不应触发解码失败。

此外,命名空间 n1 中的以下 TLV 流应触发解码失败:

  1. 无效流: 0x02 08 0000000000000226 01 01 2a

  2. 理由: 有效的 TLV 记录,但排序无效

  3. 无效流: 0x02 08 0000000000000231 02 08 0000000000000451

  4. 理由: 重复的 TLV 类型

  5. 无效流: 0x1f 00 0f 01 2a

  6. 理由: 有效(已忽略)的 TLV 记录,但排序无效

  7. 无效流: 0x1f 00 1f 01 2a

  8. 理由: 重复的 TLV 类型(已忽略)

命名空间 n2 中的以下 TLV 流应触发解码失败:

  1. 无效流: 0xffffffffffffffffff 00 00 00
  2. 理由: 有效的 TLV 记录,但排序无效

附录 C:消息扩展

本节包含init消息上有效和无效扩展的示例。这些示例的基本init消息(没有扩展)是0x001000000000(所有功能均已关闭)。

以下init消息是有效的:

  • 0x001000000000: 没有提供扩展
  • 0x001000000000c9012acb0104: 扩展包含两个未知的奇数TLV 记录(类型为0xc90xcb

以下init消息是无效的:

  • 0x00100000000001: 扩展存在但被截断
  • 0x001000000000ca012a: 该扩展包含未知的偶数TLV记录(假设TLV类型0xca是未知的)
  • 0x001000000000c90101c90102: 扩展TLV流无效(重复的TLV记录类型0xc9

请注意,当消息签名时,扩展是签名字节的一部分。节点应存储扩展字节,即使它们不理解它们,以便能够正确验证签名。

附录 D:有符号整数测试向量

以下测试向量显示了如何使用大端 two's complement 对有符号整数(s8s16s32s64)进行编码。

[
    {
        "value": 0,
        "bytes": "00"
    },
    {
        "value": 42,
        "bytes": "2a"
    },
    {
        "value": -42,
        "bytes": "d6"
    },
    {
        "value": 127,
        "bytes": "7f"
    },
    {
        "value": -128,
        "bytes": "80"
    },
    {
        "value": 128,
        "bytes": "0080"
    },
    {
        "value": -129,
        "bytes": "ff7f"
    },
    {
        "value": 15000,
        "bytes": "3a98"
    },
    {
        "value": -15000,
        "bytes": "c568"
    },
    {
        "value": 32767,
        "bytes": "7fff"
    },
    {
        "value": -32768,
        "bytes": "8000"
    },
    {
        "value": 32768,
        "bytes": "00008000"
    },
    {
        "value": -32769,
        "bytes": "ffff7fff"
    },
    {
        "value": 21000000,
        "bytes": "01406f40"
    },
    {
        "value": -21000000,
        "bytes": "febf90c0"
    },
    {
        "value": 2147483647,
        "bytes": "7fffffff"
    },
    {
        "value": -2147483648,
        "bytes": "80000000"
    },
    {
        "value": 2147483648,
        "bytes": "0000000080000000"
    },
    {
        "value": -2147483649,
        "bytes": "ffffffff7fffffff"
    },
    {
        "value": 500000000000,
        "bytes": "000000746a528800"
    },
    {
        "value": -500000000000,
        "bytes": "ffffff8b95ad7800"
    },
    {
        "value": 9223372036854775807,
        "bytes": "7fffffffffffffff"
    },
    {
        "value": -9223372036854775808,
        "bytes": "8000000000000000"
    }
]

致谢

[ TODO: (roasbeef); fin ]

参考文献

  1. <a id="reference-1">http://www.unicode.org/charts/PDF/U2600.pdf&lt;/a>

作者

[ FIXME: 插入作者列表 ]

Creative Commons License <br> 本作品已获得 Creative Commons Attribution 4.0 International License 许可。

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

0 条评论

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