Skip to content

Commit ce947f5

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 b08acd4 commit ce947f5

File tree

4 files changed

+129
-6
lines changed

4 files changed

+129
-6
lines changed

lightning/src/events/mod.rs

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

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

lightning/src/ln/channelmanager.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ use crate::ln::outbound_payment::{
8686
};
8787
use crate::ln::types::ChannelId;
8888
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
89-
use crate::offers::flow::OffersMessageFlow;
89+
use crate::offers::flow::{InvreqResponseInstructions, OffersMessageFlow};
9090
use crate::offers::invoice::{
9191
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
9292
};
@@ -5128,6 +5128,15 @@ where
51285128
self.flow.serving_static_invoice(invoice_persisted_path);
51295129
}
51305130

5131+
/// Forwards a [`StaticInvoice`] that was previously persisted by us from an
5132+
/// [`Event::PersistStaticInvoice`], in response to an [`Event::StaticInvoiceRequested`].
5133+
#[cfg(async_payments)]
5134+
pub fn send_static_invoice(
5135+
&self, invoice: StaticInvoice, responder: Responder,
5136+
) -> Result<(), Bolt12SemanticError> {
5137+
self.flow.enqueue_static_invoice(invoice, responder)
5138+
}
5139+
51315140
#[rustfmt::skip]
51325141
#[cfg(async_payments)]
51335142
fn initiate_async_payment(
@@ -12760,7 +12769,17 @@ where
1276012769
};
1276112770

1276212771
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
12763-
Ok(invoice_request) => invoice_request,
12772+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
12773+
Ok(InvreqResponseInstructions::SendStaticInvoice {
12774+
recipient_id_nonce: _recipient_id_nonce
12775+
}) => {
12776+
#[cfg(async_payments)]
12777+
self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested {
12778+
recipient_id_nonce: _recipient_id_nonce, reply_path: responder
12779+
}, None));
12780+
12781+
return None
12782+
},
1276412783
Err(_) => return None,
1276512784
};
1276612785

lightning/src/offers/flow.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,23 @@ fn enqueue_onion_message_with_reply_paths<T: OnionMessageContents + Clone>(
420420
});
421421
}
422422

423+
/// Instructions for how to respond to an `InvoiceRequest`.
424+
pub enum InvreqResponseInstructions {
425+
/// We are the recipient of this payment, and a [`Bolt12Invoice`] should be sent in response to
426+
/// the invoice request since it is now verified.
427+
SendInvoice(VerifiedInvoiceRequest),
428+
/// We are a static invoice server and should respond to this invoice request by retrieving the
429+
/// [`StaticInvoice`] corresponding to the `recipient_id_nonce` and calling
430+
/// `OffersMessageFlow::enqueue_static_invoice`.
431+
///
432+
/// TODO: if the server stores multiple invoices on behalf of the recipient, how to narrow down
433+
/// which one is being requested?
434+
SendStaticInvoice {
435+
/// An identifier for the async recipient for whom we are serving [`StaticInvoice`]s.
436+
recipient_id_nonce: Nonce,
437+
},
438+
}
439+
423440
impl<MR: Deref> OffersMessageFlow<MR>
424441
where
425442
MR::Target: MessageRouter,
@@ -437,13 +454,31 @@ where
437454
/// - The verification process (via recipient context data or metadata) fails.
438455
pub fn verify_invoice_request(
439456
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
440-
) -> Result<VerifiedInvoiceRequest, ()> {
457+
) -> Result<InvreqResponseInstructions, ()> {
441458
let secp_ctx = &self.secp_ctx;
442459
let expanded_key = &self.inbound_payment_key;
443460

444461
let nonce = match context {
445462
None if invoice_request.metadata().is_some() => None,
446463
Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce),
464+
#[cfg(async_payments)]
465+
Some(OffersContext::StaticInvoiceRequested {
466+
recipient_id_nonce,
467+
nonce,
468+
hmac,
469+
path_absolute_expiry,
470+
}) => {
471+
// TODO: vet invreq more?
472+
if signer::verify_async_recipient_invreq_context(nonce, hmac, expanded_key).is_err()
473+
{
474+
return Err(());
475+
}
476+
if path_absolute_expiry < self.duration_since_epoch() {
477+
return Err(());
478+
}
479+
480+
return Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id_nonce });
481+
},
447482
_ => return Err(()),
448483
};
449484

@@ -454,7 +489,7 @@ where
454489
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
455490
}?;
456491

457-
Ok(invoice_request)
492+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
458493
}
459494

460495
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer metadata,
@@ -1064,6 +1099,28 @@ where
10641099
Ok(())
10651100
}
10661101

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

lightning/src/offers/signer.rs

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

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

0 commit comments

Comments
 (0)