Skip to content

Commit 8614fb2

Browse files
Static invoice server: forward static invoices to payers
Here we implement serving static invoices to payers on behalf of often-offline recipients. These recipients previously encoded blinded paths terminating at our node in their offer, so we receive invoice requests on their behalf. Handle those inbound invreqs by retrieving a static invoice we previously persisted on behalf of the payee, and forward it to the payer as a reply to their invreq.
1 parent 6458c6a commit 8614fb2

File tree

4 files changed

+104
-2
lines changed

4 files changed

+104
-2
lines changed

lightning/src/events/mod.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,11 +1511,11 @@ pub enum Event {
15111511
#[cfg(async_payments)]
15121512
PersistStaticInvoice {
15131513
/// The invoice that should be persisted and later provided to payers when handling a future
1514-
/// `Event::StaticInvoiceRequested`.
1514+
/// [`Event::StaticInvoiceRequested`].
15151515
invoice: StaticInvoice,
15161516
/// An identifier for the recipient, originally surfaced in
15171517
/// [`ChannelManager::blinded_paths_for_async_recipient`]. When an
1518-
/// `Event::StaticInvoiceRequested` comes in for this invoice, this id will be surfaced so the
1518+
/// [`Event::StaticInvoiceRequested`] comes in for this invoice, this id will be surfaced so the
15191519
/// persisted invoice can be retrieved from the database.
15201520
recipient_id_nonce: Nonce,
15211521
/// Once the [`StaticInvoice`] is persisted, [`ChannelManager::static_invoice_persisted`] should
@@ -1526,6 +1526,34 @@ pub enum Event {
15261526
/// [`Offer`]: crate::offers::offer::Offer
15271527
invoice_persisted_paths: Vec<BlindedMessagePath>,
15281528
},
1529+
/// We received an [`InvoiceRequest`] on behalf of an often-offline recipient for whom we are
1530+
/// serving [`StaticInvoice`]s.
1531+
///
1532+
/// This event will only be generated if we previously created paths using
1533+
/// [`ChannelManager::blinded_paths_for_async_recipient`] and configured the recipient with them
1534+
/// via [`UserConfig::paths_to_static_invoice_server`].
1535+
///
1536+
/// If we previously persisted a [`StaticInvoice`] from an [`Event::PersistStaticInvoice`] that
1537+
/// matches the contained [`Event::StaticInvoiceRequested::recipient_id_nonce`], that
1538+
/// invoice should be retrieved now and forwarded to the payer via
1539+
/// [`ChannelManager::send_static_invoice`].
1540+
///
1541+
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient
1542+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
1543+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
1544+
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice
1545+
#[cfg(async_payments)]
1546+
StaticInvoiceRequested {
1547+
/// An identifier for the recipient previously surfaced in
1548+
/// [`Event::PersistStaticInvoice::recipient_id_nonce`]. Useful to retrieve the [`StaticInvoice`]
1549+
/// requested by the payer.
1550+
recipient_id_nonce: Nonce,
1551+
/// The path over which the [`StaticInvoice`] will be sent to the payer, which should be
1552+
/// provided to [`ChannelManager::send_static_invoice`] along with the invoice.
1553+
///
1554+
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice
1555+
reply_path: BlindedMessagePath,
1556+
},
15291557
}
15301558

15311559
impl Writeable for Event {
@@ -1868,6 +1896,14 @@ impl Writeable for Event {
18681896
(4, invoice_persisted_paths, required),
18691897
});
18701898
},
1899+
#[cfg(async_payments)]
1900+
&Event::StaticInvoiceRequested { ref recipient_id_nonce, ref reply_path } => {
1901+
47u8.write(writer)?;
1902+
write_tlv_fields!(writer, {
1903+
(0, recipient_id_nonce, required),
1904+
(2, reply_path, required),
1905+
});
1906+
},
18711907
// Note that, going forward, all new events must only write data inside of
18721908
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
18731909
// data via `write_tlv_fields`.
@@ -2397,6 +2433,20 @@ impl MaybeReadable for Event {
23972433
};
23982434
f()
23992435
},
2436+
#[cfg(async_payments)]
2437+
47u8 => {
2438+
let mut f = || {
2439+
_init_and_read_len_prefixed_tlv_fields!(reader, {
2440+
(0, recipient_id_nonce, required),
2441+
(2, reply_path, required),
2442+
});
2443+
Ok(Some(Event::StaticInvoiceRequested {
2444+
recipient_id_nonce: _init_tlv_based_struct_field!(recipient_id_nonce, required),
2445+
reply_path: _init_tlv_based_struct_field!(reply_path, required),
2446+
}))
2447+
};
2448+
f()
2449+
},
24002450
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
24012451
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
24022452
// reads.

