Skip to content

Commit bd15e47

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 c5b4cfa commit bd15e47

File tree

4 files changed

+118
-6
lines changed

4 files changed

+118
-6
lines changed

lightning/src/events/mod.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,11 +1585,11 @@ pub enum Event {
15851585
#[cfg(async_payments)]
15861586
PersistStaticInvoice {
15871587
/// The invoice that should be persisted and later provided to payers when handling a future
1588-
/// `Event::StaticInvoiceRequested`.
1588+
/// [`Event::StaticInvoiceRequested`].
15891589
invoice: StaticInvoice,
15901590
/// An identifier for the recipient, originally surfaced in
15911591
/// [`ChannelManager::blinded_paths_for_async_recipient`]. When an
1592-
/// `Event::StaticInvoiceRequested` comes in for this invoice, this id will be surfaced so the
1592+
/// [`Event::StaticInvoiceRequested`] comes in for this invoice, this id will be surfaced so the
15931593
/// persisted invoice can be retrieved from the database.
15941594
recipient_id_nonce: Nonce,
15951595
/// Once the [`StaticInvoice`] is persisted, [`ChannelManager::static_invoice_persisted`] should
@@ -1600,6 +1600,34 @@ pub enum Event {
16001600
/// [`Offer`]: crate::offers::offer::Offer
16011601
invoice_persisted_path: Responder,
16021602
},
1603+
/// We received an [`InvoiceRequest`] on behalf of an often-offline recipient for whom we are
1604+
/// serving [`StaticInvoice`]s.
1605+
///
1606+
/// This event will only be generated if we previously created paths using
1607+
/// [`ChannelManager::blinded_paths_for_async_recipient`] and configured the recipient with them
1608+
/// via [`UserConfig::paths_to_static_invoice_server`].
1609+
///
1610+
/// If we previously persisted a [`StaticInvoice`] from an [`Event::PersistStaticInvoice`] that
1611+
/// matches the contained [`Event::StaticInvoiceRequested::recipient_id_nonce`], that
1612+
/// invoice should be retrieved now and forwarded to the payer via
1613+
/// [`ChannelManager::send_static_invoice`].
1614+
///
1615+
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient
1616+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
1617+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
1618+
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice
1619+
#[cfg(async_payments)]
1620+
StaticInvoiceRequested {
1621+
/// An identifier for the recipient previously surfaced in
1622+
/// [`Event::PersistStaticInvoice::recipient_id_nonce`]. Useful to retrieve the [`StaticInvoice`]
1623+
/// requested by the payer.
1624+
recipient_id_nonce: Nonce,
1625+
/// The path over which the [`StaticInvoice`] will be sent to the payer, which should be
1626+
/// provided to [`ChannelManager::send_static_invoice`] along with the invoice.
1627+
///
1628+
/// [`ChannelManager::send_static_invoice`]: crate::ln::channelmanager::ChannelManager::send_static_invoice
1629+
reply_path: Responder,
1630+
},
16031631
}
16041632

16051633
impl Writeable for Event {
@@ -2030,6 +2058,11 @@ impl Writeable for Event {
20302058
// No need to write these events because we can just restart the static invoice negotiation
20312059
// on startup.
20322060
},
2061+
#[cfg(async_payments)]
2062+
&Event::StaticInvoiceRequested { .. } => {
2063+
47u8.write(writer)?;
2064+
// Never write StaticInvoiceRequested events as buffered onion messages aren't serialized.
2065+
},
20332066
// Note that, going forward, all new events must only write data inside of
20342067
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
20352068
// data via `write_tlv_fields`.
@@ -2597,6 +2630,9 @@ impl MaybeReadable for Event {
25972630
// Note that we do not write a length-prefixed TLV for PersistStaticInvoice events.
25982631
#[cfg(async_payments)]
25992632
45u8 => Ok(None),
2633+
// Note that we do not write a length-prefixed TLV for StaticInvoiceRequested events.
2634+
#[cfg(async_payments)]
2635+
47u8 => Ok(None),
26002636
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
26012637
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
26022638
// reads.

lightning/src/ln/channelmanager.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFun
5151
// construct one themselves.
5252
use crate::ln::inbound_payment;
5353
use crate::ln::types::ChannelId;
54-
use crate::offers::flow::OffersMessageFlow;
54+
use crate::offers::flow::{InvreqResponseInstructions, OffersMessageFlow};
5555
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
5656
use crate::ln::channel::{self, Channel, ChannelError, ChannelUpdateStatus, FundedChannel, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, ReconnectionMsg, InboundV1Channel, WithChannelContext};
5757
use crate::ln::channel::PendingV2Channel;
@@ -4974,6 +4974,15 @@ where
49744974
self.flow.serving_static_invoice(invoice_persisted_path);
49754975
}
49764976

4977+
/// Forwards a [`StaticInvoice`] that was previously persisted by us from an
4978+
/// [`Event::PersistStaticInvoice`], in response to an [`Event::StaticInvoiceRequested`].
4979+
#[cfg(async_payments)]
4980+
pub fn send_static_invoice(
4981+
&self, invoice: StaticInvoice, responder: Responder
4982+
) -> Result<(), Bolt12SemanticError> {
4983+
self.flow.enqueue_static_invoice(invoice, responder)
4984+
}
4985+
49774986
#[cfg(async_payments)]
49784987
fn initiate_async_payment(
49794988
&self, invoice: &StaticInvoice, payment_id: PaymentId
@@ -12354,7 +12363,15 @@ where
1235412363
};
1235512364

1235612365
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
12357-
Ok(invoice_request) => invoice_request,
12366+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
12367+
Ok(InvreqResponseInstructions::SendStaticInvoice(_recipient_id_nonce)) => {
12368+
#[cfg(async_payments)]
12369+
self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested {
12370+
recipient_id_nonce: _recipient_id_nonce, reply_path: responder
12371+
}, None));
12372+
12373+
return None
12374+
},
1235812375
Err(_) => return None,
1235912376
};
1236012377

