拆解閃電網絡發票格式:欄位結構、編碼方式與付款流程全解析

拆解閃電網絡發票格式:欄位結構、編碼方式與付款流程全解析
字号
【导读】從 HRP 前綴與金額單位、Bech32 編碼,到 TLV 欄位、ECDSA 簽名與九步付款流程,完整拆解閃電網絡發票(BOLT 11)的內部結構,並解答重複付款、發票過期與二維碼掃描失敗等常見疑問。

把一張閃電網絡發票複製到剪貼簿,你拿到的是一串看似毫無規律的字元。它可能短到一百多個字元,也可能長到上千個,中間夾著數字與大小寫字母。這串東西不需要連接任何伺服器就能被解讀,收款的節點也不必在線等你查詢。它本身就是一份離線合約,把收款人、金額、有效期限、路線提示與簽名全部打包在一起。

理解這份合約的內部結構,好處很實際。當錢包報錯「invoice expired」、付款卡在「找不到路徑」、或者對方抱怨收到的手續費比預期高,你能從欄位層面精準定位問題。以下從外層編碼一路拆到簽名驗證,再到付款方實際發生的動作與常見疑問解答。

前綴與金額:lnbc2500u 這段在講什麼

發票的開頭是 human readable part(簡稱 HRP)。它以 ln 開場,接著是貨幣標識,最後接上可選的金額。貨幣標識對應比特幣的不同網絡:bc 是主網,tb 是測試網,bcrt 是 regtest,sb 是 signet。所以 lnbc 開頭代表主網,lntb 開頭代表測試網。這個設計讓錢包在解碼之前就能判斷發票屬於哪條鏈,避免使用者把主網資金誤付到測試環境。

金額部分採用了 SI 前綴乘數,單位是比特幣(BTC)本身,而不是 satoshi。這一點經常讓人算錯,尤其是看到 m 的時候誤以為是「百萬(Million)」。實際上 m 是 milli,即千分之一(10⁻³)比特幣。

下表列出 HRP 裡的金額寫法對照。真實發票在 HRP 之後還有一個分隔用的字元 1,再往後才是資料區。

HRP 金額寫法 單位含義 實際金額 折合 satoshi
lnbc5 無單位後綴,整數比特幣 5 BTC 500,000,000 sat
lnbc2500m milli,10⁻³ 2.5 BTC 250,000,000 sat
lnbc2500u micro,10⁻⁶ 0.0025 BTC 250,000 sat
lnbc2500n nano,10⁻⁹ 0.0000025 BTC 250 sat
lnbc2500p pico,10⁻¹² 0.0000000025 BTC 0.25 sat(即 250 msat)

這裡有個容易踩到的混淆。HRP 與資料區之間要用一個字元 1 隔開,所以一張金額留空的發票,開頭看起來就是 lnbc1…,那個 1 是分隔符,不是一顆比特幣。反過來說,金額為 1 BTC 的發票,HRP 會寫成 lnbc1,後面再接上分隔符,整串開頭變成 lnbc11…。只看前幾個字元就下判斷,很容易把金額讀錯一個數量級,這也是為什麼解碼應該交給錢包而不是靠肉眼。

表格最後一行的 pico 寫法在實務上會踩到下限。閃電網絡的最小可表達單位是 millisatoshi(msat,即 0.001 satoshi),但路由節點普遍設有最低轉發金額,通常落在 1 satoshi 以上。一張 250 msat 的發票就算格式完全正確,也很難找到願意轉發的路徑。

HRP 裡的金額也可以整個省略,此時 HRP 就是 lnbc,接著直接接上分隔用的 1,所以字串仍然以 lnbc1 開頭。這種發票叫 amountless invoice(未指定金額發票),金額由付款方在錢包自行輸入。捐款頁、打賞按鈕常採用這種模式,但需要特別注意:amountless invoice 依然綁定單一 payment hash,本質仍是一次性的,每次收到款項後該發票就失效,後端系統必須動態為下一位付款人生成新發票,不能把同一張固定發票永久展示給多人付款。

Bech32:把位元切成人看得懂的字元

HRP 之後會出現一個分隔用的數字 1,往後就是資料區與六個字元的校驗碼。整段採用 Bech32 編碼,字元集固定為三十二個字:

qpzry9x8gf2tvdw0s3jn54khce6mua7l

