Skip to content

Commit 8b09072

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 8b68db3 commit 8b09072

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
@@ -49,7 +49,7 @@ use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFun
4949
// construct one themselves.
5050
use crate::ln::inbound_payment;
5151
use crate::ln::types::ChannelId;
52-
use crate::offers::flow::OffersMessageFlow;
52+
use crate::offers::flow::{InvreqResponseInstructions, OffersMessageFlow};
5353
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
5454
use crate::ln::channel::{self, Channel, ChannelError, ChannelUpdateStatus, FundedChannel, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, ReconnectionMsg, InboundV1Channel, WithChannelContext};
5555
use crate::ln::channel::PendingV2Channel;
@@ -4972,6 +4972,15 @@ where
49724972
self.flow.serving_static_invoice(invoice_persisted_path);
49734973
}
49744974

4975+
/// Forwards a [`StaticInvoice`] that was previously persisted by us from an
4976+
/// [`Event::PersistStaticInvoice`], in response to an [`Event::StaticInvoiceRequested`].
4977+
#[cfg(async_payments)]
4978+
pub fn send_static_invoice(
4979+
&self, invoice: StaticInvoice, responder: Responder
4980+
) -> Result<(), Bolt12SemanticError> {
4981+
self.flow.enqueue_static_invoice(invoice, responder)
4982+
}
4983+
49754984
#[cfg(async_payments)]
49764985
fn initiate_async_payment(
49774986
&self, invoice: &StaticInvoice, payment_id: PaymentId
@@ -12336,7 +12345,15 @@ where
1233612345
};
1233712346

1233812347
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
12339-
Ok(invoice_request) => invoice_request,
12348+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
12349+
Ok(InvreqResponseInstructions::SendStaticInvoice(_recipient_id_nonce)) => {
12350+
#[cfg(async_payments)]
12351+
self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested {
12352+
recipient_id_nonce: _recipient_id_nonce, reply_path: responder
12353+
}, None));
12354+
12355+
return None
12356+
},
1234012357
Err(_) => return None,
1234112358
};
1234212359

lightning/src/offers/flow.rs

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

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

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

@@ -440,7 +466,7 @@ where
440466
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
441467
}?;
442468

443-
Ok(invoice_request)
469+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
444470
}
445471

446472
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's own metadata,
@@ -1075,6 +1101,28 @@ where
10751101
Ok(())
10761102
}
10771103

1104+
/// Forwards a [`StaticInvoice`] that was previously persisted by us from an
1105+
/// [`Event::PersistStaticInvoice`], in response to an [`Event::StaticInvoiceRequested`].
1106+
#[cfg(async_payments)]
1107+
pub fn enqueue_static_invoice(
1108+
&self, invoice: StaticInvoice, responder: Responder,
1109+
) -> Result<(), Bolt12SemanticError> {
1110+
let duration_since_epoch = self.duration_since_epoch();
1111+
if invoice.is_expired_no_std(duration_since_epoch) {
1112+
return Err(Bolt12SemanticError::AlreadyExpired);
1113+
}
1114+
if invoice.is_offer_expired_no_std(duration_since_epoch) {
1115+
return Err(Bolt12SemanticError::AlreadyExpired);
1116+
}
1117+
1118+
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
1119+
let message = OffersMessage::StaticInvoice(invoice);
1120+
// TODO include reply path for invoice error
1121+
pending_offers_messages.push((message, responder.respond().into_instructions()));
1122+
1123+
Ok(())
1124+
}
1125+
10781126
/// Enqueues `held_htlc_available` onion messages to be sent to the payee via the reply paths
10791127
/// contained within the provided [`StaticInvoice`].
10801128
///

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)