Skip to content

Commit a52354b

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 bd9add5 commit a52354b

File tree

4 files changed

+133
-0
lines changed

4 files changed

+133
-0
lines changed

lightning/src/events/mod.rs

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

15771606
impl Writeable for Event {
@@ -1996,6 +2025,12 @@ impl Writeable for Event {
19962025
(8, former_temporary_channel_id, required),
19972026
});
19982027
},
2028+
#[cfg(async_payments)]
2029+
&Event::PersistStaticInvoice { .. } => {
2030+
45u8.write(writer)?;
2031+
// No need to write these events because we can just restart the static invoice negotiation
2032+
// on startup.
2033+
},
19992034
// Note that, going forward, all new events must only write data inside of
20002035
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
20012036
// data via `write_tlv_fields`.
@@ -2560,6 +2595,9 @@ impl MaybeReadable for Event {
25602595
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
25612596
}))
25622597
},
2598+
// Note that we do not write a length-prefixed TLV for PersistStaticInvoice events.
2599+
#[cfg(async_payments)]
2600+
45u8 => Ok(None),
25632601
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
25642602
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
25652603
// reads.

lightning/src/ln/channelmanager.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5121,6 +5121,13 @@ where
51215121
}
51225122
}
51235123

5124+
/// Should be called after handling an [`Event::PersistStaticInvoice`], where the `Responder`
5125+
/// comes from [`Event::PersistStaticInvoice::invoice_persisted_path`].
5126+
#[cfg(async_payments)]
5127+
pub fn static_invoice_persisted(&self, invoice_persisted_path: Responder) {
5128+
self.flow.serving_static_invoice(invoice_persisted_path);
5129+
}
5130+
51245131
#[rustfmt::skip]
51255132
#[cfg(async_payments)]
51265133
fn initiate_async_payment(
@@ -12938,6 +12945,29 @@ where
1293812945
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
1293912946
_responder: Option<Responder>,
1294012947
) {
12948+
#[cfg(async_payments)]
12949+
{
12950+
let responder = match _responder {
12951+
Some(resp) => resp,
12952+
None => return,
12953+
};
12954+
12955+
let recipient_id_nonce =
12956+
match self.flow.verify_serve_static_invoice_message(&_message, _context) {
12957+
Ok(nonce) => nonce,
12958+
Err(()) => return,
12959+
};
12960+
12961+
let mut pending_events = self.pending_events.lock().unwrap();
12962+
pending_events.push_back((
12963+
Event::PersistStaticInvoice {
12964+
invoice: _message.invoice,
12965+
recipient_id_nonce,
12966+
invoice_persisted_path: responder,
12967+
},
12968+
None,
12969+
));
12970+
}
1294112971
}
1294212972

1294312973
fn handle_static_invoice_persisted(

lightning/src/offers/flow.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ use {
7171
},
7272
crate::onion_message::async_payments::{
7373
HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice,
74+
StaticInvoicePersisted,
7475
},
7576
crate::onion_message::messenger::Responder,
7677
};
@@ -1551,6 +1552,59 @@ where
15511552
Ok((ServeStaticInvoice { invoice, forward_invoice_request_path }, reply_path_context))
15521553
}
15531554

1555+
/// Verifies an incoming [`ServeStaticInvoice`] onion message from an often-offline recipient who
1556+
/// wants us as a static invoice server to serve the [`ServeStaticInvoice::invoice`] to payers on
1557+
/// their behalf.
1558+
///
1559+
/// If verification succeeds, the provided [`ServeStaticInvoice::invoice`] should be persisted
1560+
/// keyed by [`ServeStaticInvoice::recipient_id_nonce`]. The invoice should then be served in
1561+
/// response to incoming [`InvoiceRequest`]s that have a context of
1562+
/// [`OffersContext::StaticInvoiceRequested`], where the
1563+
/// [`OffersContext::StaticInvoiceRequested::recipient_id_nonce`] matches the `recipient_id_nonce`
1564+
/// from the original [`ServeStaticInvoice`] message.
1565+
///
1566+
/// Once the invoice is persisted, [`Self::static_invoice_persisted`] must be called to confirm to
1567+
/// the recipient that their offer is ready to receive async payments.
1568+
///
1569+
/// [`ServeStaticInvoice::invoice`]: crate::onion_message::async_payments::ServeStaticInvoice::invoice
1570+
#[cfg(async_payments)]
1571+
pub(crate) fn verify_serve_static_invoice_message(
1572+
&self, message: &ServeStaticInvoice, context: AsyncPaymentsContext,
1573+
) -> Result<Nonce, ()> {
1574+
if message.invoice.is_expired_no_std(self.duration_since_epoch()) {
1575+
return Err(());
1576+
}
1577+
let expanded_key = &self.inbound_payment_key;
1578+
match context {
1579+
AsyncPaymentsContext::ServeStaticInvoice {
1580+
recipient_id_nonce,
1581+
nonce,
1582+
hmac,
1583+
path_absolute_expiry,
1584+
} => {
1585+
signer::verify_serve_static_invoice_context(nonce, hmac, expanded_key)?;
1586+
if self.duration_since_epoch() > path_absolute_expiry {
1587+
return Err(());
1588+
}
1589+
1590+
return Ok(recipient_id_nonce);
1591+
},
1592+
_ => return Err(()),
1593+
};
1594+
}
1595+
1596+
/// Indicates that a [`ServeStaticInvoice::invoice`] has been persisted and is ready to be served
1597+
/// to payers on behalf of an often-offline recipient. This method must be called after persisting
1598+
/// a [`StaticInvoice`] to confirm to the recipient that their corresponding [`Offer`] is ready to
1599+
/// receive async payments.
1600+
#[cfg(async_payments)]
1601+
pub(crate) fn serving_static_invoice(&self, responder: Responder) {
1602+
let mut pending_async_payments_messages =
1603+
self.pending_async_payments_messages.lock().unwrap();
1604+
let message = AsyncPaymentsMessage::StaticInvoicePersisted(StaticInvoicePersisted {});
1605+
pending_async_payments_messages.push((message, responder.respond().into_instructions()));
1606+
}
1607+
15541608
/// Handles an incoming [`StaticInvoicePersisted`] onion message from the static invoice server.
15551609
/// Returns a bool indicating whether the async receive offer cache needs to be re-persisted.
15561610
///

lightning/src/offers/signer.rs

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

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

0 commit comments

Comments
 (0)