Skip to content

Commit c5b4cfa

Browse files
Static invoice server: persist invoices once built
As part of serving static invoices to payers on behalf of often-offline recipients, the recipient will send us the final static invoice once it's done being interactively built. We will then persist this invoice and confirm to them that the corresponding offer is ready to be used for async payments. Surface an event once the invoice is received and expose an API to tell the recipient that it's ready for payments.
1 parent 4127d9e commit c5b4cfa

File tree

4 files changed

+125
-1
lines changed

4 files changed

+125
-1
lines changed

lightning/src/events/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ use bitcoin::{OutPoint, Transaction};
5252
use core::ops::Deref;
5353
use core::time::Duration;
5454

55+
#[cfg(async_payments)]
56+
use crate::offers::nonce::Nonce;
57+
5558
#[allow(unused_imports)]
5659
use crate::prelude::*;
5760

@@ -1572,6 +1575,31 @@ pub enum Event {
15721575
/// onion messages.
15731576
peer_node_id: PublicKey,
15741577
},
1578+
/// We received a [`StaticInvoice`] from an async recipient that wants us to serve the invoice to
1579+
/// payers on their behalf when they are offline. This event will only be generated if we
1580+
/// previously created paths using [`ChannelManager::blinded_paths_for_async_recipient`] and
1581+
/// configured the recipient with them via [`UserConfig::paths_to_static_invoice_server`].
1582+
///
1583+
/// [`ChannelManager::blinded_paths_for_async_recipient`]: crate::ln::channelmanager::ChannelManager::blinded_paths_for_async_recipient
1584+
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
1585+
#[cfg(async_payments)]
1586+
PersistStaticInvoice {
1587+
/// The invoice that should be persisted and later provided to payers when handling a future
1588+
/// `Event::StaticInvoiceRequested`.
1589+
invoice: StaticInvoice,
1590+
/// An identifier for the recipient, originally surfaced in
1591+
/// [`ChannelManager::blinded_paths_for_async_recipient`]. When an
1592+
/// `Event::StaticInvoiceRequested` comes in for this invoice, this id will be surfaced so the
1593+
/// persisted invoice can be retrieved from the database.
1594+
recipient_id_nonce: Nonce,
1595+
/// Once the [`StaticInvoice`] is persisted, [`ChannelManager::static_invoice_persisted`] should
1596+
/// be called with this responder to confirm to the recipient that their [`Offer`] is ready to
1597+
/// be used for async payments.
1598+
///
1599+
/// [`ChannelManager::static_invoice_persisted`]: crate::ln::channelmanager::ChannelManager::static_invoice_persisted
1600+
/// [`Offer`]: crate::offers::offer::Offer
1601+
invoice_persisted_path: Responder,
1602+
},
15751603
}
15761604

15771605
impl Writeable for Event {
@@ -1996,6 +2024,12 @@ impl Writeable for Event {
19962024
(8, former_temporary_channel_id, required),
19972025
});
19982026
},
2027+
#[cfg(async_payments)]
2028+
&Event::PersistStaticInvoice { .. } => {
2029+
45u8.write(writer)?;
2030+
// No need to write these events because we can just restart the static invoice negotiation
2031+
// on startup.
2032+
},
19992033
// Note that, going forward, all new events must only write data inside of
20002034
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
20012035
// data via `write_tlv_fields`.
@@ -2560,6 +2594,9 @@ impl MaybeReadable for Event {
25602594
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
25612595
}))
25622596
},
2597+
// Note that we do not write a length-prefixed TLV for PersistStaticInvoice events.
2598+
#[cfg(async_payments)]
2599+
45u8 => Ok(None),
25632600
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
25642601
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
25652602
// reads.

lightning/src/ln/channelmanager.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4967,6 +4967,13 @@ where
49674967
}
49684968
}
49694969

4970+
/// Should be called after handling an [`Event::PersistStaticInvoice`], where the `Responder`
4971+
/// comes from [`Event::PersistStaticInvoice::invoice_persisted_path`].
4972+
#[cfg(async_payments)]
4973+
pub fn static_invoice_persisted(&self, invoice_persisted_path: Responder) {
4974+
self.flow.serving_static_invoice(invoice_persisted_path);
4975+
}
4976+
49704977
#[cfg(async_payments)]
49714978
fn initiate_async_payment(
49724979
&self, invoice: &StaticInvoice, payment_id: PaymentId
@@ -12514,7 +12521,28 @@ where
1251412521
fn handle_serve_static_invoice(
1251512522
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
1251612523
_responder: Option<Responder>,
12517-
) {}
12524+
) {
12525+
#[cfg(async_payments)] {
12526+
let responder = match _responder {
12527+
Some(resp) => resp,
12528+
None => return
12529+
};
12530+
12531+
let recipient_id_nonce = match self.flow.verify_serve_static_invoice_message(
12532+
_context,
12533+
) {
12534+
Ok(nonce) => nonce,
12535+
Err(()) => return
12536+
};
12537+
12538+
let mut pending_events = self.pending_events.lock().unwrap();
12539+
pending_events.push_back((Event::PersistStaticInvoice {
12540+
invoice: _message.invoice,
12541+
recipient_id_nonce,
12542+
invoice_persisted_path: responder
12543+
}, None));
12544+
}
12545+
}
1251812546

