TronGreen

交易與支付基礎設施閃電網路 Loop

Loop v0.35.2 修補 Loop In 退款競態,LiT 0.17.8 已先內含

Lightning Labs 於 10 月 22 日(UTC)發布 Loop v0.35.2-beta。

對照圖:左側為 Loop v0.35.1 以前的 Loop In 逾時退款順序,先廣播退款、退款確認後才取消發票;右側為 v0.35.2-beta,先呼叫 CancelInvoice,取消成功才退款、已結清則擋下退款。
Loop In 逾時退款修正前後的步驟順序。TronGreen 依 lightninglabs/loop PR #1241 說明自製圖表。

核心修正是 Loop In 逾時退款:舊版在 HTLC 到期時先廣播退款、等退款確認後才取消換入發票,中間若伺服器付款成功,就可能出現發票已結清、HTLC 卻被退回的情況;新版改為退款前先呼叫 lnd 的 CancelInvoice。同版也收緊 MuSig2 Loop In 交出內部金鑰的條件,並修正靜態地址換入在 Postgres 非 UTC 時區下付款期限錯位、未付款 HTLC 不退款等問題。Lightning Terminal(LiT)v0.17.8 內附的 Loop 原始碼版本已含這些修正。

重點摘要

自行跑 loopd 的節點應升級到 v0.35.2-beta;LiT 用戶升到 v0.17.8 即已含同批 Loop 修正,但要留意它內附的 lnd 是 v0.21.5-beta.rc1。官方說明未提到任何實際損失事件。

文章目錄

發生什麼事:Loop In 的退款與付款可能撞在一起

Lightning Loop 是 Lightning Labs 的非託管換幣服務,讓閃電節點在鏈上資金與通道餘額之間互換。其中 Loop In 是「鏈上換進閃電」:客戶端先把比特幣鎖進一個鏈上 HTLC(雜湊時間鎖合約),再開一張換入發票給伺服器付款;伺服器付款後取得原像,就能領走 HTLC。若伺服器一直沒付,HTLC 到期後客戶端可走逾時路徑把錢退回。

Lightning Labs 在 10 月 22 日(UTC)發布 v0.35.2-beta。發布說明寫明,舊版在 HTLC 到期時發布退款,但換入發票要等退款確認後才取消,「發票在退款確認前都還能被付款,因此晚到的付款可能在 HTLC 退款時結清」。合併這項修正的 PR #1241 進一步說明:中間到達的伺服器付款會讓發票結清並把原像交給伺服器,而客戶端的退款仍可能搶先領走 HTLC。PR 也提到,重啟後的第一次退款嘗試,甚至在客戶端還沒檢查發票狀態前就會執行。

這種情況代表同一筆換幣兩邊都動了錢:閃電端已付款,鏈上 HTLC 卻被退回。依 PR 的描述,吃虧的是已付款卻拿不到 HTLC 的伺服器,客戶端帳面也會跟實際結果不一致。發布說明與 PR 都沒有提到任何實際損失事件。

怎麼修:先取消發票,再決定退不退款

新版在任何退款之前,先呼叫 lnd 的 CancelInvoice。PR 說明這個呼叫和發票結清是原子性的二選一:取消成功,或 lnd 已找不到這張發票(lnd 只會刪除已取消的發票),才發布退款;若發票其實已經結清,就擋下退款,把換幣狀態改為 InvoiceSettled,並從發票讀取實際收到的金額;遇到其他錯誤則不下結論,等下一個區塊再試。

同一個 PR 還修了第二個問題。MuSig2 版本的 Loop In 會在換幣完成後把客戶端的內部 HTLC 金鑰交給伺服器,讓伺服器走較便宜的金鑰路徑領款;但拿到這把金鑰,伺服器也能自行花掉 HTLC。舊版只要發票進入「完成」狀態就交出金鑰,而取消也算完成。正常流程裡發票是在退款確認後才取消,所以交出金鑰已無作用;可是若有人在 HTLC 尚未花費時手動取消發票,伺服器就會拿到一筆從未付款的 HTLC 的金鑰。新版改成只有發票確定結清才交出金鑰,這也是「先取消再退款」能安全成立的前提。

靜態地址換入的兩個問題