為什麼是這三十二個?因為 32 等於 2 的五次方,每個字元恰好承載 5 位元(5-bit group),沒有任何空間浪費。原始資料以 8 位元(byte)為一個位元組,編碼時要重新切成 5 位元一組,這個動作叫 convertbits。切完之後,原本的位元組邊界會錯開,末端不足 5 位元的部分用 0 補齊。這就是為什麼發票裡某些欄位的長度看起來很不直觀。

閃電網絡發票格式的結構拆解示意圖,包含前綴、資料編碼區塊與末端校驗段落
閃電網絡發票格式由前綴、資料區與校驗碼組成,資料區需重組為五位元單元以完成編碼

拿 payment hash 做例子。它是一個 32 位元組的雜湊值,共 256 位元。切成 5 位元一組之後是 51.2 組,向上取整成 52 個 5 位元群組,最後一組補了 4 個 0。所以在規格書上,這個欄位的長度就固定寫成 52。反過來說,任何長度標示為 52 的欄位,解碼後都會還原成 32 位元組。payment secret、description hash 全都是同樣的算法。

末尾那六個校驗字元由 BCH 多項式產生。BIP 173 對這套校驗的說明聚焦在偵測能力:四個字元以內的錯誤保證會被發現,超過這個範圍則只能靠機率攔截。對於複製貼上、螢幕截圖 OCR、手動輸入這些場景,這個能力相當實用。多數錢包解碼失敗時只會說「校驗失敗」,不會告訴你錯在哪一個字元,因為它本來就不是以錯誤更正為目標設計的。

大小寫的規則值得單獨講。Bech32 允許全大寫,也允許全小寫,唯獨禁止大小寫混合。一張發票裡只要有一個字母的大小寫與其他字母不一致,整串就無效。這個限制看似嚴格,其實跟二維碼高度相關:QR code 的 alphanumeric(字母數字)模式只接受大寫字母與數字,用這個模式可以把每個字元的體積成本降到約 5.5 位元,比 byte 模式的 8 位元省下將近三成空間。BIP 173 規範中關於大小寫與二維碼模式的說明就指出,改用 alphanumeric 模式能讓同一個二維碼版本多容納約四成五的字元。不少錢包在生成二維碼之前會先把發票整串轉成大寫,藉此降低 QR 版本密度,讓舊手機鏡頭更容易辨識。

順帶釐清一個容易混淆的地方。Taproot 地址用的是 Bech32m,校驗常數與原本的 Bech32 不同,兩者不能互通,BIP 350 對 Bech32m 的定義就是為 v1 以上的隔離見證輸出另立一套校驗。閃電網絡發票與 BOLT 12 的 offer 字串均沿用原本的 Bech32。看到 bc1p 開頭的鏈上地址時不要期待它能被當成閃電網絡發票處理。

資料區的骨架:時間戳加帶標籤欄位

資料區的第一段是 35 位元的時間戳,切出來剛好是 7 個 5 位元群組。它記錄的是這張發票的生成時間(UNIX 紀元起算的秒數)。35 位元能表達的秒數超過 343 億秒,換算下來約 1088 年,從 1970 年起算可以撐到公元 3059 年前後,在工程實務上完全夠用。

時間戳跟著就是一連串的帶標籤欄位,每個欄位由三部分組成:1 個 5 位元群組放 tag 值,2 個 5 位元群組放資料長度,然後才是資料本體。這種 tag-length-value(TLV) 的排列方式讓解碼器可以逐段讀取,遇到不認識的 tag 就按照長度跳過,而不是把整張發票判成無效。新欄位可以陸續加入規範而舊錢包仍能正常解碼其餘部分,靠的就是這條規則。

字元 tag 值 名稱 資料長度(5-bit 數) 用途與屬性
p 1 payment_hash 52 付款憑證的雜湊,整筆付款的核心(必填)
s 16 payment_secret 52 多路徑付款的配對憑證,防止付款探測
d 13 description 可變 收款方留言,明文 UTF-8 描述(與 h 二選一)
n 19 payee_pubkey 53 收款節點的 33 位元組壓縮公鑰(可選欄位)
h 23 description_hash 52 長描述的 SHA-256 雜湊,付款方自行核對原文
x 6 expiry 可變 有效秒數,省略時預設 3600 秒(1 小時)
c 24 min_final_cltv_expiry 可變 最後一跳保留的區塊數,省略時預設 18 塊
f 9 fallback_address 可變 鏈上退回地址,閃電通道走不通時的備案
r 3 route_hint 可變 私有通道路由提示,每一跳固定佔 51 位元組
9 5 features 可變 功能位元,宣告節點支援的協議擴展

payment_hash 與一次性質

