Track invoice_slot in ServeStaticInvoice context
What changed, and why it matters
This commit is a small internal cleanup in the code that handles static Lightning invoices. It makes the software remember an 'invoice_slot' number earlier in the process so that later, when serving a stored invoice, the server can look it up by slot number instead of relying only on an 'invoice_id'. The change adds a new field to a protocol data structure and threads it through the request/response flow. There is no indication this fixes a security bug; it appears to be a design/API simplification.
No immediate security action required. Treat as a normal protocol/API refactor. If deploying, note the new required TLV field means stored or in-flight blinded path contexts serialized with the old format may fail to load; verify compatibility with persisted data and peer versions.
Security signals we found
No security-relevant language in commit title or message
No bounds checks or input validation changes beyond existing expiry checks
New required TLV field changes wire format; old serialized data without the field would fail deserialization
No memory safety, cryptographic, or authorization changes visible
Evidence from the diff
The patch adds an invoice_slot: u16 field to the AsyncPaymentsContext::ServeStaticInvoice blinded-path context. It passes the OfferPathsRequest by reference into handle_offer_paths_request so the slot from the initial request can be copied into the newly created ServeStaticInvoice context. It then returns that slot from verify_serve_static_invoice_message and uses it in the Event::PersistStaticInvoice emitted in ChannelManager. The invoice_id field is retained for now, but the commit message states this lays groundwork for removing it. Serialization is updated via the existing TLV enum macro with a new required TLV record type 6.
Changed components
lightning/src/blinded_path/message.rslightning/src/ln/channelmanager.rslightning/src/offers/flow.rsInspect captured patch +19 / −8
diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs
index 37499f2..64cc57a 100644
--- a/lightning/src/blinded_path/message.rs
+++ b/lightning/src/blinded_path/message.rs
@@ -494,6 +494,13 @@ pub enum AsyncPaymentsContext {
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
invoice_id: u128,
+ /// The slot number for the specific [`StaticInvoice`] that the recipient is requesting be
+ /// served on their behalf. Useful when surfaced alongside the above `recipient_id` when payers
+ /// send an [`InvoiceRequest`], to pull the specific static invoice from the database.
+ ///
+ /// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+ invoice_slot: u16,
/// The time as duration since the Unix epoch at which this path expires and messages sent over
/// it should be ignored.
///
@@ -595,6 +602,7 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
(0, recipient_id, required),
(2, invoice_id, required),
(4, path_absolute_expiry, required),
+ (6, invoice_slot, required),
},
);
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index d72a871..857da8b 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -14441,13 +14441,13 @@ where
L::Target: Logger,
{
fn handle_offer_paths_request(
- &self, _message: OfferPathsRequest, context: AsyncPaymentsContext,
+ &self, message: OfferPathsRequest, context: AsyncPaymentsContext,
responder: Option<Responder>,
) -> Option<(OfferPaths, ResponseInstruction)> {
let peers = self.get_peers_for_blinded_path();
let entropy = &*self.entropy_source;
let (message, reply_path_context) =
- match self.flow.handle_offer_paths_request(context, peers, entropy) {
+ match self.flow.handle_offer_paths_request(&message, context, peers, entropy) {
Some(msg) => msg,
None => return None,
};
@@ -14490,9 +14490,9 @@ where
None => return,
};
- let (recipient_id, invoice_id) =
+ let (recipient_id, invoice_slot, invoice_id) =
match self.flow.verify_serve_static_invoice_message(&message, context) {
- Ok((recipient_id, inv_id)) => (recipient_id, inv_id),
+ Ok((recipient_id, inv_slot, inv_id)) => (recipient_id, inv_slot, inv_id),
Err(()) => return,
};
@@ -14500,7 +14500,7 @@ where
pending_events.push_back((
Event::PersistStaticInvoice {
invoice: message.invoice,
- invoice_slot: message.invoice_slot,
+ invoice_slot,
recipient_id,
invoice_id,
invoice_persisted_path: responder,
diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs
index 39fdd4f..160537d 100644
--- a/lightning/src/offers/flow.rs
+++ b/lightning/src/offers/flow.rs
@@ -1374,7 +1374,8 @@ where
/// wants us (the static invoice server) to serve [`StaticInvoice`]s to payers on their behalf.
/// Sends out [`OfferPaths`] onion messages in response.
pub fn handle_offer_paths_request<ES: Deref>(
- &self, context: AsyncPaymentsContext, peers: Vec<MessageForwardNode>, entropy_source: ES,
+ &self, request: &OfferPathsRequest, context: AsyncPaymentsContext,
+ peers: Vec<MessageForwardNode>, entropy_source: ES,
) -> Option<(OfferPaths, MessageContext)>
where
ES::Target: EntropySource,
@@ -1420,6 +1421,7 @@ where
MessageContext::AsyncPayments(AsyncPaymentsContext::ServeStaticInvoice {
recipient_id,
invoice_id,
+ invoice_slot: request.invoice_slot,
path_absolute_expiry,
})
};
@@ -1588,7 +1590,7 @@ where
/// [`ServeStaticInvoice::invoice`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice
pub fn verify_serve_static_invoice_message(
&self, message: &ServeStaticInvoice, context: AsyncPaymentsContext,
- ) -> Result<(Vec<u8>, u128), ()> {
+ ) -> Result<(Vec<u8>, u16, u128), ()> {
if message.invoice.is_expired_no_std(self.duration_since_epoch()) {
return Err(());
}
@@ -1599,13 +1601,14 @@ where
AsyncPaymentsContext::ServeStaticInvoice {
recipient_id,
invoice_id,
+ invoice_slot,
path_absolute_expiry,
} => {
if self.duration_since_epoch() > path_absolute_expiry {
return Err(());
}
- return Ok((recipient_id, invoice_id));
+ return Ok((recipient_id, invoice_slot, invoice_id));
},
_ => return Err(()),
};
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.