Weeks 25 & 26 of 2026
Welcome to Latest Strikes, your weekly report of the latest Lightning-related news. This issue covers two weeks worth of Lightning development, with a LND DoS vulnerability disclosure, a continuation of the K-of-N Lightning node discussion, and CLINK Offers growing adoption.
LND DoS Vulnerability Disclosure
Nishant Bansal disclosed a DoS vulnerability lying in LND’s gossip handler, and fixed in version 0.20.1.
When a Lightning node receives a channel_update gossip message1, it will attempt to rebroadcast it (if it’s valid), ensuring the message can quickly reach a large portion of the network. Like all other implementations, LND implements a deduplication system to avoid rebroadcasting messages that have already been seen before, or messages that are out of date. To do so, whenever it rebroadcasts a channel_update message, LND saves the message’s timestamp. Later, when it considers whether it should rebroadcast a new message, it compares the new message’s timestamp with the saved one. If the new message’s timestamp is higher, then the message’s content is fresher: LND broadcasts the new message and replaces the saved timestamp value by the new one.
The function where this comparison happened initialized the timestamp at 0, leading to a bug: when receiving the first ever channel_update for a given channel, if this message had a 0 timestamp for the first time, LND would believe it had already seen this message (because the message’s timestamp was equal to the initial saved value), and thus try to write in a map that wasn’t initialized yet. This led to a panic error, ultimately crashing the node.
Since the bug is only triggered on the first channel_update message for a given channel, a naive analysis of the cost of the attack could suggest that the attacker must open and announce a new channel every time they want to down a node. However, as Bansal points out, an attacker can simply send funds to a 2-of-2 multisig where they control both keys, and then craft as many channel_update (and also channel_announcements, required to announce the channel in the first place) messages as they want, each signed by both keys and indistinguishable from real channels, without ever moving the funds. The cost of the attack is hence virtually zero.
The bug was fixed in v0.20.1 of LND by rejecting messages with a zero value timestamp at parsing/validation time, even before it reaches the rebroadcast logic. The specification already indicated that implementations must set the timestamp to a strictly positive value in their channel_update messages, but didn’t specify how nodes should behave when receiving such a message. Now LND rejects zero value timestamps for both channel_update and node_announcement messages.
It’s also worth noting that like the last one we covered (this time in Core Lightning), this vulnerability was found through fuzzing. Interestingly, in both cases the offending value was some version of zero, which makes sense given the particular status of this number.
StackerNews Discussion | LND Advisory
Towards A K-of-N Lightning Network Node (cont’d)
Back in April, we covered ZmnSCPxj’s proposal for K-of-N Lightning Network node. Back then, internet’s favorite hashling showed how the use of a shachain to derive revocation keys, while very smart and effective, prevented the development of a real K-of-N node, where the compromise of at least K signers is required to compromise the whole node.
Inspired by Rusty Russel, Zmn explored in a series of posts how using multiple different shachains to derive revocation secrets could actually enable a real K-of-N node. This requires splitting the knowledge of the different seeds at the root of the shachains smartly across the different signers, so that the compromise of K - 1 of them doesn’t allow the attacker to derive revocation secrets, while retaining liveness as long as K signers are online. Combinatorics shows that for N <= 5, using 10 shachains is enough. Of course, managing 10 distinct chains where one used to do the job means a 10x increase in disk usage, but then this size is constant with the number of transactions. This new design is hence a huge improvement over Zmn’s last proposition, which consisted in removing the shachain and instead storing each revocation key, which grows linearly with the number of transactions.
To put things into perspective, let’s take a channel with a history of 10,000 transactions. Remember, these transactions don’t need to have been successful: every channel HTLC added then removed, be it through success or failure, necessitates 2 revocation keys. Therefore, a 10,000 transactions history means 20,000 revocation keys. Storing each one independently (no shachain) would require 64,000 bytes of data (one revocation key is 32 bytes). Using 10 shachains necessitates storing 10 seeds, which amounts to 26,160 bytes. Still a lot of data to write to disk, but that allows us to handle 2^47 transactions (2^48 revocations) at a fixed cost. Lowering this cost could be achieved by lowering this targeted maximum number of transactions.
CLINK
Released 2 weeks ago, version 13.1.0 of the Zeus wallet brought paying and receiving with CLINK noffers2. CLINK (which stands for Common Lightning Interface for Nostr Keys) is a set of Nostr-native standards that aim at facilitating Lightning Network interactions (e.g. paying and receiving) by enabling communications between wallets and services. It can be seen as an alternative to LNURL where web servers are replaced with nostr relays and DNS records with nostr cryptographic keys. A CLINK Offer typically contains the recipient’s Nostr public key, the URL of the recommended relay where the recipient listens for payment requests, and a few other fields to express the pricing method.
Once the recipient has published this offer, anyone with knowledge of it can send a payment request to the corresponding Nostr public key, through the recommended relay. The recipient sends back an invoice, also through Nostr. Crucially, both the payment request and the invoice-bearing response are encrypted (using NIP44) so that only the sender and the recipient can read them. CLINK Offers hence deliberately mirror the LNURL Pay user flow (static code -> invoice request -> invoice -> payment), meaning it can rather easily be used where LNURL Pay is, either as a replacement or as a complement. A crucial benefit of CLINK is that it leverages the recipient Nostr identity, which enables senders to make sure that they’re sending to the right npub, since:
- the Offer advertises the recipient’s public key (npub),
- the sender’s event requesting the invoice is addressed and encrypted to the recipient’s npub
- the recipient’s event responding with the invoice is encrypted to the sender’s npub and signed with the recipient’s npub (i.e. the corresponding nsec)
- allowing the sender to check that the invoice comes from the same identity that is specified in the Offer.
Making sure said npub actually belongs to the expected recipient (ie the person or the service we’re sending to) is a separate concern, that can be achieved with things like contact lists and web-of-trust.
Zeus’ implementation of CLINK noffer is similar to that of Lightning Addresses in the app: when it comes to paying a noffer, everything is handled client-side ; whereas for receiving the Zeus Pay service (Zeus’s LSP) is handling the noffer creation and forwards payments to the user (which notably allows to “receive” while offline). Users can display their noffer alongside their Lightning Address (in a different tab). However, it seems to me that by externalizing noffer creation to the Zeus Pay service, we lose one of the core benefits of CLINK (coupling payment code to identity) while still retaining the other one (using Nostr infrastructure). The classic siphoning attack where the server could replace user invoices with theirs is mitigated here through the use of Zaplocker in Zeus, which offers stronger guarantees that vanilla LNURL/CLINK server3.
Roughly the same week, OG Nostr client Amethyst also shipped CLINK Offers support. Interestingly, it closes the gap left by Zeus: by linking their Zeus Pay noffer in their Nostr Profile metadata, users sign an event with their own key that states this noffer should pay to them.
StackerNews Discussion Zeus | StackerNews Discussion - Amethyst
Quick Strikes
A few items that I found worth mentioning:
- Blitz shipped a “spend and replace” feature. When its enabled and you make a bitcoin payment, some of your stablecoin balance (if any) is automatically swap to put back the same amount you just spent into your balance. “Spend and replace” is very cool because it’s equivalent to spending fiat in terms of bitcoins accrued, while also helping bootstrap the Bitcoin economy. A common objection is that it’s a bit cumbersome in practice, so automating it makes a lot of sense ;
- I was absolutely amazed to learn that some routing nodes are doing “just-in-time rebalancing”, where they will intercept an HTLC, rebalance channels so that they can route it, and then forward the HTLC!
- Bennet put together an incredible primer on how Lightning works (StackerNews Announcement).
That’s it for last week! Once again, thanks a ton for reaching the end of this issue, and until next week!
Footnotes
-
As described in the vulnerability disclosure,
node_announcementmessages are also affected. For simplicity and brevity, we’ll focus onchannel_updatein the remainder of this section. The bug, behavior and fix are the same fornode_announcementmessages. ↩ -
We covered Evan Kaloudis was working on this feature a month ago. ↩
-
Note that after reviewing the code, it seems the noffer receive flow doesn’t verify the Zaplocker attestations, unlike the Lightning Address flow. ↩
Enjoyed this issue?
Get Latest Strikes delivered to your inbox every week.