Skip to content
New issue

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

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

Already on GitHub? # to your account

Client-side of static invoice server #3618

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
26 changes: 25 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 @@ -121,6 +122,29 @@ 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() }, 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,
) {
}
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
70 changes: 70 additions & 0 deletions lightning/src/blinded_path/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ 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::packet::ControlTlvs;
use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph};
use crate::sign::{EntropySource, NodeSigner, Recipient};
Expand Down Expand Up @@ -393,6 +394,64 @@ pub enum OffersContext {
/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
#[derive(Clone, Debug)]
pub enum AsyncPaymentsContext {
/// 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 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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand why this has to be here explicitly? Doesn't the StaticInvoice contain the Offer('s fields) in it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As implemented, the async recipient will never cache the StaticInvoice themselves. The recipient creates the invoice, sends it to the server, and includes the Offer in the reply path to cache once the invoice is confirmed as persisted. I think the google doc is out of date here, sorry.

This made sense to me while implementing because we don't want to cache the offer until it's confirmed as persisted by the server and I couldn't think of a reason why the client would need to have to the static invoice.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, I misread which context this was (don't we need a context for sending the static invoice to the server? I guess that's a separate PR), this makes sense.

/// 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 @@ -475,6 +534,17 @@ 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, nonce, required),
(4, hmac, required),
(6, path_absolute_expiry, required),
},
);

/// Contains a simple nonce for use in a blinded path's context.
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/async_signer_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

let closing_node = if remote_commitment { &nodes[1] } else { &nodes[0] };
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/ln/chanmon_update_fail_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2591,7 +2591,7 @@ fn test_temporary_error_during_shutdown() {

let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config), Some(config)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);

let (_, _, channel_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
Expand Down Expand Up @@ -2762,7 +2762,7 @@ fn do_test_outbound_reload_without_init_mon(use_0conf: bool) {
chan_config.manually_accept_inbound_channels = true;
chan_config.channel_handshake_limits.trust_own_funding_0conf = true;

let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config), Some(chan_config)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config.clone()), Some(chan_config)]);
let nodes_0_deserialized;

let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
Expand Down Expand Up @@ -2853,7 +2853,7 @@ fn do_test_inbound_reload_without_init_mon(use_0conf: bool, lock_commitment: boo
chan_config.manually_accept_inbound_channels = true;
chan_config.channel_handshake_limits.trust_own_funding_0conf = true;

let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config), Some(chan_config)]);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(chan_config.clone()), Some(chan_config)]);
let nodes_1_deserialized;

let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
Expand Down
Loading
Loading