Remove now-unused ServeStaticInv::inv_slot from OM
What changed, and why it matters
This commit removes an unused data field called invoice_slot from a message type used in a new Lightning protocol feature for storing static invoices on a server. The change is described by the developers as a cleanup to simplify how the server looks up stored invoices. There is no indication in the commit that this fixes a security bug; it appears to be a normal API and protocol simplification.
Treat as a routine protocol cleanup. Reviewers should verify that the invoice_slot value is still reliably conveyed via the blinded path context so the server can continue to identify the correct stored invoice, and confirm wire-format compatibility with any deployed peers that may still expect the old field.
Security signals we found
No security framing in commit title or message
Field removal is a protocol/API simplification, not a bounds-check, validation, or memory-safety fix
Removed required TLV field changes wire format; compatibility implications are possible but not security-relevant per se
No mention of vulnerability, researcher credit, CVE, or advisory
Evidence from the diff
The patch deletes the invoice_slot: u16 field from the ServeStaticInvoice onion message struct in LDK’s async/static invoice payment flow. The slot number is now carried inside the blinded path context instead, so the recipient no longer needs to include it redundantly in the onion message. Serialization/deserialization for the field is also removed. Call sites in async_receive_offer_cache.rs and flow.rs are updated to stop producing or passing the slot when building ServeStaticInvoice. The commit message frames this as a usability improvement for server-side KVStore indexing, not as a security fix.
Changed components
lightning/src/onion_message/async_payments.rs (ServeStaticInvoice struct and TLV serialization)lightning/src/offers/async_receive_offer_cache.rs (offers_needing_invoice_refresh iterator)lightning/src/offers/flow.rs (static invoice construction and serving)Inspect captured patch +9 / −23
diff --git a/lightning/src/offers/async_receive_offer_cache.rs b/lightning/src/offers/async_receive_offer_cache.rs
index 5b9ea26..8c1887a 100644
--- a/lightning/src/offers/async_receive_offer_cache.rs
+++ b/lightning/src/offers/async_receive_offer_cache.rs
@@ -368,12 +368,13 @@ impl AsyncReceiveOfferCache {
/// If we have any empty slots in the cache or offers that can and should be replaced with a fresh
/// offer, here we return the index of the slot that needs a new offer. The index is used for
- /// setting [`ServeStaticInvoice::invoice_slot`] when sending the corresponding new static invoice
- /// to the server, so the server knows which existing persisted invoice is being replaced, if any.
+ /// setting [`OfferPathsRequest::invoice_slot`] when requesting offer paths from the server, so
+ /// the server can include the slot in the offer paths and reply paths that they create in
+ /// response.
///
/// Returns `None` if the cache is full and no offers can currently be replaced.
///
- /// [`ServeStaticInvoice::invoice_slot`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice_slot
+ /// [`OfferPathsRequest::invoice_slot`]: crate::onion_message::async_payments::OfferPathsRequest::invoice_slot
fn needs_new_offer_idx(&self, duration_since_epoch: Duration) -> Option<usize> {
// If we have any empty offer slots, return the first one we find
let empty_slot_idx_opt = self.offers.iter().position(|offer_opt| offer_opt.is_none());
@@ -446,10 +447,10 @@ impl AsyncReceiveOfferCache {
/// the static invoice server.
pub(super) fn offers_needing_invoice_refresh(
&self, duration_since_epoch: Duration,
- ) -> impl Iterator<Item = (&Offer, Nonce, u16, &Responder)> {
+ ) -> impl Iterator<Item = (&Offer, Nonce, &Responder)> {
// For any offers which are either in use or pending confirmation by the server, we should send
// them a fresh invoice on each timer tick.
- self.offers_with_idx().filter_map(move |(idx, offer)| {
+ self.offers_with_idx().filter_map(move |(_, offer)| {
let needs_invoice_update = match offer.status {
OfferStatus::Used { invoice_created_at } => {
invoice_created_at.saturating_add(INVOICE_REFRESH_THRESHOLD)
@@ -461,13 +462,7 @@ impl AsyncReceiveOfferCache {
OfferStatus::Ready { .. } => false,
};
if needs_invoice_update {
- let offer_slot = idx.try_into().unwrap_or(u16::MAX);
- Some((
- &offer.offer,
- offer.offer_nonce,
- offer_slot,
- &offer.update_static_invoice_path,
- ))
+ Some((&offer.offer, offer.offer_nonce, &offer.update_static_invoice_path))
} else {
None
}
diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs
index 160537d..60615f7 100644
--- a/lightning/src/offers/flow.rs
+++ b/lightning/src/offers/flow.rs
@@ -1317,8 +1317,7 @@ where
let duration_since_epoch = self.duration_since_epoch();
let cache = self.async_receive_offer_cache.lock().unwrap();
for offer_and_metadata in cache.offers_needing_invoice_refresh(duration_since_epoch) {
- let (offer, offer_nonce, slot_number, update_static_invoice_path) =
- offer_and_metadata;
+ let (offer, offer_nonce, update_static_invoice_path) = offer_and_metadata;
let (invoice, forward_invreq_path) = match self.create_static_invoice_for_server(
offer,
@@ -1342,7 +1341,6 @@ where
let serve_invoice_message = ServeStaticInvoice {
invoice,
forward_invoice_request_path: forward_invreq_path,
- invoice_slot: slot_number,
};
serve_static_invoice_msgs.push((
serve_invoice_message,
@@ -1518,8 +1516,7 @@ where
})
};
- let serve_invoice_message =
- ServeStaticInvoice { invoice, forward_invoice_request_path, invoice_slot };
+ let serve_invoice_message = ServeStaticInvoice { invoice, forward_invoice_request_path };
Some((serve_invoice_message, reply_path_context))
}
diff --git a/lightning/src/onion_message/async_payments.rs b/lightning/src/onion_message/async_payments.rs
index 1582543..877af43 100644
--- a/lightning/src/onion_message/async_payments.rs
+++ b/lightning/src/onion_message/async_payments.rs
@@ -172,11 +172,6 @@ pub struct ServeStaticInvoice {
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub forward_invoice_request_path: BlindedMessagePath,
- /// The "slot" in the static invoice server's database that this invoice should go into. This
- /// allows recipients to replace a specific invoice that is stored by the server, which is useful
- /// for limiting the number of invoices stored by the server while also keeping all the invoices
- /// persisted with the server fresh.
- pub invoice_slot: u16,
}
/// Confirmation from a static invoice server that a [`StaticInvoice`] was persisted and the
@@ -251,7 +246,6 @@ impl_writeable_tlv_based!(OfferPaths, {
impl_writeable_tlv_based!(ServeStaticInvoice, {
(0, invoice, required),
(2, forward_invoice_request_path, required),
- (4, invoice_slot, required),
});
impl_writeable_tlv_based!(StaticInvoicePersisted, {});
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.