Skip to content

Commit d3e3aee

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 51ab441 commit d3e3aee

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
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: 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(
@@ -12931,6 +12938,29 @@ where
1293112938
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
1293212939
_responder: Option<Responder>,
1293312940
) {
12941+
#[cfg(async_payments)]
12942+
{
12943+
let responder = match _responder {
12944+
Some(resp) => resp,
12945+
None => return,
12946+
};
12947+
12948+
let recipient_id_nonce =
12949+
match self.flow.verify_serve_static_invoice_message(&_message, _context) {
12950+
Ok(nonce) => nonce,
12951+
Err(()) => return,
12952+
};
12953+
12954+
let mut pending_events = self.pending_events.lock().unwrap();
12955+
pending_events.push_back((
12956+
Event::PersistStaticInvoice {
12957+
invoice: _message.invoice,
12958+
recipient_id_nonce,
12959+
invoice_persisted_path: responder,
12960+
},
12961+
None,
12962+
));
12963+
}
1293412964
}
1293512965

1293612966
fn handle_static_invoice_persisted(

lightning/src/offers/flow.rs

Lines changed: 51 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
};
@@ -1531,6 +1532,56 @@ where
15311532
Ok((ServeStaticInvoice { invoice: static_invoice }, reply_path_context))
15321533
}
15331534

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

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)