v0.35.2-beta 也修正靜態地址(static address)換入。PR #1252 指出,靜態換入的資料表以本地時間而非 UTC 寫入風險決策、發起與更新時間;Postgres 的 TIMESTAMP 欄位會丟掉時區,所以在非 UTC 時區讀回來會偏移。付款期限從風險決策時間起算,因此在 UTC 以東會變晚,在 UTC 以西則可能一開始就已過期。PR 作者在 America/New_York 時區的 Postgres 環境重現:伺服器接受風險後 0 秒,發票就被取消。SQLite 通常能正確讀回,但像伊朗(+0330)、尼泊爾(+0545)這類以數字顯示偏移的時區則完全讀不回。新版改寫 UTC,升級前存下的時間維持原本偏移,換幣的最新狀態改以資料列 id 判斷。

另一個問題來自 PR #1240:部分程式碼操作的是從資料庫載入的副本,而不是記憶體中的即時存款物件。其中一個後果是,未付款的靜態地址 HTLC 到期後,狀態機一直重試轉換卻失敗,HTLC 永遠不會被退款。發布說明也寫到,已選存款的換入報價與 ListUnspentDeposits 現在都改看即時追蹤中的存款。

誰該升級、LiT 用戶要注意什麼

v0.35.2-beta 同時把最低 Go 版本提高到 1.27.3,以納入 CVE-2026-111943 的 crypto/x509 中繼憑證不信任修補,並更新 klauspost/compress 到 v1.18.7,修補 s2.NewDict 的越界讀取。Go 程式若直接呼叫匯出的 utils.MuSig2Sign,需要改成傳入原始 32 位元組私鑰。前一版 v0.35.1-beta(10 月 20 日)則已要求 Instant Out 驗證伺服器發票的手續費上限。

Lightning Terminal(LiT)v0.17.8 在 10 月 22 日發布,內附的 Loop 不是正式版號,而是原始碼提交 333ed1efccdd。比對 GitHub 紀錄,這個提交到 v0.35.2-beta 標籤之間只差一個調整 LoopRPC 模組版本的提交,所以上述換入與靜態地址修正都已包含在 LiT v0.17.8 裡。不過 LiT v0.17.8 內附的 lnd 是 v0.21.5-beta.rc1,仍是候選版;LiT 用戶升級前應先備份資料庫,因為內附的 Loop 會在啟動時套用一個新增鏈上成本欄位的 SQL 遷移。

AI 觀點分析與推論

AI 觀點:換幣服務的安全邊界常在「狀態轉換的順序」

這次修正的共同點,不是密碼學被破解,而是「先做哪一步」錯了:先退款後取消發票、只要發票完成就交出金鑰、用本地時間算付款期限。每一步單獨看都合理,組合起來才出現兩邊都動錢或 HTLC 卡住的窗口。對任何跨鏈上與閃電的原子換幣,退款前先讓對方付款路徑失效,應該是基本原則。

對節點營運者來說,最實際的檢查是三件事:loopd 是否已到 v0.35.2-beta 或 LiT v0.17.8;若用 Postgres 且伺服器不在 UTC 時區,留意升級前建立的靜態換入時間仍是偏移的;以及若曾手動取消過 Loop In 發票,回頭核對那些 HTLC 的最終去向。這些都是根據 PR 描述推導的建議,不代表官方認定曾有用戶受影響。

原始來源與延伸閱讀

  1. lightninglabs/loop GitHub:v0.35.2-beta 發布頁 ↗官方發布說明 · 原始發布 2026-10-22 · 查閱 2026-10-24
  2. lightninglabs/loop PR #1241:loopin: cancel the swap invoice before the timeout refund ↗原始碼合併紀錄 · 原始發布 2026-10-21 · 查閱 2026-10-24
  3. lightninglabs/loop PR #1252:staticaddr: store static loop-in times in UTC ↗原始碼合併紀錄 · 原始發布 2026-10-21 · 查閱 2026-10-24
  4. lightninglabs/loop PR #1240:staticaddr: act on the live deposit objects ↗原始碼合併紀錄 · 原始發布 2026-10-21 · 查閱 2026-10-24
  5. lightninglabs/lightning-terminal GitHub:v0.17.8 發布頁 ↗官方發布說明 · 原始發布 2026-10-22 · 查閱 2026-10-24
  6. lightninglabs/loop GitHub:v0.35.1-beta 發布頁 ↗官方發布說明 · 原始發布 2026-10-20 · 查閱 2026-10-24

更正與更新

本文發布後沒有更新。之後若有修改,會建立新版本並在這裡標示更新時間。

內容有誤?請見更正方式,或寄信至 [email protected](請附文章網址、段落與參考資料)。

延伸閱讀

更多 BTC 新聞