發生什麼事: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 描述推導的建議,不代表官方認定曾有用戶受影響。