payment_hash 是收款方隨機產生的一個 32 位元組秘密值(preimage)的 SHA-256 雜湊。收款方保管 preimage 原文,付款方則將該雜湊放入 HTLC 路由合約中。收款方要解鎖結算資金,必須在鏈下廣播揭示 preimage。這個機制同時解決了兩件事:付款方不用事先信任收款方,收款方一旦揭示 preimage 兌現,也就為付款方提供了無可爭辯的付款收據。

由於 preimage 一旦在網絡中公開,同一張發票的 payment_hash 就被永久消耗掉了,所以閃電網絡發票天生具備一次性特質。同一張發票若發起第二次付款,收款節點會直接拒絕,或因為找不到相應的未結算 HTLC 而失敗。

description 與 description_hash 的二選一規則

短備註直接塞進 d 欄位,付款方錢包會直接解碼顯示。若備註文字較長,發票字串會急劇膨脹導致二維碼過密難以辨識,此時規範建議改用 h 欄位放置長說明的雜湊值,原文透過 HTTPS 訂單系統等額外管道傳給付款方。付款方錢包拿到原文後自行計算 SHA-256,與發票中的 h 比對一致後再提示確認。兩者在規範中屬於互斥設定,不應在同一張發票中同時存在。

簽名與公鑰恢復:發票防篡改的核心

資料區讀完之後,尾端是一段長度為 104 個 5 位元群組的資料,還原成位元組剛好是 65 位元組。這是 secp256k1 ECDSA 簽名,結構包含 1 位元組的恢復標識(recovery id)以及各 32 位元組的 r 值與 s 值。

被簽署的內容是 HRP 與資料區的二進位資料組合(排除簽名本身與校驗碼)。驗證時,解碼器拿簽名與恢復標識執行公鑰恢復運算,反推出簽署節點的公鑰:

  • 若發票中包含 n 欄位,錢包會比對恢復出的公鑰是否與 n 欄位完全一致;
  • 若發票中省略了 n 欄位,錢包便直接將恢復出的公鑰作為該發票的收款節點公鑰,用於後續路徑規劃。

公鑰恢復機制的優勢在於節省空間。簽名本身無需攜帶額外的公鑰證書,既保證了整張發票的資料不可篡改,又壓縮了字串體積。只要有人在傳輸過程中改動了金額、過期時間或收款 hash,簽名驗證便會徹底失敗。

安全性提醒:驗證簽名只能保證發票確實由對應公鑰的節點簽署且未被竄改,無法證明該節點屬於可信實體。釣魚網站同樣能簽署出格式完美的發票,只是資金會流向攻擊者。核對收款人與網站憑證依舊不可省略。

付款流程:從掃碼到 preimage 回傳的九個步驟

當錢包讀取到一張發票時,底層會依序執行以下標準付款週期:

  1. 字串解碼:解碼 Bech32 字元集,核對 BCH 校驗碼,提取出 35 位元時間戳與各 TLV 帶標籤欄位。
  2. 驗證簽名:執行 ECDSA 公鑰恢復,確認簽名有效,並與可選的 n 欄位對齊。
  3. 檢查時效:將時間戳加上 x 欄位的過期秒數,與當前系統時間對比,過期則中止操作。
  4. 特性協商:讀取 9 欄位的功能位元,確認是否支援多路徑付款(MPP)或路由擴展特性。
  5. 路徑計算:結合本地 gossip 網絡拓撲圖與發票中的 r 路由提示,搜尋兼顧手續費、容量與 CLTV 延遲的最優跳轉路徑。
  6. 洋蔥封包封裝:使用 Sphinx 協定為路徑上的每一跳節點分層加密指令,各節點僅能解開屬於自己的下一跳轉發指示。
  7. HTLC 建立:沿路徑逐跳鎖定 HTLC,確保每跳的手續費扣除與 CLTV 差值符合規範,抵達終點時剩餘區塊數滿足 c 欄位要求。
  8. 終端驗證與結算:收款節點驗證 payment_hash 與 payment_secret,金額無誤後公開揭示 preimage。
  9. 反向劃轉:preimage 沿通道鏈路反向傳遞,逐跳觸發資金正式結算,付款方收到 preimage 後驗證 SHA-256 雜湊,交易確認終止。

在多路徑付款(MPP)場景下,收款方會透過相同的 payment_secret 收集多條子路徑傳來的金額,待全額到齊後再一次性揭示 preimage。若其中某條分片路徑超時失敗,整筆交易會安全回滾退款。需要釐清的是:閃電網絡中失敗取消的 HTLC 不會扣除路由手續費,付款方不會承擔手續費損失,主要成本僅為暫時鎖定通道流動性的等待時間。

