
The Bitcoin Lightning Network (LN) improves scalability by moving payments off-chain, which changes both performance and the security model. On Bitcoin Layer 1 (L1), security is mostly passive: once a transaction has enough confirmations, funds are safe, and you do not need to stay online to maintain that safety. On Lightning Layer 2 (L2), security is active. Nodes must remain online, react to time-sensitive events, and coordinate off-chain and on-chain actions under strict timing constraints.
Vulnerabilities in Lightning applications rarely stem from flaws in the underlying cryptographic primitives; rather, they emerge from fragile state management, timing irregularities, and incorrect system assumptions. A Lightning node is a hot wallet by design. A Lightning decentralized application (dApp) often needs to bridge two settlement domains: the Lightning invoice/HTLC world and the Bitcoin script/UTXO world. The moment you bridge domains, you inherit race conditions, partial finality (mempool vs confirmed), reorg realities, fee market variance, and complex failure recovery.
This post focuses on security areas that matter the most in real Lightning dApps. It is written from an audit perspective: what consistently causes loss of funds and stuck funds, common attack surfaces, and how developers can prevent them.
This post is part of our Bitcoin DeFi technical series. Explore more research and security insights across the Bitcoin ecosystem.
In the Lightning Network, a preimage is not merely data; it represents spending authority. Whoever has the preimage can complete a hashlock path, and in many swap designs, the preimage serves as the cryptographic link ensuring atomicity between the off-chain Lightning payment and the on-chain HTLC. A common developer oversight is treating the preimage as a passive receipt. In reality, it functions as a private key with a short lifecycle.
A secure preimage lifecycle has three requirements:
First, generate preimages with a cryptographically secure RNG and do not reuse them across operations. Reuse creates correlations (privacy leaks) and can introduce logic coupling between separate swaps or invoices. In swaps, reuse is especially dangerous because the same preimage may unlock multiple contracts.
Second, never reveal the preimage before the incoming value is irrevocably committed. “Irrevocably committed” is not always confirmed on-chain. In LN, it usually means the HTLC is locked into a commitment update that you can enforce if the other side disappears. In swap designs, it means you must not leak the preimage to a counterparty until you are sure that revealing it cannot give them a free option. A free option is one in which the other party can choose, after learning the preimage, whether to pay you. In reverse swaps, for example, the preimage is revealed on-chain when the user claims the HTLC. Your system must ensure that preimage revelation is tightly linked to invoice settlement (or at least to a state where settlement is guaranteed to occur once confirmations meet policy). If your system can detect a preimage but fails to settle the invoice due to a state machine bug, the user receives the on-chain funds but still times out on Lightning.
Third, protect the preimage in storage and logs. Many LN dApps have a web front-end or a mobile client that participates in signing or preimage handling. If a preimage or claim key is stored in plaintext in browser storage (IndexedDB/LocalStorage), any XSS, malicious extension, or injected dependency can exfiltrate it and take funds. This is not theoretical. If your design requires client-side persistence, you need encryption-at-rest with a key that the attacker cannot read from the same storage. If you cannot do that reliably, you should move preimage handling to hardened server components or use external wallets/hardware where possible.
This vulnerability in LND highlighted the risk of logic coupling between routing and payment settlement. An attacker (Mallory) could intercept a payment intended for a victim node (Bob) by creating a parallel, low-value circular route using the same hashlock. Due to a flaw in the database query, Bob's node mistakenly used the preimage to resolve the attacker's fake routing attempt.
This revealed the preimage to the attacker, allowing them to steal the original funds without fulfilling the payment. This incident enforces two key developer lessons:
From an audit perspective, preimage review is straightforward: identify every code path where a preimage is created, stored, logged, transmitted, or derived. Then prove that no path can be revealed before the correct economic safety condition holds. Most Lightning exploits are due to a preimage escaping at the wrong time, or a system failing to act after the preimage becomes observable.
In the Lightning Network, timestamps are not a utility; they are a primary enforcement mechanism. HTLCs, channel disputes, and on-chain contracts rely heavily on OP_CLTV (CheckLockTimeVerify) and OP_CSV (CheckSequenceVerify) semantics. If the timelock logic is flawed, the contract does not merely become unsafe—it fails to enforce the counterparty agreement at all.
A critical boundary condition in Bitcoin is the LockTime threshold. A notable edge is Bitcoin’s rule that locktime values greater than or equal to 500,000,000 are interpreted as UNIX timestamps rather than block heights. If your code assumes “locktime is a height” but allows values to cross that threshold, your on-chain HTLC can become instantly refundable because the timestamp is already in the past. This is a pure correctness bug: the script still executes, but it enforces a different time domain than intended. The right fix is simple but must be deliberate: enforce bounds on user-provided or config-provided deltas, and enforce them after any chain-specific conversions. If you support multiple chains or sidechains with different block intervals, you must re-check the derived lock value, not just the input delta.
L1 and L2 timelocks must be coordinated. In submarine swaps (on-chain → Lightning), the on-chain refund timelock must be long enough to allow the service to claim after paying the Lightning invoice, even under fee spikes and confirmation delays. In reverse swaps (Lightning → on-chain), the on-chain timeout and the Lightning HTLC expiry must be chosen such that the party who can claim on-chain cannot later escape paying on Lightning. This prevents a scenario in which the counterparty claims funds on-chain while simultaneously allowing the Lightning payment to expire, effectively stealing the funds.
Developers should not select arbitrary timelock values. Instead, implement a Safety Budget. This budget must cover block variance, mempool congestion, propagation delays, your own monitoring delays, and the confirmation depth you require before treating a spend as final. If your application requires two confirmations before settling a hold-invoice, those confirmations must be included in the budget. If you support fee bumping by CPFP (Child Pays for Parent), include the time required to detect a stuck transaction and bump it.
This vulnerability highlighted how sophisticated attackers use RBF to evict a victim’s timeout transaction from the mempool until the CLTV lock expires. Once the funds expire, the attacker claims them via the preimage path. This shows that simple timelock calculations are insufficient; developers must maintain a safety budget to account for adversarial behavior in the active mempool.
From an audit standpoint, the timelock section is where you must be extremely strict. “Seems safe” is not enough. You want explicit inequalities: L1 timeout must be greater than L2 expiry by at least X blocks; claim confirmation depth must be less than remaining timeout by at least Y blocks; derived CLTV must stay below 500,000,000 if interpreted as height; and so on. These inequalities become the basis for formal reasoning and test cases.
Lightning dApps are distributed systems. They observe events from LND (invoice state transitions), from chain indexers (new transactions and confirmations), and from their own persistence layer. Those events do not arrive in a clean, linear order. Adversaries exploit this by manipulating event sequencing, particularly during the interval between mempool acceptance and block confirmation.
A common failure mode is treating “unconfirmed” as a temporary state where anything can be overwritten, retried, or replaced without consequence. In swap systems, that is dangerous because unconfirmed data still has an identity. A funding outpoint in mempool is not final, but it is the anchor you must track if you are going to respond to a claim spend that might appear immediately. If your system allows the recorded funding transaction or funding outpoint to be overwritten by later noise (for example, a dust transaction sent to the same script/address), you can lose correlation between the real funding UTXO and the later spend that reveals the preimage. This can lead to two outcomes: either your service claims the wrong output and fails to claim the correct one, or it never settles the invoice because it “missed” the preimage event associated with the correct output.
Another failure mode is the state machine getting stuck because it expects confirmations before recording important facts. In reverse swaps, a user can claim an on-chain HTLC as soon as it appears in the mempool, not when it is confirmed. If your system ignores mempool spends until the funding is confirmed, you might miss the preimage-extraction window or fail to transition to the path that will later settle the invoice. The safe pattern is to separate “observation” from “finalization.” You can observe a claim in mempool, extract the preimage, and record “claim seen”, while still deferring invoice settlement until your confirmation policy is met. Observation must be robust to event order; finalization can be conservative.
A related trap is handling expiry. Many systems implement “timeout reached → refund + cancel invoice” logic. That is only safe if you prove that the on-chain HTLC output cannot still be claimed in a way that would entitle the counterparty to value. In many HTLC scripts, after expiry, both branches can be spendable: the refund branch becomes valid, but the hashlock branch might remain valid as well (since knowledge of the preimage does not disappear at expiry). If your application cancels a hold invoice at expiry without verifying whether the output is already spent (or about to be spent in mempool), a counterparty can outbid your refund with a higher-fee claim, take the on-chain funds, and still have the Lightning payment canceled. That is a classic “race at expiry,” and it is an application logic problem, not a Bitcoin script problem.
Auditing state machines in LN dApps is less about reading the enum values and more about proving invariants. A strong approach is to model the swap with “facts” rather than a single linear status. Facts include: funding outpoint selected, funding seen in mempool, funding confirmations, claim seen, claim confirmations, refund seen, refund confirmations, invoice accepted, invoice settled, invoice canceled, preimage extracted. Then you derive safe actions from the fact set. This style survives out-of-order events, restarts, and partial failures better than a strict linear status ladder.
Swaps operate on timescales of hours, whereas server processes operate on the order of milliseconds and can fail at any moment. A service might crash between building a transaction and broadcasting it, or between broadcasting and saving the resulting TXID to the database. Without rigorous design, these interruptions result in financial losses.
The hardest parts are the steps that create irreversible external side effects: broadcasting an on-chain transaction, settling a Lightning invoice, canceling a hold invoice, and publishing a refund. Each of those must be replay-safe. This does not simply mean the system "doesn't crash" on a retry. It means the system guarantees it will never execute the same financial action twice for the same logical swap, even across multiple restarts.
A robust safety pattern is to persist a unique execution marker identifying the intended action, perform the action, persist the result, and always reconcile on startup by querying external truth. For on-chain broadcasts, the marker is often the txid (once built) or a deterministic transaction template hash plus inputs. For hold invoice settlement, the marker might be settled_at or settlement_txid plus invoice hash. The key is that your system must treat a repeated call as a no-op once the marker exists, and must detect whether an action has already occurred by checking the chain or LND rather than relying solely on in-memory state.
Crash consistency becomes harder with horizontal scaling. If you run multiple instances, you must ensure only one instance “owns” a swap runner at a time. Otherwise, both can fund the same HTLC, both can attempt refunds, or both can try to settle/cancel invoices. In reverse swaps, this can result in a direct double payout: two on-chain HTLC fundings can be claimed with the same preimage, even though only one Lightning invoice is paid. Preventing this requires a cross-instance locking mechanism, usually a database lease with atomic acquisition, renewal, and expiry.
Crash consistency also affects user experience and recoverability. If your system marks a swap as “mismatched amount” and returns early without persisting the funding transaction details, later refund endpoints may not be able to build refund PSBTs because they lack the lock transaction and height information. This is not only a UX bug; it can become a safety bug if it prevents timely refunds. In Lightning systems, “can’t refund” is often “funds locked until timeout”, and if the timeout is long, that is a denial-of-service vector.
The most common user-induced loss in Lightning is restoring from an old channel.db backup. Broadcasting an old state triggers the counterparty’s "Justice Transaction," forfeiting all funds. This illustrates why cold recovery is critical for automated services.
On Lightning, a denial-of-service attack does not always manifest as “server down.” It can look like “liquidity locked.” A well-known class of attacks is channel jamming: attackers lock up HTLC slots or channel capacity without incurring meaningful fees. For a routing node, this is an economics and reliability problem. For a Lightning dApp that supports swaps or conditional payments, it can be both an economic and a safety problem if locked liquidity causes your system to miss timelock windows.
Griefing attacks exploit the fact that HTLCs occupy channel resources. A channel has a maximum number(483) of in-flight HTLCs, and each HTLC locks a portion of the channel balance until it resolves or times out. An attacker can route payments that intentionally fail late or never resolve, consuming HTLC slots. Even if the attacker eventually recovers their funds, your node may have been unable to route legitimate payments during the lock window.
This vulnerability demonstrated how attackers can fill all 483 HTLC slots with dust payments and then force-close the channel during high congestion. The victim cannot have their claim transactions confirmed before the timelocks expire, allowing the attacker to steal funds. This validates the need for strict resource limits and minimum HTLC values.
Swap services introduce new avenues for griefing because they often lock on-chain capital or hold invoices while waiting for the other side of the swap. If a user repeatedly triggers expensive swap setup work, generates on-chain scripts, forces chain watchers to track many addresses, or causes the service to pay fees for transactions that never complete, they can turn your system into a cost sink.
A specific but common griefing technique for swap-ins is “poisoning” the funding address. If your service watches for any transaction to a script/address and treats the first one as canonical, an attacker can send dust or mismatched amounts to trigger a swap error. If the system enters an error state and stops listening, it will fail to process a valid payment that arrives later. The general fix is to avoid “first transaction wins” logic. The system must filter noise and continue scanning the address until it identifies a UTXO that strictly matches the expected value and parameters.
Mitigation for griefing is not purely technical, but the core technical points are:
From an audit perspective, the dev team should verify that a single unauthenticated attacker cannot cheaply allocate unbounded resources. Check for safeguards against the exhaustion of HTLC slots, chain watcher subscriptions, and on-chain fee budgets. If an attacker can force the system to perform expensive work for free, the protocol is vulnerable.
Lightning dApps that interact with Layer-1 UTXOs must treat the Bitcoin fee market as adversarial. Timelocks and safety margins are only effective if transactions confirm within the allotted window. Consequently, your Confirmation Policy is a Security Policy.
A naive implementation assumes “once we broadcast X, we can do Y.” A secure implementation asserts, “once X has N confirmations, and we can still meet the deadline under current fee estimates, we do Y.” This distinction is critical for bi-directional swaps. If you settle a hold invoice based on a mempool claim spend, you may be safe economically but exposed to reorg risk; if you wait for confirmations without tracking mempool, you may lose the preimage linkage and miss settlement. The balance depends on your threat model and risk tolerance, but it must be explicit.
Fee bumping capability is not optional; It is often the difference between safety and loss under congestion or deliberate pinning. Your system should support CPFP and, where appropriate, RBF. If you publish a refund transaction at timeout, you must assume the counterparty can publish a competing claim transaction with a higher fee. If you cannot bump your refund, you are at the mercy of miner selection. Similarly, if you rely on a claim transaction to confirm before settling an invoice, you must ensure the claim can confirm quickly enough, especially if the counterparty is incentivized to delay it.
Developers must account for Transaction Pinning: attacks in which adversaries structure transactions to prevent fee bumping caused by mempool package limits. While modern Lightning channels use "Anchor Outputs" to mitigate this, custom swap contracts often lack these protections by default. Swap scripts and output structures must be designed specifically to ensure CPFP compatibility even under pinning attempts.
Audit work in this domain should focus on Market Stress Scenarios. If the mempool fee rate spikes 10x, can your refund still be confirmed before expiry? If an attacker broadcasts a conflicting spend with a high fee, can you respond? Do you have a maximum fee budget that might cause your system to “give up” and thereby violate the economic guarantee? These questions are not abstract; they determine whether your protocol remains secure during real market stress.
Lightning Network dApps require a different security mindset than single-chain smart contracts. The safety boundary is not “a transaction executed by consensus.” It is a distributed system that must coordinate preimages, timelocks, invoices, UTXOs, and confirmations under adversarial ordering and a volatile fee market. By prioritizing the six areas outlined above—preimage discipline, timelock engineering, adversarial event ordering, idempotency and crash consistency, griefing resistance, and on-chain fee strategy—developers can mitigate most high-severity failure modes common in Lightning swaps and DeFi protocols.
At CertiK, our L1 chain audits demonstrate that the distinction between a functional prototype and a secure protocol is not defined by the complexity of its on-chain scripts. Rather, it is determined by the engineering rigor applied to state consistency, strict timing enforcement, and irreversible action management. A system achieves production-grade security only when it proves resilient against the adversarial reality of the network, maintaining integrity through mempool volatility, process restarts, chain reorganizations, and fee market stress.