lightning/src/offers/flow.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,14 @@ fn enqueue_onion_message_with_reply_paths<T: OnionMessageContents + Clone>(
407407
});
408408
}
409409

410+
///
411+
pub enum InvreqResponseInstructions {
412+
///
413+
SendInvoice(VerifiedInvoiceRequest),
414+
///
415+
SendStaticInvoice(Nonce),
416+
}
417+
410418
impl<MR: Deref> OffersMessageFlow<MR>
411419
where
412420
MR::Target: MessageRouter,
@@ -424,13 +432,31 @@ where
424432
/// - The verification process (via recipient context data or metadata) fails.
425433
pub fn verify_invoice_request(
426434
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
427-
) -> Result<VerifiedInvoiceRequest, ()> {
435+
) -> Result<InvreqResponseInstructions, ()> {
428436
let secp_ctx = &self.secp_ctx;
429437
let expanded_key = &self.inbound_payment_key;
430438

431439
let nonce = match context {
432440
None if invoice_request.metadata().is_some() => None,
433441
Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce),
442+
#[cfg(async_payments)]
443+
Some(OffersContext::StaticInvoiceRequested {
444+
recipient_id_nonce,
445+
nonce,
446+
hmac,
447+
path_absolute_expiry,
448+
}) => {
449+
// TODO: vet invreq more?
450+
if signer::verify_async_recipient_invreq_context(nonce, hmac, expanded_key).is_err()
451+
{
452+
return Err(());
453+
}
454+
if path_absolute_expiry < self.duration_since_epoch() {
455+
return Err(());
456+
}
457+
458+
return Ok(InvreqResponseInstructions::SendStaticInvoice(recipient_id_nonce));
459+
},
434460
_ => return Err(()),
435461
};
436462

@@ -441,7 +467,7 @@ where
441467
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
442468
}?;
443469

444-
Ok(invoice_request)
470+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
445471
}
446472

447473
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer metadata,
@@ -1051,6 +1077,28 @@ where
10511077
Ok(())
10521078
}
10531079

1080+
/// Forwards a [`StaticInvoice`] that was previously persisted by us from an
1081+
/// [`Event::PersistStaticInvoice`], in response to an [`Event::StaticInvoiceRequested`].
1082+
#[cfg(async_payments)]
1083+
pub fn enqueue_static_invoice(
1084+
&self, invoice: StaticInvoice, responder: Responder,
1085+
) -> Result<(), Bolt12SemanticError> {
1086+
let duration_since_epoch = self.duration_since_epoch();
1087+
if invoice.is_expired_no_std(duration_since_epoch) {
1088+
return Err(Bolt12SemanticError::AlreadyExpired);
1089+
}
1090+
if invoice.is_offer_expired_no_std(duration_since_epoch) {
1091+
return Err(Bolt12SemanticError::AlreadyExpired);
1092+
}
1093+
1094+
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
1095+
let message = OffersMessage::StaticInvoice(invoice);
1096+
// TODO include reply path for invoice error
1097+
pending_offers_messages.push((message, responder.respond().into_instructions()));
1098+
1099+
Ok(())
1100+
}
1101+
10541102
/// Enqueues `held_htlc_available` onion messages to be sent to the payee via the reply paths
10551103
/// contained within the provided [`StaticInvoice`].
10561104
///

lightning/src/offers/signer.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,3 +703,14 @@ pub(crate) fn hmac_for_async_recipient_invreq_context(
703703

704704
Hmac::from_engine(hmac)
705705
}
706+
707+
#[cfg(async_payments)]
708+
pub(crate) fn verify_async_recipient_invreq_context(
709+
nonce: Nonce, hmac: Hmac<Sha256>, expanded_key: &ExpandedKey,
710+
) -> Result<(), ()> {
711+
if hmac_for_async_recipient_invreq_context(nonce, expanded_key) == hmac {
712+
Ok(())
713+
} else {
714+
Err(())
715+
}
716+
}

0 commit comments

Comments
 (0)