閃電網絡發票格式在節點間的路由轉發與密鑰驗證結算流程
付款端藉由發票哈希在路由通道建立合約,收款端揭示秘密值後即刻完成鏈下結算

路由提示:私有通道的引路導航

公有通道的資訊會透過 gossip 協定廣播至全網,任何節點都能在拓撲圖中檢索到。然而,多數手機錢包或輕節點使用私有通道(unannounced channels),這些通道在鏈上存在,但在閃電網絡公共拓撲圖中不可見。若收款節點的所有進帳通道都是私有的,外部節點便無法計算出通往該節點的路徑。

r 欄位正是為了解決私有通道尋路問題而生。每個路由提示佔 51 位元組,包含 33 位元組的上一跳公鑰、8 位元組 short_channel_id、4 位元組基礎手續費、4 位元組費率比例與 2 位元組 CLTV 差值。一張發票可以包含多條提示,為付款方提供多條備選入場路徑。

路由提示並非強制路由命令,付款方具備最終選擇權。但若收款端處於私有通道環境且發票中遺漏了 r 欄位,付款端通常會直接遭遇「no route found(找不到路徑)」錯誤。

BOLT 11、BOLT 12 與閃電地址演進

現行主流的 BOLT 11 格式具備極佳的跨錢包相容性,但其一次性特質與收款方必須動態在線簽名的限制,對定期訂閱、線下固定打賞等場景帶來了協調成本。為克服這些限制,社群推出了多種可重複使用方案:

  • BOLT 12 Offers:採用 lno1 開頭的靜態要約字串,自身不綁定單一 payment hash,而是建立一套鏈下非同步請求協議(invoice request / invoice response),並天然整合盲化路徑(blinded paths)提升收款節點隱私。詳情可參閱 BOLT 12 規範定義。
  • 閃電地址(Lightning Address):採用類似電子郵件的格式(如 user@domain.com),上層透過 LNURL-pay 或 BOLT 12 協議向後端伺服器請求即時的 BOLT 11 發票,兼顧了用戶可讀性與相容性。

雖然 BOLT 12 與閃電地址正在逐步擴展其應用場景,但 BOLT 11 依然是閃電網絡生態中支撐基礎結算的通用標準格式。深入掌握 BOLT 11 發票結構,依然是排查各類閃電網絡付款異常的核心技能。

常見問題快速解答(FAQ)

Q1:一張閃電網絡發票可以重複付款兩次嗎?

不可以。BOLT 11 發票內部綁定了固定的 payment_hash。當第一筆付款成功時,收款節點已經在網絡中揭示了對應的秘密值(preimage),該 payment_hash 便被消耗完畢。若發起第二次付款,收款節點無法再以相同的秘密值安全結算,錢包或路由節點會直接拒絕該請求。需要多次收款時,每次都必須生成獨立的新發票。

Q2:發票過期後還能強制付款嗎?過期時間是由誰決定的?

不能。發票的有效期限由收款節點在生成時寫入 x 欄位(預設為 3600 秒,即 1 小時)。付款端錢包在解析發票時會嚴格校驗目前時間是否超出「時間戳 + expiry」。一旦過期,合規的錢包會在本地直接終止發送,避免資金在網絡中遭遇不可控的路由鎖定風險。

Q3:為什麼掃描某些發票二維碼時特別容易失敗?

二維碼掃描辨識率與發票字元長度直接相關。如果發票中包含了長文本備註(d 欄位)或多條私有路由提示(r 欄位),字串長度可能突破五百字元,造成二維碼密度過高(模組點極小)。在暗光、反光或低階鏡頭下掃描極易失敗。最佳化方式是改用 h(description_hash)縮減字串,或在二維碼編碼前將英文字母全部轉換為大寫以啟用 QR code 的 alphanumeric 高效壓縮模式。

Q4:為什麼金額為整數 1 BTC 的發票前綴是 lnbc11?

這是 Bech32 分隔符與金額前綴交疊產生的視覺現象。在主網中,1 BTC 的金額寫法為 lnbc1(無單位乘數後綴代表整數 BTC),而 HRP 與資料區之間必須由一個固定的分隔符 1 分開,兩者拼接在一起便構成了 lnbc11...。這也提醒了使用者在閱讀發票時切勿僅憑肉眼猜測前幾個字元,應統一交由標準錢包解析。

admin

作者负责资讯整理、核查与撰写。 · 已发布 93 篇文章

查看作者全部文章