Skip to content

Static invoice server #3628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ad21515
Add config for paths to a static invoice server
valentinewallace Feb 4, 2025
20d85ac
Add static invoice server messages and boilerplate
valentinewallace Feb 6, 2025
694b4f1
Track cached async receive offers in offers::Flow
valentinewallace Apr 10, 2025
bedf7f8
f probably should write the cache not just read it
valentinewallace Jun 5, 2025
df866e1
Check and refresh async receive offer
valentinewallace May 7, 2025
4cdac3b
f fix deadlock
valentinewallace Jun 5, 2025
85eada6
f move consts out of struct
valentinewallace Jun 5, 2025
df113f8
f doh
valentinewallace Jun 5, 2025
be659a0
f fix linting
valentinewallace Jun 5, 2025
2ace16d
Send static invoice in response to offer paths
valentinewallace May 7, 2025
eb770b9
Cache offer on StaticInvoicePersisted onion message
valentinewallace May 8, 2025
839ce2f
Check and refresh served static invoices
valentinewallace May 13, 2025
fcbf98b
f use the right path when updating static invoices
valentinewallace Jun 5, 2025
6fe5f86
Add API to retrieve cached async receive offers
valentinewallace Apr 11, 2025
a96d124
BOLT 12 {Static}Invoices: expose more is_expired methods
valentinewallace Feb 20, 2025
d91b494
Util for blinded paths to configure an async recipient
valentinewallace Feb 3, 2025
51ab441
Send offer paths in response to requests
valentinewallace Feb 7, 2025
d3e3aee
Static invoice server: persist invoices once built
valentinewallace Feb 18, 2025
756977c
Static invoice server: forward static invoices to payers
valentinewallace Feb 20, 2025
9a1354e
Async payments tests: stop hardcoding keysend bytes
valentinewallace Jun 4, 2025
a82e603
Adapt async payments tests for static invoice server
valentinewallace May 27, 2025
8201c52
Test static invoice server protocol
valentinewallace Jun 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion fuzz/src/onion_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use lightning::ln::peer_handler::IgnoringMessageHandler;
use lightning::ln::script::ShutdownScript;
use lightning::offers::invoice::UnsignedBolt12Invoice;
use lightning::onion_message::async_payments::{
AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc,
AsyncPaymentsMessageHandler, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ReleaseHeldHtlc,
ServeStaticInvoice, StaticInvoicePersisted,
};
use lightning::onion_message::messenger::{
CustomOnionMessageHandler, Destination, MessageRouter, MessageSendInstructions,
Expand Down Expand Up @@ -124,6 +125,30 @@ impl OffersMessageHandler for TestOffersMessageHandler {
struct TestAsyncPaymentsMessageHandler {}

impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
fn handle_offer_paths_request(
&self, _message: OfferPathsRequest, _context: AsyncPaymentsContext,
responder: Option<Responder>,
) -> Option<(OfferPaths, ResponseInstruction)> {
let responder = match responder {
Some(resp) => resp,
None => return None,
};
Some((OfferPaths { paths: Vec::new(), paths_absolute_expiry: None }, responder.respond()))
}
fn handle_offer_paths(
&self, _message: OfferPaths, _context: AsyncPaymentsContext, _responder: Option<Responder>,
) -> Option<(ServeStaticInvoice, ResponseInstruction)> {
None
}
fn handle_serve_static_invoice(
&self, _message: ServeStaticInvoice, _context: AsyncPaymentsContext,
_responder: Option<Responder>,
) {
}
fn handle_static_invoice_persisted(
&self, _message: StaticInvoicePersisted, _context: AsyncPaymentsContext,
) {
}
fn handle_held_htlc_available(
&self, _message: HeldHtlcAvailable, _context: AsyncPaymentsContext,
responder: Option<Responder>,
Expand Down
219 changes: 219 additions & 0 deletions lightning/src/blinded_path/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use crate::ln::channelmanager::PaymentId;
use crate::ln::msgs::DecodeError;
use crate::ln::onion_utils;
use crate::offers::nonce::Nonce;
use crate::offers::offer::Offer;
use crate::onion_message::messenger::Responder;
use crate::onion_message::packet::ControlTlvs;
use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
use crate::sign::{EntropySource, NodeSigner, Recipient};
Expand All @@ -34,6 +36,7 @@ use bitcoin::hashes::sha256::Hash as Sha256;

use core::mem;
use core::ops::Deref;
use core::time::Duration;

/// A blinded path to be used for sending or receiving a message, hiding the identity of the
/// recipient.
Expand Down Expand Up @@ -341,6 +344,47 @@ pub enum OffersContext {
/// [`Offer`]: crate::offers::offer::Offer
nonce: Nonce,
},
/// Context used by a [`BlindedMessagePath`] within the [`Offer`] of an async recipient on behalf
/// of whom we are serving [`StaticInvoice`]s.
///
/// This variant is intended to be received when handling an [`InvoiceRequest`] on behalf of said
/// async recipient.
///
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
StaticInvoiceRequested {
/// An identifier for the async recipient for whom we are serving [`StaticInvoice`]s. Used to
/// look up a corresponding [`StaticInvoice`] to return to the payer if the recipient is offline.
///
/// Also useful to rate limit the number of [`InvoiceRequest`]s we will respond to on
/// recipient's behalf.
///
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
recipient_id_nonce: Nonce,

/// A nonce used for authenticating that a received [`InvoiceRequest`] is valid for a preceding
/// [`OfferPaths`] message that we sent.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
nonce: Nonce,

/// Authentication code for the [`InvoiceRequest`].
///
/// Prevents nodes from creating their own blinded path to us and causing us to unintentionally
/// hit our database looking for a [`StaticInvoice`] to return.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
hmac: Hmac<Sha256>,

/// The time as duration since the Unix epoch at which this path expires and messages sent over
/// it should be ignored.
///
/// Useful to timeout async recipients that are no longer supported as clients.
path_absolute_expiry: Duration,
},
/// Context used by a [`BlindedMessagePath`] within a [`Refund`] or as a reply path for an
/// [`InvoiceRequest`].
///
Expand Down Expand Up @@ -404,6 +448,149 @@ pub enum OffersContext {
/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
#[derive(Clone, Debug)]
pub enum AsyncPaymentsContext {
/// Context used by a [`BlindedMessagePath`] that an async recipient is configured with in
/// [`UserConfig::paths_to_static_invoice_server`], provided back to us in corresponding
/// [`OfferPathsRequest`]s.
///
/// [`UserConfig::paths_to_static_invoice_server`]: crate::util::config::UserConfig::paths_to_static_invoice_server
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
OfferPathsRequest {
/// An identifier for the async recipient that is requesting blinded paths to include in their
/// [`Offer::paths`]. This ID is intended to be included in the reply path to our [`OfferPaths`]
/// response, and subsequently rate limit [`ServeStaticInvoice`] messages from recipients.
///
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
recipient_id_nonce: Nonce,
/// Authentication code for the [`OfferPathsRequest`].
///
/// Prevents nodes from requesting offer paths from us without having been previously configured
/// with a [`BlindedMessagePath`] that we generated.
///
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
hmac: Hmac<Sha256>,
/// The time as duration since the Unix epoch at which this path expires and messages sent over
/// it should be ignored.
///
/// Useful to timeout async recipients that are no longer supported as clients.
path_absolute_expiry: core::time::Duration,
},
/// Context used by a reply path to an [`OfferPathsRequest`], provided back to us in corresponding
/// [`OfferPaths`] messages.
///
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
OfferPaths {
/// A nonce used for authenticating that an [`OfferPaths`] message is valid for a preceding
/// [`OfferPathsRequest`].
///
/// [`OfferPathsRequest`]: crate::onion_message::async_payments::OfferPathsRequest
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
nonce: Nonce,
/// Authentication code for the [`OfferPaths`] message.
///
/// Prevents nodes from creating their own blinded path to us and causing us to cache an
/// unintended async receive offer.
///
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
hmac: Hmac<Sha256>,
/// The time as duration since the Unix epoch at which this path expires and messages sent over
/// it should be ignored.
///
/// Used to time out a static invoice server from providing offer paths if the async recipient
/// is no longer configured to accept paths from them.
path_absolute_expiry: core::time::Duration,
},
/// Context used by a reply path to an [`OfferPaths`] message, provided back to us in
/// corresponding [`ServeStaticInvoice`] messages.
///
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
ServeStaticInvoice {
/// An identifier for the async recipient that is requesting that a [`StaticInvoice`] be served
/// on their behalf.
///
/// Useful as a key to retrieve the invoice when payers send an [`InvoiceRequest`] over the
/// paths that we previously created for the recipient's [`Offer::paths`]. Also useful to rate
/// limit the invoices being persisted on behalf of a particular recipient.
///
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
/// [`Offer::paths`]: crate::offers::offer::Offer::paths
recipient_id_nonce: Nonce,
/// A nonce used for authenticating that a [`ServeStaticInvoice`] message is valid for a preceding
/// [`OfferPaths`] message.
///
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
/// [`OfferPaths`]: crate::onion_message::async_payments::OfferPaths
nonce: Nonce,
/// Authentication code for the [`ServeStaticInvoice`] message.
///
/// Prevents nodes from creating their own blinded path to us and causing us to persist an
/// unintended [`StaticInvoice`].
///
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
hmac: Hmac<Sha256>,
/// The time as duration since the Unix epoch at which this path expires and messages sent over
/// it should be ignored.
///
/// Useful to timeout async recipients that are no longer supported as clients.
path_absolute_expiry: core::time::Duration,
},
/// Context used by a reply path to a [`ServeStaticInvoice`] message, provided back to us in
/// corresponding [`StaticInvoicePersisted`] messages.
///
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
/// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted
StaticInvoicePersisted {
/// The offer corresponding to the [`StaticInvoice`] that has been persisted. This invoice is
/// now ready to be provided by the static invoice server in response to [`InvoiceRequest`]s.
///
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
offer: Offer,
/// A [`Nonce`] useful for updating the [`StaticInvoice`] that corresponds to the
/// [`AsyncPaymentsContext::StaticInvoicePersisted::offer`], since the offer may be much longer
/// lived than the invoice.
///
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
offer_nonce: Nonce,
/// Useful to determine how far an offer is into its lifespan, to decide whether the offer is
/// expiring soon and we should start building a new one.
offer_created_at: core::time::Duration,
/// A [`Responder`] useful for updating the [`StaticInvoice`] that corresponds to the
/// [`AsyncPaymentsContext::StaticInvoicePersisted::offer`], since the offer may be much longer
/// lived than the invoice.
///
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
update_static_invoice_path: Responder,
/// The time as duration since the Unix epoch at which the [`StaticInvoice`] expires, used to track
/// when we need to generate and persist a new invoice with the static invoice server.
///
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
static_invoice_absolute_expiry: core::time::Duration,
/// A nonce used for authenticating that a [`StaticInvoicePersisted`] message is valid for a
/// preceding [`ServeStaticInvoice`] message.
///
/// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted
/// [`ServeStaticInvoice`]: crate::onion_message::async_payments::ServeStaticInvoice
nonce: Nonce,
/// Authentication code for the [`StaticInvoicePersisted`] message.
///
/// Prevents nodes from creating their own blinded path to us and causing us to cache an
/// unintended async receive offer.
///
/// [`StaticInvoicePersisted`]: crate::onion_message::async_payments::StaticInvoicePersisted
hmac: Hmac<Sha256>,
/// The time as duration since the Unix epoch at which this path expires and messages sent over
/// it should be ignored.
///
/// Prevents a static invoice server from causing an async recipient to cache an old offer if
/// the recipient is no longer configured to use that server.
path_absolute_expiry: core::time::Duration,
},
/// Context contained within the reply [`BlindedMessagePath`] we put in outbound
/// [`HeldHtlcAvailable`] messages, provided back to us in corresponding [`ReleaseHeldHtlc`]
/// messages.
Expand Down Expand Up @@ -473,6 +660,12 @@ impl_writeable_tlv_based_enum!(OffersContext,
(1, nonce, required),
(2, hmac, required)
},
(3, StaticInvoiceRequested) => {
(0, recipient_id_nonce, required),
(2, nonce, required),
(4, hmac, required),
(6, path_absolute_expiry, required),
},
);

impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
Expand All @@ -486,6 +679,32 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
(2, hmac, required),
(4, path_absolute_expiry, required),
},
(2, OfferPaths) => {
(0, nonce, required),
(2, hmac, required),
(4, path_absolute_expiry, required),
},
(3, StaticInvoicePersisted) => {
(0, offer, required),
(2, offer_nonce, required),
(4, offer_created_at, required),
(6, update_static_invoice_path, required),
(8, static_invoice_absolute_expiry, required),
(10, nonce, required),
(12, hmac, required),
(14, path_absolute_expiry, required),
},
(4, OfferPathsRequest) => {
(0, recipient_id_nonce, required),
(2, hmac, required),
(4, path_absolute_expiry, required),
},
(5, ServeStaticInvoice) => {
(0, recipient_id_nonce, required),
(2, nonce, required),
(4, hmac, required),
(6, path_absolute_expiry, required),
},
);

/// Contains a simple nonce for use in a blinded path's context.
Expand Down
Loading
Loading