拆开一串 lnbc:BOLT11 发票的字段、默认值与校验位 图 1
拆开一串 lnbc:BOLT11 发票的字段、默认值与校验位 · 图 1

一串乱码的三层结构

闪电网络的第一代发票(BOLT11 规范定义)是一串 bech32 编码字符串,看起来像随机字符,实际分三段:人类可读部分、数据部分、校验位。可读部分由网络前缀(主网 bc、测试网 tb 等)加可选的金额组成;金额用比特币的 SI 乘数表达,munp 分别对应毫、微、纳、皮比特币,因此 0.0001 枚比特币写成 100u。不写金额就是”金额任意”发票,付款时才决定数额。数据部分是一串标签字段,最后附发票签发者的签名;整个字符串末尾六位校验码覆盖全部内容,抄错任何字符都会在解码器面前当场暴露——和比特币地址同一套 bech32 家族保护。

必备字段与默认值

发票里真正的硬要求只有 payment_hash:一个 32 字节的随机哈希,它是整条支付路径的锁钥,收款方以能否凑出对应原像判断款项归属。时间戳字段记录发票生成时刻,配合过期时间字段(expiry,单位秒)决定发票何时作废;规范规定缺省过期值为一小时,所以长期有效的收款需求必须显式写大这个字段。金额在可读部分与数据部分各有一份,两者矛盾时解码器应当报错。

其余字段各管各的:descriptiondescription_hash 承载订单号之类的备注(长文本用哈希形式,字符串本体另经路由层携带);payment_secret 是收款方额外加的一把锁,早期用于抵御路由探测,后来成为多部分支付(把大额拆成多份并行投递)的必要信号;min_final_cltv_expiry_delta 告诉付款方最后一跳要留多少区块的退款缓冲;features 位图声明收款方支持的功能,比如是否接受多部分支付。规范还有一条向前兼容铁律:解码遇到不认识的标签必须忽略而不是报错,新字段因此可以持续生长。

隐私上值得知道的两件事

第一,发票本身不含收款方 IP 与身份;但 payment_secret 会随付款在通道间出现,与你建立通道关联的节点能把”这张发票”与”某个付款人”对到同一条路径上,这是闪电的结构性隐私边界,与发票格式无关。第二,用描述哈希时,描述文本经由 HTLC 附加字段传输,链上是哈希,可路由节点仍可能读到明文描述——订单内容不该放敏感信息。

实操:三步读一张发票

解码用任意 bech32 工具或节点命令行(如 lncli decodepayreq)即可。第一步看金额与网络前缀:前缀错链意味着这笔发票在别的网络根本解不开,是资金丢失防线之一;第二步看过期与时间戳:对”这张单据还有效吗”给出答案,多数钱包对过期发票会拒绝支付;第三步看特性位与 payment_secret:金额偏大而发票没带多部分支付信号时,大额拆分会被拒,表现为付款方莫名换路径重试。校验位报”checksum failed”通常就是抄写或截图转写错了一个字符,重新扫码而不是手改。把字符串当条形码读,别把它当乱码跳过,字段都会说话。

手写与机器读的边界

值得强调的是:BOLT11 从未打算让人手写或肉眼核对。bech32 的数据段用五比特分组编码十六进制内容,肉眼看不出 payment_hash 的值;金额字段虽可读,但 10m10000u 是同值不同形的合法写法。工程上唯一可靠的做法是把解码交给库或节点,程序里只消费字段对象:校验网络位与自身配置一致、金额非负且不超过通道容量、过期时间在业务容差内、签名验证指向发票声明的公钥(签名恢复出的公钥只需与路由hints或联系人绑定核对,发票签名本身不证明身份)。任何”在界面上把长字符串拆出来正则匹配金额”的实现都在重复早年钓鱼站踩过的坑:正规解码器会说这条发票与那半行正则说的不是同一件事。