1251912547
fn handle_static_invoice_persisted(
1252012548
&self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext,

lightning/src/offers/flow.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ use {
7070
},
7171
crate::onion_message::async_payments::{
7272
HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice,
73+
StaticInvoicePersisted,
7374
},
7475
crate::onion_message::messenger::Responder,
7576
};
@@ -1516,6 +1517,53 @@ where
15161517
Ok((ServeStaticInvoice { invoice: static_invoice }, reply_path_context))
15171518
}
15181519

1520+
/// Verifies an incoming [`ServeStaticInvoice`] onion message from an often-offline recipient who
1521+
/// wants us to serve the [`ServeStaticInvoice::invoice`] to payers on their behalf. If
1522+
/// verification succeeds, the invoice should be persisted keyed by
1523+
/// [`ServeStaticInvoice::recipient_id_nonce`]. The invoice should then be served in response to
1524+
/// incoming [`InvoiceRequest`]s that have a context of [`OffersContext::StaticInvoiceRequested`],
1525+
/// where the [`OffersContext::StaticInvoiceRequested::recipient_id_nonce`] matches the
1526+
/// `recipient_id_nonce` from the original [`ServeStaticInvoice`] message.
1527+
///
1528+
/// Once the invoice is persisted, [`Self::static_invoice_persisted`] must be called to confirm to
1529+
/// the recipient that their offer is ready to receive async payments.
1530+
///
1531+
/// [`ServeStaticInvoice::invoice`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice
1532+
#[cfg(async_payments)]
1533+
pub(crate) fn verify_serve_static_invoice_message(
1534+
&self, context: AsyncPaymentsContext,
1535+
) -> Result<Nonce, ()> {
1536+
let expanded_key = &self.inbound_payment_key;
1537+
match context {
1538+
AsyncPaymentsContext::ServeStaticInvoice {
1539+
recipient_id_nonce,
1540+
nonce,
1541+
hmac,
1542+
path_absolute_expiry,
1543+
} => {
1544+
signer::verify_serve_static_invoice_context(nonce, hmac, expanded_key)?;
1545+
if self.duration_since_epoch() > path_absolute_expiry {
1546+
return Err(());
1547+
}
1548+
1549+
return Ok(recipient_id_nonce);
1550+
},
1551+
_ => return Err(()),
1552+
};
1553+
}
1554+
1555+
/// Indicates that a [`ServeStaticInvoice::invoice`] has been persisted and is ready to be served
1556+
/// to payers on behalf of an often-offline recipient. This method must be called after persisting
1557+
/// a [`StaticInvoice`] to confirm to the recipient that their corresponding [`Offer`] is ready to
1558+
/// receive async payments.
1559+
#[cfg(async_payments)]
1560+
pub(crate) fn serving_static_invoice(&self, responder: Responder) {
1561+
let mut pending_async_payments_messages =
1562+
self.pending_async_payments_messages.lock().unwrap();
1563+
let message = AsyncPaymentsMessage::StaticInvoicePersisted(StaticInvoicePersisted {});
1564+
pending_async_payments_messages.push((message, responder.respond().into_instructions()));
1565+
}
1566+
15191567
/// Handles an incoming [`StaticInvoicePersisted`] onion message from the static invoice server.
15201568
/// Returns a bool indicating whether the async receive offer cache needs to be re-persisted.
15211569
///

lightning/src/offers/signer.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,17 @@ pub(crate) fn hmac_for_serve_static_invoice_context(
656656
Hmac::from_engine(hmac)
657657
}
658658

659+
#[cfg(async_payments)]
660+
pub(crate) fn verify_serve_static_invoice_context(
661+
nonce: Nonce, hmac: Hmac<Sha256>, expanded_key: &ExpandedKey,
662+
) -> Result<(), ()> {
663+
if hmac_for_serve_static_invoice_context(nonce, expanded_key) == hmac {
664+
Ok(())
665+
} else {
666+
Err(())
667+
}
668+
}
669+
659670
#[cfg(async_payments)]
660671
pub(crate) fn hmac_for_static_invoice_persisted_context(
661672
nonce: Nonce, expanded_key: &ExpandedKey,

0 commit comments

Comments
 (0)