lightning/src/ln/channelmanager.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4944,6 +4944,27 @@ where
49444944
Ok(())
49454945
}
49464946

4947+
/// Forwards a [`StaticInvoice`] that was previously persisted by us from an
4948+
/// [`Event::PersistStaticInvoice`], in response to an [`Event::StaticInvoiceRequested`].
4949+
#[cfg(async_payments)]
4950+
pub fn send_static_invoice(
4951+
&self, invoice: StaticInvoice, path: BlindedMessagePath
4952+
) -> Result<(), ()> {
4953+
let duration_since_epoch = self.duration_since_epoch();
4954+
if invoice.is_expired_no_std(duration_since_epoch) { return Err(()) }
4955+
if invoice.is_offer_expired_no_std(duration_since_epoch) { return Err(()) }
4956+
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
4957+
4958+
let message = OffersMessage::StaticInvoice(invoice);
4959+
// TODO include reply path for invoice error
4960+
let instructions = MessageSendInstructions::WithoutReplyPath {
4961+
destination: Destination::BlindedPath(path),
4962+
};
4963+
pending_offers_messages.push((message, instructions));
4964+
4965+
Ok(())
4966+
}
4967+
49474968
#[cfg(async_payments)]
49484969
fn initiate_async_payment(
49494970
&self, invoice: &StaticInvoice, payment_id: PaymentId
@@ -12533,6 +12554,25 @@ where
1253312554
let nonce = match context {
1253412555
None if invoice_request.metadata().is_some() => None,
1253512556
Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce),
12557+
#[cfg(async_payments)]
12558+
Some(OffersContext::StaticInvoiceRequested {
12559+
recipient_id_nonce, nonce, hmac, path_absolute_expiry
12560+
}) => {
12561+
// TODO: vet invreq more?
12562+
if signer::verify_async_recipient_invreq_context(nonce, hmac, expanded_key).is_err() {
12563+
return None
12564+
}
12565+
if path_absolute_expiry < self.duration_since_epoch() {
12566+
return None
12567+
}
12568+
12569+
let mut pending_events = self.pending_events.lock().unwrap();
12570+
pending_events.push_back((Event::StaticInvoiceRequested {
12571+
recipient_id_nonce, reply_path: responder.reply_path().clone()
12572+
}, None));
12573+
12574+
return None
12575+
},
1253612576
_ => return None,
1253712577
};
1253812578

lightning/src/offers/signer.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,3 +684,10 @@ pub(crate) fn hmac_for_async_recipient_invreq_context(
684684

685685
Hmac::from_engine(hmac)
686686
}
687+
688+
#[cfg(async_payments)]
689+
pub(crate) fn verify_async_recipient_invreq_context(
690+
nonce: Nonce, hmac: Hmac<Sha256>, expanded_key: &ExpandedKey,
691+
) -> Result<(), ()> {
692+
if hmac_for_async_recipient_invreq_context(nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
693+
}

lightning/src/onion_message/messenger.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,11 @@ impl Responder {
428428
context: Some(context),
429429
}
430430
}
431+
432+
#[cfg(async_payments)]
433+
pub(crate) fn reply_path(&self) -> &BlindedMessagePath {
434+
&self.reply_path
435+
}
431436
}
432437

433438
/// Instructions for how and where to send the response to an onion message.

0 commit comments

Comments
 (0)